@aglyn/tenant-runtime 1.0.0-beta.218 → 1.0.0-beta.219
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/README.md +2 -1
- package/package.json +3 -3
- package/src/lib/collection-fallback-nodes.js +5 -4
- package/src/lib/collection-fallback-nodes.js.map +1 -1
- package/src/lib/compose-screen-nodes.js +47 -35
- package/src/lib/compose-screen-nodes.js.map +1 -1
- package/src/lib/emit-host-event.d.ts +2 -2
- package/src/lib/emit-host-event.js.map +1 -1
- package/src/lib/get-collection-content.d.ts +2 -2
- package/src/lib/get-collection-content.js.map +1 -1
- package/src/lib/get-plugin-installs.js +8 -3
- package/src/lib/get-plugin-installs.js.map +1 -1
- package/src/lib/get-variables.d.ts +0 -7
- package/src/lib/get-variables.js +3 -27
- package/src/lib/get-variables.js.map +1 -1
- package/src/lib/host-event-listeners.d.ts +12 -11
- package/src/lib/host-event-listeners.js +4 -4
- package/src/lib/host-event-listeners.js.map +1 -1
- package/src/lib/convert-host-lead.d.ts +0 -189
- package/src/lib/convert-host-lead.js +0 -518
- package/src/lib/convert-host-lead.js.map +0 -1
- package/src/lib/get-datasets.d.ts +0 -45
- package/src/lib/get-datasets.js +0 -169
- package/src/lib/get-datasets.js.map +0 -1
- package/src/lib/stamp-form-dataset-bindings.d.ts +0 -32
- package/src/lib/stamp-form-dataset-bindings.js +0 -59
- package/src/lib/stamp-form-dataset-bindings.js.map +0 -1
package/src/lib/get-datasets.js
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/ import * as Aglyn from "@aglyn/aglyn/server";
|
|
17
|
-
import { orgDataQueryForHost } from "@aglyn/tenant-data-admin";
|
|
18
|
-
import { PUBLISHED_SITE_DATA_TTL_SECONDS, tenantDataTag, withRenderCache } from "@aglyn/tenant-data-admin/render-cache";
|
|
19
|
-
import { FieldPath } from "firebase-admin/firestore";
|
|
20
|
-
import { repeatRecordsFromPages } from "./repeat-record-pages.js";
|
|
21
|
-
/**
|
|
22
|
-
* The render path's largest read (AGL-1302): up to two pages of records per
|
|
23
|
-
* dataset a page repeats over. Records are mapped to plain values before
|
|
24
|
-
* return, so the cached shape is what repeatable expansion consumes. Backstop
|
|
25
|
-
* only; the publish path busts the tag — see PUBLISHED_SITE_DATA_TTL_SECONDS.
|
|
26
|
-
*/ const DATASETS_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS;
|
|
27
|
-
/**
|
|
28
|
-
* The datasets a page repeats over, with their records, for repeatable
|
|
29
|
-
* expansion (AGL-103).
|
|
30
|
-
*
|
|
31
|
-
* `keys` are the page's repeat keys (`repeatDatasetKeys`), each a dataset id or
|
|
32
|
-
* a display name — editors type the friendly name into the Repeat attribute.
|
|
33
|
-
* The map answers under every key exactly as written, and under every loaded
|
|
34
|
-
* dataset's id, which is how a reference hop (AGL-180) finds its target.
|
|
35
|
-
* Records are editor-ordered and capped at the repeat bound.
|
|
36
|
-
*
|
|
37
|
-
* Only the datasets the page names are read. A site may see thousands of
|
|
38
|
-
* datasets, and a read of "the site's datasets" can only ever be a bounded
|
|
39
|
-
* page of them, so a repeat bound to one outside that page rendered its
|
|
40
|
-
* template once with nothing to say why.
|
|
41
|
-
*
|
|
42
|
-
* Fail-open, per key: a key that cannot be read is left out, and its
|
|
43
|
-
* repeatable renders its template untouched, without costing the others.
|
|
44
|
-
*
|
|
45
|
-
* Scoped to what THIS host may see (AGL-1039): a display name resolves inside
|
|
46
|
-
* the host's scope, and an id outside it resolves to nothing. The Admin SDK
|
|
47
|
-
* does not evaluate rules, so AGL-1041 does not cover this path; the filter
|
|
48
|
-
* has to live here.
|
|
49
|
-
*/ export async function getDatasets(options) {
|
|
50
|
-
const keys = [
|
|
51
|
-
...new Set(options.keys.map((key)=>key.trim()).filter(Boolean))
|
|
52
|
-
].sort();
|
|
53
|
-
if (!keys.length) return {};
|
|
54
|
-
try {
|
|
55
|
-
return await withRenderCache({
|
|
56
|
-
key: [
|
|
57
|
-
'tenant-datasets',
|
|
58
|
-
options.hostId,
|
|
59
|
-
...keys
|
|
60
|
-
],
|
|
61
|
-
revalidate: DATASETS_TTL_SECONDS,
|
|
62
|
-
tags: [
|
|
63
|
-
tenantDataTag(options.hostId)
|
|
64
|
-
],
|
|
65
|
-
read: ()=>readDatasets(options.hostId, keys)
|
|
66
|
-
});
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error(error);
|
|
69
|
-
return readDatasets(options.hostId, keys);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async function readDatasets(hostId, keys) {
|
|
73
|
-
const datasets = {};
|
|
74
|
-
try {
|
|
75
|
-
var _ref_parent_parent, _ref_parent;
|
|
76
|
-
const { ref, query } = await orgDataQueryForHost(hostId, 'datasets');
|
|
77
|
-
// The same visibility rule `resolveDatasetDoc` applies: a keyed read
|
|
78
|
-
// bypasses the scoped query, so the scope is checked on the document.
|
|
79
|
-
const orgScoped = ((_ref_parent = ref.parent) == null ? void 0 : (_ref_parent_parent = _ref_parent.parent) == null ? void 0 : _ref_parent_parent.id) === 'orgs';
|
|
80
|
-
const usable = (snapshot)=>Boolean(snapshot == null ? void 0 : snapshot.exists) && !(snapshot == null ? void 0 : snapshot.get('deletedAt')) && (!orgScoped || Aglyn.visibleToHost(snapshot == null ? void 0 : snapshot.get('visibleTo'), hostId));
|
|
81
|
-
const loads = new Map();
|
|
82
|
-
const load = (snapshot)=>{
|
|
83
|
-
let dataset = loads.get(snapshot.id);
|
|
84
|
-
if (!dataset) {
|
|
85
|
-
dataset = readRepeatRecords(snapshot.ref).then((records)=>({
|
|
86
|
-
records,
|
|
87
|
-
model: Aglyn.effectiveDatasetModel(snapshot.data())
|
|
88
|
-
}));
|
|
89
|
-
loads.set(snapshot.id, dataset);
|
|
90
|
-
}
|
|
91
|
-
return dataset;
|
|
92
|
-
};
|
|
93
|
-
// An id first, then a display name. An id that exists but that this host
|
|
94
|
-
// cannot see does not fall through to the name: that would answer "which
|
|
95
|
-
// dataset is called X" for a key that already named a specific one.
|
|
96
|
-
const resolveKey = async (key)=>{
|
|
97
|
-
if (!key.includes('/')) {
|
|
98
|
-
const byId = await ref.doc(key).get();
|
|
99
|
-
if (byId.exists) return usable(byId) ? byId : undefined;
|
|
100
|
-
}
|
|
101
|
-
const byName = await query.where('displayName', '==', key).limit(1).get();
|
|
102
|
-
return byName.docs.find((snapshot)=>usable(snapshot));
|
|
103
|
-
};
|
|
104
|
-
const resolved = await Promise.all(keys.map(async (key)=>{
|
|
105
|
-
try {
|
|
106
|
-
const snapshot = await resolveKey(key);
|
|
107
|
-
if (!snapshot) return undefined;
|
|
108
|
-
const dataset = await load(snapshot);
|
|
109
|
-
datasets[key] = dataset;
|
|
110
|
-
datasets[snapshot.id] = dataset;
|
|
111
|
-
return dataset;
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.error(error);
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
116
|
-
}));
|
|
117
|
-
// One reference hop (AGL-180): `{{item.author.name}}` reads the target's
|
|
118
|
-
// rows by id, so the target has to be loaded even though no repeat names it.
|
|
119
|
-
const targets = new Set();
|
|
120
|
-
for (const dataset of resolved){
|
|
121
|
-
var _ref;
|
|
122
|
-
var _dataset_model;
|
|
123
|
-
for (const fieldId of (_ref = dataset == null ? void 0 : (_dataset_model = dataset.model) == null ? void 0 : _dataset_model.order) != null ? _ref : []){
|
|
124
|
-
var _dataset_model1, _field_reference;
|
|
125
|
-
const field = dataset == null ? void 0 : (_dataset_model1 = dataset.model) == null ? void 0 : _dataset_model1.fields[fieldId];
|
|
126
|
-
const targetId = (field == null ? void 0 : field.type) === 'reference' ? (_field_reference = field.reference) == null ? void 0 : _field_reference.datasetId : undefined;
|
|
127
|
-
if (targetId && !targetId.includes('/') && !datasets[targetId]) {
|
|
128
|
-
targets.add(targetId);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
await Promise.all([
|
|
133
|
-
...targets
|
|
134
|
-
].map(async (targetId)=>{
|
|
135
|
-
try {
|
|
136
|
-
const target = await ref.doc(targetId).get();
|
|
137
|
-
if (usable(target)) datasets[targetId] = await load(target);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error(error);
|
|
140
|
-
}
|
|
141
|
-
}));
|
|
142
|
-
} catch (error) {
|
|
143
|
-
console.error(error);
|
|
144
|
-
}
|
|
145
|
-
return datasets;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* The records a repeat renders, in the order it renders them.
|
|
149
|
-
*
|
|
150
|
-
* `orderBy('order')` reads the first group, and matches only documents that
|
|
151
|
-
* carry the field. When it comes back short, every ordered record is already
|
|
152
|
-
* in hand — so at most that many rows of the first page by document id can be
|
|
153
|
-
* ordered, and the rest of that page is exactly the first unordered records.
|
|
154
|
-
* Two bounded reads, never a walk of the collection; which rows win, and in
|
|
155
|
-
* what order, is {@link repeatRecordsFromPages}, the rule the besigner's
|
|
156
|
-
* canvas preview reads through as well (AGL-3111).
|
|
157
|
-
*/ async function readRepeatRecords(datasetRef) {
|
|
158
|
-
const recordsRef = datasetRef.collection('records');
|
|
159
|
-
const page = (snapshot)=>snapshot.docs.map((doc)=>({
|
|
160
|
-
id: doc.id,
|
|
161
|
-
data: doc.data()
|
|
162
|
-
}));
|
|
163
|
-
const byOrder = await recordsRef.orderBy('order').limit(Aglyn.REPEAT_MAX_RECORDS).get();
|
|
164
|
-
const byId = byOrder.docs.length < Aglyn.REPEAT_MAX_RECORDS ? await recordsRef.orderBy(FieldPath.documentId()).limit(Aglyn.REPEAT_MAX_RECORDS).get() : undefined;
|
|
165
|
-
return repeatRecordsFromPages(page(byOrder), byId ? page(byId) : []);
|
|
166
|
-
}
|
|
167
|
-
export default getDatasets;
|
|
168
|
-
|
|
169
|
-
//# sourceMappingURL=get-datasets.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/get-datasets.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 * as Aglyn from '@aglyn/aglyn/server'\nimport { orgDataQueryForHost } from '@aglyn/tenant-data-admin'\nimport {\n PUBLISHED_SITE_DATA_TTL_SECONDS,\n tenantDataTag,\n withRenderCache,\n} from '@aglyn/tenant-data-admin/render-cache'\nimport { FieldPath } from 'firebase-admin/firestore'\nimport { repeatRecordsFromPages } from './repeat-record-pages'\n\n/**\n * The render path's largest read (AGL-1302): up to two pages of records per\n * dataset a page repeats over. Records are mapped to plain values before\n * return, so the cached shape is what repeatable expansion consumes. Backstop\n * only; the publish path busts the tag — see PUBLISHED_SITE_DATA_TTL_SECONDS.\n */\nconst DATASETS_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS\n\n/**\n * The datasets a page repeats over, with their records, for repeatable\n * expansion (AGL-103).\n *\n * `keys` are the page's repeat keys (`repeatDatasetKeys`), each a dataset id or\n * a display name — editors type the friendly name into the Repeat attribute.\n * The map answers under every key exactly as written, and under every loaded\n * dataset's id, which is how a reference hop (AGL-180) finds its target.\n * Records are editor-ordered and capped at the repeat bound.\n *\n * Only the datasets the page names are read. A site may see thousands of\n * datasets, and a read of \"the site's datasets\" can only ever be a bounded\n * page of them, so a repeat bound to one outside that page rendered its\n * template once with nothing to say why.\n *\n * Fail-open, per key: a key that cannot be read is left out, and its\n * repeatable renders its template untouched, without costing the others.\n *\n * Scoped to what THIS host may see (AGL-1039): a display name resolves inside\n * the host's scope, and an id outside it resolves to nothing. The Admin SDK\n * does not evaluate rules, so AGL-1041 does not cover this path; the filter\n * has to live here.\n */\nexport async function getDatasets(options: {\n hostId: string\n keys: readonly string[]\n}): Promise<Record<string, Aglyn.RepeatableDataset>> {\n const keys = [\n ...new Set(options.keys.map((key) => key.trim()).filter(Boolean)),\n ].sort()\n if (!keys.length) return {}\n try {\n return await withRenderCache({\n key: ['tenant-datasets', options.hostId, ...keys],\n revalidate: DATASETS_TTL_SECONDS,\n tags: [tenantDataTag(options.hostId)],\n read: () => readDatasets(options.hostId, keys),\n })\n } catch (error) {\n console.error(error)\n return readDatasets(options.hostId, keys)\n }\n}\n\nasync function readDatasets(\n hostId: string,\n keys: readonly string[],\n): Promise<Record<string, Aglyn.RepeatableDataset>> {\n const datasets: Record<string, Aglyn.RepeatableDataset> = {}\n try {\n const { ref, query } = await orgDataQueryForHost(hostId, 'datasets')\n // The same visibility rule `resolveDatasetDoc` applies: a keyed read\n // bypasses the scoped query, so the scope is checked on the document.\n const orgScoped = ref.parent?.parent?.id === 'orgs'\n const usable = (snapshot: FirebaseFirestore.DocumentSnapshot | undefined) =>\n Boolean(snapshot?.exists) &&\n !snapshot?.get('deletedAt') &&\n (!orgScoped || Aglyn.visibleToHost(snapshot?.get('visibleTo'), hostId))\n\n const loads = new Map<string, Promise<Aglyn.RepeatableDataset>>()\n const load = (snapshot: FirebaseFirestore.DocumentSnapshot) => {\n let dataset = loads.get(snapshot.id)\n if (!dataset) {\n dataset = readRepeatRecords(snapshot.ref).then((records) => ({\n records,\n model: Aglyn.effectiveDatasetModel(\n snapshot.data() as Aglyn.HostDataset,\n ),\n }))\n loads.set(snapshot.id, dataset)\n }\n return dataset\n }\n\n // An id first, then a display name. An id that exists but that this host\n // cannot see does not fall through to the name: that would answer \"which\n // dataset is called X\" for a key that already named a specific one.\n const resolveKey = async (key: string) => {\n if (!key.includes('/')) {\n const byId = await ref.doc(key).get()\n if (byId.exists) return usable(byId) ? byId : undefined\n }\n const byName = await query.where('displayName', '==', key).limit(1).get()\n return byName.docs.find((snapshot) => usable(snapshot))\n }\n\n const resolved = await Promise.all(\n keys.map(async (key) => {\n try {\n const snapshot = await resolveKey(key)\n if (!snapshot) return undefined\n const dataset = await load(snapshot)\n datasets[key] = dataset\n datasets[snapshot.id] = dataset\n return dataset\n } catch (error) {\n console.error(error)\n return undefined\n }\n }),\n )\n\n // One reference hop (AGL-180): `{{item.author.name}}` reads the target's\n // rows by id, so the target has to be loaded even though no repeat names it.\n const targets = new Set<string>()\n for (const dataset of resolved) {\n for (const fieldId of dataset?.model?.order ?? []) {\n const field = dataset?.model?.fields[fieldId]\n const targetId =\n field?.type === 'reference' ? field.reference?.datasetId : undefined\n if (targetId && !targetId.includes('/') && !datasets[targetId]) {\n targets.add(targetId)\n }\n }\n }\n await Promise.all(\n [...targets].map(async (targetId) => {\n try {\n const target = await ref.doc(targetId).get()\n if (usable(target)) datasets[targetId] = await load(target)\n } catch (error) {\n console.error(error)\n }\n }),\n )\n } catch (error) {\n console.error(error)\n }\n return datasets\n}\n\n/**\n * The records a repeat renders, in the order it renders them.\n *\n * `orderBy('order')` reads the first group, and matches only documents that\n * carry the field. When it comes back short, every ordered record is already\n * in hand — so at most that many rows of the first page by document id can be\n * ordered, and the rest of that page is exactly the first unordered records.\n * Two bounded reads, never a walk of the collection; which rows win, and in\n * what order, is {@link repeatRecordsFromPages}, the rule the besigner's\n * canvas preview reads through as well (AGL-3111).\n */\nasync function readRepeatRecords(\n datasetRef: FirebaseFirestore.DocumentReference,\n): Promise<Array<Record<string, unknown>>> {\n const recordsRef = datasetRef.collection('records')\n const page = (snapshot: FirebaseFirestore.QuerySnapshot) =>\n snapshot.docs.map((doc) => ({\n id: doc.id,\n data: doc.data() as Aglyn.HostDatasetRecord,\n }))\n const byOrder = await recordsRef\n .orderBy('order')\n .limit(Aglyn.REPEAT_MAX_RECORDS)\n .get()\n const byId =\n byOrder.docs.length < Aglyn.REPEAT_MAX_RECORDS\n ? await recordsRef\n .orderBy(FieldPath.documentId())\n .limit(Aglyn.REPEAT_MAX_RECORDS)\n .get()\n : undefined\n return repeatRecordsFromPages(page(byOrder), byId ? page(byId) : [])\n}\n\nexport default getDatasets\n"],"names":["Aglyn","orgDataQueryForHost","PUBLISHED_SITE_DATA_TTL_SECONDS","tenantDataTag","withRenderCache","FieldPath","repeatRecordsFromPages","DATASETS_TTL_SECONDS","getDatasets","options","keys","Set","map","key","trim","filter","Boolean","sort","length","hostId","revalidate","tags","read","readDatasets","error","console","datasets","ref","query","orgScoped","parent","id","usable","snapshot","exists","get","visibleToHost","loads","Map","load","dataset","readRepeatRecords","then","records","model","effectiveDatasetModel","data","set","resolveKey","includes","byId","doc","undefined","byName","where","limit","docs","find","resolved","Promise","all","targets","fieldId","order","field","fields","targetId","type","reference","datasetId","add","target","datasetRef","recordsRef","collection","page","byOrder","orderBy","REPEAT_MAX_RECORDS","documentId"],"mappings":"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"}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
/** The prop a rendered `form` node carries its signed dataset binding in. */
|
|
18
|
-
export declare const FORM_DATASET_BINDING_PROP = "datasetBindingToken";
|
|
19
|
-
/**
|
|
20
|
-
* Signs every `form` node's dataset binding into its props, for the submit
|
|
21
|
-
* route to verify (AGL-2773).
|
|
22
|
-
*
|
|
23
|
-
* The route writes a record only where a valid signature says, so a form
|
|
24
|
-
* without one still submits — the Inbox copy is always written — but adds no
|
|
25
|
-
* record. That is what happens when `TOKEN_SIGNING_SECRET` is missing: the
|
|
26
|
-
* tree is returned unsigned rather than failing the page render, and the
|
|
27
|
-
* missing secret is reported once per process instead of once per render.
|
|
28
|
-
*
|
|
29
|
-
* Never mutates `nodes`; a tree with no bound form comes back as the same
|
|
30
|
-
* object.
|
|
31
|
-
*/
|
|
32
|
-
export declare function stampFormDatasetBindings<N extends Record<string, any>>(nodes: N, hostId: string): N;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
-
/**
|
|
3
|
-
* @license
|
|
4
|
-
* Copyright 2026 Aglyn LLC
|
|
5
|
-
*
|
|
6
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
-
* you may not use this file except in compliance with the License.
|
|
8
|
-
* You may obtain a copy of the License at
|
|
9
|
-
*
|
|
10
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
-
*
|
|
12
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
-
* See the License for the specific language governing permissions and
|
|
16
|
-
* limitations under the License.
|
|
17
|
-
*/ import { formDatasetBindingsOf } from "@aglyn/aglyn/app-utils/form-dataset-binding";
|
|
18
|
-
import { signFormDatasetBinding } from "@aglyn/tenant-data-admin/server/form-dataset-binding-token";
|
|
19
|
-
/** The prop a rendered `form` node carries its signed dataset binding in. */ export const FORM_DATASET_BINDING_PROP = 'datasetBindingToken';
|
|
20
|
-
/** Whether an unsigned render has been reported in this process yet. */ let reportedUnsigned = false;
|
|
21
|
-
/**
|
|
22
|
-
* Signs every `form` node's dataset binding into its props, for the submit
|
|
23
|
-
* route to verify (AGL-2773).
|
|
24
|
-
*
|
|
25
|
-
* The route writes a record only where a valid signature says, so a form
|
|
26
|
-
* without one still submits — the Inbox copy is always written — but adds no
|
|
27
|
-
* record. That is what happens when `TOKEN_SIGNING_SECRET` is missing: the
|
|
28
|
-
* tree is returned unsigned rather than failing the page render, and the
|
|
29
|
-
* missing secret is reported once per process instead of once per render.
|
|
30
|
-
*
|
|
31
|
-
* Never mutates `nodes`; a tree with no bound form comes back as the same
|
|
32
|
-
* object.
|
|
33
|
-
*/ export function stampFormDatasetBindings(nodes, hostId) {
|
|
34
|
-
const bindings = formDatasetBindingsOf(nodes);
|
|
35
|
-
if (!bindings.length) return nodes;
|
|
36
|
-
const signed = _extends({}, nodes);
|
|
37
|
-
for (const { nodeId, binding } of bindings){
|
|
38
|
-
var _ref;
|
|
39
|
-
let token;
|
|
40
|
-
try {
|
|
41
|
-
token = signFormDatasetBinding(hostId, binding);
|
|
42
|
-
} catch (error) {
|
|
43
|
-
if (!reportedUnsigned) {
|
|
44
|
-
reportedUnsigned = true;
|
|
45
|
-
console.error('Form dataset bindings are not being signed, so forms bound to a ' + 'dataset will not write records', error);
|
|
46
|
-
}
|
|
47
|
-
return nodes;
|
|
48
|
-
}
|
|
49
|
-
const node = signed[nodeId];
|
|
50
|
-
signed[nodeId] = _extends({}, node, {
|
|
51
|
-
props: _extends({}, (_ref = node == null ? void 0 : node.props) != null ? _ref : {}, {
|
|
52
|
-
[FORM_DATASET_BINDING_PROP]: token
|
|
53
|
-
})
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
return signed;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
//# sourceMappingURL=stamp-form-dataset-bindings.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/stamp-form-dataset-bindings.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 { formDatasetBindingsOf } from '@aglyn/aglyn/app-utils/form-dataset-binding'\nimport { signFormDatasetBinding } from '@aglyn/tenant-data-admin/server/form-dataset-binding-token'\n\n/** The prop a rendered `form` node carries its signed dataset binding in. */\nexport const FORM_DATASET_BINDING_PROP = 'datasetBindingToken'\n\n/** Whether an unsigned render has been reported in this process yet. */\nlet reportedUnsigned = false\n\n/**\n * Signs every `form` node's dataset binding into its props, for the submit\n * route to verify (AGL-2773).\n *\n * The route writes a record only where a valid signature says, so a form\n * without one still submits — the Inbox copy is always written — but adds no\n * record. That is what happens when `TOKEN_SIGNING_SECRET` is missing: the\n * tree is returned unsigned rather than failing the page render, and the\n * missing secret is reported once per process instead of once per render.\n *\n * Never mutates `nodes`; a tree with no bound form comes back as the same\n * object.\n */\nexport function stampFormDatasetBindings<N extends Record<string, any>>(\n nodes: N,\n hostId: string,\n): N {\n const bindings = formDatasetBindingsOf(nodes as never)\n if (!bindings.length) return nodes\n const signed: Record<string, any> = { ...nodes }\n for (const { nodeId, binding } of bindings) {\n let token: string\n try {\n token = signFormDatasetBinding(hostId, binding)\n } catch (error) {\n if (!reportedUnsigned) {\n reportedUnsigned = true\n console.error(\n 'Form dataset bindings are not being signed, so forms bound to a ' +\n 'dataset will not write records',\n error,\n )\n }\n return nodes\n }\n const node = signed[nodeId]\n signed[nodeId] = {\n ...node,\n props: { ...(node?.props ?? {}), [FORM_DATASET_BINDING_PROP]: token },\n }\n }\n return signed as N\n}\n"],"names":["formDatasetBindingsOf","signFormDatasetBinding","FORM_DATASET_BINDING_PROP","reportedUnsigned","stampFormDatasetBindings","nodes","hostId","bindings","length","signed","nodeId","binding","token","error","console","node","props"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,qBAAqB,QAAQ,8CAA6C;AACnF,SAASC,sBAAsB,QAAQ,6DAA4D;AAEnG,2EAA2E,GAC3E,OAAO,MAAMC,4BAA4B,sBAAqB;AAE9D,sEAAsE,GACtE,IAAIC,mBAAmB;AAEvB;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC,yBACdC,KAAQ,EACRC,MAAc;IAEd,MAAMC,WAAWP,sBAAsBK;IACvC,IAAI,CAACE,SAASC,MAAM,EAAE,OAAOH;IAC7B,MAAMI,SAA8B,aAAKJ;IACzC,KAAK,MAAM,EAAEK,MAAM,EAAEC,OAAO,EAAE,IAAIJ,SAAU;;QAC1C,IAAIK;QACJ,IAAI;YACFA,QAAQX,uBAAuBK,QAAQK;QACzC,EAAE,OAAOE,OAAO;YACd,IAAI,CAACV,kBAAkB;gBACrBA,mBAAmB;gBACnBW,QAAQD,KAAK,CACX,qEACE,kCACFA;YAEJ;YACA,OAAOR;QACT;QACA,MAAMU,OAAON,MAAM,CAACC,OAAO;QAC3BD,MAAM,CAACC,OAAO,GAAG,aACZK;YACHC,OAAO,qBAAMD,wBAAAA,KAAMC,KAAK,mBAAI,CAAC;gBAAI,CAACd,0BAA0B,EAAEU;;;IAElE;IACA,OAAOH;AACT"}
|