@mintplayer/ng-spark 22.17.0 → 22.19.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/fesm2022/mintplayer-ng-spark-models.mjs +177 -4
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-pipes.mjs +37 -1
- package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-create.mjs +18 -4
- package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs +3 -3
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs +88 -6
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +125 -16
- package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +1 -1
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-services.mjs +122 -40
- package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mintplayer-ng-spark-models.d.ts +91 -1
- package/types/mintplayer-ng-spark-pipes.d.ts +24 -1
- package/types/mintplayer-ng-spark-po-create.d.ts +8 -1
- package/types/mintplayer-ng-spark-po-edit.d.ts +33 -1
- package/types/mintplayer-ng-spark-po-form.d.ts +69 -6
- package/types/mintplayer-ng-spark-services.d.ts +52 -5
|
@@ -46,6 +46,130 @@ var EReferenceDisplayType;
|
|
|
46
46
|
EReferenceDisplayType["Modal"] = "Modal";
|
|
47
47
|
})(EReferenceDisplayType || (EReferenceDisplayType = {}));
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Conversions between the wire format for a timestamp and what an `<input type="datetime-local">`
|
|
51
|
+
* or `<input type="date">` will accept.
|
|
52
|
+
*
|
|
53
|
+
* A `DateTimeOffset` in Spark means an **instant**. The wire carries a full ISO-8601 string with an
|
|
54
|
+
* offset (`2026-12-31T23:59:00-08:00`); an HTML date/time control speaks only bare local wall clock
|
|
55
|
+
* (`2026-12-31T23:59`). Neither end can consume the other's format, so both directions need
|
|
56
|
+
* converting, and the browser is deliberately the side that does it: its timezone rules are kept
|
|
57
|
+
* fresh by the operating system, while a container's are frozen at image build time.
|
|
58
|
+
*
|
|
59
|
+
* Assigning a wire value straight to a `datetime-local` input does not throw and does not warn -- the
|
|
60
|
+
* control simply renders **blank**, and saving the untouched form then writes that blank back.
|
|
61
|
+
*/
|
|
62
|
+
function pad(n) {
|
|
63
|
+
return String(n).padStart(2, '0');
|
|
64
|
+
}
|
|
65
|
+
/** Formats a UTC offset in minutes as `+HH:mm` / `-HH:mm`. */
|
|
66
|
+
function formatOffset(offsetMinutes) {
|
|
67
|
+
const sign = offsetMinutes >= 0 ? '+' : '-';
|
|
68
|
+
const abs = Math.abs(offsetMinutes);
|
|
69
|
+
return `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Parses a wire timestamp. Returns `null` for anything unusable, matching the contract of
|
|
73
|
+
* `parsedDate` / `queryCellValue` -- a bad value renders as empty rather than as `Invalid Date`.
|
|
74
|
+
*/
|
|
75
|
+
function parseWireDate(value) {
|
|
76
|
+
if (value === null || value === undefined || value === '')
|
|
77
|
+
return null;
|
|
78
|
+
if (value instanceof Date)
|
|
79
|
+
return isNaN(value.getTime()) ? null : value;
|
|
80
|
+
if (typeof value !== 'string' && typeof value !== 'number')
|
|
81
|
+
return null;
|
|
82
|
+
const parsed = new Date(value);
|
|
83
|
+
return isNaN(parsed.getTime()) ? null : parsed;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Wire value -> the `yyyy-MM-ddTHH:mm` a `datetime-local` input needs, in the **viewer's** zone.
|
|
87
|
+
*
|
|
88
|
+
* The conversion is date-sensitive, not a fixed shift: a value is rendered using the zone's offset
|
|
89
|
+
* *on that value's own date*, so a timestamp in July and one in January convert differently in a
|
|
90
|
+
* zone that observes DST.
|
|
91
|
+
*/
|
|
92
|
+
function toDateTimeLocalInput(value) {
|
|
93
|
+
const d = parseWireDate(value);
|
|
94
|
+
if (!d)
|
|
95
|
+
return '';
|
|
96
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
97
|
+
}
|
|
98
|
+
/** Wire value -> the `yyyy-MM-dd` a `date` input needs, in the viewer's zone. */
|
|
99
|
+
function toDateInput(value) {
|
|
100
|
+
const d = parseWireDate(value);
|
|
101
|
+
if (!d)
|
|
102
|
+
return '';
|
|
103
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* `yyyy-MM-ddTHH:mm` from the control -> a complete ISO-8601 string carrying the viewer's offset
|
|
107
|
+
* **for the entered date**.
|
|
108
|
+
*
|
|
109
|
+
* Building the `Date` from its parts rather than parsing the string is what makes this correct: it
|
|
110
|
+
* lets the browser apply its own timezone rules for that date, including DST. Using the *current*
|
|
111
|
+
* offset instead would be wrong for any value outside the present season -- entering a January date
|
|
112
|
+
* during summer would record an offset an hour off, and the stored instant would be an hour wrong.
|
|
113
|
+
*
|
|
114
|
+
* Across a DST discontinuity the browser resolves the wall clock itself, taking the offset in force
|
|
115
|
+
* before a fall-back and after a spring-forward. That is specified ECMAScript behaviour, and the
|
|
116
|
+
* server's conversion helper deliberately reproduces the same choice so the two cannot drift.
|
|
117
|
+
*/
|
|
118
|
+
function fromDateTimeLocalInput(local) {
|
|
119
|
+
if (typeof local !== 'string' || local === '')
|
|
120
|
+
return null;
|
|
121
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?/.exec(local);
|
|
122
|
+
if (!match)
|
|
123
|
+
return null;
|
|
124
|
+
const [, y, mo, d, h, mi, s] = match;
|
|
125
|
+
const date = new Date(+y, +mo - 1, +d, +h, +mi, s ? +s : 0);
|
|
126
|
+
if (isNaN(date.getTime()))
|
|
127
|
+
return null;
|
|
128
|
+
// getTimezoneOffset() is minutes to ADD to local to reach UTC, so it is the negation of the offset
|
|
129
|
+
// an ISO string carries.
|
|
130
|
+
return `${y}-${mo}-${d}T${h}:${mi}:${pad(s ? +s : 0)}${formatOffset(-date.getTimezoneOffset())}`;
|
|
131
|
+
}
|
|
132
|
+
/** `yyyy-MM-dd` from a `date` control -> an ISO string at local midnight with the viewer's offset. */
|
|
133
|
+
function fromDateInput(local) {
|
|
134
|
+
if (typeof local !== 'string' || local === '')
|
|
135
|
+
return null;
|
|
136
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(local);
|
|
137
|
+
if (!match)
|
|
138
|
+
return null;
|
|
139
|
+
return fromDateTimeLocalInput(`${local}T00:00`);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Compares two wire timestamps **by instant**, which is what a `DateTimeOffset` means in Spark.
|
|
143
|
+
*
|
|
144
|
+
* Round-tripping a value through a date control legitimately rewrites its text -- a document holding
|
|
145
|
+
* `2026-12-31T23:59:00-08:00` comes back as `2027-01-01T08:59:00+01:00` from a viewer in Brussels --
|
|
146
|
+
* so a string comparison would mark every untouched date as edited and send a needless write on every
|
|
147
|
+
* save. Both of those name the same moment.
|
|
148
|
+
*
|
|
149
|
+
* Note this is the *opposite* of the trap on the server side, where `DateTimeOffset.Equals` compares
|
|
150
|
+
* the instant and silently hides a destroyed offset. Here comparing by instant is the correct thing:
|
|
151
|
+
* we are asking "did the user change this value", not "is the offset intact".
|
|
152
|
+
*/
|
|
153
|
+
function wireDatesEqual(a, b) {
|
|
154
|
+
const da = parseWireDate(a);
|
|
155
|
+
const db = parseWireDate(b);
|
|
156
|
+
if (da === null || db === null)
|
|
157
|
+
return da === db;
|
|
158
|
+
return da.getTime() === db.getTime();
|
|
159
|
+
}
|
|
160
|
+
/** True for the attribute data types that are edited through a native date/time control. */
|
|
161
|
+
function isDateDataType(dataType) {
|
|
162
|
+
return dataType === 'date' || dataType === 'datetime';
|
|
163
|
+
}
|
|
164
|
+
/** Wire -> control, picking the right shape for the data type. */
|
|
165
|
+
function toDateInputValue(dataType, value) {
|
|
166
|
+
return dataType === 'date' ? toDateInput(value) : toDateTimeLocalInput(value);
|
|
167
|
+
}
|
|
168
|
+
/** Control -> wire, picking the right shape for the data type. */
|
|
169
|
+
function fromDateInputValue(dataType, value) {
|
|
170
|
+
return dataType === 'date' ? fromDateInput(value) : fromDateTimeLocalInput(value);
|
|
171
|
+
}
|
|
172
|
+
|
|
49
173
|
/**
|
|
50
174
|
* Flattens a nested `PersistentObject` into the plain `Record<string, any>` shape the
|
|
51
175
|
* form state uses throughout ng-spark. Primitive / reference attributes contribute their
|
|
@@ -76,14 +200,34 @@ function nestedPoToDict(po) {
|
|
|
76
200
|
if (typeof po.id === 'string' && po.id !== '') {
|
|
77
201
|
dict[AS_DETAIL_ROW_KEY] = po.id;
|
|
78
202
|
}
|
|
203
|
+
// The wire values of any date cells, kept so the save side can tell an untouched cell from an
|
|
204
|
+
// edited one. Without it, flattening to a wall clock and converting back rewrites the stored
|
|
205
|
+
// offset to the viewer's on every save, even for rows nobody touched.
|
|
206
|
+
const originalDates = originalDatesOf(po);
|
|
207
|
+
if (originalDates)
|
|
208
|
+
dict[AS_DETAIL_ORIGINAL_DATES_KEY] = originalDates;
|
|
79
209
|
return dict;
|
|
80
210
|
}
|
|
211
|
+
function originalDatesOf(po) {
|
|
212
|
+
let originals;
|
|
213
|
+
for (const attr of po.attributes ?? []) {
|
|
214
|
+
if (isDateDataType(attr.dataType))
|
|
215
|
+
(originals ??= {})[attr.name] = attr.value;
|
|
216
|
+
}
|
|
217
|
+
return originals;
|
|
218
|
+
}
|
|
81
219
|
function attributeValueForForm(attr) {
|
|
82
220
|
if (attr.dataType === 'AsDetail') {
|
|
83
221
|
if (attr.isArray)
|
|
84
222
|
return (attr.objects ?? []).map(po => nestedPoToDict(po));
|
|
85
223
|
return attr.object ? nestedPoToDict(attr.object) : null;
|
|
86
224
|
}
|
|
225
|
+
// An embedded row's date cells are edited through the same native controls as a root object's,
|
|
226
|
+
// so they need the same wire -> wall-clock conversion. This is the FORM path only;
|
|
227
|
+
// `nestedPoToDisplayRow` deliberately keeps the wire value, because the display path formats
|
|
228
|
+
// through `parsedDate` rather than feeding an input.
|
|
229
|
+
if (isDateDataType(attr.dataType))
|
|
230
|
+
return toDateInputValue(attr.dataType, attr.value);
|
|
87
231
|
return attr.value;
|
|
88
232
|
}
|
|
89
233
|
/**
|
|
@@ -160,6 +304,22 @@ function resolvedBreadcrumb(value, typeName) {
|
|
|
160
304
|
* reference-query options page. Prefixed to avoid colliding with a real attribute name.
|
|
161
305
|
*/
|
|
162
306
|
const AS_DETAIL_BREADCRUMBS_KEY = '__sparkBreadcrumbs';
|
|
307
|
+
/**
|
|
308
|
+
* Reserved key under which a flattened row keeps the **wire** values of its date cells, keyed by
|
|
309
|
+
* attribute name.
|
|
310
|
+
*
|
|
311
|
+
* A date cell is edited through a native control that speaks only a bare local wall clock, so
|
|
312
|
+
* flattening converts the stored instant into the viewer's zone and saving converts it back. That
|
|
313
|
+
* round trip preserves the instant but necessarily rewrites the **offset** to the viewer's. For a
|
|
314
|
+
* cell the user actually edited that is correct — the offset is not business data. For one nobody
|
|
315
|
+
* touched it is pure loss: merely opening a row and saving would relabel a Seattle timestamp as a
|
|
316
|
+
* Brussels one.
|
|
317
|
+
*
|
|
318
|
+
* Keeping the original lets {@link dictToNestedPo} send back exactly what it was given whenever the
|
|
319
|
+
* instant is unchanged. Safe to carry for the same reason as the other reserved keys: the rebuild
|
|
320
|
+
* walks the entity type's attributes, never the dict's keys.
|
|
321
|
+
*/
|
|
322
|
+
const AS_DETAIL_ORIGINAL_DATES_KEY = '__sparkOriginalDates';
|
|
163
323
|
/**
|
|
164
324
|
* Whether a flattened-row key is one this module reserved rather than a model attribute.
|
|
165
325
|
*
|
|
@@ -170,7 +330,10 @@ const AS_DETAIL_BREADCRUMBS_KEY = '__sparkBreadcrumbs';
|
|
|
170
330
|
* own breadcrumb as though they were fields.
|
|
171
331
|
*/
|
|
172
332
|
function isReservedAsDetailKey(key) {
|
|
173
|
-
return key === AS_DETAIL_ROW_KEY
|
|
333
|
+
return key === AS_DETAIL_ROW_KEY
|
|
334
|
+
|| key === AS_DETAIL_SELF_BREADCRUMB_KEY
|
|
335
|
+
|| key === AS_DETAIL_BREADCRUMBS_KEY
|
|
336
|
+
|| key === AS_DETAIL_ORIGINAL_DATES_KEY;
|
|
174
337
|
}
|
|
175
338
|
/**
|
|
176
339
|
* Like {@link nestedPoToDict}, but for the read-only detail display path. In addition to each
|
|
@@ -225,8 +388,9 @@ function displayValueForAttribute(attr) {
|
|
|
225
388
|
* handled recursively.
|
|
226
389
|
*/
|
|
227
390
|
function dictToNestedPo(dict, entityType, resolve) {
|
|
391
|
+
const originalDates = (dict?.[AS_DETAIL_ORIGINAL_DATES_KEY] ?? {});
|
|
228
392
|
const attributes = (entityType.attributes ?? [])
|
|
229
|
-
.map(attrDef => buildAttribute(attrDef, dict?.[attrDef.name], resolve));
|
|
393
|
+
.map(attrDef => buildAttribute(attrDef, dict?.[attrDef.name], resolve, originalDates[attrDef.name]));
|
|
230
394
|
return {
|
|
231
395
|
// The reserved key first: it is the only one that is always present for a stored row, because
|
|
232
396
|
// a value object's key is not a model attribute. `Id`/`id` remain as fallbacks for a root
|
|
@@ -237,7 +401,7 @@ function dictToNestedPo(dict, entityType, resolve) {
|
|
|
237
401
|
attributes,
|
|
238
402
|
};
|
|
239
403
|
}
|
|
240
|
-
function buildAttribute(attrDef, raw, resolve) {
|
|
404
|
+
function buildAttribute(attrDef, raw, resolve, originalDate) {
|
|
241
405
|
const attr = {
|
|
242
406
|
id: attrDef.id,
|
|
243
407
|
name: attrDef.name,
|
|
@@ -270,6 +434,15 @@ function buildAttribute(attrDef, raw, resolve) {
|
|
|
270
434
|
}
|
|
271
435
|
return attr;
|
|
272
436
|
}
|
|
437
|
+
if (isDateDataType(attrDef.dataType)) {
|
|
438
|
+
// The mirror of the conversion in `attributeValueForForm`: back from the control's bare wall
|
|
439
|
+
// clock to a complete ISO-8601 instant carrying the viewer's offset for that date. When the
|
|
440
|
+
// instant is unchanged, send back exactly what was loaded instead, so an untouched row keeps
|
|
441
|
+
// its stored offset. See AS_DETAIL_ORIGINAL_DATES_KEY.
|
|
442
|
+
const converted = fromDateInputValue(attrDef.dataType, raw);
|
|
443
|
+
attr.value = wireDatesEqual(converted, originalDate) ? originalDate : converted;
|
|
444
|
+
return attr;
|
|
445
|
+
}
|
|
273
446
|
attr.value = raw;
|
|
274
447
|
return attr;
|
|
275
448
|
}
|
|
@@ -649,5 +822,5 @@ function toNumber(value) {
|
|
|
649
822
|
* Generated bundle index. Do not edit.
|
|
650
823
|
*/
|
|
651
824
|
|
|
652
|
-
export { AS_DETAIL_BREADCRUMBS_KEY, AS_DETAIL_ROW_KEY, AS_DETAIL_SELF_BREADCRUMB_KEY, ELookupDisplayType, EReferenceDisplayType, ShowedOn, applyOverlay, currentLanguage, dictToNestedPo, evaluateRules, filterQueryActions, hasShowedOnFlag, isPersistentObject, isQueryRow, isReservedAsDetailKey, mergeRefreshValues, nestedPoToDict, nestedPoToDisplayRow, overlayFromResponse, parseSelectionRule, resolveTranslation, resolvedBreadcrumb, selectionModeFor, selfBreadcrumb, valueFor };
|
|
825
|
+
export { AS_DETAIL_BREADCRUMBS_KEY, AS_DETAIL_ORIGINAL_DATES_KEY, AS_DETAIL_ROW_KEY, AS_DETAIL_SELF_BREADCRUMB_KEY, ELookupDisplayType, EReferenceDisplayType, ShowedOn, applyOverlay, currentLanguage, dictToNestedPo, evaluateRules, filterQueryActions, fromDateInput, fromDateInputValue, fromDateTimeLocalInput, hasShowedOnFlag, isDateDataType, isPersistentObject, isQueryRow, isReservedAsDetailKey, mergeRefreshValues, nestedPoToDict, nestedPoToDisplayRow, overlayFromResponse, parseSelectionRule, parseWireDate, resolveTranslation, resolvedBreadcrumb, selectionModeFor, selfBreadcrumb, toDateInput, toDateInputValue, toDateTimeLocalInput, valueFor, wireDatesEqual };
|
|
653
826
|
//# sourceMappingURL=mintplayer-ng-spark-models.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mintplayer-ng-spark-models.mjs","sources":["../../models/src/translated-string.ts","../../models/src/showed-on.ts","../../models/src/entity-type.ts","../../models/src/as-detail-conversions.ts","../../models/src/query-result.ts","../../models/src/lookup-reference.ts","../../models/src/query-actions.ts","../../models/src/selection-rule.ts","../../models/src/selection-mode.ts","../../models/src/refresh-overlay.ts","../../models/src/rule-evaluation.ts","../../models/mintplayer-ng-spark-models.ts"],"sourcesContent":["import { signal, type WritableSignal } from '@angular/core';\n\nexport type TranslatedString = Record<string, string>;\n\n/** Global reactive language state — shared across library boundaries via globalThis */\nexport const currentLanguage: WritableSignal<string> =\n ((globalThis as any).__sparkCurrentLanguage ??= signal('en'));\n\nexport function resolveTranslation(ts: TranslatedString | undefined, lang?: string): string {\n if (!ts) return '';\n const language = lang ?? currentLanguage();\n return ts[language] ?? ts['en'] ?? Object.values(ts)[0] ?? '';\n}\n","/**\n * Flags enum controlling on which pages an attribute should be displayed.\n * Values can be combined: ShowedOn.Query | ShowedOn.PersistentObject\n */\nexport enum ShowedOn {\n Query = 1,\n PersistentObject = 2,\n}\n\n/**\n * Helper function to check if a ShowedOn value includes a specific flag.\n */\nexport function hasShowedOnFlag(value: ShowedOn | string | undefined, flag: ShowedOn): boolean {\n if (value === undefined) return true; // Default: show on all pages\n\n // Handle string values from JSON (e.g., \"Query, PersistentObject\")\n if (typeof value === 'string') {\n const parts = value.split(',').map(s => s.trim());\n const flagName = ShowedOn[flag];\n return parts.includes(flagName);\n }\n\n // Handle numeric flag values\n return (value & flag) === flag;\n}\n","import { ShowedOn } from './showed-on';\nimport { TranslatedString } from './translated-string';\nimport { ValidationRule } from './validation-rule';\n\n/**\n * Controls how a Reference attribute is picked in the PO-edit form.\n * Serialized as a string by the server (mirrors the .NET EReferenceDisplayType).\n */\nexport enum EReferenceDisplayType {\n /** Renders as a `<bs-select>` listing every referenced item. */\n Dropdown = 'Dropdown',\n /** Renders a readonly textbox + \"…\" button that opens a searchable modal grid picker. */\n Modal = 'Modal',\n}\n\nexport interface EntityAttributeDefinition {\n id: string;\n name: string;\n label?: TranslatedString;\n /** Help text rendered as an [i] tooltip beside the label. Absent when the model declares none. */\n description?: TranslatedString;\n dataType: string;\n isRequired: boolean;\n isVisible: boolean;\n isReadOnly: boolean;\n order: number;\n query?: string;\n /** For reference attributes, specifies the target entity type's CLR type name */\n referenceType?: string;\n /** For AsDetail attributes, specifies the nested entity type's CLR type name */\n asDetailType?: string;\n /** When true, the attribute represents an array/collection of AsDetail objects */\n isArray?: boolean;\n /** For array AsDetail attributes: \"modal\" (default) or \"inline\" */\n editMode?: 'inline' | 'modal';\n /**\n * For Reference attributes: 'Modal' renders the \"…\" + modal query-grid picker;\n * 'Dropdown'/absent (default) renders a `<bs-select>`. Hand-set in the model JSON.\n */\n referenceDisplayType?: EReferenceDisplayType;\n /** For array AsDetail attributes: when true, rows can be drag-reordered (order = array position) */\n isSortable?: boolean;\n /**\n * When true, changing this attribute's value posts the in-progress object to\n * `/spark/po/{objectTypeId}/refresh` and applies the reshaped result as an overlay.\n * Schema-only by design — it never travels on a PersistentObjectAttribute, so a client\n * cannot claim a trigger the model did not declare.\n */\n triggersRefresh?: boolean;\n /** For LookupReference attributes, specifies the lookup reference type name */\n lookupReferenceType?: string;\n /**\n * Controls on which pages the attribute should be displayed.\n * Query = shown in list views, PersistentObject = shown in detail/edit views.\n * Can be a numeric flag value or a string like \"Query, PersistentObject\".\n */\n showedOn?: ShowedOn | string;\n rules: ValidationRule[];\n /** References an AttributeGroup.id to assign this attribute to a group */\n group?: string;\n /** Number of grid columns this attribute spans within a tab's column layout */\n columnSpan?: number;\n /** Renderer component name for custom display in detail/list views */\n renderer?: string;\n /** Options passed to the renderer component */\n rendererOptions?: Record<string, any>;\n}\n\nexport interface AttributeTab {\n id: string;\n name: string;\n label?: TranslatedString;\n order: number;\n /** Number of columns for the grid layout within this tab */\n columnCount?: number;\n}\n\nexport interface AttributeGroup {\n id: string;\n name: string;\n label?: TranslatedString;\n /** References an AttributeTab.id to assign this group to a tab */\n tab?: string;\n order: number;\n}\n\nexport interface EntityType {\n id: string;\n name: string;\n description?: TranslatedString;\n /**\n * The backing CLR type, or absent for a JSON-only virtual type (#325).\n *\n * Optional on purpose: the server sends null for a virtual type, and this was declared\n * non-nullable, so any unguarded `.endsWith(...)` on it threw a TypeError naming an innocent\n * query. Types are data from the wire, not a promise about it.\n */\n clrType?: string;\n\n /**\n * Whether this caller may OPEN one of these, as opposed to list them.\n *\n * The catalogue is gated on Query, so its presence here means \"you may list this type\" and says\n * nothing about opening one. A reference must not render as a link when this is false: it used\n * to, and the click refused on arrival.\n */\n canRead?: boolean;\n alias?: string;\n /**\n * Breadcrumb template: literal text plus `{AttributeName}` placeholders. A scalar placeholder\n * renders its value; a reference placeholder renders the referenced entity's breadcrumb.\n * The server resolves this — clients only read the resulting strings. Example: \"{Street}, {City}\".\n */\n breadcrumb?: string;\n /**\n * When false, the breadcrumb needs the collection document (a placeholder field is not on the\n * projection). null/absent means renderable from the projection. Informational on the client.\n */\n breadcrumbProjectionSatisfiable?: boolean;\n tabs?: AttributeTab[];\n groups?: AttributeGroup[];\n attributes: EntityAttributeDefinition[];\n /** Query aliases or IDs to display as related query tables on the detail page. */\n queries?: string[];\n /**\n * The definitions of this type's AsDetail row types, sent alongside it so a detail table can draw\n * its columns.\n *\n * Needed because a row type usually has no rights of its own — a row is edited through its parent\n * — and the entity-type catalogue is gated on `Query`. Resolving a row type from the catalogue\n * therefore failed silently, and the table rendered with **no columns at all**.\n *\n * Per-caller and gated on the *parent's* right, which is the same gate that already ships each\n * row's attribute schema inside `attr.objects`. Pruned server-side: no `queryType`, `indexName`,\n * `queries` or `alias`.\n */\n detailTypes?: EntityType[];\n /**\n * Whether adding or removing a row of this type asks the server first.\n *\n * Absent or false — the default — keeps the purely client-side behaviour: New pushes a blank row,\n * Delete splices it out, and the parent's save is the first the server hears of either. On, the\n * grid calls `newObject` before showing a row and `deleteRow` before removing one, so\n * `OnNewAsync` can default it and the delete hook can refuse.\n *\n * ⚠️ Read it off the **row type**, never the parent: the type that owns the hooks owns the\n * decision. For a row type resolved out of `detailTypes` the flag survives the server-side\n * pruning, so both sources agree.\n *\n * ⚠️ It governs the round trip and nothing else. It is not a permission, and it is outside the\n * model hash, so nothing that gates a write may be conditional on it — the save path enforces the\n * row type's New/Edit/Delete rights on every embedded collection whether this is on or off.\n */\n serverSideRowLifecycle?: boolean;\n}\n","import { EntityAttributeDefinition } from './entity-type';\nimport { EntityType } from './entity-type';\nimport { PersistentObject } from './persistent-object';\nimport { PersistentObjectAttribute } from './persistent-object-attribute';\n\n/**\n * Resolves an `EntityType` by its CLR type name (e.g. `\"HR.Entities.Address\"`).\n * Callers typically close over a list they already hold. `getEntityTypes()` is NOT cached --\n * it issues a request per call -- so resolve against a list you fetched once rather than\n * calling it inside the resolver.\n */\nexport type EntityTypeResolver = (clrTypeName: string) => EntityType | undefined;\n\n/**\n * Flattens a nested `PersistentObject` into the plain `Record<string, any>` shape the\n * form state uses throughout ng-spark. Primitive / reference attributes contribute their\n * `value`; nested AsDetail attributes recurse — single becomes an inner dict, array\n * becomes an array of inner dicts. Returns `{}` for `null` / `undefined` input.\n *\n * This is the ONE place that reads the server's new AsDetail wire shape and collapses it\n * back to the flat dict the form components already handle.\n */\nexport function nestedPoToDict(po: PersistentObject | null | undefined): Record<string, any> {\n if (!po) return {};\n const dict: Record<string, any> = {};\n for (const attr of po.attributes ?? []) {\n dict[attr.name] = attributeValueForForm(attr);\n }\n // The object's own server-resolved breadcrumb, kept under a reserved key so the form can label\n // it without re-deriving a template it may be structurally unable to resolve. Safe to carry\n // through save: `dictToNestedPo` walks the entity type's attributes, never the dict's keys, so\n // a reserved key is never sent to the server. Attached only when it resolved, which keeps the\n // common case byte-for-byte identical to the plain flat dict.\n if (typeof po.breadcrumb === 'string' && po.breadcrumb !== '') {\n dict[AS_DETAIL_SELF_BREADCRUMB_KEY] = po.breadcrumb;\n }\n // The row's own key, same reserved-key mechanism and for a sharper reason: without it the key\n // never returns, and a save cannot tell an edited row from a deleted one plus a new one. See\n // AS_DETAIL_ROW_KEY.\n if (typeof po.id === 'string' && po.id !== '') {\n dict[AS_DETAIL_ROW_KEY] = po.id;\n }\n return dict;\n}\n\nfunction attributeValueForForm(attr: PersistentObjectAttribute): any {\n if (attr.dataType === 'AsDetail') {\n if (attr.isArray) return (attr.objects ?? []).map(po => nestedPoToDict(po));\n return attr.object ? nestedPoToDict(attr.object) : null;\n }\n return attr.value;\n}\n\n/**\n * Reserved key under which a flattened nested object keeps the breadcrumb the SERVER resolved for\n * that object itself (as opposed to {@link AS_DETAIL_BREADCRUMBS_KEY}, which keys the breadcrumbs\n * of its reference attributes by attribute name).\n *\n * This exists because a breadcrumb template can name a property the model does not carry. HR's\n * `Address` declares `[Breadcrumb, IgnoreProperty] string Crumb`, and `Address.json` renders it as\n * `\"{Crumb}\"` — the server resolves that by reflecting over the CLR property, which no client can\n * do, because `[IgnoreProperty]` is exactly the instruction to keep it out of the model. Flattening\n * used to discard the resolved string, leaving the form to substitute `{Crumb}` against a dict that\n * can never contain it.\n */\nexport const AS_DETAIL_SELF_BREADCRUMB_KEY = '__sparkBreadcrumb';\n\n/**\n * Reserved key under which a flattened nested object keeps its own row key — the value of whatever\n * property the server registered as that type's `[ValueKey]`.\n *\n * A value object's key is not a model attribute (nothing declares `Id` in its model file), so it\n * travels as the nested `PersistentObject`'s `id` and has to be carried across the flat-dict form\n * state by hand. Before this existed, flattening dropped it and `dictToNestedPo` rebuilt `id` from\n * `dict['Id']`, which is never present — so **every** embedded row reached the server with an empty\n * id, and the fresh instance the mapper builds kept the guid its own field initializer had just\n * minted.\n *\n * That is not cosmetic. Stored keys and incoming keys were then both plausible and never equal, so\n * a save could not match rows: an edit was indistinguishable from a delete plus a create. Every\n * per-row rule — preserving read-only fields, and the `New`/`Edit`/`Delete` rights of the row type —\n * rests on this key surviving the round trip.\n *\n * Safe to carry, for the same reason the breadcrumb is: `dictToNestedPo` walks the entity type's\n * attributes and never the dict's keys, so a reserved key is never sent back as an attribute.\n */\nexport const AS_DETAIL_ROW_KEY = '__sparkRowKey';\n\n/**\n * The breadcrumb the server resolved for a flattened object, or null when it resolved to nothing.\n *\n * `EntityMapper` never emits an empty breadcrumb: when the template renders blank it substitutes\n * the CLR type name, so an unset `Address` arrives as the literal string `\"Address\"`\n * (EntityMapper.cs:209-211). That is a placeholder, not data, and rendering it would be worse than\n * rendering nothing — it reads as a real value. `typeName` lets a caller filter it back out.\n */\nexport function selfBreadcrumb(row: Record<string, any> | null | undefined, typeName?: string): string | null {\n return resolvedBreadcrumb(row?.[AS_DETAIL_SELF_BREADCRUMB_KEY], typeName);\n}\n\n/**\n * The same filter as {@link selfBreadcrumb}, for a caller holding the server's breadcrumb string\n * directly rather than a flattened row dict — `attr.object.breadcrumb` on a detail page, or a query\n * cell's `breadcrumb`.\n *\n * Split out so there is exactly one answer to \"is this the type-name placeholder?\". The first\n * implementation lived inside `selfBreadcrumb` and so covered only the two pipes that flatten a row\n * first; the two that read the string straight off the wire kept printing `BuildFeedback` and\n * `GateSettings` at users (#384). A second copy of the comparison is how that happens again.\n */\nexport function resolvedBreadcrumb(value: unknown, typeName?: string): string | null {\n if (typeof value !== 'string' || value.trim() === '') return null;\n\n // Callers hold the type name in two shapes — `EntityType.name` is the short model name, while an\n // attribute's `asDetailType` is the full CLR name — and the server's placeholder is always the\n // short one. Compare on the last dotted segment so either shape filters it.\n if (typeName) {\n const shortName = typeName.slice(typeName.lastIndexOf('.') + 1);\n if (value === shortName) return null;\n }\n return value;\n}\n\n/**\n * Reserved key under which {@link nestedPoToDisplayRow} stashes the server-resolved breadcrumb of\n * each reference attribute (keyed by attribute name). Lets an AsDetail reference cell render the\n * label the server already resolved by id — page-independent — instead of guessing from a single\n * reference-query options page. Prefixed to avoid colliding with a real attribute name.\n */\nexport const AS_DETAIL_BREADCRUMBS_KEY = '__sparkBreadcrumbs';\n\n/**\n * Whether a flattened-row key is one this module reserved rather than a model attribute.\n *\n * `nestedPoToDict` and `nestedPoToDisplayRow` stash the row key and the resolved breadcrumbs\n * alongside the real values, which is safe on the way back — `dictToNestedPo` walks the entity\n * type's attributes and never the dict's keys. It is *not* safe for anything that enumerates the\n * dict to build display text: a caller joining `Object.values(dict)` prints the row's guid and its\n * own breadcrumb as though they were fields.\n */\nexport function isReservedAsDetailKey(key: string): boolean {\n return key === AS_DETAIL_ROW_KEY || key === AS_DETAIL_SELF_BREADCRUMB_KEY || key === AS_DETAIL_BREADCRUMBS_KEY;\n}\n\n/**\n * Like {@link nestedPoToDict}, but for the read-only detail display path. In addition to each\n * attribute's value it preserves the server-resolved per-reference `breadcrumb` under\n * {@link AS_DETAIL_BREADCRUMBS_KEY}, so an AsDetail reference cell can render the label by id\n * regardless of whether the referenced document fits on the reference query's first options page.\n * The form/edit path keeps using {@link nestedPoToDict}, which never carries breadcrumbs.\n */\nexport function nestedPoToDisplayRow(po: PersistentObject | null | undefined): Record<string, any> {\n if (!po) return {};\n const dict: Record<string, any> = {};\n let breadcrumbs: Record<string, string> | undefined;\n for (const attr of po.attributes ?? []) {\n dict[attr.name] = displayValueForAttribute(attr);\n if (attr.dataType === 'Reference' && !attr.isArray && typeof attr.breadcrumb === 'string' && attr.breadcrumb !== '') {\n (breadcrumbs ??= {})[attr.name] = attr.breadcrumb;\n }\n }\n // Only attach the side channel when something resolved — keeps reference-free rows (the common\n // case) byte-for-byte identical to the plain flat dict.\n if (breadcrumbs) dict[AS_DETAIL_BREADCRUMBS_KEY] = breadcrumbs;\n // Same self-breadcrumb the form path carries: a nested-AsDetail COLUMN in a detail table has an\n // inner dict as its value, which used to stringify to \"[object Object]\".\n if (typeof po.breadcrumb === 'string' && po.breadcrumb !== '') {\n dict[AS_DETAIL_SELF_BREADCRUMB_KEY] = po.breadcrumb;\n }\n // And the row key, for the same reason as the form path — a display row can be handed back for\n // saving, and a row that loses its key on that trip is indistinguishable from a new one.\n if (typeof po.id === 'string' && po.id !== '') {\n dict[AS_DETAIL_ROW_KEY] = po.id;\n }\n return dict;\n}\n\nfunction displayValueForAttribute(attr: PersistentObjectAttribute): any {\n if (attr.dataType === 'AsDetail') {\n if (attr.isArray) return (attr.objects ?? []).map(po => nestedPoToDisplayRow(po));\n return attr.object ? nestedPoToDisplayRow(attr.object) : null;\n }\n return attr.value;\n}\n\n/**\n * Builds a nested `PersistentObject` from a flat dict against the schema in\n * <paramref name=\"entityType\"/>. Used when the form is about to save — AsDetail attributes\n * are no longer sent as flat dicts in `attribute.value`; the server now requires\n * `attribute.object` / `attribute.objects` with fully scaffolded nested POs.\n *\n * `resolve` walks through AsDetail types registered elsewhere (usually the full\n * `getEntityTypes()` list, keyed by CLR type name). Nested AsDetail inside AsDetail is\n * handled recursively.\n */\nexport function dictToNestedPo(\n dict: Record<string, any> | null | undefined,\n entityType: EntityType,\n resolve: EntityTypeResolver,\n): PersistentObject {\n const attributes: PersistentObjectAttribute[] = (entityType.attributes ?? [])\n .map(attrDef => buildAttribute(attrDef, dict?.[attrDef.name], resolve));\n\n return {\n // The reserved key first: it is the only one that is always present for a stored row, because\n // a value object's key is not a model attribute. `Id`/`id` remain as fallbacks for a root\n // object, whose id genuinely is part of the dict.\n id: (dict?.[AS_DETAIL_ROW_KEY] as string) ?? (dict?.['Id'] as string) ?? (dict?.['id'] as string) ?? '',\n name: entityType.name,\n objectTypeId: entityType.id,\n attributes,\n };\n}\n\nfunction buildAttribute(\n attrDef: EntityAttributeDefinition,\n raw: any,\n resolve: EntityTypeResolver,\n): PersistentObjectAttribute {\n const attr: PersistentObjectAttribute = {\n id: attrDef.id,\n name: attrDef.name,\n label: attrDef.label,\n dataType: attrDef.dataType,\n isArray: attrDef.isArray,\n isRequired: attrDef.isRequired,\n isVisible: attrDef.isVisible,\n isReadOnly: attrDef.isReadOnly,\n order: attrDef.order,\n rules: attrDef.rules ?? [],\n isValueChanged: true,\n };\n\n if (attrDef.dataType === 'AsDetail') {\n // Server expects attr.value null for AsDetail; the nested PO carries the data.\n attr.value = null;\n attr.asDetailType = attrDef.asDetailType;\n\n const nestedType = attrDef.asDetailType ? resolve(attrDef.asDetailType) : undefined;\n if (!nestedType) {\n attr.object = null;\n attr.objects = attrDef.isArray ? [] : null;\n return attr;\n }\n\n if (attrDef.isArray) {\n const items: any[] = Array.isArray(raw) ? raw : [];\n attr.objects = items.map(item => dictToNestedPo((item as Record<string, any>) ?? {}, nestedType, resolve));\n } else {\n attr.object = raw ? dictToNestedPo(raw as Record<string, any>, nestedType, resolve) : null;\n }\n return attr;\n }\n\n attr.value = raw;\n return attr;\n}\n","import { TranslatedString } from './translated-string';\nimport { PersistentObject } from './persistent-object';\nimport { AS_DETAIL_BREADCRUMBS_KEY } from './as-detail-conversions';\n\n/**\n * What a query returns: the column metadata once, then one lightweight row per result.\n *\n * Rows used to be full `PersistentObject`s, so every row repeated the complete attribute\n * metadata the client already held from `/spark/types` and never read off the row. More\n * importantly it conflated two things that are not the same: a **row is a projection**, a\n * **persistent object is a document**. Nothing here is entitled to act on a row — every\n * mutating path re-loads by id and re-applies security server-side.\n */\nexport interface QueryResult {\n columns: QueryColumn[];\n items: QueryResultItem[];\n /** Rows matching the query after filtering, before paging. */\n totalItems: number;\n skip: number;\n take: number;\n typeHints?: Record<string, string>;\n /**\n * Custom actions withheld for THIS result, by name. Absent or empty means every action the\n * caller has the right to is offered.\n *\n * The mirror of `PersistentObject.disabledActions`, set server-side via\n * `CustomQueryArgs.DisableActions(...)`. The action catalogue is per type and is never told what\n * an execution returned, so this is where a per-result answer travels.\n *\n * An affordance, not a permission -- the action handler still refuses on its own terms.\n */\n disabledActions?: string[];\n}\n\n/**\n * The subset of column metadata a cell needs in order to render.\n *\n * Both {@link QueryColumn} (a query result's column) and `EntityAttributeDefinition` (a detail\n * page's attribute) satisfy this, which is what lets one cell component serve the query grid and\n * the AsDetail sub-table without either side knowing about the other's shape.\n */\nexport interface SparkCellColumn {\n name: string;\n label?: TranslatedString;\n /** Help text rendered as an [i] tooltip in the column header. Absent when the model declares none. */\n description?: TranslatedString;\n dataType: string;\n isArray?: boolean;\n renderer?: string;\n rendererOptions?: Record<string, any>;\n referenceType?: string;\n lookupReferenceType?: string;\n asDetailType?: string;\n}\n\n/** One column of a query result, sent once rather than repeated on every row. */\nexport interface QueryColumn extends SparkCellColumn {\n order: number;\n isSortable?: boolean;\n /**\n * Whether the grid draws this column. `false` means the row carries the value but no column is\n * rendered — for a renderer that needs a sibling value (a lock glyph beside a name) without\n * spending a column on it. `showedOn` decides what ships; this decides what is drawn.\n */\n isVisible?: boolean;\n /** The query backing a Reference column, by name — the client's option source. */\n query?: string;\n}\n\n/**\n * One row: an id, a display string, and a value per column.\n *\n * Deliberately too weak to act on — no attribute metadata, no `can` block, no etag, because none\n * of those can be trusted from a projection.\n */\nexport interface QueryResultItem {\n /** Never null and unique within a result; the server refuses anything else. */\n id: string;\n /** What to show when the row is named rather than tabulated (a reference picker's value). */\n breadcrumb?: string;\n values: QueryResultItemValue[];\n /** Presentation hints for the whole row. Keys arrive lower-cased. */\n typeHints?: Record<string, string>;\n}\n\n/** One cell. */\nexport interface QueryResultItemValue {\n /** The {@link QueryColumn.name} this value belongs to. */\n key: string;\n /** Typed JSON, not a string — the server converts on the way out. */\n value?: any;\n /** For a reference cell: the target document's id, so a link needs no second lookup. */\n objectId?: string;\n /** For a reference cell: the target's display string. */\n breadcrumb?: string;\n /** Resolved display labels per referenced id, for a multi-reference cell. */\n breadcrumbs?: Record<string, string | null>;\n typeHints?: Record<string, string>;\n}\n\n/**\n * Every shape a renderer's `item` can arrive in.\n *\n * Three, and they are genuinely different objects rather than variations of one: a query grid hands\n * a {@link QueryResultItem}, an AsDetail sub-table hands the flat record `nestedPoToDisplayRow`\n * built, and a detail or form host hands the `PersistentObject` itself. A renderer reused across a\n * grid and an AsDetail table therefore sees two of them, which is why {@link valueFor} reads all\n * three rather than making every app write the branch.\n */\nexport type SparkRow = QueryResultItem | PersistentObject | Record<string, unknown>;\n\n/**\n * The value a row carries for a column, whatever shape the row is in — or `undefined` when it\n * carries none.\n *\n * ```ts\n * const isPrivate = valueFor(this.item(), 'IsPrivate')?.value === true;\n * ```\n *\n * Always returns the **cell** rather than the bare value, because a reference cell is worth as much\n * for its `objectId` as for its text, and a helper that discarded it would have to be replaced the\n * first time a renderer wanted to link somewhere. `?.value` is the cost, once per call site.\n *\n * ⚠️ A value only reaches a grid row if its attribute is on the **query surface**. To read a sibling\n * the grid does not draw, mark it `\"showedOn\": \"Query\", \"isVisible\": false` — shipped, not drawn. An\n * attribute marked `\"showedOn\": \"PersistentObject\"` is absent from a row by design, and this returns\n * `undefined` for it; that is the first thing to check when a sibling read comes back empty.\n */\nexport function valueFor(item: SparkRow | null | undefined, columnName: string): QueryResultItemValue | undefined {\n if (!item) return undefined;\n\n if (isQueryRow(item)) {\n return item.values.find(v => v.key === columnName);\n }\n\n if (isPersistentObject(item)) {\n const attribute = item.attributes.find(a => a.name === columnName);\n if (!attribute) return undefined;\n return {\n key: attribute.name,\n value: attribute.value,\n // A single reference's value IS the target's id, which is what the row shape carries\n // separately. Deriving it here keeps a renderer from having to know that.\n objectId: attribute.dataType === 'Reference' && !attribute.isArray && attribute.value != null\n ? String(attribute.value)\n : undefined,\n breadcrumb: attribute.breadcrumb,\n breadcrumbs: attribute.breadcrumbs,\n };\n }\n\n const record = item as Record<string, unknown>;\n if (!(columnName in record)) return undefined;\n\n const value = record[columnName];\n // The flat row's side channel is populated only for single references, so its presence for this\n // key is what identifies one — the record itself carries no dataType to ask.\n const breadcrumb = (record[AS_DETAIL_BREADCRUMBS_KEY] as Record<string, string> | undefined)?.[columnName];\n\n return {\n key: columnName,\n value,\n objectId: breadcrumb !== undefined && value != null ? String(value) : undefined,\n breadcrumb,\n };\n}\n\n/**\n * Whether this row is a query result item.\n *\n * Discriminated on the *elements*, not just on `Array.isArray(values)`: a flat AsDetail record may\n * legitimately have a column named `values` holding an array, and mistaking one for the other would\n * silently read the wrong thing.\n */\nexport function isQueryRow(row: SparkRow | null | undefined): row is QueryResultItem {\n const values = (row as QueryResultItem | undefined)?.values;\n return Array.isArray(values) && (values.length === 0 || typeof values[0]?.key === 'string');\n}\n\n/** Whether this row is a full persistent object. Discriminated on elements, as above. */\nexport function isPersistentObject(row: SparkRow | null | undefined): row is PersistentObject {\n const attributes = (row as PersistentObject | undefined)?.attributes;\n return Array.isArray(attributes) && (attributes.length === 0 || typeof attributes[0]?.name === 'string');\n}\n","import { TranslatedString } from './translated-string';\n\nexport enum ELookupDisplayType {\n Dropdown = 0,\n Modal = 1\n}\n\nexport interface LookupReferenceListItem {\n name: string;\n isTransient: boolean;\n valueCount: number;\n displayType: ELookupDisplayType;\n}\n\nexport interface LookupReference {\n name: string;\n isTransient: boolean;\n displayType: ELookupDisplayType;\n values: LookupReferenceValue[];\n}\n\nexport interface LookupReferenceValue {\n key: string;\n values: TranslatedString;\n isActive: boolean;\n extra?: Record<string, unknown>;\n}\n","import { CustomActionDefinition } from './custom-action';\n\n/**\n * The custom actions a query should offer, from the entity type's full set.\n *\n * `showedOn` must include the query side. The accepted values are `\"detail\"`, `\"query\"`\n * and `\"both\"` — as the server model and the custom-actions guide have always\n * documented. Both grids previously tested for `\"list\"`, a value nothing emits, so an\n * action authored per the documentation rendered nowhere at all.\n *\n * ⚠️ This narrows what is DISPLAYED. It is NOT an authorization boundary: the grant\n * is, and it is enforced independently in `ExecuteCustomAction` regardless of which\n * query the caller clicked from — a caller can always POST directly.\n */\nexport function filterQueryActions(\n actions: CustomActionDefinition[],\n): CustomActionDefinition[] {\n return actions.filter(a => a.showedOn === 'query' || a.showedOn === 'both');\n}\n","/**\n * Parses a custom action's `selectionRule` — a cardinality expression over the number\n * of selected rows — into a predicate.\n *\n * A port of the server's `SelectionRuleParser`, and the two MUST agree: they are tested\n * against one shared fixture (`selection-rule.fixture.json`) for exactly this reason.\n * Vidyano, where this grammar comes from, has the same algorithm in C# and JavaScript and\n * the two have already drifted — one throws on a non-numeric operand where the other\n * silently permits everything.\n *\n * Grammar: `X` is the count placeholder, whitespace is insignificant, terms split on `X`\n * are AND-combined (`1<X<5` is a range), operators are `<= >= < > != =` matched in that\n * order so `>=` is never read as `>`, and a number-first term is mirrored (`0<X` is `>0`).\n *\n * Client-side this only drives whether a button is disabled. The server enforces the same\n * rule independently — and neither is an authorization boundary: the action's grant is.\n */\nexport function parseSelectionRule(rule?: string | null): (count: number) => boolean {\n if (!rule || !rule.trim()) return () => true;\n try {\n return compile(rule);\n } catch {\n // Unlike the server, which refuses to start on a malformed rule, the client cannot\n // usefully fail: the rule arrived over the wire from a server that already validated\n // it. Disabling the button is the safe direction — it never permits an action the\n // server would refuse.\n return () => false;\n }\n}\n\nconst OPERATORS = ['<=', '>=', '<', '>', '!=', '='] as const;\n\nfunction compile(rule: string): (count: number) => boolean {\n const normalized = rule.replace(/ /g, '').toUpperCase();\n const terms = normalized.split('X').filter(t => t.length > 0);\n if (terms.length === 0) throw new Error(`Selection rule '${rule}' has no condition.`);\n\n const numberFirst = !normalized.startsWith('X') && normalized.includes('X');\n const predicates = terms.map((term, i) => compileTerm(term, rule, numberFirst && i === 0));\n\n return count => predicates.every(p => p(count));\n}\n\nfunction compileTerm(term: string, rule: string, mirrored: boolean): (count: number) => boolean {\n const op = OPERATORS.find(o => (mirrored ? term.endsWith(o) : term.startsWith(o)));\n if (!op) throw new Error(`Selection rule '${rule}' has no recognised operator in '${term}'.`);\n\n const numberPart = mirrored ? term.slice(0, term.length - op.length) : term.slice(op.length);\n // Number(' ') is 0 and Number('1.5') is 1.5 — neither is a valid operand here.\n if (!/^-?\\d+$/.test(numberPart)) {\n throw new Error(`Selection rule '${rule}' has a non-numeric operand in '${term}'.`);\n }\n const value = Number(numberPart);\n const effective = mirrored ? mirror(op) : op;\n\n switch (effective) {\n case '<=': return count => count <= value;\n case '>=': return count => count >= value;\n case '<': return count => count < value;\n case '>': return count => count > value;\n case '!=': return count => count !== value;\n case '=': return count => count === value;\n default: throw new Error(`Selection rule '${rule}' has an unsupported operator '${effective}'.`);\n }\n}\n\nfunction mirror(op: string): string {\n switch (op) {\n case '<': return '>';\n case '>': return '<';\n case '<=': return '>=';\n case '>=': return '<=';\n default: return op;\n }\n}\n","import { CustomActionDefinition } from './custom-action';\nimport { parseSelectionRule } from './selection-rule';\n\nexport type SparkSelectionMode = 'none' | 'single' | 'multiple';\n\n/**\n * The selection mode a grid needs in order to satisfy the actions offered on it.\n *\n * Derived rather than configured, so a grid gains a checkbox column exactly when an\n * action needs one and is otherwise pixel-identical to a grid with no selection at all.\n * Vidyano's query grid does the same thing — it renders the checkbox column only if some\n * action is selection-gated.\n *\n * `'single'` when every gated action is satisfied by one row and refused by two; anything\n * else that cares about the count gets `'multiple'`.\n */\nexport function selectionModeFor(actions: CustomActionDefinition[]): SparkSelectionMode {\n // An action with no rule is not selection-gated: it acts on the query, not on rows.\n const gated = actions.filter(a => !!a.selectionRule?.trim());\n if (gated.length === 0) return 'none';\n\n const everyRuleWantsExactlyOne = gated.every(a => {\n const rule = parseSelectionRule(a.selectionRule);\n return rule(1) && !rule(2);\n });\n\n return everyRuleWantsExactlyOne ? 'single' : 'multiple';\n}\n","import { EntityAttributeDefinition } from './entity-type';\nimport { PersistentObject } from './persistent-object';\nimport { ValidationRule } from './validation-rule';\n\n/**\n * One selectable value, as replaced by a refresh hook. Mirrors the server's\n * `PersistentObjectAttributeOption`.\n */\nexport interface RefreshedOption {\n key: string;\n label?: Record<string, string>;\n}\n\n/**\n * What a refresh changed about one attribute's *presentation*.\n *\n * Kept separate from `EntityType` on purpose. The form's option loading hangs off a single effect\n * keyed on `entityType` identity, and `SparkService` caches nothing — so applying a refresh by\n * setting a new `EntityType` re-issues every reference query, every lookup fetch, a full\n * `getEntityTypes()` and a `getPermissions()` per array-AsDetail attribute, on every refresh.\n * Mutating the existing object instead is inert, because the rendering computed would not re-run.\n * An overlay is the only shape that is both reactive and free.\n */\nexport interface AttributeOverlay {\n isRequired?: boolean;\n isReadOnly?: boolean;\n isVisible?: boolean;\n rules?: ValidationRule[];\n query?: string;\n /** `undefined` means the hook did not touch the options; an empty array means there are none. */\n options?: RefreshedOption[];\n}\n\nexport type RefreshOverlay = Record<string, AttributeOverlay>;\n\n/** Applies an overlay to one attribute definition, returning a new object when anything changed. */\nexport function applyOverlay(\n attr: EntityAttributeDefinition,\n overlay: AttributeOverlay | undefined,\n): EntityAttributeDefinition {\n if (!overlay) return attr;\n\n return {\n ...attr,\n isRequired: overlay.isRequired ?? attr.isRequired,\n isReadOnly: overlay.isReadOnly ?? attr.isReadOnly,\n isVisible: overlay.isVisible ?? attr.isVisible,\n rules: overlay.rules ?? attr.rules,\n query: overlay.query ?? attr.query,\n };\n}\n\n/**\n * Reads a refresh response into an overlay.\n *\n * Everything here is presentation the server owns outright, so it is taken verbatim — there is no\n * merging to do on this half, only on values.\n */\nexport function overlayFromResponse(response: PersistentObject): RefreshOverlay {\n const overlay: RefreshOverlay = {};\n\n for (const attr of response.attributes ?? []) {\n overlay[attr.name] = {\n isRequired: attr.isRequired,\n isReadOnly: attr.isReadOnly,\n isVisible: attr.isVisible,\n rules: attr.rules ?? [],\n query: attr.query,\n options: (attr as { options?: RefreshedOption[] | null }).options ?? undefined,\n };\n }\n\n return overlay;\n}\n\n/**\n * Merges a refresh response's values into the live form.\n *\n * The rule, and the reason for it: a refresh is not instant, and the user keeps typing during it —\n * the form is deliberately never frozen. So for each attribute we ask whether the *server* changed\n * it, by comparing the response against the values that were **sent**, not against what is on\n * screen now.\n *\n * - server value equals what we sent → the hook did not touch it, so whatever is in the form now\n * wins, including anything typed while the request was in flight;\n * - server value differs → the hook deliberately changed it, and it wins over a concurrent edit.\n *\n * Comparing against the displayed value instead is the classic \"refresh eats my typing\" bug;\n * refusing to overwrite anything the user touched is the equally wrong opposite, where a dependent\n * field the hook computed never appears.\n *\n * @param sent values as they were POSTed, captured before the request left\n * @param current values as they are now, which may have moved on\n * @param response the reshaped object\n */\nexport function mergeRefreshValues(\n sent: Record<string, any>,\n current: Record<string, any>,\n response: PersistentObject,\n): Record<string, any> {\n const merged = { ...current };\n\n for (const attr of response.attributes ?? []) {\n const serverValue = attr.value ?? null;\n const sentValue = sent[attr.name] ?? null;\n\n if (!valuesEqual(serverValue, sentValue)) {\n merged[attr.name] = attr.value;\n }\n }\n\n return merged;\n}\n\n/**\n * Structural for arrays, `===` otherwise.\n *\n * Multi-reference attributes hold `string[]`, and a fresh array of the same ids is a different\n * object — so reference equality would report every one of them as \"changed by the server\" on\n * every refresh, and clobber in-flight edits to them.\n */\nfunction valuesEqual(a: any, b: any): boolean {\n if (a === b) return true;\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((item, i) => valuesEqual(item, b[i]));\n }\n // Values arrive off the wire as JSON scalars; anything else compares by JSON shape rather than\n // by identity, which is what a caller means by \"did this change\".\n if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') {\n return JSON.stringify(a) === JSON.stringify(b);\n }\n return false;\n}\n","import { ValidationRule } from './validation-rule';\nimport { TranslatedString } from './translated-string';\n\n/** One rule failure, in the shape the form already renders per field. */\nexport interface RuleFailure {\n attributeName: string;\n ruleType: string;\n message: string;\n}\n\nexport interface EvaluableAttribute {\n name: string;\n label?: TranslatedString;\n isRequired?: boolean;\n rules?: ValidationRule[];\n}\n\nconst EMAIL = /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/i;\nconst URL = /^https?:\\/\\/[^\\s]+$/i;\n\nfunction isEmpty(value: any): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === 'string') return value.trim() === '';\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * Evaluates an attribute's rules against a value, mirroring the server's `ValidationService`.\n *\n * ⚠️ **Parity with the server is the point, and disagreement is worse than silence.** A client that\n * rejects something the server would accept blocks legitimate work with no recourse; one that\n * accepts something the server rejects merely defers the error to the round-trip, which is where it\n * used to live anyway. So the rule set here is deliberately limited to the types the server\n * implements, and an unrecognised rule type is ignored rather than guessed at.\n *\n * This exists because a refresh hook that imposes a rule needs it to bite before Save — previously\n * `rules` was carried on the wire and never evaluated in the browser at all.\n */\nexport function evaluateRules(attr: EvaluableAttribute, value: any): RuleFailure[] {\n const failures: RuleFailure[] = [];\n const label = attr.label?.['en'] ?? attr.name;\n\n if (attr.isRequired && isEmpty(value)) {\n return [{ attributeName: attr.name, ruleType: 'required', message: `${label} is required` }];\n }\n\n // A rule other than \"required\" says nothing about an absent value — that is what required is for.\n if (isEmpty(value)) return failures;\n\n const text = value?.toString() ?? '';\n\n for (const rule of attr.rules ?? []) {\n const failure = evaluateRule(attr.name, label, rule, value, text);\n if (failure) failures.push(failure);\n }\n\n return failures;\n}\n\nfunction evaluateRule(\n name: string,\n label: string,\n rule: ValidationRule,\n value: any,\n text: string,\n): RuleFailure | null {\n const message = rule.message?.['en'];\n\n switch (rule.type?.toLowerCase()) {\n case 'maxlength': {\n const max = toInt(rule.value);\n if (max === null || text.length <= max) return null;\n return { attributeName: name, ruleType: 'maxLength', message: message ?? `${label} must be at most ${max} characters` };\n }\n case 'minlength': {\n const min = toInt(rule.value);\n if (min === null || text.length >= min) return null;\n return { attributeName: name, ruleType: 'minLength', message: message ?? `${label} must be at least ${min} characters` };\n }\n case 'range': {\n const numeric = toNumber(value);\n if (numeric === null) return null;\n if (rule.min !== undefined && rule.min !== null && numeric < rule.min) {\n return { attributeName: name, ruleType: 'range', message: message ?? `${label} must be at least ${rule.min}` };\n }\n if (rule.max !== undefined && rule.max !== null && numeric > rule.max) {\n return { attributeName: name, ruleType: 'range', message: message ?? `${label} must be at most ${rule.max}` };\n }\n return null;\n }\n case 'regex': {\n const pattern = rule.value?.toString();\n if (!pattern) return null;\n let re: RegExp;\n try {\n re = new RegExp(pattern);\n } catch {\n // An unparseable pattern is a server-side authoring bug. Staying silent leaves the server to\n // report it, which is strictly better than blocking the user over a rule nobody can evaluate.\n return null;\n }\n return re.test(text) ? null : { attributeName: name, ruleType: 'regex', message: message ?? `${label} is not in the expected format` };\n }\n case 'email':\n return EMAIL.test(text) ? null : { attributeName: name, ruleType: 'email', message: message ?? `${label} must be a valid email address` };\n case 'url':\n return URL.test(text) ? null : { attributeName: name, ruleType: 'url', message: message ?? `${label} must be a valid URL` };\n default:\n return null;\n }\n}\n\nfunction toInt(value: any): number | null {\n const n = Number(value);\n return Number.isFinite(n) ? Math.trunc(n) : null;\n}\n\nfunction toNumber(value: any): number | null {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mintplayer-ng-spark-models.mjs","sources":["../../models/src/translated-string.ts","../../models/src/showed-on.ts","../../models/src/entity-type.ts","../../models/src/datetime-local.ts","../../models/src/as-detail-conversions.ts","../../models/src/query-result.ts","../../models/src/lookup-reference.ts","../../models/src/query-actions.ts","../../models/src/selection-rule.ts","../../models/src/selection-mode.ts","../../models/src/refresh-overlay.ts","../../models/src/rule-evaluation.ts","../../models/mintplayer-ng-spark-models.ts"],"sourcesContent":["import { signal, type WritableSignal } from '@angular/core';\n\nexport type TranslatedString = Record<string, string>;\n\n/** Global reactive language state — shared across library boundaries via globalThis */\nexport const currentLanguage: WritableSignal<string> =\n ((globalThis as any).__sparkCurrentLanguage ??= signal('en'));\n\nexport function resolveTranslation(ts: TranslatedString | undefined, lang?: string): string {\n if (!ts) return '';\n const language = lang ?? currentLanguage();\n return ts[language] ?? ts['en'] ?? Object.values(ts)[0] ?? '';\n}\n","/**\n * Flags enum controlling on which pages an attribute should be displayed.\n * Values can be combined: ShowedOn.Query | ShowedOn.PersistentObject\n */\nexport enum ShowedOn {\n Query = 1,\n PersistentObject = 2,\n}\n\n/**\n * Helper function to check if a ShowedOn value includes a specific flag.\n */\nexport function hasShowedOnFlag(value: ShowedOn | string | undefined, flag: ShowedOn): boolean {\n if (value === undefined) return true; // Default: show on all pages\n\n // Handle string values from JSON (e.g., \"Query, PersistentObject\")\n if (typeof value === 'string') {\n const parts = value.split(',').map(s => s.trim());\n const flagName = ShowedOn[flag];\n return parts.includes(flagName);\n }\n\n // Handle numeric flag values\n return (value & flag) === flag;\n}\n","import { ShowedOn } from './showed-on';\nimport { TranslatedString } from './translated-string';\nimport { ValidationRule } from './validation-rule';\n\n/**\n * Controls how a Reference attribute is picked in the PO-edit form.\n * Serialized as a string by the server (mirrors the .NET EReferenceDisplayType).\n */\nexport enum EReferenceDisplayType {\n /** Renders as a `<bs-select>` listing every referenced item. */\n Dropdown = 'Dropdown',\n /** Renders a readonly textbox + \"…\" button that opens a searchable modal grid picker. */\n Modal = 'Modal',\n}\n\nexport interface EntityAttributeDefinition {\n id: string;\n name: string;\n label?: TranslatedString;\n /** Help text rendered as an [i] tooltip beside the label. Absent when the model declares none. */\n description?: TranslatedString;\n dataType: string;\n isRequired: boolean;\n isVisible: boolean;\n isReadOnly: boolean;\n order: number;\n query?: string;\n /** For reference attributes, specifies the target entity type's CLR type name */\n referenceType?: string;\n /** For AsDetail attributes, specifies the nested entity type's CLR type name */\n asDetailType?: string;\n /** When true, the attribute represents an array/collection of AsDetail objects */\n isArray?: boolean;\n /** For array AsDetail attributes: \"modal\" (default) or \"inline\" */\n editMode?: 'inline' | 'modal';\n /**\n * For Reference attributes: 'Modal' renders the \"…\" + modal query-grid picker;\n * 'Dropdown'/absent (default) renders a `<bs-select>`. Hand-set in the model JSON.\n */\n referenceDisplayType?: EReferenceDisplayType;\n /** For array AsDetail attributes: when true, rows can be drag-reordered (order = array position) */\n isSortable?: boolean;\n /**\n * When true, changing this attribute's value posts the in-progress object to\n * `/spark/po/{objectTypeId}/refresh` and applies the reshaped result as an overlay.\n * Schema-only by design — it never travels on a PersistentObjectAttribute, so a client\n * cannot claim a trigger the model did not declare.\n */\n triggersRefresh?: boolean;\n /** For LookupReference attributes, specifies the lookup reference type name */\n lookupReferenceType?: string;\n /**\n * Controls on which pages the attribute should be displayed.\n * Query = shown in list views, PersistentObject = shown in detail/edit views.\n * Can be a numeric flag value or a string like \"Query, PersistentObject\".\n */\n showedOn?: ShowedOn | string;\n rules: ValidationRule[];\n /** References an AttributeGroup.id to assign this attribute to a group */\n group?: string;\n /** Number of grid columns this attribute spans within a tab's column layout */\n columnSpan?: number;\n /** Renderer component name for custom display in detail/list views */\n renderer?: string;\n /** Options passed to the renderer component */\n rendererOptions?: Record<string, any>;\n}\n\nexport interface AttributeTab {\n id: string;\n name: string;\n label?: TranslatedString;\n order: number;\n /** Number of columns for the grid layout within this tab */\n columnCount?: number;\n}\n\nexport interface AttributeGroup {\n id: string;\n name: string;\n label?: TranslatedString;\n /** References an AttributeTab.id to assign this group to a tab */\n tab?: string;\n order: number;\n}\n\nexport interface EntityType {\n id: string;\n name: string;\n description?: TranslatedString;\n /**\n * The backing CLR type, or absent for a JSON-only virtual type (#325).\n *\n * Optional on purpose: the server sends null for a virtual type, and this was declared\n * non-nullable, so any unguarded `.endsWith(...)` on it threw a TypeError naming an innocent\n * query. Types are data from the wire, not a promise about it.\n */\n clrType?: string;\n\n /**\n * Whether this caller may OPEN one of these, as opposed to list them.\n *\n * The catalogue is gated on Query, so its presence here means \"you may list this type\" and says\n * nothing about opening one. A reference must not render as a link when this is false: it used\n * to, and the click refused on arrival.\n */\n canRead?: boolean;\n alias?: string;\n /**\n * Breadcrumb template: literal text plus `{AttributeName}` placeholders. A scalar placeholder\n * renders its value; a reference placeholder renders the referenced entity's breadcrumb.\n * The server resolves this — clients only read the resulting strings. Example: \"{Street}, {City}\".\n */\n breadcrumb?: string;\n /**\n * When false, the breadcrumb needs the collection document (a placeholder field is not on the\n * projection). null/absent means renderable from the projection. Informational on the client.\n */\n breadcrumbProjectionSatisfiable?: boolean;\n tabs?: AttributeTab[];\n groups?: AttributeGroup[];\n attributes: EntityAttributeDefinition[];\n /** Query aliases or IDs to display as related query tables on the detail page. */\n queries?: string[];\n /**\n * The definitions of this type's AsDetail row types, sent alongside it so a detail table can draw\n * its columns.\n *\n * Needed because a row type usually has no rights of its own — a row is edited through its parent\n * — and the entity-type catalogue is gated on `Query`. Resolving a row type from the catalogue\n * therefore failed silently, and the table rendered with **no columns at all**.\n *\n * Per-caller and gated on the *parent's* right, which is the same gate that already ships each\n * row's attribute schema inside `attr.objects`. Pruned server-side: no `queryType`, `indexName`,\n * `queries` or `alias`.\n */\n detailTypes?: EntityType[];\n /**\n * Whether adding or removing a row of this type asks the server first.\n *\n * Absent or false — the default — keeps the purely client-side behaviour: New pushes a blank row,\n * Delete splices it out, and the parent's save is the first the server hears of either. On, the\n * grid calls `newObject` before showing a row and `deleteRow` before removing one, so\n * `OnNewAsync` can default it and the delete hook can refuse.\n *\n * ⚠️ Read it off the **row type**, never the parent: the type that owns the hooks owns the\n * decision. For a row type resolved out of `detailTypes` the flag survives the server-side\n * pruning, so both sources agree.\n *\n * ⚠️ It governs the round trip and nothing else. It is not a permission, and it is outside the\n * model hash, so nothing that gates a write may be conditional on it — the save path enforces the\n * row type's New/Edit/Delete rights on every embedded collection whether this is on or off.\n */\n serverSideRowLifecycle?: boolean;\n}\n","/**\n * Conversions between the wire format for a timestamp and what an `<input type=\"datetime-local\">`\n * or `<input type=\"date\">` will accept.\n *\n * A `DateTimeOffset` in Spark means an **instant**. The wire carries a full ISO-8601 string with an\n * offset (`2026-12-31T23:59:00-08:00`); an HTML date/time control speaks only bare local wall clock\n * (`2026-12-31T23:59`). Neither end can consume the other's format, so both directions need\n * converting, and the browser is deliberately the side that does it: its timezone rules are kept\n * fresh by the operating system, while a container's are frozen at image build time.\n *\n * Assigning a wire value straight to a `datetime-local` input does not throw and does not warn -- the\n * control simply renders **blank**, and saving the untouched form then writes that blank back.\n */\n\nfunction pad(n: number): string {\n return String(n).padStart(2, '0');\n}\n\n/** Formats a UTC offset in minutes as `+HH:mm` / `-HH:mm`. */\nfunction formatOffset(offsetMinutes: number): string {\n const sign = offsetMinutes >= 0 ? '+' : '-';\n const abs = Math.abs(offsetMinutes);\n return `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;\n}\n\n/**\n * Parses a wire timestamp. Returns `null` for anything unusable, matching the contract of\n * `parsedDate` / `queryCellValue` -- a bad value renders as empty rather than as `Invalid Date`.\n */\nexport function parseWireDate(value: unknown): Date | null {\n if (value === null || value === undefined || value === '') return null;\n if (value instanceof Date) return isNaN(value.getTime()) ? null : value;\n if (typeof value !== 'string' && typeof value !== 'number') return null;\n\n const parsed = new Date(value);\n return isNaN(parsed.getTime()) ? null : parsed;\n}\n\n/**\n * Wire value -> the `yyyy-MM-ddTHH:mm` a `datetime-local` input needs, in the **viewer's** zone.\n *\n * The conversion is date-sensitive, not a fixed shift: a value is rendered using the zone's offset\n * *on that value's own date*, so a timestamp in July and one in January convert differently in a\n * zone that observes DST.\n */\nexport function toDateTimeLocalInput(value: unknown): string {\n const d = parseWireDate(value);\n if (!d) return '';\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;\n}\n\n/** Wire value -> the `yyyy-MM-dd` a `date` input needs, in the viewer's zone. */\nexport function toDateInput(value: unknown): string {\n const d = parseWireDate(value);\n if (!d) return '';\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n}\n\n/**\n * `yyyy-MM-ddTHH:mm` from the control -> a complete ISO-8601 string carrying the viewer's offset\n * **for the entered date**.\n *\n * Building the `Date` from its parts rather than parsing the string is what makes this correct: it\n * lets the browser apply its own timezone rules for that date, including DST. Using the *current*\n * offset instead would be wrong for any value outside the present season -- entering a January date\n * during summer would record an offset an hour off, and the stored instant would be an hour wrong.\n *\n * Across a DST discontinuity the browser resolves the wall clock itself, taking the offset in force\n * before a fall-back and after a spring-forward. That is specified ECMAScript behaviour, and the\n * server's conversion helper deliberately reproduces the same choice so the two cannot drift.\n */\nexport function fromDateTimeLocalInput(local: unknown): string | null {\n if (typeof local !== 'string' || local === '') return null;\n\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2}))?/.exec(local);\n if (!match) return null;\n\n const [, y, mo, d, h, mi, s] = match;\n const date = new Date(+y, +mo - 1, +d, +h, +mi, s ? +s : 0);\n if (isNaN(date.getTime())) return null;\n\n // getTimezoneOffset() is minutes to ADD to local to reach UTC, so it is the negation of the offset\n // an ISO string carries.\n return `${y}-${mo}-${d}T${h}:${mi}:${pad(s ? +s : 0)}${formatOffset(-date.getTimezoneOffset())}`;\n}\n\n/** `yyyy-MM-dd` from a `date` control -> an ISO string at local midnight with the viewer's offset. */\nexport function fromDateInput(local: unknown): string | null {\n if (typeof local !== 'string' || local === '') return null;\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(local);\n if (!match) return null;\n return fromDateTimeLocalInput(`${local}T00:00`);\n}\n\n/**\n * Compares two wire timestamps **by instant**, which is what a `DateTimeOffset` means in Spark.\n *\n * Round-tripping a value through a date control legitimately rewrites its text -- a document holding\n * `2026-12-31T23:59:00-08:00` comes back as `2027-01-01T08:59:00+01:00` from a viewer in Brussels --\n * so a string comparison would mark every untouched date as edited and send a needless write on every\n * save. Both of those name the same moment.\n *\n * Note this is the *opposite* of the trap on the server side, where `DateTimeOffset.Equals` compares\n * the instant and silently hides a destroyed offset. Here comparing by instant is the correct thing:\n * we are asking \"did the user change this value\", not \"is the offset intact\".\n */\nexport function wireDatesEqual(a: unknown, b: unknown): boolean {\n const da = parseWireDate(a);\n const db = parseWireDate(b);\n if (da === null || db === null) return da === db;\n return da.getTime() === db.getTime();\n}\n\n/** True for the attribute data types that are edited through a native date/time control. */\nexport function isDateDataType(dataType: string | null | undefined): dataType is 'date' | 'datetime' {\n return dataType === 'date' || dataType === 'datetime';\n}\n\n/** Wire -> control, picking the right shape for the data type. */\nexport function toDateInputValue(dataType: string | null | undefined, value: unknown): string {\n return dataType === 'date' ? toDateInput(value) : toDateTimeLocalInput(value);\n}\n\n/** Control -> wire, picking the right shape for the data type. */\nexport function fromDateInputValue(dataType: string | null | undefined, value: unknown): string | null {\n return dataType === 'date' ? fromDateInput(value) : fromDateTimeLocalInput(value);\n}\n","import { fromDateInputValue, isDateDataType, toDateInputValue, wireDatesEqual } from './datetime-local';\nimport { EntityAttributeDefinition } from './entity-type';\nimport { EntityType } from './entity-type';\nimport { PersistentObject } from './persistent-object';\nimport { PersistentObjectAttribute } from './persistent-object-attribute';\n\n/**\n * Resolves an `EntityType` by its CLR type name (e.g. `\"HR.Entities.Address\"`).\n * Callers typically close over a list they already hold. `getEntityTypes()` is NOT cached --\n * it issues a request per call -- so resolve against a list you fetched once rather than\n * calling it inside the resolver.\n */\nexport type EntityTypeResolver = (clrTypeName: string) => EntityType | undefined;\n\n/**\n * Flattens a nested `PersistentObject` into the plain `Record<string, any>` shape the\n * form state uses throughout ng-spark. Primitive / reference attributes contribute their\n * `value`; nested AsDetail attributes recurse — single becomes an inner dict, array\n * becomes an array of inner dicts. Returns `{}` for `null` / `undefined` input.\n *\n * This is the ONE place that reads the server's new AsDetail wire shape and collapses it\n * back to the flat dict the form components already handle.\n */\nexport function nestedPoToDict(po: PersistentObject | null | undefined): Record<string, any> {\n if (!po) return {};\n const dict: Record<string, any> = {};\n for (const attr of po.attributes ?? []) {\n dict[attr.name] = attributeValueForForm(attr);\n }\n // The object's own server-resolved breadcrumb, kept under a reserved key so the form can label\n // it without re-deriving a template it may be structurally unable to resolve. Safe to carry\n // through save: `dictToNestedPo` walks the entity type's attributes, never the dict's keys, so\n // a reserved key is never sent to the server. Attached only when it resolved, which keeps the\n // common case byte-for-byte identical to the plain flat dict.\n if (typeof po.breadcrumb === 'string' && po.breadcrumb !== '') {\n dict[AS_DETAIL_SELF_BREADCRUMB_KEY] = po.breadcrumb;\n }\n // The row's own key, same reserved-key mechanism and for a sharper reason: without it the key\n // never returns, and a save cannot tell an edited row from a deleted one plus a new one. See\n // AS_DETAIL_ROW_KEY.\n if (typeof po.id === 'string' && po.id !== '') {\n dict[AS_DETAIL_ROW_KEY] = po.id;\n }\n // The wire values of any date cells, kept so the save side can tell an untouched cell from an\n // edited one. Without it, flattening to a wall clock and converting back rewrites the stored\n // offset to the viewer's on every save, even for rows nobody touched.\n const originalDates = originalDatesOf(po);\n if (originalDates) dict[AS_DETAIL_ORIGINAL_DATES_KEY] = originalDates;\n return dict;\n}\n\nfunction originalDatesOf(po: PersistentObject): Record<string, any> | undefined {\n let originals: Record<string, any> | undefined;\n for (const attr of po.attributes ?? []) {\n if (isDateDataType(attr.dataType)) (originals ??= {})[attr.name] = attr.value;\n }\n return originals;\n}\n\nfunction attributeValueForForm(attr: PersistentObjectAttribute): any {\n if (attr.dataType === 'AsDetail') {\n if (attr.isArray) return (attr.objects ?? []).map(po => nestedPoToDict(po));\n return attr.object ? nestedPoToDict(attr.object) : null;\n }\n // An embedded row's date cells are edited through the same native controls as a root object's,\n // so they need the same wire -> wall-clock conversion. This is the FORM path only;\n // `nestedPoToDisplayRow` deliberately keeps the wire value, because the display path formats\n // through `parsedDate` rather than feeding an input.\n if (isDateDataType(attr.dataType)) return toDateInputValue(attr.dataType, attr.value);\n return attr.value;\n}\n\n/**\n * Reserved key under which a flattened nested object keeps the breadcrumb the SERVER resolved for\n * that object itself (as opposed to {@link AS_DETAIL_BREADCRUMBS_KEY}, which keys the breadcrumbs\n * of its reference attributes by attribute name).\n *\n * This exists because a breadcrumb template can name a property the model does not carry. HR's\n * `Address` declares `[Breadcrumb, IgnoreProperty] string Crumb`, and `Address.json` renders it as\n * `\"{Crumb}\"` — the server resolves that by reflecting over the CLR property, which no client can\n * do, because `[IgnoreProperty]` is exactly the instruction to keep it out of the model. Flattening\n * used to discard the resolved string, leaving the form to substitute `{Crumb}` against a dict that\n * can never contain it.\n */\nexport const AS_DETAIL_SELF_BREADCRUMB_KEY = '__sparkBreadcrumb';\n\n/**\n * Reserved key under which a flattened nested object keeps its own row key — the value of whatever\n * property the server registered as that type's `[ValueKey]`.\n *\n * A value object's key is not a model attribute (nothing declares `Id` in its model file), so it\n * travels as the nested `PersistentObject`'s `id` and has to be carried across the flat-dict form\n * state by hand. Before this existed, flattening dropped it and `dictToNestedPo` rebuilt `id` from\n * `dict['Id']`, which is never present — so **every** embedded row reached the server with an empty\n * id, and the fresh instance the mapper builds kept the guid its own field initializer had just\n * minted.\n *\n * That is not cosmetic. Stored keys and incoming keys were then both plausible and never equal, so\n * a save could not match rows: an edit was indistinguishable from a delete plus a create. Every\n * per-row rule — preserving read-only fields, and the `New`/`Edit`/`Delete` rights of the row type —\n * rests on this key surviving the round trip.\n *\n * Safe to carry, for the same reason the breadcrumb is: `dictToNestedPo` walks the entity type's\n * attributes and never the dict's keys, so a reserved key is never sent back as an attribute.\n */\nexport const AS_DETAIL_ROW_KEY = '__sparkRowKey';\n\n/**\n * The breadcrumb the server resolved for a flattened object, or null when it resolved to nothing.\n *\n * `EntityMapper` never emits an empty breadcrumb: when the template renders blank it substitutes\n * the CLR type name, so an unset `Address` arrives as the literal string `\"Address\"`\n * (EntityMapper.cs:209-211). That is a placeholder, not data, and rendering it would be worse than\n * rendering nothing — it reads as a real value. `typeName` lets a caller filter it back out.\n */\nexport function selfBreadcrumb(row: Record<string, any> | null | undefined, typeName?: string): string | null {\n return resolvedBreadcrumb(row?.[AS_DETAIL_SELF_BREADCRUMB_KEY], typeName);\n}\n\n/**\n * The same filter as {@link selfBreadcrumb}, for a caller holding the server's breadcrumb string\n * directly rather than a flattened row dict — `attr.object.breadcrumb` on a detail page, or a query\n * cell's `breadcrumb`.\n *\n * Split out so there is exactly one answer to \"is this the type-name placeholder?\". The first\n * implementation lived inside `selfBreadcrumb` and so covered only the two pipes that flatten a row\n * first; the two that read the string straight off the wire kept printing `BuildFeedback` and\n * `GateSettings` at users (#384). A second copy of the comparison is how that happens again.\n */\nexport function resolvedBreadcrumb(value: unknown, typeName?: string): string | null {\n if (typeof value !== 'string' || value.trim() === '') return null;\n\n // Callers hold the type name in two shapes — `EntityType.name` is the short model name, while an\n // attribute's `asDetailType` is the full CLR name — and the server's placeholder is always the\n // short one. Compare on the last dotted segment so either shape filters it.\n if (typeName) {\n const shortName = typeName.slice(typeName.lastIndexOf('.') + 1);\n if (value === shortName) return null;\n }\n return value;\n}\n\n/**\n * Reserved key under which {@link nestedPoToDisplayRow} stashes the server-resolved breadcrumb of\n * each reference attribute (keyed by attribute name). Lets an AsDetail reference cell render the\n * label the server already resolved by id — page-independent — instead of guessing from a single\n * reference-query options page. Prefixed to avoid colliding with a real attribute name.\n */\nexport const AS_DETAIL_BREADCRUMBS_KEY = '__sparkBreadcrumbs';\n\n/**\n * Reserved key under which a flattened row keeps the **wire** values of its date cells, keyed by\n * attribute name.\n *\n * A date cell is edited through a native control that speaks only a bare local wall clock, so\n * flattening converts the stored instant into the viewer's zone and saving converts it back. That\n * round trip preserves the instant but necessarily rewrites the **offset** to the viewer's. For a\n * cell the user actually edited that is correct — the offset is not business data. For one nobody\n * touched it is pure loss: merely opening a row and saving would relabel a Seattle timestamp as a\n * Brussels one.\n *\n * Keeping the original lets {@link dictToNestedPo} send back exactly what it was given whenever the\n * instant is unchanged. Safe to carry for the same reason as the other reserved keys: the rebuild\n * walks the entity type's attributes, never the dict's keys.\n */\nexport const AS_DETAIL_ORIGINAL_DATES_KEY = '__sparkOriginalDates';\n\n/**\n * Whether a flattened-row key is one this module reserved rather than a model attribute.\n *\n * `nestedPoToDict` and `nestedPoToDisplayRow` stash the row key and the resolved breadcrumbs\n * alongside the real values, which is safe on the way back — `dictToNestedPo` walks the entity\n * type's attributes and never the dict's keys. It is *not* safe for anything that enumerates the\n * dict to build display text: a caller joining `Object.values(dict)` prints the row's guid and its\n * own breadcrumb as though they were fields.\n */\nexport function isReservedAsDetailKey(key: string): boolean {\n return key === AS_DETAIL_ROW_KEY\n || key === AS_DETAIL_SELF_BREADCRUMB_KEY\n || key === AS_DETAIL_BREADCRUMBS_KEY\n || key === AS_DETAIL_ORIGINAL_DATES_KEY;\n}\n\n/**\n * Like {@link nestedPoToDict}, but for the read-only detail display path. In addition to each\n * attribute's value it preserves the server-resolved per-reference `breadcrumb` under\n * {@link AS_DETAIL_BREADCRUMBS_KEY}, so an AsDetail reference cell can render the label by id\n * regardless of whether the referenced document fits on the reference query's first options page.\n * The form/edit path keeps using {@link nestedPoToDict}, which never carries breadcrumbs.\n */\nexport function nestedPoToDisplayRow(po: PersistentObject | null | undefined): Record<string, any> {\n if (!po) return {};\n const dict: Record<string, any> = {};\n let breadcrumbs: Record<string, string> | undefined;\n for (const attr of po.attributes ?? []) {\n dict[attr.name] = displayValueForAttribute(attr);\n if (attr.dataType === 'Reference' && !attr.isArray && typeof attr.breadcrumb === 'string' && attr.breadcrumb !== '') {\n (breadcrumbs ??= {})[attr.name] = attr.breadcrumb;\n }\n }\n // Only attach the side channel when something resolved — keeps reference-free rows (the common\n // case) byte-for-byte identical to the plain flat dict.\n if (breadcrumbs) dict[AS_DETAIL_BREADCRUMBS_KEY] = breadcrumbs;\n // Same self-breadcrumb the form path carries: a nested-AsDetail COLUMN in a detail table has an\n // inner dict as its value, which used to stringify to \"[object Object]\".\n if (typeof po.breadcrumb === 'string' && po.breadcrumb !== '') {\n dict[AS_DETAIL_SELF_BREADCRUMB_KEY] = po.breadcrumb;\n }\n // And the row key, for the same reason as the form path — a display row can be handed back for\n // saving, and a row that loses its key on that trip is indistinguishable from a new one.\n if (typeof po.id === 'string' && po.id !== '') {\n dict[AS_DETAIL_ROW_KEY] = po.id;\n }\n return dict;\n}\n\nfunction displayValueForAttribute(attr: PersistentObjectAttribute): any {\n if (attr.dataType === 'AsDetail') {\n if (attr.isArray) return (attr.objects ?? []).map(po => nestedPoToDisplayRow(po));\n return attr.object ? nestedPoToDisplayRow(attr.object) : null;\n }\n return attr.value;\n}\n\n/**\n * Builds a nested `PersistentObject` from a flat dict against the schema in\n * <paramref name=\"entityType\"/>. Used when the form is about to save — AsDetail attributes\n * are no longer sent as flat dicts in `attribute.value`; the server now requires\n * `attribute.object` / `attribute.objects` with fully scaffolded nested POs.\n *\n * `resolve` walks through AsDetail types registered elsewhere (usually the full\n * `getEntityTypes()` list, keyed by CLR type name). Nested AsDetail inside AsDetail is\n * handled recursively.\n */\nexport function dictToNestedPo(\n dict: Record<string, any> | null | undefined,\n entityType: EntityType,\n resolve: EntityTypeResolver,\n): PersistentObject {\n const originalDates = (dict?.[AS_DETAIL_ORIGINAL_DATES_KEY] ?? {}) as Record<string, any>;\n const attributes: PersistentObjectAttribute[] = (entityType.attributes ?? [])\n .map(attrDef => buildAttribute(attrDef, dict?.[attrDef.name], resolve, originalDates[attrDef.name]));\n\n return {\n // The reserved key first: it is the only one that is always present for a stored row, because\n // a value object's key is not a model attribute. `Id`/`id` remain as fallbacks for a root\n // object, whose id genuinely is part of the dict.\n id: (dict?.[AS_DETAIL_ROW_KEY] as string) ?? (dict?.['Id'] as string) ?? (dict?.['id'] as string) ?? '',\n name: entityType.name,\n objectTypeId: entityType.id,\n attributes,\n };\n}\n\nfunction buildAttribute(\n attrDef: EntityAttributeDefinition,\n raw: any,\n resolve: EntityTypeResolver,\n originalDate?: any,\n): PersistentObjectAttribute {\n const attr: PersistentObjectAttribute = {\n id: attrDef.id,\n name: attrDef.name,\n label: attrDef.label,\n dataType: attrDef.dataType,\n isArray: attrDef.isArray,\n isRequired: attrDef.isRequired,\n isVisible: attrDef.isVisible,\n isReadOnly: attrDef.isReadOnly,\n order: attrDef.order,\n rules: attrDef.rules ?? [],\n isValueChanged: true,\n };\n\n if (attrDef.dataType === 'AsDetail') {\n // Server expects attr.value null for AsDetail; the nested PO carries the data.\n attr.value = null;\n attr.asDetailType = attrDef.asDetailType;\n\n const nestedType = attrDef.asDetailType ? resolve(attrDef.asDetailType) : undefined;\n if (!nestedType) {\n attr.object = null;\n attr.objects = attrDef.isArray ? [] : null;\n return attr;\n }\n\n if (attrDef.isArray) {\n const items: any[] = Array.isArray(raw) ? raw : [];\n attr.objects = items.map(item => dictToNestedPo((item as Record<string, any>) ?? {}, nestedType, resolve));\n } else {\n attr.object = raw ? dictToNestedPo(raw as Record<string, any>, nestedType, resolve) : null;\n }\n return attr;\n }\n\n if (isDateDataType(attrDef.dataType)) {\n // The mirror of the conversion in `attributeValueForForm`: back from the control's bare wall\n // clock to a complete ISO-8601 instant carrying the viewer's offset for that date. When the\n // instant is unchanged, send back exactly what was loaded instead, so an untouched row keeps\n // its stored offset. See AS_DETAIL_ORIGINAL_DATES_KEY.\n const converted = fromDateInputValue(attrDef.dataType, raw);\n attr.value = wireDatesEqual(converted, originalDate) ? originalDate : converted;\n return attr;\n }\n\n attr.value = raw;\n return attr;\n}\n","import { TranslatedString } from './translated-string';\nimport { PersistentObject } from './persistent-object';\nimport { AS_DETAIL_BREADCRUMBS_KEY } from './as-detail-conversions';\n\n/**\n * What a query returns: the column metadata once, then one lightweight row per result.\n *\n * Rows used to be full `PersistentObject`s, so every row repeated the complete attribute\n * metadata the client already held from `/spark/types` and never read off the row. More\n * importantly it conflated two things that are not the same: a **row is a projection**, a\n * **persistent object is a document**. Nothing here is entitled to act on a row — every\n * mutating path re-loads by id and re-applies security server-side.\n */\nexport interface QueryResult {\n columns: QueryColumn[];\n items: QueryResultItem[];\n /** Rows matching the query after filtering, before paging. */\n totalItems: number;\n skip: number;\n take: number;\n typeHints?: Record<string, string>;\n /**\n * Custom actions withheld for THIS result, by name. Absent or empty means every action the\n * caller has the right to is offered.\n *\n * The mirror of `PersistentObject.disabledActions`, set server-side via\n * `CustomQueryArgs.DisableActions(...)`. The action catalogue is per type and is never told what\n * an execution returned, so this is where a per-result answer travels.\n *\n * An affordance, not a permission -- the action handler still refuses on its own terms.\n */\n disabledActions?: string[];\n}\n\n/**\n * The subset of column metadata a cell needs in order to render.\n *\n * Both {@link QueryColumn} (a query result's column) and `EntityAttributeDefinition` (a detail\n * page's attribute) satisfy this, which is what lets one cell component serve the query grid and\n * the AsDetail sub-table without either side knowing about the other's shape.\n */\nexport interface SparkCellColumn {\n name: string;\n label?: TranslatedString;\n /** Help text rendered as an [i] tooltip in the column header. Absent when the model declares none. */\n description?: TranslatedString;\n dataType: string;\n isArray?: boolean;\n renderer?: string;\n rendererOptions?: Record<string, any>;\n referenceType?: string;\n lookupReferenceType?: string;\n asDetailType?: string;\n}\n\n/** One column of a query result, sent once rather than repeated on every row. */\nexport interface QueryColumn extends SparkCellColumn {\n order: number;\n isSortable?: boolean;\n /**\n * Whether the grid draws this column. `false` means the row carries the value but no column is\n * rendered — for a renderer that needs a sibling value (a lock glyph beside a name) without\n * spending a column on it. `showedOn` decides what ships; this decides what is drawn.\n */\n isVisible?: boolean;\n /** The query backing a Reference column, by name — the client's option source. */\n query?: string;\n}\n\n/**\n * One row: an id, a display string, and a value per column.\n *\n * Deliberately too weak to act on — no attribute metadata, no `can` block, no etag, because none\n * of those can be trusted from a projection.\n */\nexport interface QueryResultItem {\n /** Never null and unique within a result; the server refuses anything else. */\n id: string;\n /** What to show when the row is named rather than tabulated (a reference picker's value). */\n breadcrumb?: string;\n values: QueryResultItemValue[];\n /** Presentation hints for the whole row. Keys arrive lower-cased. */\n typeHints?: Record<string, string>;\n}\n\n/** One cell. */\nexport interface QueryResultItemValue {\n /** The {@link QueryColumn.name} this value belongs to. */\n key: string;\n /** Typed JSON, not a string — the server converts on the way out. */\n value?: any;\n /** For a reference cell: the target document's id, so a link needs no second lookup. */\n objectId?: string;\n /** For a reference cell: the target's display string. */\n breadcrumb?: string;\n /** Resolved display labels per referenced id, for a multi-reference cell. */\n breadcrumbs?: Record<string, string | null>;\n typeHints?: Record<string, string>;\n}\n\n/**\n * Every shape a renderer's `item` can arrive in.\n *\n * Three, and they are genuinely different objects rather than variations of one: a query grid hands\n * a {@link QueryResultItem}, an AsDetail sub-table hands the flat record `nestedPoToDisplayRow`\n * built, and a detail or form host hands the `PersistentObject` itself. A renderer reused across a\n * grid and an AsDetail table therefore sees two of them, which is why {@link valueFor} reads all\n * three rather than making every app write the branch.\n */\nexport type SparkRow = QueryResultItem | PersistentObject | Record<string, unknown>;\n\n/**\n * The value a row carries for a column, whatever shape the row is in — or `undefined` when it\n * carries none.\n *\n * ```ts\n * const isPrivate = valueFor(this.item(), 'IsPrivate')?.value === true;\n * ```\n *\n * Always returns the **cell** rather than the bare value, because a reference cell is worth as much\n * for its `objectId` as for its text, and a helper that discarded it would have to be replaced the\n * first time a renderer wanted to link somewhere. `?.value` is the cost, once per call site.\n *\n * ⚠️ A value only reaches a grid row if its attribute is on the **query surface**. To read a sibling\n * the grid does not draw, mark it `\"showedOn\": \"Query\", \"isVisible\": false` — shipped, not drawn. An\n * attribute marked `\"showedOn\": \"PersistentObject\"` is absent from a row by design, and this returns\n * `undefined` for it; that is the first thing to check when a sibling read comes back empty.\n */\nexport function valueFor(item: SparkRow | null | undefined, columnName: string): QueryResultItemValue | undefined {\n if (!item) return undefined;\n\n if (isQueryRow(item)) {\n return item.values.find(v => v.key === columnName);\n }\n\n if (isPersistentObject(item)) {\n const attribute = item.attributes.find(a => a.name === columnName);\n if (!attribute) return undefined;\n return {\n key: attribute.name,\n value: attribute.value,\n // A single reference's value IS the target's id, which is what the row shape carries\n // separately. Deriving it here keeps a renderer from having to know that.\n objectId: attribute.dataType === 'Reference' && !attribute.isArray && attribute.value != null\n ? String(attribute.value)\n : undefined,\n breadcrumb: attribute.breadcrumb,\n breadcrumbs: attribute.breadcrumbs,\n };\n }\n\n const record = item as Record<string, unknown>;\n if (!(columnName in record)) return undefined;\n\n const value = record[columnName];\n // The flat row's side channel is populated only for single references, so its presence for this\n // key is what identifies one — the record itself carries no dataType to ask.\n const breadcrumb = (record[AS_DETAIL_BREADCRUMBS_KEY] as Record<string, string> | undefined)?.[columnName];\n\n return {\n key: columnName,\n value,\n objectId: breadcrumb !== undefined && value != null ? String(value) : undefined,\n breadcrumb,\n };\n}\n\n/**\n * Whether this row is a query result item.\n *\n * Discriminated on the *elements*, not just on `Array.isArray(values)`: a flat AsDetail record may\n * legitimately have a column named `values` holding an array, and mistaking one for the other would\n * silently read the wrong thing.\n */\nexport function isQueryRow(row: SparkRow | null | undefined): row is QueryResultItem {\n const values = (row as QueryResultItem | undefined)?.values;\n return Array.isArray(values) && (values.length === 0 || typeof values[0]?.key === 'string');\n}\n\n/** Whether this row is a full persistent object. Discriminated on elements, as above. */\nexport function isPersistentObject(row: SparkRow | null | undefined): row is PersistentObject {\n const attributes = (row as PersistentObject | undefined)?.attributes;\n return Array.isArray(attributes) && (attributes.length === 0 || typeof attributes[0]?.name === 'string');\n}\n","import { TranslatedString } from './translated-string';\n\nexport enum ELookupDisplayType {\n Dropdown = 0,\n Modal = 1\n}\n\nexport interface LookupReferenceListItem {\n name: string;\n isTransient: boolean;\n valueCount: number;\n displayType: ELookupDisplayType;\n}\n\nexport interface LookupReference {\n name: string;\n isTransient: boolean;\n displayType: ELookupDisplayType;\n values: LookupReferenceValue[];\n}\n\nexport interface LookupReferenceValue {\n key: string;\n values: TranslatedString;\n isActive: boolean;\n extra?: Record<string, unknown>;\n}\n","import { CustomActionDefinition } from './custom-action';\n\n/**\n * The custom actions a query should offer, from the entity type's full set.\n *\n * `showedOn` must include the query side. The accepted values are `\"detail\"`, `\"query\"`\n * and `\"both\"` — as the server model and the custom-actions guide have always\n * documented. Both grids previously tested for `\"list\"`, a value nothing emits, so an\n * action authored per the documentation rendered nowhere at all.\n *\n * ⚠️ This narrows what is DISPLAYED. It is NOT an authorization boundary: the grant\n * is, and it is enforced independently in `ExecuteCustomAction` regardless of which\n * query the caller clicked from — a caller can always POST directly.\n */\nexport function filterQueryActions(\n actions: CustomActionDefinition[],\n): CustomActionDefinition[] {\n return actions.filter(a => a.showedOn === 'query' || a.showedOn === 'both');\n}\n","/**\n * Parses a custom action's `selectionRule` — a cardinality expression over the number\n * of selected rows — into a predicate.\n *\n * A port of the server's `SelectionRuleParser`, and the two MUST agree: they are tested\n * against one shared fixture (`selection-rule.fixture.json`) for exactly this reason.\n * Vidyano, where this grammar comes from, has the same algorithm in C# and JavaScript and\n * the two have already drifted — one throws on a non-numeric operand where the other\n * silently permits everything.\n *\n * Grammar: `X` is the count placeholder, whitespace is insignificant, terms split on `X`\n * are AND-combined (`1<X<5` is a range), operators are `<= >= < > != =` matched in that\n * order so `>=` is never read as `>`, and a number-first term is mirrored (`0<X` is `>0`).\n *\n * Client-side this only drives whether a button is disabled. The server enforces the same\n * rule independently — and neither is an authorization boundary: the action's grant is.\n */\nexport function parseSelectionRule(rule?: string | null): (count: number) => boolean {\n if (!rule || !rule.trim()) return () => true;\n try {\n return compile(rule);\n } catch {\n // Unlike the server, which refuses to start on a malformed rule, the client cannot\n // usefully fail: the rule arrived over the wire from a server that already validated\n // it. Disabling the button is the safe direction — it never permits an action the\n // server would refuse.\n return () => false;\n }\n}\n\nconst OPERATORS = ['<=', '>=', '<', '>', '!=', '='] as const;\n\nfunction compile(rule: string): (count: number) => boolean {\n const normalized = rule.replace(/ /g, '').toUpperCase();\n const terms = normalized.split('X').filter(t => t.length > 0);\n if (terms.length === 0) throw new Error(`Selection rule '${rule}' has no condition.`);\n\n const numberFirst = !normalized.startsWith('X') && normalized.includes('X');\n const predicates = terms.map((term, i) => compileTerm(term, rule, numberFirst && i === 0));\n\n return count => predicates.every(p => p(count));\n}\n\nfunction compileTerm(term: string, rule: string, mirrored: boolean): (count: number) => boolean {\n const op = OPERATORS.find(o => (mirrored ? term.endsWith(o) : term.startsWith(o)));\n if (!op) throw new Error(`Selection rule '${rule}' has no recognised operator in '${term}'.`);\n\n const numberPart = mirrored ? term.slice(0, term.length - op.length) : term.slice(op.length);\n // Number(' ') is 0 and Number('1.5') is 1.5 — neither is a valid operand here.\n if (!/^-?\\d+$/.test(numberPart)) {\n throw new Error(`Selection rule '${rule}' has a non-numeric operand in '${term}'.`);\n }\n const value = Number(numberPart);\n const effective = mirrored ? mirror(op) : op;\n\n switch (effective) {\n case '<=': return count => count <= value;\n case '>=': return count => count >= value;\n case '<': return count => count < value;\n case '>': return count => count > value;\n case '!=': return count => count !== value;\n case '=': return count => count === value;\n default: throw new Error(`Selection rule '${rule}' has an unsupported operator '${effective}'.`);\n }\n}\n\nfunction mirror(op: string): string {\n switch (op) {\n case '<': return '>';\n case '>': return '<';\n case '<=': return '>=';\n case '>=': return '<=';\n default: return op;\n }\n}\n","import { CustomActionDefinition } from './custom-action';\nimport { parseSelectionRule } from './selection-rule';\n\nexport type SparkSelectionMode = 'none' | 'single' | 'multiple';\n\n/**\n * The selection mode a grid needs in order to satisfy the actions offered on it.\n *\n * Derived rather than configured, so a grid gains a checkbox column exactly when an\n * action needs one and is otherwise pixel-identical to a grid with no selection at all.\n * Vidyano's query grid does the same thing — it renders the checkbox column only if some\n * action is selection-gated.\n *\n * `'single'` when every gated action is satisfied by one row and refused by two; anything\n * else that cares about the count gets `'multiple'`.\n */\nexport function selectionModeFor(actions: CustomActionDefinition[]): SparkSelectionMode {\n // An action with no rule is not selection-gated: it acts on the query, not on rows.\n const gated = actions.filter(a => !!a.selectionRule?.trim());\n if (gated.length === 0) return 'none';\n\n const everyRuleWantsExactlyOne = gated.every(a => {\n const rule = parseSelectionRule(a.selectionRule);\n return rule(1) && !rule(2);\n });\n\n return everyRuleWantsExactlyOne ? 'single' : 'multiple';\n}\n","import { EntityAttributeDefinition } from './entity-type';\nimport { PersistentObject } from './persistent-object';\nimport { ValidationRule } from './validation-rule';\n\n/**\n * One selectable value, as replaced by a refresh hook. Mirrors the server's\n * `PersistentObjectAttributeOption`.\n */\nexport interface RefreshedOption {\n key: string;\n label?: Record<string, string>;\n}\n\n/**\n * What a refresh changed about one attribute's *presentation*.\n *\n * Kept separate from `EntityType` on purpose. The form's option loading hangs off a single effect\n * keyed on `entityType` identity, and `SparkService` caches nothing — so applying a refresh by\n * setting a new `EntityType` re-issues every reference query, every lookup fetch, a full\n * `getEntityTypes()` and a `getPermissions()` per array-AsDetail attribute, on every refresh.\n * Mutating the existing object instead is inert, because the rendering computed would not re-run.\n * An overlay is the only shape that is both reactive and free.\n */\nexport interface AttributeOverlay {\n isRequired?: boolean;\n isReadOnly?: boolean;\n isVisible?: boolean;\n rules?: ValidationRule[];\n query?: string;\n /** `undefined` means the hook did not touch the options; an empty array means there are none. */\n options?: RefreshedOption[];\n}\n\nexport type RefreshOverlay = Record<string, AttributeOverlay>;\n\n/** Applies an overlay to one attribute definition, returning a new object when anything changed. */\nexport function applyOverlay(\n attr: EntityAttributeDefinition,\n overlay: AttributeOverlay | undefined,\n): EntityAttributeDefinition {\n if (!overlay) return attr;\n\n return {\n ...attr,\n isRequired: overlay.isRequired ?? attr.isRequired,\n isReadOnly: overlay.isReadOnly ?? attr.isReadOnly,\n isVisible: overlay.isVisible ?? attr.isVisible,\n rules: overlay.rules ?? attr.rules,\n query: overlay.query ?? attr.query,\n };\n}\n\n/**\n * Reads a refresh response into an overlay.\n *\n * Everything here is presentation the server owns outright, so it is taken verbatim — there is no\n * merging to do on this half, only on values.\n */\nexport function overlayFromResponse(response: PersistentObject): RefreshOverlay {\n const overlay: RefreshOverlay = {};\n\n for (const attr of response.attributes ?? []) {\n overlay[attr.name] = {\n isRequired: attr.isRequired,\n isReadOnly: attr.isReadOnly,\n isVisible: attr.isVisible,\n rules: attr.rules ?? [],\n query: attr.query,\n options: (attr as { options?: RefreshedOption[] | null }).options ?? undefined,\n };\n }\n\n return overlay;\n}\n\n/**\n * Merges a refresh response's values into the live form.\n *\n * The rule, and the reason for it: a refresh is not instant, and the user keeps typing during it —\n * the form is deliberately never frozen. So for each attribute we ask whether the *server* changed\n * it, by comparing the response against the values that were **sent**, not against what is on\n * screen now.\n *\n * - server value equals what we sent → the hook did not touch it, so whatever is in the form now\n * wins, including anything typed while the request was in flight;\n * - server value differs → the hook deliberately changed it, and it wins over a concurrent edit.\n *\n * Comparing against the displayed value instead is the classic \"refresh eats my typing\" bug;\n * refusing to overwrite anything the user touched is the equally wrong opposite, where a dependent\n * field the hook computed never appears.\n *\n * @param sent values as they were POSTed, captured before the request left\n * @param current values as they are now, which may have moved on\n * @param response the reshaped object\n */\nexport function mergeRefreshValues(\n sent: Record<string, any>,\n current: Record<string, any>,\n response: PersistentObject,\n): Record<string, any> {\n const merged = { ...current };\n\n for (const attr of response.attributes ?? []) {\n const serverValue = attr.value ?? null;\n const sentValue = sent[attr.name] ?? null;\n\n if (!valuesEqual(serverValue, sentValue)) {\n merged[attr.name] = attr.value;\n }\n }\n\n return merged;\n}\n\n/**\n * Structural for arrays, `===` otherwise.\n *\n * Multi-reference attributes hold `string[]`, and a fresh array of the same ids is a different\n * object — so reference equality would report every one of them as \"changed by the server\" on\n * every refresh, and clobber in-flight edits to them.\n */\nfunction valuesEqual(a: any, b: any): boolean {\n if (a === b) return true;\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((item, i) => valuesEqual(item, b[i]));\n }\n // Values arrive off the wire as JSON scalars; anything else compares by JSON shape rather than\n // by identity, which is what a caller means by \"did this change\".\n if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') {\n return JSON.stringify(a) === JSON.stringify(b);\n }\n return false;\n}\n","import { ValidationRule } from './validation-rule';\nimport { TranslatedString } from './translated-string';\n\n/** One rule failure, in the shape the form already renders per field. */\nexport interface RuleFailure {\n attributeName: string;\n ruleType: string;\n message: string;\n}\n\nexport interface EvaluableAttribute {\n name: string;\n label?: TranslatedString;\n isRequired?: boolean;\n rules?: ValidationRule[];\n}\n\nconst EMAIL = /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/i;\nconst URL = /^https?:\\/\\/[^\\s]+$/i;\n\nfunction isEmpty(value: any): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === 'string') return value.trim() === '';\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * Evaluates an attribute's rules against a value, mirroring the server's `ValidationService`.\n *\n * ⚠️ **Parity with the server is the point, and disagreement is worse than silence.** A client that\n * rejects something the server would accept blocks legitimate work with no recourse; one that\n * accepts something the server rejects merely defers the error to the round-trip, which is where it\n * used to live anyway. So the rule set here is deliberately limited to the types the server\n * implements, and an unrecognised rule type is ignored rather than guessed at.\n *\n * This exists because a refresh hook that imposes a rule needs it to bite before Save — previously\n * `rules` was carried on the wire and never evaluated in the browser at all.\n */\nexport function evaluateRules(attr: EvaluableAttribute, value: any): RuleFailure[] {\n const failures: RuleFailure[] = [];\n const label = attr.label?.['en'] ?? attr.name;\n\n if (attr.isRequired && isEmpty(value)) {\n return [{ attributeName: attr.name, ruleType: 'required', message: `${label} is required` }];\n }\n\n // A rule other than \"required\" says nothing about an absent value — that is what required is for.\n if (isEmpty(value)) return failures;\n\n const text = value?.toString() ?? '';\n\n for (const rule of attr.rules ?? []) {\n const failure = evaluateRule(attr.name, label, rule, value, text);\n if (failure) failures.push(failure);\n }\n\n return failures;\n}\n\nfunction evaluateRule(\n name: string,\n label: string,\n rule: ValidationRule,\n value: any,\n text: string,\n): RuleFailure | null {\n const message = rule.message?.['en'];\n\n switch (rule.type?.toLowerCase()) {\n case 'maxlength': {\n const max = toInt(rule.value);\n if (max === null || text.length <= max) return null;\n return { attributeName: name, ruleType: 'maxLength', message: message ?? `${label} must be at most ${max} characters` };\n }\n case 'minlength': {\n const min = toInt(rule.value);\n if (min === null || text.length >= min) return null;\n return { attributeName: name, ruleType: 'minLength', message: message ?? `${label} must be at least ${min} characters` };\n }\n case 'range': {\n const numeric = toNumber(value);\n if (numeric === null) return null;\n if (rule.min !== undefined && rule.min !== null && numeric < rule.min) {\n return { attributeName: name, ruleType: 'range', message: message ?? `${label} must be at least ${rule.min}` };\n }\n if (rule.max !== undefined && rule.max !== null && numeric > rule.max) {\n return { attributeName: name, ruleType: 'range', message: message ?? `${label} must be at most ${rule.max}` };\n }\n return null;\n }\n case 'regex': {\n const pattern = rule.value?.toString();\n if (!pattern) return null;\n let re: RegExp;\n try {\n re = new RegExp(pattern);\n } catch {\n // An unparseable pattern is a server-side authoring bug. Staying silent leaves the server to\n // report it, which is strictly better than blocking the user over a rule nobody can evaluate.\n return null;\n }\n return re.test(text) ? null : { attributeName: name, ruleType: 'regex', message: message ?? `${label} is not in the expected format` };\n }\n case 'email':\n return EMAIL.test(text) ? null : { attributeName: name, ruleType: 'email', message: message ?? `${label} must be a valid email address` };\n case 'url':\n return URL.test(text) ? null : { attributeName: name, ruleType: 'url', message: message ?? `${label} must be a valid URL` };\n default:\n return null;\n }\n}\n\nfunction toInt(value: any): number | null {\n const n = Number(value);\n return Number.isFinite(n) ? Math.trunc(n) : null;\n}\n\nfunction toNumber(value: any): number | null {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -59,6 +59,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
59
59
|
args: [{ name: 'inputType', standalone: true, pure: true }]
|
|
60
60
|
}] });
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Parses a wire value into a `Date`, or `null` when it is absent or unparseable.
|
|
64
|
+
*
|
|
65
|
+
* Exists so the detail page and the grid agree. The grid gets its `Date` from `queryCellValue`, which
|
|
66
|
+
* needs a cell and a column and so cannot be reused on a detail attribute; before this, the detail page
|
|
67
|
+
* had no parsing step at all and printed the raw ISO string — `2026-12-31T23:59:00-08:00` in a
|
|
68
|
+
* definition list, while the grid showed `01/01/2027, 08:59` for the same document.
|
|
69
|
+
*
|
|
70
|
+
* The parsing contract is deliberately identical to `queryCellValue`'s: `new Date(...)`, and an
|
|
71
|
+
* unparseable value yields `null` so the caller can fall back to its own text rather than rendering
|
|
72
|
+
* "Invalid Date".
|
|
73
|
+
*
|
|
74
|
+
* Note what `new Date(...)` does to an offset, because it is the whole reason this is a separate step:
|
|
75
|
+
* it collapses the value to an instant and forgets the offset it was written with. That is correct for
|
|
76
|
+
* display — a viewer wants the moment in their own zone — but it means a `Date` can never be the thing
|
|
77
|
+
* you reach for when you need the *originating* wall clock. Read that from the wire value instead.
|
|
78
|
+
*/
|
|
79
|
+
class ParsedDatePipe {
|
|
80
|
+
transform(value) {
|
|
81
|
+
if (value == null || value === '')
|
|
82
|
+
return null;
|
|
83
|
+
if (value instanceof Date)
|
|
84
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
85
|
+
if (typeof value !== 'string' && typeof value !== 'number')
|
|
86
|
+
return null;
|
|
87
|
+
const parsed = new Date(value);
|
|
88
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
89
|
+
}
|
|
90
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ParsedDatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
91
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.3", ngImport: i0, type: ParsedDatePipe, isStandalone: true, name: "parsedDate" });
|
|
92
|
+
}
|
|
93
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ParsedDatePipe, decorators: [{
|
|
94
|
+
type: Pipe,
|
|
95
|
+
args: [{ name: 'parsedDate', standalone: true, pure: true }]
|
|
96
|
+
}] });
|
|
97
|
+
|
|
62
98
|
/**
|
|
63
99
|
* Fills a `{Field}` template from a plain object's own fields. Used ONLY for AsDetail
|
|
64
100
|
* nested-object summary display: an embedded value object (no id, no document) is rendered
|
|
@@ -728,5 +764,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
728
764
|
* Generated bundle index. Do not edit.
|
|
729
765
|
*/
|
|
730
766
|
|
|
731
|
-
export { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AttributeValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, CanEditDetailRowPipe, ErrorForAttributePipe, IconNamePipe, InlineRefOptionsPipe, InputTypePipe, LookupDisplayTypePipe, LookupDisplayValuePipe, LookupOptionsPipe, QueryCellValuePipe, QueryReferenceChipsPipe, RawAttributeValuePipe, ReferenceAttrValuePipe, ReferenceChipsPipe, ReferenceDisplayValuePipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, RouterLinkPipe, TranslateKeyPipe };
|
|
767
|
+
export { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AttributeValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, CanEditDetailRowPipe, ErrorForAttributePipe, IconNamePipe, InlineRefOptionsPipe, InputTypePipe, LookupDisplayTypePipe, LookupDisplayValuePipe, LookupOptionsPipe, ParsedDatePipe, QueryCellValuePipe, QueryReferenceChipsPipe, RawAttributeValuePipe, ReferenceAttrValuePipe, ReferenceChipsPipe, ReferenceDisplayValuePipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, RouterLinkPipe, TranslateKeyPipe };
|
|
732
768
|
//# sourceMappingURL=mintplayer-ng-spark-pipes.mjs.map
|