@entifix/core 0.2.0 → 0.4.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 (83) hide show
  1. package/dist/entity-definition/describe/describe-child-columns.d.ts +3 -0
  2. package/dist/entity-definition/describe/describe-child-columns.d.ts.map +1 -1
  3. package/dist/entity-definition/describe/describe-child-columns.js +11 -0
  4. package/dist/entity-definition/describe/describe-child-columns.js.map +1 -1
  5. package/dist/entity-definition/describe/describe-entity-columns.d.ts +7 -1
  6. package/dist/entity-definition/describe/describe-entity-columns.d.ts.map +1 -1
  7. package/dist/entity-definition/describe/describe-entity-columns.js +41 -4
  8. package/dist/entity-definition/describe/describe-entity-columns.js.map +1 -1
  9. package/dist/entity-definition/describe/entity-view.d.ts +19 -0
  10. package/dist/entity-definition/describe/entity-view.d.ts.map +1 -0
  11. package/dist/entity-definition/describe/entity-view.js +35 -0
  12. package/dist/entity-definition/describe/entity-view.js.map +1 -0
  13. package/dist/entity-definition/describe/index.d.ts +2 -0
  14. package/dist/entity-definition/describe/index.d.ts.map +1 -1
  15. package/dist/entity-definition/describe/index.js +2 -0
  16. package/dist/entity-definition/describe/index.js.map +1 -1
  17. package/dist/entity-definition/describe/query-property.d.ts +17 -0
  18. package/dist/entity-definition/describe/query-property.d.ts.map +1 -0
  19. package/dist/entity-definition/describe/query-property.js +18 -0
  20. package/dist/entity-definition/describe/query-property.js.map +1 -0
  21. package/dist/entity-definition/meta-entities/bounds.d.ts +10 -0
  22. package/dist/entity-definition/meta-entities/bounds.d.ts.map +1 -0
  23. package/dist/entity-definition/meta-entities/bounds.js +17 -0
  24. package/dist/entity-definition/meta-entities/bounds.js.map +1 -0
  25. package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.d.ts +24 -1
  26. package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.d.ts.map +1 -1
  27. package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.js +8 -1
  28. package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.js.map +1 -1
  29. package/dist/entity-definition/meta-entities/meta-entity/meta-entity.d.ts +45 -0
  30. package/dist/entity-definition/meta-entities/meta-entity/meta-entity.d.ts.map +1 -1
  31. package/dist/entity-definition/meta-entities/meta-entity/meta-entity.js +4 -0
  32. package/dist/entity-definition/meta-entities/meta-entity/meta-entity.js.map +1 -1
  33. package/dist/entity-definition/reconstruct/coerce-field-value.d.ts +7 -0
  34. package/dist/entity-definition/reconstruct/coerce-field-value.d.ts.map +1 -1
  35. package/dist/entity-definition/reconstruct/coerce-field-value.js +9 -0
  36. package/dist/entity-definition/reconstruct/coerce-field-value.js.map +1 -1
  37. package/dist/entity-definition/reconstruct/reconstruct-entity.d.ts.map +1 -1
  38. package/dist/entity-definition/reconstruct/reconstruct-entity.js +8 -2
  39. package/dist/entity-definition/reconstruct/reconstruct-entity.js.map +1 -1
  40. package/dist/entity-definition/serializer/serialize.d.ts +13 -1
  41. package/dist/entity-definition/serializer/serialize.d.ts.map +1 -1
  42. package/dist/entity-definition/serializer/serialize.js +14 -2
  43. package/dist/entity-definition/serializer/serialize.js.map +1 -1
  44. package/dist/entity-definition/validate/index.d.ts +2 -0
  45. package/dist/entity-definition/validate/index.d.ts.map +1 -0
  46. package/dist/entity-definition/validate/index.js +3 -0
  47. package/dist/entity-definition/validate/index.js.map +1 -0
  48. package/dist/entity-definition/validate/validate-records.d.ts +75 -0
  49. package/dist/entity-definition/validate/validate-records.d.ts.map +1 -0
  50. package/dist/entity-definition/validate/validate-records.js +205 -0
  51. package/dist/entity-definition/validate/validate-records.js.map +1 -0
  52. package/dist/i18n/locales.d.ts +46 -7
  53. package/dist/i18n/locales.d.ts.map +1 -1
  54. package/dist/i18n/locales.js +49 -10
  55. package/dist/i18n/locales.js.map +1 -1
  56. package/dist/i18n/localized-text.d.ts +27 -0
  57. package/dist/i18n/localized-text.d.ts.map +1 -0
  58. package/dist/i18n/localized-text.js +18 -0
  59. package/dist/i18n/localized-text.js.map +1 -0
  60. package/dist/i18n/negotiate.d.ts +1 -1
  61. package/dist/i18n/negotiate.d.ts.map +1 -1
  62. package/dist/i18n/negotiate.js +3 -3
  63. package/dist/i18n/negotiate.js.map +1 -1
  64. package/dist/index.d.ts +3 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +3 -0
  67. package/dist/index.js.map +1 -1
  68. package/dist/query/in-memory-query.d.ts +49 -0
  69. package/dist/query/in-memory-query.d.ts.map +1 -0
  70. package/dist/query/in-memory-query.js +133 -0
  71. package/dist/query/in-memory-query.js.map +1 -0
  72. package/dist/rsql/coerce-rsql.d.ts +21 -0
  73. package/dist/rsql/coerce-rsql.d.ts.map +1 -1
  74. package/dist/rsql/coerce-rsql.js +44 -8
  75. package/dist/rsql/coerce-rsql.js.map +1 -1
  76. package/dist/rsql/sort-param.d.ts.map +1 -1
  77. package/dist/rsql/sort-param.js +3 -9
  78. package/dist/rsql/sort-param.js.map +1 -1
  79. package/dist/types/EntityDraft.d.ts +18 -2
  80. package/dist/types/EntityDraft.d.ts.map +1 -1
  81. package/dist/types/EntityDraft.js +11 -0
  82. package/dist/types/EntityDraft.js.map +1 -1
  83. package/package.json +1 -2
@@ -15,6 +15,9 @@ import { type EntityFieldDescriptor } from './describe-entity-columns.js';
15
15
  * member would write the wrong column, and preferring the member over the key
16
16
  * would leave the grid with no identity at all.
17
17
  *
18
+ * **Nor a `localized` member**, for a reason of the same kind: a row's draft is a
19
+ * flat string map, and a text per locale has no single cell to live in.
20
+ *
18
21
  * `EntifixBuildError` rather than a logic error, because this is a declaration
19
22
  * fault in the entity — the same class `assertNotQueryable` raises when a
20
23
  * collection claims to be sortable.
@@ -1 +1 @@
1
- {"version":3,"file":"describe-child-columns.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/describe-child-columns.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAChF,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AAEtC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,SAAS,MAAM,EACxD,SAAS,EAAE,gBAAgB,CAAC,MAAM,CAAC,EACnC,MAAM,CAAC,EAAE,MAAM,GACd,qBAAqB,EAAE,CAezB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,SAAS,qBAAqB,EAAE,GAC5C,qBAAqB,EAAE,CAOzB"}
1
+ {"version":3,"file":"describe-child-columns.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/describe-child-columns.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAChF,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,SAAS,MAAM,EACxD,SAAS,EAAE,gBAAgB,CAAC,MAAM,CAAC,EACnC,MAAM,CAAC,EAAE,MAAM,GACd,qBAAqB,EAAE,CA8BzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,SAAS,qBAAqB,EAAE,GAC5C,qBAAqB,EAAE,CAOzB"}
@@ -16,6 +16,9 @@ import { describeEntityColumns } from './describe-entity-columns.js';
16
16
  * member would write the wrong column, and preferring the member over the key
