@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.
- package/LICENSE +201 -0
- package/README.md +26 -0
- package/package.json +39 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +22 -0
- package/src/index.js.map +1 -0
- package/src/lib/apply-publish-schedule.d.ts +34 -0
- package/src/lib/apply-publish-schedule.js +285 -0
- package/src/lib/apply-publish-schedule.js.map +1 -0
- package/src/lib/assign-contact-owner.d.ts +162 -0
- package/src/lib/assign-contact-owner.js +322 -0
- package/src/lib/assign-contact-owner.js.map +1 -0
- package/src/lib/associate-company-by-domain.d.ts +70 -0
- package/src/lib/associate-company-by-domain.js +150 -0
- package/src/lib/associate-company-by-domain.js.map +1 -0
- package/src/lib/author-page-nodes.d.ts +36 -0
- package/src/lib/author-page-nodes.js +296 -0
- package/src/lib/author-page-nodes.js.map +1 -0
- package/src/lib/built-in-page-layout.d.ts +48 -0
- package/src/lib/built-in-page-layout.js +107 -0
- package/src/lib/built-in-page-layout.js.map +1 -0
- package/src/lib/capture-host-contact.d.ts +95 -0
- package/src/lib/capture-host-contact.js +137 -0
- package/src/lib/capture-host-contact.js.map +1 -0
- package/src/lib/collection-fallback-nodes.d.ts +90 -0
- package/src/lib/collection-fallback-nodes.js +602 -0
- package/src/lib/collection-fallback-nodes.js.map +1 -0
- package/src/lib/compose-author-page.d.ts +109 -0
- package/src/lib/compose-author-page.js +225 -0
- package/src/lib/compose-author-page.js.map +1 -0
- package/src/lib/compose-collection-page.d.ts +94 -0
- package/src/lib/compose-collection-page.js +256 -0
- package/src/lib/compose-collection-page.js.map +1 -0
- package/src/lib/compose-screen-nodes.d.ts +156 -0
- package/src/lib/compose-screen-nodes.js +547 -0
- package/src/lib/compose-screen-nodes.js.map +1 -0
- package/src/lib/compose-search-page.d.ts +39 -0
- package/src/lib/compose-search-page.js +57 -0
- package/src/lib/compose-search-page.js.map +1 -0
- package/src/lib/convert-host-lead.d.ts +189 -0
- package/src/lib/convert-host-lead.js +443 -0
- package/src/lib/convert-host-lead.js.map +1 -0
- package/src/lib/defer-lazy-panels.d.ts +58 -0
- package/src/lib/defer-lazy-panels.js +171 -0
- package/src/lib/defer-lazy-panels.js.map +1 -0
- package/src/lib/element-animation-assets.d.ts +62 -0
- package/src/lib/element-animation-assets.js +290 -0
- package/src/lib/element-animation-assets.js.map +1 -0
- package/src/lib/emit-host-event.d.ts +29 -0
- package/src/lib/emit-host-event.js +30 -0
- package/src/lib/emit-host-event.js.map +1 -0
- package/src/lib/enrich-gated-page.d.ts +67 -0
- package/src/lib/enrich-gated-page.js +134 -0
- package/src/lib/enrich-gated-page.js.map +1 -0
- package/src/lib/entry-link-routes.d.ts +61 -0
- package/src/lib/entry-link-routes.js +169 -0
- package/src/lib/entry-link-routes.js.map +1 -0
- package/src/lib/get-author-content.d.ts +119 -0
- package/src/lib/get-author-content.js +288 -0
- package/src/lib/get-author-content.js.map +1 -0
- package/src/lib/get-collection-content.d.ts +290 -0
- package/src/lib/get-collection-content.js +565 -0
- package/src/lib/get-collection-content.js.map +1 -0
- package/src/lib/get-components.d.ts +24 -0
- package/src/lib/get-components.js +102 -0
- package/src/lib/get-components.js.map +1 -0
- package/src/lib/get-datasets.d.ts +45 -0
- package/src/lib/get-datasets.js +169 -0
- package/src/lib/get-datasets.js.map +1 -0
- package/src/lib/get-forms.d.ts +24 -0
- package/src/lib/get-forms.js +116 -0
- package/src/lib/get-forms.js.map +1 -0
- package/src/lib/get-layout-version.d.ts +26 -0
- package/src/lib/get-layout-version.js +99 -0
- package/src/lib/get-layout-version.js.map +1 -0
- package/src/lib/get-media-asset-facts.d.ts +74 -0
- package/src/lib/get-media-asset-facts.js +116 -0
- package/src/lib/get-media-asset-facts.js.map +1 -0
- package/src/lib/get-plugin-installs.d.ts +29 -0
- package/src/lib/get-plugin-installs.js +94 -0
- package/src/lib/get-plugin-installs.js.map +1 -0
- package/src/lib/get-screen-version.d.ts +27 -0
- package/src/lib/get-screen-version.js +66 -0
- package/src/lib/get-screen-version.js.map +1 -0
- package/src/lib/get-screen.d.ts +39 -0
- package/src/lib/get-screen.js +126 -0
- package/src/lib/get-screen.js.map +1 -0
- package/src/lib/get-variables.d.ts +41 -0
- package/src/lib/get-variables.js +128 -0
- package/src/lib/get-variables.js.map +1 -0
- package/src/lib/get-video-asset-facts.d.ts +22 -0
- package/src/lib/get-video-asset-facts.js +22 -0
- package/src/lib/get-video-asset-facts.js.map +1 -0
- package/src/lib/host-event-listeners.d.ts +83 -0
- package/src/lib/host-event-listeners.js +91 -0
- package/src/lib/host-event-listeners.js.map +1 -0
- package/src/lib/org-permissions.d.ts +63 -0
- package/src/lib/org-permissions.js +187 -0
- package/src/lib/org-permissions.js.map +1 -0
- package/src/lib/repeat-record-pages.d.ts +48 -0
- package/src/lib/repeat-record-pages.js +64 -0
- package/src/lib/repeat-record-pages.js.map +1 -0
- package/src/lib/required-site-plugins.d.ts +114 -0
- package/src/lib/required-site-plugins.js +121 -0
- package/src/lib/required-site-plugins.js.map +1 -0
- package/src/lib/resolve-dataset.d.ts +40 -0
- package/src/lib/resolve-dataset.js +64 -0
- package/src/lib/resolve-dataset.js.map +1 -0
- package/src/lib/search-results-nodes.d.ts +48 -0
- package/src/lib/search-results-nodes.js +302 -0
- package/src/lib/search-results-nodes.js.map +1 -0
- package/src/lib/social-image-facts.d.ts +107 -0
- package/src/lib/social-image-facts.js +133 -0
- package/src/lib/social-image-facts.js.map +1 -0
- package/src/lib/stamp-form-dataset-bindings.d.ts +32 -0
- package/src/lib/stamp-form-dataset-bindings.js +59 -0
- package/src/lib/stamp-form-dataset-bindings.js.map +1 -0
- package/src/lib/template-screens.d.ts +227 -0
- package/src/lib/template-screens.js +364 -0
- 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;
|