@entifix/core 0.6.0 → 0.7.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.
- package/dist/entity-definition/describe/describe-entity-columns.d.ts +24 -0
- package/dist/entity-definition/describe/describe-entity-columns.d.ts.map +1 -1
- package/dist/entity-definition/describe/describe-entity-columns.js +36 -2
- package/dist/entity-definition/describe/describe-entity-columns.js.map +1 -1
- package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.d.ts +13 -0
- package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.d.ts.map +1 -1
- package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.js +1 -0
- package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.js.map +1 -1
- package/dist/entity-definition/reconstruct/clone-entity-draft.d.ts.map +1 -1
- package/dist/entity-definition/reconstruct/clone-entity-draft.js +4 -1
- package/dist/entity-definition/reconstruct/clone-entity-draft.js.map +1 -1
- package/dist/hierarchy/hierarchy.d.ts +77 -0
- package/dist/hierarchy/hierarchy.d.ts.map +1 -0
- package/dist/hierarchy/hierarchy.js +165 -0
- package/dist/hierarchy/hierarchy.js.map +1 -0
- package/dist/hierarchy/index.d.ts +2 -0
- package/dist/hierarchy/index.d.ts.map +1 -0
- package/dist/hierarchy/index.js +3 -0
- package/dist/hierarchy/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -51,6 +51,8 @@ export interface EntityFieldDescriptor {
|
|
|
51
51
|
maxItems?: number;
|
|
52
52
|
/** The `date` member this one may not precede. */
|
|
53
53
|
notBefore?: string;
|
|
54
|
+
/** This member holds the parent record's id: the entity forms a tree. */
|
|
55
|
+
parent?: boolean;
|
|
54
56
|
}
|
|
55
57
|
/** Types whose values are scalars a user can meaningfully sort/filter on. */
|
|
56
58
|
export declare const SCALAR_TYPES: readonly MetaAccessorType[];
|
|
@@ -68,6 +70,28 @@ export declare const SCALAR_TYPES: readonly MetaAccessorType[];
|
|
|
68
70
|
* to add silently.
|
|
69
71
|
*/
|
|
70
72
|
export declare const COLLECTION_TYPES: readonly MetaAccessorType[];
|
|
73
|
+
/**
|
|
74
|
+
* One parent member per entity. Two would be two trees over the same records,
|
|
75
|
+
* and which one a table indents by would depend on declaration order.
|
|
76
|
+
*/
|
|
77
|
+
export declare function assertOneParent(members: readonly {
|
|
78
|
+
readonly name: string | symbol;
|
|
79
|
+
readonly parent?: boolean;
|
|
80
|
+
}[]): void;
|
|
81
|
+
/**
|
|
82
|
+
* ⚠️ **A parent member must travel.** The serializer skips a `hidden` or
|
|
83
|
+
* `readonly` member in both directions, so the parent key would not survive a
|
|
84
|
+
* single trip over the wire and the tree would arrive flat, with nothing
|
|
85
|
+
* failing. Hide it from a table or a form with a slot instead. Checked before
|
|
86
|
+
* hidden members are dropped, since a hidden parent is exactly the one
|
|
87
|
+
* describing would never see.
|
|
88
|
+
*/
|
|
89
|
+
export declare function assertParentVisible(members: readonly {
|
|
90
|
+
readonly name: string | symbol;
|
|
91
|
+
readonly parent?: boolean;
|
|
92
|
+
readonly hidden?: boolean;
|
|
93
|
+
readonly readonly?: boolean;
|
|
94
|
+
}[]): void;
|
|
71
95
|
/**
|
|
72
96
|
* Resolves an entity's displayable members from its accessor metadata, sorted by
|
|
73
97
|
* `order` (declaration order when undeclared).
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;IACnB,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,6EAA6E;AAC7E,eAAO,MAAM,YAAY,EAAE,SAAS,gBAAgB,EAMnD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,gBAAgB,EAIvD,CAAC;AAgGF;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,SAAS;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B,EAAE,QAQJ;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,SAAS;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,EAAE,QAWJ;AA4CD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,SAAS,MAAM,EAC1D,iBAAiB,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAC5C,MAAM,CAAC,EAAE,OAAO,GACf,qBAAqB,EAAE,CAiBzB"}
|
|
@@ -84,12 +84,43 @@ import { assertBounds } from '../meta-entities/bounds.js';
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* A parent is one record's id, so it is an `id` or a `string` member — a
|
|
89
|
+
* number or a date cannot name a record, and a collection would be two
|
|
90
|
+
* parents. Checked per member, since the type is only known once inferred.
|
|
91
|
+
*/ function assertParentType(metaAccessor, type) {
|
|
92
|
+
if (metaAccessor.parent !== true) return;
|
|
93
|
+
if (type === 'id' || type === 'string') return;
|
|
94
|
+
throw new EntifixBuildError(`"${String(metaAccessor.name)}" is a ${type} and cannot be the parent member — a parent is one record's id, so it is an id or a string.`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* One parent member per entity. Two would be two trees over the same records,
|
|
98
|
+
* and which one a table indents by would depend on declaration order.
|
|
99
|
+
*/ export function assertOneParent(members) {
|
|
100
|
+
const parents = members.filter((member)=>member.parent === true);
|
|
101
|
+
if (parents.length > 1) {
|
|
102
|
+
throw new EntifixBuildError(`An entity declares one parent member at most; found ${parents.map((member)=>`"${String(member.name)}"`).join(', ')}.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* ⚠️ **A parent member must travel.** The serializer skips a `hidden` or
|
|
107
|
+
* `readonly` member in both directions, so the parent key would not survive a
|
|
108
|
+
* single trip over the wire and the tree would arrive flat, with nothing
|
|
109
|
+
* failing. Hide it from a table or a form with a slot instead. Checked before
|
|
110
|
+
* hidden members are dropped, since a hidden parent is exactly the one
|
|
111
|
+
* describing would never see.
|
|
112
|
+
*/ export function assertParentVisible(members) {
|
|
113
|
+
const stuck = members.find((member)=>member.parent === true && (member.hidden === true || member.readonly === true));
|
|
114
|
+
if (stuck === undefined) return;
|
|
115
|
+
throw new EntifixBuildError(`"${String(stuck.name)}" is the parent member and is ${stuck.hidden === true ? 'hidden' : 'readonly'} — such a member is never serialized, so the tree would arrive flat. Hide it with a slot instead.`);
|
|
116
|
+
}
|
|
87
117
|
function toDescriptor(metaAccessor, index, sample) {
|
|
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;
|
|
118
|
+
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, _metaAccessor_parent;
|
|
89
119
|
const name = String(metaAccessor.name);
|
|
90
120
|
const type = (_metaAccessor_type = metaAccessor.type) != null ? _metaAccessor_type : inferType(name, sample == null ? void 0 : sample[name]);
|
|
91
121
|
assertNotQueryable(metaAccessor, type);
|
|
92
122
|
assertItemCounts(metaAccessor, type);
|
|
123
|
+
assertParentType(metaAccessor, type);
|
|
93
124
|
// A localized member is queried one locale at a time (`name.en`), which
|
|
94
125
|
// makes it as queryable as the text each locale holds.
|
|
95
126
|
const isScalar = SCALAR_TYPES.includes(type) || type === 'localized';
|
|
@@ -114,7 +145,8 @@ function toDescriptor(metaAccessor, index, sample) {
|
|
|
114
145
|
resetOnClone: (_metaAccessor_resetOnClone = metaAccessor.resetOnClone) != null ? _metaAccessor_resetOnClone : false,
|
|
115
146
|
minItems: metaAccessor.minItems,
|
|
116
147
|
maxItems: metaAccessor.maxItems,
|
|
117
|
-
notBefore: metaAccessor.notBefore
|
|
148
|
+
notBefore: metaAccessor.notBefore,
|
|
149
|
+
parent: (_metaAccessor_parent = metaAccessor.parent) != null ? _metaAccessor_parent : false
|
|
118
150
|
};
|
|
119
151
|
}
|
|
120
152
|
/**
|
|
@@ -135,6 +167,7 @@ function toDescriptor(metaAccessor, index, sample) {
|
|
|
135
167
|
* from one implementation.
|
|
136
168
|
*/ export function describeEntityColumns(entityConstructor, sample) {
|
|
137
169
|
const seen = new Set();
|
|
170
|
+
assertParentVisible(extractMetaAccessors(entityConstructor));
|
|
138
171
|
const descriptors = extractMetaAccessors(entityConstructor).filter((metaAccessor)=>{
|
|
139
172
|
if (metaAccessor.hidden || metaAccessor.kind !== 'getter') return false;
|
|
140
173
|
const name = String(metaAccessor.name);
|
|
@@ -143,6 +176,7 @@ function toDescriptor(metaAccessor, index, sample) {
|
|
|
143
176
|
return true;
|
|
144
177
|
}).map((metaAccessor, index)=>toDescriptor(metaAccessor, index, sample)).sort((left, right)=>left.order - right.order);
|
|
145
178
|
assertNotBefore(descriptors);
|
|
179
|
+
assertOneParent(descriptors);
|
|
146
180
|
return descriptors;
|
|
147
181
|
}
|
|
148
182
|
|
|
@@ -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 { 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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"}
|
|
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 /** This member holds the parent record's id: the entity forms a tree. */\n parent?: 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}, 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\n/**\n * A parent is one record's id, so it is an `id` or a `string` member — a\n * number or a date cannot name a record, and a collection would be two\n * parents. Checked per member, since the type is only known once inferred.\n */\nfunction assertParentType(metaAccessor: MetaAccessor, type: MetaAccessorType) {\n if (metaAccessor.parent !== true) return;\n if (type === 'id' || type === 'string') return;\n throw new EntifixBuildError(\n `\"${String(metaAccessor.name)}\" is a ${type} and cannot be the parent member — a parent is one record's id, so it is an id or a string.`,\n );\n}\n\n/**\n * One parent member per entity. Two would be two trees over the same records,\n * and which one a table indents by would depend on declaration order.\n */\nexport function assertOneParent(\n members: readonly {\n readonly name: string | symbol;\n readonly parent?: boolean;\n }[],\n) {\n const parents = members.filter(member => member.parent === true);\n if (parents.length > 1) {\n throw new EntifixBuildError(\n `An entity declares one parent member at most; found ${parents.map(member => `\"${String(member.name)}\"`).join(', ')}.`,\n );\n }\n}\n\n/**\n * ⚠️ **A parent member must travel.** The serializer skips a `hidden` or\n * `readonly` member in both directions, so the parent key would not survive a\n * single trip over the wire and the tree would arrive flat, with nothing\n * failing. Hide it from a table or a form with a slot instead. Checked before\n * hidden members are dropped, since a hidden parent is exactly the one\n * describing would never see.\n */\nexport function assertParentVisible(\n members: readonly {\n readonly name: string | symbol;\n readonly parent?: boolean;\n readonly hidden?: boolean;\n readonly readonly?: boolean;\n }[],\n) {\n const stuck = members.find(\n member =>\n member.parent === true &&\n (member.hidden === true || member.readonly === true),\n );\n if (stuck === undefined) return;\n throw new EntifixBuildError(\n `\"${String(stuck.name)}\" is the parent member and is ${stuck.hidden === true ? 'hidden' : 'readonly'} — such a member is never serialized, so the tree would arrive flat. Hide it with a slot instead.`,\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 assertParentType(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 parent: metaAccessor.parent ?? 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 assertParentVisible(extractMetaAccessors(entityConstructor));\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 assertOneParent(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","assertParentType","parent","assertOneParent","members","parents","filter","member","length","map","join","assertParentVisible","stuck","hidden","readonly","toDescriptor","index","sample","isScalar","linkLabelProperty","key","alias","label","labelKey","order","required","enumValues","enumLabelKey","linkSearchProperty","linkSerialization","childType","resetOnClone","describeEntityColumns","entityConstructor","seen","Set","kind","has","add","sort","left","right"],"mappings":"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"}
|
|
@@ -131,6 +131,18 @@ export interface MetaAccessorOptions {
|
|
|
131
131
|
* be `date`s, or describing the entity fails.
|
|
132
132
|
*/
|
|
133
133
|
notBefore?: string;
|
|
134
|
+
/**
|
|
135
|
+
* This member holds the id of the record's **parent**, another record of
|
|
136
|
+
* the same entity — so the entity forms a tree. At most one member per
|
|
137
|
+
* entity, and it must be an `id` or a `string`; describing the entity fails
|
|
138
|
+
* otherwise.
|
|
139
|
+
*
|
|
140
|
+
* An id rather than a `link` on purpose. A tree is always built from the
|
|
141
|
+
* whole set of its records ({@link buildTree}), never by resolving one parent
|
|
142
|
+
* at a time, so a link's lazy resolution would be a fetch per node that
|
|
143
|
+
* nothing needs. Absent means a root.
|
|
144
|
+
*/
|
|
145
|
+
parent?: boolean;
|
|
134
146
|
}
|
|
135
147
|
export declare class MetaAccessor {
|
|
136
148
|
readonly name: string | symbol;
|
|
@@ -155,6 +167,7 @@ export declare class MetaAccessor {
|
|
|
155
167
|
readonly minItems?: number;
|
|
156
168
|
readonly maxItems?: number;
|
|
157
169
|
readonly notBefore?: string;
|
|
170
|
+
readonly parent?: boolean;
|
|
158
171
|
constructor(name: string | symbol, kind: MetaAccessorKind, options?: MetaAccessorOptions);
|
|
159
172
|
}
|
|
160
173
|
//# 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":"AAAA,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnD;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,0IAYpB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,gBAAgB,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,IAAI,KAI7D,GAAG,IAAI,EAAE,GAAG,EAAE,KACX,MAAM,CAAC;AAEZ;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,6BAA8B,CAAC;AACpE,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhF,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;IAC5C;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,MAAM,gBAAgB,CAAC;IACnC;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
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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"}
|
|
@@ -53,6 +53,7 @@ export class MetaAccessor {
|
|
|
53
53
|
this.minItems = options == null ? void 0 : options.minItems;
|
|
54
54
|
this.maxItems = options == null ? void 0 : options.maxItems;
|
|
55
55
|
this.notBefore = options == null ? void 0 : options.notBefore;
|
|
56
|
+
this.parent = options == null ? void 0 : options.parent;
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -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 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"}
|
|
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 * This member holds the id of the record's **parent**, another record of\n * the same entity — so the entity forms a tree. At most one member per\n * entity, and it must be an `id` or a `string`; describing the entity fails\n * otherwise.\n *\n * An id rather than a `link` on purpose. A tree is always built from the\n * whole set of its records ({@link buildTree}), never by resolving one parent\n * at a time, so a link's lazy resolution would be a fetch per node that\n * nothing needs. Absent means a root.\n */\n parent?: 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 readonly minItems?: number;\n readonly maxItems?: number;\n readonly notBefore?: string;\n readonly parent?: 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 this.minItems = options?.minItems;\n this.maxItems = options?.maxItems;\n this.notBefore = options?.notBefore;\n this.parent = options?.parent;\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","parent"],"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;AAkHpE,OAAO,MAAMC;IA2BX,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;QACnC,IAAI,CAACC,MAAM,GAAGrB,2BAAAA,QAASqB,MAAM;IAC/B;AAQF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clone-entity-draft.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/reconstruct/clone-entity-draft.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAyB,MAAM,4BAA4B,CAAC;AAWhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,MAAM,EACrD,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAC7C,MAAM,EAAE,WAAW,GAClB,WAAW,
|
|
1
|
+
{"version":3,"file":"clone-entity-draft.d.ts","sourceRoot":"","sources":["../../../src/entity-definition/reconstruct/clone-entity-draft.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAyB,MAAM,4BAA4B,CAAC;AAWhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,MAAM,EACrD,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAC7C,MAAM,EAAE,WAAW,GAClB,WAAW,CA6Bb"}
|
|
@@ -46,7 +46,10 @@ import { describeEntityColumns } from '../describe/index.js';
|
|
|
46
46
|
cloned[descriptor.name] = clonedRows(cloned[descriptor.name], descriptor.resetOnClone);
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
// An `id` member is reset unless it is the parent: a copy of a node is its
|
|
50
|
+
// sibling, and a copy that came out as a root would be moved, not copied.
|
|
51
|
+
const isIdentityLike = descriptor.type === 'id' && !descriptor.parent;
|
|
52
|
+
if (!isIdentityLike && !descriptor.resetOnClone) continue;
|
|
50
53
|
if (!(descriptor.name in cloned)) continue;
|
|
51
54
|
cloned[descriptor.name] = '';
|
|
52
55
|
}
|
|
@@ -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, 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
|
|
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 // An `id` member is reset unless it is the parent: a copy of a node is its\n // sibling, and a copy that came out as a root would be moved, not copied.\n const isIdentityLike = descriptor.type === 'id' && !descriptor.parent;\n if (!isIdentityLike && !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","isIdentityLike","parent","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,2EAA2E;QAC3E,0EAA0E;QAC1E,MAAMC,iBAAiBL,WAAWC,IAAI,KAAK,QAAQ,CAACD,WAAWM,MAAM;QACrE,IAAI,CAACD,kBAAkB,CAACL,WAAWI,YAAY,EAAE;QACjD,IAAI,CAAEJ,CAAAA,WAAWE,IAAI,IAAIH,MAAK,GAAI;QAClCA,MAAM,CAACC,WAAWE,IAAI,CAAC,GAAG;IAC5B;IAEA,OAAOH;AACT;AAEA;;;;;;;;;;;;;CAaC,GACD,SAASI,WACPI,KAAmC,EACnCC,KAAc;IAEd,IAAIA,OAAO,OAAO,EAAE;IACpB,OAAOhB,cAAce,OAAOE,GAAG,CAACC,CAAAA,MAAQ,aAAKA;YAAK,CAACjB,QAAQ,EAAEF;;AAC/D"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ChildConstructor } from '../entity-definition/meta-entities/meta-accessor/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* How to read a record's place in a tree: its own key, and its parent's key or
|
|
4
|
+
* `undefined` for a root. Every helper here takes one, so the same walk serves
|
|
5
|
+
* an entity, a plain fixture row and a draft.
|
|
6
|
+
*/
|
|
7
|
+
export interface HierarchyKeys<T> {
|
|
8
|
+
readonly idOf: (item: T) => string;
|
|
9
|
+
readonly parentOf: (item: T) => string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/** One record and the records whose parent it is, in their incoming order. */
|
|
12
|
+
export interface TreeNode<T> {
|
|
13
|
+
readonly item: T;
|
|
14
|
+
readonly depth: number;
|
|
15
|
+
readonly children: readonly TreeNode<T>[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A set of records arranged as a tree.
|
|
19
|
+
*
|
|
20
|
+
* `detached` holds the records on a cycle — `a` is `b`'s parent and `b` is
|
|
21
|
+
* `a`'s. No root leads to them, so they are in no subtree. They are
|
|
22
|
+
* returned rather than dropped, because a record that silently disappears
|
|
23
|
+
* from a listing is a defect nobody can see. A host that enforces the
|
|
24
|
+
* invariant at save ({@link wouldCycle}) never has any.
|
|
25
|
+
*/
|
|
26
|
+
export interface Tree<T> {
|
|
27
|
+
readonly roots: readonly TreeNode<T>[];
|
|
28
|
+
readonly detached: readonly T[];
|
|
29
|
+
}
|
|
30
|
+
/** A tree node as one row of a flat listing. */
|
|
31
|
+
export interface TreeRow<T> {
|
|
32
|
+
readonly item: T;
|
|
33
|
+
readonly depth: number;
|
|
34
|
+
readonly hasChildren: boolean;
|
|
35
|
+
/** The keys of every ancestor, root first. */
|
|
36
|
+
readonly path: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Arrange records as a tree.
|
|
40
|
+
*
|
|
41
|
+
* A record whose parent is not in the set is a **root**: a page of records
|
|
42
|
+
* holds children whose parents are on another page, and a retired or deleted
|
|
43
|
+
* parent leaves its children behind. Siblings keep the order they came in,
|
|
44
|
+
* so a sorted load stays sorted within each level.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildTree<T>(items: readonly T[], keys: HierarchyKeys<T>): Tree<T>;
|
|
47
|
+
/** The tree as rows, depth first: each record followed by its subtree. */
|
|
48
|
+
export declare function flattenTree<T>(tree: Tree<T>, keys: HierarchyKeys<T>): TreeRow<T>[];
|
|
49
|
+
/**
|
|
50
|
+
* The ancestors of `key`, root first, not including the record itself.
|
|
51
|
+
*
|
|
52
|
+
* Stops at the first key it has already visited, so data that holds a cycle
|
|
53
|
+
* yields a finite chain rather than a hung render.
|
|
54
|
+
*/
|
|
55
|
+
export declare function ancestorsOf<T>(items: readonly T[], keys: HierarchyKeys<T>, key: string): T[];
|
|
56
|
+
/** The keys of every record under `key`, at any depth. Cycle-safe. */
|
|
57
|
+
export declare function descendantsOf<T>(items: readonly T[], keys: HierarchyKeys<T>, key: string): string[];
|
|
58
|
+
/**
|
|
59
|
+
* Would giving `key` the parent `parent` make a cycle? True when the parent is
|
|
60
|
+
* the record itself or anything under it. The check a save runs, and the set a
|
|
61
|
+
* parent picker excludes.
|
|
62
|
+
*/
|
|
63
|
+
export declare function wouldCycle<T>(items: readonly T[], keys: HierarchyKeys<T>, key: string, parent: string | undefined): boolean;
|
|
64
|
+
/** How deep `key` sits: 1 for a root, 2 for its children, and so on. */
|
|
65
|
+
export declare function depthOf<T>(items: readonly T[], keys: HierarchyKeys<T>, key: string): number;
|
|
66
|
+
/**
|
|
67
|
+
* The keys an entity's own metadata declares: its `id`, and the member marked
|
|
68
|
+
* `@accessor({ parent: true })` — read by its wire key, the way a record
|
|
69
|
+
* arrives. `undefined` when the entity declares no parent and so is not a
|
|
70
|
+
* tree.
|
|
71
|
+
*
|
|
72
|
+
* Refuses a `hidden` or `readonly` parent, for the reason describing an
|
|
73
|
+
* entity does: such a member is never serialized, so the tree would arrive
|
|
74
|
+
* flat.
|
|
75
|
+
*/
|
|
76
|
+
export declare function hierarchyOf<T extends object>(target: ChildConstructor<T>): HierarchyKeys<T> | undefined;
|
|
77
|
+
//# sourceMappingURL=hierarchy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../src/hierarchy/hierarchy.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2DAA2D,CAAC;AAElG;;;;GAIG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;CACpD;AAED,8EAA8E;AAC9E,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;CAC3C;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,IAAI,CAAC,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;CACjC;AAED,gDAAgD;AAChD,MAAM,WAAW,OAAO,CAAC,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAQD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,GACrB,IAAI,CAAC,CAAC,CAAC,CA+BT;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,CAAC,EAC3B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EACb,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,GACrB,OAAO,CAAC,CAAC,CAAC,EAAE,CAed;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,EACtB,GAAG,EAAE,MAAM,GACV,CAAC,EAAE,CAaL;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,EACtB,GAAG,EAAE,MAAM,GACV,MAAM,EAAE,CAqBV;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,EACtB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,OAAO,CAGT;AAED,wEAAwE;AACxE,wBAAgB,OAAO,CAAC,CAAC,EACvB,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,EACtB,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC1C,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC1B,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,CA2B9B"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { EntifixBuildError } from '../base-entities/entifix-error/index.js';
|
|
2
|
+
import { assertOneParent, assertParentVisible } from '../entity-definition/describe/index.js';
|
|
3
|
+
import { extractMetaAccessors } from '../entity-definition/helpers/index.js';
|
|
4
|
+
/** An empty parent key is no parent, the way an empty form control is no value. */ const parentKey = (keys, item)=>{
|
|
5
|
+
const parent = keys.parentOf(item);
|
|
6
|
+
return parent === undefined || parent === '' ? undefined : parent;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Arrange records as a tree.
|
|
10
|
+
*
|
|
11
|
+
* A record whose parent is not in the set is a **root**: a page of records
|
|
12
|
+
* holds children whose parents are on another page, and a retired or deleted
|
|
13
|
+
* parent leaves its children behind. Siblings keep the order they came in,
|
|
14
|
+
* so a sorted load stays sorted within each level.
|
|
15
|
+
*/ export function buildTree(items, keys) {
|
|
16
|
+
const byKey = new Map(items.map((item)=>[
|
|
17
|
+
keys.idOf(item),
|
|
18
|
+
item
|
|
19
|
+
]));
|
|
20
|
+
const childrenOf = new Map();
|
|
21
|
+
const roots = [];
|
|
22
|
+
for (const item of items){
|
|
23
|
+
const parent = parentKey(keys, item);
|
|
24
|
+
if (parent === undefined || !byKey.has(parent)) {
|
|
25
|
+
roots.push(item);
|
|
26
|
+
} else {
|
|
27
|
+
var _childrenOf_get;
|
|
28
|
+
childrenOf.set(parent, [
|
|
29
|
+
...(_childrenOf_get = childrenOf.get(parent)) != null ? _childrenOf_get : [],
|
|
30
|
+
item
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const placed = new Set();
|
|
35
|
+
const node = (item, depth)=>{
|
|
36
|
+
var _childrenOf_get;
|
|
37
|
+
const key = keys.idOf(item);
|
|
38
|
+
placed.add(key);
|
|
39
|
+
return {
|
|
40
|
+
item,
|
|
41
|
+
depth,
|
|
42
|
+
children: ((_childrenOf_get = childrenOf.get(key)) != null ? _childrenOf_get : []).map((child)=>node(child, depth + 1))
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const tree = roots.map((root)=>node(root, 0));
|
|
46
|
+
return {
|
|
47
|
+
roots: tree,
|
|
48
|
+
detached: items.filter((item)=>!placed.has(keys.idOf(item)))
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** The tree as rows, depth first: each record followed by its subtree. */ export function flattenTree(tree, keys) {
|
|
52
|
+
const rows = [];
|
|
53
|
+
const walk = (node, path)=>{
|
|
54
|
+
rows.push({
|
|
55
|
+
item: node.item,
|
|
56
|
+
depth: node.depth,
|
|
57
|
+
hasChildren: node.children.length > 0,
|
|
58
|
+
path
|
|
59
|
+
});
|
|
60
|
+
for (const child of node.children){
|
|
61
|
+
walk(child, [
|
|
62
|
+
...path,
|
|
63
|
+
keys.idOf(node.item)
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
for (const root of tree.roots)walk(root, []);
|
|
68
|
+
return rows;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The ancestors of `key`, root first, not including the record itself.
|
|
72
|
+
*
|
|
73
|
+
* Stops at the first key it has already visited, so data that holds a cycle
|
|
74
|
+
* yields a finite chain rather than a hung render.
|
|
75
|
+
*/ export function ancestorsOf(items, keys, key) {
|
|
76
|
+
const byKey = new Map(items.map((item)=>[
|
|
77
|
+
keys.idOf(item),
|
|
78
|
+
item
|
|
79
|
+
]));
|
|
80
|
+
const chain = [];
|
|
81
|
+
const seen = new Set([
|
|
82
|
+
key
|
|
83
|
+
]);
|
|
84
|
+
let current = byKey.get(key);
|
|
85
|
+
while(current !== undefined){
|
|
86
|
+
const parent = parentKey(keys, current);
|
|
87
|
+
if (parent === undefined || seen.has(parent)) break;
|
|
88
|
+
seen.add(parent);
|
|
89
|
+
current = byKey.get(parent);
|
|
90
|
+
if (current !== undefined) chain.unshift(current);
|
|
91
|
+
}
|
|
92
|
+
return chain;
|
|
93
|
+
}
|
|
94
|
+
/** The keys of every record under `key`, at any depth. Cycle-safe. */ export function descendantsOf(items, keys, key) {
|
|
95
|
+
var _childrenOf_get;
|
|
96
|
+
const childrenOf = new Map();
|
|
97
|
+
for (const item of items){
|
|
98
|
+
var _childrenOf_get1;
|
|
99
|
+
const parent = parentKey(keys, item);
|
|
100
|
+
if (parent === undefined) continue;
|
|
101
|
+
childrenOf.set(parent, [
|
|
102
|
+
...(_childrenOf_get1 = childrenOf.get(parent)) != null ? _childrenOf_get1 : [],
|
|
103
|
+
keys.idOf(item)
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
const found = [];
|
|
107
|
+
const seen = new Set([
|
|
108
|
+
key
|
|
109
|
+
]);
|
|
110
|
+
const queue = [
|
|
111
|
+
...(_childrenOf_get = childrenOf.get(key)) != null ? _childrenOf_get : []
|
|
112
|
+
];
|
|
113
|
+
while(queue.length > 0){
|
|
114
|
+
var _childrenOf_get2;
|
|
115
|
+
const next = queue.shift();
|
|
116
|
+
if (seen.has(next)) continue;
|
|
117
|
+
seen.add(next);
|
|
118
|
+
found.push(next);
|
|
119
|
+
queue.push(...(_childrenOf_get2 = childrenOf.get(next)) != null ? _childrenOf_get2 : []);
|
|
120
|
+
}
|
|
121
|
+
return found;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Would giving `key` the parent `parent` make a cycle? True when the parent is
|
|
125
|
+
* the record itself or anything under it. The check a save runs, and the set a
|
|
126
|
+
* parent picker excludes.
|
|
127
|
+
*/ export function wouldCycle(items, keys, key, parent) {
|
|
128
|
+
if (parent === undefined || parent === '') return false;
|
|
129
|
+
return parent === key || descendantsOf(items, keys, key).includes(parent);
|
|
130
|
+
}
|
|
131
|
+
/** How deep `key` sits: 1 for a root, 2 for its children, and so on. */ export function depthOf(items, keys, key) {
|
|
132
|
+
return ancestorsOf(items, keys, key).length + 1;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The keys an entity's own metadata declares: its `id`, and the member marked
|
|
136
|
+
* `@accessor({ parent: true })` — read by its wire key, the way a record
|
|
137
|
+
* arrives. `undefined` when the entity declares no parent and so is not a
|
|
138
|
+
* tree.
|
|
139
|
+
*
|
|
140
|
+
* Refuses a `hidden` or `readonly` parent, for the reason describing an
|
|
141
|
+
* entity does: such a member is never serialized, so the tree would arrive
|
|
142
|
+
* flat.
|
|
143
|
+
*/ export function hierarchyOf(target) {
|
|
144
|
+
var _parent_alias;
|
|
145
|
+
const accessors = extractMetaAccessors(target).filter((accessor)=>accessor.kind === 'getter');
|
|
146
|
+
assertOneParent(accessors);
|
|
147
|
+
assertParentVisible(accessors);
|
|
148
|
+
const parent = accessors.find((accessor)=>accessor.parent === true);
|
|
149
|
+
if (parent === undefined) return undefined;
|
|
150
|
+
if (parent.type !== undefined && parent.type !== 'id' && parent.type !== 'string') {
|
|
151
|
+
throw new EntifixBuildError(`"${String(parent.name)}" is a ${parent.type} and cannot be the parent member — a parent is one record's id, so it is an id or a string.`);
|
|
152
|
+
}
|
|
153
|
+
const member = String((_parent_alias = parent.alias) != null ? _parent_alias : parent.name);
|
|
154
|
+
const read = (item, name)=>item[name];
|
|
155
|
+
const asKey = (value)=>value === undefined || value === null ? undefined : String(value);
|
|
156
|
+
return {
|
|
157
|
+
idOf: (item)=>{
|
|
158
|
+
var _read;
|
|
159
|
+
return String((_read = read(item, 'id')) != null ? _read : '');
|
|
160
|
+
},
|
|
161
|
+
parentOf: (item)=>asKey(read(item, member))
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
//# sourceMappingURL=hierarchy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hierarchy/hierarchy.ts"],"sourcesContent":["import { EntifixBuildError } from '../base-entities/entifix-error/index.js';\nimport {\n assertOneParent,\n assertParentVisible,\n} from '../entity-definition/describe/index.js';\nimport { extractMetaAccessors } from '../entity-definition/helpers/index.js';\nimport type { ChildConstructor } from '../entity-definition/meta-entities/meta-accessor/index.js';\n\n/**\n * How to read a record's place in a tree: its own key, and its parent's key or\n * `undefined` for a root. Every helper here takes one, so the same walk serves\n * an entity, a plain fixture row and a draft.\n */\nexport interface HierarchyKeys<T> {\n readonly idOf: (item: T) => string;\n readonly parentOf: (item: T) => string | undefined;\n}\n\n/** One record and the records whose parent it is, in their incoming order. */\nexport interface TreeNode<T> {\n readonly item: T;\n readonly depth: number;\n readonly children: readonly TreeNode<T>[];\n}\n\n/**\n * A set of records arranged as a tree.\n *\n * `detached` holds the records on a cycle — `a` is `b`'s parent and `b` is\n * `a`'s. No root leads to them, so they are in no subtree. They are\n * returned rather than dropped, because a record that silently disappears\n * from a listing is a defect nobody can see. A host that enforces the\n * invariant at save ({@link wouldCycle}) never has any.\n */\nexport interface Tree<T> {\n readonly roots: readonly TreeNode<T>[];\n readonly detached: readonly T[];\n}\n\n/** A tree node as one row of a flat listing. */\nexport interface TreeRow<T> {\n readonly item: T;\n readonly depth: number;\n readonly hasChildren: boolean;\n /** The keys of every ancestor, root first. */\n readonly path: readonly string[];\n}\n\n/** An empty parent key is no parent, the way an empty form control is no value. */\nconst parentKey = <T>(keys: HierarchyKeys<T>, item: T) => {\n const parent = keys.parentOf(item);\n return parent === undefined || parent === '' ? undefined : parent;\n};\n\n/**\n * Arrange records as a tree.\n *\n * A record whose parent is not in the set is a **root**: a page of records\n * holds children whose parents are on another page, and a retired or deleted\n * parent leaves its children behind. Siblings keep the order they came in,\n * so a sorted load stays sorted within each level.\n */\nexport function buildTree<T>(\n items: readonly T[],\n keys: HierarchyKeys<T>,\n): Tree<T> {\n const byKey = new Map(items.map(item => [keys.idOf(item), item]));\n const childrenOf = new Map<string, T[]>();\n const roots: T[] = [];\n for (const item of items) {\n const parent = parentKey(keys, item);\n if (parent === undefined || !byKey.has(parent)) {\n roots.push(item);\n } else {\n childrenOf.set(parent, [...(childrenOf.get(parent) ?? []), item]);\n }\n }\n\n const placed = new Set<string>();\n const node = (item: T, depth: number): TreeNode<T> => {\n const key = keys.idOf(item);\n placed.add(key);\n return {\n item,\n depth,\n children: (childrenOf.get(key) ?? []).map(child =>\n node(child, depth + 1),\n ),\n };\n };\n const tree = roots.map(root => node(root, 0));\n\n return {\n roots: tree,\n detached: items.filter(item => !placed.has(keys.idOf(item))),\n };\n}\n\n/** The tree as rows, depth first: each record followed by its subtree. */\nexport function flattenTree<T>(\n tree: Tree<T>,\n keys: HierarchyKeys<T>,\n): TreeRow<T>[] {\n const rows: TreeRow<T>[] = [];\n const walk = (node: TreeNode<T>, path: readonly string[]) => {\n rows.push({\n item: node.item,\n depth: node.depth,\n hasChildren: node.children.length > 0,\n path,\n });\n for (const child of node.children) {\n walk(child, [...path, keys.idOf(node.item)]);\n }\n };\n for (const root of tree.roots) walk(root, []);\n return rows;\n}\n\n/**\n * The ancestors of `key`, root first, not including the record itself.\n *\n * Stops at the first key it has already visited, so data that holds a cycle\n * yields a finite chain rather than a hung render.\n */\nexport function ancestorsOf<T>(\n items: readonly T[],\n keys: HierarchyKeys<T>,\n key: string,\n): T[] {\n const byKey = new Map(items.map(item => [keys.idOf(item), item]));\n const chain: T[] = [];\n const seen = new Set([key]);\n let current = byKey.get(key);\n while (current !== undefined) {\n const parent = parentKey(keys, current);\n if (parent === undefined || seen.has(parent)) break;\n seen.add(parent);\n current = byKey.get(parent);\n if (current !== undefined) chain.unshift(current);\n }\n return chain;\n}\n\n/** The keys of every record under `key`, at any depth. Cycle-safe. */\nexport function descendantsOf<T>(\n items: readonly T[],\n keys: HierarchyKeys<T>,\n key: string,\n): string[] {\n const childrenOf = new Map<string, string[]>();\n for (const item of items) {\n const parent = parentKey(keys, item);\n if (parent === undefined) continue;\n childrenOf.set(parent, [\n ...(childrenOf.get(parent) ?? []),\n keys.idOf(item),\n ]);\n }\n const found: string[] = [];\n const seen = new Set([key]);\n const queue = [...(childrenOf.get(key) ?? [])];\n while (queue.length > 0) {\n const next = queue.shift() as string;\n if (seen.has(next)) continue;\n seen.add(next);\n found.push(next);\n queue.push(...(childrenOf.get(next) ?? []));\n }\n return found;\n}\n\n/**\n * Would giving `key` the parent `parent` make a cycle? True when the parent is\n * the record itself or anything under it. The check a save runs, and the set a\n * parent picker excludes.\n */\nexport function wouldCycle<T>(\n items: readonly T[],\n keys: HierarchyKeys<T>,\n key: string,\n parent: string | undefined,\n): boolean {\n if (parent === undefined || parent === '') return false;\n return parent === key || descendantsOf(items, keys, key).includes(parent);\n}\n\n/** How deep `key` sits: 1 for a root, 2 for its children, and so on. */\nexport function depthOf<T>(\n items: readonly T[],\n keys: HierarchyKeys<T>,\n key: string,\n): number {\n return ancestorsOf(items, keys, key).length + 1;\n}\n\n/**\n * The keys an entity's own metadata declares: its `id`, and the member marked\n * `@accessor({ parent: true })` — read by its wire key, the way a record\n * arrives. `undefined` when the entity declares no parent and so is not a\n * tree.\n *\n * Refuses a `hidden` or `readonly` parent, for the reason describing an\n * entity does: such a member is never serialized, so the tree would arrive\n * flat.\n */\nexport function hierarchyOf<T extends object>(\n target: ChildConstructor<T>,\n): HierarchyKeys<T> | undefined {\n const accessors = extractMetaAccessors(target).filter(\n accessor => accessor.kind === 'getter',\n );\n assertOneParent(accessors);\n assertParentVisible(accessors);\n const parent = accessors.find(accessor => accessor.parent === true);\n if (parent === undefined) return undefined;\n if (\n parent.type !== undefined &&\n parent.type !== 'id' &&\n parent.type !== 'string'\n ) {\n throw new EntifixBuildError(\n `\"${String(parent.name)}\" is a ${parent.type} and cannot be the parent member — a parent is one record's id, so it is an id or a string.`,\n );\n }\n const member = String(parent.alias ?? parent.name);\n const read = (item: T, name: string) =>\n (item as Record<string, unknown>)[name];\n const asKey = (value: unknown) =>\n value === undefined || value === null ? undefined : String(value);\n\n return {\n idOf: item => String(read(item, 'id') ?? ''),\n parentOf: item => asKey(read(item, member)),\n };\n}\n"],"names":["EntifixBuildError","assertOneParent","assertParentVisible","extractMetaAccessors","parentKey","keys","item","parent","parentOf","undefined","buildTree","items","byKey","Map","map","idOf","childrenOf","roots","has","push","set","get","placed","Set","node","depth","key","add","children","child","tree","root","detached","filter","flattenTree","rows","walk","path","hasChildren","length","ancestorsOf","chain","seen","current","unshift","descendantsOf","found","queue","next","shift","wouldCycle","includes","depthOf","hierarchyOf","target","accessors","accessor","kind","find","type","String","name","member","alias","read","asKey","value"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hierarchy/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hierarchy/index.ts"],"sourcesContent":["export * from './hierarchy.js';\n"],"names":[],"mappings":"AAAA,cAAc,iBAAiB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './entity-definition/reconstruct/index.js';
|
|
|
22
22
|
export * from './entity-definition/serializer/index.js';
|
|
23
23
|
export * from './entity-definition/validate/index.js';
|
|
24
24
|
export * from './envelope/index.js';
|
|
25
|
+
export * from './hierarchy/index.js';
|
|
25
26
|
export * from './http/session-cookies.js';
|
|
26
27
|
export * from './i18n/format.js';
|
|
27
28
|
export * from './i18n/locales.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+CAA+C,CAAC;AAC9D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kDAAkD,CAAC;AACjE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,kDAAkD,CAAC;AACjE,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uDAAuD,CAAC;AACtE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uDAAuD,CAAC;AACtE,cAAc,iDAAiD,CAAC;AAChE,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,wDAAwD,CAAC;AACvE,cAAc,0DAA0D,CAAC;AACzE,cAAc,uCAAuC,CAAC;AACtD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+CAA+C,CAAC;AAC9D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kDAAkD,CAAC;AACjE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,kDAAkD,CAAC;AACjE,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uDAAuD,CAAC;AACtE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uDAAuD,CAAC;AACtE,cAAc,iDAAiD,CAAC;AAChE,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,wDAAwD,CAAC;AACvE,cAAc,0DAA0D,CAAC;AACzE,cAAc,uCAAuC,CAAC;AACtD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export * from './entity-definition/reconstruct/index.js';
|
|
|
22
22
|
export * from './entity-definition/serializer/index.js';
|
|
23
23
|
export * from './entity-definition/validate/index.js';
|
|
24
24
|
export * from './envelope/index.js';
|
|
25
|
+
export * from './hierarchy/index.js';
|
|
25
26
|
export * from './http/session-cookies.js';
|
|
26
27
|
export * from './i18n/format.js';
|
|
27
28
|
export * from './i18n/locales.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './base-entities/configuration-client/index.js';\nexport * from './base-entities/entifix-error/index.js';\nexport * from './command-palette/index.js';\nexport * from './entity-definition/decorators/accessor/index.js';\nexport * from './entity-definition/decorators/entity/index.js';\nexport * from './entity-definition/decorators/method/index.js';\nexport * from './entity-definition/decorators/use-case/index.js';\nexport * from './entity-definition/describe/index.js';\nexport * from './entity-definition/field-path.js';\nexport * from './entity-definition/helpers/index.js';\nexport * from './entity-definition/links/apply-entity-links/index.js';\nexport * from './entity-definition/links/entity-collection-link/index.js';\nexport * from './entity-definition/links/entity-link/index.js';\nexport * from './entity-definition/links/entity-link-source/index.js';\nexport * from './entity-definition/links/link-targets/index.js';\nexport * from './entity-definition/meta-entities/meta-accessor/index.js';\nexport * from './entity-definition/meta-entities/meta-entity/index.js';\nexport * from './entity-definition/meta-entities/meta-method/index.js';\nexport * from './entity-definition/meta-entities/meta-use-case/index.js';\nexport * from './entity-definition/metadata/index.js';\nexport * from './entity-definition/reconstruct/index.js';\nexport * from './entity-definition/serializer/index.js';\nexport * from './entity-definition/validate/index.js';\nexport * from './envelope/index.js';\nexport * from './http/session-cookies.js';\nexport * from './i18n/format.js';\nexport * from './i18n/locales.js';\nexport * from './i18n/localized-text.js';\nexport * from './i18n/negotiate.js';\nexport * from './query/in-memory-query.js';\nexport * from './rsql/index.js';\nexport * from './types/BulkOutcome.js';\nexport * from './types/Entity.js';\nexport * from './types/EntityAction.js';\nexport * from './types/EntityDraft.js';\nexport * from './types/EntityFiltering.js';\nexport * from './types/EntityLoadRequest.js';\nexport * from './types/EntityPage.js';\nexport * from './types/EntityRowDraft.js';\nexport * from './types/EntitySelection.js';\nexport * from './types/EntitySorting.js';\nexport * from './types/Json.js';\nexport * from './types/StandardSchema.js';\nexport * from './types/UseCase.js';\nexport * from './wizard/index.js';\n"],"names":[],"mappings":"AAAA,cAAc,gDAAgD;AAC9D,cAAc,yCAAyC;AACvD,cAAc,6BAA6B;AAC3C,cAAc,mDAAmD;AACjE,cAAc,iDAAiD;AAC/D,cAAc,iDAAiD;AAC/D,cAAc,mDAAmD;AACjE,cAAc,wCAAwC;AACtD,cAAc,oCAAoC;AAClD,cAAc,uCAAuC;AACrD,cAAc,wDAAwD;AACtE,cAAc,4DAA4D;AAC1E,cAAc,iDAAiD;AAC/D,cAAc,wDAAwD;AACtE,cAAc,kDAAkD;AAChE,cAAc,2DAA2D;AACzE,cAAc,yDAAyD;AACvE,cAAc,yDAAyD;AACvE,cAAc,2DAA2D;AACzE,cAAc,wCAAwC;AACtD,cAAc,2CAA2C;AACzD,cAAc,0CAA0C;AACxD,cAAc,wCAAwC;AACtD,cAAc,sBAAsB;AACpC,cAAc,4BAA4B;AAC1C,cAAc,mBAAmB;AACjC,cAAc,oBAAoB;AAClC,cAAc,2BAA2B;AACzC,cAAc,sBAAsB;AACpC,cAAc,6BAA6B;AAC3C,cAAc,kBAAkB;AAChC,cAAc,yBAAyB;AACvC,cAAc,oBAAoB;AAClC,cAAc,0BAA0B;AACxC,cAAc,yBAAyB;AACvC,cAAc,6BAA6B;AAC3C,cAAc,+BAA+B;AAC7C,cAAc,wBAAwB;AACtC,cAAc,4BAA4B;AAC1C,cAAc,6BAA6B;AAC3C,cAAc,2BAA2B;AACzC,cAAc,kBAAkB;AAChC,cAAc,4BAA4B;AAC1C,cAAc,qBAAqB;AACnC,cAAc,oBAAoB"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './base-entities/configuration-client/index.js';\nexport * from './base-entities/entifix-error/index.js';\nexport * from './command-palette/index.js';\nexport * from './entity-definition/decorators/accessor/index.js';\nexport * from './entity-definition/decorators/entity/index.js';\nexport * from './entity-definition/decorators/method/index.js';\nexport * from './entity-definition/decorators/use-case/index.js';\nexport * from './entity-definition/describe/index.js';\nexport * from './entity-definition/field-path.js';\nexport * from './entity-definition/helpers/index.js';\nexport * from './entity-definition/links/apply-entity-links/index.js';\nexport * from './entity-definition/links/entity-collection-link/index.js';\nexport * from './entity-definition/links/entity-link/index.js';\nexport * from './entity-definition/links/entity-link-source/index.js';\nexport * from './entity-definition/links/link-targets/index.js';\nexport * from './entity-definition/meta-entities/meta-accessor/index.js';\nexport * from './entity-definition/meta-entities/meta-entity/index.js';\nexport * from './entity-definition/meta-entities/meta-method/index.js';\nexport * from './entity-definition/meta-entities/meta-use-case/index.js';\nexport * from './entity-definition/metadata/index.js';\nexport * from './entity-definition/reconstruct/index.js';\nexport * from './entity-definition/serializer/index.js';\nexport * from './entity-definition/validate/index.js';\nexport * from './envelope/index.js';\nexport * from './hierarchy/index.js';\nexport * from './http/session-cookies.js';\nexport * from './i18n/format.js';\nexport * from './i18n/locales.js';\nexport * from './i18n/localized-text.js';\nexport * from './i18n/negotiate.js';\nexport * from './query/in-memory-query.js';\nexport * from './rsql/index.js';\nexport * from './types/BulkOutcome.js';\nexport * from './types/Entity.js';\nexport * from './types/EntityAction.js';\nexport * from './types/EntityDraft.js';\nexport * from './types/EntityFiltering.js';\nexport * from './types/EntityLoadRequest.js';\nexport * from './types/EntityPage.js';\nexport * from './types/EntityRowDraft.js';\nexport * from './types/EntitySelection.js';\nexport * from './types/EntitySorting.js';\nexport * from './types/Json.js';\nexport * from './types/StandardSchema.js';\nexport * from './types/UseCase.js';\nexport * from './wizard/index.js';\n"],"names":[],"mappings":"AAAA,cAAc,gDAAgD;AAC9D,cAAc,yCAAyC;AACvD,cAAc,6BAA6B;AAC3C,cAAc,mDAAmD;AACjE,cAAc,iDAAiD;AAC/D,cAAc,iDAAiD;AAC/D,cAAc,mDAAmD;AACjE,cAAc,wCAAwC;AACtD,cAAc,oCAAoC;AAClD,cAAc,uCAAuC;AACrD,cAAc,wDAAwD;AACtE,cAAc,4DAA4D;AAC1E,cAAc,iDAAiD;AAC/D,cAAc,wDAAwD;AACtE,cAAc,kDAAkD;AAChE,cAAc,2DAA2D;AACzE,cAAc,yDAAyD;AACvE,cAAc,yDAAyD;AACvE,cAAc,2DAA2D;AACzE,cAAc,wCAAwC;AACtD,cAAc,2CAA2C;AACzD,cAAc,0CAA0C;AACxD,cAAc,wCAAwC;AACtD,cAAc,sBAAsB;AACpC,cAAc,uBAAuB;AACrC,cAAc,4BAA4B;AAC1C,cAAc,mBAAmB;AACjC,cAAc,oBAAoB;AAClC,cAAc,2BAA2B;AACzC,cAAc,sBAAsB;AACpC,cAAc,6BAA6B;AAC3C,cAAc,kBAAkB;AAChC,cAAc,yBAAyB;AACvC,cAAc,oBAAoB;AAClC,cAAc,0BAA0B;AACxC,cAAc,yBAAyB;AACvC,cAAc,6BAA6B;AAC3C,cAAc,+BAA+B;AAC7C,cAAc,wBAAwB;AACtC,cAAc,4BAA4B;AAC1C,cAAc,6BAA6B;AAC3C,cAAc,2BAA2B;AACzC,cAAc,kBAAkB;AAChC,cAAc,4BAA4B;AAC1C,cAAc,qBAAqB;AACnC,cAAc,oBAAoB"}
|
package/package.json
CHANGED