17
17
  * would leave the grid with no identity at all.
18
18
  *
19
+ * **Nor a `localized` member**, for a reason of the same kind: a row's draft is a
20
+ * flat string map, and a text per locale has no single cell to live in.
21
+ *
19
22
  * `EntifixBuildError` rather than a logic error, because this is a declaration
20
23
  * fault in the entity — the same class `assertNotQueryable` raises when a
21
24
  * collection claims to be sortable.
@@ -27,6 +30,14 @@ import { describeEntityColumns } from './describe-entity-columns.js';
27
30
  member: ROW_KEY
28
31
  });
29
32
  }
33
+ // A row's draft is a flat string map, which is what keeps a draft's type
34
+ // bounded for the form engine; a text per locale has no cell to live in.
35
+ const localized = descriptors.find((descriptor)=>descriptor.type === 'localized');
36
+ if (localized !== undefined) {
37
+ throw new EntifixBuildError(`A composition child declares "${localized.name}" as localized, which a row ` + 'cannot hold: a row is a flat string map. Keep the localized text on the ' + 'entity, or make the child an entity of its own.', undefined, {
38
+ member: localized.name
39
+ });
40
+ }
30
41
  return descriptors;
31
42
  }
32
43
  /**
@@ -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 * `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 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","editableChildColumns","filter","type"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,6CAA6C;AAC/E,SAASC,OAAO,QAAQ,gCAAgC;AAExD,SACEC,qBAAqB,QAEhB,+BAA+B;AAEtC;;;;;;;;;;;;;;;;;;CAkBC,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,OAAOK;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASO,qBACdP,WAA6C;IAE7C,OAAOA,YAAYQ,MAAM,CACvBL,CAAAA,aACEA,WAAWM,IAAI,KAAK,iBACpBN,WAAWM,IAAI,KAAK,oBACpBN,WAAWM,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()` ([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"}
@@ -45,6 +45,12 @@ export interface EntityFieldDescriptor {
45
45
  * stamp. The identity member is always reset and is not flagged.
46
46
  */
47
47
  resetOnClone: boolean;
48
+ /** The fewest values a collection member may hold. */
49
+ minItems?: number;
50
+ /** The most values a collection member may hold. */
51
+ maxItems?: number;
52
+ /** The `date` member this one may not precede. */
53
+ notBefore?: string;
48
54
  }
49
55
  /** Types whose values are scalars a user can meaningfully sort/filter on. */
50
56
  export declare const SCALAR_TYPES: readonly MetaAccessorType[];
@@ -55,7 +61,7 @@ export declare const SCALAR_TYPES: readonly MetaAccessorType[];
55
61
  * would quietly return an empty page.
56
62
  *
57
63
  * Exported so `describe-entity-columns.spec.ts` can assert that this list,
58
- * {@link SCALAR_TYPES} and the two reference types partition
64
+ * {@link SCALAR_TYPES}, the two reference types and `localized` partition
59
65
  * `MetaAccessorTypes` exactly. Nothing in the repo guards `MetaAccessorType`
60
66
  * exhaustively — every switch over it has a `default` that treats the value as
61
67
  * a string — so that partition spec is what makes an eleventh type impossible
@@ -1 +1 @@
1
- {"version":3,"file":"describe-entity-columns.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/describe-entity-columns.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAE5B,gBAAgB,EACjB,MAAM,yCAAyC,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,0FAA0F;IAC1F,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,iBAAiB,EAAE,uBAAuB,CAAC;IAC3C;;;OAGG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,6EAA6E;AAC7E,eAAO,MAAM,YAAY,EAAE,SAAS,gBAAgB,EAMnD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,gBAAgB,EAIvD,CAAC;AAkFF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,SAAS,MAAM,EAC1D,iBAAiB,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAC5C,MAAM,CAAC,EAAE,OAAO,GACf,qBAAqB,EAAE,CAazB"}
1
+ {"version":3,"file":"describe-entity-columns.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/describe-entity-columns.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAE5B,gBAAgB,EACjB,MAAM,yCAAyC,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,0FAA0F;IAC1F,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,iBAAiB,EAAE,uBAAuB,CAAC;IAC3C;;;OAGG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,6EAA6E;AAC7E,eAAO,MAAM,YAAY,EAAE,SAAS,gBAAgB,EAMnD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,gBAAgB,EAIvD,CAAC;AA2HF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,SAAS,MAAM,EAC1D,iBAAiB,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAC5C,MAAM,CAAC,EAAE,OAAO,GACf,qBAAqB,EAAE,CAezB"}
@@ -2,6 +2,7 @@ import { EntifixBuildError } from '../../base-entities/entifix-error/index.js';
2
2
  import { extractMetaAccessors } from '../helpers/index.js';
3
3
  import { EntityCollectionLink } from '../links/entity-collection-link/index.js';
4
4
  import { EntityLink } from '../links/entity-link/index.js';
5
+ import { assertBounds } from '../meta-entities/bounds.js';
5
6
  /** Types whose values are scalars a user can meaningfully sort/filter on. */ export const SCALAR_TYPES = [
6
7
  'string',
7
8
  'number',
@@ -16,7 +17,7 @@ import { EntityLink } from '../links/entity-link/index.js';
16
17
  * would quietly return an empty page.
17
18
  *
18
19
  * Exported so `describe-entity-columns.spec.ts` can assert that this list,
19
- * {@link SCALAR_TYPES} and the two reference types partition
20
+ * {@link SCALAR_TYPES}, the two reference types and `localized` partition
20
21
  * `MetaAccessorTypes` exactly. Nothing in the repo guards `MetaAccessorType`
21
22
  * exhaustively — every switch over it has a `default` that treats the value as
22
23
  * a string — so that partition spec is what makes an eleventh type impossible
@@ -55,12 +56,43 @@ import { EntityLink } from '../links/entity-link/index.js';
55
56
  if (metaAccessor.sortable !== true && metaAccessor.filterable !== true) return;
56
57
  throw new EntifixBuildError(`"${String(metaAccessor.name)}" is a ${type} and cannot be sortable or filterable — member metadata is the server-side query allowlist, and a collection compared as a scalar matches nothing.`);
57
58
  }
