work-key.ts
Language TypeScript2.5 KB64 lines
/*** The key that names a piece of work on this board: `order:MD2639582`,* `task:41837`.** Its own module because two features share it: `board:notes`, whose KV map is* keyed on exactly this string, and the private list, where a line may carry* an optional reference to the order or task it is about. The point of storing* that reference in this spelling is that it is the same identifier the note* map and the work rows already use (`data-note-key` in surfaces.ts). Two* copies of the regex would make that claim true today and silently false the* first time somebody widened one of them.** Runtime-neutral: the Worker validates form input with it and the* dashboard renders with it, so it may not reach for anything Node-only.** It validates a shape, not an existence. An order number that matches this* regex may be for an order that does not exist, or that belongs to somebody* else. It lives in Plinth and nothing here can check it. Callers that need the* work to be real must look it up; callers that only need the string to be* renderable and storable use this.*//*** The upper bound is 40 characters after the prefix, which is far more than an* Plinth number (`MD2639582`, nine) or a Bitrix task id needs. It is a cap against* an unbounded key, not a claim about the format. A source that starts issuing* longer ids should widen this deliberately rather than have its rows silently* refused.*/export const WORK_KEY_RE = /^(order|task):[A-Za-z0-9_-]{1,40}$/export type WorkKind = 'order' | 'task'export interface WorkKey {kind: WorkKind/** The bare identifier, with the prefix removed. Safe to render. */id: string/** The whole key, as stored. */key: string}/*** Parse a key, or null if it is not one.** Null rather than a throw, because every caller here is handling something a* person typed into a form and the answer to a malformed one is a sentence on* the page, never a 500.*/export function parseWorkKey(raw: string | null | undefined): WorkKey | null {const key = (raw ?? '').trim()if (!WORK_KEY_RE.test(key)) return nullconst at = key.indexOf(':')return { kind: key.slice(0, at) as WorkKind, id: key.slice(at + 1), key }}/*** What a key is called on the page: `Order MD2639582`, `Task 41837`.** Not escaped here: the caller escapes, because the caller knows whether it is* building an attribute or a text node.*/export function workKeyLabel(k: WorkKey): string {return k.kind === 'order' ? `Order ${k.id}` : `Task ${k.id}`}