@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,107 @@
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
+ /**
18
+ * A page's social card, described by the assets it names as they are NOW
19
+ * (AGL-2850).
20
+ *
21
+ * A card's reference is stored with the pixel pair its picker copied from the
22
+ * asset: on the host, on each screen, on each template screen. A replace
23
+ * (AGL-2732) rewrites the asset's pair and cannot reach those copies, so the
24
+ * head is handed what each named asset's document records when the page is
25
+ * composed, and `resolveSocialImage` prefers that to the copy.
26
+ *
27
+ * The documents are read in the batch `composeNodesWithChrome` already issues
28
+ * for the images and films a page places (`get-media-asset-facts.ts`), so a
29
+ * card adds documents to that read rather than a read of its own. This module
30
+ * turns the card's references into the batch's input and the batch's answer
31
+ * into the head's view, and it reads on its own only for a page that composes
32
+ * nothing.
33
+ */
34
+ import { type MediaRef, type SocialImageAssetFacts } from '@aglyn/aglyn/server';
35
+ import { type MediaAssetFacts } from '@aglyn/aglyn/app-utils/media-asset-facts';
36
+ /** The references a card may resolve from, in the head's precedence order. */
37
+ export type SocialCardImages = ReadonlyArray<string | null | undefined>;
38
+ /**
39
+ * The card handed to a composition, so the assets it names are read in the
40
+ * page's one facts batch.
41
+ */
42
+ export interface ComposeSocialImages {
43
+ /**
44
+ * The references the head resolves the card from: a screen's and the host's
45
+ * `seo.image`, an entry's cover, an author's pictures. Unset, blank and
46
+ * non-library values are skipped.
47
+ */
48
+ images: SocialCardImages;
49
+ /**
50
+ * Called once the batch answers, with each reference's current pair. Not
51
+ * called when it answered for none of them (no usable pair, an asset this
52
+ * site may not be shown, a failed read), which leaves the card on the pair
53
+ * stored beside its reference.
54
+ */
55
+ onFacts: (facts: SocialImageAssetFacts) => void;
56
+ }
57
+ /**
58
+ * Each distinct library asset the references name, in the order given.
59
+ *
60
+ * `[]` when none is a library reference, so a caller that gates its read on
61
+ * the length spends nothing. A pinned and an unpinned reference to one asset
62
+ * are one entry: a pin names bytes, not a different asset.
63
+ */
64
+ export declare function socialImageRefs(images: SocialCardImages): MediaRef[];
65
+ /**
66
+ * What the head is told: the current pair of each reference whose document
67
+ * records a usable one, keyed by the reference exactly as it is stored.
68
+ * `undefined` when none does, which the head reads as "emit the stored copy",
69
+ * the same answer a failed read gets.
70
+ *
71
+ * A usable pair passes the gate a placed image's does (`intrinsicMediaSize`):
72
+ * both halves, finite and positive. The card and the Image element agree on
73
+ * what a document's pair is worth.
74
+ */
75
+ export declare function socialImageAssetFacts(images: SocialCardImages, facts: ReadonlyMap<string, MediaAssetFacts>): SocialImageAssetFacts | undefined;
76
+ /**
77
+ * A composition's `socialImages` option, with somewhere for its facts to land.
78
+ *
79
+ * The composition answers with nodes and reports the card through a callback,
80
+ * so a caller that hands both on needs a place for the callback to write. This
81
+ * is that place, once, rather than a closure at every caller.
82
+ */
83
+ export declare function collectSocialImageFacts(images: SocialCardImages): {
84
+ /** Hand to `composeScreenNodes` or `composeNodesWithChrome`. */
85
+ socialImages: ComposeSocialImages;
86
+ /** `{ socialImageFacts }` once the batch answered for the card, else `{}`. */
87
+ collected: () => {
88
+ socialImageFacts?: SocialImageAssetFacts;
89
+ };
90
+ };
91
+ /**
92
+ * The card's current pairs for a page that composes NOTHING, so has no batch
93
+ * for its documents to join: a password-protected screen, whose nodes stay
94
+ * withheld until unlock while its head still shares the card.
95
+ *
96
+ * ONE projected read, through the composition's own reader, so the verdict on
97
+ * which assets this site may be shown is the CDN's here too. No read at all
98
+ * when no reference names a library asset.
99
+ *
100
+ * Fail-open: any failure answers `undefined` and the card keeps its stored
101
+ * pair. A page is never lost to the description of its share card.
102
+ */
103
+ export declare function getSocialImageAssetFacts(options: {
104
+ hostId: string;
105
+ images: SocialCardImages;
106
+ }): Promise<SocialImageAssetFacts | undefined>;
107
+ export default getSocialImageAssetFacts;
@@ -0,0 +1,133 @@
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
+ * A page's social card, described by the assets it names as they are NOW
18
+ * (AGL-2850).
19
+ *
20
+ * A card's reference is stored with the pixel pair its picker copied from the
21
+ * asset: on the host, on each screen, on each template screen. A replace
22
+ * (AGL-2732) rewrites the asset's pair and cannot reach those copies, so the
23
+ * head is handed what each named asset's document records when the page is
24
+ * composed, and `resolveSocialImage` prefers that to the copy.
25
+ *
26
+ * The documents are read in the batch `composeNodesWithChrome` already issues
27
+ * for the images and films a page places (`get-media-asset-facts.ts`), so a
28
+ * card adds documents to that read rather than a read of its own. This module
29
+ * turns the card's references into the batch's input and the batch's answer
30
+ * into the head's view, and it reads on its own only for a page that composes
31
+ * nothing.
32
+ */ import { intrinsicMediaSize, parseMediaRef } from "@aglyn/aglyn/server";
33
+ // By path rather than through a barrel: the overlay is server-only, and every
34
+ // `@aglyn/aglyn` barrel re-exports `app-utils/server` into published pages.
35
+ import { IMAGE_COMPONENT_ID, mediaAssetFactsKey } from "@aglyn/aglyn/app-utils/media-asset-facts";
36
+ import getMediaAssetFacts from "./get-media-asset-facts.js";
37
+ /**
38
+ * Each distinct library asset the references name, in the order given.
39
+ *
40
+ * `[]` when none is a library reference, so a caller that gates its read on
41
+ * the length spends nothing. A pinned and an unpinned reference to one asset
42
+ * are one entry: a pin names bytes, not a different asset.
43
+ */ export function socialImageRefs(images) {
44
+ const refs = [];
45
+ const seen = new Set();
46
+ for (const image of images){
47
+ const ref = parseMediaRef(image);
48
+ if (!ref) continue;
49
+ const key = mediaAssetFactsKey(ref);
50
+ if (seen.has(key)) continue;
51
+ seen.add(key);
52
+ refs.push(ref);
53
+ }
54
+ return refs;
55
+ }
56
+ /**
57
+ * What the head is told: the current pair of each reference whose document
58
+ * records a usable one, keyed by the reference exactly as it is stored.
59
+ * `undefined` when none does, which the head reads as "emit the stored copy",
60
+ * the same answer a failed read gets.
61
+ *
62
+ * A usable pair passes the gate a placed image's does (`intrinsicMediaSize`):
63
+ * both halves, finite and positive. The card and the Image element agree on
64
+ * what a document's pair is worth.
65
+ */ export function socialImageAssetFacts(images, facts) {
66
+ let answered;
67
+ for (const image of images){
68
+ const ref = parseMediaRef(image);
69
+ const found = ref ? facts.get(mediaAssetFactsKey(ref)) : undefined;
70
+ if (!found) continue;
71
+ const { intrinsicWidth, intrinsicHeight } = intrinsicMediaSize({
72
+ componentId: IMAGE_COMPONENT_ID,
73
+ propName: 'src',
74
+ assetWidth: found.width,
75
+ assetHeight: found.height
76
+ });
77
+ if (intrinsicWidth === undefined || intrinsicHeight === undefined) continue;
78
+ if (!answered) answered = {};
79
+ answered[image] = {
80
+ width: intrinsicWidth,
81
+ height: intrinsicHeight
82
+ };
83
+ }
84
+ return answered;
85
+ }
86
+ /**
87
+ * A composition's `socialImages` option, with somewhere for its facts to land.
88
+ *
89
+ * The composition answers with nodes and reports the card through a callback,
90
+ * so a caller that hands both on needs a place for the callback to write. This
91
+ * is that place, once, rather than a closure at every caller.
92
+ */ export function collectSocialImageFacts(images) {
93
+ let reported;
94
+ return {
95
+ socialImages: {
96
+ images,
97
+ onFacts: (facts)=>{
98
+ reported = facts;
99
+ }
100
+ },
101
+ collected: ()=>reported ? {
102
+ socialImageFacts: reported
103
+ } : {}
104
+ };
105
+ }
106
+ /**
107
+ * The card's current pairs for a page that composes NOTHING, so has no batch
108
+ * for its documents to join: a password-protected screen, whose nodes stay
109
+ * withheld until unlock while its head still shares the card.
110
+ *
111
+ * ONE projected read, through the composition's own reader, so the verdict on
112
+ * which assets this site may be shown is the CDN's here too. No read at all
113
+ * when no reference names a library asset.
114
+ *
115
+ * Fail-open: any failure answers `undefined` and the card keeps its stored
116
+ * pair. A page is never lost to the description of its share card.
117
+ */ export async function getSocialImageAssetFacts(options) {
118
+ const { hostId, images } = options;
119
+ try {
120
+ const refs = socialImageRefs(images);
121
+ if (!refs.length) return undefined;
122
+ return socialImageAssetFacts(images, await getMediaAssetFacts({
123
+ hostId,
124
+ refs
125
+ }));
126
+ } catch (error) {
127
+ console.error('[social-image-facts] read failed; the card keeps its stored pair', error);
128
+ return undefined;
129
+ }
130
+ }
131
+ export default getSocialImageAssetFacts;
132
+
133
+ //# sourceMappingURL=social-image-facts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/social-image-facts.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\n/**\n * A page's social card, described by the assets it names as they are NOW\n * (AGL-2850).\n *\n * A card's reference is stored with the pixel pair its picker copied from the\n * asset: on the host, on each screen, on each template screen. A replace\n * (AGL-2732) rewrites the asset's pair and cannot reach those copies, so the\n * head is handed what each named asset's document records when the page is\n * composed, and `resolveSocialImage` prefers that to the copy.\n *\n * The documents are read in the batch `composeNodesWithChrome` already issues\n * for the images and films a page places (`get-media-asset-facts.ts`), so a\n * card adds documents to that read rather than a read of its own. This module\n * turns the card's references into the batch's input and the batch's answer\n * into the head's view, and it reads on its own only for a page that composes\n * nothing.\n */\n\nimport {\n intrinsicMediaSize,\n type MediaRef,\n parseMediaRef,\n type SocialImageAssetFacts,\n} from '@aglyn/aglyn/server'\n// By path rather than through a barrel: the overlay is server-only, and every\n// `@aglyn/aglyn` barrel re-exports `app-utils/server` into published pages.\nimport {\n IMAGE_COMPONENT_ID,\n type MediaAssetFacts,\n mediaAssetFactsKey,\n} from '@aglyn/aglyn/app-utils/media-asset-facts'\nimport getMediaAssetFacts from './get-media-asset-facts'\n\n/** The references a card may resolve from, in the head's precedence order. */\nexport type SocialCardImages = ReadonlyArray<string | null | undefined>\n\n/**\n * The card handed to a composition, so the assets it names are read in the\n * page's one facts batch.\n */\nexport interface ComposeSocialImages {\n /**\n * The references the head resolves the card from: a screen's and the host's\n * `seo.image`, an entry's cover, an author's pictures. Unset, blank and\n * non-library values are skipped.\n */\n images: SocialCardImages\n /**\n * Called once the batch answers, with each reference's current pair. Not\n * called when it answered for none of them (no usable pair, an asset this\n * site may not be shown, a failed read), which leaves the card on the pair\n * stored beside its reference.\n */\n onFacts: (facts: SocialImageAssetFacts) => void\n}\n\n/**\n * Each distinct library asset the references name, in the order given.\n *\n * `[]` when none is a library reference, so a caller that gates its read on\n * the length spends nothing. A pinned and an unpinned reference to one asset\n * are one entry: a pin names bytes, not a different asset.\n */\nexport function socialImageRefs(images: SocialCardImages): MediaRef[] {\n const refs: MediaRef[] = []\n const seen = new Set<string>()\n for (const image of images) {\n const ref = parseMediaRef(image)\n if (!ref) continue\n const key = mediaAssetFactsKey(ref)\n if (seen.has(key)) continue\n seen.add(key)\n refs.push(ref)\n }\n return refs\n}\n\n/**\n * What the head is told: the current pair of each reference whose document\n * records a usable one, keyed by the reference exactly as it is stored.\n * `undefined` when none does, which the head reads as \"emit the stored copy\",\n * the same answer a failed read gets.\n *\n * A usable pair passes the gate a placed image's does (`intrinsicMediaSize`):\n * both halves, finite and positive. The card and the Image element agree on\n * what a document's pair is worth.\n */\nexport function socialImageAssetFacts(\n images: SocialCardImages,\n facts: ReadonlyMap<string, MediaAssetFacts>,\n): SocialImageAssetFacts | undefined {\n let answered: Record<string, { width: number; height: number }> | undefined\n for (const image of images) {\n const ref = parseMediaRef(image)\n const found = ref ? facts.get(mediaAssetFactsKey(ref)) : undefined\n if (!found) continue\n const { intrinsicWidth, intrinsicHeight } = intrinsicMediaSize({\n componentId: IMAGE_COMPONENT_ID,\n propName: 'src',\n assetWidth: found.width,\n assetHeight: found.height,\n })\n if (intrinsicWidth === undefined || intrinsicHeight === undefined) continue\n if (!answered) answered = {}\n answered[image as string] = {\n width: intrinsicWidth,\n height: intrinsicHeight,\n }\n }\n return answered\n}\n\n/**\n * A composition's `socialImages` option, with somewhere for its facts to land.\n *\n * The composition answers with nodes and reports the card through a callback,\n * so a caller that hands both on needs a place for the callback to write. This\n * is that place, once, rather than a closure at every caller.\n */\nexport function collectSocialImageFacts(images: SocialCardImages): {\n /** Hand to `composeScreenNodes` or `composeNodesWithChrome`. */\n socialImages: ComposeSocialImages\n /** `{ socialImageFacts }` once the batch answered for the card, else `{}`. */\n collected: () => { socialImageFacts?: SocialImageAssetFacts }\n} {\n let reported: SocialImageAssetFacts | undefined\n return {\n socialImages: {\n images,\n onFacts: (facts) => {\n reported = facts\n },\n },\n collected: () => (reported ? { socialImageFacts: reported } : {}),\n }\n}\n\n/**\n * The card's current pairs for a page that composes NOTHING, so has no batch\n * for its documents to join: a password-protected screen, whose nodes stay\n * withheld until unlock while its head still shares the card.\n *\n * ONE projected read, through the composition's own reader, so the verdict on\n * which assets this site may be shown is the CDN's here too. No read at all\n * when no reference names a library asset.\n *\n * Fail-open: any failure answers `undefined` and the card keeps its stored\n * pair. A page is never lost to the description of its share card.\n */\nexport async function getSocialImageAssetFacts(options: {\n hostId: string\n images: SocialCardImages\n}): Promise<SocialImageAssetFacts | undefined> {\n const { hostId, images } = options\n try {\n const refs = socialImageRefs(images)\n if (!refs.length) return undefined\n return socialImageAssetFacts(\n images,\n await getMediaAssetFacts({ hostId, refs }),\n )\n } catch (error) {\n console.error(\n '[social-image-facts] read failed; the card keeps its stored pair',\n error,\n )\n return undefined\n }\n}\n\nexport default getSocialImageAssetFacts\n"],"names":["intrinsicMediaSize","parseMediaRef","IMAGE_COMPONENT_ID","mediaAssetFactsKey","getMediaAssetFacts","socialImageRefs","images","refs","seen","Set","image","ref","key","has","add","push","socialImageAssetFacts","facts","answered","found","get","undefined","intrinsicWidth","intrinsicHeight","componentId","propName","assetWidth","width","assetHeight","height","collectSocialImageFacts","reported","socialImages","onFacts","collected","socialImageFacts","getSocialImageAssetFacts","options","hostId","length","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;CAgBC,GAED,SACEA,kBAAkB,EAElBC,aAAa,QAER,sBAAqB;AAC5B,8EAA8E;AAC9E,4EAA4E;AAC5E,SACEC,kBAAkB,EAElBC,kBAAkB,QACb,2CAA0C;AACjD,OAAOC,wBAAwB,6BAAyB;AAyBxD;;;;;;CAMC,GACD,OAAO,SAASC,gBAAgBC,MAAwB;IACtD,MAAMC,OAAmB,EAAE;IAC3B,MAAMC,OAAO,IAAIC;IACjB,KAAK,MAAMC,SAASJ,OAAQ;QAC1B,MAAMK,MAAMV,cAAcS;QAC1B,IAAI,CAACC,KAAK;QACV,MAAMC,MAAMT,mBAAmBQ;QAC/B,IAAIH,KAAKK,GAAG,CAACD,MAAM;QACnBJ,KAAKM,GAAG,CAACF;QACTL,KAAKQ,IAAI,CAACJ;IACZ;IACA,OAAOJ;AACT;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASS,sBACdV,MAAwB,EACxBW,KAA2C;IAE3C,IAAIC;IACJ,KAAK,MAAMR,SAASJ,OAAQ;QAC1B,MAAMK,MAAMV,cAAcS;QAC1B,MAAMS,QAAQR,MAAMM,MAAMG,GAAG,CAACjB,mBAAmBQ,QAAQU;QACzD,IAAI,CAACF,OAAO;QACZ,MAAM,EAAEG,cAAc,EAAEC,eAAe,EAAE,GAAGvB,mBAAmB;YAC7DwB,aAAatB;YACbuB,UAAU;YACVC,YAAYP,MAAMQ,KAAK;YACvBC,aAAaT,MAAMU,MAAM;QAC3B;QACA,IAAIP,mBAAmBD,aAAaE,oBAAoBF,WAAW;QACnE,IAAI,CAACH,UAAUA,WAAW,CAAC;QAC3BA,QAAQ,CAACR,MAAgB,GAAG;YAC1BiB,OAAOL;YACPO,QAAQN;QACV;IACF;IACA,OAAOL;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASY,wBAAwBxB,MAAwB;IAM9D,IAAIyB;IACJ,OAAO;QACLC,cAAc;YACZ1B;YACA2B,SAAS,CAAChB;gBACRc,WAAWd;YACb;QACF;QACAiB,WAAW,IAAOH,WAAW;gBAAEI,kBAAkBJ;YAAS,IAAI,CAAC;IACjE;AACF;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,eAAeK,yBAAyBC,OAG9C;IACC,MAAM,EAAEC,MAAM,EAAEhC,MAAM,EAAE,GAAG+B;IAC3B,IAAI;QACF,MAAM9B,OAAOF,gBAAgBC;QAC7B,IAAI,CAACC,KAAKgC,MAAM,EAAE,OAAOlB;QACzB,OAAOL,sBACLV,QACA,MAAMF,mBAAmB;YAAEkC;YAAQ/B;QAAK;IAE5C,EAAE,OAAOiC,OAAO;QACdC,QAAQD,KAAK,CACX,oEACAA;QAEF,OAAOnB;IACT;AACF;AAEA,eAAee,yBAAwB"}
@@ -0,0 +1,32 @@
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;
@@ -0,0 +1,59 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,227 @@
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
+ /**
18
+ * The three fields a CONTENT collection can point at a template screen with.
19
+ * `templateScreenId` is the legacy AGL-105 field `resolveCollectionTemplateScreenId`
20
+ * still falls back to, so hosts predating the list/entry split are covered.
21
+ *
22
+ * `apps/console/app/api/hosts/resources/count-billable-screens.ts` subtracts
23
+ * this same set from the plan's screen allowance. It deliberately does NOT
24
+ * subtract the store fields below — what a template costs the subscriber is a
25
+ * separate question from whether it is a page.
26
+ */
27
+ export declare const COLLECTION_TEMPLATE_SCREEN_FIELDS: readonly ["listScreenId", "entryScreenId", "templateScreenId"];
28
+ /**
29
+ * The two fields COMMERCE points at a template screen with (AGL-1270). They
30
+ * live on `hosts/{hostId}/settings/store` (AGL-295), not on a collection doc,
31
+ * which is the only reason AGL-1267's fix did not already reach them —
32
+ * `pdpScreenId` renders `/products/{slug}` and `collectionScreenId` renders
33
+ * `/collections/{slug}`, both of which are templates in exactly the same sense.
34
+ *
35
+ * NOT yet mirrored in `apps/console/constants/collection-templates.ts`, which
36
+ * re-declares the collection fields for its `'use client'` readers: publishing a
37
+ * PDP template still toasts the author a route it does not have (the AGL-1269
38
+ * half of AGL-1270). Widening it is a console change and belongs with that file.
39
+ */
40
+ export declare const STORE_TEMPLATE_SCREEN_FIELDS: readonly ["pdpScreenId", "collectionScreenId"];
41
+ /**
42
+ * The field the HOST points a template screen at (AGL-2518).
43
+ *
44
+ * One so far: the screen every `/author/{slug}` page renders through. It sits
45
+ * on the host rather than on a collection because there is one author page
46
+ * shape per SITE, not per collection.
47
+ *
48
+ * Listed here rather than left to `kind: 'template'` because designating this
49
+ * screen deliberately does NOT demote it. An author template is a designed
50
+ * page in exactly the sense a collection's LIST template is (AGL-1387) — it
51
+ * stays billable, and the site keeps paying for the page it built — but like
52
+ * that one it must still 404 at the slug it was published under, or the site
53
+ * ships a page rendering raw `{{author.*}}` as body text. That is AGL-1267's
54
+ * defect, and the pointer half of this union is what closes it without the
55
+ * stamp that would also make the screen free (AGL-1400's settlement: a
56
+ * pointer excuses nothing, only the stamp does).
57
+ */
58
+ export declare const HOST_TEMPLATE_SCREEN_FIELDS: readonly ["authorScreenId"];
59
+ /** The shape this reads off a Firestore snapshot — kept structural so unit
60
+ * tests don't need the admin SDK. */
61
+ interface FieldSnapshotLike {
62
+ get(field: string): unknown;
63
+ }
64
+ interface QuerySnapshotLike {
65
+ /** `id` is the document id — read only for a collection's listing. */
66
+ docs: Array<FieldSnapshotLike & {
67
+ id?: string;
68
+ }>;
69
+ }
70
+ /** Screen documents that say what they are — only the id is read. */
71
+ interface IdSnapshotLike {
72
+ id: string;
73
+ }
74
+ /**
75
+ * Every screen id this host treats as a template — the screens that SAY so
76
+ * (`kind: 'template'`), plus a content collection's list/entry screens and
77
+ * commerce's PDP and catalog-collection screens. Pure, so the routing rule can
78
+ * be tested without Firestore.
79
+ *
80
+ * A UNION, and each half covers what the other cannot (AGL-1400):
81
+ *
82
+ * - The `kind` half reaches a template no pointer names any more. Clearing a
83
+ * collection's `entryScreenId` deliberately does NOT promote the screen back
84
+ * to a page — promotion is a gated act — so without this an orphaned
85
+ * template would become reachable at its own slug again, rendering raw
86
+ * `{{entry.*}}` tokens: AGL-1267, back.
87
+ * - The pointer half reaches the templates that are NOT `kind: 'template'`,
88
+ * and there are two kinds of those on purpose. A collection's LIST template
89
+ * is a page (AGL-1387) — billable, and served at `/{collectionSlug}` — but
90
+ * it must still 404 at the slug it was published under. Commerce's PDP and
91
+ * catalog templates are billable today too, and re-pricing them is a
92
+ * separate decision from this one.
93
+ *
94
+ * Any source may be absent (`null`/`undefined`): a host with no store settings
95
+ * doc, or a read that failed on its own while the others succeeded.
96
+ */
97
+ export declare function collectTemplateScreenIds(sources: {
98
+ collections?: QuerySnapshotLike | null;
99
+ storeSettings?: FieldSnapshotLike | null;
100
+ templateScreens?: {
101
+ docs: Array<IdSnapshotLike>;
102
+ } | null;
103
+ /** The host document, for {@link HOST_TEMPLATE_SCREEN_FIELDS}. */
104
+ host?: FieldSnapshotLike | null;
105
+ }): Set<string>;
106
+ /**
107
+ * Where a collection's LIST template is actually served, keyed by screen id
108
+ * (AGL-1998): `blogListTmpl` → `blog`, in the routing map's own path format.
109
+ *
110
+ * A list template is the one template kind that IS a page (AGL-1387) — and it
111
+ * is a page at somebody ELSE's address. `/{collectionSlug}` renders it through
112
+ * `composeCollectionTemplatePage`, while the slug it was published under is
113
+ * dropped from the route table by {@link getTemplateScreenIds} and 404s. So
114
+ * the routing map says one thing and the router does another, and everything
115
+ * that RESOLVES a screen link believed the map: on aglyn.com no screen link
116
+ * could point at `/blog` at all, because the only entry for the screen that
117
+ * serves it read `blog-list-template`.
118
+ *
119
+ * Two conditions, matching `collectionListTemplateScreenIds` in the console
120
+ * (which answers the same question for the Screens list's Path column):
121
+ *
122
+ * - **A slug**, since `/{slug}` is the whole address; and
123
+ * - **content kind**, because only a content collection answers `/{slug}` —
124
+ * a catalog collection's listing is `/collections/{slug}`, composed by
125
+ * commerce, and pointing a link there through this map would send visitors
126
+ * to a path that does not exist.
127
+ *
128
+ * Publishing is deliberately NOT a condition: the collection branch resolves
129
+ * the screen through `listScreenId` and never through the routing map, so an
130
+ * unpublished list template serves `/{slug}` exactly like a published one.
131
+ * That is the live blog's state today.
132
+ */
133
+ export declare function collectCollectionListRoutes(sources: {
134
+ collections?: QuerySnapshotLike | null;
135
+ }): Record<string, string>;
136
+ /**
137
+ * Every content collection's LISTING as a link target: collection id → slug
138
+ * (AGL-2799).
139
+ *
140
+ * `/{slug}` serves a content collection's listing whether or not a list
141
+ * template screen renders it — the built-in fallback composes one otherwise —
142
+ * so this is a different fact from {@link collectCollectionListRoutes}, which
143
+ * answers only for the collections that have such a screen. A link authored
144
+ * against a listing stores the collection's id, and `linkableScreenRoutes`
145
+ * turns this table into the `collection:<id>` entries it resolves through.
146
+ *
147
+ * The same two conditions as the list routes, for the same reasons: a slug,
148
+ * since `/{slug}` is the whole address, and content kind, since a catalog
149
+ * collection's listing is commerce's `/collections/{slug}`. `id` is optional
150
+ * on the structural snapshot type, so a document without one is skipped
151
+ * rather than keyed as `undefined`.
152
+ */
153
+ export declare function collectCollectionListings(sources: {
154
+ collections?: QuerySnapshotLike | null;
155
+ }): Record<string, string>;
156
+ /** What the tenant router has to correct the published route table by. */
157
+ export interface TemplateScreenRouting {
158
+ /** Screens the router drops before matching — see {@link getTemplateScreenIds}. */
159
+ templateScreenIds: Set<string>;
160
+ /** Where a list template is really served — see {@link collectCollectionListRoutes}. */
161
+ listRoutes: Record<string, string>;
162
+ /** Every content collection's listing, by id — see {@link collectCollectionListings}. */
163
+ collectionListings: Record<string, string>;
164
+ }
165
+ /**
166
+ * Screens that must NOT resolve as pages of the site (AGL-1267, AGL-1270).
167
+ *
168
+ * A template screen is a composition input, not a page. It only has meaning
169
+ * against a routed subject — `/{collection}/{entry}` substitutes `{{entry.*}}`,
170
+ * `/products/{slug}` substitutes `{{product.*}}` — so there is no request for
171
+ * which serving it at its own slug produces something correct. Publishing one
172
+ * is what makes the compose pipeline pick it up (`publishScreenRoute` stamps
173
+ * `publishedAt`), but publishing ALSO writes `hosts/{hostId}.screens[screenId]`,
174
+ * which is the tenant router's whole route table. That second write is the bug:
175
+ * publishing the blog's entry template put a live `/blog-entry-template` page on
176
+ * the site rendering seven raw `{{entry.*}}` tokens as body text, and publishing
177
+ * a PDP template does the same with `{{product.*}}`.
178
+ *
179
+ * A stored marker AND the pointers (AGL-1400): `kind: 'template'` is the fact
180
+ * for an entry template, and the pointers still answer for the two template
181
+ * kinds that stay pages — a collection's LIST template (AGL-1387) and
182
+ * commerce's PDP/catalog templates. See {@link collectTemplateScreenIds} for
183
+ * why neither half subsumes the other. Re-pointing a collection (or the store)
184
+ * at a different screen still takes effect on the next revalidate.
185
+ *
186
+ * THREE reads, not one — and one read cannot cover them. The template screens
187
+ * and the collection templates are QUERIES over different subcollections; the
188
+ * store templates are a single document at `hosts/{h}/settings/store`. Firestore
189
+ * has no read that spans a query and a document in a different subcollection
190
+ * (`getAll` batches document refs only, and cannot take the query). So they are
191
+ * issued CONCURRENTLY: three RPCs, one round trip of latency, which is what the
192
+ * cold-start budget cares about (AGL-1152). Both queries carry a field mask —
193
+ * the screens one projects to ids alone — and the store doc is a single small
194
+ * doc read whole, since `select` is a Query method and there is nothing to
195
+ * project on a document get.
196
+ *
197
+ * Fails OPEN — an empty set on error, and PER SOURCE. This sits on the critical
198
+ * path of every tenant page render, and a transient Firestore error must degrade
199
+ * to "the template is briefly reachable again", never to a site-wide 404. Per
200
+ * source because a store-settings failure has no business re-exposing the blog's
201
+ * entry template, or vice versa.
202
+ *
203
+ * The reads themselves are {@link getTemplateScreenRouting}'s, which answers
204
+ * the other half of the same question in the same trip (AGL-1998). This is the
205
+ * narrow view of it, and stays a named export because the four callers that
206
+ * only route requests have no use for the other half.
207
+ */
208
+ export declare function getTemplateScreenIds(options: {
209
+ hostId: string;
210
+ }): Promise<Set<string>>;
211
+ /**
212
+ * Both halves of what the route table has to be corrected by, from ONE read
213
+ * (AGL-1998): the screens to drop, and where a list template really lives.
214
+ *
215
+ * They come out of the same collections query — the ids need `listScreenId`,
216
+ * the routes need the `slug` beside it — so asking separately would be a
217
+ * second identical round trip on the render path this arc keeps trimming
218
+ * (AGL-1152). {@link getTemplateScreenIds} is the narrow view of it, kept as
219
+ * its own export because four callers want only the set.
220
+ *
221
+ * Never rejects, and fails open to "no templates, no overrides" — the routing
222
+ * map as published — for the reason on {@link getTemplateScreenIds}.
223
+ */
224
+ export declare function getTemplateScreenRouting(options: {
225
+ hostId: string;
226
+ }): Promise<TemplateScreenRouting>;
227
+ export default getTemplateScreenIds;