@aglyn/aglyn 1.0.0-beta.239 → 1.0.0-beta.241
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/package.json +11 -11
- package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
- package/src/lib/app-utils/docs-help.generated.js +1 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +9 -2
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/error-beacon.d.ts +39 -0
- package/src/lib/app-utils/error-beacon.js +66 -9
- package/src/lib/app-utils/error-beacon.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +8 -3
- package/src/lib/app-utils/expand-repeatables.js +46 -17
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/expand-repeatables.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AglynNodeSchema, NodeId } from '../foundation'\nimport {\n applyRepeatQuery,\n parseRepeatFilter,\n parseRepeatSort,\n} from './repeat-query'\n\n/** Namespaces cloned template ids per container/record. */\nexport const REPEAT_NODE_ID_PREFIX = 'rep__'\n\n/**\n * `{{item.field}}` token, plus an optional single reference hop\n * (`{{item.author.name}}`, AGL-180). One hop only — the pattern itself is\n * the depth guard, so cycles can't recurse.\n */\nconst ITEM_TOKEN_PATTERN =\n /\\{\\{\\s*item\\.([A-Za-z][A-Za-z0-9_]*)(?:\\.([A-Za-z][A-Za-z0-9_]*))?\\s*\\}\\}/g\n\n/** Hard bound on rows a single repeatable renders, before `repeatLimit`. */\nexport const REPEAT_MAX_RECORDS = 100\n\n/**\n * What the expansion reads of the rows' field model (AGL-177, AGL-180): which\n * fields are references, each with the key its target's rows are answered\n * under (`datasetsByKey`). The model itself is the plugin's that keeps the\n * rows, and it states this much of it when it hands the rows over\n * (`plugin-manager/repeat-rows`, `repeat-sources`); a field it does not name\n * here hops nowhere, and its token is left as written.\n */\nexport interface RepeatRowsModel {\n /** Reference field id → the key of the rows it points into. */\n references?: Readonly<Record<string, string>>\n}\n\nexport interface RepeatableDataset {\n /**\n * Row value maps, in display order. Rows carry `$id` (needed to resolve\n * incoming references); values may be typed (AGL-177), stringified at\n * substitution.\n */\n records: Array<Record<string, unknown>>\n /** The rows' references; required for reference-hop bindings. */\n model?: RepeatRowsModel\n}\n\ninterface SubstituteContext {\n record: Record<string, unknown>\n /** The repeated rows' references (AGL-180). */\n model?: RepeatRowsModel\n /** All host datasets keyed by id (and name) for hop resolution. */\n datasetsByKey?: Record<string, RepeatableDataset | undefined>\n}\n\nconst displayValue = (value: unknown): string =>\n Array.isArray(value) ? value.join(', ') : String(value)\n\n/** Resolves `{{item.ref.field}}`: FKey(s) → target document field value. */\nfunction resolveReferenceHop(\n context: SubstituteContext,\n fieldId: string,\n targetFieldId: string,\n): string | null {\n const targetKey = context.model?.references?.[fieldId]\n if (!targetKey) return null\n const target = context.datasetsByKey?.[targetKey]\n if (!target) return null\n const keys = context.record[fieldId]\n const ids = Array.isArray(keys) ? keys : keys != null ? [keys] : []\n const resolved = ids\n .map((id) => {\n const match = target.records.find((row) => row['$id'] === id)\n const value = match?.[targetFieldId]\n return value != null ? displayValue(value) : null\n })\n .filter((value): value is string => value != null)\n return resolved.length ? resolved.join(', ') : null\n}\n\nfunction substituteValue(\n value: unknown,\n context: SubstituteContext,\n): unknown {\n const { record } = context\n if (typeof value === 'string') {\n return value.replace(\n ITEM_TOKEN_PATTERN,\n (token, field: string, hop?: string) => {\n if (hop) {\n const resolved = resolveReferenceHop(context, field, hop)\n return resolved != null ? resolved : token\n }\n return record[field] != null ? displayValue(record[field]) : token\n },\n )\n }\n if (Array.isArray(value)) {\n return value.map((entry) => substituteValue(entry, context))\n }\n if (value && typeof value === 'object') {\n const next: Record<string, unknown> = {}\n for (const [key, entry] of Object.entries(value)) {\n next[key] = substituteValue(entry, context)\n }\n return next\n }\n return value\n}\n\n/**\n * The key a node repeats over, trimmed, or `''` when it repeats over nothing.\n *\n * The one predicate every repeat question is asked through: the expansion, the\n * datasets-read gate below, the reusable-component graft that moves a\n * placement's repeat onto the element it becomes, and the besigner's preview\n * and badge. Two copies of it are how a `.trim()` on one side and not the\n * other becomes an empty list on a customer's page.\n */\nexport const repeatKey = (node: unknown): string => {\n const key = (node as { props?: { repeatDataset?: unknown } })?.props\n ?.repeatDataset\n return typeof key === 'string' ? key.trim() : ''\n}\n\n/**\n * Makes an element that holds children repeat ITSELF, once per record, instead\n * of what is inside it (AGL-3111): a card whose contents are the design, rather\n * than a list whose contents are the item.\n *\n * Persisted in screen, layout and component documents — never rename. `'true'`\n * counts too, because a switch round-tripped through a text-shaped store comes\n * back as the string.\n */\nexport const REPEAT_SELF_PROP = 'repeatSelf'\n\n/**\n * Every prop that directs a repeat. They are instructions to the composition,\n * never attributes of the element: the expansion consumes them from every node\n * it expands and every copy it makes, so a published element never carries\n * one. Persisted — never rename.\n */\nexport const REPEAT_DIRECTIVE_PROPS: readonly string[] = [\n 'repeatDataset',\n 'repeatLimit',\n 'repeatFilter',\n 'repeatSort',\n REPEAT_SELF_PROP,\n]\n\n/**\n * What a repeating node copies once per record (AGL-3111).\n *\n * - `children` — what is inside it. The node stays one element around the\n * copies: the list, grid or gallery they fill. This is how a Stack has\n * always repeated (AGL-103), and a stored Stack names neither scope.\n * - `self` — the node itself, with everything inside it. What a leaf and a\n * component instance repeat, and what any element carrying\n * {@link REPEAT_SELF_PROP} repeats.\n */\nexport type RepeatScope = 'self' | 'children'\n\n/**\n * Which scope a repeating node has, read off the node alone so the published\n * page's composition and the besigner decide it identically.\n *\n * {@link REPEAT_SELF_PROP} decides it whenever the node carries one, which is\n * every repeat the Attributes panel writes. Without one the node's own shape\n * decides, and it decides CONSERVATIVELY: anything that holds a child list —\n * a Stack, empty or not — keeps repeating its children, and only a node with\n * no child list at all repeats itself. A stored repeat predates the prop, so\n * this branch is the one that has to render it unchanged, and an author who\n * named a dataset before filling the container in would otherwise come back to\n * a hundred empty copies of it.\n *\n * A component instance never reaches that branch: the graft stamps the prop on\n * the element it becomes, whose children are the component's own.\n */\nexport function repeatScope(node: unknown): RepeatScope {\n const { props, nodes } = (node ?? {}) as {\n props?: Record<string, unknown>\n nodes?: unknown\n }\n // `'true'`/`'false'` too: a switch round-tripped through a text-shaped\n // store comes back as the string it was written as.\n const self = props?.[REPEAT_SELF_PROP]\n if (self === true || self === 'true') return 'self'\n if (self === false || self === 'false') return 'children'\n return Array.isArray(nodes) ? 'children' : 'self'\n}\n\n/**\n * One record's values put into `{{item.field}}` tokens, anywhere in a props\n * map — the substitution {@link expandRepeatables} makes on every copy it\n * builds, for a caller that already knows which record it is rendering.\n *\n * The besigner's canvas is that caller (AGL-3111): the element an author\n * edits IS the first copy, so it draws with the first record's values while\n * the node it saves keeps the tokens. Exported rather than reimplemented\n * because a canvas that resolved a token differently from the page would be\n * showing the author a page that does not exist.\n */\nexport function substituteRecordTokens<T>(\n props: T,\n context: {\n record: Record<string, unknown>\n /** The repeated rows' references (AGL-180). */\n model?: RepeatRowsModel\n /** Rows by key, for a one-hop reference (AGL-180). */\n datasetsByKey?: Record<string, RepeatableDataset | undefined>\n },\n): T {\n return substituteValue(props, context) as T\n}\n\n/**\n * The one record a whole page is rendered for (AGL-3475): a record template's\n * routed record, in the shape a repeat hands each of its copies.\n */\nexport interface PageRecordScope {\n record: Record<string, unknown>\n /** The record's references (AGL-180). */\n model?: RepeatRowsModel\n /** Rows by key, for a one-hop reference (AGL-180). */\n datasetsByKey?: Record<string, RepeatableDataset | undefined>\n}\n\n/**\n * Every node's props with the page's record put into its `{{item.field}}`\n * tokens — the substitution a repeat makes on each copy, made once over a\n * page that renders one record (AGL-3475).\n *\n * Run AFTER {@link expandRepeatables}: a repeat inside the page has already\n * spent its own `{{item.*}}` tokens on its own rows by then, so what is left\n * is the page's. A token naming a field the record does not have stays as\n * written, exactly as in a repeat. Returns the input when there is no record.\n */\nexport function substituteNodesRecordTokens<N>(\n nodes: Record<NodeId, N>,\n scope: PageRecordScope | null | undefined,\n): Record<NodeId, N> {\n if (!scope) return nodes\n const next: Record<NodeId, N> = {}\n for (const [id, node] of Object.entries(nodes) as Array<[NodeId, N]>) {\n const props = (node as { props?: unknown })?.props\n next[id] = props\n ? ({ ...node, props: substituteValue(props, scope) } as N)\n : node\n }\n return next\n}\n\n/**\n * The page's record put into each repeat's FILTER, before the repeats expand\n * (AGL-3475): on a record template, `category == {{item.category}}` lists the\n * other records in the routed record's category. Only `repeatFilter` is\n * touched — everything else inside a repeat is its template, whose\n * `{{item.*}}` belongs to its own rows. Returns the input when there is no\n * record or no filter names one.\n */\nexport function substituteRepeatFilterRecordTokens<N>(\n nodes: Record<NodeId, N>,\n scope: PageRecordScope | null | undefined,\n): Record<NodeId, N> {\n if (!scope) return nodes\n let next: Record<NodeId, N> | null = null\n for (const [id, node] of Object.entries(nodes) as Array<[NodeId, N]>) {\n const props = (node as { props?: Record<string, unknown> })?.props\n const filter = props?.['repeatFilter']\n if (typeof filter !== 'string' || !filter.includes('{{')) continue\n next ??= { ...nodes }\n next[id] = {\n ...node,\n props: { ...props, repeatFilter: substituteValue(filter, scope) },\n } as N\n }\n return next ?? nodes\n}\n\n/** The node minus its repeat directives, or the node itself when it has none. */\nexport function withoutRepeatDirective<N>(node: N): N {\n const props = (node as { props?: Record<string, unknown> })?.props\n if (!props || !REPEAT_DIRECTIVE_PROPS.some((key) => key in props)) {\n return node\n }\n const kept = { ...props }\n for (const key of REPEAT_DIRECTIVE_PROPS) delete kept[key]\n return { ...node, props: kept }\n}\n\n/**\n * The records a repeating node renders, in order (AGL-181): its filter and sort\n * evaluated over the rows, then its limit, never past\n * {@link REPEAT_MAX_RECORDS}. An unparseable filter or sort fails open.\n *\n * Shared by the expansion and the besigner's canvas preview and badge, so the\n * canvas can never show a copy or a count the published page does not.\n */\nexport function repeatedRecords(\n node: unknown,\n dataset: RepeatableDataset | undefined,\n): Array<Record<string, unknown>> {\n const props = ((node as { props?: unknown })?.props ?? {}) as Record<\n string,\n unknown\n >\n const where = parseRepeatFilter(String(props['repeatFilter'] ?? ''))\n const orderBy = parseRepeatSort(String(props['repeatSort'] ?? ''))\n const limit = Number(props['repeatLimit'])\n return applyRepeatQuery(dataset?.records ?? [], {\n ...(where ? { where: [where] } : {}),\n ...(orderBy ? { orderBy } : {}),\n }).slice(\n 0,\n Number.isFinite(limit) && limit > 0\n ? Math.min(limit, REPEAT_MAX_RECORDS)\n : REPEAT_MAX_RECORDS,\n )\n}\n\n/**\n * Does this tree contain anything {@link expandRepeatables} would expand —\n * or, with no rows, empty?\n *\n * The rows read is the largest on the render path — up to two pages of\n * {@link REPEAT_MAX_RECORDS} records for every key a page repeats over — and a\n * page that repeats over nothing should pay none of it (AGL-1440).\n *\n * Any gate on that read has to ask EXACTLY the question the expansion asks,\n * which is why this and {@link repeatKeys} share the one predicate\n * `expandRepeatables` looks keys up with, {@link repeatKey}. A gate that is\n * even slightly stricter than the expansion is not a saving: it is a published\n * page whose repeat never reaches the expansion at all, and so goes out as\n * its raw template — `{{item.*}}` tokens and all — where the author put a list.\n */\nexport function hasRepeatableNodes(\n nodes: Record<NodeId, unknown> | null | undefined,\n): boolean {\n if (!nodes) return false\n return Object.values(nodes).some((node) => repeatKey(node) !== '')\n}\n\n/**\n * Every key this tree repeats over, exactly as {@link expandRepeatables} looks\n * it up — sorted and without repeats.\n *\n * What the tenant compose asks the repeat-rows contract for\n * (`plugin-manager/repeat-rows.ts`), so a page loads the rows it renders and\n * not everything its site may see. Built on the same predicate as\n * {@link hasRepeatableNodes} for the reason given there.\n */\nexport function repeatKeys(\n nodes: Record<NodeId, unknown> | null | undefined,\n): string[] {\n const keys = new Set<string>()\n for (const node of Object.values(nodes ?? {})) {\n const key = repeatKey(node)\n if (key) keys.add(key)\n }\n return [...keys].sort()\n}\n\n/**\n * Repeats (AGL-103, AGL-3111): any node carrying `props.repeatDataset` (a\n * dataset id or display name) renders once per record of that dataset, with\n * `{{item.field}}` tokens in the copied string props replaced by the record's\n * values (unknown fields keep the literal token, like variable bindings).\n *\n * What is copied is the node's {@link repeatScope}:\n *\n * - `children` — its children are the item template. The node stays, and its\n * child list becomes the copies, record by record. Its own props are not the\n * template, so no token in them is substituted.\n * - `self` — the node and everything inside it are the template. The copies\n * take the node's place in its parent's child list, and every prop in the\n * subtree is substituted, the node's own included.\n *\n * - Copy ids are namespaced `rep__{repeatId}__{index}__…` — the repeat's own\n * id, then the template node's — so repeats never collide, including inside\n * grafted reusable components. A self-scoped node's first copy is\n * `rep__{id}__0__{id}`. Run this AFTER `composeReusableComponentNodes` and\n * BEFORE binding resolution.\n * - Rows are the node's {@link repeatedRecords}.\n * - The repeat directives ({@link REPEAT_DIRECTIVE_PROPS}) are consumed: every\n * node this expands, and every copy it makes, comes out without them.\n * - Repeats do not nest. A repeat inside another repeat's template is copied\n * with its directives consumed, so it renders once in each copy.\n * - No rows renders ZERO copies (AGL-3496): a `children` repeat keeps its\n * element with an empty child list, a `self` repeat leaves its parent's\n * list. That covers a filter matching nothing, an empty dataset, and a key\n * the rows answer nothing under — a deleted or unshared dataset, or no rows\n * map at all — because the template drawn without a record is its literal\n * `{{item.*}}` tokens, printed to every visitor. Never a throw: a dataset\n * going away empties a list, it does not take the page down.\n * - An empty template, or a self-scoped node its parent does not list (the\n * document root, say), has nothing to copy or nowhere to put copies, and is\n * left as written.\n * - Inputs are never mutated; templates stay in the map unreferenced.\n *\n * This is the PUBLISHED page's composition. The besigner's canvas keeps the\n * template on screen whatever the rows say — it is what an author edits — and\n * flags a repeat with no rows on its badge instead ({@link repeatedRecords}\n * is the count both read).\n */\nexport function expandRepeatables<N extends AglynNodeSchema = AglynNodeSchema>(\n nodes: Record<NodeId, N>,\n datasetsByKey: Record<string, RepeatableDataset | undefined> | undefined,\n): Record<NodeId, N> {\n const rowsByKey = datasetsByKey ?? {}\n const repeatIds = Object.entries(nodes).filter(\n ([, node]) => repeatKey(node) !== '',\n )\n if (!repeatIds.length) return nodes\n\n const next: Record<NodeId, N> = { ...nodes }\n for (const [repeatId, repeated] of repeatIds) {\n const dataset = rowsByKey[repeatKey(repeated)]\n const records = repeatedRecords(repeated, dataset)\n const self = repeatScope(repeated) === 'self'\n // The copies' parent: the node itself, or — for a self-scoped node — the\n // parent whose child list the copies take its place in, read from `next`\n // so two self-scoped siblings both land in the list the other has left.\n const parentId = (self ? repeated.parentId : repeatId) as NodeId\n const siblings = next[parentId]?.nodes\n const slot = Array.isArray(siblings)\n ? (siblings as NodeId[]).indexOf(repeatId)\n : -1\n const templateIds = self\n ? [repeatId]\n : Array.isArray(repeated.nodes)\n ? (repeated.nodes as NodeId[])\n : []\n next[repeatId] = withoutRepeatDirective(next[repeatId])\n if (!templateIds.length || (self && slot < 0)) continue\n if (!records.length) {\n // Zero copies (AGL-3496). The template is a design, not content: drawn\n // without a record it prints `{{item.name}}` to every visitor.\n if (self) {\n const listed = [...(next[parentId].nodes as NodeId[])]\n listed.splice(slot, 1)\n next[parentId] = { ...next[parentId], nodes: listed }\n } else {\n next[repeatId] = { ...next[repeatId], nodes: [] }\n }\n continue\n }\n\n const copyIds: NodeId[] = []\n records.forEach((record, index) => {\n const prefix = `${REPEAT_NODE_ID_PREFIX}${repeatId}__${index}__`\n const prefixId = (id: NodeId) => `${prefix}${id}`\n const cloneSubtree = (id: NodeId, clonedParentId: NodeId) => {\n const node = nodes[id]\n if (!node) return\n const clonedChildren = Array.isArray(node.nodes)\n ? (node.nodes as NodeId[])\n : undefined\n next[prefixId(id)] = withoutRepeatDirective({\n ...node,\n $id: prefixId(id),\n parentId: clonedParentId,\n props: substituteValue(node.props ?? {}, {\n record,\n model: dataset?.model,\n datasetsByKey: rowsByKey,\n }) as any,\n ...(clonedChildren && {\n nodes: clonedChildren.map((childId) => prefixId(childId)),\n }),\n })\n clonedChildren?.forEach((childId) =>\n cloneSubtree(childId, prefixId(id)),\n )\n }\n for (const templateId of templateIds) {\n cloneSubtree(templateId, parentId)\n copyIds.push(prefixId(templateId))\n }\n })\n if (self) {\n const listed = [...(next[parentId].nodes as NodeId[])]\n listed.splice(slot, 1, ...copyIds)\n next[parentId] = { ...next[parentId], nodes: listed }\n } else {\n next[repeatId] = { ...next[repeatId], nodes: copyIds }\n }\n }\n return next\n}\n\nexport default expandRepeatables\n"],"names":["applyRepeatQuery","parseRepeatFilter","parseRepeatSort","REPEAT_NODE_ID_PREFIX","ITEM_TOKEN_PATTERN","REPEAT_MAX_RECORDS","displayValue","value","Array","isArray","join","String","resolveReferenceHop","context","fieldId","targetFieldId","targetKey","model","references","target","datasetsByKey","keys","record","ids","resolved","map","id","match","records","find","row","filter","length","substituteValue","replace","token","field","hop","entry","next","key","Object","entries","repeatKey","node","props","repeatDataset","trim","REPEAT_SELF_PROP","REPEAT_DIRECTIVE_PROPS","repeatScope","nodes","self","substituteRecordTokens","substituteNodesRecordTokens","scope","substituteRepeatFilterRecordTokens","includes","repeatFilter","withoutRepeatDirective","some","kept","repeatedRecords","dataset","where","orderBy","limit","Number","slice","isFinite","Math","min","hasRepeatableNodes","values","repeatKeys","Set","add","sort","expandRepeatables","rowsByKey","repeatIds","repeatId","repeated","parentId","siblings","slot","indexOf","templateIds","listed","splice","copyIds","forEach","index","prefix","prefixId","cloneSubtree","clonedParentId","clonedChildren","undefined","$id","childId","templateId","push"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/expand-repeatables.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AglynNodeSchema, NodeId } from '../foundation'\nimport {\n applyRepeatQuery,\n parseRepeatFilter,\n parseRepeatSort,\n} from './repeat-query'\n\n/** Namespaces cloned template ids per container/record. */\nexport const REPEAT_NODE_ID_PREFIX = 'rep__'\n\n/**\n * `{{item.field}}` token, plus an optional single reference hop\n * (`{{item.author.name}}`, AGL-180). One hop only — the pattern itself is\n * the depth guard, so cycles can't recurse.\n */\nconst ITEM_TOKEN_PATTERN =\n /\\{\\{\\s*item\\.([A-Za-z][A-Za-z0-9_]*)(?:\\.([A-Za-z][A-Za-z0-9_]*))?\\s*\\}\\}/g\n\n/** Hard bound on rows a single repeatable renders, before `repeatLimit`. */\nexport const REPEAT_MAX_RECORDS = 100\n\n/**\n * What the expansion reads of the rows' field model (AGL-177, AGL-180): which\n * fields are references, each with the key its target's rows are answered\n * under (`datasetsByKey`). The model itself is the plugin's that keeps the\n * rows, and it states this much of it when it hands the rows over\n * (`plugin-manager/repeat-rows`, `repeat-sources`); a field it does not name\n * here hops nowhere, and its token is left as written.\n */\nexport interface RepeatRowsModel {\n /** Reference field id → the key of the rows it points into. */\n references?: Readonly<Record<string, string>>\n}\n\nexport interface RepeatableDataset {\n /**\n * Row value maps, in display order. Rows carry `$id` (needed to resolve\n * incoming references); values may be typed (AGL-177), stringified at\n * substitution.\n */\n records: Array<Record<string, unknown>>\n /** The rows' references; required for reference-hop bindings. */\n model?: RepeatRowsModel\n}\n\ninterface SubstituteContext {\n record: Record<string, unknown>\n /** The repeated rows' references (AGL-180). */\n model?: RepeatRowsModel\n /** All host datasets keyed by id (and name) for hop resolution. */\n datasetsByKey?: Record<string, RepeatableDataset | undefined>\n /**\n * What a token whose field the record leaves empty becomes: `'token'` keeps\n * it as written (the besigner's canvas, where an author edits the template),\n * `'empty'` prints nothing (a rendered page, AGL-3616). A published page\n * never shows a visitor `{{item.venue}}` because one record has no venue.\n */\n missing?: 'token' | 'empty'\n}\n\nconst displayValue = (value: unknown): string =>\n Array.isArray(value) ? value.join(', ') : String(value)\n\n/** Resolves `{{item.ref.field}}`: FKey(s) → target document field value. */\nfunction resolveReferenceHop(\n context: SubstituteContext,\n fieldId: string,\n targetFieldId: string,\n): string | null {\n const targetKey = context.model?.references?.[fieldId]\n if (!targetKey) return null\n const target = context.datasetsByKey?.[targetKey]\n if (!target) return null\n const keys = context.record[fieldId]\n const ids = Array.isArray(keys) ? keys : keys != null ? [keys] : []\n const resolved = ids\n .map((id) => {\n const match = target.records.find((row) => row['$id'] === id)\n const value = match?.[targetFieldId]\n return value != null ? displayValue(value) : null\n })\n .filter((value): value is string => value != null)\n return resolved.length ? resolved.join(', ') : null\n}\n\nfunction substituteValue(\n value: unknown,\n context: SubstituteContext,\n): unknown {\n const { record } = context\n if (typeof value === 'string') {\n const unresolved = (token: string) =>\n context.missing === 'empty' ? '' : token\n return value.replace(\n ITEM_TOKEN_PATTERN,\n (token, field: string, hop?: string) => {\n if (hop) {\n const resolved = resolveReferenceHop(context, field, hop)\n return resolved != null ? resolved : unresolved(token)\n }\n return record[field] != null\n ? displayValue(record[field])\n : unresolved(token)\n },\n )\n }\n if (Array.isArray(value)) {\n return value.map((entry) => substituteValue(entry, context))\n }\n if (value && typeof value === 'object') {\n const next: Record<string, unknown> = {}\n for (const [key, entry] of Object.entries(value)) {\n next[key] = substituteValue(entry, context)\n }\n return next\n }\n return value\n}\n\n/** A text that is nothing but `{{item.*}}` tokens: a binding, not words. */\nconst WHOLE_BINDING = new RegExp(\n `^\\\\s*(?:${ITEM_TOKEN_PATTERN.source}\\\\s*)+$`,\n)\n\n/**\n * Whether a copy's element is a binding its record left empty (AGL-3616):\n * its text, `children`, was only `{{item.*}}` tokens and they printed\n * nothing. The copy leaves the element out rather than drawing an empty line\n * (an eyebrow with no venue), and a record that fills the field draws it.\n */\nfunction isEmptyBinding(\n template: Record<string, unknown> | undefined,\n substituted: Record<string, unknown>,\n): boolean {\n const text = template?.['children']\n if (typeof text !== 'string' || !WHOLE_BINDING.test(text)) return false\n const printed = substituted['children']\n return typeof printed === 'string' && printed.trim() === ''\n}\n\n/**\n * The key a node repeats over, trimmed, or `''` when it repeats over nothing.\n *\n * The one predicate every repeat question is asked through: the expansion, the\n * datasets-read gate below, the reusable-component graft that moves a\n * placement's repeat onto the element it becomes, and the besigner's preview\n * and badge. Two copies of it are how a `.trim()` on one side and not the\n * other becomes an empty list on a customer's page.\n */\nexport const repeatKey = (node: unknown): string => {\n const key = (node as { props?: { repeatDataset?: unknown } })?.props\n ?.repeatDataset\n return typeof key === 'string' ? key.trim() : ''\n}\n\n/**\n * Makes an element that holds children repeat ITSELF, once per record, instead\n * of what is inside it (AGL-3111): a card whose contents are the design, rather\n * than a list whose contents are the item.\n *\n * Persisted in screen, layout and component documents — never rename. `'true'`\n * counts too, because a switch round-tripped through a text-shaped store comes\n * back as the string.\n */\nexport const REPEAT_SELF_PROP = 'repeatSelf'\n\n/**\n * Every prop that directs a repeat. They are instructions to the composition,\n * never attributes of the element: the expansion consumes them from every node\n * it expands and every copy it makes, so a published element never carries\n * one. Persisted — never rename.\n */\nexport const REPEAT_DIRECTIVE_PROPS: readonly string[] = [\n 'repeatDataset',\n 'repeatLimit',\n 'repeatFilter',\n 'repeatSort',\n REPEAT_SELF_PROP,\n]\n\n/**\n * What a repeating node copies once per record (AGL-3111).\n *\n * - `children` — what is inside it. The node stays one element around the\n * copies: the list, grid or gallery they fill. This is how a Stack has\n * always repeated (AGL-103), and a stored Stack names neither scope.\n * - `self` — the node itself, with everything inside it. What a leaf and a\n * component instance repeat, and what any element carrying\n * {@link REPEAT_SELF_PROP} repeats.\n */\nexport type RepeatScope = 'self' | 'children'\n\n/**\n * Which scope a repeating node has, read off the node alone so the published\n * page's composition and the besigner decide it identically.\n *\n * {@link REPEAT_SELF_PROP} decides it whenever the node carries one, which is\n * every repeat the Attributes panel writes. Without one the node's own shape\n * decides, and it decides CONSERVATIVELY: anything that holds a child list —\n * a Stack, empty or not — keeps repeating its children, and only a node with\n * no child list at all repeats itself. A stored repeat predates the prop, so\n * this branch is the one that has to render it unchanged, and an author who\n * named a dataset before filling the container in would otherwise come back to\n * a hundred empty copies of it.\n *\n * A component instance never reaches that branch: the graft stamps the prop on\n * the element it becomes, whose children are the component's own.\n */\nexport function repeatScope(node: unknown): RepeatScope {\n const { props, nodes } = (node ?? {}) as {\n props?: Record<string, unknown>\n nodes?: unknown\n }\n // `'true'`/`'false'` too: a switch round-tripped through a text-shaped\n // store comes back as the string it was written as.\n const self = props?.[REPEAT_SELF_PROP]\n if (self === true || self === 'true') return 'self'\n if (self === false || self === 'false') return 'children'\n return Array.isArray(nodes) ? 'children' : 'self'\n}\n\n/**\n * One record's values put into `{{item.field}}` tokens, anywhere in a props\n * map — the substitution {@link expandRepeatables} makes on every copy it\n * builds, for a caller that already knows which record it is rendering.\n *\n * The besigner's canvas is that caller (AGL-3111): the element an author\n * edits IS the first copy, so it draws with the first record's values while\n * the node it saves keeps the tokens. Exported rather than reimplemented\n * because a canvas that resolved a token differently from the page would be\n * showing the author a page that does not exist.\n */\nexport function substituteRecordTokens<T>(\n props: T,\n context: {\n record: Record<string, unknown>\n /** The repeated rows' references (AGL-180). */\n model?: RepeatRowsModel\n /** Rows by key, for a one-hop reference (AGL-180). */\n datasetsByKey?: Record<string, RepeatableDataset | undefined>\n },\n): T {\n return substituteValue(props, context) as T\n}\n\n/**\n * The one record a whole page is rendered for (AGL-3475): a record template's\n * routed record, in the shape a repeat hands each of its copies.\n */\nexport interface PageRecordScope {\n record: Record<string, unknown>\n /** The record's references (AGL-180). */\n model?: RepeatRowsModel\n /** Rows by key, for a one-hop reference (AGL-180). */\n datasetsByKey?: Record<string, RepeatableDataset | undefined>\n}\n\n/**\n * Every node's props with the page's record put into its `{{item.field}}`\n * tokens — the substitution a repeat makes on each copy, made once over a\n * page that renders one record (AGL-3475).\n *\n * Run AFTER {@link expandRepeatables}: a repeat inside the page has already\n * spent its own `{{item.*}}` tokens on its own rows by then, so what is left\n * is the page's. A token naming a field the record leaves empty prints\n * nothing, exactly as in a repeat (AGL-3616). Returns the input when there is\n * no record.\n */\nexport function substituteNodesRecordTokens<N>(\n nodes: Record<NodeId, N>,\n scope: PageRecordScope | null | undefined,\n): Record<NodeId, N> {\n if (!scope) return nodes\n const next: Record<NodeId, N> = {}\n for (const [id, node] of Object.entries(nodes) as Array<[NodeId, N]>) {\n const props = (node as { props?: unknown })?.props\n next[id] = props\n ? ({\n ...node,\n props: substituteValue(props, { ...scope, missing: 'empty' }),\n } as N)\n : node\n }\n return next\n}\n\n/**\n * The page's record put into each repeat's FILTER, before the repeats expand\n * (AGL-3475): on a record template, `category == {{item.category}}` lists the\n * other records in the routed record's category. Only `repeatFilter` is\n * touched — everything else inside a repeat is its template, whose\n * `{{item.*}}` belongs to its own rows. Returns the input when there is no\n * record or no filter names one.\n */\nexport function substituteRepeatFilterRecordTokens<N>(\n nodes: Record<NodeId, N>,\n scope: PageRecordScope | null | undefined,\n): Record<NodeId, N> {\n if (!scope) return nodes\n let next: Record<NodeId, N> | null = null\n for (const [id, node] of Object.entries(nodes) as Array<[NodeId, N]>) {\n const props = (node as { props?: Record<string, unknown> })?.props\n const filter = props?.['repeatFilter']\n if (typeof filter !== 'string' || !filter.includes('{{')) continue\n next ??= { ...nodes }\n next[id] = {\n ...node,\n props: { ...props, repeatFilter: substituteValue(filter, scope) },\n } as N\n }\n return next ?? nodes\n}\n\n/** The node minus its repeat directives, or the node itself when it has none. */\nexport function withoutRepeatDirective<N>(node: N): N {\n const props = (node as { props?: Record<string, unknown> })?.props\n if (!props || !REPEAT_DIRECTIVE_PROPS.some((key) => key in props)) {\n return node\n }\n const kept = { ...props }\n for (const key of REPEAT_DIRECTIVE_PROPS) delete kept[key]\n return { ...node, props: kept }\n}\n\n/**\n * The records a repeating node renders, in order (AGL-181): its filter and sort\n * evaluated over the rows, then its limit, never past\n * {@link REPEAT_MAX_RECORDS}. An unparseable filter or sort fails open.\n *\n * Shared by the expansion and the besigner's canvas preview and badge, so the\n * canvas can never show a copy or a count the published page does not.\n */\nexport function repeatedRecords(\n node: unknown,\n dataset: RepeatableDataset | undefined,\n): Array<Record<string, unknown>> {\n const props = ((node as { props?: unknown })?.props ?? {}) as Record<\n string,\n unknown\n >\n const where = parseRepeatFilter(String(props['repeatFilter'] ?? ''))\n const orderBy = parseRepeatSort(String(props['repeatSort'] ?? ''))\n const limit = Number(props['repeatLimit'])\n return applyRepeatQuery(dataset?.records ?? [], {\n ...(where ? { where: [where] } : {}),\n ...(orderBy ? { orderBy } : {}),\n }).slice(\n 0,\n Number.isFinite(limit) && limit > 0\n ? Math.min(limit, REPEAT_MAX_RECORDS)\n : REPEAT_MAX_RECORDS,\n )\n}\n\n/**\n * Does this tree contain anything {@link expandRepeatables} would expand —\n * or, with no rows, empty?\n *\n * The rows read is the largest on the render path — up to two pages of\n * {@link REPEAT_MAX_RECORDS} records for every key a page repeats over — and a\n * page that repeats over nothing should pay none of it (AGL-1440).\n *\n * Any gate on that read has to ask EXACTLY the question the expansion asks,\n * which is why this and {@link repeatKeys} share the one predicate\n * `expandRepeatables` looks keys up with, {@link repeatKey}. A gate that is\n * even slightly stricter than the expansion is not a saving: it is a published\n * page whose repeat never reaches the expansion at all, and so goes out as\n * its raw template — `{{item.*}}` tokens and all — where the author put a list.\n */\nexport function hasRepeatableNodes(\n nodes: Record<NodeId, unknown> | null | undefined,\n): boolean {\n if (!nodes) return false\n return Object.values(nodes).some((node) => repeatKey(node) !== '')\n}\n\n/**\n * Every key this tree repeats over, exactly as {@link expandRepeatables} looks\n * it up — sorted and without repeats.\n *\n * What the tenant compose asks the repeat-rows contract for\n * (`plugin-manager/repeat-rows.ts`), so a page loads the rows it renders and\n * not everything its site may see. Built on the same predicate as\n * {@link hasRepeatableNodes} for the reason given there.\n */\nexport function repeatKeys(\n nodes: Record<NodeId, unknown> | null | undefined,\n): string[] {\n const keys = new Set<string>()\n for (const node of Object.values(nodes ?? {})) {\n const key = repeatKey(node)\n if (key) keys.add(key)\n }\n return [...keys].sort()\n}\n\n/**\n * Repeats (AGL-103, AGL-3111): any node carrying `props.repeatDataset` (a\n * dataset id or display name) renders once per record of that dataset, with\n * `{{item.field}}` tokens in the copied string props replaced by the record's\n * values. A field the record leaves empty prints nothing — never the raw\n * token — and an element whose whole text is such a binding is left out of\n * that copy (AGL-3616: a tour date with no venue drew `{{ITEM.VENUE}}` as its\n * eyebrow on a published site). The besigner's canvas keeps the tokens of\n * the template an author edits ({@link substituteRecordTokens}).\n *\n * What is copied is the node's {@link repeatScope}:\n *\n * - `children` — its children are the item template. The node stays, and its\n * child list becomes the copies, record by record. Its own props are not the\n * template, so no token in them is substituted.\n * - `self` — the node and everything inside it are the template. The copies\n * take the node's place in its parent's child list, and every prop in the\n * subtree is substituted, the node's own included.\n *\n * - Copy ids are namespaced `rep__{repeatId}__{index}__…` — the repeat's own\n * id, then the template node's — so repeats never collide, including inside\n * grafted reusable components. A self-scoped node's first copy is\n * `rep__{id}__0__{id}`. Run this AFTER `composeReusableComponentNodes` and\n * BEFORE binding resolution.\n * - Rows are the node's {@link repeatedRecords}.\n * - The repeat directives ({@link REPEAT_DIRECTIVE_PROPS}) are consumed: every\n * node this expands, and every copy it makes, comes out without them.\n * - Repeats do not nest. A repeat inside another repeat's template is copied\n * with its directives consumed, so it renders once in each copy.\n * - No rows renders ZERO copies (AGL-3496): a `children` repeat keeps its\n * element with an empty child list, a `self` repeat leaves its parent's\n * list. That covers a filter matching nothing, an empty dataset, and a key\n * the rows answer nothing under — a deleted or unshared dataset, or no rows\n * map at all — because the template drawn without a record is its literal\n * `{{item.*}}` tokens, printed to every visitor. Never a throw: a dataset\n * going away empties a list, it does not take the page down.\n * - An empty template, or a self-scoped node its parent does not list (the\n * document root, say), has nothing to copy or nowhere to put copies, and is\n * left as written.\n * - Inputs are never mutated; templates stay in the map unreferenced.\n *\n * This is the PUBLISHED page's composition. The besigner's canvas keeps the\n * template on screen whatever the rows say — it is what an author edits — and\n * flags a repeat with no rows on its badge instead ({@link repeatedRecords}\n * is the count both read).\n */\nexport function expandRepeatables<N extends AglynNodeSchema = AglynNodeSchema>(\n nodes: Record<NodeId, N>,\n datasetsByKey: Record<string, RepeatableDataset | undefined> | undefined,\n): Record<NodeId, N> {\n const rowsByKey = datasetsByKey ?? {}\n const repeatIds = Object.entries(nodes).filter(\n ([, node]) => repeatKey(node) !== '',\n )\n if (!repeatIds.length) return nodes\n\n const next: Record<NodeId, N> = { ...nodes }\n for (const [repeatId, repeated] of repeatIds) {\n const dataset = rowsByKey[repeatKey(repeated)]\n const records = repeatedRecords(repeated, dataset)\n const self = repeatScope(repeated) === 'self'\n // The copies' parent: the node itself, or — for a self-scoped node — the\n // parent whose child list the copies take its place in, read from `next`\n // so two self-scoped siblings both land in the list the other has left.\n const parentId = (self ? repeated.parentId : repeatId) as NodeId\n const siblings = next[parentId]?.nodes\n const slot = Array.isArray(siblings)\n ? (siblings as NodeId[]).indexOf(repeatId)\n : -1\n const templateIds = self\n ? [repeatId]\n : Array.isArray(repeated.nodes)\n ? (repeated.nodes as NodeId[])\n : []\n next[repeatId] = withoutRepeatDirective(next[repeatId])\n if (!templateIds.length || (self && slot < 0)) continue\n if (!records.length) {\n // Zero copies (AGL-3496). The template is a design, not content: drawn\n // without a record it prints `{{item.name}}` to every visitor.\n if (self) {\n const listed = [...(next[parentId].nodes as NodeId[])]\n listed.splice(slot, 1)\n next[parentId] = { ...next[parentId], nodes: listed }\n } else {\n next[repeatId] = { ...next[repeatId], nodes: [] }\n }\n continue\n }\n\n const copyIds: NodeId[] = []\n records.forEach((record, index) => {\n const prefix = `${REPEAT_NODE_ID_PREFIX}${repeatId}__${index}__`\n const prefixId = (id: NodeId) => `${prefix}${id}`\n // Whether the copy drew the node: an element whose whole text is a\n // binding this record leaves empty is left out of it (AGL-3616).\n const cloneSubtree = (id: NodeId, clonedParentId: NodeId): boolean => {\n const node = nodes[id]\n if (!node) return false\n const props = substituteValue(node.props ?? {}, {\n record,\n model: dataset?.model,\n datasetsByKey: rowsByKey,\n missing: 'empty',\n }) as Record<string, unknown>\n if (isEmptyBinding(node.props as Record<string, unknown>, props)) {\n return false\n }\n const clonedChildren = Array.isArray(node.nodes)\n ? (node.nodes as NodeId[])\n : undefined\n const drawn = clonedChildren?.filter((childId) =>\n cloneSubtree(childId, prefixId(id)),\n )\n next[prefixId(id)] = withoutRepeatDirective({\n ...node,\n $id: prefixId(id),\n parentId: clonedParentId,\n props: props as any,\n ...(drawn && {\n nodes: drawn.map((childId) => prefixId(childId)),\n }),\n })\n return true\n }\n for (const templateId of templateIds) {\n if (cloneSubtree(templateId, parentId)) {\n copyIds.push(prefixId(templateId))\n }\n }\n })\n if (self) {\n const listed = [...(next[parentId].nodes as NodeId[])]\n listed.splice(slot, 1, ...copyIds)\n next[parentId] = { ...next[parentId], nodes: listed }\n } else {\n next[repeatId] = { ...next[repeatId], nodes: copyIds }\n }\n }\n return next\n}\n\nexport default expandRepeatables\n"],"names":["applyRepeatQuery","parseRepeatFilter","parseRepeatSort","REPEAT_NODE_ID_PREFIX","ITEM_TOKEN_PATTERN","REPEAT_MAX_RECORDS","displayValue","value","Array","isArray","join","String","resolveReferenceHop","context","fieldId","targetFieldId","targetKey","model","references","target","datasetsByKey","keys","record","ids","resolved","map","id","match","records","find","row","filter","length","substituteValue","unresolved","token","missing","replace","field","hop","entry","next","key","Object","entries","WHOLE_BINDING","RegExp","source","isEmptyBinding","template","substituted","text","test","printed","trim","repeatKey","node","props","repeatDataset","REPEAT_SELF_PROP","REPEAT_DIRECTIVE_PROPS","repeatScope","nodes","self","substituteRecordTokens","substituteNodesRecordTokens","scope","substituteRepeatFilterRecordTokens","includes","repeatFilter","withoutRepeatDirective","some","kept","repeatedRecords","dataset","where","orderBy","limit","Number","slice","isFinite","Math","min","hasRepeatableNodes","values","repeatKeys","Set","add","sort","expandRepeatables","rowsByKey","repeatIds","repeatId","repeated","parentId","siblings","slot","indexOf","templateIds","listed","splice","copyIds","forEach","index","prefix","prefixId","cloneSubtree","clonedParentId","clonedChildren","undefined","drawn","childId","$id","templateId","push"],"mappings":"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"}
|