@entifix/core 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/command-palette/command-matching.d.ts +1 -1
  2. package/dist/command-palette/command-matching.js +1 -1
  3. package/dist/command-palette/command-matching.js.map +1 -1
  4. package/dist/command-palette/command-source.d.ts +3 -3
  5. package/dist/command-palette/command-source.js.map +1 -1
  6. package/dist/entity-definition/decorators/use-case/use-case.d.ts +1 -1
  7. package/dist/entity-definition/decorators/use-case/use-case.js +1 -1
  8. package/dist/entity-definition/decorators/use-case/use-case.js.map +1 -1
  9. package/dist/entity-definition/describe/describe-child-columns.d.ts +1 -1
  10. package/dist/entity-definition/describe/describe-child-columns.js +1 -1
  11. package/dist/entity-definition/describe/describe-child-columns.js.map +1 -1
  12. package/dist/entity-definition/describe/describe-entity-use-cases.d.ts +2 -1
  13. package/dist/entity-definition/describe/describe-entity-use-cases.d.ts.map +1 -1
  14. package/dist/entity-definition/describe/describe-entity-use-cases.js +2 -1
  15. package/dist/entity-definition/describe/describe-entity-use-cases.js.map +1 -1
  16. package/dist/entity-definition/helpers/entity-definition-helpers.d.ts +2 -2
  17. package/dist/entity-definition/helpers/entity-definition-helpers.js +2 -2
  18. package/dist/entity-definition/helpers/entity-definition-helpers.js.map +1 -1
  19. package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.d.ts +4 -4
  20. package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.js.map +1 -1
  21. package/dist/entity-definition/meta-entities/meta-use-case/meta-use-case.d.ts +1 -1
  22. package/dist/entity-definition/meta-entities/meta-use-case/meta-use-case.js.map +1 -1
  23. package/dist/entity-definition/reconstruct/clone-entity-draft.d.ts +1 -1
  24. package/dist/entity-definition/reconstruct/clone-entity-draft.js +1 -1
  25. package/dist/entity-definition/reconstruct/clone-entity-draft.js.map +1 -1
  26. package/dist/entity-definition/reconstruct/reconstruct-entity.js +1 -1
  27. package/dist/entity-definition/reconstruct/reconstruct-entity.js.map +1 -1
  28. package/dist/envelope/types.d.ts +5 -7
  29. package/dist/envelope/types.d.ts.map +1 -1
  30. package/dist/envelope/types.js.map +1 -1
  31. package/dist/http/session-cookies.d.ts +2 -3
  32. package/dist/http/session-cookies.d.ts.map +1 -1
  33. package/dist/http/session-cookies.js +2 -3
  34. package/dist/http/session-cookies.js.map +1 -1
  35. package/dist/i18n/locales.js +2 -2
  36. package/dist/i18n/locales.js.map +1 -1
  37. package/dist/types/BulkOutcome.d.ts +1 -2
  38. package/dist/types/BulkOutcome.d.ts.map +1 -1
  39. package/dist/types/BulkOutcome.js.map +1 -1
  40. package/dist/types/EntityAction.d.ts +1 -2
  41. package/dist/types/EntityAction.d.ts.map +1 -1
  42. package/dist/types/EntityAction.js +1 -2
  43. package/dist/types/EntityAction.js.map +1 -1
  44. package/dist/types/EntityDraft.d.ts +2 -3
  45. package/dist/types/EntityDraft.d.ts.map +1 -1
  46. package/dist/types/EntityDraft.js.map +1 -1
  47. package/dist/types/EntityRowDraft.d.ts +2 -3
  48. package/dist/types/EntityRowDraft.d.ts.map +1 -1
  49. package/dist/types/EntityRowDraft.js +1 -1
  50. package/dist/types/EntityRowDraft.js.map +1 -1
  51. package/dist/types/EntitySelection.d.ts +2 -3
  52. package/dist/types/EntitySelection.d.ts.map +1 -1
  53. package/dist/types/EntitySelection.js.map +1 -1
  54. package/dist/wizard/wizard-definition.d.ts +5 -8
  55. package/dist/wizard/wizard-definition.d.ts.map +1 -1
  56. package/dist/wizard/wizard-definition.js +3 -5
  57. package/dist/wizard/wizard-definition.js.map +1 -1
  58. package/dist/wizard/wizard-state.d.ts +4 -7
  59. package/dist/wizard/wizard-state.d.ts.map +1 -1
  60. package/dist/wizard/wizard-state.js.map +1 -1
  61. package/package.json +1 -1
@@ -19,7 +19,7 @@ export declare function foldForMatch(text: string): string;
19
19
  *
20
20
  * Subsequence rather than substring, so `prbr` finds "Producto Bruto" and `numa`
21
21
  * finds "Nueva marca" — the thing that makes a palette feel fast. Deliberately
22
- * **not** scored: ADR 0040 fixed group order as the ranking, and a score inside
22
+ * **not** scored: group order is the ranking, and a score inside
23
23
  * a group would reshuffle it on every keystroke for no gain a person can learn.
24
24
  *
25
25
  * An empty term matches everything, which is what makes an unfiltered palette
@@ -20,7 +20,7 @@ import { EntifixLogicError } from '../base-entities/entifix-error/index.js';
20
20
  *
21
21
  * Subsequence rather than substring, so `prbr` finds "Producto Bruto" and `numa`
22
22
  * finds "Nueva marca" — the thing that makes a palette feel fast. Deliberately
23
- * **not** scored: ADR 0040 fixed group order as the ranking, and a score inside
23
+ * **not** scored: group order is the ranking, and a score inside
24
24
  * a group would reshuffle it on every keystroke for no gain a person can learn.
25
25
  *
