@vectoriox/iox-ui 5.42.0 → 5.44.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.
@@ -0,0 +1,491 @@
1
+ /**
2
+ * Shared data-source resolving pattern — the SINGLE source of truth for how an IoxDataSource
3
+ * becomes a backend fetch, consumed by BOTH the page builder (iox-cms-client bindings panel) and
4
+ * the SSR client engine (iox-client-engine DataSourceResolverService). There must be no second
5
+ * copy of this decision logic; only the transport differs between the two:
6
+ *
7
+ * builder → authenticated org-scoped controllers (admin session):
8
+ * single by id → GET admin.../resources/:type/:id (ContentService.getContentById)
9
+ * collection → GET admin.../resources/:type (ContentService.getContent)
10
+ * engine → @Public() controllers (no auth, isPublished filter applied server-side):
11
+ * single by id → GET api/:type/:id (UserResourcesController)
12
+ * collection → GET api/:type?filters (UserResourcesController)
13
+ *
14
+ * The DECISION (by-id vs collection, which id, which filters, how to pick the single result) is
15
+ * identical for both — that's what this module owns. Each app maps the returned {@link DataSourcePlan}
16
+ * onto its own transport. A single item is ALWAYS fetched by its id as a path segment — never a
17
+ * `?_id=` query — to match the builder's getContentById.
18
+ */
19
+ /** The order a query source returns its rows in (CC2, board 4 "Sort" — the data source popover). */
20
+ var DataSourceSortDir;
21
+ (function (DataSourceSortDir) {
22
+ DataSourceSortDir["Asc"] = "asc";
23
+ DataSourceSortDir["Desc"] = "desc";
24
+ })(DataSourceSortDir || (DataSourceSortDir = {}));
25
+ /** The query parameter carrying the order: `sort=order` ascending, `sort=-order` descending. A CONTROL like
26
+ * `limit`, not a field filter — the public controllers split it out (iox-cms `query-controls.ts`). */
27
+ const SORT_PARAM = 'sort';
28
+ const SORT_FIELD = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
29
+ /** `sort`'s wire value, or null when the source sets no (valid) order. */
30
+ function sortParamValue(sort) {
31
+ const field = sort?.field?.trim();
32
+ if (!field || !SORT_FIELD.test(field))
33
+ return null;
34
+ return sort?.dir === DataSourceSortDir.Desc ? `-${field}` : field;
35
+ }
36
+ /**
37
+ * Match a page route template against an actual path and extract named params.
38
+ * matchRouteParams('/project/:projectId', 'project/68abc') → { projectId: '68abc' }
39
+ * Both sides are normalised (leading slash stripped) and split on '/'; values are URL-decoded.
40
+ */
41
+ function matchRouteParams(template, actual) {
42
+ const t = (template ?? '').replace(/^\/+/, '').split('/');
43
+ const a = (actual ?? '').replace(/^\/+/, '').split('/');
44
+ const params = {};
45
+ for (let i = 0; i < t.length; i++) {
46
+ const seg = t[i];
47
+ if (seg && seg.startsWith(':'))
48
+ params[seg.slice(1)] = decodeURIComponent(a[i] ?? '');
49
+ }
50
+ return params;
51
+ }
52
+ /**
53
+ * Build the query filter for a data-source request from its filterByRouteParam / filterByQueryParams
54
+ * config. `field` is filtered by the route-param value (falling back to `defaultId`); query filters
55
+ * map each `field` to a query-param value (falling back to `defaultValue`), and a filter with NO
56
+ * `queryParam` is a constant on `defaultValue`. Empty values are omitted so the source isn't
57
+ * accidentally filtered to nothing.
58
+ */
59
+ function buildDataSourceFilter(req, routeParams = {}, queryParams = {}) {
60
+ const params = {};
61
+ const byRoute = req?.filterByRouteParam;
62
+ if (byRoute?.field && byRoute?.routeParam) {
63
+ const value = routeParams[byRoute.routeParam] ?? byRoute.defaultId;
64
+ if (value !== undefined && value !== null && value !== '')
65
+ params[byRoute.field] = value;
66
+ }
67
+ for (const q of req?.filterByQueryParams ?? []) {
68
+ if (!q?.field)
69
+ continue;
70
+ // No queryParam → a CONSTANT filter: defaultValue is the value, in the builder preview and
71
+ // on the live site alike. With a queryParam the URL wins and defaultValue is the fallback.
72
+ const value = q.queryParam ? (queryParams[q.queryParam] ?? q.defaultValue) : q.defaultValue;
73
+ if (value !== undefined && value !== null && value !== '')
74
+ params[q.field] = value;
75
+ }
76
+ return params;
77
+ }
78
+ /**
79
+ * Mirrors the builder's getContentById decision. When a SINGLE source is filtered by the item id
80
+ * (`_id`) via a route param, the item is fetched by PATH (`.../:type/:id`), not a `?_id=` query.
81
+ * Returns that id (route-param value, else `defaultId`), or `null` when the source isn't a
82
+ * single-by-id fetch — in which case the caller uses the collection endpoint + filters. The
83
+ * path-based form only applies to the id field; a single source filtered by any other field still
84
+ * goes through the collection endpoint.
85
+ */
86
+ function resolveSingleItemId(req, routeParams = {}) {
87
+ const byRoute = req?.filterByRouteParam;
88
+ if (!byRoute?.routeParam)
89
+ return null;
90
+ if (byRoute.field && byRoute.field !== '_id')
91
+ return null;
92
+ const value = routeParams[byRoute.routeParam] ?? byRoute.defaultId;
93
+ return value !== undefined && value !== null && value !== '' ? String(value) : null;
94
+ }
95
+ /**
96
+ * Resolve an IoxDataSource into a transport-agnostic {@link DataSourcePlan}. This is the shared
97
+ * decision both the builder and the SSR engine run; each then executes the plan with its own
98
+ * HTTP layer. Single sources with a route-param id resolve to a `by-id` (path) fetch — exactly like
99
+ * the builder's getContentById.
100
+ */
101
+ function planDataSource(ds, routeParams = {}, queryParams = {}) {
102
+ const mode = ds?.mode === 'single' ? 'single' : 'list';
103
+ switch (ds?.type) {
104
+ case 'cms':
105
+ case 'shop': {
106
+ const domain = ds.type;
107
+ const req = domain === 'cms' ? ds.request?.cms : ds.request?.shop;
108
+ const resource = req?.contentType;
109
+ if (!resource)
110
+ return { kind: 'none' };
111
+ const id = mode === 'single' ? resolveSingleItemId(req, routeParams) : null;
112
+ if (id)
113
+ return { kind: 'by-id', domain, resource, id, mode };
114
+ const filter = buildDataSourceFilter(req, routeParams, queryParams);
115
+ const plan = { kind: 'collection', domain, resource, filter, mode };
116
+ if (req?.limit)
117
+ plan.limit = req.limit;
118
+ const sort = sortParamValue(req?.sort);
119
+ if (sort)
120
+ plan.sort = sort;
121
+ return plan;
122
+ }
123
+ case 'external-api': {
124
+ const endpoint = ds.request?.endpoint;
125
+ return endpoint ? { kind: 'external', endpoint, mode } : { kind: 'none' };
126
+ }
127
+ case 'static':
128
+ return { kind: 'static', data: ds.request?.data ?? null, mode };
129
+ case 'derived': {
130
+ const d = ds.request?.derived;
131
+ return d?.from ? { kind: 'derived', from: d.from, path: d.path ?? '', mode } : { kind: 'none' };
132
+ }
133
+ case 'variable':
134
+ // A VARIABLE is a page-local resource whose value is WRITTEN at runtime by an interaction
135
+ // (setContext), never fetched. It resolves to nothing here on purpose: the page renders
136
+ // its empty state until something writes it. `request.variable.itemsFrom` only names the
137
+ // source whose items it holds, so the bind picker can suggest fields.
138
+ return { kind: 'none' };
139
+ default:
140
+ return { kind: 'none' };
141
+ }
142
+ }
143
+ /**
144
+ * The aliases on a page that are VARIABLES — runtime-written resources, not fetched data.
145
+ *
146
+ * Shared so the builder and the SSR engine agree on which aliases start empty and are filled by
147
+ * interactions. See element-context-state-plan.md.
148
+ */
149
+ function variableAliases(dataSources) {
150
+ const out = new Set();
151
+ for (const ds of dataSources ?? []) {
152
+ if (ds?.type === 'variable' && ds.alias)
153
+ out.add(ds.alias);
154
+ }
155
+ return out;
156
+ }
157
+ /** The source alias whose items a variable holds, if it declares one (for field suggestions). */
158
+ function variableItemsFrom(dataSources, alias) {
159
+ for (const ds of dataSources ?? []) {
160
+ if (ds?.type === 'variable' && ds.alias === alias)
161
+ return ds.request?.variable?.itemsFrom || undefined;
162
+ }
163
+ return undefined;
164
+ }
165
+ /**
166
+ * Normalise a collection response into the shape a source expects: unwraps `{items}`/`{data}`
167
+ * envelopes, then returns the first item for a `single` source or the whole array for a `list`.
168
+ * A `by-id` response is already the item, so callers pass it through unchanged.
169
+ */
170
+ function normalizeCollectionResult(mode, data) {
171
+ const items = Array.isArray(data) ? data : (data?.items ?? data?.data ?? []);
172
+ return mode === 'single' ? (items[0] ?? null) : items;
173
+ }
174
+ /** Navigate a dot/bracket path into an object, e.g. `gallery`, `details.images`, `variants[0].skus`.
175
+ * Returns undefined if any segment is missing. Kept minimal + self-contained so it is shareable by
176
+ * both the builder and the SSR engine (the two have divergent local `resolvePath` copies). */
177
+ function getByPath(obj, path) {
178
+ if (obj == null || !path)
179
+ return obj;
180
+ // Turn `a[0].b` into ['a','0','b'].
181
+ const segments = path.replace(/\[(\w+)\]/g, '.$1').split('.').filter(Boolean);
182
+ let cur = obj;
183
+ for (const seg of segments) {
184
+ if (cur == null)
185
+ return undefined;
186
+ cur = cur[seg];
187
+ }
188
+ return cur;
189
+ }
190
+ /**
191
+ * The array a Repeater (or a repeating Slider) iterates over — the SHARED decision so the builder
192
+ * canvas and the SSR engine agree (see data-source-resolving.md).
193
+ * - No `sourcePath`: the resolved source IS the list. An array passes through; a lone object degrades
194
+ * to a single-row list (back-compat with the old `fetchItems`); null/undefined → `[]`.
195
+ * - With `sourcePath`: the source resolved to a SINGLE resource (e.g. a product); dive into the
196
+ * nested array property (e.g. `gallery`). Non-array / missing → `[]`.
197
+ */
198
+ /**
199
+ * Fill in `derived` aliases in an already-resolved map. Given the data sources and a map of resolved
200
+ * NON-derived values (by alias), compute each `type:'derived'` alias as `getByPath(parent, path)` in
201
+ * dependency order (chains resolved by iterating until stable). Mutates + returns the map. Used by the
202
+ * SSR engine after it resolves the root sources; the builder resolves derived lazily per alias.
203
+ */
204
+ function resolveDerivedInto(dataSources, resolved) {
205
+ const derived = (dataSources ?? []).filter(d => d?.type === 'derived' && !!d.alias);
206
+ let changed = true, guard = 0;
207
+ while (changed && guard++ < 25) {
208
+ changed = false;
209
+ for (const d of derived) {
210
+ if (d.alias in resolved)
211
+ continue;
212
+ const from = d.request?.derived?.from;
213
+ if (from && from in resolved) {
214
+ resolved[d.alias] = getByPath(resolved[from], d.request.derived.path ?? '');
215
+ changed = true;
216
+ }
217
+ }
218
+ }
219
+ return resolved;
220
+ }
221
+ function resolveRepeaterItems(resolved, sourcePath) {
222
+ if (sourcePath) {
223
+ const nested = getByPath(resolved, sourcePath);
224
+ return Array.isArray(nested) ? nested : [];
225
+ }
226
+ if (Array.isArray(resolved))
227
+ return resolved;
228
+ return resolved == null ? [] : [resolved];
229
+ }
230
+ // ── Referenced data sources (shared: builder-save + SSR resolve) ─────────────────────────────────
231
+ // Which data-source aliases a node tree uses, so a consumer resolves ONLY what a page/global needs
232
+ // (not the whole org library). Used by cms-client (persist globals' sources) and the SSR engine
233
+ // (resolve the referenced subset per page). See org-data-sources-plan.md.
234
+ /** Aliases referenced anywhere in a node tree: a repeater/slider `source` (a `bindings` entry with
235
+ * `prop === 'source'`, or `props.source` for the slider) plus every binding's `source`. */
236
+ function collectReferencedAliases(nodes, acc = new Set()) {
237
+ for (const n of nodes ?? []) {
238
+ for (const b of n?.bindings ?? []) {
239
+ if (b?.source)
240
+ acc.add(b.source);
241
+ }
242
+ if (n?.props?.source)
243
+ acc.add(n.props.source);
244
+ if (n?.children?.length)
245
+ collectReferencedAliases(n.children, acc);
246
+ }
247
+ return acc;
248
+ }
249
+ /** The subset of `all` whose `alias` is referenced by `nodes` (shallow — direct references only). */
250
+ function referencedDataSources(nodes, all) {
251
+ const aliases = collectReferencedAliases(nodes);
252
+ return (all ?? []).filter(ds => aliases.has(ds?.alias));
253
+ }
254
+ /** Like `referencedDataSources`, but also pulls in a referenced source's DEPENDENCIES (transitively),
255
+ * none of which the layout names directly:
256
+ * - a `derived` alias needs its `request.derived.from` parent;
257
+ * - a `relative` (route-context) alias needs its `request.relative.subjectFrom` (the current item)
258
+ * and `request.relative.from` (the collection it navigates).
259
+ * Use this when RESOLVING (the engine) so derived views and siblings/related don't resolve to nothing. */
260
+ function referencedDataSourcesDeep(nodes, all) {
261
+ const byAlias = new Map((all ?? []).map(d => [d?.alias, d]));
262
+ const wanted = collectReferencedAliases(nodes);
263
+ const queue = [...wanted];
264
+ const enqueue = (alias) => {
265
+ if (alias && !wanted.has(alias)) {
266
+ wanted.add(alias);
267
+ queue.push(alias);
268
+ }
269
+ };
270
+ while (queue.length) {
271
+ const ds = byAlias.get(queue.pop());
272
+ enqueue(ds?.request?.derived?.from);
273
+ enqueue(ds?.request?.relative?.subjectFrom);
274
+ enqueue(ds?.request?.relative?.from);
275
+ }
276
+ return (all ?? []).filter(d => wanted.has(d?.alias));
277
+ }
278
+ /**
279
+ * Turn a resolved {@link DataSourcePlan} into the concrete public HTTP request (url + params) — the
280
+ * SINGLE place that decides the URL shape, shared by the builder binding preview and the SSR engine
281
+ * so both hit the SAME public endpoints (no admin, no duplication). Returns `null` for `static` /
282
+ * `none` plans (no HTTP call). A single item is a PATH segment (`/:resource/:id`), never `?_id=`.
283
+ */
284
+ function dataSourcePlanToRequest(plan, endpoints) {
285
+ const baseFor = (domain) => (domain === 'shop' ? endpoints.shopBase : endpoints.cmsBase);
286
+ switch (plan.kind) {
287
+ case 'by-id':
288
+ return { url: `${baseFor(plan.domain)}${plan.resource}/${encodeURIComponent(plan.id)}`, params: {} };
289
+ case 'collection': {
290
+ const params = { ...plan.filter };
291
+ if (plan.limit)
292
+ params['limit'] = plan.limit;
293
+ if (plan.sort)
294
+ params[SORT_PARAM] = plan.sort;
295
+ return { url: `${baseFor(plan.domain)}${plan.resource}`, params };
296
+ }
297
+ case 'external':
298
+ return { url: plan.endpoint, params: {} };
299
+ default:
300
+ return null;
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Shared RELATIVE (route-context) data resolving — the SINGLE source of truth for how a detail page
306
+ * derives values FROM its current route item, consumed by BOTH the page builder (iox-cms-client) and
307
+ * the SSR client engine (iox-client-engine). Companion to `data-source-resolver.ts`.
308
+ *
309
+ * These are the pure, transport-agnostic pieces behind the Route binding scope
310
+ * (`$route` / `$current` / `$siblings` / `$related`). Resolution is a DEPENDENT SECOND PASS: the
311
+ * consumer resolves `$current` first (the existing single by-id fetch via `planDataSource`), then
312
+ * calls these to derive the relative values — there is NO new `DataSourcePlan` kind (`$related` still
313
+ * feeds a normal `collection` plan built from {@link buildRelatedFilter}). See
314
+ * `architecture/builder/relative-data-sources-plan.md`.
315
+ *
316
+ * There must be no second copy of this decision logic; only the transport (which HTTP layer fetches
317
+ * the ordered collection / related list) differs between the two consumers.
318
+ */
319
+ /** Aliases in the Route scope are platform-owned and start with this prefix; a USER alias may never
320
+ * start with it, which keeps the Route scope structurally collision-proof with Local/Global. Used to
321
+ * validate new/renamed aliases and to render the reserved (read-only) item state in the bind picker. */
322
+ const RESERVED_ALIAS_PREFIX = '$';
323
+ /** True for a platform-owned Route-scope alias (`$route`, `$current`, `$siblings`, `$related`, …). */
324
+ function isReservedAlias(alias) {
325
+ return typeof alias === 'string' && alias.startsWith(RESERVED_ALIAS_PREFIX);
326
+ }
327
+ const EMPTY_SIBLINGS = { prev: null, next: null, index: -1, count: 0, isFirst: false, isLast: false };
328
+ /** Generic, deterministic comparator: numbers numerically, Dates by time, everything else by
329
+ * locale-aware string compare (ISO date strings sort correctly this way). Nullish sorts first. */
330
+ function compareValues(a, b) {
331
+ if (a == null && b == null)
332
+ return 0;
333
+ if (a == null)
334
+ return -1;
335
+ if (b == null)
336
+ return 1;
337
+ if (typeof a === 'number' && typeof b === 'number')
338
+ return a - b;
339
+ const da = a instanceof Date ? a.getTime() : NaN;
340
+ const db = b instanceof Date ? b.getTime() : NaN;
341
+ if (!isNaN(da) && !isNaN(db))
342
+ return da - db;
343
+ return String(a).localeCompare(String(b));
344
+ }
345
+ /**
346
+ * Locate the current item within an ordered set and return its neighbours + position. Pure: the input
347
+ * list is never mutated (sorting works on a shallow copy). `currentId` is compared to each item's
348
+ * `idField` via string equality (route-param values are strings). When the current id isn't found,
349
+ * `index` is `-1` and `prev`/`next` are `null` (but `count` still reflects the set size).
350
+ */
351
+ function resolveSiblings(items, currentId, options = {}) {
352
+ const list = Array.isArray(items) ? items.slice() : [];
353
+ const count = list.length;
354
+ if (count === 0)
355
+ return { ...EMPTY_SIBLINGS };
356
+ const idField = options.idField ?? '_id';
357
+ if (options.sortBy) {
358
+ const dir = options.sortDir === 'desc' ? -1 : 1;
359
+ list.sort((a, b) => dir * compareValues(getByPath(a, options.sortBy), getByPath(b, options.sortBy)));
360
+ }
361
+ const cid = currentId == null ? null : String(currentId);
362
+ const index = cid == null ? -1 : list.findIndex(it => String(getByPath(it, idField)) === cid);
363
+ if (index === -1)
364
+ return { prev: null, next: null, index: -1, count, isFirst: false, isLast: false };
365
+ const canWrap = !!options.wrap && count > 1;
366
+ const prev = index > 0 ? list[index - 1] : (canWrap ? list[count - 1] : null);
367
+ const next = index < count - 1 ? list[index + 1] : (canWrap ? list[0] : null);
368
+ return { prev, next, index, count, isFirst: index === 0, isLast: index === count - 1 };
369
+ }
370
+ /** A value usable as an equality filter param: a non-empty scalar. Objects/arrays/nullish are skipped
371
+ * so a related query isn't built from an unresolved rel object (surfaced as a config issue in the UI
372
+ * rather than silently matching everything). Multi-value match is a v2 concern. */
373
+ function isScalarFilterValue(v) {
374
+ if (v == null)
375
+ return false;
376
+ if (typeof v === 'string')
377
+ return v !== '';
378
+ if (typeof v === 'number')
379
+ return Number.isFinite(v);
380
+ return typeof v === 'boolean';
381
+ }
382
+ /**
383
+ * Build the collection filter for `$related` from the resolved current item: an equality match on
384
+ * each `matchField`'s value (e.g. same `category`). Feeds a normal `collection` plan — no new plan
385
+ * kind. Fields whose value is not a scalar (or is empty) are omitted, matching
386
+ * `buildDataSourceFilter`'s "don't over-filter" rule. Exclude-self is applied to the RESULT via
387
+ * {@link excludeSelf} (the public query controllers are equality-only, so it can't be a `$ne` param).
388
+ */
389
+ function buildRelatedFilter(current, options) {
390
+ const fields = Array.isArray(options.matchFields) ? options.matchFields : [options.matchFields];
391
+ const filter = {};
392
+ for (const field of fields) {
393
+ if (!field)
394
+ continue;
395
+ const value = getByPath(current, field);
396
+ if (isScalarFilterValue(value))
397
+ filter[field] = value;
398
+ }
399
+ return filter;
400
+ }
401
+ /**
402
+ * Drop the current item from a list by `idField` (default `_id`). Used to exclude self from a
403
+ * `$related` result after fetch. A nullish/empty `currentId` leaves the list unchanged.
404
+ */
405
+ function excludeSelf(items, currentId, idField = '_id') {
406
+ if (!Array.isArray(items))
407
+ return [];
408
+ if (currentId == null || currentId === '')
409
+ return items;
410
+ const cid = String(currentId);
411
+ return items.filter(it => String(getByPath(it, idField)) !== cid);
412
+ }
413
+ /** Resolve kinds in a fixed order so a `siblings`/`related` source may reference `$current` (or any
414
+ * earlier relative alias) via `subjectFrom` and still see it resolved. */
415
+ const KIND_ORDER = { route: 0, current: 1, siblings: 2, related: 3 };
416
+ /** Does an item satisfy every field in an equality predicate map (string-compared)? */
417
+ function matchesFilter(item, filter) {
418
+ return Object.keys(filter).every(k => String(getByPath(item, k)) === String(filter[k]));
419
+ }
420
+ /**
421
+ * Compute ONE relative source's value from its already-resolved {@link RelativeInputs} — the SHARED
422
+ * kind→computation decision, so the batch pass ({@link resolveRelativeInto}) and the builder's async
423
+ * per-alias resolver produce identical results with no forked switch. `route` → the route params;
424
+ * `current` → the subject; `siblings` → {@link resolveSiblings} over the collection; `related` → the
425
+ * collection filtered by {@link buildRelatedFilter}'s predicate (+ {@link excludeSelf}).
426
+ */
427
+ function computeRelative(spec, inputs) {
428
+ const idField = spec.idField ?? '_id';
429
+ switch (spec.kind) {
430
+ case 'route':
431
+ return inputs.routeParams ?? {};
432
+ case 'current':
433
+ return inputs.subject ?? null;
434
+ case 'siblings': {
435
+ const siblings = resolveSiblings(inputs.collection, getByPath(inputs.subject, idField), {
436
+ idField, sortBy: spec.sortBy, sortDir: spec.sortDir, wrap: spec.wrap,
437
+ });
438
+ // `pick` resolves the source directly to that neighbour item (named-source shape,
439
+ // e.g. `nextProject`); without it the caller binds a sub-path of the whole object.
440
+ return spec.pick ? siblings[spec.pick] : siblings;
441
+ }
442
+ case 'related': {
443
+ const filter = buildRelatedFilter(inputs.subject, { matchFields: spec.matchFields ?? [] });
444
+ const pool = Array.isArray(inputs.collection) ? inputs.collection : [];
445
+ const items = pool.filter(it => matchesFilter(it, filter));
446
+ return spec.excludeSelf !== false ? excludeSelf(items, getByPath(inputs.subject, idField), idField) : items;
447
+ }
448
+ default:
449
+ return null;
450
+ }
451
+ }
452
+ /**
453
+ * Compute every `type:'relative'` source into `resolved` (keyed by alias), IN PLACE. Pure w.r.t. HTTP
454
+ * — it only reads already-resolved roots + the route params, so both consumers run it identically as
455
+ * a second pass. Delegates each source to {@link computeRelative}. `current`'s subject is `from`;
456
+ * `siblings`/`related` take `collection` = `from` and `subject` = `subjectFrom ?? from`. Mutates and
457
+ * returns `resolved`.
458
+ */
459
+ function resolveRelativeInto(dataSources, resolved, routeParams = {}) {
460
+ const relatives = (dataSources ?? [])
461
+ .filter(d => d?.type === 'relative' && !!d.alias && !!d.request?.relative)
462
+ .sort((a, b) => KIND_ORDER[a.request.relative.kind] - KIND_ORDER[b.request.relative.kind]);
463
+ for (const d of relatives) {
464
+ const spec = d.request.relative;
465
+ const subjectAlias = spec.kind === 'current' ? spec.from : (spec.subjectFrom ?? spec.from);
466
+ resolved[d.alias] = computeRelative(spec, {
467
+ subject: subjectAlias ? resolved[subjectAlias] : undefined,
468
+ collection: spec.from ? resolved[spec.from] : undefined,
469
+ routeParams,
470
+ });
471
+ }
472
+ return resolved;
473
+ }
474
+
475
+ /**
476
+ * @vectoriox/iox-ui/data-core — the shared, FRAMEWORK-FREE data-source core (data-source-resolving.md): the ONE
477
+ * decision of how a data source becomes a fetch (`planDataSource`), the route-param matcher, the derived /
478
+ * relative (`$siblings`, `$related`) resolvers, the repeater item reader, and which sources a node tree
479
+ * references (`referencedDataSourcesDeep`). The builder and the SSR engine resolve with it; iox-cms reads it
480
+ * under plain Node (W2's Undo: is a data source still in use). Pure: no transport, no Angular. The main
481
+ * entry re-exports it by package path, so `@vectoriox/iox-ui` keeps working.
482
+ * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig paths —
483
+ * or `nest start` dies with ERR_REQUIRE_ESM while jest and build both pass.
484
+ */
485
+
486
+ /**
487
+ * Generated bundle index. Do not edit.
488
+ */
489
+
490
+ export { DataSourceSortDir, RESERVED_ALIAS_PREFIX, SORT_PARAM, buildDataSourceFilter, buildRelatedFilter, collectReferencedAliases, computeRelative, dataSourcePlanToRequest, excludeSelf, getByPath, isReservedAlias, matchRouteParams, normalizeCollectionResult, planDataSource, referencedDataSources, referencedDataSourcesDeep, resolveDerivedInto, resolveRelativeInto, resolveRepeaterItems, resolveSiblings, resolveSingleItemId, sortParamValue, variableAliases, variableItemsFrom };
491
+ //# sourceMappingURL=vectoriox-iox-ui-data-core.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vectoriox-iox-ui-data-core.mjs","sources":["../../../projects/iox-ui/src/data-core/data-source-resolver.ts","../../../projects/iox-ui/src/data-core/relative-sources.ts","../../../projects/iox-ui/src/data-core/public-api.ts","../../../projects/iox-ui/src/data-core/vectoriox-iox-ui-data-core.ts"],"sourcesContent":["/**\n * Shared data-source resolving pattern — the SINGLE source of truth for how an IoxDataSource\n * becomes a backend fetch, consumed by BOTH the page builder (iox-cms-client bindings panel) and\n * the SSR client engine (iox-client-engine DataSourceResolverService). There must be no second\n * copy of this decision logic; only the transport differs between the two:\n *\n * builder → authenticated org-scoped controllers (admin session):\n * single by id → GET admin.../resources/:type/:id (ContentService.getContentById)\n * collection → GET admin.../resources/:type (ContentService.getContent)\n * engine → @Public() controllers (no auth, isPublished filter applied server-side):\n * single by id → GET api/:type/:id (UserResourcesController)\n * collection → GET api/:type?filters (UserResourcesController)\n *\n * The DECISION (by-id vs collection, which id, which filters, how to pick the single result) is\n * identical for both — that's what this module owns. Each app maps the returned {@link DataSourcePlan}\n * onto its own transport. A single item is ALWAYS fetched by its id as a path segment — never a\n * `?_id=` query — to match the builder's getContentById.\n */\n\nexport type DataSourceMode = 'single' | 'list';\nexport type DataSourceDomain = 'cms' | 'shop';\n\nexport interface DataSourceRouteFilter {\n field?: string;\n routeParam?: string;\n defaultId?: string;\n}\n\nexport interface DataSourceQueryFilter {\n field?: string;\n /** URL query param the value is read from. OMIT for a CONSTANT filter — then `defaultValue`\n * alone is the value, so `isReviewShow=true` filters identically in preview and on the site. */\n queryParam?: string;\n /** Value used when the query param is absent — i.e. always, for a constant filter, and in the\n * builder preview, where there are no live query params. NOT preview-only: it is the runtime\n * fallback too. An empty default with no query-param value means the filter is DROPPED. */\n defaultValue?: string;\n}\n\n/** The order a query source returns its rows in (CC2, board 4 \"Sort\" — the data source popover). */\nexport enum DataSourceSortDir {\n Asc = 'asc',\n Desc = 'desc',\n}\n\nexport interface DataSourceSort {\n /** A field of the content type (a dot path into a nested object is allowed). */\n field?: string;\n dir?: DataSourceSortDir | `${DataSourceSortDir}`;\n}\n\n/** The query parameter carrying the order: `sort=order` ascending, `sort=-order` descending. A CONTROL like\n * `limit`, not a field filter — the public controllers split it out (iox-cms `query-controls.ts`). */\nexport const SORT_PARAM = 'sort';\nconst SORT_FIELD = /^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$/;\n\n/** `sort`'s wire value, or null when the source sets no (valid) order. */\nexport function sortParamValue(sort: DataSourceSort | null | undefined): string | null {\n const field = sort?.field?.trim();\n if (!field || !SORT_FIELD.test(field)) return null;\n return sort?.dir === DataSourceSortDir.Desc ? `-${field}` : field;\n}\n\nexport interface DataSourceRequestSpec {\n contentType?: string;\n limit?: number;\n /** A query source's order (CC2). Absent = the backend's natural order, as before. */\n sort?: DataSourceSort;\n filterByRouteParam?: DataSourceRouteFilter;\n filterByQueryParams?: DataSourceQueryFilter[];\n}\n\n/** Structural (duck-typed) view of an IoxDataSource — kept free of the editor model so both libs\n * can depend on it without pulling in iox-builder. */\nexport interface DataSourceSpec {\n alias?: string;\n type?: string; // 'cms' | 'shop' | 'external-api' | 'static'\n mode?: DataSourceMode;\n request?: {\n cms?: DataSourceRequestSpec;\n shop?: DataSourceRequestSpec;\n endpoint?: string;\n data?: any;\n /** `derived` alias: a VIEW into another variable — resolve `from` then getByPath(value, path)\n * (e.g. galleryImages = product.gallery). See repeater-binding-ux-plan.md. */\n derived?: { from: string; path: string };\n };\n}\n\nexport type DataSourcePlan =\n | { kind: 'by-id'; domain: DataSourceDomain; resource: string; id: string; mode: DataSourceMode }\n | { kind: 'collection'; domain: DataSourceDomain; resource: string; filter: Record<string, any>; limit?: number; sort?: string; mode: DataSourceMode }\n | { kind: 'external'; endpoint: string; mode: DataSourceMode }\n | { kind: 'static'; data: any; mode: DataSourceMode }\n | { kind: 'derived'; from: string; path: string; mode: DataSourceMode }\n | { kind: 'none' };\n\n/**\n * Match a page route template against an actual path and extract named params.\n * matchRouteParams('/project/:projectId', 'project/68abc') → { projectId: '68abc' }\n * Both sides are normalised (leading slash stripped) and split on '/'; values are URL-decoded.\n */\nexport function matchRouteParams(template: string | undefined, actual: string | undefined): Record<string, string> {\n const t = (template ?? '').replace(/^\\/+/, '').split('/');\n const a = (actual ?? '').replace(/^\\/+/, '').split('/');\n const params: Record<string, string> = {};\n for (let i = 0; i < t.length; i++) {\n const seg = t[i];\n if (seg && seg.startsWith(':')) params[seg.slice(1)] = decodeURIComponent(a[i] ?? '');\n }\n return params;\n}\n\n/**\n * Build the query filter for a data-source request from its filterByRouteParam / filterByQueryParams\n * config. `field` is filtered by the route-param value (falling back to `defaultId`); query filters\n * map each `field` to a query-param value (falling back to `defaultValue`), and a filter with NO\n * `queryParam` is a constant on `defaultValue`. Empty values are omitted so the source isn't\n * accidentally filtered to nothing.\n */\nexport function buildDataSourceFilter(\n req: DataSourceRequestSpec | undefined,\n routeParams: Record<string, any> = {},\n queryParams: Record<string, any> = {},\n): Record<string, any> {\n const params: Record<string, any> = {};\n const byRoute = req?.filterByRouteParam;\n if (byRoute?.field && byRoute?.routeParam) {\n const value = routeParams[byRoute.routeParam] ?? byRoute.defaultId;\n if (value !== undefined && value !== null && value !== '') params[byRoute.field] = value;\n }\n for (const q of req?.filterByQueryParams ?? []) {\n if (!q?.field) continue;\n // No queryParam → a CONSTANT filter: defaultValue is the value, in the builder preview and\n // on the live site alike. With a queryParam the URL wins and defaultValue is the fallback.\n const value = q.queryParam ? (queryParams[q.queryParam] ?? q.defaultValue) : q.defaultValue;\n if (value !== undefined && value !== null && value !== '') params[q.field] = value;\n }\n return params;\n}\n\n/**\n * Mirrors the builder's getContentById decision. When a SINGLE source is filtered by the item id\n * (`_id`) via a route param, the item is fetched by PATH (`.../:type/:id`), not a `?_id=` query.\n * Returns that id (route-param value, else `defaultId`), or `null` when the source isn't a\n * single-by-id fetch — in which case the caller uses the collection endpoint + filters. The\n * path-based form only applies to the id field; a single source filtered by any other field still\n * goes through the collection endpoint.\n */\nexport function resolveSingleItemId(req: DataSourceRequestSpec | undefined, routeParams: Record<string, any> = {}): string | null {\n const byRoute = req?.filterByRouteParam;\n if (!byRoute?.routeParam) return null;\n if (byRoute.field && byRoute.field !== '_id') return null;\n const value = routeParams[byRoute.routeParam] ?? byRoute.defaultId;\n return value !== undefined && value !== null && value !== '' ? String(value) : null;\n}\n\n/**\n * Resolve an IoxDataSource into a transport-agnostic {@link DataSourcePlan}. This is the shared\n * decision both the builder and the SSR engine run; each then executes the plan with its own\n * HTTP layer. Single sources with a route-param id resolve to a `by-id` (path) fetch — exactly like\n * the builder's getContentById.\n */\nexport function planDataSource(\n ds: DataSourceSpec | undefined,\n routeParams: Record<string, any> = {},\n queryParams: Record<string, any> = {},\n): DataSourcePlan {\n const mode: DataSourceMode = ds?.mode === 'single' ? 'single' : 'list';\n switch (ds?.type) {\n case 'cms':\n case 'shop': {\n const domain = ds.type as DataSourceDomain;\n const req = domain === 'cms' ? ds.request?.cms : ds.request?.shop;\n const resource = req?.contentType;\n if (!resource) return { kind: 'none' };\n const id = mode === 'single' ? resolveSingleItemId(req, routeParams) : null;\n if (id) return { kind: 'by-id', domain, resource, id, mode };\n const filter = buildDataSourceFilter(req, routeParams, queryParams);\n const plan: DataSourcePlan = { kind: 'collection', domain, resource, filter, mode };\n if (req?.limit) (plan as any).limit = req.limit;\n const sort = sortParamValue(req?.sort);\n if (sort) (plan as any).sort = sort;\n return plan;\n }\n case 'external-api': {\n const endpoint = ds.request?.endpoint;\n return endpoint ? { kind: 'external', endpoint, mode } : { kind: 'none' };\n }\n case 'static':\n return { kind: 'static', data: ds.request?.data ?? null, mode };\n case 'derived': {\n const d = ds.request?.derived;\n return d?.from ? { kind: 'derived', from: d.from, path: d.path ?? '', mode } : { kind: 'none' };\n }\n case 'variable':\n // A VARIABLE is a page-local resource whose value is WRITTEN at runtime by an interaction\n // (setContext), never fetched. It resolves to nothing here on purpose: the page renders\n // its empty state until something writes it. `request.variable.itemsFrom` only names the\n // source whose items it holds, so the bind picker can suggest fields.\n return { kind: 'none' };\n default:\n return { kind: 'none' };\n }\n}\n\n/**\n * The aliases on a page that are VARIABLES — runtime-written resources, not fetched data.\n *\n * Shared so the builder and the SSR engine agree on which aliases start empty and are filled by\n * interactions. See element-context-state-plan.md.\n */\nexport function variableAliases(dataSources: readonly { alias?: string; type?: string }[] | undefined): Set<string> {\n const out = new Set<string>();\n for (const ds of dataSources ?? []) {\n if (ds?.type === 'variable' && ds.alias) out.add(ds.alias);\n }\n return out;\n}\n\n/** The source alias whose items a variable holds, if it declares one (for field suggestions). */\nexport function variableItemsFrom(\n dataSources: readonly { alias?: string; type?: string; request?: any }[] | undefined,\n alias: string,\n): string | undefined {\n for (const ds of dataSources ?? []) {\n if (ds?.type === 'variable' && ds.alias === alias) return ds.request?.variable?.itemsFrom || undefined;\n }\n return undefined;\n}\n\n/**\n * Normalise a collection response into the shape a source expects: unwraps `{items}`/`{data}`\n * envelopes, then returns the first item for a `single` source or the whole array for a `list`.\n * A `by-id` response is already the item, so callers pass it through unchanged.\n */\nexport function normalizeCollectionResult(mode: DataSourceMode, data: any): any {\n const items = Array.isArray(data) ? data : (data?.items ?? data?.data ?? []);\n return mode === 'single' ? (items[0] ?? null) : items;\n}\n\n/** Navigate a dot/bracket path into an object, e.g. `gallery`, `details.images`, `variants[0].skus`.\n * Returns undefined if any segment is missing. Kept minimal + self-contained so it is shareable by\n * both the builder and the SSR engine (the two have divergent local `resolvePath` copies). */\nexport function getByPath(obj: any, path: string): any {\n if (obj == null || !path) return obj;\n // Turn `a[0].b` into ['a','0','b'].\n const segments = path.replace(/\\[(\\w+)\\]/g, '.$1').split('.').filter(Boolean);\n let cur = obj;\n for (const seg of segments) {\n if (cur == null) return undefined;\n cur = cur[seg];\n }\n return cur;\n}\n\n/**\n * The array a Repeater (or a repeating Slider) iterates over — the SHARED decision so the builder\n * canvas and the SSR engine agree (see data-source-resolving.md).\n * - No `sourcePath`: the resolved source IS the list. An array passes through; a lone object degrades\n * to a single-row list (back-compat with the old `fetchItems`); null/undefined → `[]`.\n * - With `sourcePath`: the source resolved to a SINGLE resource (e.g. a product); dive into the\n * nested array property (e.g. `gallery`). Non-array / missing → `[]`.\n */\n/**\n * Fill in `derived` aliases in an already-resolved map. Given the data sources and a map of resolved\n * NON-derived values (by alias), compute each `type:'derived'` alias as `getByPath(parent, path)` in\n * dependency order (chains resolved by iterating until stable). Mutates + returns the map. Used by the\n * SSR engine after it resolves the root sources; the builder resolves derived lazily per alias.\n */\nexport function resolveDerivedInto(dataSources: DataSourceSpec[], resolved: Record<string, any>): Record<string, any> {\n const derived = (dataSources ?? []).filter(d => d?.type === 'derived' && !!d.alias);\n let changed = true, guard = 0;\n while (changed && guard++ < 25) {\n changed = false;\n for (const d of derived) {\n if (d.alias! in resolved) continue;\n const from = d.request?.derived?.from;\n if (from && from in resolved) {\n resolved[d.alias!] = getByPath(resolved[from], d.request!.derived!.path ?? '');\n changed = true;\n }\n }\n }\n return resolved;\n}\n\nexport function resolveRepeaterItems(resolved: any, sourcePath?: string | null): any[] {\n if (sourcePath) {\n const nested = getByPath(resolved, sourcePath);\n return Array.isArray(nested) ? nested : [];\n }\n if (Array.isArray(resolved)) return resolved;\n return resolved == null ? [] : [resolved];\n}\n\n// ── Referenced data sources (shared: builder-save + SSR resolve) ─────────────────────────────────\n// Which data-source aliases a node tree uses, so a consumer resolves ONLY what a page/global needs\n// (not the whole org library). Used by cms-client (persist globals' sources) and the SSR engine\n// (resolve the referenced subset per page). See org-data-sources-plan.md.\n\n/** Aliases referenced anywhere in a node tree: a repeater/slider `source` (a `bindings` entry with\n * `prop === 'source'`, or `props.source` for the slider) plus every binding's `source`. */\nexport function collectReferencedAliases(nodes: any[], acc: Set<string> = new Set<string>()): Set<string> {\n for (const n of nodes ?? []) {\n for (const b of n?.bindings ?? []) { if (b?.source) acc.add(b.source); }\n if (n?.props?.source) acc.add(n.props.source);\n if (n?.children?.length) collectReferencedAliases(n.children, acc);\n }\n return acc;\n}\n\n/** The subset of `all` whose `alias` is referenced by `nodes` (shallow — direct references only). */\nexport function referencedDataSources(nodes: any[], all: any[]): any[] {\n const aliases = collectReferencedAliases(nodes);\n return (all ?? []).filter(ds => aliases.has(ds?.alias));\n}\n\n/** Like `referencedDataSources`, but also pulls in a referenced source's DEPENDENCIES (transitively),\n * none of which the layout names directly:\n * - a `derived` alias needs its `request.derived.from` parent;\n * - a `relative` (route-context) alias needs its `request.relative.subjectFrom` (the current item)\n * and `request.relative.from` (the collection it navigates).\n * Use this when RESOLVING (the engine) so derived views and siblings/related don't resolve to nothing. */\nexport function referencedDataSourcesDeep(nodes: any[], all: any[]): any[] {\n const byAlias = new Map((all ?? []).map(d => [d?.alias, d]));\n const wanted = collectReferencedAliases(nodes);\n const queue = [...wanted];\n const enqueue = (alias?: string) => {\n if (alias && !wanted.has(alias)) { wanted.add(alias); queue.push(alias); }\n };\n while (queue.length) {\n const ds: any = byAlias.get(queue.pop() as string);\n enqueue(ds?.request?.derived?.from);\n enqueue(ds?.request?.relative?.subjectFrom);\n enqueue(ds?.request?.relative?.from);\n }\n return (all ?? []).filter(d => wanted.has(d?.alias));\n}\n\n/** Base URLs for the PUBLIC content controllers. Both must end with `/`. The builder and the SSR\n * engine pass their own host (they proxy `/api` differently) but MUST target the public\n * (`@Public()`) controllers, never the authed admin ones — data resolving is storefront data. */\nexport interface DataSourceEndpoints {\n /** cms resources, e.g. `${HOST}` (engine, HOST ends `/api/`) or `${HOST}api/` (builder). */\n cmsBase: string;\n /** shop resources, e.g. `${HOST}shop/` (engine) or `${HOST}api/shop/` (builder). */\n shopBase: string;\n}\n\nexport interface DataSourceRequest {\n url: string;\n params: Record<string, any>;\n}\n\n/**\n * Turn a resolved {@link DataSourcePlan} into the concrete public HTTP request (url + params) — the\n * SINGLE place that decides the URL shape, shared by the builder binding preview and the SSR engine\n * so both hit the SAME public endpoints (no admin, no duplication). Returns `null` for `static` /\n * `none` plans (no HTTP call). A single item is a PATH segment (`/:resource/:id`), never `?_id=`.\n */\nexport function dataSourcePlanToRequest(plan: DataSourcePlan, endpoints: DataSourceEndpoints): DataSourceRequest | null {\n const baseFor = (domain: DataSourceDomain) => (domain === 'shop' ? endpoints.shopBase : endpoints.cmsBase);\n switch (plan.kind) {\n case 'by-id':\n return { url: `${baseFor(plan.domain)}${plan.resource}/${encodeURIComponent(plan.id)}`, params: {} };\n case 'collection': {\n const params: Record<string, any> = { ...plan.filter };\n if (plan.limit) params['limit'] = plan.limit;\n if (plan.sort) params[SORT_PARAM] = plan.sort;\n return { url: `${baseFor(plan.domain)}${plan.resource}`, params };\n }\n case 'external':\n return { url: plan.endpoint, params: {} };\n default:\n return null;\n }\n}\n","/**\n * Shared RELATIVE (route-context) data resolving — the SINGLE source of truth for how a detail page\n * derives values FROM its current route item, consumed by BOTH the page builder (iox-cms-client) and\n * the SSR client engine (iox-client-engine). Companion to `data-source-resolver.ts`.\n *\n * These are the pure, transport-agnostic pieces behind the Route binding scope\n * (`$route` / `$current` / `$siblings` / `$related`). Resolution is a DEPENDENT SECOND PASS: the\n * consumer resolves `$current` first (the existing single by-id fetch via `planDataSource`), then\n * calls these to derive the relative values — there is NO new `DataSourcePlan` kind (`$related` still\n * feeds a normal `collection` plan built from {@link buildRelatedFilter}). See\n * `architecture/builder/relative-data-sources-plan.md`.\n *\n * There must be no second copy of this decision logic; only the transport (which HTTP layer fetches\n * the ordered collection / related list) differs between the two consumers.\n */\n\nimport { getByPath } from './data-source-resolver';\n\n/** Aliases in the Route scope are platform-owned and start with this prefix; a USER alias may never\n * start with it, which keeps the Route scope structurally collision-proof with Local/Global. Used to\n * validate new/renamed aliases and to render the reserved (read-only) item state in the bind picker. */\nexport const RESERVED_ALIAS_PREFIX = '$';\n\n/** True for a platform-owned Route-scope alias (`$route`, `$current`, `$siblings`, `$related`, …). */\nexport function isReservedAlias(alias: string | null | undefined): boolean {\n return typeof alias === 'string' && alias.startsWith(RESERVED_ALIAS_PREFIX);\n}\n\n/** The `$siblings` value: the current item's neighbours within an ordered set. */\nexport interface SiblingsResult<T = any> {\n /** The item before the current one (`null` at the start, unless `wrap`). */\n prev: T | null;\n /** The item after the current one (`null` at the end, unless `wrap`). */\n next: T | null;\n /** Zero-based position of the current item in the ordered set; `-1` when not found. */\n index: number;\n /** Total items in the set. */\n count: number;\n /** `true` when the current item is the first in the set. */\n isFirst: boolean;\n /** `true` when the current item is the last in the set. */\n isLast: boolean;\n}\n\nexport interface ResolveSiblingsOptions {\n /** Field identifying the current item within the list. Default `_id`. */\n idField?: string;\n /** Dot/bracket path to sort by BEFORE locating neighbours (e.g. `order`, `date`). Omit to use the\n * list's existing order. The same item neighbours differently per ordering, so this is the one\n * choice `$siblings` requires. */\n sortBy?: string;\n /** Sort direction when `sortBy` is set. Default `asc`. */\n sortDir?: 'asc' | 'desc';\n /** Wrap around the ends (`last.next` → first, `first.prev` → last). Only applies when `count > 1`.\n * Default `false`. */\n wrap?: boolean;\n}\n\nconst EMPTY_SIBLINGS: SiblingsResult = { prev: null, next: null, index: -1, count: 0, isFirst: false, isLast: false };\n\n/** Generic, deterministic comparator: numbers numerically, Dates by time, everything else by\n * locale-aware string compare (ISO date strings sort correctly this way). Nullish sorts first. */\nfunction compareValues(a: any, b: any): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === 'number' && typeof b === 'number') return a - b;\n const da = a instanceof Date ? a.getTime() : NaN;\n const db = b instanceof Date ? b.getTime() : NaN;\n if (!isNaN(da) && !isNaN(db)) return da - db;\n return String(a).localeCompare(String(b));\n}\n\n/**\n * Locate the current item within an ordered set and return its neighbours + position. Pure: the input\n * list is never mutated (sorting works on a shallow copy). `currentId` is compared to each item's\n * `idField` via string equality (route-param values are strings). When the current id isn't found,\n * `index` is `-1` and `prev`/`next` are `null` (but `count` still reflects the set size).\n */\nexport function resolveSiblings<T = any>(\n items: T[] | null | undefined,\n currentId: string | number | null | undefined,\n options: ResolveSiblingsOptions = {},\n): SiblingsResult<T> {\n const list = Array.isArray(items) ? items.slice() : [];\n const count = list.length;\n if (count === 0) return { ...EMPTY_SIBLINGS };\n\n const idField = options.idField ?? '_id';\n if (options.sortBy) {\n const dir = options.sortDir === 'desc' ? -1 : 1;\n list.sort((a, b) => dir * compareValues(getByPath(a, options.sortBy!), getByPath(b, options.sortBy!)));\n }\n\n const cid = currentId == null ? null : String(currentId);\n const index = cid == null ? -1 : list.findIndex(it => String(getByPath(it, idField)) === cid);\n if (index === -1) return { prev: null, next: null, index: -1, count, isFirst: false, isLast: false };\n\n const canWrap = !!options.wrap && count > 1;\n const prev = index > 0 ? list[index - 1] : (canWrap ? list[count - 1] : null);\n const next = index < count - 1 ? list[index + 1] : (canWrap ? list[0] : null);\n return { prev, next, index, count, isFirst: index === 0, isLast: index === count - 1 };\n}\n\nexport interface BuildRelatedFilterOptions {\n /** Field(s) on the current item whose value(s) the related items must match (e.g. `category`,\n * or `['category', 'brand']`). Each contributes an equality filter on that field. */\n matchFields: string | string[];\n}\n\n/** A value usable as an equality filter param: a non-empty scalar. Objects/arrays/nullish are skipped\n * so a related query isn't built from an unresolved rel object (surfaced as a config issue in the UI\n * rather than silently matching everything). Multi-value match is a v2 concern. */\nfunction isScalarFilterValue(v: any): boolean {\n if (v == null) return false;\n if (typeof v === 'string') return v !== '';\n if (typeof v === 'number') return Number.isFinite(v);\n return typeof v === 'boolean';\n}\n\n/**\n * Build the collection filter for `$related` from the resolved current item: an equality match on\n * each `matchField`'s value (e.g. same `category`). Feeds a normal `collection` plan — no new plan\n * kind. Fields whose value is not a scalar (or is empty) are omitted, matching\n * `buildDataSourceFilter`'s \"don't over-filter\" rule. Exclude-self is applied to the RESULT via\n * {@link excludeSelf} (the public query controllers are equality-only, so it can't be a `$ne` param).\n */\nexport function buildRelatedFilter(current: any, options: BuildRelatedFilterOptions): Record<string, any> {\n const fields = Array.isArray(options.matchFields) ? options.matchFields : [options.matchFields];\n const filter: Record<string, any> = {};\n for (const field of fields) {\n if (!field) continue;\n const value = getByPath(current, field);\n if (isScalarFilterValue(value)) filter[field] = value;\n }\n return filter;\n}\n\n/**\n * Drop the current item from a list by `idField` (default `_id`). Used to exclude self from a\n * `$related` result after fetch. A nullish/empty `currentId` leaves the list unchanged.\n */\nexport function excludeSelf<T = any>(items: T[] | null | undefined, currentId: string | number | null | undefined, idField = '_id'): T[] {\n if (!Array.isArray(items)) return [];\n if (currentId == null || currentId === '') return items;\n const cid = String(currentId);\n return items.filter(it => String(getByPath(it, idField)) !== cid);\n}\n\n/** The resolved Route-scope context bound values read from (`$route.projectId`, `$current.title`,\n * `$siblings.next`, `$related`). Assembled by each consumer during its dependent second pass. */\nexport interface RouteContext {\n /** Route path params for the current URL (from `matchRouteParams`). */\n $route: Record<string, string>;\n /** The resolved current item (the page's single by-id source). */\n $current: any;\n /** Neighbours of the current item within a chosen ordered set. */\n $siblings?: SiblingsResult;\n /** Items related to the current one (same-attribute), excluding self. */\n $related?: any[];\n}\n\n// ── Relative source declaration + the shared second-pass resolver ────────────────────────────────\n// A relative source is declared like a `derived` one (`type:'relative'`), and resolved AFTER the\n// root/derived sources by a PURE pass over the resolved map — mirroring `resolveDerivedInto`, so the\n// SSR engine and the builder canvas agree with no forked logic. `planDataSource` returns `none` for\n// it (not fetched); each consumer excludes `relative` from its fetched roots and runs this pass.\n\nexport type RelativeSourceKind = 'route' | 'current' | 'siblings' | 'related';\n\nexport interface RelativeSourceSpec {\n kind: RelativeSourceKind;\n /** The source alias this derives from: for `current` the subject single-source; for\n * `siblings`/`related` the COLLECTION alias to locate within / search. Unused for `route`. */\n from?: string;\n /** `siblings`/`related`: the SUBJECT source alias (the current item) whose id / field values drive\n * the computation. Defaults to `from` when omitted. May reference `$current`. */\n subjectFrom?: string;\n /** Field identifying an item. Default `_id`. */\n idField?: string;\n /** `siblings`: sort the set before locating neighbours. */\n sortBy?: string;\n sortDir?: 'asc' | 'desc';\n wrap?: boolean;\n /** `siblings`: resolve to a SINGLE neighbour ITEM (`next`/`prev`) instead of the whole\n * `{prev,next,index,count,…}` object — so a source can be named e.g. `nextProject` and bound\n * directly (`nextProject.route`). Omit for the whole-object shape. */\n pick?: 'next' | 'prev';\n /** `related`: field(s) on the subject the results must match. */\n matchFields?: string | string[];\n /** `related`: drop the subject from its own results. Default `true`. */\n excludeSelf?: boolean;\n}\n\n/** Duck-typed view of a data source carrying an optional relative descriptor (avoids a hard dep on\n * the full editor model / a circular type import). */\ninterface RelativeCapableSource {\n alias?: string;\n type?: string;\n request?: { relative?: RelativeSourceSpec };\n}\n\n/** The already-resolved inputs a single relative source computes from. Assembled by each consumer:\n * the batch pass reads them from the resolved map; the builder resolves them async per alias. */\nexport interface RelativeInputs {\n /** The subject item (the current resource) — for `current`/`siblings`/`related`. */\n subject?: any;\n /** The collection to locate within / search — for `siblings`/`related`. */\n collection?: any;\n /** Route path params — for `route`. */\n routeParams?: Record<string, any>;\n}\n\n/** Resolve kinds in a fixed order so a `siblings`/`related` source may reference `$current` (or any\n * earlier relative alias) via `subjectFrom` and still see it resolved. */\nconst KIND_ORDER: Record<RelativeSourceKind, number> = { route: 0, current: 1, siblings: 2, related: 3 };\n\n/** Does an item satisfy every field in an equality predicate map (string-compared)? */\nfunction matchesFilter(item: any, filter: Record<string, any>): boolean {\n return Object.keys(filter).every(k => String(getByPath(item, k)) === String(filter[k]));\n}\n\n/**\n * Compute ONE relative source's value from its already-resolved {@link RelativeInputs} — the SHARED\n * kind→computation decision, so the batch pass ({@link resolveRelativeInto}) and the builder's async\n * per-alias resolver produce identical results with no forked switch. `route` → the route params;\n * `current` → the subject; `siblings` → {@link resolveSiblings} over the collection; `related` → the\n * collection filtered by {@link buildRelatedFilter}'s predicate (+ {@link excludeSelf}).\n */\nexport function computeRelative(spec: RelativeSourceSpec, inputs: RelativeInputs): any {\n const idField = spec.idField ?? '_id';\n switch (spec.kind) {\n case 'route':\n return inputs.routeParams ?? {};\n case 'current':\n return inputs.subject ?? null;\n case 'siblings': {\n const siblings = resolveSiblings(inputs.collection, getByPath(inputs.subject, idField), {\n idField, sortBy: spec.sortBy, sortDir: spec.sortDir, wrap: spec.wrap,\n });\n // `pick` resolves the source directly to that neighbour item (named-source shape,\n // e.g. `nextProject`); without it the caller binds a sub-path of the whole object.\n return spec.pick ? siblings[spec.pick] : siblings;\n }\n case 'related': {\n const filter = buildRelatedFilter(inputs.subject, { matchFields: spec.matchFields ?? [] });\n const pool = Array.isArray(inputs.collection) ? inputs.collection : [];\n const items = pool.filter(it => matchesFilter(it, filter));\n return spec.excludeSelf !== false ? excludeSelf(items, getByPath(inputs.subject, idField), idField) : items;\n }\n default:\n return null;\n }\n}\n\n/**\n * Compute every `type:'relative'` source into `resolved` (keyed by alias), IN PLACE. Pure w.r.t. HTTP\n * — it only reads already-resolved roots + the route params, so both consumers run it identically as\n * a second pass. Delegates each source to {@link computeRelative}. `current`'s subject is `from`;\n * `siblings`/`related` take `collection` = `from` and `subject` = `subjectFrom ?? from`. Mutates and\n * returns `resolved`.\n */\nexport function resolveRelativeInto(\n dataSources: RelativeCapableSource[],\n resolved: Record<string, any>,\n routeParams: Record<string, any> = {},\n): Record<string, any> {\n const relatives = (dataSources ?? [])\n .filter(d => d?.type === 'relative' && !!d.alias && !!d.request?.relative)\n .sort((a, b) => KIND_ORDER[a.request!.relative!.kind] - KIND_ORDER[b.request!.relative!.kind]);\n\n for (const d of relatives) {\n const spec = d.request!.relative!;\n const subjectAlias = spec.kind === 'current' ? spec.from : (spec.subjectFrom ?? spec.from);\n resolved[d.alias!] = computeRelative(spec, {\n subject: subjectAlias ? resolved[subjectAlias] : undefined,\n collection: spec.from ? resolved[spec.from] : undefined,\n routeParams,\n });\n }\n return resolved;\n}\n","/**\n * @vectoriox/iox-ui/data-core — the shared, FRAMEWORK-FREE data-source core (data-source-resolving.md): the ONE\n * decision of how a data source becomes a fetch (`planDataSource`), the route-param matcher, the derived /\n * relative (`$siblings`, `$related`) resolvers, the repeater item reader, and which sources a node tree\n * references (`referencedDataSourcesDeep`). The builder and the SSR engine resolve with it; iox-cms reads it\n * under plain Node (W2's Undo: is a data source still in use). Pure: no transport, no Angular. The main\n * entry re-exports it by package path, so `@vectoriox/iox-ui` keeps working.\n * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig paths —\n * or `nest start` dies with ERR_REQUIRE_ESM while jest and build both pass.\n */\nexport * from './data-source-resolver';\nexport * from './relative-sources';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;AAiBG;AAsBH;IACY;AAAZ,CAAA,UAAY,iBAAiB,EAAA;AACzB,IAAA,iBAAA,CAAA,KAAA,CAAA,GAAA,KAAW;AACX,IAAA,iBAAA,CAAA,MAAA,CAAA,GAAA,MAAa;AACjB,CAAC,EAHW,iBAAiB,KAAjB,iBAAiB,GAAA,EAAA,CAAA,CAAA;AAW7B;AACuG;AAChG,MAAM,UAAU,GAAG;AAC1B,MAAM,UAAU,GAAG,qDAAqD;AAExE;AACM,SAAU,cAAc,CAAC,IAAuC,EAAA;IAClE,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IACjC,IAAI,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,IAAI;AAClD,IAAA,OAAO,IAAI,EAAE,GAAG,KAAK,iBAAiB,CAAC,IAAI,GAAG,IAAI,KAAK,CAAA,CAAE,GAAG,KAAK;AACrE;AAoCA;;;;AAIG;AACG,SAAU,gBAAgB,CAAC,QAA4B,EAAE,MAA0B,EAAA;AACrF,IAAA,MAAM,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;AACzD,IAAA,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;IACvD,MAAM,MAAM,GAA2B,EAAE;AACzC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AAC/B,QAAA,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAChB,QAAA,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;AAAE,YAAA,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzF;AACA,IAAA,OAAO,MAAM;AACjB;AAEA;;;;;;AAMG;AACG,SAAU,qBAAqB,CACjC,GAAsC,EACtC,WAAA,GAAmC,EAAE,EACrC,WAAA,GAAmC,EAAE,EAAA;IAErC,MAAM,MAAM,GAAwB,EAAE;AACtC,IAAA,MAAM,OAAO,GAAG,GAAG,EAAE,kBAAkB;IACvC,IAAI,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,UAAU,EAAE;AACvC,QAAA,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,SAAS;QAClE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;AAAE,YAAA,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK;IAC5F;IACA,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,mBAAmB,IAAI,EAAE,EAAE;QAC5C,IAAI,CAAC,CAAC,EAAE,KAAK;YAAE;;;QAGf,MAAM,KAAK,GAAG,CAAC,CAAC,UAAU,IAAI,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,YAAY;QAC3F,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;AAAE,YAAA,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,KAAK;IACtF;AACA,IAAA,OAAO,MAAM;AACjB;AAEA;;;;;;;AAOG;SACa,mBAAmB,CAAC,GAAsC,EAAE,cAAmC,EAAE,EAAA;AAC7G,IAAA,MAAM,OAAO,GAAG,GAAG,EAAE,kBAAkB;IACvC,IAAI,CAAC,OAAO,EAAE,UAAU;AAAE,QAAA,OAAO,IAAI;IACrC,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK;AAAE,QAAA,OAAO,IAAI;AACzD,IAAA,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,SAAS;IAClE,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI;AACvF;AAEA;;;;;AAKG;AACG,SAAU,cAAc,CAC1B,EAA8B,EAC9B,WAAA,GAAmC,EAAE,EACrC,WAAA,GAAmC,EAAE,EAAA;AAErC,IAAA,MAAM,IAAI,GAAmB,EAAE,EAAE,IAAI,KAAK,QAAQ,GAAG,QAAQ,GAAG,MAAM;AACtE,IAAA,QAAQ,EAAE,EAAE,IAAI;AACZ,QAAA,KAAK,KAAK;QACV,KAAK,MAAM,EAAE;AACT,YAAA,MAAM,MAAM,GAAG,EAAE,CAAC,IAAwB;YAC1C,MAAM,GAAG,GAAG,MAAM,KAAK,KAAK,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI;AACjE,YAAA,MAAM,QAAQ,GAAG,GAAG,EAAE,WAAW;AACjC,YAAA,IAAI,CAAC,QAAQ;AAAE,gBAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE;AACtC,YAAA,MAAM,EAAE,GAAG,IAAI,KAAK,QAAQ,GAAG,mBAAmB,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,IAAI;AAC3E,YAAA,IAAI,EAAE;AAAE,gBAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE;YAC5D,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,WAAW,CAAC;AACnE,YAAA,MAAM,IAAI,GAAmB,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE;YACnF,IAAI,GAAG,EAAE,KAAK;AAAG,gBAAA,IAAY,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK;YAC/C,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC;AACtC,YAAA,IAAI,IAAI;AAAG,gBAAA,IAAY,CAAC,IAAI,GAAG,IAAI;AACnC,YAAA,OAAO,IAAI;QACf;QACA,KAAK,cAAc,EAAE;AACjB,YAAA,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ;YACrC,OAAO,QAAQ,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE;QAC7E;AACA,QAAA,KAAK,QAAQ;AACT,YAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,EAAE,IAAI,IAAI,IAAI,EAAE,IAAI,EAAE;QACnE,KAAK,SAAS,EAAE;AACZ,YAAA,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO;AAC7B,YAAA,OAAO,CAAC,EAAE,IAAI,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE;QACnG;AACA,QAAA,KAAK,UAAU;;;;;AAKX,YAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE;AAC3B,QAAA;AACI,YAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE;;AAEnC;AAEA;;;;;AAKG;AACG,SAAU,eAAe,CAAC,WAAqE,EAAA;AACjG,IAAA,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU;AAC7B,IAAA,KAAK,MAAM,EAAE,IAAI,WAAW,IAAI,EAAE,EAAE;QAChC,IAAI,EAAE,EAAE,IAAI,KAAK,UAAU,IAAI,EAAE,CAAC,KAAK;AAAE,YAAA,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC;IAC9D;AACA,IAAA,OAAO,GAAG;AACd;AAEA;AACM,SAAU,iBAAiB,CAC7B,WAAoF,EACpF,KAAa,EAAA;AAEb,IAAA,KAAK,MAAM,EAAE,IAAI,WAAW,IAAI,EAAE,EAAE;QAChC,IAAI,EAAE,EAAE,IAAI,KAAK,UAAU,IAAI,EAAE,CAAC,KAAK,KAAK,KAAK;YAAE,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,IAAI,SAAS;IAC1G;AACA,IAAA,OAAO,SAAS;AACpB;AAEA;;;;AAIG;AACG,SAAU,yBAAyB,CAAC,IAAoB,EAAE,IAAS,EAAA;IACrE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;AAC5E,IAAA,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,KAAK;AACzD;AAEA;;AAE+F;AACzF,SAAU,SAAS,CAAC,GAAQ,EAAE,IAAY,EAAA;AAC5C,IAAA,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI;AAAE,QAAA,OAAO,GAAG;;IAEpC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IAC7E,IAAI,GAAG,GAAG,GAAG;AACb,IAAA,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE;QACxB,IAAI,GAAG,IAAI,IAAI;AAAE,YAAA,OAAO,SAAS;AACjC,QAAA,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IAClB;AACA,IAAA,OAAO,GAAG;AACd;AAEA;;;;;;;AAOG;AACH;;;;;AAKG;AACG,SAAU,kBAAkB,CAAC,WAA6B,EAAE,QAA6B,EAAA;IAC3F,MAAM,OAAO,GAAG,CAAC,WAAW,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACnF,IAAA,IAAI,OAAO,GAAG,IAAI,EAAE,KAAK,GAAG,CAAC;AAC7B,IAAA,OAAO,OAAO,IAAI,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5B,OAAO,GAAG,KAAK;AACf,QAAA,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE;AACrB,YAAA,IAAI,CAAC,CAAC,KAAM,IAAI,QAAQ;gBAAE;YAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI;AACrC,YAAA,IAAI,IAAI,IAAI,IAAI,IAAI,QAAQ,EAAE;gBAC1B,QAAQ,CAAC,CAAC,CAAC,KAAM,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAQ,CAAC,OAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC9E,OAAO,GAAG,IAAI;YAClB;QACJ;IACJ;AACA,IAAA,OAAO,QAAQ;AACnB;AAEM,SAAU,oBAAoB,CAAC,QAAa,EAAE,UAA0B,EAAA;IAC1E,IAAI,UAAU,EAAE;QACZ,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC;AAC9C,QAAA,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,EAAE;IAC9C;AACA,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;AAAE,QAAA,OAAO,QAAQ;AAC5C,IAAA,OAAO,QAAQ,IAAI,IAAI,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC;AAC7C;AAEA;AACA;AACA;AACA;AAEA;AAC4F;AACtF,SAAU,wBAAwB,CAAC,KAAY,EAAE,GAAA,GAAmB,IAAI,GAAG,EAAU,EAAA;AACvF,IAAA,KAAK,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,EAAE;QACzB,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,IAAI,EAAE,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM;AAAE,gBAAA,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;QAAE;AACvE,QAAA,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM;YAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;AAC7C,QAAA,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM;AAAE,YAAA,wBAAwB,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC;IACtE;AACA,IAAA,OAAO,GAAG;AACd;AAEA;AACM,SAAU,qBAAqB,CAAC,KAAY,EAAE,GAAU,EAAA;AAC1D,IAAA,MAAM,OAAO,GAAG,wBAAwB,CAAC,KAAK,CAAC;IAC/C,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,MAAM,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AAC3D;AAEA;;;;;AAK2G;AACrG,SAAU,yBAAyB,CAAC,KAAY,EAAE,GAAU,EAAA;IAC9D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5D,IAAA,MAAM,MAAM,GAAG,wBAAwB,CAAC,KAAK,CAAC;AAC9C,IAAA,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC;AACzB,IAAA,MAAM,OAAO,GAAG,CAAC,KAAc,KAAI;QAC/B,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;AAAE,YAAA,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;AAAE,YAAA,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE;AAC7E,IAAA,CAAC;AACD,IAAA,OAAO,KAAK,CAAC,MAAM,EAAE;QACjB,MAAM,EAAE,GAAQ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAY,CAAC;QAClD,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;QACnC,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC;QAC3C,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC;IACxC;IACA,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACxD;AAiBA;;;;;AAKG;AACG,SAAU,uBAAuB,CAAC,IAAoB,EAAE,SAA8B,EAAA;IACxF,MAAM,OAAO,GAAG,CAAC,MAAwB,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC;AAC1G,IAAA,QAAQ,IAAI,CAAC,IAAI;AACb,QAAA,KAAK,OAAO;YACR,OAAO,EAAE,GAAG,EAAE,CAAA,EAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA,EAAG,IAAI,CAAC,QAAQ,CAAA,CAAA,EAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA,CAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QACxG,KAAK,YAAY,EAAE;YACf,MAAM,MAAM,GAAwB,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE;YACtD,IAAI,IAAI,CAAC,KAAK;AAAE,gBAAA,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK;YAC5C,IAAI,IAAI,CAAC,IAAI;AAAE,gBAAA,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,IAAI;AAC7C,YAAA,OAAO,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA,EAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE;QACrE;AACA,QAAA,KAAK,UAAU;YACX,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE;AAC7C,QAAA;AACI,YAAA,OAAO,IAAI;;AAEvB;;ACzXA;;;;;;;;;;;;;;AAcG;AAIH;;AAEyG;AAClG,MAAM,qBAAqB,GAAG;AAErC;AACM,SAAU,eAAe,CAAC,KAAgC,EAAA;IAC5D,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,qBAAqB,CAAC;AAC/E;AAgCA,MAAM,cAAc,GAAmB,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;AAErH;AACmG;AACnG,SAAS,aAAa,CAAC,CAAM,EAAE,CAAM,EAAA;AACjC,IAAA,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI;AAAE,QAAA,OAAO,CAAC;IACpC,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,CAAC,CAAC;IACxB,IAAI,CAAC,IAAI,IAAI;AAAE,QAAA,OAAO,CAAC;IACvB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,GAAG,CAAC;AAChE,IAAA,MAAM,EAAE,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,GAAG;AAChD,IAAA,MAAM,EAAE,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,GAAG;IAChD,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE;AAC5C,IAAA,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAC7C;AAEA;;;;;AAKG;AACG,SAAU,eAAe,CAC3B,KAA6B,EAC7B,SAA6C,EAC7C,UAAkC,EAAE,EAAA;AAEpC,IAAA,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE;AACtD,IAAA,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM;IACzB,IAAI,KAAK,KAAK,CAAC;AAAE,QAAA,OAAO,EAAE,GAAG,cAAc,EAAE;AAE7C,IAAA,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,KAAK;AACxC,IAAA,IAAI,OAAO,CAAC,MAAM,EAAE;AAChB,QAAA,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC;AAC/C,QAAA,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,MAAO,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,MAAO,CAAC,CAAC,CAAC;IAC1G;AAEA,IAAA,MAAM,GAAG,GAAG,SAAS,IAAI,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC;AACxD,IAAA,MAAM,KAAK,GAAG,GAAG,IAAI,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC;IAC7F,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;IAEpG,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,KAAK,GAAG,CAAC;AAC3C,IAAA,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC;AAC7E,IAAA,MAAM,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7E,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,KAAK,KAAK,GAAG,CAAC,EAAE;AAC1F;AAQA;;AAEoF;AACpF,SAAS,mBAAmB,CAAC,CAAM,EAAA;IAC/B,IAAI,CAAC,IAAI,IAAI;AAAE,QAAA,OAAO,KAAK;IAC3B,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,KAAK,EAAE;IAC1C,IAAI,OAAO,CAAC,KAAK,QAAQ;AAAE,QAAA,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AACpD,IAAA,OAAO,OAAO,CAAC,KAAK,SAAS;AACjC;AAEA;;;;;;AAMG;AACG,SAAU,kBAAkB,CAAC,OAAY,EAAE,OAAkC,EAAA;IAC/E,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC;IAC/F,MAAM,MAAM,GAAwB,EAAE;AACtC,IAAA,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;AACxB,QAAA,IAAI,CAAC,KAAK;YAAE;QACZ,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC;QACvC,IAAI,mBAAmB,CAAC,KAAK,CAAC;AAAE,YAAA,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK;IACzD;AACA,IAAA,OAAO,MAAM;AACjB;AAEA;;;AAGG;AACG,SAAU,WAAW,CAAU,KAA6B,EAAE,SAA6C,EAAE,OAAO,GAAG,KAAK,EAAA;AAC9H,IAAA,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,EAAE;AACpC,IAAA,IAAI,SAAS,IAAI,IAAI,IAAI,SAAS,KAAK,EAAE;AAAE,QAAA,OAAO,KAAK;AACvD,IAAA,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC;IAC7B,OAAO,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC;AACrE;AAkEA;AAC2E;AAC3E,MAAM,UAAU,GAAuC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;AAExG;AACA,SAAS,aAAa,CAAC,IAAS,EAAE,MAA2B,EAAA;AACzD,IAAA,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3F;AAEA;;;;;;AAMG;AACG,SAAU,eAAe,CAAC,IAAwB,EAAE,MAAsB,EAAA;AAC5E,IAAA,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,KAAK;AACrC,IAAA,QAAQ,IAAI,CAAC,IAAI;AACb,QAAA,KAAK,OAAO;AACR,YAAA,OAAO,MAAM,CAAC,WAAW,IAAI,EAAE;AACnC,QAAA,KAAK,SAAS;AACV,YAAA,OAAO,MAAM,CAAC,OAAO,IAAI,IAAI;QACjC,KAAK,UAAU,EAAE;AACb,YAAA,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE;AACpF,gBAAA,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI;AACvE,aAAA,CAAC;;;AAGF,YAAA,OAAO,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ;QACrD;QACA,KAAK,SAAS,EAAE;AACZ,YAAA,MAAM,MAAM,GAAG,kBAAkB,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;YAC1F,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,UAAU,GAAG,EAAE;AACtE,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,aAAa,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAC1D,OAAO,IAAI,CAAC,WAAW,KAAK,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,GAAG,KAAK;QAC/G;AACA,QAAA;AACI,YAAA,OAAO,IAAI;;AAEvB;AAEA;;;;;;AAMG;AACG,SAAU,mBAAmB,CAC/B,WAAoC,EACpC,QAA6B,EAC7B,cAAmC,EAAE,EAAA;AAErC,IAAA,MAAM,SAAS,GAAG,CAAC,WAAW,IAAI,EAAE;SAC/B,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ;AACxE,SAAA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,OAAQ,CAAC,QAAS,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,OAAQ,CAAC,QAAS,CAAC,IAAI,CAAC,CAAC;AAElG,IAAA,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE;AACvB,QAAA,MAAM,IAAI,GAAG,CAAC,CAAC,OAAQ,CAAC,QAAS;QACjC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,KAAK,SAAS,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC;QAC1F,QAAQ,CAAC,CAAC,CAAC,KAAM,CAAC,GAAG,eAAe,CAAC,IAAI,EAAE;AACvC,YAAA,OAAO,EAAE,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC,GAAG,SAAS;AAC1D,YAAA,UAAU,EAAE,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,SAAS;YACvD,WAAW;AACd,SAAA,CAAC;IACN;AACA,IAAA,OAAO,QAAQ;AACnB;;ACzRA;;;;;;;;;AASG;;ACTH;;AAEG;;;;"}