@aglyn/tenant-runtime 1.0.0-beta.143

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.
Files changed (120) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +26 -0
  3. package/package.json +39 -0
  4. package/src/index.d.ts +21 -0
  5. package/src/index.js +22 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/apply-publish-schedule.d.ts +34 -0
  8. package/src/lib/apply-publish-schedule.js +285 -0
  9. package/src/lib/apply-publish-schedule.js.map +1 -0
  10. package/src/lib/assign-contact-owner.d.ts +162 -0
  11. package/src/lib/assign-contact-owner.js +322 -0
  12. package/src/lib/assign-contact-owner.js.map +1 -0
  13. package/src/lib/associate-company-by-domain.d.ts +70 -0
  14. package/src/lib/associate-company-by-domain.js +150 -0
  15. package/src/lib/associate-company-by-domain.js.map +1 -0
  16. package/src/lib/author-page-nodes.d.ts +36 -0
  17. package/src/lib/author-page-nodes.js +296 -0
  18. package/src/lib/author-page-nodes.js.map +1 -0
  19. package/src/lib/built-in-page-layout.d.ts +48 -0
  20. package/src/lib/built-in-page-layout.js +107 -0
  21. package/src/lib/built-in-page-layout.js.map +1 -0
  22. package/src/lib/capture-host-contact.d.ts +95 -0
  23. package/src/lib/capture-host-contact.js +137 -0
  24. package/src/lib/capture-host-contact.js.map +1 -0
  25. package/src/lib/collection-fallback-nodes.d.ts +90 -0
  26. package/src/lib/collection-fallback-nodes.js +602 -0
  27. package/src/lib/collection-fallback-nodes.js.map +1 -0
  28. package/src/lib/compose-author-page.d.ts +109 -0
  29. package/src/lib/compose-author-page.js +225 -0
  30. package/src/lib/compose-author-page.js.map +1 -0
  31. package/src/lib/compose-collection-page.d.ts +94 -0
  32. package/src/lib/compose-collection-page.js +256 -0
  33. package/src/lib/compose-collection-page.js.map +1 -0
  34. package/src/lib/compose-screen-nodes.d.ts +156 -0
  35. package/src/lib/compose-screen-nodes.js +547 -0
  36. package/src/lib/compose-screen-nodes.js.map +1 -0
  37. package/src/lib/compose-search-page.d.ts +39 -0
  38. package/src/lib/compose-search-page.js +57 -0
  39. package/src/lib/compose-search-page.js.map +1 -0
  40. package/src/lib/convert-host-lead.d.ts +189 -0
  41. package/src/lib/convert-host-lead.js +443 -0
  42. package/src/lib/convert-host-lead.js.map +1 -0
  43. package/src/lib/defer-lazy-panels.d.ts +58 -0
  44. package/src/lib/defer-lazy-panels.js +171 -0
  45. package/src/lib/defer-lazy-panels.js.map +1 -0
  46. package/src/lib/element-animation-assets.d.ts +62 -0
  47. package/src/lib/element-animation-assets.js +290 -0
  48. package/src/lib/element-animation-assets.js.map +1 -0
  49. package/src/lib/emit-host-event.d.ts +29 -0
  50. package/src/lib/emit-host-event.js +30 -0
  51. package/src/lib/emit-host-event.js.map +1 -0
  52. package/src/lib/enrich-gated-page.d.ts +67 -0
  53. package/src/lib/enrich-gated-page.js +134 -0
  54. package/src/lib/enrich-gated-page.js.map +1 -0
  55. package/src/lib/entry-link-routes.d.ts +61 -0
  56. package/src/lib/entry-link-routes.js +169 -0
  57. package/src/lib/entry-link-routes.js.map +1 -0
  58. package/src/lib/get-author-content.d.ts +119 -0
  59. package/src/lib/get-author-content.js +288 -0
  60. package/src/lib/get-author-content.js.map +1 -0
  61. package/src/lib/get-collection-content.d.ts +290 -0
  62. package/src/lib/get-collection-content.js +565 -0
  63. package/src/lib/get-collection-content.js.map +1 -0
  64. package/src/lib/get-components.d.ts +24 -0
  65. package/src/lib/get-components.js +102 -0
  66. package/src/lib/get-components.js.map +1 -0
  67. package/src/lib/get-datasets.d.ts +45 -0
  68. package/src/lib/get-datasets.js +169 -0
  69. package/src/lib/get-datasets.js.map +1 -0
  70. package/src/lib/get-forms.d.ts +24 -0
  71. package/src/lib/get-forms.js +116 -0
  72. package/src/lib/get-forms.js.map +1 -0
  73. package/src/lib/get-layout-version.d.ts +26 -0
  74. package/src/lib/get-layout-version.js +99 -0
  75. package/src/lib/get-layout-version.js.map +1 -0
  76. package/src/lib/get-media-asset-facts.d.ts +74 -0
  77. package/src/lib/get-media-asset-facts.js +116 -0
  78. package/src/lib/get-media-asset-facts.js.map +1 -0
  79. package/src/lib/get-plugin-installs.d.ts +29 -0
  80. package/src/lib/get-plugin-installs.js +94 -0
  81. package/src/lib/get-plugin-installs.js.map +1 -0
  82. package/src/lib/get-screen-version.d.ts +27 -0
  83. package/src/lib/get-screen-version.js +66 -0
  84. package/src/lib/get-screen-version.js.map +1 -0
  85. package/src/lib/get-screen.d.ts +39 -0
  86. package/src/lib/get-screen.js +126 -0
  87. package/src/lib/get-screen.js.map +1 -0
  88. package/src/lib/get-variables.d.ts +41 -0
  89. package/src/lib/get-variables.js +128 -0
  90. package/src/lib/get-variables.js.map +1 -0
  91. package/src/lib/get-video-asset-facts.d.ts +22 -0
  92. package/src/lib/get-video-asset-facts.js +22 -0
  93. package/src/lib/get-video-asset-facts.js.map +1 -0
  94. package/src/lib/host-event-listeners.d.ts +83 -0
  95. package/src/lib/host-event-listeners.js +91 -0
  96. package/src/lib/host-event-listeners.js.map +1 -0
  97. package/src/lib/org-permissions.d.ts +63 -0
  98. package/src/lib/org-permissions.js +187 -0
  99. package/src/lib/org-permissions.js.map +1 -0
  100. package/src/lib/repeat-record-pages.d.ts +48 -0
  101. package/src/lib/repeat-record-pages.js +64 -0
  102. package/src/lib/repeat-record-pages.js.map +1 -0
  103. package/src/lib/required-site-plugins.d.ts +114 -0
  104. package/src/lib/required-site-plugins.js +121 -0
  105. package/src/lib/required-site-plugins.js.map +1 -0
  106. package/src/lib/resolve-dataset.d.ts +40 -0
  107. package/src/lib/resolve-dataset.js +64 -0
  108. package/src/lib/resolve-dataset.js.map +1 -0
  109. package/src/lib/search-results-nodes.d.ts +48 -0
  110. package/src/lib/search-results-nodes.js +302 -0
  111. package/src/lib/search-results-nodes.js.map +1 -0
  112. package/src/lib/social-image-facts.d.ts +107 -0
  113. package/src/lib/social-image-facts.js +133 -0
  114. package/src/lib/social-image-facts.js.map +1 -0
  115. package/src/lib/stamp-form-dataset-bindings.d.ts +32 -0
  116. package/src/lib/stamp-form-dataset-bindings.js +59 -0
  117. package/src/lib/stamp-form-dataset-bindings.js.map +1 -0
  118. package/src/lib/template-screens.d.ts +227 -0
  119. package/src/lib/template-screens.js +364 -0
  120. package/src/lib/template-screens.js.map +1 -0
