| name | twake-javascript-conventions |
| description | Use when writing or reviewing JavaScript/TypeScript in Twake/Cozy projects. Enforces async/await over Promise chains, null over undefined, error handling contract (null / typed result / throw), function declaration style, strict equality, date handling via Intl/date-fns (never moment.js), AppLinker for redirections (never window.location), and business-logic-only comments. |
JavaScript Conventions (Twake / Cozy)
Apply these rules when writing or modifying JavaScript or TypeScript in any Twake or Cozy project.
Syntax
Private members
# prefix. Never _ convention. # has native runtime enforcement.
class ConnectionPool {
#connections = [];
}
Equality
Always === / !==. Never == / !=.
Null-or-undefined check: value === null || value === undefined. Never rely on loose equality for this.
Function declaration style
- Top-level standalone functions:
function keyword (hoists, readable top-down).
- Callbacks, closures, inline arguments:
const + arrow function.
function processOrder(order) {
const valid = order.items.filter((item) => item.isAvailable);
return valid.map((item) => item.price * item.quantity);
}
Error handling
Always return a meaningful value. Pick the return type by how much information the caller needs:
| Return type | When to use | Example |
|---|
null | Value is non-existent | findUser(id): User | null |
Any meaningful type (string, boolean, number, user-defined class, union, …) | Outcome is directly expressible without wrapping | hasAttribute(name): boolean, getLabel(): string, parse(): Config |
{ ok: true, value: T } | { ok: false, error: string } | Complex outcome where the caller needs data or a reason | getDBRowsWith(col) |
throw | Internal invariant violated or situation is unprocessable | division by zero, corrupt state |
null — value does not exist:
function findUser(id) {
const user = db.get(id);
return user ?? null;
}
Any meaningful type — outcome is directly expressible:
Return whatever type naturally describes the result: a string, boolean, number, a user-defined class, a union, an array. Reach for a discriminated result only when the caller genuinely needs to distinguish success from failure.
function hasAttribute(name) {
return attributes.includes(name);
}
function getLabel(key) {
return LABELS[key] ?? null;
}
function parseConfig(raw) {
return new Config(raw);
}
Discriminated result — caller needs data and/or a reason:
async function getDBRowsWith(col) {
try {
const r = await db.query({ col });
return { ok: true, value: r };
} catch (e) {
return { ok: false, error: e.message };
}
}
const result = await getDBRowsWith('email');
if (!result.ok) {
logger.warn(result.error);
return;
}
process(result.value);
Libraries like neverthrow or an Either type are valid project-wide alternatives — they make the error path structurally impossible to ignore. Adopt consistently across a project, not per-file.
throw — unacceptable or unprocessable situation:
function divide(a, b) {
if (b === 0) throw new Error('Division by zero — caller invariant violated');
return a / b;
}
Preserve cause when rethrowing:
try {
await syncData();
} catch (err) {
throw new Error('Sync failed', { cause: err });
}
Async code
Use async / await, not .then() chains, except when composing many independent promises with Promise.all.
const user = await fetchUser(id)
const posts = await fetchPosts(user.id)
fetchUser(id).then(user => fetchPosts(user.id)).then(...)
Fire-and-forget must have .catch():
sendAnalyticsEvent(event).catch((e) =>
logger.warn({ err: e }, 'Analytics event failed'),
);
Null vs undefined
Return null when a value is intentionally absent. Never return undefined on purpose — undefined should only appear for unset variables or missing object keys.
const findUser = (id) => users.find(u => u.id === id) ?? null
const findUser = (id) => users.find(u => u.id === id)
## Dates
Priority order for date handling:
1. **Native [`Intl` API](https:
2. **[`date-fns`](https:
3. **Never `moment.js`** — it is deprecated and heavy. Remove it when you touch code that uses it.
```js
// ✅ Good
new Intl.DateTimeFormat('fr-FR', { dateStyle: 'long' }).format(date)
import { addDays } from 'date-fns'
// ❌ Forbidden
import moment from 'moment'
Redirections
Use AppLinker for in-app and cross-app navigation. Never rely on window.location, window.open, or manual URL assembly for Cozy app links — those break sharing, deep-linking, and the mobile native shell.
<AppLinker app={{ slug: 'drive' }} href="/#/folder/123">...</AppLinker>
window.location.href = 'https://myapp.mycozy.cloud/drive'
window.location is acceptable only for truly external URLs that are not Cozy apps.
Comments
Only comment business-logic complexity — the why, not the what. Do not comment obvious code.
counter += 1
const debouncedSave = debounce(save, 50)
If the comment describes what the code does, delete it and improve the naming instead.
Forbidden
console.log in committed code — use a structured logger.
== / !=
- Empty
catch blocks.
.then().catch() chains — use async/await + try/catch.
moment.js — use Intl or date-fns instead.
window.location for Cozy app navigation — use AppLinker.