26
26
  * An empty term matches everything, which is what makes an unfiltered palette
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/command-palette/command-matching.ts"],"sourcesContent":["import { EntifixLogicError } from '../base-entities/entifix-error/index.js';\nimport type { CommandOption } from './command-source.js';\n\n/**\n * Normalize a string for matching: case-folded, and with accents removed.\n *\n * ⚠️ The accent fold is correctness, not polish. The fleet ships Spanish as its\n * default locale, and without it `categoria` does not match `Categoría`, `marron`\n * does not match `Marrón`, and `articulo` does not match `Artículo` — so the\n * palette silently finds nothing for the most natural way to type most of its\n * own copy. Nobody reaches for the accented key in a search box.\n *\n * NFD splits a precomposed letter into its base plus a combining mark, and the\n * `\\p{Diacritic}` class is what then removes only the mark. `ñ` decomposes too,\n * so `nino` matches `Niño` — which is what a person typing quickly wants, even\n * though ñ is a letter of its own in Spanish rather than an accented n.\n */\nexport function foldForMatch(text: string): string {\n return text\n .normalize('NFD')\n .replace(/\\p{Diacritic}/gu, '')\n .toLocaleLowerCase();\n}\n\n/**\n * Does `text` contain every character of `term`, in order?\n *\n * Subsequence rather than substring, so `prbr` finds \"Producto Bruto\" and `numa`\n * finds \"Nueva marca\" — the thing that makes a palette feel fast. Deliberately\n * **not** scored: ADR 0040 fixed group order as the ranking, and a score inside\n * a group would reshuffle it on every keystroke for no gain a person can learn.\n *\n * An empty term matches everything, which is what makes an unfiltered palette\n * the same code path as a filtered one.\n */\nexport function subsequenceMatch(term: string, text: string): boolean {\n const needle = foldForMatch(term);\n if (needle === '') return true;\n\n const haystack = foldForMatch(text);\n let cursor = 0;\n for (const character of haystack) {\n if (character === needle[cursor]) cursor += 1;\n if (cursor === needle.length) return true;\n }\n return false;\n}\n\n/**\n * Does this option answer to `term`, by its label or by any of its keywords?\n *\n * The keywords are what let a Spanish command match an English word someone\n * typed — \"Nuevo producto\" answering to `new` — without translating the label.\n */\nexport function matchesCommand(term: string, option: CommandOption): boolean {\n if (subsequenceMatch(term, option.label)) return true;\n return (option.keywords ?? []).some(keyword =>\n subsequenceMatch(term, keyword),\n );\n}\n\n/**\n * Split a catalog-resolved keyword list into terms.\n *\n * `MetaUseCase.keywordsKey` is a catalog **key** rather than a `string[]` so the\n * terms are per-locale; what the catalog holds is therefore one string, and this\n * is the convention for reading it. Commas, because a keyword may itself be two\n * words (\"cerrar sesión\") and splitting on whitespace would break that into\n * terms that match nothing on their own.\n *\n * Empty entries are dropped rather than kept as `''`, which would match every\n * option through {@link subsequenceMatch}'s empty-term rule.\n */\nexport function parseKeywords(value: string | undefined): string[] {\n if (value === undefined) return [];\n return value\n .split(',')\n .map(keyword => keyword.trim())\n .filter(keyword => keyword !== '');\n}\n\n/** Which sources a typed term is asking for. */\nexport type CommandScope = 'all' | 'commands' | 'records';\n\n/** A term, split into the scope its prefix asked for and the rest. */\nexport interface ParsedCommandTerm {\n readonly scope: CommandScope;\n readonly term: string;\n}\n\n/**\n * `>` narrows to commands, `#` to records, anything else searches both.\n *\n * A grammar for *entry* rather than for depth: descending into a sub-picker is a\n * page stack, not a third prefix, because a prefix a person has to remember is\n * worse than a list they can see. Two characters only, and both are the ones\n * VS Code and GitHub already trained people on.\n *\n * The prefix is stripped and the remainder trimmed at the front only — a\n * trailing space is something the person is still typing, and eating it would\n * make the list jump between keystrokes.\n */\nexport function parseCommandTerm(raw: string): ParsedCommandTerm {\n if (raw.startsWith('>')) {\n return { scope: 'commands', term: raw.slice(1).trimStart() };\n }\n if (raw.startsWith('#')) {\n return { scope: 'records', term: raw.slice(1).trimStart() };\n }\n return { scope: 'all', term: raw };\n}\n\n/**\n * The one thing selecting an option does.\n *\n * An option declaring none is a line that looks selectable and does nothing;\n * one declaring two is a line whose behaviour depends on the order a renderer\n * happens to check its members in. Both are silent, so this throws — at the\n * first render of the palette rather than on the click, which is the posture\n * `surfaceFor` and `assertSearchable` already take for the same reason.\n */\nexport function commandEffectOf(\n option: CommandOption,\n): 'href' | 'run' | 'push' {\n const declared = (['href', 'run', 'push'] as const).filter(\n effect => option[effect] !== undefined,\n );\n\n if (declared.length !== 1) {\n throw new EntifixLogicError(\n `The command \"${option.id}\" declares ${declared.length} effects (${declared.join(', ') || 'none'}), and must declare exactly one. ` +\n 'Give it an `href` to navigate, a `run` to invoke, or a `push` to open another page.',\n undefined,\n { id: option.id, declared },\n );\n }\n\n return declared[0];\n}\n"],"names":["EntifixLogicError","foldForMatch","text","normalize","replace","toLocaleLowerCase","subsequenceMatch","term","needle","haystack","cursor","character","length","matchesCommand","option","label","keywords","some","keyword","parseKeywords","value","undefined","split","map","trim","filter","parseCommandTerm","raw","startsWith","scope","slice","trimStart","commandEffectOf","declared","effect","id","join"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,0CAA0C;AAG5E;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,aAAaC,IAAY;IACvC,OAAOA,KACJC,SAAS,CAAC,OACVC,OAAO,CAAC,q6FAAmB,IAC3BC,iBAAiB;AACtB;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,iBAAiBC,IAAY,EAAEL,IAAY;IACzD,MAAMM,SAASP,aAAaM;IAC5B,IAAIC,WAAW,IAAI,OAAO;IAE1B,MAAMC,WAAWR,aAAaC;IAC9B,IAAIQ,SAAS;IACb,KAAK,MAAMC,aAAaF,SAAU;QAChC,IAAIE,cAAcH,MAAM,CAACE,OAAO,EAAEA,UAAU;QAC5C,IAAIA,WAAWF,OAAOI,MAAM,EAAE,OAAO;IACvC;IACA,OAAO;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASC,eAAeN,IAAY,EAAEO,MAAqB;QAExDA;IADR,IAAIR,iBAAiBC,MAAMO,OAAOC,KAAK,GAAG,OAAO;IACjD,OAAO,EAACD,mBAAAA,OAAOE,QAAQ,YAAfF,mBAAmB,EAAE,EAAEG,IAAI,CAACC,CAAAA,UAClCZ,iBAAiBC,MAAMW;AAE3B;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,cAAcC,KAAyB;IACrD,IAAIA,UAAUC,WAAW,OAAO,EAAE;IAClC,OAAOD,MACJE,KAAK,CAAC,KACNC,GAAG,CAACL,CAAAA,UAAWA,QAAQM,IAAI,IAC3BC,MAAM,CAACP,CAAAA,UAAWA,YAAY;AACnC;AAWA;;;;;;;;;;;CAWC,GACD,OAAO,SAASQ,iBAAiBC,GAAW;IAC1C,IAAIA,IAAIC,UAAU,CAAC,MAAM;QACvB,OAAO;YAAEC,OAAO;YAAYtB,MAAMoB,IAAIG,KAAK,CAAC,GAAGC,SAAS;QAAG;IAC7D;IACA,IAAIJ,IAAIC,UAAU,CAAC,MAAM;QACvB,OAAO;YAAEC,OAAO;YAAWtB,MAAMoB,IAAIG,KAAK,CAAC,GAAGC,SAAS;QAAG;IAC5D;IACA,OAAO;QAAEF,OAAO;QAAOtB,MAAMoB;IAAI;AACnC;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASK,gBACdlB,MAAqB;IAErB,MAAMmB,WAAW,AAAC;QAAC;QAAQ;QAAO;KAAO,CAAWR,MAAM,CACxDS,CAAAA,SAAUpB,MAAM,CAACoB,OAAO,KAAKb;IAG/B,IAAIY,SAASrB,MAAM,KAAK,GAAG;QACzB,MAAM,IAAIZ,kBACR,CAAC,aAAa,EAAEc,OAAOqB,EAAE,CAAC,WAAW,EAAEF,SAASrB,MAAM,CAAC,UAAU,EAAEqB,SAASG,IAAI,CAAC,SAAS,OAAO,iCAAiC,CAAC,GACjI,uFACFf,WACA;YAAEc,IAAIrB,OAAOqB,EAAE;YAAEF;QAAS;IAE9B;IAEA,OAAOA,QAAQ,CAAC,EAAE;AACpB"}
1
+ {"version":3,"sources":["../../src/command-palette/command-matching.ts"],"sourcesContent":["import { EntifixLogicError } from '../base-entities/entifix-error/index.js';\nimport type { CommandOption } from './command-source.js';\n\n/**\n * Normalize a string for matching: case-folded, and with accents removed.\n *\n * ⚠️ The accent fold is correctness, not polish. The fleet ships Spanish as its\n * default locale, and without it `categoria` does not match `Categoría`, `marron`\n * does not match `Marrón`, and `articulo` does not match `Artículo` — so the\n * palette silently finds nothing for the most natural way to type most of its\n * own copy. Nobody reaches for the accented key in a search box.\n *\n * NFD splits a precomposed letter into its base plus a combining mark, and the\n * `\\p{Diacritic}` class is what then removes only the mark. `ñ` decomposes too,\n * so `nino` matches `Niño` — which is what a person typing quickly wants, even\n * though ñ is a letter of its own in Spanish rather than an accented n.\n */\nexport function foldForMatch(text: string): string {\n return text\n .normalize('NFD')\n .replace(/\\p{Diacritic}/gu, '')\n .toLocaleLowerCase();\n}\n\n/**\n * Does `text` contain every character of `term`, in order?\n *\n * Subsequence rather than substring, so `prbr` finds \"Producto Bruto\" and `numa`\n * finds \"Nueva marca\" — the thing that makes a palette feel fast. Deliberately\n * **not** scored: group order is the ranking, and a score inside\n * a group would reshuffle it on every keystroke for no gain a person can learn.\n *\n * An empty term matches everything, which is what makes an unfiltered palette\n * the same code path as a filtered one.\n */\nexport function subsequenceMatch(term: string, text: string): boolean {\n const needle = foldForMatch(term);\n if (needle === '') return true;\n\n const haystack = foldForMatch(text);\n let cursor = 0;\n for (const character of haystack) {\n if (character === needle[cursor]) cursor += 1;\n if (cursor === needle.length) return true;\n }\n return false;\n}\n\n/**\n * Does this option answer to `term`, by its label or by any of its keywords?\n *\n * The keywords are what let a Spanish command match an English word someone\n * typed — \"Nuevo producto\" answering to `new` — without translating the label.\n */\nexport function matchesCommand(term: string, option: CommandOption): boolean {\n if (subsequenceMatch(term, option.label)) return true;\n return (option.keywords ?? []).some(keyword =>\n subsequenceMatch(term, keyword),\n );\n}\n\n/**\n * Split a catalog-resolved keyword list into terms.\n *\n * `MetaUseCase.keywordsKey` is a catalog **key** rather than a `string[]` so the\n * terms are per-locale; what the catalog holds is therefore one string, and this\n * is the convention for reading it. Commas, because a keyword may itself be two\n * words (\"cerrar sesión\") and splitting on whitespace would break that into\n * terms that match nothing on their own.\n *\n * Empty entries are dropped rather than kept as `''`, which would match every\n * option through {@link subsequenceMatch}'s empty-term rule.\n */\nexport function parseKeywords(value: string | undefined): string[] {\n if (value === undefined) return [];\n return value\n .split(',')\n .map(keyword => keyword.trim())\n .filter(keyword => keyword !== '');\n}\n\n/** Which sources a typed term is asking for. */\nexport type CommandScope = 'all' | 'commands' | 'records';\n\n/** A term, split into the scope its prefix asked for and the rest. */\nexport interface ParsedCommandTerm {\n readonly scope: CommandScope;\n readonly term: string;\n}\n\n/**\n * `>` narrows to commands, `#` to records, anything else searches both.\n *\n * A grammar for *entry* rather than for depth: descending into a sub-picker is a\n * page stack, not a third prefix, because a prefix a person has to remember is\n * worse than a list they can see. Two characters only, and both are the ones\n * VS Code and GitHub already trained people on.\n *\n * The prefix is stripped and the remainder trimmed at the front only — a\n * trailing space is something the person is still typing, and eating it would\n * make the list jump between keystrokes.\n */\nexport function parseCommandTerm(raw: string): ParsedCommandTerm {\n if (raw.startsWith('>')) {\n return { scope: 'commands', term: raw.slice(1).trimStart() };\n }\n if (raw.startsWith('#')) {\n return { scope: 'records', term: raw.slice(1).trimStart() };\n }\n return { scope: 'all', term: raw };\n}\n\n/**\n * The one thing selecting an option does.\n *\n * An option declaring none is a line that looks selectable and does nothing;\n * one declaring two is a line whose behaviour depends on the order a renderer\n * happens to check its members in. Both are silent, so this throws — at the\n * first render of the palette rather than on the click, which is the posture\n * `surfaceFor` and `assertSearchable` already take for the same reason.\n */\nexport function commandEffectOf(\n option: CommandOption,\n): 'href' | 'run' | 'push' {\n const declared = (['href', 'run', 'push'] as const).filter(\n effect => option[effect] !== undefined,\n );\n\n if (declared.length !== 1) {\n throw new EntifixLogicError(\n `The command \"${option.id}\" declares ${declared.length} effects (${declared.join(', ') || 'none'}), and must declare exactly one. ` +\n 'Give it an `href` to navigate, a `run` to invoke, or a `push` to open another page.',\n undefined,\n { id: option.id, declared },\n );\n }\n\n return declared[0];\n}\n"],"names":["EntifixLogicError","foldForMatch","text","normalize","replace","toLocaleLowerCase","subsequenceMatch","term","needle","haystack","cursor","character","length","matchesCommand","option","label","keywords","some","keyword","parseKeywords","value","undefined","split","map","trim","filter","parseCommandTerm","raw","startsWith","scope","slice","trimStart","commandEffectOf","declared","effect","id","join"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,0CAA0C;AAG5E;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,aAAaC,IAAY;IACvC,OAAOA,KACJC,SAAS,CAAC,OACVC,OAAO,CAAC,q6FAAmB,IAC3BC,iBAAiB;AACtB;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,iBAAiBC,IAAY,EAAEL,IAAY;IACzD,MAAMM,SAASP,aAAaM;IAC5B,IAAIC,WAAW,IAAI,OAAO;IAE1B,MAAMC,WAAWR,aAAaC;IAC9B,IAAIQ,SAAS;IACb,KAAK,MAAMC,aAAaF,SAAU;QAChC,IAAIE,cAAcH,MAAM,CAACE,OAAO,EAAEA,UAAU;QAC5C,IAAIA,WAAWF,OAAOI,MAAM,EAAE,OAAO;IACvC;IACA,OAAO;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASC,eAAeN,IAAY,EAAEO,MAAqB;QAExDA;IADR,IAAIR,iBAAiBC,MAAMO,OAAOC,KAAK,GAAG,OAAO;IACjD,OAAO,EAACD,mBAAAA,OAAOE,QAAQ,YAAfF,mBAAmB,EAAE,EAAEG,IAAI,CAACC,CAAAA,UAClCZ,iBAAiBC,MAAMW;AAE3B;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,cAAcC,KAAyB;IACrD,IAAIA,UAAUC,WAAW,OAAO,EAAE;IAClC,OAAOD,MACJE,KAAK,CAAC,KACNC,GAAG,CAACL,CAAAA,UAAWA,QAAQM,IAAI,IAC3BC,MAAM,CAACP,CAAAA,UAAWA,YAAY;AACnC;AAWA;;;;;;;;;;;CAWC,GACD,OAAO,SAASQ,iBAAiBC,GAAW;IAC1C,IAAIA,IAAIC,UAAU,CAAC,MAAM;QACvB,OAAO;YAAEC,OAAO;YAAYtB,MAAMoB,IAAIG,KAAK,CAAC,GAAGC,SAAS;QAAG;IAC7D;IACA,IAAIJ,IAAIC,UAAU,CAAC,MAAM;QACvB,OAAO;YAAEC,OAAO;YAAWtB,MAAMoB,IAAIG,KAAK,CAAC,GAAGC,SAAS;QAAG;IAC5D;IACA,OAAO;QAAEF,OAAO;QAAOtB,MAAMoB;IAAI;AACnC;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASK,gBACdlB,MAAqB;IAErB,MAAMmB,WAAW,AAAC;QAAC;QAAQ;QAAO;KAAO,CAAWR,MAAM,CACxDS,CAAAA,SAAUpB,MAAM,CAACoB,OAAO,KAAKb;IAG/B,IAAIY,SAASrB,MAAM,KAAK,GAAG;QACzB,MAAM,IAAIZ,kBACR,CAAC,aAAa,EAAEc,OAAOqB,EAAE,CAAC,WAAW,EAAEF,SAASrB,MAAM,CAAC,UAAU,EAAEqB,SAASG,IAAI,CAAC,SAAS,OAAO,iCAAiC,CAAC,GACjI,uFACFf,WACA;YAAEc,IAAIrB,OAAOqB,EAAE;YAAEF;QAAS;IAE9B;IAEA,OAAOA,QAAQ,CAAC,EAAE;AACpB"}
@@ -12,8 +12,8 @@ export type CommandUnavailableSeverity = 'scope' | 'reachability';
12
12
  /**
13
13
  * A source that produced no options, and why.
14
14
  *
15
- * The two severities must not be conflated, which is the same split ADR 0040's
16
- * `RecordSearchUnavailableReason` draws and the reason this member is not a
15
+ * The two severities must not be conflated, which is the same split the record
16
+ * search's `RecordSearchUnavailableReason` draws and the reason this member is not a
17
17
  * bare string. `scope` is the **normal** state for some callers — an operator
18
18
  * holds no membership, so every tenant-plane search answers `409` on every
19
19
  * keystroke — while `reachability` means something is actually wrong. Rendering
@@ -84,7 +84,7 @@ export interface CommandGroup {
84
84
  *
85
85
  * Several groups, not one, because the record search fans out over four entities
86
86
  * and each is its own heading — flattening them here would make the palette
87
- * re-derive what ADR 0040 already grouped.
87
+ * re-derive what the record search already grouped.
88
88
  */
89
89
  export interface CommandSource {
90
90
  readonly key: string;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/command-palette/command-source.ts"],"sourcesContent":["/**\n * What a command palette renders, as state rather than as a query.\n *\n * The same contract `EntityLinkSource` is, and for the same boundary reason: the\n * hooks that fetch live in the shell layer, the component that renders lives in\n * `@entifix/react-controls`, and those two may not import each other. It carries\n * no Effect, no React and no router — a source hands over resolved copy and a\n * thunk, never a key and never a `push()`.\n */\n\n/** How a group's absence is explained. */\nexport type CommandUnavailableSeverity = 'scope' | 'reachability';\n\n/**\n * A source that produced no options, and why.\n *\n * The two severities must not be conflated, which is the same split ADR 0040's\n * `RecordSearchUnavailableReason` draws and the reason this member is not a\n * bare string. `scope` is the **normal** state for some callers — an operator\n * holds no membership, so every tenant-plane search answers `409` on every\n * keystroke — while `reachability` means something is actually wrong. Rendering\n * a warning for the ordinary case teaches people to ignore the one that matters.\n */\nexport interface CommandUnavailable {\n /** Already-resolved copy. The source owns the catalog lookup. */\n readonly message: string;\n readonly severity: CommandUnavailableSeverity;\n}\n\n/** What selecting an option does. Exactly one arm is populated. */\nexport interface CommandEffect {\n /**\n * Navigate. **Already locale-prefixed** by the source, because the palette is\n * presentational and a control may not reach for a router or a locale.\n */\n readonly href?: string;\n /** Invoke. The source owns the request, the optimism and the error. */\n readonly run?: () => void | Promise<void>;\n /** Descend into another {@link CommandPage} by id — this is the page stack. */\n readonly push?: string;\n}\n\n/** Ask before running. Resolved copy, like everything else that crosses here. */\nexport interface CommandConfirm {\n readonly tone: 'destructive' | 'neutral';\n readonly message: string;\n}\n\n/** One selectable line. */\nexport interface CommandOption extends CommandEffect {\n /** Unique within the whole palette — it is also the recency key. */\n readonly id: string;\n /** Resolved copy. A source that holds a catalog key resolves it before here. */\n readonly label: string;\n /** A second line: a code, a role, the record's entity. */\n readonly sublabel?: string;\n /**\n * Extra terms this option matches on, so a Spanish command still answers to\n * the English word someone typed. Raycast's mechanism, and the reason\n * `MetaUseCase` carries a `keywordsKey` at all.\n */\n readonly keywords?: readonly string[];\n /** Trailing text — a shortcut hint, the source's own name. */\n readonly hint?: string;\n readonly confirm?: CommandConfirm;\n}\n\n/** One heading and the options under it. */\nexport interface CommandGroup {\n readonly key: string;\n /** Resolved copy. */\n readonly label: string;\n readonly options: readonly CommandOption[];\n /**\n * Matches **before** any per-group limit, so \"N más\" is renderable. Omitted\n * when the source did not count — never defaulted to `options.length`, which\n * would claim there is no more when nobody looked.\n */\n readonly total?: number;\n readonly isLoading: boolean;\n /**\n * Set when this group could not be produced. A group carrying this is still\n * rendered: an omitted group reads as \"nothing matched\", which is a confident\n * claim nobody is in a position to make about a source they could not reach.\n */\n readonly unavailable?: CommandUnavailable;\n}\n\n/**\n * One contributor's whole answer.\n *\n * Several groups, not one, because the record search fans out over four entities\n * and each is its own heading — flattening them here would make the palette\n * re-derive what ADR 0040 already grouped.\n */\nexport interface CommandSource {\n readonly key: string;\n readonly groups: readonly CommandGroup[];\n}\n\n/**\n * One level of the palette.\n *\n * Depth is a **stack of pages** rather than nested prefixes: selecting \"Nuevo…\"\n * makes the palette *become* the entity picker, which is Raycast's and Linear's\n * arrangement and reads far better than a second grammar character.\n */\nexport interface CommandPage {\n readonly id: string;\n /** Shown as a chip beside the input on any page but the root. */\n readonly title?: string;\n readonly placeholder: string;\n readonly sources: readonly CommandSource[];\n}\n\n/** The id of the page a palette opens on. */\nexport const ROOT_COMMAND_PAGE = 'root';\n"],"names":["ROOT_COMMAND_PAGE"],"mappings":"AAAA;;;;;;;;CAQC,GAED,wCAAwC,GAyGxC,2CAA2C,GAC3C,OAAO,MAAMA,oBAAoB,OAAO"}
1
+ {"version":3,"sources":["../../src/command-palette/command-source.ts"],"sourcesContent":["/**\n * What a command palette renders, as state rather than as a query.\n *\n * The same contract `EntityLinkSource` is, and for the same boundary reason: the\n * hooks that fetch live in the shell layer, the component that renders lives in\n * `@entifix/react-controls`, and those two may not import each other. It carries\n * no Effect, no React and no router — a source hands over resolved copy and a\n * thunk, never a key and never a `push()`.\n */\n\n/** How a group's absence is explained. */\nexport type CommandUnavailableSeverity = 'scope' | 'reachability';\n\n/**\n * A source that produced no options, and why.\n *\n * The two severities must not be conflated, which is the same split the record\n * search's `RecordSearchUnavailableReason` draws and the reason this member is not a\n * bare string. `scope` is the **normal** state for some callers — an operator\n * holds no membership, so every tenant-plane search answers `409` on every\n * keystroke — while `reachability` means something is actually wrong. Rendering\n * a warning for the ordinary case teaches people to ignore the one that matters.\n */\nexport interface CommandUnavailable {\n /** Already-resolved copy. The source owns the catalog lookup. */\n readonly message: string;\n readonly severity: CommandUnavailableSeverity;\n}\n\n/** What selecting an option does. Exactly one arm is populated. */\nexport interface CommandEffect {\n /**\n * Navigate. **Already locale-prefixed** by the source, because the palette is\n * presentational and a control may not reach for a router or a locale.\n */\n readonly href?: string;\n /** Invoke. The source owns the request, the optimism and the error. */\n readonly run?: () => void | Promise<void>;\n /** Descend into another {@link CommandPage} by id — this is the page stack. */\n readonly push?: string;\n}\n\n/** Ask before running. Resolved copy, like everything else that crosses here. */\nexport interface CommandConfirm {\n readonly tone: 'destructive' | 'neutral';\n readonly message: string;\n}\n\n/** One selectable line. */\nexport interface CommandOption extends CommandEffect {\n /** Unique within the whole palette — it is also the recency key. */\n readonly id: string;\n /** Resolved copy. A source that holds a catalog key resolves it before here. */\n readonly label: string;\n /** A second line: a code, a role, the record's entity. */\n readonly sublabel?: string;\n /**\n * Extra terms this option matches on, so a Spanish command still answers to\n * the English word someone typed. Raycast's mechanism, and the reason\n * `MetaUseCase` carries a `keywordsKey` at all.\n */\n readonly keywords?: readonly string[];\n /** Trailing text — a shortcut hint, the source's own name. */\n readonly hint?: string;\n readonly confirm?: CommandConfirm;\n}\n\n/** One heading and the options under it. */\nexport interface CommandGroup {\n readonly key: string;\n /** Resolved copy. */\n readonly label: string;\n readonly options: readonly CommandOption[];\n /**\n * Matches **before** any per-group limit, so \"N más\" is renderable. Omitted\n * when the source did not count — never defaulted to `options.length`, which\n * would claim there is no more when nobody looked.\n */\n readonly total?: number;\n readonly isLoading: boolean;\n /**\n * Set when this group could not be produced. A group carrying this is still\n * rendered: an omitted group reads as \"nothing matched\", which is a confident\n * claim nobody is in a position to make about a source they could not reach.\n */\n readonly unavailable?: CommandUnavailable;\n}\n\n/**\n * One contributor's whole answer.\n *\n * Several groups, not one, because the record search fans out over four entities\n * and each is its own heading — flattening them here would make the palette\n * re-derive what the record search already grouped.\n */\nexport interface CommandSource {\n readonly key: string;\n readonly groups: readonly CommandGroup[];\n}\n\n/**\n * One level of the palette.\n *\n * Depth is a **stack of pages** rather than nested prefixes: selecting \"Nuevo…\"\n * makes the palette *become* the entity picker, which is Raycast's and Linear's\n * arrangement and reads far better than a second grammar character.\n */\nexport interface CommandPage {\n readonly id: string;\n /** Shown as a chip beside the input on any page but the root. */\n readonly title?: string;\n readonly placeholder: string;\n readonly sources: readonly CommandSource[];\n}\n\n/** The id of the page a palette opens on. */\nexport const ROOT_COMMAND_PAGE = 'root';\n"],"names":["ROOT_COMMAND_PAGE"],"mappings":"AAAA;;;;;;;;CAQC,GAED,wCAAwC,GAyGxC,2CAA2C,GAC3C,OAAO,MAAMA,oBAAoB,OAAO"}
@@ -12,7 +12,7 @@ import { MetaUseCaseOptions } from '../../meta-entities/meta-use-case/index.js';
12
12
  *
13
13
  * The `const` type parameter keeps `key` a literal at the declaration site.
14
14
  * Write it inline — `@useCase({ ...DESCRIPTOR })` would compile and would make
15
- * the source scan in `@r10c/slices` stop seeing the declaration.
15
+ * a host's source scan stop seeing the declaration.
16
16
  */
17
17
  export declare function useCase<const TKey extends string, TEntity extends Entity>(options: MetaUseCaseOptions<TKey, TEntity>): (_target: unknown, context: ClassDecoratorContext) => void;
18
18
  //# sourceMappingURL=use-case.d.ts.map
@@ -12,7 +12,7 @@ import { MetaUseCase } from '../../meta-entities/meta-use-case/index.js';
12
12
  *
13
13
  * The `const` type parameter keeps `key` a literal at the declaration site.
14
14
  * Write it inline — `@useCase({ ...DESCRIPTOR })` would compile and would make
15
- * the source scan in `@r10c/slices` stop seeing the declaration.
15
+ * a host's source scan stop seeing the declaration.
16
16
  */ export function useCase(options) {
17
17
  return (_target, context)=>{
18
18
  appendMetaUseCase(options.entity, new MetaUseCase(options));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/entity-definition/decorators/use-case/use-case.ts"],"sourcesContent":["import type { Entity } from '../../../types/Entity.js';\nimport {\n appendMetaUseCase,\n setMetaUseCaseBinding,\n} from '../../helpers/index.js';\nimport {\n MetaUseCase,\n MetaUseCaseOptions,\n} from '../../meta-entities/meta-use-case/index.js';\n\n/**\n * Declares a use case: a verb on an entity beyond the `read`/`write`/`delete`\n * triple, with everything a surface needs to render it.\n *\n * It writes twice, and the two writes have different jobs. The descriptor is\n * appended to the **entity's** metadata, because that is what a form, a table or\n * the palette holds. The entity/verb pair is written to the **class's own**\n * metadata, which is what lets `permissionForUseCase(SomeUC)` derive the\n * permission from one argument.\n *\n * The `const` type parameter keeps `key` a literal at the declaration site.\n * Write it inline — `@useCase({ ...DESCRIPTOR })` would compile and would make\n * the source scan in `@r10c/slices` stop seeing the declaration.\n */\nexport function useCase<const TKey extends string, TEntity extends Entity>(\n options: MetaUseCaseOptions<TKey, TEntity>,\n) {\n return (_target: unknown, context: ClassDecoratorContext) => {\n appendMetaUseCase(options.entity, new MetaUseCase(options));\n setMetaUseCaseBinding(context.metadata, {\n entity: options.entity,\n key: options.key,\n });\n };\n}\n"],"names":["appendMetaUseCase","setMetaUseCaseBinding","MetaUseCase","useCase","options","_target","context","entity","metadata","key"],"mappings":"AACA,SACEA,iBAAiB,EACjBC,qBAAqB,QAChB,yBAAyB;AAChC,SACEC,WAAW,QAEN,6CAA6C;AAEpD;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,QACdC,OAA0C;IAE1C,OAAO,CAACC,SAAkBC;QACxBN,kBAAkBI,QAAQG,MAAM,EAAE,IAAIL,YAAYE;QAClDH,sBAAsBK,QAAQE,QAAQ,EAAE;YACtCD,QAAQH,QAAQG,MAAM;YACtBE,KAAKL,QAAQK,GAAG;QAClB;IACF;AACF"}
1
+ {"version":3,"sources":["../../../../src/entity-definition/decorators/use-case/use-case.ts"],"sourcesContent":["import type { Entity } from '../../../types/Entity.js';\nimport {\n appendMetaUseCase,\n setMetaUseCaseBinding,\n} from '../../helpers/index.js';\nimport {\n MetaUseCase,\n MetaUseCaseOptions,\n} from '../../meta-entities/meta-use-case/index.js';\n\n/**\n * Declares a use case: a verb on an entity beyond the `read`/`write`/`delete`\n * triple, with everything a surface needs to render it.\n *\n * It writes twice, and the two writes have different jobs. The descriptor is\n * appended to the **entity's** metadata, because that is what a form, a table or\n * the palette holds. The entity/verb pair is written to the **class's own**\n * metadata, which is what lets `permissionForUseCase(SomeUC)` derive the\n * permission from one argument.\n *\n * The `const` type parameter keeps `key` a literal at the declaration site.\n * Write it inline — `@useCase({ ...DESCRIPTOR })` would compile and would make\n * a host's source scan stop seeing the declaration.\n */\nexport function useCase<const TKey extends string, TEntity extends Entity>(\n options: MetaUseCaseOptions<TKey, TEntity>,\n) {\n return (_target: unknown, context: ClassDecoratorContext) => {\n appendMetaUseCase(options.entity, new MetaUseCase(options));\n setMetaUseCaseBinding(context.metadata, {\n entity: options.entity,\n key: options.key,\n });\n };\n}\n"],"names":["appendMetaUseCase","setMetaUseCaseBinding","MetaUseCase","useCase","options","_target","context","entity","metadata","key"],"mappings":"AACA,SACEA,iBAAiB,EACjBC,qBAAqB,QAChB,yBAAyB;AAChC,SACEC,WAAW,QAEN,6CAA6C;AAEpD;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,QACdC,OAA0C;IAE1C,OAAO,CAACC,SAAkBC;QACxBN,kBAAkBI,QAAQG,MAAM,EAAE,IAAIL,YAAYE;QAClDH,sBAAsBK,QAAQE,QAAQ,EAAE;YACtCD,QAAQH,QAAQG,MAAM;YACtBE,KAAKL,QAAQK,GAAG;QAClB;IACF;AACF"}
@@ -5,7 +5,7 @@ import { type EntityFieldDescriptor } from './describe-entity-columns.js';
5
5
  *
6
6
  * Thin over {@link describeEntityColumns}, which already accepts a
7
7
  * `ChildConstructor` — a child is described by its accessors and needs no
8
- * `@entity()` ([ADR 0034](../../../../../../../docs/adr/0034-composition-metadata.md)).
8
+ * `@entity()`.
9
9
  * What this adds is the one rule a row has and an entity does not.
10
10
  *
11
11
  * **A child may not declare `$key`.** That name is the row's minted client
@@ -6,7 +6,7 @@ import { describeEntityColumns } from './describe-entity-columns.js';
6
6
  *
7
7
  * Thin over {@link describeEntityColumns}, which already accepts a
8
8
  * `ChildConstructor` — a child is described by its accessors and needs no
9
- * `@entity()` ([ADR 0034](../../../../../../../docs/adr/0034-composition-metadata.md)).
9
+ * `@entity()`.
10
10
  * What this adds is the one rule a row has and an entity does not.
11
11
  *
12
12
  * **A child may not declare `$key`.** That name is the row's minted client
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/entity-definition/describe/describe-child-columns.ts"],"sourcesContent":["import { EntifixBuildError } from '../../base-entities/entifix-error/index.js';\nimport { ROW_KEY } from '../../types/EntityRowDraft.js';\nimport type { ChildConstructor } from '../meta-entities/meta-accessor/index.js';\nimport {\n describeEntityColumns,\n type EntityFieldDescriptor,\n} from './describe-entity-columns.js';\n\n/**\n * The columns of one row of an owned collection.\n *\n * Thin over {@link describeEntityColumns}, which already accepts a\n * `ChildConstructor` — a child is described by its accessors and needs no\n * `@entity()` ([ADR 0034](../../../../../../../docs/adr/0034-composition-metadata.md)).\n * What this adds is the one rule a row has and an entity does not.\n *\n * **A child may not declare `$key`.** That name is the row's minted client\n * identity, and a member of the same name would be overwritten by it — the row\n * would re-key on every keystroke, drop focus mid-word, and address another\n * row's errors. Throwing is the only honest option: silently renaming the\n * member would write the wrong column, and preferring the member over the key\n * would leave the grid with no identity at all.\n *\n * **Nor a `localized` member**, for a reason of the same kind: a row's draft is a\n * flat string map, and a text per locale has no single cell to live in.\n *\n * `EntifixBuildError` rather than a logic error, because this is a declaration\n * fault in the entity — the same class `assertNotQueryable` raises when a\n * collection claims to be sortable.\n */\nexport function describeChildColumns<TChild extends object>(\n childType: ChildConstructor<TChild>,\n sample?: TChild,\n): EntityFieldDescriptor[] {\n const descriptors = describeEntityColumns(childType, sample);\n const reserved = descriptors.find(descriptor => descriptor.name === ROW_KEY);\n\n if (reserved !== undefined) {\n throw new EntifixBuildError(\n `A composition child declares a member named \\`${ROW_KEY}\\`, which is reserved ` +\n 'for the row’s client-side key. Rename the member: the key and the member ' +\n 'cannot share a name, and the row would lose its identity on every edit.',\n undefined,\n { member: ROW_KEY },\n );\n }\n\n // A row's draft is a flat string map, which is what keeps a draft's type\n // bounded for the form engine; a text per locale has no cell to live in.\n const localized = descriptors.find(\n descriptor => descriptor.type === 'localized',\n );\n if (localized !== undefined) {\n throw new EntifixBuildError(\n `A composition child declares \"${localized.name}\" as localized, which a row ` +\n 'cannot hold: a row is a flat string map. Keep the localized text on the ' +\n 'entity, or make the child an entity of its own.',\n undefined,\n { member: localized.name },\n );\n }\n\n return descriptors;\n}\n\n/**\n * The columns a detail grid actually edits.\n *\n * A read-only member still has a column — it is shown, just not written, exactly\n * as a form shows one. What is dropped is a **nested collection**: a child that\n * owns rows of its own would need a grid inside a grid, and `reconstructChild`\n * is one level deep by decision. Dropping it here rather than rendering a\n * disabled cell keeps that decision in one place.\n */\nexport function editableChildColumns(\n descriptors: readonly EntityFieldDescriptor[],\n): EntityFieldDescriptor[] {\n return descriptors.filter(\n descriptor =>\n descriptor.type !== 'composition' &&\n descriptor.type !== 'linkCollection' &&\n descriptor.type !== 'link',\n );\n}\n"],"names":["EntifixBuildError","ROW_KEY","describeEntityColumns","describeChildColumns","childType","sample","descriptors","reserved","find","descriptor","name","undefined","member","localized","type","editableChildColumns","filter"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,6CAA6C;AAC/E,SAASC,OAAO,QAAQ,gCAAgC;AAExD,SACEC,qBAAqB,QAEhB,+BAA+B;AAEtC;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,SAASC,qBACdC,SAAmC,EACnCC,MAAe;IAEf,MAAMC,cAAcJ,sBAAsBE,WAAWC;IACrD,MAAME,WAAWD,YAAYE,IAAI,CAACC,CAAAA,aAAcA,WAAWC,IAAI,KAAKT;IAEpE,IAAIM,aAAaI,WAAW;QAC1B,MAAM,IAAIX,kBACR,CAAC,8CAA8C,EAAEC,QAAQ,sBAAsB,CAAC,GAC9E,8EACA,2EACFU,WACA;YAAEC,QAAQX;QAAQ;IAEtB;IAEA,yEAAyE;IACzE,yEAAyE;IACzE,MAAMY,YAAYP,YAAYE,IAAI,CAChCC,CAAAA,aAAcA,WAAWK,IAAI,KAAK;IAEpC,IAAID,cAAcF,WAAW;QAC3B,MAAM,IAAIX,kBACR,CAAC,8BAA8B,EAAEa,UAAUH,IAAI,CAAC,4BAA4B,CAAC,GAC3E,6EACA,mDACFC,WACA;YAAEC,QAAQC,UAAUH,IAAI;QAAC;IAE7B;IAEA,OAAOJ;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASS,qBACdT,WAA6C;IAE7C,OAAOA,YAAYU,MAAM,CACvBP,CAAAA,aACEA,WAAWK,IAAI,KAAK,iBACpBL,WAAWK,IAAI,KAAK,oBACpBL,WAAWK,IAAI,KAAK;AAE1B"}
1
+ {"version":3,"sources":["../../../src/entity-definition/describe/describe-child-columns.ts"],"sourcesContent":["import { EntifixBuildError } from '../../base-entities/entifix-error/index.js';\nimport { ROW_KEY } from '../../types/EntityRowDraft.js';\nimport type { ChildConstructor } from '../meta-entities/meta-accessor/index.js';\nimport {\n describeEntityColumns,\n type EntityFieldDescriptor,\n} from './describe-entity-columns.js';\n\n/**\n * The columns of one row of an owned collection.\n *\n * Thin over {@link describeEntityColumns}, which already accepts a\n * `ChildConstructor` — a child is described by its accessors and needs no\n * `@entity()`.\n * What this adds is the one rule a row has and an entity does not.\n *\n * **A child may not declare `$key`.** That name is the row's minted client\n * identity, and a member of the same name would be overwritten by it — the row\n * would re-key on every keystroke, drop focus mid-word, and address another\n * row's errors. Throwing is the only honest option: silently renaming the\n * member would write the wrong column, and preferring the member over the key\n * would leave the grid with no identity at all.\n *\n * **Nor a `localized` member**, for a reason of the same kind: a row's draft is a\n * flat string map, and a text per locale has no single cell to live in.\n *\n * `EntifixBuildError` rather than a logic error, because this is a declaration\n * fault in the entity — the same class `assertNotQueryable` raises when a\n * collection claims to be sortable.\n */\nexport function describeChildColumns<TChild extends object>(\n childType: ChildConstructor<TChild>,\n sample?: TChild,\n): EntityFieldDescriptor[] {\n const descriptors = describeEntityColumns(childType, sample);\n const reserved = descriptors.find(descriptor => descriptor.name === ROW_KEY);\n\n if (reserved !== undefined) {\n throw new EntifixBuildError(\n `A composition child declares a member named \\`${ROW_KEY}\\`, which is reserved ` +\n 'for the row’s client-side key. Rename the member: the key and the member ' +\n 'cannot share a name, and the row would lose its identity on every edit.',\n undefined,\n { member: ROW_KEY },\n );\n }\n\n // A row's draft is a flat string map, which is what keeps a draft's type\n // bounded for the form engine; a text per locale has no cell to live in.\n const localized = descriptors.find(\n descriptor => descriptor.type === 'localized',\n );\n if (localized !== undefined) {\n throw new EntifixBuildError(\n `A composition child declares \"${localized.name}\" as localized, which a row ` +\n 'cannot hold: a row is a flat string map. Keep the localized text on the ' +\n 'entity, or make the child an entity of its own.',\n undefined,\n { member: localized.name },\n );\n }\n\n return descriptors;\n}\n\n/**\n * The columns a detail grid actually edits.\n *\n * A read-only member still has a column — it is shown, just not written, exactly\n * as a form shows one. What is dropped is a **nested collection**: a child that\n * owns rows of its own would need a grid inside a grid, and `reconstructChild`\n * is one level deep by decision. Dropping it here rather than rendering a\n * disabled cell keeps that decision in one place.\n */\nexport function editableChildColumns(\n descriptors: readonly EntityFieldDescriptor[],\n): EntityFieldDescriptor[] {\n return descriptors.filter(\n descriptor =>\n descriptor.type !== 'composition' &&\n descriptor.type !== 'linkCollection' &&\n descriptor.type !== 'link',\n );\n}\n"],"names":["EntifixBuildError","ROW_KEY","describeEntityColumns","describeChildColumns","childType","sample","descriptors","reserved","find","descriptor","name","undefined","member","localized","type","editableChildColumns","filter"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,6CAA6C;AAC/E,SAASC,OAAO,QAAQ,gCAAgC;AAExD,SACEC,qBAAqB,QAEhB,+BAA+B;AAEtC;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,SAASC,qBACdC,SAAmC,EACnCC,MAAe;IAEf,MAAMC,cAAcJ,sBAAsBE,WAAWC;IACrD,MAAME,WAAWD,YAAYE,IAAI,CAACC,CAAAA,aAAcA,WAAWC,IAAI,KAAKT;IAEpE,IAAIM,aAAaI,WAAW;QAC1B,MAAM,IAAIX,kBACR,CAAC,8CAA8C,EAAEC,QAAQ,sBAAsB,CAAC,GAC9E,8EACA,2EACFU,WACA;YAAEC,QAAQX;QAAQ;IAEtB;IAEA,yEAAyE;IACzE,yEAAyE;IACzE,MAAMY,YAAYP,YAAYE,IAAI,CAChCC,CAAAA,aAAcA,WAAWK,IAAI,KAAK;IAEpC,IAAID,cAAcF,WAAW;QAC3B,MAAM,IAAIX,kBACR,CAAC,8BAA8B,EAAEa,UAAUH,IAAI,CAAC,4BAA4B,CAAC,GAC3E,6EACA,mDACFC,WACA;YAAEC,QAAQC,UAAUH,IAAI;QAAC;IAE7B;IAEA,OAAOJ;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASS,qBACdT,WAA6C;IAE7C,OAAOA,YAAYU,MAAM,CACvBP,CAAAA,aACEA,WAAWK,IAAI,KAAK,iBACpBL,WAAWK,IAAI,KAAK,oBACpBL,WAAWK,IAAI,KAAK;AAE1B"}
@@ -26,7 +26,8 @@ export interface UseCaseDescriptor {
26
26
  * Unlike {@link describeEntityColumns}, this is only trustworthy where the
27
27
  * use-case modules have been loaded: a `@useCase()` class registers itself when
28
28
  * its module evaluates. On a service that is the composition root's job, and
29
- * `@r10c/slices` fails the build on a class its package barrel does not export.
29
+ * a host's register should fail the build on a class its package barrel does
30
+ * not export.
30
31
  * A browser never calls this — it fetches the descriptors instead, so no
31
32
  * use-case implementation reaches a client bundle.
32
33
  */
@@ -1 +1 @@
1
- {"version":3,"file":"describe-entity-use-cases.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/describe-entity-use-cases.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAElE,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACX,cAAc,EACd,gBAAgB,EACjB,MAAM,yCAAyC,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,SAAS,MAAM,EAC3D,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAC5C,iBAAiB,EAAE,CAWrB"}
1
+ {"version":3,"file":"describe-entity-use-cases.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/describe-entity-use-cases.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAElE,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACX,cAAc,EACd,gBAAgB,EACjB,MAAM,yCAAyC,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,SAAS,MAAM,EAC3D,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAC5C,iBAAiB,EAAE,CAWrB"}
@@ -5,7 +5,8 @@ import { extractMetaUseCases } from '../helpers/index.js';
5
5
  * Unlike {@link describeEntityColumns}, this is only trustworthy where the
6
6
  * use-case modules have been loaded: a `@useCase()` class registers itself when
7
7
  * its module evaluates. On a service that is the composition root's job, and
8
- * `@r10c/slices` fails the build on a class its package barrel does not export.
8
+ * a host's register should fail the build on a class its package barrel does
9
+ * not export.
9
10
  * A browser never calls this — it fetches the descriptors instead, so no
10
11
  * use-case implementation reaches a client bundle.
11
12
  */ export function describeEntityUseCases(entityConstructor) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/entity-definition/describe/describe-entity-use-cases.ts"],"sourcesContent":["import { Entity, EntityConstructor } from '../../types/Entity.js';\nimport { extractMetaUseCases } from '../helpers/index.js';\nimport type {\n UseCaseBinding,\n UseCaseCell,\n UseCaseConfirm,\n UseCasePlacement,\n} from '../meta-entities/meta-use-case/index.js';\n\n/**\n * An action a caller may take on an entity, resolved from its use-case\n * metadata. The counterpart to {@link EntityFieldDescriptor}, and like it,\n * framework-free and copy-free — every human-readable member is a catalog key.\n *\n * It carries no `entity`: this is the shape that goes over the wire from\n * `GET /api/<entity>/$metadata`, where the entity is already the address.\n */\nexport interface UseCaseDescriptor {\n /** The verb. Also the third segment of the permission guarding it. */\n key: string;\n binding: UseCaseBinding;\n placement: UseCasePlacement;\n /** Further cells this same verb appears in — see `MetaUseCaseOptions`. */\n alsoAt?: readonly UseCaseCell[];\n labelKey: string;\n keywordsKey?: string;\n confirm?: UseCaseConfirm;\n form?: string;\n}\n\n/**\n * Resolves the use cases declared against an entity, in declaration order.\n *\n * Unlike {@link describeEntityColumns}, this is only trustworthy where the\n * use-case modules have been loaded: a `@useCase()` class registers itself when\n * its module evaluates. On a service that is the composition root's job, and\n * `@r10c/slices` fails the build on a class its package barrel does not export.\n * A browser never calls this — it fetches the descriptors instead, so no\n * use-case implementation reaches a client bundle.\n */\nexport function describeEntityUseCases<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n): UseCaseDescriptor[] {\n return extractMetaUseCases(entityConstructor).map(metaUseCase => ({\n key: metaUseCase.key,\n binding: metaUseCase.binding,\n placement: metaUseCase.placement,\n alsoAt: metaUseCase.alsoAt,\n labelKey: metaUseCase.labelKey,\n keywordsKey: metaUseCase.keywordsKey,\n confirm: metaUseCase.confirm,\n form: metaUseCase.form,\n }));\n}\n"],"names":["extractMetaUseCases","describeEntityUseCases","entityConstructor","map","metaUseCase","key","binding","placement","alsoAt","labelKey","keywordsKey","confirm","form"],"mappings":"AACA,SAASA,mBAAmB,QAAQ,sBAAsB;AA6B1D;;;;;;;;;CASC,GACD,OAAO,SAASC,uBACdC,iBAA6C;IAE7C,OAAOF,oBAAoBE,mBAAmBC,GAAG,CAACC,CAAAA,cAAgB,CAAA;YAChEC,KAAKD,YAAYC,GAAG;YACpBC,SAASF,YAAYE,OAAO;YAC5BC,WAAWH,YAAYG,SAAS;YAChCC,QAAQJ,YAAYI,MAAM;YAC1BC,UAAUL,YAAYK,QAAQ;YAC9BC,aAAaN,YAAYM,WAAW;YACpCC,SAASP,YAAYO,OAAO;YAC5BC,MAAMR,YAAYQ,IAAI;QACxB,CAAA;AACF"}
1
+ {"version":3,"sources":["../../../src/entity-definition/describe/describe-entity-use-cases.ts"],"sourcesContent":["import { Entity, EntityConstructor } from '../../types/Entity.js';\nimport { extractMetaUseCases } from '../helpers/index.js';\nimport type {\n UseCaseBinding,\n UseCaseCell,\n UseCaseConfirm,\n UseCasePlacement,\n} from '../meta-entities/meta-use-case/index.js';\n\n/**\n * An action a caller may take on an entity, resolved from its use-case\n * metadata. The counterpart to {@link EntityFieldDescriptor}, and like it,\n * framework-free and copy-free — every human-readable member is a catalog key.\n *\n * It carries no `entity`: this is the shape that goes over the wire from\n * `GET /api/<entity>/$metadata`, where the entity is already the address.\n */\nexport interface UseCaseDescriptor {\n /** The verb. Also the third segment of the permission guarding it. */\n key: string;\n binding: UseCaseBinding;\n placement: UseCasePlacement;\n /** Further cells this same verb appears in — see `MetaUseCaseOptions`. */\n alsoAt?: readonly UseCaseCell[];\n labelKey: string;\n keywordsKey?: string;\n confirm?: UseCaseConfirm;\n form?: string;\n}\n\n/**\n * Resolves the use cases declared against an entity, in declaration order.\n *\n * Unlike {@link describeEntityColumns}, this is only trustworthy where the\n * use-case modules have been loaded: a `@useCase()` class registers itself when\n * its module evaluates. On a service that is the composition root's job, and\n * a host's register should fail the build on a class its package barrel does\n * not export.\n * A browser never calls this — it fetches the descriptors instead, so no\n * use-case implementation reaches a client bundle.\n */\nexport function describeEntityUseCases<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n): UseCaseDescriptor[] {\n return extractMetaUseCases(entityConstructor).map(metaUseCase => ({\n key: metaUseCase.key,\n binding: metaUseCase.binding,\n placement: metaUseCase.placement,\n alsoAt: metaUseCase.alsoAt,\n labelKey: metaUseCase.labelKey,\n keywordsKey: metaUseCase.keywordsKey,\n confirm: metaUseCase.confirm,\n form: metaUseCase.form,\n }));\n}\n"],"names":["extractMetaUseCases","describeEntityUseCases","entityConstructor","map","metaUseCase","key","binding","placement","alsoAt","labelKey","keywordsKey","confirm","form"],"mappings":"AACA,SAASA,mBAAmB,QAAQ,sBAAsB;AA6B1D;;;;;;;;;;CAUC,GACD,OAAO,SAASC,uBACdC,iBAA6C;IAE7C,OAAOF,oBAAoBE,mBAAmBC,GAAG,CAACC,CAAAA,cAAgB,CAAA;YAChEC,KAAKD,YAAYC,GAAG;YACpBC,SAASF,YAAYE,OAAO;YAC5BC,WAAWH,YAAYG,SAAS;YAChCC,QAAQJ,YAAYI,MAAM;YAC1BC,UAAUL,YAAYK,QAAQ;YAC9BC,aAAaN,YAAYM,WAAW;YACpCC,SAASP,YAAYO,OAAO;YAC5BC,MAAMR,YAAYQ,IAAI;QACxB,CAAA;AACF"}
@@ -24,8 +24,8 @@ export declare function appendMetaMethod(metadata: DecoratorMetadataObject | und
24
24
  * declared on its own class, but the entity is what a surface holds, so the
25
25
  * descriptor has to end up there. The consequence is that a use-case module
26
26
  * which is never imported leaves the entity looking as if it had no actions,
27
- * which is why `@r10c/slices` asserts every `@useCase()` class is re-exported
28
- * from its package barrel.
27
+ * which is why a host's register should assert every `@useCase()` class is
28
+ * re-exported from its package barrel.
29
29
  *
30
30
  * It reads the target's **own** metadata rather than going through
31
31
  * {@link readMetadata}. `Symbol.metadata` resolves along the prototype chain, so
@@ -40,8 +40,8 @@ export function appendMetaMethod(metadata, metaMethod) {
40
40
  * declared on its own class, but the entity is what a surface holds, so the
41
41
  * descriptor has to end up there. The consequence is that a use-case module
42
42
  * which is never imported leaves the entity looking as if it had no actions,
43
- * which is why `@r10c/slices` asserts every `@useCase()` class is re-exported
44
- * from its package barrel.
43
+ * which is why a host's register should assert every `@useCase()` class is
44
+ * re-exported from its package barrel.
45
45
  *
46
46
  * It reads the target's **own** metadata rather than going through
47
47
  * {@link readMetadata}. `Symbol.metadata` resolves along the prototype chain, so
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/entity-definition/helpers/entity-definition-helpers.ts"],"sourcesContent":["(Symbol as { metadata?: symbol }).metadata ??= Symbol.for('Symbol.metadata');\n\nimport { EntifixBuildError } from '../../base-entities/entifix-error/index.js';\nimport { Entity, EntityConstructor } from '../../types/Entity.js';\nimport { UseCaseConstructor } from '../../types/UseCase.js';\nimport {\n type ChildConstructor,\n MetaAccessor,\n} from '../meta-entities/meta-accessor/index.js';\nimport { MetaEntity } from '../meta-entities/meta-entity/index.js';\nimport { MetaMethod } from '../meta-entities/meta-method/index.js';\nimport {\n MetaUseCase,\n MetaUseCaseBinding,\n} from '../meta-entities/meta-use-case/index.js';\n\ndeclare global {\n interface SymbolConstructor {\n readonly metadata: unique symbol;\n }\n}\n\nexport const META_ENTITY_KEY = Symbol('MetaEntity');\nexport const META_ACCESSOR_KEY = Symbol('MetaAccessor');\nexport const META_METHOD_KEY = Symbol('MetaMethod');\nexport const META_USE_CASE_KEY = Symbol('MetaUseCase');\nexport const META_USE_CASE_BINDING_KEY = Symbol('MetaUseCaseBinding');\n\nfunction requireMetadata(\n metadata: DecoratorMetadataObject | undefined,\n): DecoratorMetadataObject {\n if (!metadata) {\n throw new EntifixBuildError(\n 'Decorator metadata unavailable — Symbol.metadata polyfill missing or stage-3 decorator emit not enabled.',\n );\n }\n return metadata;\n}\n\nexport function setMetaEntity(\n metadata: DecoratorMetadataObject | undefined,\n metaEntity: MetaEntity,\n) {\n requireMetadata(metadata)[META_ENTITY_KEY] = metaEntity;\n}\n\nexport function appendMetaAccessor(\n metadata: DecoratorMetadataObject | undefined,\n metaAccessor: MetaAccessor,\n) {\n const meta = requireMetadata(metadata);\n const existing =\n (meta[META_ACCESSOR_KEY] as MetaAccessor[] | undefined) ?? [];\n meta[META_ACCESSOR_KEY] = [...existing, metaAccessor];\n}\n\nexport function appendMetaMethod(\n metadata: DecoratorMetadataObject | undefined,\n metaMethod: MetaMethod,\n) {\n const meta = requireMetadata(metadata);\n const existing = (meta[META_METHOD_KEY] as MetaMethod[] | undefined) ?? [];\n meta[META_METHOD_KEY] = [...existing, metaMethod];\n}\n\n/**\n * Append a use case to the metadata of the entity it acts on.\n *\n * This is the one decorator that writes a bag other than its own: a use case is\n * declared on its own class, but the entity is what a surface holds, so the\n * descriptor has to end up there. The consequence is that a use-case module\n * which is never imported leaves the entity looking as if it had no actions,\n * which is why `@r10c/slices` asserts every `@useCase()` class is re-exported\n * from its package barrel.\n *\n * It reads the target's **own** metadata rather than going through\n * {@link readMetadata}. `Symbol.metadata` resolves along the prototype chain, so\n * an inherited read would append the verb to a base class's bag and leak it to\n * every subclass. A target with no own metadata is an entity missing\n * `@entity()`, or a circular import that evaluated the use case first.\n */\nexport function appendMetaUseCase<TEntity extends Entity>(\n target: EntityConstructor<TEntity>,\n metaUseCase: MetaUseCase,\n) {\n const own = Object.getOwnPropertyDescriptor(target, Symbol.metadata)\n ?.value as DecoratorMetadataObject | null | undefined;\n if (!own) {\n throw new EntifixBuildError(\n `Use case \"${metaUseCase.key}\" targets ${target.name}, which has no metadata of its own — it is missing @entity(), or a circular import evaluated the use case first.`,\n );\n }\n const existing = (own[META_USE_CASE_KEY] as MetaUseCase[] | undefined) ?? [];\n own[META_USE_CASE_KEY] = [...existing, metaUseCase];\n}\n\n/**\n * Record on the use-case class itself which entity and verb it implements, so\n * the permission can be derived from the class alone. That is what keeps the\n * verb string written once, at the declaration, instead of being retyped at\n * every guard.\n */\nexport function setMetaUseCaseBinding(\n metadata: DecoratorMetadataObject | undefined,\n binding: MetaUseCaseBinding,\n) {\n requireMetadata(metadata)[META_USE_CASE_BINDING_KEY] = binding;\n}\n\nfunction readMetadata(\n target: ChildConstructor,\n): DecoratorMetadataObject | undefined {\n return target[Symbol.metadata] ?? undefined;\n}\n\nexport function extractMetaEntity<TEntity extends Entity>(\n target: EntityConstructor<TEntity>,\n): MetaEntity {\n const metadata = readMetadata(target);\n const metaEntity = metadata?.[META_ENTITY_KEY] as MetaEntity | undefined;\n if (!metaEntity) {\n throw new EntifixBuildError(`MetaEntity not found for ${target.name}`);\n }\n return metaEntity;\n}\n\n/**\n * Every accessor declared on a class.\n *\n * The target is a {@link ChildConstructor}, not an `EntityConstructor`: an\n * `@accessor()` writes to its own class's metadata bag with no help from\n * `@entity()`, so a `composition` member's child — a value with no `id` — is\n * described by exactly this walk.\n */\nexport function extractMetaAccessors(target: ChildConstructor): MetaAccessor[] {\n const metadata = readMetadata(target);\n return (metadata?.[META_ACCESSOR_KEY] as MetaAccessor[] | undefined) ?? [];\n}\n\nexport function extractMetaMethods<TEntity extends Entity>(\n target: EntityConstructor<TEntity>,\n): MetaMethod[] {\n const metadata = readMetadata(target);\n return (metadata?.[META_METHOD_KEY] as MetaMethod[] | undefined) ?? [];\n}\n\n/** Every use case declared against an entity. Empty until one is imported. */\nexport function extractMetaUseCases<TEntity extends Entity>(\n target: EntityConstructor<TEntity>,\n): MetaUseCase[] {\n const metadata = readMetadata(target);\n return (metadata?.[META_USE_CASE_KEY] as MetaUseCase[] | undefined) ?? [];\n}\n\n/** The entity and verb a `@useCase()` class implements. */\nexport function extractMetaUseCaseBinding(\n target: UseCaseConstructor,\n): MetaUseCaseBinding {\n const metadata = (\n target as { [Symbol.metadata]?: DecoratorMetadataObject | null }\n )[Symbol.metadata];\n const binding = metadata?.[META_USE_CASE_BINDING_KEY] as\n MetaUseCaseBinding | undefined;\n if (!binding) {\n throw new EntifixBuildError(\n `MetaUseCase not found for ${target.name} — is it missing @useCase()?`,\n );\n }\n return binding;\n}\n"],"names":["Symbol","metadata","for","EntifixBuildError","META_ENTITY_KEY","META_ACCESSOR_KEY","META_METHOD_KEY","META_USE_CASE_KEY","META_USE_CASE_BINDING_KEY","requireMetadata","setMetaEntity","metaEntity","appendMetaAccessor","metaAccessor","meta","existing","appendMetaMethod","metaMethod","appendMetaUseCase","target","metaUseCase","own","Object","getOwnPropertyDescriptor","value","key","name","setMetaUseCaseBinding","binding","readMetadata","undefined","extractMetaEntity","extractMetaAccessors","extractMetaMethods","extractMetaUseCases","extractMetaUseCaseBinding"],"mappings":"IAACA,SAAD;CAAA,YAAA,CAACA,UAAAA,QAAiCC,QAAQ,YAA1C,YAAA,AAACD,QAAiCC,QAAQ,GAAKD,OAAOE,GAAG,CAAC;AAE1D,SAASC,iBAAiB,QAAQ,6CAA6C;AAoB/E,OAAO,MAAMC,kBAAkBJ,OAAO,cAAc;AACpD,OAAO,MAAMK,oBAAoBL,OAAO,gBAAgB;AACxD,OAAO,MAAMM,kBAAkBN,OAAO,cAAc;AACpD,OAAO,MAAMO,oBAAoBP,OAAO,eAAe;AACvD,OAAO,MAAMQ,4BAA4BR,OAAO,sBAAsB;AAEtE,SAASS,gBACPR,QAA6C;IAE7C,IAAI,CAACA,UAAU;QACb,MAAM,IAAIE,kBACR;IAEJ;IACA,OAAOF;AACT;AAEA,OAAO,SAASS,cACdT,QAA6C,EAC7CU,UAAsB;IAEtBF,gBAAgBR,SAAS,CAACG,gBAAgB,GAAGO;AAC/C;AAEA,OAAO,SAASC,mBACdX,QAA6C,EAC7CY,YAA0B;QAIvBC;IAFH,MAAMA,OAAOL,gBAAgBR;IAC7B,MAAMc,YACHD,0BAAAA,IAAI,CAACT,kBAAkB,YAAvBS,0BAA0D,EAAE;IAC/DA,IAAI,CAACT,kBAAkB,GAAG;WAAIU;QAAUF;KAAa;AACvD;AAEA,OAAO,SAASG,iBACdf,QAA6C,EAC7CgB,UAAsB;QAGJH;IADlB,MAAMA,OAAOL,gBAAgBR;IAC7B,MAAMc,YAAYD,wBAAAA,IAAI,CAACR,gBAAgB,YAArBQ,wBAAsD,EAAE;IAC1EA,IAAI,CAACR,gBAAgB,GAAG;WAAIS;QAAUE;KAAW;AACnD;AAEA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,kBACdC,MAAkC,EAClCC,WAAwB;QASNC;QAPNC;IAAZ,MAAMD,OAAMC,mCAAAA,OAAOC,wBAAwB,CAACJ,QAAQnB,OAAOC,QAAQ,sBAAvDqB,iCACRE,KAAK;IACT,IAAI,CAACH,KAAK;QACR,MAAM,IAAIlB,kBACR,CAAC,UAAU,EAAEiB,YAAYK,GAAG,CAAC,UAAU,EAAEN,OAAOO,IAAI,CAAC,gHAAgH,CAAC;IAE1K;IACA,MAAMX,YAAYM,yBAAAA,GAAG,CAACd,kBAAkB,YAAtBc,yBAAwD,EAAE;IAC5EA,GAAG,CAACd,kBAAkB,GAAG;WAAIQ;QAAUK;KAAY;AACrD;AAEA;;;;;CAKC,GACD,OAAO,SAASO,sBACd1B,QAA6C,EAC7C2B,OAA2B;IAE3BnB,gBAAgBR,SAAS,CAACO,0BAA0B,GAAGoB;AACzD;AAEA,SAASC,aACPV,MAAwB;QAEjBA;IAAP,QAAOA,0BAAAA,MAAM,CAACnB,OAAOC,QAAQ,CAAC,YAAvBkB,0BAA2BW;AACpC;AAEA,OAAO,SAASC,kBACdZ,MAAkC;IAElC,MAAMlB,WAAW4B,aAAaV;IAC9B,MAAMR,aAAaV,4BAAAA,QAAU,CAACG,gBAAgB;IAC9C,IAAI,CAACO,YAAY;QACf,MAAM,IAAIR,kBAAkB,CAAC,yBAAyB,EAAEgB,OAAOO,IAAI,EAAE;IACvE;IACA,OAAOf;AACT;AAEA;;;;;;;CAOC,GACD,OAAO,SAASqB,qBAAqBb,MAAwB;;IAC3D,MAAMlB,WAAW4B,aAAaV;IAC9B,eAAQlB,4BAAAA,QAAU,CAACI,kBAAkB,mBAAmC,EAAE;AAC5E;AAEA,OAAO,SAAS4B,mBACdd,MAAkC;;IAElC,MAAMlB,WAAW4B,aAAaV;IAC9B,eAAQlB,4BAAAA,QAAU,CAACK,gBAAgB,mBAAiC,EAAE;AACxE;AAEA,4EAA4E,GAC5E,OAAO,SAAS4B,oBACdf,MAAkC;;IAElC,MAAMlB,WAAW4B,aAAaV;IAC9B,eAAQlB,4BAAAA,QAAU,CAACM,kBAAkB,mBAAkC,EAAE;AAC3E;AAEA,yDAAyD,GACzD,OAAO,SAAS4B,0BACdhB,MAA0B;IAE1B,MAAMlB,WAAW,AACfkB,MACD,CAACnB,OAAOC,QAAQ,CAAC;IAClB,MAAM2B,UAAU3B,4BAAAA,QAAU,CAACO,0BAA0B;IAErD,IAAI,CAACoB,SAAS;QACZ,MAAM,IAAIzB,kBACR,CAAC,0BAA0B,EAAEgB,OAAOO,IAAI,CAAC,4BAA4B,CAAC;IAE1E;IACA,OAAOE;AACT"}
1
+ {"version":3,"sources":["../../../src/entity-definition/helpers/entity-definition-helpers.ts"],"sourcesContent":["(Symbol as { metadata?: symbol }).metadata ??= Symbol.for('Symbol.metadata');\n\nimport { EntifixBuildError } from '../../base-entities/entifix-error/index.js';\nimport { Entity, EntityConstructor } from '../../types/Entity.js';\nimport { UseCaseConstructor } from '../../types/UseCase.js';\nimport {\n type ChildConstructor,\n MetaAccessor,\n} from '../meta-entities/meta-accessor/index.js';\nimport { MetaEntity } from '../meta-entities/meta-entity/index.js';\nimport { MetaMethod } from '../meta-entities/meta-method/index.js';\nimport {\n MetaUseCase,\n MetaUseCaseBinding,\n} from '../meta-entities/meta-use-case/index.js';\n\ndeclare global {\n interface SymbolConstructor {\n readonly metadata: unique symbol;\n }\n}\n\nexport const META_ENTITY_KEY = Symbol('MetaEntity');\nexport const META_ACCESSOR_KEY = Symbol('MetaAccessor');\nexport const META_METHOD_KEY = Symbol('MetaMethod');\nexport const META_USE_CASE_KEY = Symbol('MetaUseCase');\nexport const META_USE_CASE_BINDING_KEY = Symbol('MetaUseCaseBinding');\n\nfunction requireMetadata(\n metadata: DecoratorMetadataObject | undefined,\n): DecoratorMetadataObject {\n if (!metadata) {\n throw new EntifixBuildError(\n 'Decorator metadata unavailable — Symbol.metadata polyfill missing or stage-3 decorator emit not enabled.',\n );\n }\n return metadata;\n}\n\nexport function setMetaEntity(\n metadata: DecoratorMetadataObject | undefined,\n metaEntity: MetaEntity,\n) {\n requireMetadata(metadata)[META_ENTITY_KEY] = metaEntity;\n}\n\nexport function appendMetaAccessor(\n metadata: DecoratorMetadataObject | undefined,\n metaAccessor: MetaAccessor,\n) {\n const meta = requireMetadata(metadata);\n const existing =\n (meta[META_ACCESSOR_KEY] as MetaAccessor[] | undefined) ?? [];\n meta[META_ACCESSOR_KEY] = [...existing, metaAccessor];\n}\n\nexport function appendMetaMethod(\n metadata: DecoratorMetadataObject | undefined,\n metaMethod: MetaMethod,\n) {\n const meta = requireMetadata(metadata);\n const existing = (meta[META_METHOD_KEY] as MetaMethod[] | undefined) ?? [];\n meta[META_METHOD_KEY] = [...existing, metaMethod];\n}\n\n/**\n * Append a use case to the metadata of the entity it acts on.\n *\n * This is the one decorator that writes a bag other than its own: a use case is\n * declared on its own class, but the entity is what a surface holds, so the\n * descriptor has to end up there. The consequence is that a use-case module\n * which is never imported leaves the entity looking as if it had no actions,\n * which is why a host's register should assert every `@useCase()` class is\n * re-exported from its package barrel.\n *\n * It reads the target's **own** metadata rather than going through\n * {@link readMetadata}. `Symbol.metadata` resolves along the prototype chain, so\n * an inherited read would append the verb to a base class's bag and leak it to\n * every subclass. A target with no own metadata is an entity missing\n * `@entity()`, or a circular import that evaluated the use case first.\n */\nexport function appendMetaUseCase<TEntity extends Entity>(\n target: EntityConstructor<TEntity>,\n metaUseCase: MetaUseCase,\n) {\n const own = Object.getOwnPropertyDescriptor(target, Symbol.metadata)\n ?.value as DecoratorMetadataObject | null | undefined;\n if (!own) {\n throw new EntifixBuildError(\n `Use case \"${metaUseCase.key}\" targets ${target.name}, which has no metadata of its own — it is missing @entity(), or a circular import evaluated the use case first.`,\n );\n }\n const existing = (own[META_USE_CASE_KEY] as MetaUseCase[] | undefined) ?? [];\n own[META_USE_CASE_KEY] = [...existing, metaUseCase];\n}\n\n/**\n * Record on the use-case class itself which entity and verb it implements, so\n * the permission can be derived from the class alone. That is what keeps the\n * verb string written once, at the declaration, instead of being retyped at\n * every guard.\n */\nexport function setMetaUseCaseBinding(\n metadata: DecoratorMetadataObject | undefined,\n binding: MetaUseCaseBinding,\n) {\n requireMetadata(metadata)[META_USE_CASE_BINDING_KEY] = binding;\n}\n\nfunction readMetadata(\n target: ChildConstructor,\n): DecoratorMetadataObject | undefined {\n return target[Symbol.metadata] ?? undefined;\n}\n\nexport function extractMetaEntity<TEntity extends Entity>(\n target: EntityConstructor<TEntity>,\n): MetaEntity {\n const metadata = readMetadata(target);\n const metaEntity = metadata?.[META_ENTITY_KEY] as MetaEntity | undefined;\n if (!metaEntity) {\n throw new EntifixBuildError(`MetaEntity not found for ${target.name}`);\n }\n return metaEntity;\n}\n\n/**\n * Every accessor declared on a class.\n *\n * The target is a {@link ChildConstructor}, not an `EntityConstructor`: an\n * `@accessor()` writes to its own class's metadata bag with no help from\n * `@entity()`, so a `composition` member's child — a value with no `id` — is\n * described by exactly this walk.\n */\nexport function extractMetaAccessors(target: ChildConstructor): MetaAccessor[] {\n const metadata = readMetadata(target);\n return (metadata?.[META_ACCESSOR_KEY] as MetaAccessor[] | undefined) ?? [];\n}\n\nexport function extractMetaMethods<TEntity extends Entity>(\n target: EntityConstructor<TEntity>,\n): MetaMethod[] {\n const metadata = readMetadata(target);\n return (metadata?.[META_METHOD_KEY] as MetaMethod[] | undefined) ?? [];\n}\n\n/** Every use case declared against an entity. Empty until one is imported. */\nexport function extractMetaUseCases<TEntity extends Entity>(\n target: EntityConstructor<TEntity>,\n): MetaUseCase[] {\n const metadata = readMetadata(target);\n return (metadata?.[META_USE_CASE_KEY] as MetaUseCase[] | undefined) ?? [];\n}\n\n/** The entity and verb a `@useCase()` class implements. */\nexport function extractMetaUseCaseBinding(\n target: UseCaseConstructor,\n): MetaUseCaseBinding {\n const metadata = (\n target as { [Symbol.metadata]?: DecoratorMetadataObject | null }\n )[Symbol.metadata];\n const binding = metadata?.[META_USE_CASE_BINDING_KEY] as\n MetaUseCaseBinding | undefined;\n if (!binding) {\n throw new EntifixBuildError(\n `MetaUseCase not found for ${target.name} — is it missing @useCase()?`,\n );\n }\n return binding;\n}\n"],"names":["Symbol","metadata","for","EntifixBuildError","META_ENTITY_KEY","META_ACCESSOR_KEY","META_METHOD_KEY","META_USE_CASE_KEY","META_USE_CASE_BINDING_KEY","requireMetadata","setMetaEntity","metaEntity","appendMetaAccessor","metaAccessor","meta","existing","appendMetaMethod","metaMethod","appendMetaUseCase","target","metaUseCase","own","Object","getOwnPropertyDescriptor","value","key","name","setMetaUseCaseBinding","binding","readMetadata","undefined","extractMetaEntity","extractMetaAccessors","extractMetaMethods","extractMetaUseCases","extractMetaUseCaseBinding"],"mappings":"IAACA,SAAD;CAAA,YAAA,CAACA,UAAAA,QAAiCC,QAAQ,YAA1C,YAAA,AAACD,QAAiCC,QAAQ,GAAKD,OAAOE,GAAG,CAAC;AAE1D,SAASC,iBAAiB,QAAQ,6CAA6C;AAoB/E,OAAO,MAAMC,kBAAkBJ,OAAO,cAAc;AACpD,OAAO,MAAMK,oBAAoBL,OAAO,gBAAgB;AACxD,OAAO,MAAMM,kBAAkBN,OAAO,cAAc;AACpD,OAAO,MAAMO,oBAAoBP,OAAO,eAAe;AACvD,OAAO,MAAMQ,4BAA4BR,OAAO,sBAAsB;AAEtE,SAASS,gBACPR,QAA6C;IAE7C,IAAI,CAACA,UAAU;QACb,MAAM,IAAIE,kBACR;IAEJ;IACA,OAAOF;AACT;AAEA,OAAO,SAASS,cACdT,QAA6C,EAC7CU,UAAsB;IAEtBF,gBAAgBR,SAAS,CAACG,gBAAgB,GAAGO;AAC/C;AAEA,OAAO,SAASC,mBACdX,QAA6C,EAC7CY,YAA0B;QAIvBC;IAFH,MAAMA,OAAOL,gBAAgBR;IAC7B,MAAMc,YACHD,0BAAAA,IAAI,CAACT,kBAAkB,YAAvBS,0BAA0D,EAAE;IAC/DA,IAAI,CAACT,kBAAkB,GAAG;WAAIU;QAAUF;KAAa;AACvD;AAEA,OAAO,SAASG,iBACdf,QAA6C,EAC7CgB,UAAsB;QAGJH;IADlB,MAAMA,OAAOL,gBAAgBR;IAC7B,MAAMc,YAAYD,wBAAAA,IAAI,CAACR,gBAAgB,YAArBQ,wBAAsD,EAAE;IAC1EA,IAAI,CAACR,gBAAgB,GAAG;WAAIS;QAAUE;KAAW;AACnD;AAEA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,kBACdC,MAAkC,EAClCC,WAAwB;QASNC;QAPNC;IAAZ,MAAMD,OAAMC,mCAAAA,OAAOC,wBAAwB,CAACJ,QAAQnB,OAAOC,QAAQ,sBAAvDqB,iCACRE,KAAK;IACT,IAAI,CAACH,KAAK;QACR,MAAM,IAAIlB,kBACR,CAAC,UAAU,EAAEiB,YAAYK,GAAG,CAAC,UAAU,EAAEN,OAAOO,IAAI,CAAC,gHAAgH,CAAC;IAE1K;IACA,MAAMX,YAAYM,yBAAAA,GAAG,CAACd,kBAAkB,YAAtBc,yBAAwD,EAAE;IAC5EA,GAAG,CAACd,kBAAkB,GAAG;WAAIQ;QAAUK;KAAY;AACrD;AAEA;;;;;CAKC,GACD,OAAO,SAASO,sBACd1B,QAA6C,EAC7C2B,OAA2B;IAE3BnB,gBAAgBR,SAAS,CAACO,0BAA0B,GAAGoB;AACzD;AAEA,SAASC,aACPV,MAAwB;QAEjBA;IAAP,QAAOA,0BAAAA,MAAM,CAACnB,OAAOC,QAAQ,CAAC,YAAvBkB,0BAA2BW;AACpC;AAEA,OAAO,SAASC,kBACdZ,MAAkC;IAElC,MAAMlB,WAAW4B,aAAaV;IAC9B,MAAMR,aAAaV,4BAAAA,QAAU,CAACG,gBAAgB;IAC9C,IAAI,CAACO,YAAY;QACf,MAAM,IAAIR,kBAAkB,CAAC,yBAAyB,EAAEgB,OAAOO,IAAI,EAAE;IACvE;IACA,OAAOf;AACT;AAEA;;;;;;;CAOC,GACD,OAAO,SAASqB,qBAAqBb,MAAwB;;IAC3D,MAAMlB,WAAW4B,aAAaV;IAC9B,eAAQlB,4BAAAA,QAAU,CAACI,kBAAkB,mBAAmC,EAAE;AAC5E;AAEA,OAAO,SAAS4B,mBACdd,MAAkC;;IAElC,MAAMlB,WAAW4B,aAAaV;IAC9B,eAAQlB,4BAAAA,QAAU,CAACK,gBAAgB,mBAAiC,EAAE;AACxE;AAEA,4EAA4E,GAC5E,OAAO,SAAS4B,oBACdf,MAAkC;;IAElC,MAAMlB,WAAW4B,aAAaV;IAC9B,eAAQlB,4BAAAA,QAAU,CAACM,kBAAkB,mBAAkC,EAAE;AAC3E;AAEA,yDAAyD,GACzD,OAAO,SAAS4B,0BACdhB,MAA0B;IAE1B,MAAMlB,WAAW,AACfkB,MACD,CAACnB,OAAOC,QAAQ,CAAC;IAClB,MAAM2B,UAAU3B,4BAAAA,QAAU,CAACO,0BAA0B;IAErD,IAAI,CAACoB,SAAS;QACZ,MAAM,IAAIzB,kBACR,CAAC,0BAA0B,EAAEgB,OAAOO,IAAI,CAAC,4BAA4B,CAAC;IAE1E;IACA,OAAOE;AACT"}
@@ -23,8 +23,8 @@ export type MetaAccessorType = (typeof MetaAccessorTypes)[number];
23
23
  * ⚠️ It is a **shape declaration, not a runtime constructor contract.** Children
24
24
  * arrive off the wire as plain objects — the serializer walks `instanceof
25
25
  * EntityLink`, not the declared type, so an embedded array passes straight
26
- * through — and ADR 0032 forbids a class instance inside an autosaved draft
27
- * anyway. Nothing may branch on `instanceof` against this constructor.
26
+ * through — and an autosaved draft may hold nothing that does not survive a
27
+ * JSON round trip anyway. Nothing may branch on `instanceof` against this constructor.
28
28
  */
29
29
  export type ChildConstructor<TChild extends object = object> = new (...args: any[]) => TChild;
30
30
  /**
@@ -104,8 +104,8 @@ export interface MetaAccessorOptions {
104
104
  * Reset this member when the record is copied.
105
105
  *
106
106
  * It lives on the **accessor**, not on a use case's descriptor, and that is a
107
- * decision already on file: ADR 0026 closed `UseCaseDescriptor` against
108
- * per-verb payloads and named this exact case, because a descriptor that
107
+ * decision already made: `UseCaseDescriptor` is closed against per-verb
108
+ * payloads, and this is the exact case, because a descriptor that
109
109
  * grows a field per verb ends up a union of every verb's arguments. Which
110
110
  * members a copy must not carry is a property of the *member* — a unique
111
111
  * code, an audit stamp, a sequence number — and it is the same answer however
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/entity-definition/meta-entities/meta-accessor/meta-accessor.ts"],"sourcesContent":["export type MetaAccessorKind = 'getter' | 'setter';\n\n/**\n * The kinds of value an accessor can hold, as far as generic UI and adapters\n * care. It is deliberately a presentation/behaviour taxonomy rather than a\n * TypeScript mirror: `id` and `enum` are strings at runtime but are rendered,\n * sorted and filtered differently.\n *\n * `localized` holds a text per locale (`LocalizedText`). It is queried only\n * through one locale at a time — `name.en` — never as the whole object.\n */\nexport const MetaAccessorTypes = [\n 'string',\n 'number',\n 'boolean',\n 'date',\n 'enum',\n 'id',\n 'link',\n 'linkCollection',\n 'composition',\n 'scalarCollection',\n 'localized',\n] as const;\nexport type MetaAccessorType = (typeof MetaAccessorTypes)[number];\n\n/**\n * The class whose accessors describe one row of a `composition` member.\n *\n * Deliberately not `EntityConstructor`: a child of a composition is a **value**,\n * not an entity. It has no identity of its own, is never addressed apart from\n * the master that holds it, and therefore has no `id` for `Entity` to require.\n * What it does have is `@accessor()` metadata, which is written to its own\n * class's `Symbol.metadata` bag independently of `@entity()` — so a child is\n * described by its accessors and by nothing else.\n *\n * ⚠️ It is a **shape declaration, not a runtime constructor contract.** Children\n * arrive off the wire as plain objects — the serializer walks `instanceof\n * EntityLink`, not the declared type, so an embedded array passes straight\n * through — and ADR 0032 forbids a class instance inside an autosaved draft\n * anyway. Nothing may branch on `instanceof` against this constructor.\n */\nexport type ChildConstructor<TChild extends object = object> = new (\n // Mirrors `EntityConstructor`: the arguments are the child's business, only\n // the class identity matters here.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...args: any[]\n) => TChild;\n\n/**\n * The wire shape a relation takes: the target's foreign key, or the whole\n * target inlined. Declared here rather than next to `EntityLink` because it is\n * metadata about a member, not state of a link instance.\n */\nexport const EntityLinkSerializations = ['id', 'embedded'] as const;\nexport type EntityLinkSerialization = (typeof EntityLinkSerializations)[number];\n\nexport interface MetaAccessorOptions {\n alias?: string;\n type?: MetaAccessorType;\n readonly?: boolean;\n hidden?: boolean;\n /** Member must hold a value. Drives form validation; defaults to optional. */\n required?: boolean;\n /** Human label for UI. Falls back to a humanized `name`. */\n label?: string;\n /**\n * Translation key for the label, resolved by the presentation layer. Takes\n * precedence over `label`, which stays as the untranslated fallback for a\n * member that has no catalog entry yet.\n *\n * Keys mirror the entity's own `@entity({ key })`, so they are derivable\n * rather than invented: `entity:product-specification.fields.code`.\n */\n labelKey?: string;\n /** Opt in/out of sorting controls. Defaults per `type`. */\n sortable?: boolean;\n /** Opt in/out of filtering controls. Defaults per `type`. */\n filterable?: boolean;\n /** Default position among the entity's members; ties keep declaration order. */\n order?: number;\n /** Allowed values when `type` is `enum` — drives the filter value control. */\n enumValues?: readonly string[];\n /**\n * Catalog prefix for those values; a value's label is `${enumLabelKey}.${value}`.\n * Explicit rather than derived from `labelKey`, so a member whose values are\n * shared with another member can point both at one vocabulary.\n */\n enumLabelKey?: string;\n /** Property of a `link` target used as its display label. Default `name`. */\n linkLabelProperty?: string;\n /**\n * Property of a `link` target a picker searches on. Defaults to\n * {@link linkLabelProperty} — you search for what you read. It must be\n * `filterable` on the target entity, since member metadata is also the\n * server-side filter allowlist.\n */\n linkSearchProperty?: string;\n /**\n * How a `link` travels when its owner is serialized: as the target's scalar\n * foreign key (`id`, the default) or with the whole target inlined\n * (`embedded`). The serializer decides per *link instance* — it inlines\n * whatever `isLoaded` — so without this the shape a write takes would depend\n * on whether the form happened to hold the target. Declaring it on the entity\n * makes the wire shape a property of the relation instead of an accident of\n * the UI.\n */\n linkSerialization?: EntityLinkSerialization;\n /**\n * The class describing one row of a `composition` member — the child's own\n * `@accessor()` metadata is what a detail grid renders its columns from.\n *\n * A **thunk**, because a child module and the entity that owns it are usually\n * declared in the same package and a direct reference makes the decorator's\n * evaluation order load-bearing. Deferring the read to first use removes the\n * ordering question entirely, which is the same reason a to-one relation's\n * target is reached through a resolver rather than an import.\n *\n * Meaningless on any other type. There is no inference fallback: an empty\n * array cannot be told apart from an empty `string[]` at runtime, so a\n * collection must be declared.\n */\n childType?: () => ChildConstructor;\n /**\n * Reset this member when the record is copied.\n *\n * It lives on the **accessor**, not on a use case's descriptor, and that is a\n * decision already on file: ADR 0026 closed `UseCaseDescriptor` against\n * per-verb payloads and named this exact case, because a descriptor that\n * grows a field per verb ends up a union of every verb's arguments. Which\n * members a copy must not carry is a property of the *member* — a unique\n * code, an audit stamp, a sequence number — and it is the same answer however\n * the copy is triggered.\n *\n * The identity member is always reset and needs no flag.\n */\n resetOnClone?: boolean;\n /**\n * The fewest values a collection member may hold — `1` for \"not empty\". Only\n * meaningful on a collection type (`scalarCollection`, `linkCollection`,\n * `composition`); declaring it elsewhere fails when the entity is described.\n *\n * Distinct from {@link required}: on a collection `required` already means\n * \"at least one\", and a `minItems` of two is a fact `required` cannot state.\n */\n minItems?: number;\n /** The most values a collection member may hold. Collection types only. */\n maxItems?: number;\n /**\n * Another `date` member this one may not precede — a period's `end` declares\n * `notBefore: 'start'`. Checked only when both hold a value. Both members must\n * be `date`s, or describing the entity fails.\n */\n notBefore?: string;\n}\n\nexport class MetaAccessor {\n //#region Properties\n\n readonly name: string | symbol;\n readonly kind: MetaAccessorKind;\n readonly alias?: string;\n readonly type?: MetaAccessorType;\n readonly readonly?: boolean;\n readonly hidden?: boolean;\n readonly required?: boolean;\n readonly label?: string;\n readonly labelKey?: string;\n readonly sortable?: boolean;\n readonly filterable?: boolean;\n readonly order?: number;\n readonly enumValues?: readonly string[];\n readonly enumLabelKey?: string;\n readonly linkLabelProperty?: string;\n readonly linkSearchProperty?: string;\n readonly linkSerialization?: EntityLinkSerialization;\n readonly childType?: () => ChildConstructor;\n readonly resetOnClone?: boolean;\n readonly minItems?: number;\n readonly maxItems?: number;\n readonly notBefore?: string;\n\n //#endregion\n\n //#region Constructors\n constructor(\n name: string | symbol,\n kind: MetaAccessorKind,\n options?: MetaAccessorOptions,\n ) {\n this.name = name;\n this.kind = kind;\n this.alias = options?.alias;\n this.type = options?.type;\n this.readonly = options?.readonly;\n this.hidden = options?.hidden;\n this.required = options?.required;\n this.label = options?.label;\n this.labelKey = options?.labelKey;\n this.sortable = options?.sortable;\n this.filterable = options?.filterable;\n this.order = options?.order;\n this.enumValues = options?.enumValues;\n this.enumLabelKey = options?.enumLabelKey;\n this.linkLabelProperty = options?.linkLabelProperty;\n this.linkSearchProperty = options?.linkSearchProperty;\n this.linkSerialization = options?.linkSerialization;\n this.childType = options?.childType;\n this.resetOnClone = options?.resetOnClone;\n this.minItems = options?.minItems;\n this.maxItems = options?.maxItems;\n this.notBefore = options?.notBefore;\n }\n //#endregion\n\n //#region Methods\n //#endregion\n\n //#region Accessors\n //#endregion\n}\n"],"names":["MetaAccessorTypes","EntityLinkSerializations","MetaAccessor","name","kind","options","alias","type","readonly","hidden","required","label","labelKey","sortable","filterable","order","enumValues","enumLabelKey","linkLabelProperty","linkSearchProperty","linkSerialization","childType","resetOnClone","minItems","maxItems","notBefore"],"mappings":"AAEA;;;;;;;;CAQC,GACD,OAAO,MAAMA,oBAAoB;IAC/B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAU;AA0BX;;;;CAIC,GACD,OAAO,MAAMC,2BAA2B;IAAC;IAAM;CAAW,CAAU;AAsGpE,OAAO,MAAMC;IA0BX,YAAY;IAEZ,sBAAsB;IACtB,YACEC,IAAqB,EACrBC,IAAsB,EACtBC,OAA6B,CAC7B;QACA,IAAI,CAACF,IAAI,GAAGA;QACZ,IAAI,CAACC,IAAI,GAAGA;QACZ,IAAI,CAACE,KAAK,GAAGD,2BAAAA,QAASC,KAAK;QAC3B,IAAI,CAACC,IAAI,GAAGF,2BAAAA,QAASE,IAAI;QACzB,IAAI,CAACC,QAAQ,GAAGH,2BAAAA,QAASG,QAAQ;QACjC,IAAI,CAACC,MAAM,GAAGJ,2BAAAA,QAASI,MAAM;QAC7B,IAAI,CAACC,QAAQ,GAAGL,2BAAAA,QAASK,QAAQ;QACjC,IAAI,CAACC,KAAK,GAAGN,2BAAAA,QAASM,KAAK;QAC3B,IAAI,CAACC,QAAQ,GAAGP,2BAAAA,QAASO,QAAQ;QACjC,IAAI,CAACC,QAAQ,GAAGR,2BAAAA,QAASQ,QAAQ;QACjC,IAAI,CAACC,UAAU,GAAGT,2BAAAA,QAASS,UAAU;QACrC,IAAI,CAACC,KAAK,GAAGV,2BAAAA,QAASU,KAAK;QAC3B,IAAI,CAACC,UAAU,GAAGX,2BAAAA,QAASW,UAAU;QACrC,IAAI,CAACC,YAAY,GAAGZ,2BAAAA,QAASY,YAAY;QACzC,IAAI,CAACC,iBAAiB,GAAGb,2BAAAA,QAASa,iBAAiB;QACnD,IAAI,CAACC,kBAAkB,GAAGd,2BAAAA,QAASc,kBAAkB;QACrD,IAAI,CAACC,iBAAiB,GAAGf,2BAAAA,QAASe,iBAAiB;QACnD,IAAI,CAACC,SAAS,GAAGhB,2BAAAA,QAASgB,SAAS;QACnC,IAAI,CAACC,YAAY,GAAGjB,2BAAAA,QAASiB,YAAY;QACzC,IAAI,CAACC,QAAQ,GAAGlB,2BAAAA,QAASkB,QAAQ;QACjC,IAAI,CAACC,QAAQ,GAAGnB,2BAAAA,QAASmB,QAAQ;QACjC,IAAI,CAACC,SAAS,GAAGpB,2BAAAA,QAASoB,SAAS;IACrC;AAQF"}
1
+ {"version":3,"sources":["../../../../src/entity-definition/meta-entities/meta-accessor/meta-accessor.ts"],"sourcesContent":["export type MetaAccessorKind = 'getter' | 'setter';\n\n/**\n * The kinds of value an accessor can hold, as far as generic UI and adapters\n * care. It is deliberately a presentation/behaviour taxonomy rather than a\n * TypeScript mirror: `id` and `enum` are strings at runtime but are rendered,\n * sorted and filtered differently.\n *\n * `localized` holds a text per locale (`LocalizedText`). It is queried only\n * through one locale at a time — `name.en` — never as the whole object.\n */\nexport const MetaAccessorTypes = [\n 'string',\n 'number',\n 'boolean',\n 'date',\n 'enum',\n 'id',\n 'link',\n 'linkCollection',\n 'composition',\n 'scalarCollection',\n 'localized',\n] as const;\nexport type MetaAccessorType = (typeof MetaAccessorTypes)[number];\n\n/**\n * The class whose accessors describe one row of a `composition` member.\n *\n * Deliberately not `EntityConstructor`: a child of a composition is a **value**,\n * not an entity. It has no identity of its own, is never addressed apart from\n * the master that holds it, and therefore has no `id` for `Entity` to require.\n * What it does have is `@accessor()` metadata, which is written to its own\n * class's `Symbol.metadata` bag independently of `@entity()` — so a child is\n * described by its accessors and by nothing else.\n *\n * ⚠️ It is a **shape declaration, not a runtime constructor contract.** Children\n * arrive off the wire as plain objects — the serializer walks `instanceof\n * EntityLink`, not the declared type, so an embedded array passes straight\n * through — and an autosaved draft may hold nothing that does not survive a\n * JSON round trip anyway. Nothing may branch on `instanceof` against this constructor.\n */\nexport type ChildConstructor<TChild extends object = object> = new (\n // Mirrors `EntityConstructor`: the arguments are the child's business, only\n // the class identity matters here.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...args: any[]\n) => TChild;\n\n/**\n * The wire shape a relation takes: the target's foreign key, or the whole\n * target inlined. Declared here rather than next to `EntityLink` because it is\n * metadata about a member, not state of a link instance.\n */\nexport const EntityLinkSerializations = ['id', 'embedded'] as const;\nexport type EntityLinkSerialization = (typeof EntityLinkSerializations)[number];\n\nexport interface MetaAccessorOptions {\n alias?: string;\n type?: MetaAccessorType;\n readonly?: boolean;\n hidden?: boolean;\n /** Member must hold a value. Drives form validation; defaults to optional. */\n required?: boolean;\n /** Human label for UI. Falls back to a humanized `name`. */\n label?: string;\n /**\n * Translation key for the label, resolved by the presentation layer. Takes\n * precedence over `label`, which stays as the untranslated fallback for a\n * member that has no catalog entry yet.\n *\n * Keys mirror the entity's own `@entity({ key })`, so they are derivable\n * rather than invented: `entity:product-specification.fields.code`.\n */\n labelKey?: string;\n /** Opt in/out of sorting controls. Defaults per `type`. */\n sortable?: boolean;\n /** Opt in/out of filtering controls. Defaults per `type`. */\n filterable?: boolean;\n /** Default position among the entity's members; ties keep declaration order. */\n order?: number;\n /** Allowed values when `type` is `enum` — drives the filter value control. */\n enumValues?: readonly string[];\n /**\n * Catalog prefix for those values; a value's label is `${enumLabelKey}.${value}`.\n * Explicit rather than derived from `labelKey`, so a member whose values are\n * shared with another member can point both at one vocabulary.\n */\n enumLabelKey?: string;\n /** Property of a `link` target used as its display label. Default `name`. */\n linkLabelProperty?: string;\n /**\n * Property of a `link` target a picker searches on. Defaults to\n * {@link linkLabelProperty} — you search for what you read. It must be\n * `filterable` on the target entity, since member metadata is also the\n * server-side filter allowlist.\n */\n linkSearchProperty?: string;\n /**\n * How a `link` travels when its owner is serialized: as the target's scalar\n * foreign key (`id`, the default) or with the whole target inlined\n * (`embedded`). The serializer decides per *link instance* — it inlines\n * whatever `isLoaded` — so without this the shape a write takes would depend\n * on whether the form happened to hold the target. Declaring it on the entity\n * makes the wire shape a property of the relation instead of an accident of\n * the UI.\n */\n linkSerialization?: EntityLinkSerialization;\n /**\n * The class describing one row of a `composition` member — the child's own\n * `@accessor()` metadata is what a detail grid renders its columns from.\n *\n * A **thunk**, because a child module and the entity that owns it are usually\n * declared in the same package and a direct reference makes the decorator's\n * evaluation order load-bearing. Deferring the read to first use removes the\n * ordering question entirely, which is the same reason a to-one relation's\n * target is reached through a resolver rather than an import.\n *\n * Meaningless on any other type. There is no inference fallback: an empty\n * array cannot be told apart from an empty `string[]` at runtime, so a\n * collection must be declared.\n */\n childType?: () => ChildConstructor;\n /**\n * Reset this member when the record is copied.\n *\n * It lives on the **accessor**, not on a use case's descriptor, and that is a\n * decision already made: `UseCaseDescriptor` is closed against per-verb\n * payloads, and this is the exact case, because a descriptor that\n * grows a field per verb ends up a union of every verb's arguments. Which\n * members a copy must not carry is a property of the *member* — a unique\n * code, an audit stamp, a sequence number — and it is the same answer however\n * the copy is triggered.\n *\n * The identity member is always reset and needs no flag.\n */\n resetOnClone?: boolean;\n /**\n * The fewest values a collection member may hold — `1` for \"not empty\". Only\n * meaningful on a collection type (`scalarCollection`, `linkCollection`,\n * `composition`); declaring it elsewhere fails when the entity is described.\n *\n * Distinct from {@link required}: on a collection `required` already means\n * \"at least one\", and a `minItems` of two is a fact `required` cannot state.\n */\n minItems?: number;\n /** The most values a collection member may hold. Collection types only. */\n maxItems?: number;\n /**\n * Another `date` member this one may not precede — a period's `end` declares\n * `notBefore: 'start'`. Checked only when both hold a value. Both members must\n * be `date`s, or describing the entity fails.\n */\n notBefore?: string;\n}\n\nexport class MetaAccessor {\n //#region Properties\n\n readonly name: string | symbol;\n readonly kind: MetaAccessorKind;\n readonly alias?: string;\n readonly type?: MetaAccessorType;\n readonly readonly?: boolean;\n readonly hidden?: boolean;\n readonly required?: boolean;\n readonly label?: string;\n readonly labelKey?: string;\n readonly sortable?: boolean;\n readonly filterable?: boolean;\n readonly order?: number;\n readonly enumValues?: readonly string[];\n readonly enumLabelKey?: string;\n readonly linkLabelProperty?: string;\n readonly linkSearchProperty?: string;\n readonly linkSerialization?: EntityLinkSerialization;\n readonly childType?: () => ChildConstructor;\n readonly resetOnClone?: boolean;\n readonly minItems?: number;\n readonly maxItems?: number;\n readonly notBefore?: string;\n\n //#endregion\n\n //#region Constructors\n constructor(\n name: string | symbol,\n kind: MetaAccessorKind,\n options?: MetaAccessorOptions,\n ) {\n this.name = name;\n this.kind = kind;\n this.alias = options?.alias;\n this.type = options?.type;\n this.readonly = options?.readonly;\n this.hidden = options?.hidden;\n this.required = options?.required;\n this.label = options?.label;\n this.labelKey = options?.labelKey;\n this.sortable = options?.sortable;\n this.filterable = options?.filterable;\n this.order = options?.order;\n this.enumValues = options?.enumValues;\n this.enumLabelKey = options?.enumLabelKey;\n this.linkLabelProperty = options?.linkLabelProperty;\n this.linkSearchProperty = options?.linkSearchProperty;\n this.linkSerialization = options?.linkSerialization;\n this.childType = options?.childType;\n this.resetOnClone = options?.resetOnClone;\n this.minItems = options?.minItems;\n this.maxItems = options?.maxItems;\n this.notBefore = options?.notBefore;\n }\n //#endregion\n\n //#region Methods\n //#endregion\n\n //#region Accessors\n //#endregion\n}\n"],"names":["MetaAccessorTypes","EntityLinkSerializations","MetaAccessor","name","kind","options","alias","type","readonly","hidden","required","label","labelKey","sortable","filterable","order","enumValues","enumLabelKey","linkLabelProperty","linkSearchProperty","linkSerialization","childType","resetOnClone","minItems","maxItems","notBefore"],"mappings":"AAEA;;;;;;;;CAQC,GACD,OAAO,MAAMA,oBAAoB;IAC/B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAU;AA0BX;;;;CAIC,GACD,OAAO,MAAMC,2BAA2B;IAAC;IAAM;CAAW,CAAU;AAsGpE,OAAO,MAAMC;IA0BX,YAAY;IAEZ,sBAAsB;IACtB,YACEC,IAAqB,EACrBC,IAAsB,EACtBC,OAA6B,CAC7B;QACA,IAAI,CAACF,IAAI,GAAGA;QACZ,IAAI,CAACC,IAAI,GAAGA;QACZ,IAAI,CAACE,KAAK,GAAGD,2BAAAA,QAASC,KAAK;QAC3B,IAAI,CAACC,IAAI,GAAGF,2BAAAA,QAASE,IAAI;QACzB,IAAI,CAACC,QAAQ,GAAGH,2BAAAA,QAASG,QAAQ;QACjC,IAAI,CAACC,MAAM,GAAGJ,2BAAAA,QAASI,MAAM;QAC7B,IAAI,CAACC,QAAQ,GAAGL,2BAAAA,QAASK,QAAQ;QACjC,IAAI,CAACC,KAAK,GAAGN,2BAAAA,QAASM,KAAK;QAC3B,IAAI,CAACC,QAAQ,GAAGP,2BAAAA,QAASO,QAAQ;QACjC,IAAI,CAACC,QAAQ,GAAGR,2BAAAA,QAASQ,QAAQ;QACjC,IAAI,CAACC,UAAU,GAAGT,2BAAAA,QAASS,UAAU;QACrC,IAAI,CAACC,KAAK,GAAGV,2BAAAA,QAASU,KAAK;QAC3B,IAAI,CAACC,UAAU,GAAGX,2BAAAA,QAASW,UAAU;QACrC,IAAI,CAACC,YAAY,GAAGZ,2BAAAA,QAASY,YAAY;QACzC,IAAI,CAACC,iBAAiB,GAAGb,2BAAAA,QAASa,iBAAiB;QACnD,IAAI,CAACC,kBAAkB,GAAGd,2BAAAA,QAASc,kBAAkB;QACrD,IAAI,CAACC,iBAAiB,GAAGf,2BAAAA,QAASe,iBAAiB;QACnD,IAAI,CAACC,SAAS,GAAGhB,2BAAAA,QAASgB,SAAS;QACnC,IAAI,CAACC,YAAY,GAAGjB,2BAAAA,QAASiB,YAAY;QACzC,IAAI,CAACC,QAAQ,GAAGlB,2BAAAA,QAASkB,QAAQ;QACjC,IAAI,CAACC,QAAQ,GAAGnB,2BAAAA,QAASmB,QAAQ;QACjC,IAAI,CAACC,SAAS,GAAGpB,2BAAAA,QAASoB,SAAS;IACrC;AAQF"}
@@ -56,7 +56,7 @@ export interface MetaUseCaseOptions<TKey extends string, TEntity extends Entity>
56
56
  * refuses anyway, since a verb key is the third segment of one permission and
57
57
  * two classes cannot share a key. Three classes would have meant three
58
58
  * permissions for one act, and a grant that let somebody publish one offering
59
- * but not twenty (#216).
59
+ * but not twenty.
60
60
  *
61
61
  * The primary cell stays where it is so that nothing already declared
62
62
  * changes, and so a descriptor still reads as "this verb lives here" with the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/entity-definition/meta-entities/meta-use-case/meta-use-case.ts"],"sourcesContent":["import type { Entity, EntityConstructor } from '../../../types/Entity.js';\n\n/** What a use case acts on: one record, a selection, or nothing. */\nexport type UseCaseBinding = 'entity' | 'collection' | 'unbound';\n\n/**\n * Where a surface puts the action. Deliberately **not** derivable from\n * {@link UseCaseBinding}: an entity-bound action can be determining (a footer\n * \"Publish\" that finalizes the page) or context-independent (a toolbar action\n * available whenever a record is open). Collapsing the two would force every\n * surface to re-derive a placement the author already knew.\n */\nexport type UseCasePlacement =\n 'context-dependent' | 'context-independent' | 'determining';\n\n/**\n * One place a verb can appear: a binding and a placement together.\n *\n * The pair is the unit rather than either half, because the two answer\n * different questions — placement decides the *surface* and binding decides the\n * *payload* — and a verb reachable from a row and from a selection differs in\n * both at once.\n */\nexport interface UseCaseCell {\n binding: UseCaseBinding;\n placement: UseCasePlacement;\n}\n\n/** Confirmation a surface must obtain before running the use case. */\nexport interface UseCaseConfirm {\n tone: 'destructive' | 'neutral';\n /** Catalog key. Copy never lives in a descriptor. */\n messageKey: string;\n}\n\n/**\n * What `@useCase()` declares.\n *\n * `entity` is the target the verb is registered against — it is what the\n * decorator appends to, and it is not part of the served descriptor.\n *\n * `labelKey` is mandatory and namespace-qualified, the `GuardedNavItem`\n * convention: i18n is mandatory and `react/jsx-no-literals` fails the build on a\n * string written into JSX, so a descriptor carrying copy could not be rendered.\n *\n * `keywordsKey` is a catalog key rather than a `string[]` so the command palette\n * matches across locales — a user typing an English term reaches a Spanish\n * command.\n */\nexport interface MetaUseCaseOptions<\n TKey extends string,\n TEntity extends Entity,\n> {\n entity: EntityConstructor<TEntity>;\n key: TKey;\n binding: UseCaseBinding;\n placement: UseCasePlacement;\n /**\n * Further places the same verb appears, beyond the {@link binding} and\n * {@link placement} above.\n *\n * ⚠️ **One verb, one permission, several surfaces.** Publishing an offering\n * from its form, from a row menu and over a selection is one act reached three\n * ways, and the alternative was three `@useCase()` classes — which `slices`\n * refuses anyway, since a verb key is the third segment of one permission and\n * two classes cannot share a key. Three classes would have meant three\n * permissions for one act, and a grant that let somebody publish one offering\n * but not twenty (#216).\n *\n * The primary cell stays where it is so that nothing already declared\n * changes, and so a descriptor still reads as \"this verb lives here\" with the\n * rest as additions.\n */\n alsoAt?: readonly UseCaseCell[];\n labelKey: string;\n keywordsKey?: string;\n confirm?: UseCaseConfirm;\n /** Names a form a surface should open instead of acting immediately. */\n form?: string;\n}\n\n/**\n * What the use-case class's **own** metadata carries, as opposed to what is\n * appended to the entity. Two fields are enough to derive the permission, which\n * is what lets `permissionForUseCase(SomeUC)` take a single argument and keeps\n * the verb string written exactly once.\n */\nexport interface MetaUseCaseBinding {\n entity: EntityConstructor<Entity>;\n key: string;\n}\n\nexport class MetaUseCase {\n //#region Properties\n readonly entity: EntityConstructor<Entity>;\n readonly key: string;\n readonly binding: UseCaseBinding;\n readonly placement: UseCasePlacement;\n readonly alsoAt?: readonly UseCaseCell[];\n readonly labelKey: string;\n readonly keywordsKey?: string;\n readonly confirm?: UseCaseConfirm;\n readonly form?: string;\n //#endregion\n\n //#region Constructors\n constructor(options: MetaUseCaseOptions<string, Entity>) {\n this.entity = options.entity;\n this.key = options.key;\n this.binding = options.binding;\n this.placement = options.placement;\n this.alsoAt = options.alsoAt;\n this.labelKey = options.labelKey;\n this.keywordsKey = options.keywordsKey;\n this.confirm = options.confirm;\n this.form = options.form;\n }\n //#endregion\n\n //#region Methods\n //#endregion\n\n //#region Accessors\n //#endregion\n}\n"],"names":["MetaUseCase","options","entity","key","binding","placement","alsoAt","labelKey","keywordsKey","confirm","form"],"mappings":"AA4FA,OAAO,MAAMA;IAWX,YAAY;IAEZ,sBAAsB;IACtB,YAAYC,OAA2C,CAAE;QACvD,IAAI,CAACC,MAAM,GAAGD,QAAQC,MAAM;QAC5B,IAAI,CAACC,GAAG,GAAGF,QAAQE,GAAG;QACtB,IAAI,CAACC,OAAO,GAAGH,QAAQG,OAAO;QAC9B,IAAI,CAACC,SAAS,GAAGJ,QAAQI,SAAS;QAClC,IAAI,CAACC,MAAM,GAAGL,QAAQK,MAAM;QAC5B,IAAI,CAACC,QAAQ,GAAGN,QAAQM,QAAQ;QAChC,IAAI,CAACC,WAAW,GAAGP,QAAQO,WAAW;QACtC,IAAI,CAACC,OAAO,GAAGR,QAAQQ,OAAO;QAC9B,IAAI,CAACC,IAAI,GAAGT,QAAQS,IAAI;IAC1B;AAQF"}
1
+ {"version":3,"sources":["../../../../src/entity-definition/meta-entities/meta-use-case/meta-use-case.ts"],"sourcesContent":["import type { Entity, EntityConstructor } from '../../../types/Entity.js';\n\n/** What a use case acts on: one record, a selection, or nothing. */\nexport type UseCaseBinding = 'entity' | 'collection' | 'unbound';\n\n/**\n * Where a surface puts the action. Deliberately **not** derivable from\n * {@link UseCaseBinding}: an entity-bound action can be determining (a footer\n * \"Publish\" that finalizes the page) or context-independent (a toolbar action\n * available whenever a record is open). Collapsing the two would force every\n * surface to re-derive a placement the author already knew.\n */\nexport type UseCasePlacement =\n 'context-dependent' | 'context-independent' | 'determining';\n\n/**\n * One place a verb can appear: a binding and a placement together.\n *\n * The pair is the unit rather than either half, because the two answer\n * different questions — placement decides the *surface* and binding decides the\n * *payload* — and a verb reachable from a row and from a selection differs in\n * both at once.\n */\nexport interface UseCaseCell {\n binding: UseCaseBinding;\n placement: UseCasePlacement;\n}\n\n/** Confirmation a surface must obtain before running the use case. */\nexport interface UseCaseConfirm {\n tone: 'destructive' | 'neutral';\n /** Catalog key. Copy never lives in a descriptor. */\n messageKey: string;\n}\n\n/**\n * What `@useCase()` declares.\n *\n * `entity` is the target the verb is registered against — it is what the\n * decorator appends to, and it is not part of the served descriptor.\n *\n * `labelKey` is mandatory and namespace-qualified, the `GuardedNavItem`\n * convention: i18n is mandatory and `react/jsx-no-literals` fails the build on a\n * string written into JSX, so a descriptor carrying copy could not be rendered.\n *\n * `keywordsKey` is a catalog key rather than a `string[]` so the command palette\n * matches across locales — a user typing an English term reaches a Spanish\n * command.\n */\nexport interface MetaUseCaseOptions<\n TKey extends string,\n TEntity extends Entity,\n> {\n entity: EntityConstructor<TEntity>;\n key: TKey;\n binding: UseCaseBinding;\n placement: UseCasePlacement;\n /**\n * Further places the same verb appears, beyond the {@link binding} and\n * {@link placement} above.\n *\n * ⚠️ **One verb, one permission, several surfaces.** Publishing an offering\n * from its form, from a row menu and over a selection is one act reached three\n * ways, and the alternative was three `@useCase()` classes — which `slices`\n * refuses anyway, since a verb key is the third segment of one permission and\n * two classes cannot share a key. Three classes would have meant three\n * permissions for one act, and a grant that let somebody publish one offering\n * but not twenty.\n *\n * The primary cell stays where it is so that nothing already declared\n * changes, and so a descriptor still reads as \"this verb lives here\" with the\n * rest as additions.\n */\n alsoAt?: readonly UseCaseCell[];\n labelKey: string;\n keywordsKey?: string;\n confirm?: UseCaseConfirm;\n /** Names a form a surface should open instead of acting immediately. */\n form?: string;\n}\n\n/**\n * What the use-case class's **own** metadata carries, as opposed to what is\n * appended to the entity. Two fields are enough to derive the permission, which\n * is what lets `permissionForUseCase(SomeUC)` take a single argument and keeps\n * the verb string written exactly once.\n */\nexport interface MetaUseCaseBinding {\n entity: EntityConstructor<Entity>;\n key: string;\n}\n\nexport class MetaUseCase {\n //#region Properties\n readonly entity: EntityConstructor<Entity>;\n readonly key: string;\n readonly binding: UseCaseBinding;\n readonly placement: UseCasePlacement;\n readonly alsoAt?: readonly UseCaseCell[];\n readonly labelKey: string;\n readonly keywordsKey?: string;\n readonly confirm?: UseCaseConfirm;\n readonly form?: string;\n //#endregion\n\n //#region Constructors\n constructor(options: MetaUseCaseOptions<string, Entity>) {\n this.entity = options.entity;\n this.key = options.key;\n this.binding = options.binding;\n this.placement = options.placement;\n this.alsoAt = options.alsoAt;\n this.labelKey = options.labelKey;\n this.keywordsKey = options.keywordsKey;\n this.confirm = options.confirm;\n this.form = options.form;\n }\n //#endregion\n\n //#region Methods\n //#endregion\n\n //#region Accessors\n //#endregion\n}\n"],"names":["MetaUseCase","options","entity","key","binding","placement","alsoAt","labelKey","keywordsKey","confirm","form"],"mappings":"AA4FA,OAAO,MAAMA;IAWX,YAAY;IAEZ,sBAAsB;IACtB,YAAYC,OAA2C,CAAE;QACvD,IAAI,CAACC,MAAM,GAAGD,QAAQC,MAAM;QAC5B,IAAI,CAACC,GAAG,GAAGF,QAAQE,GAAG;QACtB,IAAI,CAACC,OAAO,GAAGH,QAAQG,OAAO;QAC9B,IAAI,CAACC,SAAS,GAAGJ,QAAQI,SAAS;QAClC,IAAI,CAACC,MAAM,GAAGL,QAAQK,MAAM;QAC5B,IAAI,CAACC,QAAQ,GAAGN,QAAQM,QAAQ;QAChC,IAAI,CAACC,WAAW,GAAGP,QAAQO,WAAW;QACtC,IAAI,CAACC,OAAO,GAAGR,QAAQQ,OAAO;QAC9B,IAAI,CAACC,IAAI,GAAGT,QAAQS,IAAI;IAC1B;AAQF"}
@@ -7,7 +7,7 @@ import { EntityDraft } from '../../types/EntityDraft.js';
7
7
  * **A draft in and a draft out**, rather than an instance in and an instance
8
8
  * out, and that is the whole reason this composes with the rest of the form
9
9
  * layer. A form's state is a `Record<string, string>` that a workspace
10
- * autosaves (ADR 0032), so a clone that returned an instance would have to be
10
+ * autosaves, so a clone that returned an instance would have to be
11
11
  * re-seeded back into a draft by the caller — through `seedFieldValue`, which
12
12
  * lives in a package above this one. Staying in the draft keeps the operation
13
13
  * on one side of that boundary, and `reconstructEntity` is still what turns the
@@ -9,7 +9,7 @@ import { describeEntityColumns } from '../describe/index.js';
9
9
  * **A draft in and a draft out**, rather than an instance in and an instance
10
10
  * out, and that is the whole reason this composes with the rest of the form
11
11
  * layer. A form's state is a `Record<string, string>` that a workspace
12
- * autosaves (ADR 0032), so a clone that returned an instance would have to be
12
+ * autosaves, so a clone that returned an instance would have to be
13
13
  * re-seeded back into a draft by the caller — through `seedFieldValue`, which
14
14
  * lives in a package above this one. Staying in the draft keeps the operation
15
15
  * on one side of that boundary, and `reconstructEntity` is still what turns the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/entity-definition/reconstruct/clone-entity-draft.ts"],"sourcesContent":["import { Entity, EntityConstructor } from '../../types/Entity.js';\nimport { EntityDraft, type EntityDraftValue } from '../../types/EntityDraft.js';\nimport {\n newRowKey,\n readRowDrafts,\n ROW_KEY,\n} from '../../types/EntityRowDraft.js';\nimport { describeEntityColumns } from '../describe/index.js';\n\n/** The member `Entity` requires every record to carry. */\nconst ID_MEMBER = 'id';\n\n/**\n * The draft for a copy of a record: everything the original held, minus the\n * members a copy must not carry.\n *\n * **A draft in and a draft out**, rather than an instance in and an instance\n * out, and that is the whole reason this composes with the rest of the form\n * layer. A form's state is a `Record<string, string>` that a workspace\n * autosaves (ADR 0032), so a clone that returned an instance would have to be\n * re-seeded back into a draft by the caller — through `seedFieldValue`, which\n * lives in a package above this one. Staying in the draft keeps the operation\n * on one side of that boundary, and `reconstructEntity` is still what turns the\n * result into an entity at submit.\n *\n * Two kinds of member are dropped:\n *\n * - **the identity member**, always, with no flag — a copy that carries the\n * original's `id` is an update to the original, which is the one outcome a\n * Clone button must never produce;\n * - **anything declared `@accessor({ resetOnClone: true })`** — a unique code,\n * an audit stamp, a sequence number.\n *\n * Cleared to `''` rather than deleted, because `''` is what an empty control\n * drafts as: a member removed from the record entirely would arrive\n * `undefined`, and an input handed `undefined` flips from controlled to\n * uncontrolled — the same trap `restoreEntityDraft` layers a restored draft\n * over a seeded one to avoid.\n *\n * A member the entity declares but the draft never held stays absent, so this\n * neither invents keys nor widens the draft.\n */\nexport function cloneEntityDraft<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n values: EntityDraft,\n): EntityDraft {\n const cloned: EntityDraft = { ...values };\n\n // The identity member, cleared **without consulting the descriptors**.\n // `describeEntityColumns` drops `hidden` members, and a form hiding its id is\n // the ordinary case rather than the exotic one — every generated catalog page\n // does it — so a descriptor-driven sweep would leave the id in place on\n // exactly the forms a Clone button appears on, and the \"copy\" would silently\n // save over the original. `Entity` fixes this member's name, so there is no\n // metadata to consult anyway.\n if (ID_MEMBER in cloned) cloned[ID_MEMBER] = '';\n\n for (const descriptor of describeEntityColumns(entityConstructor)) {\n if (descriptor.type === 'composition') {\n cloned[descriptor.name] = clonedRows(\n cloned[descriptor.name],\n descriptor.resetOnClone,\n );\n continue;\n }\n if (descriptor.type !== 'id' && !descriptor.resetOnClone) continue;\n if (!(descriptor.name in cloned)) continue;\n cloned[descriptor.name] = '';\n }\n\n return cloned;\n}\n\n/**\n * The rows a copy starts with.\n *\n * Two things the `''` rule above cannot express. A collection is **emptied to\n * `[]`, never to `''`**: an empty string is not a readable row list, so\n * `reconstructEntity` would skip the member and the \"reset\" collection would\n * come back holding the original's lines — a `resetOnClone` that resets nothing.\n *\n * And a row that *is* copied gets a **fresh key**. The key identifies a row\n * within one editing session; carrying the original's over means the copy and\n * the original name their lines identically, so a workspace holding both open\n * has two tabs whose rows claim the same identity — harmless until anything\n * addresses a row across them, and impossible to debug once it does.\n */\nfunction clonedRows(\n value: EntityDraftValue | undefined,\n reset: boolean,\n): EntityDraftValue {\n if (reset) return [];\n return readRowDrafts(value).map(row => ({ ...row, [ROW_KEY]: newRowKey() }));\n}\n"],"names":["newRowKey","readRowDrafts","ROW_KEY","describeEntityColumns","ID_MEMBER","cloneEntityDraft","entityConstructor","values","cloned","descriptor","type","name","clonedRows","resetOnClone","value","reset","map","row"],"mappings":";AAEA,SACEA,SAAS,EACTC,aAAa,EACbC,OAAO,QACF,gCAAgC;AACvC,SAASC,qBAAqB,QAAQ,uBAAuB;AAE7D,wDAAwD,GACxD,MAAMC,YAAY;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD,OAAO,SAASC,iBACdC,iBAA6C,EAC7CC,MAAmB;IAEnB,MAAMC,SAAsB,aAAKD;IAEjC,uEAAuE;IACvE,8EAA8E;IAC9E,8EAA8E;IAC9E,wEAAwE;IACxE,6EAA6E;IAC7E,4EAA4E;IAC5E,8BAA8B;IAC9B,IAAIH,aAAaI,QAAQA,MAAM,CAACJ,UAAU,GAAG;IAE7C,KAAK,MAAMK,cAAcN,sBAAsBG,mBAAoB;QACjE,IAAIG,WAAWC,IAAI,KAAK,eAAe;YACrCF,MAAM,CAACC,WAAWE,IAAI,CAAC,GAAGC,WACxBJ,MAAM,CAACC,WAAWE,IAAI,CAAC,EACvBF,WAAWI,YAAY;YAEzB;QACF;QACA,IAAIJ,WAAWC,IAAI,KAAK,QAAQ,CAACD,WAAWI,YAAY,EAAE;QAC1D,IAAI,CAAEJ,CAAAA,WAAWE,IAAI,IAAIH,MAAK,GAAI;QAClCA,MAAM,CAACC,WAAWE,IAAI,CAAC,GAAG;IAC5B;IAEA,OAAOH;AACT;AAEA;;;;;;;;;;;;;CAaC,GACD,SAASI,WACPE,KAAmC,EACnCC,KAAc;IAEd,IAAIA,OAAO,OAAO,EAAE;IACpB,OAAOd,cAAca,OAAOE,GAAG,CAACC,CAAAA,MAAQ,aAAKA;YAAK,CAACf,QAAQ,EAAEF;;AAC/D"}
1
+ {"version":3,"sources":["../../../src/entity-definition/reconstruct/clone-entity-draft.ts"],"sourcesContent":["import { Entity, EntityConstructor } from '../../types/Entity.js';\nimport { EntityDraft, type EntityDraftValue } from '../../types/EntityDraft.js';\nimport {\n newRowKey,\n readRowDrafts,\n ROW_KEY,\n} from '../../types/EntityRowDraft.js';\nimport { describeEntityColumns } from '../describe/index.js';\n\n/** The member `Entity` requires every record to carry. */\nconst ID_MEMBER = 'id';\n\n/**\n * The draft for a copy of a record: everything the original held, minus the\n * members a copy must not carry.\n *\n * **A draft in and a draft out**, rather than an instance in and an instance\n * out, and that is the whole reason this composes with the rest of the form\n * layer. A form's state is a `Record<string, string>` that a workspace\n * autosaves, so a clone that returned an instance would have to be\n * re-seeded back into a draft by the caller — through `seedFieldValue`, which\n * lives in a package above this one. Staying in the draft keeps the operation\n * on one side of that boundary, and `reconstructEntity` is still what turns the\n * result into an entity at submit.\n *\n * Two kinds of member are dropped:\n *\n * - **the identity member**, always, with no flag — a copy that carries the\n * original's `id` is an update to the original, which is the one outcome a\n * Clone button must never produce;\n * - **anything declared `@accessor({ resetOnClone: true })`** — a unique code,\n * an audit stamp, a sequence number.\n *\n * Cleared to `''` rather than deleted, because `''` is what an empty control\n * drafts as: a member removed from the record entirely would arrive\n * `undefined`, and an input handed `undefined` flips from controlled to\n * uncontrolled — the same trap `restoreEntityDraft` layers a restored draft\n * over a seeded one to avoid.\n *\n * A member the entity declares but the draft never held stays absent, so this\n * neither invents keys nor widens the draft.\n */\nexport function cloneEntityDraft<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n values: EntityDraft,\n): EntityDraft {\n const cloned: EntityDraft = { ...values };\n\n // The identity member, cleared **without consulting the descriptors**.\n // `describeEntityColumns` drops `hidden` members, and a form hiding its id is\n // the ordinary case rather than the exotic one — every generated catalog page\n // does it — so a descriptor-driven sweep would leave the id in place on\n // exactly the forms a Clone button appears on, and the \"copy\" would silently\n // save over the original. `Entity` fixes this member's name, so there is no\n // metadata to consult anyway.\n if (ID_MEMBER in cloned) cloned[ID_MEMBER] = '';\n\n for (const descriptor of describeEntityColumns(entityConstructor)) {\n if (descriptor.type === 'composition') {\n cloned[descriptor.name] = clonedRows(\n cloned[descriptor.name],\n descriptor.resetOnClone,\n );\n continue;\n }\n if (descriptor.type !== 'id' && !descriptor.resetOnClone) continue;\n if (!(descriptor.name in cloned)) continue;\n cloned[descriptor.name] = '';\n }\n\n return cloned;\n}\n\n/**\n * The rows a copy starts with.\n *\n * Two things the `''` rule above cannot express. A collection is **emptied to\n * `[]`, never to `''`**: an empty string is not a readable row list, so\n * `reconstructEntity` would skip the member and the \"reset\" collection would\n * come back holding the original's lines — a `resetOnClone` that resets nothing.\n *\n * And a row that *is* copied gets a **fresh key**. The key identifies a row\n * within one editing session; carrying the original's over means the copy and\n * the original name their lines identically, so a workspace holding both open\n * has two tabs whose rows claim the same identity — harmless until anything\n * addresses a row across them, and impossible to debug once it does.\n */\nfunction clonedRows(\n value: EntityDraftValue | undefined,\n reset: boolean,\n): EntityDraftValue {\n if (reset) return [];\n return readRowDrafts(value).map(row => ({ ...row, [ROW_KEY]: newRowKey() }));\n}\n"],"names":["newRowKey","readRowDrafts","ROW_KEY","describeEntityColumns","ID_MEMBER","cloneEntityDraft","entityConstructor","values","cloned","descriptor","type","name","clonedRows","resetOnClone","value","reset","map","row"],"mappings":";AAEA,SACEA,SAAS,EACTC,aAAa,EACbC,OAAO,QACF,gCAAgC;AACvC,SAASC,qBAAqB,QAAQ,uBAAuB;AAE7D,wDAAwD,GACxD,MAAMC,YAAY;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD,OAAO,SAASC,iBACdC,iBAA6C,EAC7CC,MAAmB;IAEnB,MAAMC,SAAsB,aAAKD;IAEjC,uEAAuE;IACvE,8EAA8E;IAC9E,8EAA8E;IAC9E,wEAAwE;IACxE,6EAA6E;IAC7E,4EAA4E;IAC5E,8BAA8B;IAC9B,IAAIH,aAAaI,QAAQA,MAAM,CAACJ,UAAU,GAAG;IAE7C,KAAK,MAAMK,cAAcN,sBAAsBG,mBAAoB;QACjE,IAAIG,WAAWC,IAAI,KAAK,eAAe;YACrCF,MAAM,CAACC,WAAWE,IAAI,CAAC,GAAGC,WACxBJ,MAAM,CAACC,WAAWE,IAAI,CAAC,EACvBF,WAAWI,YAAY;YAEzB;QACF;QACA,IAAIJ,WAAWC,IAAI,KAAK,QAAQ,CAACD,WAAWI,YAAY,EAAE;QAC1D,IAAI,CAAEJ,CAAAA,WAAWE,IAAI,IAAIH,MAAK,GAAI;QAClCA,MAAM,CAACC,WAAWE,IAAI,CAAC,GAAG;IAC5B;IAEA,OAAOH;AACT;AAEA;;;;;;;;;;;;;CAaC,GACD,SAASI,WACPE,KAAmC,EACnCC,KAAc;IAEd,IAAIA,OAAO,OAAO,EAAE;IACpB,OAAOd,cAAca,OAAOE,GAAG,CAACC,CAAAA,MAAQ,aAAKA;YAAK,CAACf,QAAQ,EAAEF;;AAC/D"}
@@ -55,7 +55,7 @@ import { reconstructChild } from './reconstruct-child.js';
55
55
  * `composition` is excluded for the reason `linkCollection` is: it has no
56
56
  * editor yet, so a draft never holds its rows and writing one would mean
57
57
  * writing `undefined` over the master's own lines on every save. The rows'
58
- * write path lands with the detail control (#122). `scalarCollection` is *not*
58
+ * write path lands with the detail control. `scalarCollection` is *not*
59
59
  * excluded — it is a scalar as far as this walk is concerned, because its
60
60
  * comma-list draft coerces losslessly back to a `string[]`.
61
61
  */ function isWritableScalar(descriptor, current) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/entity-definition/reconstruct/reconstruct-entity.ts"],"sourcesContent":["import { Entity, EntityConstructor } from '../../types/Entity.js';\nimport {\n EntityDraft,\n readDraftString,\n readLocalizedDraft,\n} from '../../types/EntityDraft.js';\nimport { isRowDraftArray } from '../../types/EntityRowDraft.js';\nimport {\n describeEntityColumns,\n EntityFieldDescriptor,\n} from '../describe/index.js';\nimport {\n applyEntityLinks,\n type EntityLinkSelection,\n} from '../links/apply-entity-links/index.js';\nimport { EntityCollectionLink } from '../links/entity-collection-link/index.js';\nimport { EntityLink } from '../links/entity-link/index.js';\nimport {\n coerceFieldValue,\n coerceLocalizedDraft,\n} from './coerce-field-value.js';\nimport { reconstructChild } from './reconstruct-child.js';\n\n/** Everything {@link reconstructEntity} needs beyond the draft itself. */\nexport interface ReconstructEntityOptions<TEntity extends Entity> {\n /**\n * The record being edited. Supplies the `id` and nothing else — see\n * {@link reconstructEntity} on why it is only the id.\n */\n existing?: TEntity;\n /**\n * The instances a picker handed over, for members whose declared\n * `linkSerialization` is `embedded`. Passed straight to `applyEntityLinks`.\n */\n selection?: EntityLinkSelection;\n}\n\n/**\n * Writes an entity's owned collections back from the rows its draft holds.\n *\n * A **second pass** rather than a branch inside the scalar walk, and the reason\n * is the invariant it protects: `isWritableScalar` must go on refusing\n * `composition`, because assigning a coerced string to a member that holds rows\n * is how the master's own lines get blanked on a save that touches nothing else.\n * Keeping the two walks separate means a `composition` is written by exactly one\n * piece of code, and that code cannot be reached with a string.\n *\n * A member whose draft entry is missing or unreadable is **skipped, not\n * cleared** — the same rule the scalar walk applies to a member the form hides.\n * A draft written before this member existed, or one restored from a build that\n * did not know it, must leave the record's rows exactly as they were rather than\n * emptying them, which is a data loss the user never asked for and cannot see.\n * An explicitly empty list *is* a value and is written: that is a user who\n * removed every row.\n *\n * Read-only is honoured for the same reason as everywhere else, and the\n * `childType` guard is not defensive — a `composition` that declared no child\n * has no columns to coerce against, so there is nothing to rebuild.\n */\nfunction applyEntityComposition(\n instance: object,\n descriptors: readonly EntityFieldDescriptor[],\n values: EntityDraft,\n): void {\n for (const descriptor of descriptors) {\n if (descriptor.type !== 'composition') continue;\n if (descriptor.readonly || descriptor.childType === undefined) continue;\n\n const drafted = values[descriptor.name];\n if (!isRowDraftArray(drafted)) continue;\n\n const rows = drafted;\n const childType = descriptor.childType;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (instance as any)[descriptor.name] = rows.map(row =>\n reconstructChild(childType, row),\n );\n }\n}\n\n/**\n * A member this function must not write.\n *\n * `readonly` is the declared half — `describeEntityColumns` keeps read-only\n * members because a form still *shows* them, so unlike the column list this\n * walk has to filter them itself, exactly as `buildEntityInstance` does.\n *\n * The relation half is checked twice on purpose. The declared `link` /\n * `linkCollection` types are `applyEntityLinks`' business, and the runtime\n * `instanceof` catches a bare `@accessor()` on a relation that declared no\n * type: those accessors are getter-only, so assigning a coerced string to one\n * would throw rather than misbehave quietly.\n *\n * `composition` is excluded for the reason `linkCollection` is: it has no\n * editor yet, so a draft never holds its rows and writing one would mean\n * writing `undefined` over the master's own lines on every save. The rows'\n * write path lands with the detail control (#122). `scalarCollection` is *not*\n * excluded — it is a scalar as far as this walk is concerned, because its\n * comma-list draft coerces losslessly back to a `string[]`.\n */\nfunction isWritableScalar(\n descriptor: EntityFieldDescriptor,\n current: unknown,\n): boolean {\n if (descriptor.readonly) return false;\n if (\n descriptor.type === 'link' ||\n descriptor.type === 'linkCollection' ||\n descriptor.type === 'composition'\n ) {\n return false;\n }\n return !(\n current instanceof EntityLink || current instanceof EntityCollectionLink\n );\n}\n\n/**\n * Rebuilds a domain instance from a form's string draft, deriving every\n * assignment from the entity's own accessor metadata.\n *\n * This is the one genuinely hand-written part of an entity form, and writing it\n * per entity is a bug farm: each one repeats the same empty-string-to-undefined\n * rule, and each one has to remember to carry back a member the form hides —\n * `ProductBrand.code` is assigned by the create transaction, and dropping it\n * blanks the record's identifier on every update. Deriving the walk from the\n * metadata makes that structural instead of remembered.\n *\n * **Construction is zero-argument.** `new entityConstructor()` then assignment\n * through setters, the same shape `buildEntityInstance` uses to rebuild a record\n * off the wire. Entities default their constructor parameters, and a required\n * member is filled by its setter a moment later — so required-ness stays one\n * fact (the `@accessor({ required })` flag the form already validates against)\n * rather than a second, unverifiable list of constructor argument names.\n *\n * Constructing first also gives `describeEntityColumns` a sample, which is what\n * lets it infer `link`-ness for an accessor that declared no `type`: relations\n * are initialized by the constructor, so the fresh instance already carries them.\n *\n * **`existing` supplies the id and nothing else.** Every other member comes from\n * the draft, which was seeded from the record — so a create/update distinction\n * lives here once instead of a `target.id = entity?.id` line per form. A draft\n * may be client-supplied (a workspace autosaves it), which is not a new surface:\n * a save route already overwrites the id from its own route parameter.\n *\n * Pure and framework-free, so the same reconstruction runs in a browser form and\n * in a backend use-case assembling an entity from a raw draft.\n */\nexport function reconstructEntity<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n values: EntityDraft,\n { existing, selection }: ReconstructEntityOptions<TEntity> = {},\n): TEntity {\n const instance = new entityConstructor();\n const descriptors = describeEntityColumns(entityConstructor, instance);\n\n for (const descriptor of descriptors) {\n // The draft is keyed by accessor name, and so is the assignment: `key` is\n // `alias ?? name`, which is the wire/SQL column, not the JS property.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const current = (instance as any)[descriptor.name];\n if (descriptor.type === 'localized') {\n if (descriptor.readonly) continue;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (instance as any)[descriptor.name] = coerceLocalizedDraft(\n readLocalizedDraft(values, descriptor.name),\n );\n continue;\n }\n if (!isWritableScalar(descriptor, current)) continue;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (instance as any)[descriptor.name] = coerceFieldValue(\n descriptor,\n readDraftString(values, descriptor.name),\n );\n }\n\n applyEntityComposition(instance, descriptors, values);\n applyEntityLinks(instance, descriptors, values, selection);\n\n if (existing !== undefined) instance.id = existing.id;\n\n return instance;\n}\n"],"names":["readDraftString","readLocalizedDraft","isRowDraftArray","describeEntityColumns","applyEntityLinks","EntityCollectionLink","EntityLink","coerceFieldValue","coerceLocalizedDraft","reconstructChild","applyEntityComposition","instance","descriptors","values","descriptor","type","readonly","childType","undefined","drafted","name","rows","map","row","isWritableScalar","current","reconstructEntity","entityConstructor","existing","selection","id"],"mappings":"AACA,SAEEA,eAAe,EACfC,kBAAkB,QACb,6BAA6B;AACpC,SAASC,eAAe,QAAQ,gCAAgC;AAChE,SACEC,qBAAqB,QAEhB,uBAAuB;AAC9B,SACEC,gBAAgB,QAEX,uCAAuC;AAC9C,SAASC,oBAAoB,QAAQ,2CAA2C;AAChF,SAASC,UAAU,QAAQ,gCAAgC;AAC3D,SACEC,gBAAgB,EAChBC,oBAAoB,QACf,0BAA0B;AACjC,SAASC,gBAAgB,QAAQ,yBAAyB;AAgB1D;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,SAASC,uBACPC,QAAgB,EAChBC,WAA6C,EAC7CC,MAAmB;IAEnB,KAAK,MAAMC,cAAcF,YAAa;QACpC,IAAIE,WAAWC,IAAI,KAAK,eAAe;QACvC,IAAID,WAAWE,QAAQ,IAAIF,WAAWG,SAAS,KAAKC,WAAW;QAE/D,MAAMC,UAAUN,MAAM,CAACC,WAAWM,IAAI,CAAC;QACvC,IAAI,CAAClB,gBAAgBiB,UAAU;QAE/B,MAAME,OAAOF;QACb,MAAMF,YAAYH,WAAWG,SAAS;QAEtC,8DAA8D;QAC7DN,QAAgB,CAACG,WAAWM,IAAI,CAAC,GAAGC,KAAKC,GAAG,CAACC,CAAAA,MAC5Cd,iBAAiBQ,WAAWM;IAEhC;AACF;AAEA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,SAASC,iBACPV,UAAiC,EACjCW,OAAgB;IAEhB,IAAIX,WAAWE,QAAQ,EAAE,OAAO;IAChC,IACEF,WAAWC,IAAI,KAAK,UACpBD,WAAWC,IAAI,KAAK,oBACpBD,WAAWC,IAAI,KAAK,eACpB;QACA,OAAO;IACT;IACA,OAAO,CACLU,CAAAA,mBAAmBnB,cAAcmB,mBAAmBpB,oBAAmB;AAE3E;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GACD,OAAO,SAASqB,kBACdC,iBAA6C,EAC7Cd,MAAmB,EACnB,EAAEe,QAAQ,EAAEC,SAAS,EAAqC,GAAG,CAAC,CAAC;IAE/D,MAAMlB,WAAW,IAAIgB;IACrB,MAAMf,cAAcT,sBAAsBwB,mBAAmBhB;IAE7D,KAAK,MAAMG,cAAcF,YAAa;QACpC,0EAA0E;QAC1E,sEAAsE;QACtE,8DAA8D;QAC9D,MAAMa,UAAU,AAACd,QAAgB,CAACG,WAAWM,IAAI,CAAC;QAClD,IAAIN,WAAWC,IAAI,KAAK,aAAa;YACnC,IAAID,WAAWE,QAAQ,EAAE;YACzB,8DAA8D;YAC7DL,QAAgB,CAACG,WAAWM,IAAI,CAAC,GAAGZ,qBACnCP,mBAAmBY,QAAQC,WAAWM,IAAI;YAE5C;QACF;QACA,IAAI,CAACI,iBAAiBV,YAAYW,UAAU;QAE5C,8DAA8D;QAC7Dd,QAAgB,CAACG,WAAWM,IAAI,CAAC,GAAGb,iBACnCO,YACAd,gBAAgBa,QAAQC,WAAWM,IAAI;IAE3C;IAEAV,uBAAuBC,UAAUC,aAAaC;IAC9CT,iBAAiBO,UAAUC,aAAaC,QAAQgB;IAEhD,IAAID,aAAaV,WAAWP,SAASmB,EAAE,GAAGF,SAASE,EAAE;IAErD,OAAOnB;AACT"}
1
+ {"version":3,"sources":["../../../src/entity-definition/reconstruct/reconstruct-entity.ts"],"sourcesContent":["import { Entity, EntityConstructor } from '../../types/Entity.js';\nimport {\n EntityDraft,\n readDraftString,\n readLocalizedDraft,\n} from '../../types/EntityDraft.js';\nimport { isRowDraftArray } from '../../types/EntityRowDraft.js';\nimport {\n describeEntityColumns,\n EntityFieldDescriptor,\n} from '../describe/index.js';\nimport {\n applyEntityLinks,\n type EntityLinkSelection,\n} from '../links/apply-entity-links/index.js';\nimport { EntityCollectionLink } from '../links/entity-collection-link/index.js';\nimport { EntityLink } from '../links/entity-link/index.js';\nimport {\n coerceFieldValue,\n coerceLocalizedDraft,\n} from './coerce-field-value.js';\nimport { reconstructChild } from './reconstruct-child.js';\n\n/** Everything {@link reconstructEntity} needs beyond the draft itself. */\nexport interface ReconstructEntityOptions<TEntity extends Entity> {\n /**\n * The record being edited. Supplies the `id` and nothing else — see\n * {@link reconstructEntity} on why it is only the id.\n */\n existing?: TEntity;\n /**\n * The instances a picker handed over, for members whose declared\n * `linkSerialization` is `embedded`. Passed straight to `applyEntityLinks`.\n */\n selection?: EntityLinkSelection;\n}\n\n/**\n * Writes an entity's owned collections back from the rows its draft holds.\n *\n * A **second pass** rather than a branch inside the scalar walk, and the reason\n * is the invariant it protects: `isWritableScalar` must go on refusing\n * `composition`, because assigning a coerced string to a member that holds rows\n * is how the master's own lines get blanked on a save that touches nothing else.\n * Keeping the two walks separate means a `composition` is written by exactly one\n * piece of code, and that code cannot be reached with a string.\n *\n * A member whose draft entry is missing or unreadable is **skipped, not\n * cleared** — the same rule the scalar walk applies to a member the form hides.\n * A draft written before this member existed, or one restored from a build that\n * did not know it, must leave the record's rows exactly as they were rather than\n * emptying them, which is a data loss the user never asked for and cannot see.\n * An explicitly empty list *is* a value and is written: that is a user who\n * removed every row.\n *\n * Read-only is honoured for the same reason as everywhere else, and the\n * `childType` guard is not defensive — a `composition` that declared no child\n * has no columns to coerce against, so there is nothing to rebuild.\n */\nfunction applyEntityComposition(\n instance: object,\n descriptors: readonly EntityFieldDescriptor[],\n values: EntityDraft,\n): void {\n for (const descriptor of descriptors) {\n if (descriptor.type !== 'composition') continue;\n if (descriptor.readonly || descriptor.childType === undefined) continue;\n\n const drafted = values[descriptor.name];\n if (!isRowDraftArray(drafted)) continue;\n\n const rows = drafted;\n const childType = descriptor.childType;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (instance as any)[descriptor.name] = rows.map(row =>\n reconstructChild(childType, row),\n );\n }\n}\n\n/**\n * A member this function must not write.\n *\n * `readonly` is the declared half — `describeEntityColumns` keeps read-only\n * members because a form still *shows* them, so unlike the column list this\n * walk has to filter them itself, exactly as `buildEntityInstance` does.\n *\n * The relation half is checked twice on purpose. The declared `link` /\n * `linkCollection` types are `applyEntityLinks`' business, and the runtime\n * `instanceof` catches a bare `@accessor()` on a relation that declared no\n * type: those accessors are getter-only, so assigning a coerced string to one\n * would throw rather than misbehave quietly.\n *\n * `composition` is excluded for the reason `linkCollection` is: it has no\n * editor yet, so a draft never holds its rows and writing one would mean\n * writing `undefined` over the master's own lines on every save. The rows'\n * write path lands with the detail control. `scalarCollection` is *not*\n * excluded — it is a scalar as far as this walk is concerned, because its\n * comma-list draft coerces losslessly back to a `string[]`.\n */\nfunction isWritableScalar(\n descriptor: EntityFieldDescriptor,\n current: unknown,\n): boolean {\n if (descriptor.readonly) return false;\n if (\n descriptor.type === 'link' ||\n descriptor.type === 'linkCollection' ||\n descriptor.type === 'composition'\n ) {\n return false;\n }\n return !(\n current instanceof EntityLink || current instanceof EntityCollectionLink\n );\n}\n\n/**\n * Rebuilds a domain instance from a form's string draft, deriving every\n * assignment from the entity's own accessor metadata.\n *\n * This is the one genuinely hand-written part of an entity form, and writing it\n * per entity is a bug farm: each one repeats the same empty-string-to-undefined\n * rule, and each one has to remember to carry back a member the form hides —\n * `ProductBrand.code` is assigned by the create transaction, and dropping it\n * blanks the record's identifier on every update. Deriving the walk from the\n * metadata makes that structural instead of remembered.\n *\n * **Construction is zero-argument.** `new entityConstructor()` then assignment\n * through setters, the same shape `buildEntityInstance` uses to rebuild a record\n * off the wire. Entities default their constructor parameters, and a required\n * member is filled by its setter a moment later — so required-ness stays one\n * fact (the `@accessor({ required })` flag the form already validates against)\n * rather than a second, unverifiable list of constructor argument names.\n *\n * Constructing first also gives `describeEntityColumns` a sample, which is what\n * lets it infer `link`-ness for an accessor that declared no `type`: relations\n * are initialized by the constructor, so the fresh instance already carries them.\n *\n * **`existing` supplies the id and nothing else.** Every other member comes from\n * the draft, which was seeded from the record — so a create/update distinction\n * lives here once instead of a `target.id = entity?.id` line per form. A draft\n * may be client-supplied (a workspace autosaves it), which is not a new surface:\n * a save route already overwrites the id from its own route parameter.\n *\n * Pure and framework-free, so the same reconstruction runs in a browser form and\n * in a backend use-case assembling an entity from a raw draft.\n */\nexport function reconstructEntity<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n values: EntityDraft,\n { existing, selection }: ReconstructEntityOptions<TEntity> = {},\n): TEntity {\n const instance = new entityConstructor();\n const descriptors = describeEntityColumns(entityConstructor, instance);\n\n for (const descriptor of descriptors) {\n // The draft is keyed by accessor name, and so is the assignment: `key` is\n // `alias ?? name`, which is the wire/SQL column, not the JS property.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const current = (instance as any)[descriptor.name];\n if (descriptor.type === 'localized') {\n if (descriptor.readonly) continue;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (instance as any)[descriptor.name] = coerceLocalizedDraft(\n readLocalizedDraft(values, descriptor.name),\n );\n continue;\n }\n if (!isWritableScalar(descriptor, current)) continue;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (instance as any)[descriptor.name] = coerceFieldValue(\n descriptor,\n readDraftString(values, descriptor.name),\n );\n }\n\n applyEntityComposition(instance, descriptors, values);\n applyEntityLinks(instance, descriptors, values, selection);\n\n if (existing !== undefined) instance.id = existing.id;\n\n return instance;\n}\n"],"names":["readDraftString","readLocalizedDraft","isRowDraftArray","describeEntityColumns","applyEntityLinks","EntityCollectionLink","EntityLink","coerceFieldValue","coerceLocalizedDraft","reconstructChild","applyEntityComposition","instance","descriptors","values","descriptor","type","readonly","childType","undefined","drafted","name","rows","map","row","isWritableScalar","current","reconstructEntity","entityConstructor","existing","selection","id"],"mappings":"AACA,SAEEA,eAAe,EACfC,kBAAkB,QACb,6BAA6B;AACpC,SAASC,eAAe,QAAQ,gCAAgC;AAChE,SACEC,qBAAqB,QAEhB,uBAAuB;AAC9B,SACEC,gBAAgB,QAEX,uCAAuC;AAC9C,SAASC,oBAAoB,QAAQ,2CAA2C;AAChF,SAASC,UAAU,QAAQ,gCAAgC;AAC3D,SACEC,gBAAgB,EAChBC,oBAAoB,QACf,0BAA0B;AACjC,SAASC,gBAAgB,QAAQ,yBAAyB;AAgB1D;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,SAASC,uBACPC,QAAgB,EAChBC,WAA6C,EAC7CC,MAAmB;IAEnB,KAAK,MAAMC,cAAcF,YAAa;QACpC,IAAIE,WAAWC,IAAI,KAAK,eAAe;QACvC,IAAID,WAAWE,QAAQ,IAAIF,WAAWG,SAAS,KAAKC,WAAW;QAE/D,MAAMC,UAAUN,MAAM,CAACC,WAAWM,IAAI,CAAC;QACvC,IAAI,CAAClB,gBAAgBiB,UAAU;QAE/B,MAAME,OAAOF;QACb,MAAMF,YAAYH,WAAWG,SAAS;QAEtC,8DAA8D;QAC7DN,QAAgB,CAACG,WAAWM,IAAI,CAAC,GAAGC,KAAKC,GAAG,CAACC,CAAAA,MAC5Cd,iBAAiBQ,WAAWM;IAEhC;AACF;AAEA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,SAASC,iBACPV,UAAiC,EACjCW,OAAgB;IAEhB,IAAIX,WAAWE,QAAQ,EAAE,OAAO;IAChC,IACEF,WAAWC,IAAI,KAAK,UACpBD,WAAWC,IAAI,KAAK,oBACpBD,WAAWC,IAAI,KAAK,eACpB;QACA,OAAO;IACT;IACA,OAAO,CACLU,CAAAA,mBAAmBnB,cAAcmB,mBAAmBpB,oBAAmB;AAE3E;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GACD,OAAO,SAASqB,kBACdC,iBAA6C,EAC7Cd,MAAmB,EACnB,EAAEe,QAAQ,EAAEC,SAAS,EAAqC,GAAG,CAAC,CAAC;IAE/D,MAAMlB,WAAW,IAAIgB;IACrB,MAAMf,cAAcT,sBAAsBwB,mBAAmBhB;IAE7D,KAAK,MAAMG,cAAcF,YAAa;QACpC,0EAA0E;QAC1E,sEAAsE;QACtE,8DAA8D;QAC9D,MAAMa,UAAU,AAACd,QAAgB,CAACG,WAAWM,IAAI,CAAC;QAClD,IAAIN,WAAWC,IAAI,KAAK,aAAa;YACnC,IAAID,WAAWE,QAAQ,EAAE;YACzB,8DAA8D;YAC7DL,QAAgB,CAACG,WAAWM,IAAI,CAAC,GAAGZ,qBACnCP,mBAAmBY,QAAQC,WAAWM,IAAI;YAE5C;QACF;QACA,IAAI,CAACI,iBAAiBV,YAAYW,UAAU;QAE5C,8DAA8D;QAC7Dd,QAAgB,CAACG,WAAWM,IAAI,CAAC,GAAGb,iBACnCO,YACAd,gBAAgBa,QAAQC,WAAWM,IAAI;IAE3C;IAEAV,uBAAuBC,UAAUC,aAAaC;IAC9CT,iBAAiBO,UAAUC,aAAaC,QAAQgB;IAEhD,IAAID,aAAaV,WAAWP,SAASmB,EAAE,GAAGF,SAASE,EAAE;IAErD,OAAOnB;AACT"}