mattrguerra/meridian-ops-boardmainPrivate on GitHub, published here in full

work-key.ts

Language TypeScript2.5 KB64 lines

apps/dashboard/src/work-key.ts

  1. /**
  2. * The key that names a piece of work on this board: `order:MD2639582`,
  3. * `task:41837`.
  4. *
  5. * Its own module because two features share it: `board:notes`, whose KV map is
  6. * keyed on exactly this string, and the private list, where a line may carry
  7. * an optional reference to the order or task it is about. The point of storing
  8. * that reference in this spelling is that it is the same identifier the note
  9. * map and the work rows already use (`data-note-key` in surfaces.ts). Two
  10. * copies of the regex would make that claim true today and silently false the
  11. * first time somebody widened one of them.
  12. *
  13. * Runtime-neutral: the Worker validates form input with it and the
  14. * dashboard renders with it, so it may not reach for anything Node-only.
  15. *
  16. * It validates a shape, not an existence. An order number that matches this
  17. * regex may be for an order that does not exist, or that belongs to somebody
  18. * else. It lives in Plinth and nothing here can check it. Callers that need the
  19. * work to be real must look it up; callers that only need the string to be
  20. * renderable and storable use this.
  21. */
  22. /**
  23. * The upper bound is 40 characters after the prefix, which is far more than an
  24. * Plinth number (`MD2639582`, nine) or a Bitrix task id needs. It is a cap against
  25. * an unbounded key, not a claim about the format. A source that starts issuing
  26. * longer ids should widen this deliberately rather than have its rows silently
  27. * refused.
  28. */
  29. export const WORK_KEY_RE = /^(order|task):[A-Za-z0-9_-]{1,40}$/
  30. export type WorkKind = 'order' | 'task'
  31. export interface WorkKey {
  32. kind: WorkKind
  33. /** The bare identifier, with the prefix removed. Safe to render. */
  34. id: string
  35. /** The whole key, as stored. */
  36. key: string
  37. }
  38. /**
  39. * Parse a key, or null if it is not one.
  40. *
  41. * Null rather than a throw, because every caller here is handling something a
  42. * person typed into a form and the answer to a malformed one is a sentence on
  43. * the page, never a 500.
  44. */
  45. export function parseWorkKey(raw: string | null | undefined): WorkKey | null {
  46. const key = (raw ?? '').trim()
  47. if (!WORK_KEY_RE.test(key)) return null
  48. const at = key.indexOf(':')
  49. return { kind: key.slice(0, at) as WorkKind, id: key.slice(at + 1), key }
  50. }
  51. /**
  52. * What a key is called on the page: `Order MD2639582`, `Task 41837`.
  53. *
  54. * Not escaped here: the caller escapes, because the caller knows whether it is
  55. * building an attribute or a text node.
  56. */
  57. export function workKeyLabel(k: WorkKey): string {
  58. return k.kind === 'order' ? `Order ${k.id}` : `Task ${k.id}`
  59. }