59
+ /**
60
+ * Item counts belong to a collection, and must be counts. A `minItems` on a
61
+ * string would read as honoured and constrain nothing.
62
+ */ function assertItemCounts(metaAccessor, type) {
63
+ const { minItems, maxItems } = metaAccessor;
64
+ if (minItems === undefined && maxItems === undefined) return;
65
+ const name = String(metaAccessor.name);
66
+ if (!COLLECTION_TYPES.includes(type)) {
67
+ throw new EntifixBuildError(`"${name}" is a ${type}, and only a collection can declare minItems or maxItems.`);
68
+ }
69
+ assertBounds(`"${name}"`, {
70
+ min: minItems,
71
+ max: maxItems
72
+ });
73
+ }
74
+ /**
75
+ * `notBefore` compares two instants, so both ends must be `date` members of
76
+ * the same entity. Checked once every member is described, since the one it
77
+ * names may be declared after it.
78
+ */ function assertNotBefore(descriptors) {
79
+ for (const descriptor of descriptors){
80
+ if (descriptor.notBefore === undefined) continue;
81
+ const earlier = descriptors.find((other)=>other.name === descriptor.notBefore);
82
+ if (descriptor.type !== 'date' || (earlier == null ? void 0 : earlier.type) !== 'date') {
83
+ throw new EntifixBuildError(`"${descriptor.name}" declares notBefore "${descriptor.notBefore}", and both must be date members of the entity.`);
84
+ }
85
+ }
86
+ }
58
87
  function toDescriptor(metaAccessor, index, sample) {
59
88
  var _metaAccessor_type, _metaAccessor_linkLabelProperty, _metaAccessor_alias, _metaAccessor_label, _metaAccessor_sortable, _metaAccessor_filterable, _metaAccessor_order, _metaAccessor_readonly, _metaAccessor_required, _metaAccessor_linkSearchProperty, _metaAccessor_linkSerialization, _metaAccessor_resetOnClone;
60
89
  const name = String(metaAccessor.name);
61
90
  const type = (_metaAccessor_type = metaAccessor.type) != null ? _metaAccessor_type : inferType(name, sample == null ? void 0 : sample[name]);
62
91
  assertNotQueryable(metaAccessor, type);
63
- const isScalar = SCALAR_TYPES.includes(type);
92
+ assertItemCounts(metaAccessor, type);
93
+ // A localized member is queried one locale at a time (`name.en`), which
94
+ // makes it as queryable as the text each locale holds.
95
+ const isScalar = SCALAR_TYPES.includes(type) || type === 'localized';
64
96
  const linkLabelProperty = (_metaAccessor_linkLabelProperty = metaAccessor.linkLabelProperty) != null ? _metaAccessor_linkLabelProperty : 'name';
65
97
  return {
66
98
  name,
@@ -79,7 +111,10 @@ function toDescriptor(metaAccessor, index, sample) {
79
111
  linkSearchProperty: (_metaAccessor_linkSearchProperty = metaAccessor.linkSearchProperty) != null ? _metaAccessor_linkSearchProperty : linkLabelProperty,
80
112
  linkSerialization: (_metaAccessor_linkSerialization = metaAccessor.linkSerialization) != null ? _metaAccessor_linkSerialization : 'id',
81
113
  childType: metaAccessor.childType == null ? void 0 : metaAccessor.childType.call(metaAccessor),
82
- resetOnClone: (_metaAccessor_resetOnClone = metaAccessor.resetOnClone) != null ? _metaAccessor_resetOnClone : false
114
+ resetOnClone: (_metaAccessor_resetOnClone = metaAccessor.resetOnClone) != null ? _metaAccessor_resetOnClone : false,
115
+ minItems: metaAccessor.minItems,
116
+ maxItems: metaAccessor.maxItems,
117
+ notBefore: metaAccessor.notBefore
83
118
  };
84
119
  }
85
120
  /**
@@ -100,13 +135,15 @@ function toDescriptor(metaAccessor, index, sample) {
100
135
  * from one implementation.
101
136
  */ export function describeEntityColumns(entityConstructor, sample) {
102
137
  const seen = new Set();
103
- return extractMetaAccessors(entityConstructor).filter((metaAccessor)=>{
138
+ const descriptors = extractMetaAccessors(entityConstructor).filter((metaAccessor)=>{
104
139
  if (metaAccessor.hidden || metaAccessor.kind !== 'getter') return false;
105
140
  const name = String(metaAccessor.name);
106
141
  if (seen.has(name)) return false;
107
142
  seen.add(name);
108
143
  return true;
109
144
  }).map((metaAccessor, index)=>toDescriptor(metaAccessor, index, sample)).sort((left, right)=>left.order - right.order);
145
+ assertNotBefore(descriptors);
146
+ return descriptors;
110
147
  }
111
148
 
112
149
  //# sourceMappingURL=describe-entity-columns.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/entity-definition/describe/describe-entity-columns.ts"],"sourcesContent":["import { EntifixBuildError } from '../../base-entities/entifix-error/index.js';\nimport { extractMetaAccessors } from '../helpers/index.js';\nimport { EntityCollectionLink } from '../links/entity-collection-link/index.js';\nimport { EntityLink } from '../links/entity-link/index.js';\nimport {\n type ChildConstructor,\n type EntityLinkSerialization,\n MetaAccessor,\n MetaAccessorType,\n} from '../meta-entities/meta-accessor/index.js';\n\n/**\n * A displayable member of an entity, resolved from its accessor metadata. This\n * is the contract generic UI builds itself from — a table's columns today, a\n * form's fields later — so it stays framework-free and lives next to the\n * metadata it derives from.\n */\nexport interface EntityFieldDescriptor {\n /** Accessor name. The stable identity used by preferences and slots. */\n name: string;\n /** Wire/property key: `alias ?? name`. */\n key: string;\n label: string;\n /**\n * Catalog key for {@link label}, when the entity declared one. Resolution is\n * the presentation layer's job — core has no locale and no catalogs, and the\n * same descriptor feeds the server-side filter allowlist, where a translated\n * label would be meaningless.\n */\n labelKey?: string;\n type: MetaAccessorType;\n sortable: boolean;\n filterable: boolean;\n order: number;\n /** Cannot be written back. A form shows it but disables its input. */\n readonly: boolean;\n /** Must hold a value. A form rejects submit while it is empty. */\n required: boolean;\n enumValues?: readonly string[];\n /** Catalog prefix for {@link enumValues}; a value reads as `${enumLabelKey}.${value}`. */\n enumLabelKey?: string;\n /** Property of a `link` target used as its display label. */\n linkLabelProperty: string;\n /** Property of a `link` target a picker searches on. Defaults to the label. */\n linkSearchProperty: string;\n /** Whether a `link` writes back its foreign key or the inlined target. */\n linkSerialization: EntityLinkSerialization;\n /**\n * The class describing one row of a `composition` member, resolved from the\n * accessor's thunk. Absent on every other type.\n */\n childType?: ChildConstructor;\n /**\n * The member is dropped when the record is copied — a unique code, an audit\n * stamp. The identity member is always reset and is not flagged.\n */\n resetOnClone: boolean;\n}\n\n/** Types whose values are scalars a user can meaningfully sort/filter on. */\nexport const SCALAR_TYPES: readonly MetaAccessorType[] = [\n 'string',\n 'number',\n 'boolean',\n 'date',\n 'enum',\n];\n\n/**\n * Types whose value is a collection. None of them may ever be sorted or\n * filtered: this descriptor is also the **server-side RSQL allowlist**, and an\n * array compared as a scalar matches nothing — so the query would not fail, it\n * would quietly return an empty page.\n *\n * Exported so `describe-entity-columns.spec.ts` can assert that this list,\n * {@link SCALAR_TYPES} and the two reference types partition\n * `MetaAccessorTypes` exactly. Nothing in the repo guards `MetaAccessorType`\n * exhaustively — every switch over it has a `default` that treats the value as\n * a string — so that partition spec is what makes an eleventh type impossible\n * to add silently.\n */\nexport const COLLECTION_TYPES: readonly MetaAccessorType[] = [\n 'linkCollection',\n 'composition',\n 'scalarCollection',\n];\n\n/** `productCode` / `product-code` / `product_code` → `Product Code`. */\nfunction humanize(name: string): string {\n return name\n .replace(/[-_]+/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^./, first => first.toUpperCase());\n}\n\n/**\n * Best-effort type for accessors that did not declare one. Entities are not\n * required to annotate every member, so a sample row keeps a bare `@accessor()`\n * usable; a declared `type` always wins over this.\n */\nfunction inferType(name: string, value: unknown): MetaAccessorType {\n if (value instanceof EntityLink) return 'link';\n if (value instanceof EntityCollectionLink) return 'linkCollection';\n if (value instanceof Date) return 'date';\n if (typeof value === 'number') return 'number';\n if (typeof value === 'boolean') return 'boolean';\n if (name === 'id') return 'id';\n return 'string';\n}\n\n/**\n * A collection member declared queryable is always a mistake, so it breaks the\n * developer rather than the query — the same posture as `applyEntityLinks`\n * throwing on an `embedded` link it cannot build.\n *\n * Silently clamping to `false` was the alternative and is worse: the author\n * keeps a declaration that reads as honoured, and the symptom is an empty\n * result page rather than an error anyone can act on.\n */\nfunction assertNotQueryable(\n metaAccessor: MetaAccessor,\n type: MetaAccessorType,\n) {\n if (!COLLECTION_TYPES.includes(type)) return;\n if (metaAccessor.sortable !== true && metaAccessor.filterable !== true)\n return;\n throw new EntifixBuildError(\n `\"${String(metaAccessor.name)}\" is a ${type} and cannot be sortable or filterable — member metadata is the server-side query allowlist, and a collection compared as a scalar matches nothing.`,\n );\n}\n\nfunction toDescriptor(\n metaAccessor: MetaAccessor,\n index: number,\n sample: unknown,\n): EntityFieldDescriptor {\n const name = String(metaAccessor.name);\n const type =\n metaAccessor.type ??\n inferType(name, (sample as Record<string, unknown> | undefined)?.[name]);\n assertNotQueryable(metaAccessor, type);\n const isScalar = SCALAR_TYPES.includes(type);\n const linkLabelProperty = metaAccessor.linkLabelProperty ?? 'name';\n\n return {\n name,\n key: String(metaAccessor.alias ?? metaAccessor.name),\n label: metaAccessor.label ?? humanize(name),\n labelKey: metaAccessor.labelKey,\n type,\n sortable: metaAccessor.sortable ?? isScalar,\n filterable: metaAccessor.filterable ?? isScalar,\n order: metaAccessor.order ?? index,\n readonly: metaAccessor.readonly ?? false,\n required: metaAccessor.required ?? false,\n enumValues: metaAccessor.enumValues,\n enumLabelKey: metaAccessor.enumLabelKey,\n linkLabelProperty,\n linkSearchProperty: metaAccessor.linkSearchProperty ?? linkLabelProperty,\n linkSerialization: metaAccessor.linkSerialization ?? 'id',\n childType: metaAccessor.childType?.(),\n resetOnClone: metaAccessor.resetOnClone ?? false,\n };\n}\n\n/**\n * Resolves an entity's displayable members from its accessor metadata, sorted by\n * `order` (declaration order when undeclared).\n *\n * Getter-kind and non-`hidden` mirror the filter {@link serializeEntity} walks,\n * minus its `readonly` exclusion: a read-only member is still worth showing,\n * it just cannot be written back.\n *\n * `sample` is an optional instance used only to infer the `type` of accessors\n * that did not declare one.\n *\n * The target is a {@link ChildConstructor} rather than an `EntityConstructor`,\n * which is wider than it looks: nothing here reads `id`, and a `composition`\n * member's child is a **value** with accessors and no identity. So the same\n * walk describes an entity and describes one row of the collection it owns,\n * from one implementation.\n */\nexport function describeEntityColumns<TTarget extends object>(\n entityConstructor: ChildConstructor<TTarget>,\n sample?: TTarget,\n): EntityFieldDescriptor[] {\n const seen = new Set<string>();\n\n return extractMetaAccessors(entityConstructor)\n .filter(metaAccessor => {\n if (metaAccessor.hidden || metaAccessor.kind !== 'getter') return false;\n const name = String(metaAccessor.name);\n if (seen.has(name)) return false;\n seen.add(name);\n return true;\n })\n .map((metaAccessor, index) => toDescriptor(metaAccessor, index, sample))\n .sort((left, right) => left.order - right.order);\n}\n"],"names":["EntifixBuildError","extractMetaAccessors","EntityCollectionLink","EntityLink","SCALAR_TYPES","COLLECTION_TYPES","humanize","name","replace","trim","first","toUpperCase","inferType","value","Date","assertNotQueryable","metaAccessor","type","includes","sortable","filterable","String","toDescriptor","index","sample","isScalar","linkLabelProperty","key","alias","label","labelKey","order","readonly","required","enumValues","enumLabelKey","linkSearchProperty","linkSerialization","childType","resetOnClone","describeEntityColumns","entityConstructor","seen","Set","filter","hidden","kind","has","add","map","sort","left","right"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/entity-definition/describe/describe-entity-columns.ts"],"sourcesContent":["import { EntifixBuildError } from '../../base-entities/entifix-error/index.js';\nimport { extractMetaAccessors } from '../helpers/index.js';\nimport { EntityCollectionLink } from '../links/entity-collection-link/index.js';\nimport { EntityLink } from '../links/entity-link/index.js';\nimport { assertBounds } from '../meta-entities/bounds.js';\nimport {\n type ChildConstructor,\n type EntityLinkSerialization,\n MetaAccessor,\n MetaAccessorType,\n} from '../meta-entities/meta-accessor/index.js';\n\n/**\n * A displayable member of an entity, resolved from its accessor metadata. This\n * is the contract generic UI builds itself from — a table's columns today, a\n * form's fields later — so it stays framework-free and lives next to the\n * metadata it derives from.\n */\nexport interface EntityFieldDescriptor {\n /** Accessor name. The stable identity used by preferences and slots. */\n name: string;\n /** Wire/property key: `alias ?? name`. */\n key: string;\n label: string;\n /**\n * Catalog key for {@link label}, when the entity declared one. Resolution is\n * the presentation layer's job — core has no locale and no catalogs, and the\n * same descriptor feeds the server-side filter allowlist, where a translated\n * label would be meaningless.\n */\n labelKey?: string;\n type: MetaAccessorType;\n sortable: boolean;\n filterable: boolean;\n order: number;\n /** Cannot be written back. A form shows it but disables its input. */\n readonly: boolean;\n /** Must hold a value. A form rejects submit while it is empty. */\n required: boolean;\n enumValues?: readonly string[];\n /** Catalog prefix for {@link enumValues}; a value reads as `${enumLabelKey}.${value}`. */\n enumLabelKey?: string;\n /** Property of a `link` target used as its display label. */\n linkLabelProperty: string;\n /** Property of a `link` target a picker searches on. Defaults to the label. */\n linkSearchProperty: string;\n /** Whether a `link` writes back its foreign key or the inlined target. */\n linkSerialization: EntityLinkSerialization;\n /**\n * The class describing one row of a `composition` member, resolved from the\n * accessor's thunk. Absent on every other type.\n */\n childType?: ChildConstructor;\n /**\n * The member is dropped when the record is copied — a unique code, an audit\n * stamp. The identity member is always reset and is not flagged.\n */\n resetOnClone: boolean;\n /** The fewest values a collection member may hold. */\n minItems?: number;\n /** The most values a collection member may hold. */\n maxItems?: number;\n /** The `date` member this one may not precede. */\n notBefore?: string;\n}\n\n/** Types whose values are scalars a user can meaningfully sort/filter on. */\nexport const SCALAR_TYPES: readonly MetaAccessorType[] = [\n 'string',\n 'number',\n 'boolean',\n 'date',\n 'enum',\n];\n\n/**\n * Types whose value is a collection. None of them may ever be sorted or\n * filtered: this descriptor is also the **server-side RSQL allowlist**, and an\n * array compared as a scalar matches nothing — so the query would not fail, it\n * would quietly return an empty page.\n *\n * Exported so `describe-entity-columns.spec.ts` can assert that this list,\n * {@link SCALAR_TYPES}, the two reference types and `localized` partition\n * `MetaAccessorTypes` exactly. Nothing in the repo guards `MetaAccessorType`\n * exhaustively — every switch over it has a `default` that treats the value as\n * a string — so that partition spec is what makes an eleventh type impossible\n * to add silently.\n */\nexport const COLLECTION_TYPES: readonly MetaAccessorType[] = [\n 'linkCollection',\n 'composition',\n 'scalarCollection',\n];\n\n/** `productCode` / `product-code` / `product_code` → `Product Code`. */\nfunction humanize(name: string): string {\n return name\n .replace(/[-_]+/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^./, first => first.toUpperCase());\n}\n\n/**\n * Best-effort type for accessors that did not declare one. Entities are not\n * required to annotate every member, so a sample row keeps a bare `@accessor()`\n * usable; a declared `type` always wins over this.\n */\nfunction inferType(name: string, value: unknown): MetaAccessorType {\n if (value instanceof EntityLink) return 'link';\n if (value instanceof EntityCollectionLink) return 'linkCollection';\n if (value instanceof Date) return 'date';\n if (typeof value === 'number') return 'number';\n if (typeof value === 'boolean') return 'boolean';\n if (name === 'id') return 'id';\n return 'string';\n}\n\n/**\n * A collection member declared queryable is always a mistake, so it breaks the\n * developer rather than the query — the same posture as `applyEntityLinks`\n * throwing on an `embedded` link it cannot build.\n *\n * Silently clamping to `false` was the alternative and is worse: the author\n * keeps a declaration that reads as honoured, and the symptom is an empty\n * result page rather than an error anyone can act on.\n */\nfunction assertNotQueryable(\n metaAccessor: MetaAccessor,\n type: MetaAccessorType,\n) {\n if (!COLLECTION_TYPES.includes(type)) return;\n if (metaAccessor.sortable !== true && metaAccessor.filterable !== true)\n return;\n throw new EntifixBuildError(\n `\"${String(metaAccessor.name)}\" is a ${type} and cannot be sortable or filterable — member metadata is the server-side query allowlist, and a collection compared as a scalar matches nothing.`,\n );\n}\n\n/**\n * Item counts belong to a collection, and must be counts. A `minItems` on a\n * string would read as honoured and constrain nothing.\n */\nfunction assertItemCounts(metaAccessor: MetaAccessor, type: MetaAccessorType) {\n const { minItems, maxItems } = metaAccessor;\n if (minItems === undefined && maxItems === undefined) return;\n const name = String(metaAccessor.name);\n if (!COLLECTION_TYPES.includes(type)) {\n throw new EntifixBuildError(\n `\"${name}\" is a ${type}, and only a collection can declare minItems or maxItems.`,\n );\n }\n assertBounds(`\"${name}\"`, { min: minItems, max: maxItems });\n}\n\n/**\n * `notBefore` compares two instants, so both ends must be `date` members of\n * the same entity. Checked once every member is described, since the one it\n * names may be declared after it.\n */\nfunction assertNotBefore(descriptors: readonly EntityFieldDescriptor[]) {\n for (const descriptor of descriptors) {\n if (descriptor.notBefore === undefined) continue;\n const earlier = descriptors.find(\n other => other.name === descriptor.notBefore,\n );\n if (descriptor.type !== 'date' || earlier?.type !== 'date') {\n throw new EntifixBuildError(\n `\"${descriptor.name}\" declares notBefore \"${descriptor.notBefore}\", and both must be date members of the entity.`,\n );\n }\n }\n}\n\nfunction toDescriptor(\n metaAccessor: MetaAccessor,\n index: number,\n sample: unknown,\n): EntityFieldDescriptor {\n const name = String(metaAccessor.name);\n const type =\n metaAccessor.type ??\n inferType(name, (sample as Record<string, unknown> | undefined)?.[name]);\n assertNotQueryable(metaAccessor, type);\n assertItemCounts(metaAccessor, type);\n // A localized member is queried one locale at a time (`name.en`), which\n // makes it as queryable as the text each locale holds.\n const isScalar = SCALAR_TYPES.includes(type) || type === 'localized';\n const linkLabelProperty = metaAccessor.linkLabelProperty ?? 'name';\n\n return {\n name,\n key: String(metaAccessor.alias ?? metaAccessor.name),\n label: metaAccessor.label ?? humanize(name),\n labelKey: metaAccessor.labelKey,\n type,\n sortable: metaAccessor.sortable ?? isScalar,\n filterable: metaAccessor.filterable ?? isScalar,\n order: metaAccessor.order ?? index,\n readonly: metaAccessor.readonly ?? false,\n required: metaAccessor.required ?? false,\n enumValues: metaAccessor.enumValues,\n enumLabelKey: metaAccessor.enumLabelKey,\n linkLabelProperty,\n linkSearchProperty: metaAccessor.linkSearchProperty ?? linkLabelProperty,\n linkSerialization: metaAccessor.linkSerialization ?? 'id',\n childType: metaAccessor.childType?.(),\n resetOnClone: metaAccessor.resetOnClone ?? false,\n minItems: metaAccessor.minItems,\n maxItems: metaAccessor.maxItems,\n notBefore: metaAccessor.notBefore,\n };\n}\n\n/**\n * Resolves an entity's displayable members from its accessor metadata, sorted by\n * `order` (declaration order when undeclared).\n *\n * Getter-kind and non-`hidden` mirror the filter {@link serializeEntity} walks,\n * minus its `readonly` exclusion: a read-only member is still worth showing,\n * it just cannot be written back.\n *\n * `sample` is an optional instance used only to infer the `type` of accessors\n * that did not declare one.\n *\n * The target is a {@link ChildConstructor} rather than an `EntityConstructor`,\n * which is wider than it looks: nothing here reads `id`, and a `composition`\n * member's child is a **value** with accessors and no identity. So the same\n * walk describes an entity and describes one row of the collection it owns,\n * from one implementation.\n */\nexport function describeEntityColumns<TTarget extends object>(\n entityConstructor: ChildConstructor<TTarget>,\n sample?: TTarget,\n): EntityFieldDescriptor[] {\n const seen = new Set<string>();\n\n const descriptors = extractMetaAccessors(entityConstructor)\n .filter(metaAccessor => {\n if (metaAccessor.hidden || metaAccessor.kind !== 'getter') return false;\n const name = String(metaAccessor.name);\n if (seen.has(name)) return false;\n seen.add(name);\n return true;\n })\n .map((metaAccessor, index) => toDescriptor(metaAccessor, index, sample))\n .sort((left, right) => left.order - right.order);\n assertNotBefore(descriptors);\n return descriptors;\n}\n"],"names":["EntifixBuildError","extractMetaAccessors","EntityCollectionLink","EntityLink","assertBounds","SCALAR_TYPES","COLLECTION_TYPES","humanize","name","replace","trim","first","toUpperCase","inferType","value","Date","assertNotQueryable","metaAccessor","type","includes","sortable","filterable","String","assertItemCounts","minItems","maxItems","undefined","min","max","assertNotBefore","descriptors","descriptor","notBefore","earlier","find","other","toDescriptor","index","sample","isScalar","linkLabelProperty","key","alias","label","labelKey","order","readonly","required","enumValues","enumLabelKey","linkSearchProperty","linkSerialization","childType","resetOnClone","describeEntityColumns","entityConstructor","seen","Set","filter","hidden","kind","has","add","map","sort","left","right"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Entity, EntityConstructor } from '../../types/Entity.js';
2
+ import type { EntityViewOptions } from '../meta-entities/meta-entity/index.js';
3
+ import { type EntityFieldDescriptor } from './describe-entity-columns.js';
4
+ /** A view, resolved against the entity it belongs to. */
5
+ export interface EntityView extends EntityViewOptions {
6
+ readonly name: string;
7
+ /** The omitted members' descriptors, so a caller can drop them by wire key. */
8
+ readonly omitted: readonly EntityFieldDescriptor[];
9
+ }
10
+ /**
11
+ * One of an entity's named views, checked against the entity: a view it does
12
+ * not declare, or one omitting a member it does not have, is a declaration
13
+ * error rather than a view that silently publishes the whole record.
14
+ *
15
+ * The identity member cannot be omitted — a published record still has to say
16
+ * which record it is.
17
+ */
18
+ export declare function extractEntityView<TEntity extends Entity>(entityConstructor: EntityConstructor<TEntity>, name: string): EntityView;
19
+ //# sourceMappingURL=entity-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-view.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/entity-view.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AAEtC,yDAAyD;AACzD,MAAM,WAAW,UAAW,SAAQ,iBAAiB;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,MAAM,EACtD,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAC7C,IAAI,EAAE,MAAM,GACX,UAAU,CAuBZ"}
@@ -0,0 +1,35 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { EntifixBuildError } from '../../base-entities/entifix-error/index.js';
3
+ import { extractMetaEntity } from '../helpers/index.js';
4
+ import { describeEntityColumns } from './describe-entity-columns.js';
5
+ /**
6
+ * One of an entity's named views, checked against the entity: a view it does
7
+ * not declare, or one omitting a member it does not have, is a declaration
8
+ * error rather than a view that silently publishes the whole record.
9
+ *
10
+ * The identity member cannot be omitted — a published record still has to say
11
+ * which record it is.
12
+ */ export function extractEntityView(entityConstructor, name) {
13
+ var _view_omit;
14
+ var _meta_views;
15
+ const meta = extractMetaEntity(entityConstructor);
16
+ const view = (_meta_views = meta.views) == null ? void 0 : _meta_views[name];
17
+ if (view === undefined) {
18
+ var _meta_views1;
19
+ throw new EntifixBuildError(`${meta.name} declares no view "${name}"; it declares ${JSON.stringify(Object.keys((_meta_views1 = meta.views) != null ? _meta_views1 : {}))}.`);
20
+ }
21
+ const descriptors = describeEntityColumns(entityConstructor);
22
+ const omitted = ((_view_omit = view.omit) != null ? _view_omit : []).map((member)=>{
23
+ const descriptor = descriptors.find((each)=>each.name === member);
24
+ if (descriptor === undefined || descriptor.name === 'id') {
25
+ throw new EntifixBuildError(`${meta.name}'s view "${name}" omits "${member}", which is ${descriptor === undefined ? 'not a member of it' : 'its identity'}.`);
26
+ }
27
+ return descriptor;
28
+ });
29
+ return _extends({}, view, {
30
+ name,
31
+ omitted
32
+ });
33
+ }
34
+
35
+ //# sourceMappingURL=entity-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/entity-definition/describe/entity-view.ts"],"sourcesContent":["import { EntifixBuildError } from '../../base-entities/entifix-error/index.js';\nimport type { Entity, EntityConstructor } from '../../types/Entity.js';\nimport { extractMetaEntity } from '../helpers/index.js';\nimport type { EntityViewOptions } from '../meta-entities/meta-entity/index.js';\nimport {\n describeEntityColumns,\n type EntityFieldDescriptor,\n} from './describe-entity-columns.js';\n\n/** A view, resolved against the entity it belongs to. */\nexport interface EntityView extends EntityViewOptions {\n readonly name: string;\n /** The omitted members' descriptors, so a caller can drop them by wire key. */\n readonly omitted: readonly EntityFieldDescriptor[];\n}\n\n/**\n * One of an entity's named views, checked against the entity: a view it does\n * not declare, or one omitting a member it does not have, is a declaration\n * error rather than a view that silently publishes the whole record.\n *\n * The identity member cannot be omitted — a published record still has to say\n * which record it is.\n */\nexport function extractEntityView<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n name: string,\n): EntityView {\n const meta = extractMetaEntity(entityConstructor);\n const view = meta.views?.[name];\n if (view === undefined) {\n throw new EntifixBuildError(\n `${meta.name} declares no view \"${name}\"; it declares ${JSON.stringify(\n Object.keys(meta.views ?? {}),\n )}.`,\n );\n }\n const descriptors = describeEntityColumns(entityConstructor);\n const omitted = (view.omit ?? []).map(member => {\n const descriptor = descriptors.find(each => each.name === member);\n if (descriptor === undefined || descriptor.name === 'id') {\n throw new EntifixBuildError(\n `${meta.name}'s view \"${name}\" omits \"${member}\", which is ${\n descriptor === undefined ? 'not a member of it' : 'its identity'\n }.`,\n );\n }\n return descriptor;\n });\n return { ...view, name, omitted };\n}\n"],"names":["EntifixBuildError","extractMetaEntity","describeEntityColumns","extractEntityView","entityConstructor","name","view","meta","views","undefined","JSON","stringify","Object","keys","descriptors","omitted","omit","map","member","descriptor","find","each"],"mappings":";AAAA,SAASA,iBAAiB,QAAQ,6CAA6C;AAE/E,SAASC,iBAAiB,QAAQ,sBAAsB;AAExD,SACEC,qBAAqB,QAEhB,+BAA+B;AAStC;;;;;;;CAOC,GACD,OAAO,SAASC,kBACdC,iBAA6C,EAC7CC,IAAY;QAYKC;QATJC;IADb,MAAMA,OAAON,kBAAkBG;IAC/B,MAAME,QAAOC,cAAAA,KAAKC,KAAK,qBAAVD,WAAY,CAACF,KAAK;IAC/B,IAAIC,SAASG,WAAW;YAGNF;QAFhB,MAAM,IAAIP,kBACR,GAAGO,KAAKF,IAAI,CAAC,mBAAmB,EAAEA,KAAK,eAAe,EAAEK,KAAKC,SAAS,CACpEC,OAAOC,IAAI,EAACN,eAAAA,KAAKC,KAAK,YAAVD,eAAc,CAAC,IAC3B,CAAC,CAAC;IAER;IACA,MAAMO,cAAcZ,sBAAsBE;IAC1C,MAAMW,UAAU,EAACT,aAAAA,KAAKU,IAAI,YAATV,aAAa,EAAE,EAAEW,GAAG,CAACC,CAAAA;QACpC,MAAMC,aAAaL,YAAYM,IAAI,CAACC,CAAAA,OAAQA,KAAKhB,IAAI,KAAKa;QAC1D,IAAIC,eAAeV,aAAaU,WAAWd,IAAI,KAAK,MAAM;YACxD,MAAM,IAAIL,kBACR,GAAGO,KAAKF,IAAI,CAAC,SAAS,EAAEA,KAAK,SAAS,EAAEa,OAAO,YAAY,EACzDC,eAAeV,YAAY,uBAAuB,eACnD,CAAC,CAAC;QAEP;QACA,OAAOU;IACT;IACA,OAAO,aAAKb;QAAMD;QAAMU;;AAC1B"}
@@ -1,4 +1,6 @@
1
1
  export * from './describe-child-columns.js';
2
2
  export * from './describe-entity-columns.js';
3
3
  export * from './describe-entity-use-cases.js';
4
+ export * from './entity-view.js';
5
+ export * from './query-property.js';
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC"}
@@ -1,5 +1,7 @@
1
1
  export * from './describe-child-columns.js';
2
2
  export * from './describe-entity-columns.js';
3
3
  export * from './describe-entity-use-cases.js';
4
+ export * from './entity-view.js';
5
+ export * from './query-property.js';
4
6
 
5
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/entity-definition/describe/index.ts"],"sourcesContent":["export * from './describe-child-columns.js';\nexport * from './describe-entity-columns.js';\nexport * from './describe-entity-use-cases.js';\n"],"names":[],"mappings":"AAAA,cAAc,8BAA8B;AAC5C,cAAc,+BAA+B;AAC7C,cAAc,iCAAiC"}
1
+ {"version":3,"sources":["../../../src/entity-definition/describe/index.ts"],"sourcesContent":["export * from './describe-child-columns.js';\nexport * from './describe-entity-columns.js';\nexport * from './describe-entity-use-cases.js';\nexport * from './entity-view.js';\nexport * from './query-property.js';\n"],"names":[],"mappings":"AAAA,cAAc,8BAA8B;AAC5C,cAAc,+BAA+B;AAC7C,cAAc,iCAAiC;AAC/C,cAAc,mBAAmB;AACjC,cAAc,sBAAsB"}
@@ -0,0 +1,17 @@
1
+ import type { Locale } from '../../i18n/locales.js';
2
+ import type { EntityFieldDescriptor } from './describe-entity-columns.js';
3
+ /**
4
+ * The property a filter or sort names for a member: its wire key, or for a
5
+ * `localized` member the path to one locale's text — the only way one is
6
+ * queried (`title.en`). A control building a query from a descriptor asks this
7
+ * rather than reading `key`, so it never sends the bare object the server
8
+ * refuses.
9
+ */
10
+ export declare function queryPropertyFor(descriptor: EntityFieldDescriptor, locale: Locale): string;
11
+ /**
12
+ * The member an applied filter or sort names — by its accessor name, its wire
13
+ * key, or a locale path into a `localized` one — so a control can show what is
14
+ * in effect.
15
+ */
16
+ export declare function findQueriedDescriptor(descriptors: readonly EntityFieldDescriptor[], property: string): EntityFieldDescriptor | undefined;
17
+ //# sourceMappingURL=query-property.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-property.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/describe/query-property.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE,MAAM,GACb,MAAM,CAIR;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,SAAS,qBAAqB,EAAE,EAC7C,QAAQ,EAAE,MAAM,GACf,qBAAqB,GAAG,SAAS,CASnC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The property a filter or sort names for a member: its wire key, or for a
3
+ * `localized` member the path to one locale's text — the only way one is
4
+ * queried (`title.en`). A control building a query from a descriptor asks this
5
+ * rather than reading `key`, so it never sends the bare object the server
6
+ * refuses.
7
+ */ export function queryPropertyFor(descriptor, locale) {
8
+ return descriptor.type === 'localized' ? `${descriptor.key}.${locale}` : descriptor.key;
9
+ }
10
+ /**
11
+ * The member an applied filter or sort names — by its accessor name, its wire
12
+ * key, or a locale path into a `localized` one — so a control can show what is
13
+ * in effect.
14
+ */ export function findQueriedDescriptor(descriptors, property) {
15
+ return descriptors.find((descriptor)=>descriptor.key === property || descriptor.name === property || descriptor.type === 'localized' && (property.startsWith(`${descriptor.key}.`) || property.startsWith(`${descriptor.name}.`)));
16
+ }
17
+
18
+ //# sourceMappingURL=query-property.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/entity-definition/describe/query-property.ts"],"sourcesContent":["import type { Locale } from '../../i18n/locales.js';\nimport type { EntityFieldDescriptor } from './describe-entity-columns.js';\n\n/**\n * The property a filter or sort names for a member: its wire key, or for a\n * `localized` member the path to one locale's text — the only way one is\n * queried (`title.en`). A control building a query from a descriptor asks this\n * rather than reading `key`, so it never sends the bare object the server\n * refuses.\n */\nexport function queryPropertyFor(\n descriptor: EntityFieldDescriptor,\n locale: Locale,\n): string {\n return descriptor.type === 'localized'\n ? `${descriptor.key}.${locale}`\n : descriptor.key;\n}\n\n/**\n * The member an applied filter or sort names — by its accessor name, its wire\n * key, or a locale path into a `localized` one — so a control can show what is\n * in effect.\n */\nexport function findQueriedDescriptor(\n descriptors: readonly EntityFieldDescriptor[],\n property: string,\n): EntityFieldDescriptor | undefined {\n return descriptors.find(\n descriptor =>\n descriptor.key === property ||\n descriptor.name === property ||\n (descriptor.type === 'localized' &&\n (property.startsWith(`${descriptor.key}.`) ||\n property.startsWith(`${descriptor.name}.`))),\n );\n}\n"],"names":["queryPropertyFor","descriptor","locale","type","key","findQueriedDescriptor","descriptors","property","find","name","startsWith"],"mappings":"AAGA;;;;;;CAMC,GACD,OAAO,SAASA,iBACdC,UAAiC,EACjCC,MAAc;IAEd,OAAOD,WAAWE,IAAI,KAAK,cACvB,GAAGF,WAAWG,GAAG,CAAC,CAAC,EAAEF,QAAQ,GAC7BD,WAAWG,GAAG;AACpB;AAEA;;;;CAIC,GACD,OAAO,SAASC,sBACdC,WAA6C,EAC7CC,QAAgB;IAEhB,OAAOD,YAAYE,IAAI,CACrBP,CAAAA,aACEA,WAAWG,GAAG,KAAKG,YACnBN,WAAWQ,IAAI,KAAKF,YACnBN,WAAWE,IAAI,KAAK,eAClBI,CAAAA,SAASG,UAAU,CAAC,GAAGT,WAAWG,GAAG,CAAC,CAAC,CAAC,KACvCG,SAASG,UAAU,CAAC,GAAGT,WAAWQ,IAAI,CAAC,CAAC,CAAC,CAAA;AAEnD"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A count option that cannot be honoured is a declaration error, thrown when
3
+ * the class is decorated rather than the first time a set of records is
4
+ * checked.
5
+ */
6
+ export declare function assertBounds(owner: string, bounds: {
7
+ readonly min?: number;
8
+ readonly max?: number;
9
+ } | undefined): void;
10
+ //# sourceMappingURL=bounds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bounds.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/meta-entities/bounds.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE;IAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACnE,IAAI,CAgBN"}
@@ -0,0 +1,17 @@
1
+ import { EntifixBuildError } from '../../base-entities/entifix-error/index.js';
2
+ const isCount = (value)=>value === undefined || Number.isInteger(value) && value >= 0;
3
+ /**
4
+ * A count option that cannot be honoured is a declaration error, thrown when
5
+ * the class is decorated rather than the first time a set of records is
6
+ * checked.
7
+ */ export function assertBounds(owner, bounds) {
8
+ if (bounds === undefined) return;
9
+ if (!isCount(bounds.min) || !isCount(bounds.max)) {
10
+ throw new EntifixBuildError(`${owner} declares a count that is not a whole number of zero or more.`);
11
+ }
12
+ if (bounds.min !== undefined && bounds.max !== undefined && bounds.min > bounds.max) {
13
+ throw new EntifixBuildError(`${owner} declares a minimum of ${bounds.min} above its maximum of ${bounds.max}.`);
14
+ }
15
+ }
16
+
17
+ //# sourceMappingURL=bounds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/entity-definition/meta-entities/bounds.ts"],"sourcesContent":["import { EntifixBuildError } from '../../base-entities/entifix-error/index.js';\n\nconst isCount = (value: number | undefined) =>\n value === undefined || (Number.isInteger(value) && value >= 0);\n\n/**\n * A count option that cannot be honoured is a declaration error, thrown when\n * the class is decorated rather than the first time a set of records is\n * checked.\n */\nexport function assertBounds(\n owner: string,\n bounds: { readonly min?: number; readonly max?: number } | undefined,\n): void {\n if (bounds === undefined) return;\n if (!isCount(bounds.min) || !isCount(bounds.max)) {\n throw new EntifixBuildError(\n `${owner} declares a count that is not a whole number of zero or more.`,\n );\n }\n if (\n bounds.min !== undefined &&\n bounds.max !== undefined &&\n bounds.min > bounds.max\n ) {\n throw new EntifixBuildError(\n `${owner} declares a minimum of ${bounds.min} above its maximum of ${bounds.max}.`,\n );\n }\n}\n"],"names":["EntifixBuildError","isCount","value","undefined","Number","isInteger","assertBounds","owner","bounds","min","max"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,6CAA6C;AAE/E,MAAMC,UAAU,CAACC,QACfA,UAAUC,aAAcC,OAAOC,SAAS,CAACH,UAAUA,SAAS;AAE9D;;;;CAIC,GACD,OAAO,SAASI,aACdC,KAAa,EACbC,MAAoE;IAEpE,IAAIA,WAAWL,WAAW;IAC1B,IAAI,CAACF,QAAQO,OAAOC,GAAG,KAAK,CAACR,QAAQO,OAAOE,GAAG,GAAG;QAChD,MAAM,IAAIV,kBACR,GAAGO,MAAM,6DAA6D,CAAC;IAE3E;IACA,IACEC,OAAOC,GAAG,KAAKN,aACfK,OAAOE,GAAG,KAAKP,aACfK,OAAOC,GAAG,GAAGD,OAAOE,GAAG,EACvB;QACA,MAAM,IAAIV,kBACR,GAAGO,MAAM,uBAAuB,EAAEC,OAAOC,GAAG,CAAC,sBAAsB,EAAED,OAAOE,GAAG,CAAC,CAAC,CAAC;IAEtF;AACF"}
@@ -4,8 +4,11 @@ export type MetaAccessorKind = 'getter' | 'setter';
4
4
  * care. It is deliberately a presentation/behaviour taxonomy rather than a
5
5
  * TypeScript mirror: `id` and `enum` are strings at runtime but are rendered,
6
6
  * sorted and filtered differently.
7
+ *
8
+ * `localized` holds a text per locale (`LocalizedText`). It is queried only
9
+ * through one locale at a time — `name.en` — never as the whole object.
7
10
  */
8
- export declare const MetaAccessorTypes: readonly ["string", "number", "boolean", "date", "enum", "id", "link", "linkCollection", "composition", "scalarCollection"];
11
+ export declare const MetaAccessorTypes: readonly ["string", "number", "boolean", "date", "enum", "id", "link", "linkCollection", "composition", "scalarCollection", "localized"];
9
12
  export type MetaAccessorType = (typeof MetaAccessorTypes)[number];
10
13
  /**
11
14
  * The class whose accessors describe one row of a `composition` member.
@@ -111,6 +114,23 @@ export interface MetaAccessorOptions {
111
114
  * The identity member is always reset and needs no flag.
112
115
  */
113
116
  resetOnClone?: boolean;
117
+ /**
118
+ * The fewest values a collection member may hold — `1` for "not empty". Only
119
+ * meaningful on a collection type (`scalarCollection`, `linkCollection`,
120
+ * `composition`); declaring it elsewhere fails when the entity is described.
121
+ *
122
+ * Distinct from {@link required}: on a collection `required` already means
123
+ * "at least one", and a `minItems` of two is a fact `required` cannot state.
124
+ */
125
+ minItems?: number;
126
+ /** The most values a collection member may hold. Collection types only. */
127
+ maxItems?: number;
128
+ /**
129
+ * Another `date` member this one may not precede — a period's `end` declares
130
+ * `notBefore: 'start'`. Checked only when both hold a value. Both members must
131
+ * be `date`s, or describing the entity fails.
132
+ */
133
+ notBefore?: string;
114
134
  }
115
135
  export declare class MetaAccessor {
116
136
  readonly name: string | symbol;
@@ -132,6 +152,9 @@ export declare class MetaAccessor {
132
152
  readonly linkSerialization?: EntityLinkSerialization;
133
153
  readonly childType?: () => ChildConstructor;
134
154
  readonly resetOnClone?: boolean;
155
+ readonly minItems?: number;
156
+ readonly maxItems?: number;
157
+ readonly notBefore?: string;
135
158
  constructor(name: string | symbol, kind: MetaAccessorKind, options?: MetaAccessorOptions);
136
159
  }
137
160
  //# sourceMappingURL=meta-accessor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"meta-accessor.d.ts","sourceRoot":"","sources":["../../../../src/entity-definition/meta-entities/meta-accessor/meta-accessor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"meta-accessor.d.ts","sourceRoot":"","sources":["../../../../src/entity-definition/meta-entities/meta-accessor/meta-accessor.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,9 @@
3
3
  * care. It is deliberately a presentation/behaviour taxonomy rather than a
4
4
  * TypeScript mirror: `id` and `enum` are strings at runtime but are rendered,
5
5
  * sorted and filtered differently.
6
+ *
7
+ * `localized` holds a text per locale (`LocalizedText`). It is queried only
8
+ * through one locale at a time — `name.en` — never as the whole object.
6
9
  */ export const MetaAccessorTypes = [
7
10
  'string',
8
11
  'number',
@@ -13,7 +16,8 @@
13
16
  'link',
14
17
  'linkCollection',
15
18
  'composition',
16
- 'scalarCollection'
19
+ 'scalarCollection',
20
+ 'localized'
17
21
  ];
18
22
  /**
19
23
  * The wire shape a relation takes: the target's foreign key, or the whole
@@ -46,6 +50,9 @@ export class MetaAccessor {
46
50
  this.linkSerialization = options == null ? void 0 : options.linkSerialization;
47
51
  this.childType = options == null ? void 0 : options.childType;
48
52
  this.resetOnClone = options == null ? void 0 : options.resetOnClone;
53
+ this.minItems = options == null ? void 0 : options.minItems;
54
+ this.maxItems = options == null ? void 0 : options.maxItems;
55
+ this.notBefore = options == null ? void 0 : options.notBefore;
49
56
  }
50
57
  }
51
58
 
@@ -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 */\nexport const MetaAccessorTypes = [\n 'string',\n 'number',\n 'boolean',\n 'date',\n 'enum',\n 'id',\n 'link',\n 'linkCollection',\n 'composition',\n 'scalarCollection',\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\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\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 }\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"],"mappings":"AAEA;;;;;CAKC,GACD,OAAO,MAAMA,oBAAoB;IAC/B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAU;AA0BX;;;;CAIC,GACD,OAAO,MAAMC,2BAA2B;IAAC;IAAM;CAAW,CAAU;AAqFpE,OAAO,MAAMC;IAuBX,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;IAC3C;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 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"}