@@ -0,0 +1,364 @@
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 { SCREEN_KIND_TEMPLATE } from "@aglyn/aglyn/server";
17
+ import { firebaseAdmin } from "@aglyn/tenant-data-admin";
18
+ import { PUBLISHED_SITE_DATA_TTL_SECONDS, tenantDataTag, withRenderCache } from "@aglyn/tenant-data-admin/render-cache";
19
+ /**
20
+ * Cached as an ARRAY (a `Set` does not survive the cache's JSON round trip)
21
+ * and rehydrated at the boundary (AGL-1302). Publishing a template screen
22
+ * re-points a collection or the store settings, and both writes ride the
23
+ * same publish flow that busts `tenant-data:{hostId}`; 60s is the backstop.
24
+ */ const TEMPLATE_SCREEN_IDS_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS;
25
+ /**
26
+ * The three fields a CONTENT collection can point at a template screen with.
27
+ * `templateScreenId` is the legacy AGL-105 field `resolveCollectionTemplateScreenId`
28
+ * still falls back to, so hosts predating the list/entry split are covered.
29
+ *
30
+ * `apps/console/app/api/hosts/resources/count-billable-screens.ts` subtracts
31
+ * this same set from the plan's screen allowance. It deliberately does NOT
32
+ * subtract the store fields below — what a template costs the subscriber is a
33
+ * separate question from whether it is a page.
34
+ */ export const COLLECTION_TEMPLATE_SCREEN_FIELDS = [
35
+ 'listScreenId',
36
+ 'entryScreenId',
37
+ 'templateScreenId'
38
+ ];
39
+ /**
40
+ * The two fields COMMERCE points at a template screen with (AGL-1270). They
41
+ * live on `hosts/{hostId}/settings/store` (AGL-295), not on a collection doc,
42
+ * which is the only reason AGL-1267's fix did not already reach them —
43
+ * `pdpScreenId` renders `/products/{slug}` and `collectionScreenId` renders
44
+ * `/collections/{slug}`, both of which are templates in exactly the same sense.
45
+ *
46
+ * NOT yet mirrored in `apps/console/constants/collection-templates.ts`, which
47
+ * re-declares the collection fields for its `'use client'` readers: publishing a
48
+ * PDP template still toasts the author a route it does not have (the AGL-1269
49
+ * half of AGL-1270). Widening it is a console change and belongs with that file.
50
+ */ export const STORE_TEMPLATE_SCREEN_FIELDS = [
51
+ 'pdpScreenId',
52
+ 'collectionScreenId'
53
+ ];
54
+ /**
55
+ * The field the HOST points a template screen at (AGL-2518).
56
+ *
57
+ * One so far: the screen every `/author/{slug}` page renders through. It sits
58
+ * on the host rather than on a collection because there is one author page
59
+ * shape per SITE, not per collection.
60
+ *
61
+ * Listed here rather than left to `kind: 'template'` because designating this
62
+ * screen deliberately does NOT demote it. An author template is a designed
63
+ * page in exactly the sense a collection's LIST template is (AGL-1387) — it
64
+ * stays billable, and the site keeps paying for the page it built — but like
65
+ * that one it must still 404 at the slug it was published under, or the site
66
+ * ships a page rendering raw `{{author.*}}` as body text. That is AGL-1267's
67
+ * defect, and the pointer half of this union is what closes it without the
68
+ * stamp that would also make the screen free (AGL-1400's settlement: a
69
+ * pointer excuses nothing, only the stamp does).
70
+ */ export const HOST_TEMPLATE_SCREEN_FIELDS = [
71
+ 'authorScreenId'
72
+ ];
73
+ /**
74
+ * Every screen id this host treats as a template — the screens that SAY so
75
+ * (`kind: 'template'`), plus a content collection's list/entry screens and
76
+ * commerce's PDP and catalog-collection screens. Pure, so the routing rule can
77
+ * be tested without Firestore.
78
+ *
79
+ * A UNION, and each half covers what the other cannot (AGL-1400):
80
+ *
81
+ * - The `kind` half reaches a template no pointer names any more. Clearing a
82
+ * collection's `entryScreenId` deliberately does NOT promote the screen back
83
+ * to a page — promotion is a gated act — so without this an orphaned
84
+ * template would become reachable at its own slug again, rendering raw
85
+ * `{{entry.*}}` tokens: AGL-1267, back.
86
+ * - The pointer half reaches the templates that are NOT `kind: 'template'`,
87
+ * and there are two kinds of those on purpose. A collection's LIST template
88
+ * is a page (AGL-1387) — billable, and served at `/{collectionSlug}` — but
89
+ * it must still 404 at the slug it was published under. Commerce's PDP and
90
+ * catalog templates are billable today too, and re-pricing them is a
91
+ * separate decision from this one.
92
+ *
93
+ * Any source may be absent (`null`/`undefined`): a host with no store settings
94
+ * doc, or a read that failed on its own while the others succeeded.
95
+ */ export function collectTemplateScreenIds(sources) {
96
+ var _ref, _ref1;
97
+ var _sources_templateScreens, _sources_collections;
98
+ const ids = new Set();
99
+ const add = (screenId)=>{
100
+ if (typeof screenId === 'string' && screenId) ids.add(screenId);
101
+ };
102
+ for (const screen of (_ref = (_sources_templateScreens = sources.templateScreens) == null ? void 0 : _sources_templateScreens.docs) != null ? _ref : [])add(screen.id);
103
+ for (const contentCollection of (_ref1 = (_sources_collections = sources.collections) == null ? void 0 : _sources_collections.docs) != null ? _ref1 : []){
104
+ for (const field of COLLECTION_TEMPLATE_SCREEN_FIELDS){
105
+ add(contentCollection.get(field));
106
+ }
107
+ }
108
+ if (sources.storeSettings) {
109
+ for (const field of STORE_TEMPLATE_SCREEN_FIELDS){
110
+ add(sources.storeSettings.get(field));
111
+ }
112
+ }
113
+ if (sources.host) {
114
+ for (const field of HOST_TEMPLATE_SCREEN_FIELDS){
115
+ add(sources.host.get(field));
116
+ }
117
+ }
118
+ return ids;
119
+ }
120
+ /**
121
+ * Where a collection's LIST template is actually served, keyed by screen id
122
+ * (AGL-1998): `blogListTmpl` → `blog`, in the routing map's own path format.
123
+ *
124
+ * A list template is the one template kind that IS a page (AGL-1387) — and it
125
+ * is a page at somebody ELSE's address. `/{collectionSlug}` renders it through
126
+ * `composeCollectionTemplatePage`, while the slug it was published under is
127
+ * dropped from the route table by {@link getTemplateScreenIds} and 404s. So
128
+ * the routing map says one thing and the router does another, and everything
129
+ * that RESOLVES a screen link believed the map: on aglyn.com no screen link
130
+ * could point at `/blog` at all, because the only entry for the screen that
131
+ * serves it read `blog-list-template`.
132
+ *
133
+ * Two conditions, matching `collectionListTemplateScreenIds` in the console
134
+ * (which answers the same question for the Screens list's Path column):
135
+ *
136
+ * - **A slug**, since `/{slug}` is the whole address; and
137
+ * - **content kind**, because only a content collection answers `/{slug}` —
138
+ * a catalog collection's listing is `/collections/{slug}`, composed by
139
+ * commerce, and pointing a link there through this map would send visitors
140
+ * to a path that does not exist.
141
+ *
142
+ * Publishing is deliberately NOT a condition: the collection branch resolves
143
+ * the screen through `listScreenId` and never through the routing map, so an
144
+ * unpublished list template serves `/{slug}` exactly like a published one.
145
+ * That is the live blog's state today.
146
+ */ export function collectCollectionListRoutes(sources) {
147
+ var _ref;
148
+ var _sources_collections;
149
+ const routes = {};
150
+ for (const contentCollection of (_ref = (_sources_collections = sources.collections) == null ? void 0 : _sources_collections.docs) != null ? _ref : []){
151
+ // Mirrors `hostCollectionKind`: only `kind: 'catalog'` is catalog, and a
152
+ // document that does not say what it is reads as content.
153
+ if (contentCollection.get('kind') === 'catalog') continue;
154
+ const slug = contentCollection.get('slug');
155
+ if (typeof slug !== 'string' || !slug) continue;
156
+ const listScreenId = contentCollection.get('listScreenId');
157
+ if (typeof listScreenId !== 'string' || !listScreenId) continue;
158
+ routes[listScreenId] = slug;
159
+ }
160
+ return routes;
161
+ }
162
+ /**
163
+ * Every content collection's LISTING as a link target: collection id → slug
164
+ * (AGL-2799).
165
+ *
166
+ * `/{slug}` serves a content collection's listing whether or not a list
167
+ * template screen renders it — the built-in fallback composes one otherwise —
168
+ * so this is a different fact from {@link collectCollectionListRoutes}, which
169
+ * answers only for the collections that have such a screen. A link authored
170
+ * against a listing stores the collection's id, and `linkableScreenRoutes`
171
+ * turns this table into the `collection:<id>` entries it resolves through.
172
+ *
173
+ * The same two conditions as the list routes, for the same reasons: a slug,
174
+ * since `/{slug}` is the whole address, and content kind, since a catalog
175
+ * collection's listing is commerce's `/collections/{slug}`. `id` is optional
176
+ * on the structural snapshot type, so a document without one is skipped
177
+ * rather than keyed as `undefined`.
178
+ */ export function collectCollectionListings(sources) {
179
+ var _ref;
180
+ var _sources_collections;
181
+ const listings = {};
182
+ for (const contentCollection of (_ref = (_sources_collections = sources.collections) == null ? void 0 : _sources_collections.docs) != null ? _ref : []){
183
+ if (contentCollection.get('kind') === 'catalog') continue;
184
+ const collectionId = contentCollection.id;
185
+ if (typeof collectionId !== 'string' || !collectionId) continue;
186
+ const slug = contentCollection.get('slug');
187
+ if (typeof slug !== 'string' || !slug) continue;
188
+ listings[collectionId] = slug;
189
+ }
190
+ return listings;
191
+ }
192
+ /**
193
+ * Screens that must NOT resolve as pages of the site (AGL-1267, AGL-1270).
194
+ *
195
+ * A template screen is a composition input, not a page. It only has meaning
196
+ * against a routed subject — `/{collection}/{entry}` substitutes `{{entry.*}}`,
197
+ * `/products/{slug}` substitutes `{{product.*}}` — so there is no request for
198
+ * which serving it at its own slug produces something correct. Publishing one
199
+ * is what makes the compose pipeline pick it up (`publishScreenRoute` stamps
200
+ * `publishedAt`), but publishing ALSO writes `hosts/{hostId}.screens[screenId]`,
201
+ * which is the tenant router's whole route table. That second write is the bug:
202
+ * publishing the blog's entry template put a live `/blog-entry-template` page on
203
+ * the site rendering seven raw `{{entry.*}}` tokens as body text, and publishing
204
+ * a PDP template does the same with `{{product.*}}`.
205
+ *
206
+ * A stored marker AND the pointers (AGL-1400): `kind: 'template'` is the fact
207
+ * for an entry template, and the pointers still answer for the two template
208
+ * kinds that stay pages — a collection's LIST template (AGL-1387) and
209
+ * commerce's PDP/catalog templates. See {@link collectTemplateScreenIds} for
210
+ * why neither half subsumes the other. Re-pointing a collection (or the store)
211
+ * at a different screen still takes effect on the next revalidate.
212
+ *
213
+ * THREE reads, not one — and one read cannot cover them. The template screens
214
+ * and the collection templates are QUERIES over different subcollections; the
215
+ * store templates are a single document at `hosts/{h}/settings/store`. Firestore
216
+ * has no read that spans a query and a document in a different subcollection
217
+ * (`getAll` batches document refs only, and cannot take the query). So they are
218
+ * issued CONCURRENTLY: three RPCs, one round trip of latency, which is what the
219
+ * cold-start budget cares about (AGL-1152). Both queries carry a field mask —
220
+ * the screens one projects to ids alone — and the store doc is a single small
221
+ * doc read whole, since `select` is a Query method and there is nothing to
222
+ * project on a document get.
223
+ *
224
+ * Fails OPEN — an empty set on error, and PER SOURCE. This sits on the critical
225
+ * path of every tenant page render, and a transient Firestore error must degrade
226
+ * to "the template is briefly reachable again", never to a site-wide 404. Per
227
+ * source because a store-settings failure has no business re-exposing the blog's
228
+ * entry template, or vice versa.
229
+ *
230
+ * The reads themselves are {@link getTemplateScreenRouting}'s, which answers
231
+ * the other half of the same question in the same trip (AGL-1998). This is the
232
+ * narrow view of it, and stays a named export because the four callers that
233
+ * only route requests have no use for the other half.
234
+ */ export async function getTemplateScreenIds(options) {
235
+ return (await getTemplateScreenRouting(options)).templateScreenIds;
236
+ }
237
+ /**
238
+ * Both halves of what the route table has to be corrected by, from ONE read
239
+ * (AGL-1998): the screens to drop, and where a list template really lives.
240
+ *
241
+ * They come out of the same collections query — the ids need `listScreenId`,
242
+ * the routes need the `slug` beside it — so asking separately would be a
243
+ * second identical round trip on the render path this arc keeps trimming
244
+ * (AGL-1152). {@link getTemplateScreenIds} is the narrow view of it, kept as
245
+ * its own export because four callers want only the set.
246
+ *
247
+ * Never rejects, and fails open to "no templates, no overrides" — the routing
248
+ * map as published — for the reason on {@link getTemplateScreenIds}.
249
+ */ export async function getTemplateScreenRouting(options) {
250
+ try {
251
+ var _ref, _ref1, _ref2;
252
+ // A NEW key rather than the old `tenant-template-screens`: the cached
253
+ // value's shape changed from an array to an object, and a deploy that
254
+ // read the previous shape back under the previous key would hand every
255
+ // in-flight render an `ids` of `undefined`.
256
+ const cached = await withRenderCache({
257
+ key: [
258
+ 'tenant-template-routing',
259
+ options.hostId
260
+ ],
261
+ revalidate: TEMPLATE_SCREEN_IDS_TTL_SECONDS,
262
+ tags: [
263
+ tenantDataTag(options.hostId)
264
+ ],
265
+ read: async ()=>{
266
+ const routing = await readTemplateScreenRouting(options);
267
+ return {
268
+ ids: [
269
+ ...routing.templateScreenIds
270
+ ],
271
+ listRoutes: routing.listRoutes,
272
+ collectionListings: routing.collectionListings
273
+ };
274
+ }
275
+ });
276
+ return {
277
+ // `Set`s and `Map`s do not survive the cache's JSON round trip, so the
278
+ // stored shape is an array and it is rehydrated here.
279
+ templateScreenIds: new Set((_ref = cached == null ? void 0 : cached.ids) != null ? _ref : []),
280
+ listRoutes: (_ref1 = cached == null ? void 0 : cached.listRoutes) != null ? _ref1 : {},
281
+ // An entry written without this field reads as no listings until it
282
+ // revalidates, which the TTL above bounds.
283
+ collectionListings: (_ref2 = cached == null ? void 0 : cached.collectionListings) != null ? _ref2 : {}
284
+ };
285
+ } catch (error) {
286
+ // The contract is "never rejects" — a cache failure degrades to the
287
+ // uncached read, which itself fails open to an empty set.
288
+ console.error('template screen lookup failed:', error);
289
+ return readTemplateScreenRouting(options);
290
+ }
291
+ }
292
+ async function readTemplateScreenRouting(options) {
293
+ try {
294
+ const hostRef = firebaseAdmin.app().firestore().collection('hosts').doc(options.hostId);
295
+ const [templateScreens, collections, storeSettings, host] = await Promise.all([
296
+ // The screens that say so (AGL-1400). `select()` with no fields returns
297
+ // ids only, and the equality filter rides Firestore's automatic
298
+ // single-field index — no composite index, and no read of the 100+ screen
299
+ // documents a large site has.
300
+ hostRef.collection('screens').where('kind', '==', SCREEN_KIND_TEMPLATE).select().limit(200).get().catch((error)=>{
301
+ console.error('template screen kind lookup failed:', error);
302
+ return null;
303
+ }),
304
+ hostRef.collection('collections')// `slug` and `kind` ride along for AGL-1998: which route a LIST
305
+ // template is really served at is decided by the same document, and
306
+ // widening a field mask costs nothing next to a second query.
307
+ .select(...COLLECTION_TEMPLATE_SCREEN_FIELDS, 'slug', 'kind').limit(100).get().catch((error)=>{
308
+ console.error('collection template lookup failed:', error);
309
+ return null;
310
+ }),
311
+ // A host with commerce disabled — or one that never opened Store
312
+ // settings — has no doc here. A missing doc is not an error: `get()`
313
+ // resolves with `exists: false` and every field read returns undefined,
314
+ // so it contributes nothing and costs nothing else.
315
+ hostRef.collection('settings').doc('store').get().catch((error)=>{
316
+ console.error('store template lookup failed:', error);
317
+ return null;
318
+ }),
319
+ // The host's own template pointer (AGL-2518). A FOURTH concurrent RPC
320
+ // in the same round trip rather than a fourth round trip, which is what
321
+ // the cold-start budget cares about (AGL-1152) — and the document is
322
+ // one every request has already read through `getHostCached`, so it is
323
+ // warm by the time this asks for it.
324
+ // Wrapped rather than called directly, so this source fails open the
325
+ // way the three above it do. `.catch()` only guards a REJECTION; a
326
+ // synchronous throw here would escape into the outer catch, which
327
+ // returns an EMPTY set — and an empty set re-exposes every template
328
+ // screen on the site as a page. Per-source isolation is the contract
329
+ // this function states, and it has to hold for the source that reads
330
+ // a different shape of ref from the other three.
331
+ Promise.resolve().then(()=>hostRef.get()).catch((error)=>{
332
+ console.error('host template pointer lookup failed:', error);
333
+ return null;
334
+ })
335
+ ]);
336
+ return {
337
+ templateScreenIds: collectTemplateScreenIds({
338
+ templateScreens,
339
+ collections,
340
+ storeSettings,
341
+ host
342
+ }),
343
+ listRoutes: collectCollectionListRoutes({
344
+ collections
345
+ }),
346
+ // The same read again: a document's id comes with it whatever the
347
+ // field mask projects.
348
+ collectionListings: collectCollectionListings({
349
+ collections
350
+ })
351
+ };
352
+ } catch (error) {
353
+ // The ref construction itself threw (no initialised admin app, say).
354
+ console.error('template screen lookup failed:', error);
355
+ return {
356
+ templateScreenIds: new Set(),
357
+ listRoutes: {},
358
+ collectionListings: {}
359
+ };
360
+ }
361
+ }
362
+ export default getTemplateScreenIds;
363
+
364
+ //# sourceMappingURL=template-screens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/template-screens.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 { SCREEN_KIND_TEMPLATE } from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n PUBLISHED_SITE_DATA_TTL_SECONDS,\n tenantDataTag,\n withRenderCache,\n} from '@aglyn/tenant-data-admin/render-cache'\n\n/**\n * Cached as an ARRAY (a `Set` does not survive the cache's JSON round trip)\n * and rehydrated at the boundary (AGL-1302). Publishing a template screen\n * re-points a collection or the store settings, and both writes ride the\n * same publish flow that busts `tenant-data:{hostId}`; 60s is the backstop.\n */\nconst TEMPLATE_SCREEN_IDS_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS\n\n/**\n * The three fields a CONTENT collection can point at a template screen with.\n * `templateScreenId` is the legacy AGL-105 field `resolveCollectionTemplateScreenId`\n * still falls back to, so hosts predating the list/entry split are covered.\n *\n * `apps/console/app/api/hosts/resources/count-billable-screens.ts` subtracts\n * this same set from the plan's screen allowance. It deliberately does NOT\n * subtract the store fields below — what a template costs the subscriber is a\n * separate question from whether it is a page.\n */\nexport const COLLECTION_TEMPLATE_SCREEN_FIELDS = [\n 'listScreenId',\n 'entryScreenId',\n 'templateScreenId',\n] as const\n\n/**\n * The two fields COMMERCE points at a template screen with (AGL-1270). They\n * live on `hosts/{hostId}/settings/store` (AGL-295), not on a collection doc,\n * which is the only reason AGL-1267's fix did not already reach them —\n * `pdpScreenId` renders `/products/{slug}` and `collectionScreenId` renders\n * `/collections/{slug}`, both of which are templates in exactly the same sense.\n *\n * NOT yet mirrored in `apps/console/constants/collection-templates.ts`, which\n * re-declares the collection fields for its `'use client'` readers: publishing a\n * PDP template still toasts the author a route it does not have (the AGL-1269\n * half of AGL-1270). Widening it is a console change and belongs with that file.\n */\nexport const STORE_TEMPLATE_SCREEN_FIELDS = [\n 'pdpScreenId',\n 'collectionScreenId',\n] as const\n\n/**\n * The field the HOST points a template screen at (AGL-2518).\n *\n * One so far: the screen every `/author/{slug}` page renders through. It sits\n * on the host rather than on a collection because there is one author page\n * shape per SITE, not per collection.\n *\n * Listed here rather than left to `kind: 'template'` because designating this\n * screen deliberately does NOT demote it. An author template is a designed\n * page in exactly the sense a collection's LIST template is (AGL-1387) — it\n * stays billable, and the site keeps paying for the page it built — but like\n * that one it must still 404 at the slug it was published under, or the site\n * ships a page rendering raw `{{author.*}}` as body text. That is AGL-1267's\n * defect, and the pointer half of this union is what closes it without the\n * stamp that would also make the screen free (AGL-1400's settlement: a\n * pointer excuses nothing, only the stamp does).\n */\nexport const HOST_TEMPLATE_SCREEN_FIELDS = ['authorScreenId'] as const\n\n/** The shape this reads off a Firestore snapshot — kept structural so unit\n * tests don't need the admin SDK. */\ninterface FieldSnapshotLike {\n get(field: string): unknown\n}\n\ninterface QuerySnapshotLike {\n /** `id` is the document id — read only for a collection's listing. */\n docs: Array<FieldSnapshotLike & { id?: string }>\n}\n\n/** Screen documents that say what they are — only the id is read. */\ninterface IdSnapshotLike {\n id: string\n}\n\n/**\n * Every screen id this host treats as a template — the screens that SAY so\n * (`kind: 'template'`), plus a content collection's list/entry screens and\n * commerce's PDP and catalog-collection screens. Pure, so the routing rule can\n * be tested without Firestore.\n *\n * A UNION, and each half covers what the other cannot (AGL-1400):\n *\n * - The `kind` half reaches a template no pointer names any more. Clearing a\n * collection's `entryScreenId` deliberately does NOT promote the screen back\n * to a page — promotion is a gated act — so without this an orphaned\n * template would become reachable at its own slug again, rendering raw\n * `{{entry.*}}` tokens: AGL-1267, back.\n * - The pointer half reaches the templates that are NOT `kind: 'template'`,\n * and there are two kinds of those on purpose. A collection's LIST template\n * is a page (AGL-1387) — billable, and served at `/{collectionSlug}` — but\n * it must still 404 at the slug it was published under. Commerce's PDP and\n * catalog templates are billable today too, and re-pricing them is a\n * separate decision from this one.\n *\n * Any source may be absent (`null`/`undefined`): a host with no store settings\n * doc, or a read that failed on its own while the others succeeded.\n */\nexport function collectTemplateScreenIds(sources: {\n collections?: QuerySnapshotLike | null\n storeSettings?: FieldSnapshotLike | null\n templateScreens?: { docs: Array<IdSnapshotLike> } | null\n /** The host document, for {@link HOST_TEMPLATE_SCREEN_FIELDS}. */\n host?: FieldSnapshotLike | null\n}): Set<string> {\n const ids = new Set<string>()\n const add = (screenId: unknown) => {\n if (typeof screenId === 'string' && screenId) ids.add(screenId)\n }\n for (const screen of sources.templateScreens?.docs ?? []) add(screen.id)\n for (const contentCollection of sources.collections?.docs ?? []) {\n for (const field of COLLECTION_TEMPLATE_SCREEN_FIELDS) {\n add(contentCollection.get(field))\n }\n }\n if (sources.storeSettings) {\n for (const field of STORE_TEMPLATE_SCREEN_FIELDS) {\n add(sources.storeSettings.get(field))\n }\n }\n if (sources.host) {\n for (const field of HOST_TEMPLATE_SCREEN_FIELDS) {\n add(sources.host.get(field))\n }\n }\n return ids\n}\n\n/**\n * Where a collection's LIST template is actually served, keyed by screen id\n * (AGL-1998): `blogListTmpl` → `blog`, in the routing map's own path format.\n *\n * A list template is the one template kind that IS a page (AGL-1387) — and it\n * is a page at somebody ELSE's address. `/{collectionSlug}` renders it through\n * `composeCollectionTemplatePage`, while the slug it was published under is\n * dropped from the route table by {@link getTemplateScreenIds} and 404s. So\n * the routing map says one thing and the router does another, and everything\n * that RESOLVES a screen link believed the map: on aglyn.com no screen link\n * could point at `/blog` at all, because the only entry for the screen that\n * serves it read `blog-list-template`.\n *\n * Two conditions, matching `collectionListTemplateScreenIds` in the console\n * (which answers the same question for the Screens list's Path column):\n *\n * - **A slug**, since `/{slug}` is the whole address; and\n * - **content kind**, because only a content collection answers `/{slug}` —\n * a catalog collection's listing is `/collections/{slug}`, composed by\n * commerce, and pointing a link there through this map would send visitors\n * to a path that does not exist.\n *\n * Publishing is deliberately NOT a condition: the collection branch resolves\n * the screen through `listScreenId` and never through the routing map, so an\n * unpublished list template serves `/{slug}` exactly like a published one.\n * That is the live blog's state today.\n */\nexport function collectCollectionListRoutes(sources: {\n collections?: QuerySnapshotLike | null\n}): Record<string, string> {\n const routes: Record<string, string> = {}\n for (const contentCollection of sources.collections?.docs ?? []) {\n // Mirrors `hostCollectionKind`: only `kind: 'catalog'` is catalog, and a\n // document that does not say what it is reads as content.\n if (contentCollection.get('kind') === 'catalog') continue\n const slug = contentCollection.get('slug')\n if (typeof slug !== 'string' || !slug) continue\n const listScreenId = contentCollection.get('listScreenId')\n if (typeof listScreenId !== 'string' || !listScreenId) continue\n routes[listScreenId] = slug\n }\n return routes\n}\n\n/**\n * Every content collection's LISTING as a link target: collection id → slug\n * (AGL-2799).\n *\n * `/{slug}` serves a content collection's listing whether or not a list\n * template screen renders it — the built-in fallback composes one otherwise —\n * so this is a different fact from {@link collectCollectionListRoutes}, which\n * answers only for the collections that have such a screen. A link authored\n * against a listing stores the collection's id, and `linkableScreenRoutes`\n * turns this table into the `collection:<id>` entries it resolves through.\n *\n * The same two conditions as the list routes, for the same reasons: a slug,\n * since `/{slug}` is the whole address, and content kind, since a catalog\n * collection's listing is commerce's `/collections/{slug}`. `id` is optional\n * on the structural snapshot type, so a document without one is skipped\n * rather than keyed as `undefined`.\n */\nexport function collectCollectionListings(sources: {\n collections?: QuerySnapshotLike | null\n}): Record<string, string> {\n const listings: Record<string, string> = {}\n for (const contentCollection of sources.collections?.docs ?? []) {\n if (contentCollection.get('kind') === 'catalog') continue\n const collectionId = contentCollection.id\n if (typeof collectionId !== 'string' || !collectionId) continue\n const slug = contentCollection.get('slug')\n if (typeof slug !== 'string' || !slug) continue\n listings[collectionId] = slug\n }\n return listings\n}\n\n/** What the tenant router has to correct the published route table by. */\nexport interface TemplateScreenRouting {\n /** Screens the router drops before matching — see {@link getTemplateScreenIds}. */\n templateScreenIds: Set<string>\n /** Where a list template is really served — see {@link collectCollectionListRoutes}. */\n listRoutes: Record<string, string>\n /** Every content collection's listing, by id — see {@link collectCollectionListings}. */\n collectionListings: Record<string, string>\n}\n\n/**\n * Screens that must NOT resolve as pages of the site (AGL-1267, AGL-1270).\n *\n * A template screen is a composition input, not a page. It only has meaning\n * against a routed subject — `/{collection}/{entry}` substitutes `{{entry.*}}`,\n * `/products/{slug}` substitutes `{{product.*}}` — so there is no request for\n * which serving it at its own slug produces something correct. Publishing one\n * is what makes the compose pipeline pick it up (`publishScreenRoute` stamps\n * `publishedAt`), but publishing ALSO writes `hosts/{hostId}.screens[screenId]`,\n * which is the tenant router's whole route table. That second write is the bug:\n * publishing the blog's entry template put a live `/blog-entry-template` page on\n * the site rendering seven raw `{{entry.*}}` tokens as body text, and publishing\n * a PDP template does the same with `{{product.*}}`.\n *\n * A stored marker AND the pointers (AGL-1400): `kind: 'template'` is the fact\n * for an entry template, and the pointers still answer for the two template\n * kinds that stay pages — a collection's LIST template (AGL-1387) and\n * commerce's PDP/catalog templates. See {@link collectTemplateScreenIds} for\n * why neither half subsumes the other. Re-pointing a collection (or the store)\n * at a different screen still takes effect on the next revalidate.\n *\n * THREE reads, not one — and one read cannot cover them. The template screens\n * and the collection templates are QUERIES over different subcollections; the\n * store templates are a single document at `hosts/{h}/settings/store`. Firestore\n * has no read that spans a query and a document in a different subcollection\n * (`getAll` batches document refs only, and cannot take the query). So they are\n * issued CONCURRENTLY: three RPCs, one round trip of latency, which is what the\n * cold-start budget cares about (AGL-1152). Both queries carry a field mask —\n * the screens one projects to ids alone — and the store doc is a single small\n * doc read whole, since `select` is a Query method and there is nothing to\n * project on a document get.\n *\n * Fails OPEN — an empty set on error, and PER SOURCE. This sits on the critical\n * path of every tenant page render, and a transient Firestore error must degrade\n * to \"the template is briefly reachable again\", never to a site-wide 404. Per\n * source because a store-settings failure has no business re-exposing the blog's\n * entry template, or vice versa.\n *\n * The reads themselves are {@link getTemplateScreenRouting}'s, which answers\n * the other half of the same question in the same trip (AGL-1998). This is the\n * narrow view of it, and stays a named export because the four callers that\n * only route requests have no use for the other half.\n */\nexport async function getTemplateScreenIds(options: {\n hostId: string\n}): Promise<Set<string>> {\n return (await getTemplateScreenRouting(options)).templateScreenIds\n}\n\n/**\n * Both halves of what the route table has to be corrected by, from ONE read\n * (AGL-1998): the screens to drop, and where a list template really lives.\n *\n * They come out of the same collections query — the ids need `listScreenId`,\n * the routes need the `slug` beside it — so asking separately would be a\n * second identical round trip on the render path this arc keeps trimming\n * (AGL-1152). {@link getTemplateScreenIds} is the narrow view of it, kept as\n * its own export because four callers want only the set.\n *\n * Never rejects, and fails open to \"no templates, no overrides\" — the routing\n * map as published — for the reason on {@link getTemplateScreenIds}.\n */\nexport async function getTemplateScreenRouting(options: {\n hostId: string\n}): Promise<TemplateScreenRouting> {\n try {\n // A NEW key rather than the old `tenant-template-screens`: the cached\n // value's shape changed from an array to an object, and a deploy that\n // read the previous shape back under the previous key would hand every\n // in-flight render an `ids` of `undefined`.\n const cached = await withRenderCache({\n key: ['tenant-template-routing', options.hostId],\n revalidate: TEMPLATE_SCREEN_IDS_TTL_SECONDS,\n tags: [tenantDataTag(options.hostId)],\n read: async () => {\n const routing = await readTemplateScreenRouting(options)\n return {\n ids: [...routing.templateScreenIds],\n listRoutes: routing.listRoutes,\n collectionListings: routing.collectionListings,\n }\n },\n })\n return {\n // `Set`s and `Map`s do not survive the cache's JSON round trip, so the\n // stored shape is an array and it is rehydrated here.\n templateScreenIds: new Set(cached?.ids ?? []),\n listRoutes: cached?.listRoutes ?? {},\n // An entry written without this field reads as no listings until it\n // revalidates, which the TTL above bounds.\n collectionListings: cached?.collectionListings ?? {},\n }\n } catch (error) {\n // The contract is \"never rejects\" — a cache failure degrades to the\n // uncached read, which itself fails open to an empty set.\n console.error('template screen lookup failed:', error)\n return readTemplateScreenRouting(options)\n }\n}\n\nasync function readTemplateScreenRouting(options: {\n hostId: string\n}): Promise<TemplateScreenRouting> {\n try {\n const hostRef = firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(options.hostId)\n\n const [templateScreens, collections, storeSettings, host] =\n await Promise.all([\n // The screens that say so (AGL-1400). `select()` with no fields returns\n // ids only, and the equality filter rides Firestore's automatic\n // single-field index — no composite index, and no read of the 100+ screen\n // documents a large site has.\n hostRef\n .collection('screens')\n .where('kind', '==', SCREEN_KIND_TEMPLATE)\n .select()\n .limit(200)\n .get()\n .catch((error: unknown) => {\n console.error('template screen kind lookup failed:', error)\n return null\n }),\n hostRef\n .collection('collections')\n // `slug` and `kind` ride along for AGL-1998: which route a LIST\n // template is really served at is decided by the same document, and\n // widening a field mask costs nothing next to a second query.\n .select(...COLLECTION_TEMPLATE_SCREEN_FIELDS, 'slug', 'kind')\n .limit(100)\n .get()\n .catch((error: unknown) => {\n console.error('collection template lookup failed:', error)\n return null\n }),\n // A host with commerce disabled — or one that never opened Store\n // settings — has no doc here. A missing doc is not an error: `get()`\n // resolves with `exists: false` and every field read returns undefined,\n // so it contributes nothing and costs nothing else.\n hostRef\n .collection('settings')\n .doc('store')\n .get()\n .catch((error: unknown) => {\n console.error('store template lookup failed:', error)\n return null\n }),\n // The host's own template pointer (AGL-2518). A FOURTH concurrent RPC\n // in the same round trip rather than a fourth round trip, which is what\n // the cold-start budget cares about (AGL-1152) — and the document is\n // one every request has already read through `getHostCached`, so it is\n // warm by the time this asks for it.\n // Wrapped rather than called directly, so this source fails open the\n // way the three above it do. `.catch()` only guards a REJECTION; a\n // synchronous throw here would escape into the outer catch, which\n // returns an EMPTY set — and an empty set re-exposes every template\n // screen on the site as a page. Per-source isolation is the contract\n // this function states, and it has to hold for the source that reads\n // a different shape of ref from the other three.\n Promise.resolve()\n .then(() => hostRef.get())\n .catch((error: unknown) => {\n console.error('host template pointer lookup failed:', error)\n return null\n }),\n ])\n\n return {\n templateScreenIds: collectTemplateScreenIds({\n templateScreens,\n collections,\n storeSettings,\n host,\n }),\n listRoutes: collectCollectionListRoutes({ collections }),\n // The same read again: a document's id comes with it whatever the\n // field mask projects.\n collectionListings: collectCollectionListings({ collections }),\n }\n } catch (error) {\n // The ref construction itself threw (no initialised admin app, say).\n console.error('template screen lookup failed:', error)\n return {\n templateScreenIds: new Set<string>(),\n listRoutes: {},\n collectionListings: {},\n }\n }\n}\n\nexport default getTemplateScreenIds\n"],"names":["SCREEN_KIND_TEMPLATE","firebaseAdmin","PUBLISHED_SITE_DATA_TTL_SECONDS","tenantDataTag","withRenderCache","TEMPLATE_SCREEN_IDS_TTL_SECONDS","COLLECTION_TEMPLATE_SCREEN_FIELDS","STORE_TEMPLATE_SCREEN_FIELDS","HOST_TEMPLATE_SCREEN_FIELDS","collectTemplateScreenIds","sources","ids","Set","add","screenId","screen","templateScreens","docs","id","contentCollection","collections","field","get","storeSettings","host","collectCollectionListRoutes","routes","slug","listScreenId","collectCollectionListings","listings","collectionId","getTemplateScreenIds","options","getTemplateScreenRouting","templateScreenIds","cached","key","hostId","revalidate","tags","read","routing","readTemplateScreenRouting","listRoutes","collectionListings","error","console","hostRef","app","firestore","collection","doc","Promise","all","where","select","limit","catch","resolve","then"],"mappings":"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"}