@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
|
+
*/ import * as Aglyn from "@aglyn/aglyn/server";
|
|
17
|
+
import getScreen from "./get-screen.js";
|
|
18
|
+
import getScreenVersion from "./get-screen-version.js";
|
|
19
|
+
/**
|
|
20
|
+
* Which shared layout wraps the pages the platform builds rather than the
|
|
21
|
+
* author does (AGL-2513) — site search today, and the collection article a
|
|
22
|
+
* site with no entry template falls back to.
|
|
23
|
+
*
|
|
24
|
+
* ## Why a setting and not just the home screen's layout
|
|
25
|
+
*
|
|
26
|
+
* The home screen's layout was already the implicit answer for the collection
|
|
27
|
+
* fallback, and it is the right DEFAULT — a site with one layout needs to
|
|
28
|
+
* configure nothing, and every site has a home page. It is the wrong RULE.
|
|
29
|
+
* Plenty of sites give the home page a layout of its own: a transparent
|
|
30
|
+
* header over a hero, no breadcrumb, a fat marketing footer. Search results
|
|
31
|
+
* inheriting that is how `/search` ends up with a header designed to sit on
|
|
32
|
+
* top of an image it does not have.
|
|
33
|
+
*
|
|
34
|
+
* `host.builtInPageLayoutId` is the escape hatch, and it is a LAYOUT id
|
|
35
|
+
* rather than a screen id on purpose. What a built-in page needs is the
|
|
36
|
+
* site's chrome around a body the platform composes; designating a screen
|
|
37
|
+
* would mean designating a page whose content is ignored — a slot the author
|
|
38
|
+
* cannot fill and cannot see the shape of.
|
|
39
|
+
*
|
|
40
|
+
* Returns `undefined` when neither is available, and every caller treats that
|
|
41
|
+
* as "render the body with no chrome" rather than as a failure: a search page
|
|
42
|
+
* without a header is worse than one with, and a search page that 500s
|
|
43
|
+
* because a layout was deleted is worse than both.
|
|
44
|
+
*/ export const BUILT_IN_PAGE_LAYOUT_FIELD = 'builtInPageLayoutId';
|
|
45
|
+
export async function resolveBuiltInPageLayoutId(options) {
|
|
46
|
+
var _ref;
|
|
47
|
+
const { hostId, host } = options;
|
|
48
|
+
const designated = String((_ref = host == null ? void 0 : host[BUILT_IN_PAGE_LAYOUT_FIELD]) != null ? _ref : '').trim();
|
|
49
|
+
if (designated) return designated;
|
|
50
|
+
try {
|
|
51
|
+
var _ref1;
|
|
52
|
+
/*
|
|
53
|
+
The home screen's layout, which is the closest thing a site has to a
|
|
54
|
+
site-wide default.
|
|
55
|
+
|
|
56
|
+
## Version-first, then the screen
|
|
57
|
+
|
|
58
|
+
This used to read `screen.layoutId` alone, on the stated grounds that
|
|
59
|
+
"screens carry their own `layoutId`". They frequently do not. The
|
|
60
|
+
binding lives on the VERSION document whenever a layout was chosen while
|
|
61
|
+
editing — key-present on the version wins over the screen's, which is
|
|
62
|
+
the precedence `composeScreenNodes` applies on every published page —
|
|
63
|
+
and a screen whose layout was only ever set that way has no `layoutId`
|
|
64
|
+
of its own at all.
|
|
65
|
+
|
|
66
|
+
So the fallback resolved to `undefined` for those hosts and every
|
|
67
|
+
built-in page rendered with NO CHROME, which is precisely the defect
|
|
68
|
+
AGL-2513 existed to fix. It was invisible for a while because nothing
|
|
69
|
+
linked to a built-in page; AGL-2518 made every byline on every article
|
|
70
|
+
link to one, and `aglyn.com` turned out to be exactly this shape — a
|
|
71
|
+
home page bound to "Marketing base" on its version, an unset
|
|
72
|
+
`builtInPageLayoutId`, and a chrome-less `/search` nobody had reason to
|
|
73
|
+
visit.
|
|
74
|
+
|
|
75
|
+
`null` on the version is a deliberate "no layout" and is honoured as
|
|
76
|
+
such rather than falling through to the screen — same as composition.
|
|
77
|
+
*/ const screensMap = (_ref1 = host == null ? void 0 : host.screens) != null ? _ref1 : {};
|
|
78
|
+
const homeEntry = Object.entries(screensMap).find(([, path])=>path === Aglyn.SCREEN_ROOT_PATH);
|
|
79
|
+
if (!homeEntry) return undefined;
|
|
80
|
+
const homeRes = await getScreen({
|
|
81
|
+
hostId,
|
|
82
|
+
screenId: homeEntry[0]
|
|
83
|
+
});
|
|
84
|
+
const screen = homeRes.screen;
|
|
85
|
+
if (!screen) return undefined;
|
|
86
|
+
if (screen.versionId) {
|
|
87
|
+
// Fail-open to the screen's own binding, exactly as composition does:
|
|
88
|
+
// a version read that throws must not cost the page its chrome.
|
|
89
|
+
const versionRes = await getScreenVersion({
|
|
90
|
+
hostId: hostId,
|
|
91
|
+
screenId: homeEntry[0],
|
|
92
|
+
versionId: screen.versionId
|
|
93
|
+
}).catch(()=>null);
|
|
94
|
+
const version = versionRes == null ? void 0 : versionRes.version;
|
|
95
|
+
if (version && 'layoutId' in version) {
|
|
96
|
+
return version.layoutId ? String(version.layoutId) : undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return screen.layoutId ? String(screen.layoutId) : undefined;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('built-in page layout lookup failed', error);
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export default resolveBuiltInPageLayoutId;
|
|
106
|
+
|
|
107
|
+
//# sourceMappingURL=built-in-page-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/built-in-page-layout.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport getScreen from './get-screen'\nimport getScreenVersion from './get-screen-version'\n\n/**\n * Which shared layout wraps the pages the platform builds rather than the\n * author does (AGL-2513) — site search today, and the collection article a\n * site with no entry template falls back to.\n *\n * ## Why a setting and not just the home screen's layout\n *\n * The home screen's layout was already the implicit answer for the collection\n * fallback, and it is the right DEFAULT — a site with one layout needs to\n * configure nothing, and every site has a home page. It is the wrong RULE.\n * Plenty of sites give the home page a layout of its own: a transparent\n * header over a hero, no breadcrumb, a fat marketing footer. Search results\n * inheriting that is how `/search` ends up with a header designed to sit on\n * top of an image it does not have.\n *\n * `host.builtInPageLayoutId` is the escape hatch, and it is a LAYOUT id\n * rather than a screen id on purpose. What a built-in page needs is the\n * site's chrome around a body the platform composes; designating a screen\n * would mean designating a page whose content is ignored — a slot the author\n * cannot fill and cannot see the shape of.\n *\n * Returns `undefined` when neither is available, and every caller treats that\n * as \"render the body with no chrome\" rather than as a failure: a search page\n * without a header is worse than one with, and a search page that 500s\n * because a layout was deleted is worse than both.\n */\nexport const BUILT_IN_PAGE_LAYOUT_FIELD = 'builtInPageLayoutId' as const\n\nexport async function resolveBuiltInPageLayoutId(options: {\n hostId: string\n host: any\n}): Promise<string | undefined> {\n const { hostId, host } = options\n const designated = String(host?.[BUILT_IN_PAGE_LAYOUT_FIELD] ?? '').trim()\n if (designated) return designated\n try {\n /*\n The home screen's layout, which is the closest thing a site has to a\n site-wide default.\n\n ## Version-first, then the screen\n\n This used to read `screen.layoutId` alone, on the stated grounds that\n \"screens carry their own `layoutId`\". They frequently do not. The\n binding lives on the VERSION document whenever a layout was chosen while\n editing — key-present on the version wins over the screen's, which is\n the precedence `composeScreenNodes` applies on every published page —\n and a screen whose layout was only ever set that way has no `layoutId`\n of its own at all.\n\n So the fallback resolved to `undefined` for those hosts and every\n built-in page rendered with NO CHROME, which is precisely the defect\n AGL-2513 existed to fix. It was invisible for a while because nothing\n linked to a built-in page; AGL-2518 made every byline on every article\n link to one, and `aglyn.com` turned out to be exactly this shape — a\n home page bound to \"Marketing base\" on its version, an unset\n `builtInPageLayoutId`, and a chrome-less `/search` nobody had reason to\n visit.\n\n `null` on the version is a deliberate \"no layout\" and is honoured as\n such rather than falling through to the screen — same as composition.\n */\n const screensMap = (host?.screens ?? {}) as Record<string, string>\n const homeEntry = Object.entries(screensMap).find(\n ([, path]) => path === Aglyn.SCREEN_ROOT_PATH,\n )\n if (!homeEntry) return undefined\n const homeRes = await getScreen({ hostId, screenId: homeEntry[0] })\n const screen = homeRes.screen as\n | { layoutId?: string | null; versionId?: string }\n | undefined\n if (!screen) return undefined\n if (screen.versionId) {\n // Fail-open to the screen's own binding, exactly as composition does:\n // a version read that throws must not cost the page its chrome.\n const versionRes = await getScreenVersion({\n hostId: hostId as never,\n screenId: homeEntry[0] as never,\n versionId: screen.versionId as never,\n }).catch(() => null)\n const version = versionRes?.version as\n | { layoutId?: string | null }\n | undefined\n if (version && 'layoutId' in version) {\n return version.layoutId ? String(version.layoutId) : undefined\n }\n }\n return screen.layoutId ? String(screen.layoutId) : undefined\n } catch (error) {\n console.error('built-in page layout lookup failed', error)\n return undefined\n }\n}\n\nexport default resolveBuiltInPageLayoutId\n"],"names":["Aglyn","getScreen","getScreenVersion","BUILT_IN_PAGE_LAYOUT_FIELD","resolveBuiltInPageLayoutId","options","hostId","host","designated","String","trim","screensMap","screens","homeEntry","Object","entries","find","path","SCREEN_ROOT_PATH","undefined","homeRes","screenId","screen","versionId","versionRes","catch","version","layoutId","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,sBAAqB;AAC5C,OAAOC,eAAe,kBAAc;AACpC,OAAOC,sBAAsB,0BAAsB;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,MAAMC,6BAA6B,sBAA8B;AAExE,OAAO,eAAeC,2BAA2BC,OAGhD;;IACC,MAAM,EAAEC,MAAM,EAAEC,IAAI,EAAE,GAAGF;IACzB,MAAMG,aAAaC,eAAOF,wBAAAA,IAAM,CAACJ,2BAA2B,mBAAI,IAAIO,IAAI;IACxE,IAAIF,YAAY,OAAOA;IACvB,IAAI;;QACF;;;;;;;;;;;;;;;;;;;;;;;;;IAyBA,GACA,MAAMG,sBAAcJ,wBAAAA,KAAMK,OAAO,oBAAI,CAAC;QACtC,MAAMC,YAAYC,OAAOC,OAAO,CAACJ,YAAYK,IAAI,CAC/C,CAAC,GAAGC,KAAK,GAAKA,SAASjB,MAAMkB,gBAAgB;QAE/C,IAAI,CAACL,WAAW,OAAOM;QACvB,MAAMC,UAAU,MAAMnB,UAAU;YAAEK;YAAQe,UAAUR,SAAS,CAAC,EAAE;QAAC;QACjE,MAAMS,SAASF,QAAQE,MAAM;QAG7B,IAAI,CAACA,QAAQ,OAAOH;QACpB,IAAIG,OAAOC,SAAS,EAAE;YACpB,sEAAsE;YACtE,gEAAgE;YAChE,MAAMC,aAAa,MAAMtB,iBAAiB;gBACxCI,QAAQA;gBACRe,UAAUR,SAAS,CAAC,EAAE;gBACtBU,WAAWD,OAAOC,SAAS;YAC7B,GAAGE,KAAK,CAAC,IAAM;YACf,MAAMC,UAAUF,8BAAAA,WAAYE,OAAO;YAGnC,IAAIA,WAAW,cAAcA,SAAS;gBACpC,OAAOA,QAAQC,QAAQ,GAAGlB,OAAOiB,QAAQC,QAAQ,IAAIR;YACvD;QACF;QACA,OAAOG,OAAOK,QAAQ,GAAGlB,OAAOa,OAAOK,QAAQ,IAAIR;IACrD,EAAE,OAAOS,OAAO;QACdC,QAAQD,KAAK,CAAC,sCAAsCA;QACpD,OAAOT;IACT;AACF;AAEA,eAAef,2BAA0B"}
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
import { type HostContactCreated, type UpsertHostContactOptions, type UpsertHostContactVerdict } from '@aglyn/tenant-data-admin';
|
|
18
|
+
import type { HostEventPayload } from './host-event-listeners';
|
|
19
|
+
/**
|
|
20
|
+
* THE contact capture door for a server path (AGL-2605).
|
|
21
|
+
*
|
|
22
|
+
* `upsertHostContact` writes the contact and reports, through its
|
|
23
|
+
* `onCreated` hook, when the write made a NEW person. This wrapper is the
|
|
24
|
+
* one place that hook is bound to `contactCreated`, and it is the function
|
|
25
|
+
* every server door calls — the forms route, the membership and newsletter
|
|
26
|
+
* handlers, the order and booking webhooks. A door that calls
|
|
27
|
+
* `upsertHostContact` directly still captures the contact and fires nothing,
|
|
28
|
+
* which is a contact no automation can welcome; `capture-host-contact.spec`
|
|
29
|
+
* scans for exactly that call.
|
|
30
|
+
*
|
|
31
|
+
* ## Why the binding lives here and not in the data library
|
|
32
|
+
*
|
|
33
|
+
* What listens for the event — the automation engine among them — imports
|
|
34
|
+
* the data library for its Firestore handle, its org helpers and its
|
|
35
|
+
* senders. The data library emitting an event would import the listeners
|
|
36
|
+
* back, which is a cycle, and the module boundaries (`scope:data` depends on
|
|
37
|
+
* data and util only) refuse it in any case. So the lower layer reports a
|
|
38
|
+
* fact and this layer, which already knows how to announce one, announces
|
|
39
|
+
* it. The alternative — a sink the runtime registers into when its module
|
|
40
|
+
* happens to be imported — would fire only in a process that had loaded the
|
|
41
|
+
* runtime, and a Stripe webhook that created a contact in a process that had
|
|
42
|
+
* not would announce nothing with no error anywhere. The listeners themselves
|
|
43
|
+
* are registered by a call made at boot (`host-event-listeners.ts`), so every
|
|
44
|
+
* process has them before its first request.
|
|
45
|
+
*
|
|
46
|
+
* Fire-and-forget in the same sense the capture itself is: the hook's
|
|
47
|
+
* failure is caught inside `upsertHostContact`, so a runner that throws
|
|
48
|
+
* costs the door nothing, and this function never rejects.
|
|
49
|
+
*
|
|
50
|
+
* ## The company, before the announcement (AGL-2613)
|
|
51
|
+
*
|
|
52
|
+
* A new person with a work email address is linked to the company at that
|
|
53
|
+
* domain here, in the same hook, BEFORE `contactCreated` goes out — so an
|
|
54
|
+
* automation that reads the new contact finds them already filed, rather
|
|
55
|
+
* than racing a link that lands a moment later. Only when the door did not
|
|
56
|
+
* name a company itself: the console's drawer and an import row that
|
|
57
|
+
* carried one have written it into the facet, and the capture must not
|
|
58
|
+
* second-guess a person's choice with a domain match. The association never
|
|
59
|
+
* rejects, and its own catch keeps a failed lookup from costing the event.
|
|
60
|
+
*
|
|
61
|
+
* ## The owner, on the same terms (AGL-2618)
|
|
62
|
+
*
|
|
63
|
+
* Then the org's assignment rules and the site's default owner decide who
|
|
64
|
+
* follows the new person up — again before `contactCreated`, so an
|
|
65
|
+
* automation's "create a task for the owner" finds one — and again only
|
|
66
|
+
* when the door named none: a drawer, an import column or a conversion
|
|
67
|
+
* that picked an owner has said whose the record is. The pass reads the
|
|
68
|
+
* form and the tags off the same options the data library was handed,
|
|
69
|
+
* because the created-report carries the identity and not the routing.
|
|
70
|
+
* It never rejects either; a record it could not assign is one somebody
|
|
71
|
+
* assigns by hand, which is what every record was before this existed.
|
|
72
|
+
*/
|
|
73
|
+
export declare function captureHostContact(options: Omit<UpsertHostContactOptions, 'onCreated'>): Promise<UpsertHostContactVerdict>;
|
|
74
|
+
/**
|
|
75
|
+
* The `contactCreated` payload, as the scalars an event may carry.
|
|
76
|
+
*
|
|
77
|
+
* `HostEventPayload` holds strings, numbers and booleans and nothing
|
|
78
|
+
* nested, because the payload seeds an expression scope and a condition
|
|
79
|
+
* editor whose operators compare strings. So `campaignIds` rides as one
|
|
80
|
+
* comma-joined string — `contains` still finds a campaign in it — and is
|
|
81
|
+
* present only when the capture had campaigns, so `notEmpty` on it reads
|
|
82
|
+
* as "came in through a campaign form". `name` and `lifecycleStage` are
|
|
83
|
+
* always present, empty when the door had none, so a condition on either
|
|
84
|
+
* never sees a missing key: `lifecycleStage == "lead"` is the filter that
|
|
85
|
+
* picks the form captures out of the sign-ups (AGL-2612).
|
|
86
|
+
*
|
|
87
|
+
* `formId` rides the same way `campaignIds` does — present only when the
|
|
88
|
+
* capture came through a form — because the created-report carries the
|
|
89
|
+
* person's identity and not the routing, and the form is routing: it is
|
|
90
|
+
* read off the interaction the door was handed. Present, it is what lets a
|
|
91
|
+
* condition say `formId` equals this form and no other (AGL-2626); absent,
|
|
92
|
+
* `notEmpty` on it reads as "came in through a form".
|
|
93
|
+
*/
|
|
94
|
+
export declare function contactCreatedPayload(created: HostContactCreated, formId?: string | null): HostEventPayload;
|
|
95
|
+
export default captureHostContact;
|
|
@@ -0,0 +1,137 @@
|
|
|
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 { upsertHostContact } from "@aglyn/tenant-data-admin";
|
|
18
|
+
import { assignOwnerForCapture } from "./assign-contact-owner.js";
|
|
19
|
+
import { associateCompanyByDomain } from "./associate-company-by-domain.js";
|
|
20
|
+
import { emitHostEvent } from "./emit-host-event.js";
|
|
21
|
+
/**
|
|
22
|
+
* THE contact capture door for a server path (AGL-2605).
|
|
23
|
+
*
|
|
24
|
+
* `upsertHostContact` writes the contact and reports, through its
|
|
25
|
+
* `onCreated` hook, when the write made a NEW person. This wrapper is the
|
|
26
|
+
* one place that hook is bound to `contactCreated`, and it is the function
|
|
27
|
+
* every server door calls — the forms route, the membership and newsletter
|
|
28
|
+
* handlers, the order and booking webhooks. A door that calls
|
|
29
|
+
* `upsertHostContact` directly still captures the contact and fires nothing,
|
|
30
|
+
* which is a contact no automation can welcome; `capture-host-contact.spec`
|
|
31
|
+
* scans for exactly that call.
|
|
32
|
+
*
|
|
33
|
+
* ## Why the binding lives here and not in the data library
|
|
34
|
+
*
|
|
35
|
+
* What listens for the event — the automation engine among them — imports
|
|
36
|
+
* the data library for its Firestore handle, its org helpers and its
|
|
37
|
+
* senders. The data library emitting an event would import the listeners
|
|
38
|
+
* back, which is a cycle, and the module boundaries (`scope:data` depends on
|
|
39
|
+
* data and util only) refuse it in any case. So the lower layer reports a
|
|
40
|
+
* fact and this layer, which already knows how to announce one, announces
|
|
41
|
+
* it. The alternative — a sink the runtime registers into when its module
|
|
42
|
+
* happens to be imported — would fire only in a process that had loaded the
|
|
43
|
+
* runtime, and a Stripe webhook that created a contact in a process that had
|
|
44
|
+
* not would announce nothing with no error anywhere. The listeners themselves
|
|
45
|
+
* are registered by a call made at boot (`host-event-listeners.ts`), so every
|
|
46
|
+
* process has them before its first request.
|
|
47
|
+
*
|
|
48
|
+
* Fire-and-forget in the same sense the capture itself is: the hook's
|
|
49
|
+
* failure is caught inside `upsertHostContact`, so a runner that throws
|
|
50
|
+
* costs the door nothing, and this function never rejects.
|
|
51
|
+
*
|
|
52
|
+
* ## The company, before the announcement (AGL-2613)
|
|
53
|
+
*
|
|
54
|
+
* A new person with a work email address is linked to the company at that
|
|
55
|
+
* domain here, in the same hook, BEFORE `contactCreated` goes out — so an
|
|
56
|
+
* automation that reads the new contact finds them already filed, rather
|
|
57
|
+
* than racing a link that lands a moment later. Only when the door did not
|
|
58
|
+
* name a company itself: the console's drawer and an import row that
|
|
59
|
+
* carried one have written it into the facet, and the capture must not
|
|
60
|
+
* second-guess a person's choice with a domain match. The association never
|
|
61
|
+
* rejects, and its own catch keeps a failed lookup from costing the event.
|
|
62
|
+
*
|
|
63
|
+
* ## The owner, on the same terms (AGL-2618)
|
|
64
|
+
*
|
|
65
|
+
* Then the org's assignment rules and the site's default owner decide who
|
|
66
|
+
* follows the new person up — again before `contactCreated`, so an
|
|
67
|
+
* automation's "create a task for the owner" finds one — and again only
|
|
68
|
+
* when the door named none: a drawer, an import column or a conversion
|
|
69
|
+
* that picked an owner has said whose the record is. The pass reads the
|
|
70
|
+
* form and the tags off the same options the data library was handed,
|
|
71
|
+
* because the created-report carries the identity and not the routing.
|
|
72
|
+
* It never rejects either; a record it could not assign is one somebody
|
|
73
|
+
* assigns by hand, which is what every record was before this existed.
|
|
74
|
+
*/ export async function captureHostContact(options) {
|
|
75
|
+
return upsertHostContact(_extends({}, options, {
|
|
76
|
+
onCreated: async (created)=>{
|
|
77
|
+
var _options_facet, _options_facet1;
|
|
78
|
+
if (!((_options_facet = options.facet) == null ? void 0 : _options_facet.companyId)) {
|
|
79
|
+
await associateCompanyByDomain(created).catch((error)=>{
|
|
80
|
+
console.error('captureHostContact company association failed', error);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (!((_options_facet1 = options.facet) == null ? void 0 : _options_facet1.ownerUid)) {
|
|
84
|
+
var _options_interaction_formId;
|
|
85
|
+
await assignOwnerForCapture({
|
|
86
|
+
hostId: created.hostId,
|
|
87
|
+
contactId: created.contactId,
|
|
88
|
+
email: created.email,
|
|
89
|
+
source: created.source,
|
|
90
|
+
formId: (_options_interaction_formId = options.interaction.formId) != null ? _options_interaction_formId : null,
|
|
91
|
+
tags: options.tags
|
|
92
|
+
}).catch((error)=>{
|
|
93
|
+
console.error('captureHostContact owner assignment failed', error);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
await emitHostEvent(created.hostId, 'contactCreated', contactCreatedPayload(created, options.interaction.formId));
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The `contactCreated` payload, as the scalars an event may carry.
|
|
102
|
+
*
|
|
103
|
+
* `HostEventPayload` holds strings, numbers and booleans and nothing
|
|
104
|
+
* nested, because the payload seeds an expression scope and a condition
|
|
105
|
+
* editor whose operators compare strings. So `campaignIds` rides as one
|
|
106
|
+
* comma-joined string — `contains` still finds a campaign in it — and is
|
|
107
|
+
* present only when the capture had campaigns, so `notEmpty` on it reads
|
|
108
|
+
* as "came in through a campaign form". `name` and `lifecycleStage` are
|
|
109
|
+
* always present, empty when the door had none, so a condition on either
|
|
110
|
+
* never sees a missing key: `lifecycleStage == "lead"` is the filter that
|
|
111
|
+
* picks the form captures out of the sign-ups (AGL-2612).
|
|
112
|
+
*
|
|
113
|
+
* `formId` rides the same way `campaignIds` does — present only when the
|
|
114
|
+
* capture came through a form — because the created-report carries the
|
|
115
|
+
* person's identity and not the routing, and the form is routing: it is
|
|
116
|
+
* read off the interaction the door was handed. Present, it is what lets a
|
|
117
|
+
* condition say `formId` equals this form and no other (AGL-2626); absent,
|
|
118
|
+
* `notEmpty` on it reads as "came in through a form".
|
|
119
|
+
*/ export function contactCreatedPayload(created, formId) {
|
|
120
|
+
var _created_name, _created_lifecycleStage;
|
|
121
|
+
const form = String(formId != null ? formId : '').trim();
|
|
122
|
+
return _extends({
|
|
123
|
+
contactId: created.contactId,
|
|
124
|
+
email: created.email,
|
|
125
|
+
name: (_created_name = created.name) != null ? _created_name : '',
|
|
126
|
+
source: created.source,
|
|
127
|
+
hostId: created.hostId,
|
|
128
|
+
lifecycleStage: (_created_lifecycleStage = created.lifecycleStage) != null ? _created_lifecycleStage : ''
|
|
129
|
+
}, created.campaignIds.length ? {
|
|
130
|
+
campaignIds: created.campaignIds.join(',')
|
|
131
|
+
} : {}, form ? {
|
|
132
|
+
formId: form
|
|
133
|
+
} : {});
|
|
134
|
+
}
|
|
135
|
+
export default captureHostContact;
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=capture-host-contact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/capture-host-contact.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 {\n type HostContactCreated,\n upsertHostContact,\n type UpsertHostContactOptions,\n type UpsertHostContactVerdict,\n} from '@aglyn/tenant-data-admin'\nimport { assignOwnerForCapture } from './assign-contact-owner'\nimport { associateCompanyByDomain } from './associate-company-by-domain'\nimport { emitHostEvent } from './emit-host-event'\nimport type { HostEventPayload } from './host-event-listeners'\n\n/**\n * THE contact capture door for a server path (AGL-2605).\n *\n * `upsertHostContact` writes the contact and reports, through its\n * `onCreated` hook, when the write made a NEW person. This wrapper is the\n * one place that hook is bound to `contactCreated`, and it is the function\n * every server door calls — the forms route, the membership and newsletter\n * handlers, the order and booking webhooks. A door that calls\n * `upsertHostContact` directly still captures the contact and fires nothing,\n * which is a contact no automation can welcome; `capture-host-contact.spec`\n * scans for exactly that call.\n *\n * ## Why the binding lives here and not in the data library\n *\n * What listens for the event — the automation engine among them — imports\n * the data library for its Firestore handle, its org helpers and its\n * senders. The data library emitting an event would import the listeners\n * back, which is a cycle, and the module boundaries (`scope:data` depends on\n * data and util only) refuse it in any case. So the lower layer reports a\n * fact and this layer, which already knows how to announce one, announces\n * it. The alternative — a sink the runtime registers into when its module\n * happens to be imported — would fire only in a process that had loaded the\n * runtime, and a Stripe webhook that created a contact in a process that had\n * not would announce nothing with no error anywhere. The listeners themselves\n * are registered by a call made at boot (`host-event-listeners.ts`), so every\n * process has them before its first request.\n *\n * Fire-and-forget in the same sense the capture itself is: the hook's\n * failure is caught inside `upsertHostContact`, so a runner that throws\n * costs the door nothing, and this function never rejects.\n *\n * ## The company, before the announcement (AGL-2613)\n *\n * A new person with a work email address is linked to the company at that\n * domain here, in the same hook, BEFORE `contactCreated` goes out — so an\n * automation that reads the new contact finds them already filed, rather\n * than racing a link that lands a moment later. Only when the door did not\n * name a company itself: the console's drawer and an import row that\n * carried one have written it into the facet, and the capture must not\n * second-guess a person's choice with a domain match. The association never\n * rejects, and its own catch keeps a failed lookup from costing the event.\n *\n * ## The owner, on the same terms (AGL-2618)\n *\n * Then the org's assignment rules and the site's default owner decide who\n * follows the new person up — again before `contactCreated`, so an\n * automation's \"create a task for the owner\" finds one — and again only\n * when the door named none: a drawer, an import column or a conversion\n * that picked an owner has said whose the record is. The pass reads the\n * form and the tags off the same options the data library was handed,\n * because the created-report carries the identity and not the routing.\n * It never rejects either; a record it could not assign is one somebody\n * assigns by hand, which is what every record was before this existed.\n */\nexport async function captureHostContact(\n options: Omit<UpsertHostContactOptions, 'onCreated'>,\n): Promise<UpsertHostContactVerdict> {\n return upsertHostContact({\n ...options,\n onCreated: async (created) => {\n if (!options.facet?.companyId) {\n await associateCompanyByDomain(created).catch((error: unknown) => {\n console.error('captureHostContact company association failed', error)\n })\n }\n if (!options.facet?.ownerUid) {\n await assignOwnerForCapture({\n hostId: created.hostId,\n contactId: created.contactId,\n email: created.email,\n source: created.source,\n formId: options.interaction.formId ?? null,\n tags: options.tags,\n }).catch((error: unknown) => {\n console.error('captureHostContact owner assignment failed', error)\n })\n }\n await emitHostEvent(\n created.hostId,\n 'contactCreated',\n contactCreatedPayload(created, options.interaction.formId),\n )\n },\n })\n}\n\n/**\n * The `contactCreated` payload, as the scalars an event may carry.\n *\n * `HostEventPayload` holds strings, numbers and booleans and nothing\n * nested, because the payload seeds an expression scope and a condition\n * editor whose operators compare strings. So `campaignIds` rides as one\n * comma-joined string — `contains` still finds a campaign in it — and is\n * present only when the capture had campaigns, so `notEmpty` on it reads\n * as \"came in through a campaign form\". `name` and `lifecycleStage` are\n * always present, empty when the door had none, so a condition on either\n * never sees a missing key: `lifecycleStage == \"lead\"` is the filter that\n * picks the form captures out of the sign-ups (AGL-2612).\n *\n * `formId` rides the same way `campaignIds` does — present only when the\n * capture came through a form — because the created-report carries the\n * person's identity and not the routing, and the form is routing: it is\n * read off the interaction the door was handed. Present, it is what lets a\n * condition say `formId` equals this form and no other (AGL-2626); absent,\n * `notEmpty` on it reads as \"came in through a form\".\n */\nexport function contactCreatedPayload(\n created: HostContactCreated,\n formId?: string | null,\n): HostEventPayload {\n const form = String(formId ?? '').trim()\n return {\n contactId: created.contactId,\n email: created.email,\n name: created.name ?? '',\n source: created.source,\n hostId: created.hostId,\n lifecycleStage: created.lifecycleStage ?? '',\n ...(created.campaignIds.length\n ? { campaignIds: created.campaignIds.join(',') }\n : {}),\n ...(form ? { formId: form } : {}),\n }\n}\n\nexport default captureHostContact\n"],"names":["upsertHostContact","assignOwnerForCapture","associateCompanyByDomain","emitHostEvent","captureHostContact","options","onCreated","created","facet","companyId","catch","error","console","ownerUid","hostId","contactId","email","source","formId","interaction","tags","contactCreatedPayload","form","String","trim","name","lifecycleStage","campaignIds","length","join"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAEEA,iBAAiB,QAGZ,2BAA0B;AACjC,SAASC,qBAAqB,QAAQ,4BAAwB;AAC9D,SAASC,wBAAwB,QAAQ,mCAA+B;AACxE,SAASC,aAAa,QAAQ,uBAAmB;AAGjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDC,GACD,OAAO,eAAeC,mBACpBC,OAAoD;IAEpD,OAAOL,kBAAkB,aACpBK;QACHC,WAAW,OAAOC;gBACXF,gBAKAA;YALL,IAAI,GAACA,iBAAAA,QAAQG,KAAK,qBAAbH,eAAeI,SAAS,GAAE;gBAC7B,MAAMP,yBAAyBK,SAASG,KAAK,CAAC,CAACC;oBAC7CC,QAAQD,KAAK,CAAC,iDAAiDA;gBACjE;YACF;YACA,IAAI,GAACN,kBAAAA,QAAQG,KAAK,qBAAbH,gBAAeQ,QAAQ,GAAE;oBAMlBR;gBALV,MAAMJ,sBAAsB;oBAC1Ba,QAAQP,QAAQO,MAAM;oBACtBC,WAAWR,QAAQQ,SAAS;oBAC5BC,OAAOT,QAAQS,KAAK;oBACpBC,QAAQV,QAAQU,MAAM;oBACtBC,MAAM,GAAEb,8BAAAA,QAAQc,WAAW,CAACD,MAAM,YAA1Bb,8BAA8B;oBACtCe,MAAMf,QAAQe,IAAI;gBACpB,GAAGV,KAAK,CAAC,CAACC;oBACRC,QAAQD,KAAK,CAAC,8CAA8CA;gBAC9D;YACF;YACA,MAAMR,cACJI,QAAQO,MAAM,EACd,kBACAO,sBAAsBd,SAASF,QAAQc,WAAW,CAACD,MAAM;QAE7D;;AAEJ;AAEA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,SAASG,sBACdd,OAA2B,EAC3BW,MAAsB;QAMdX,eAGUA;IAPlB,MAAMe,OAAOC,OAAOL,iBAAAA,SAAU,IAAIM,IAAI;IACtC,OAAO;QACLT,WAAWR,QAAQQ,SAAS;QAC5BC,OAAOT,QAAQS,KAAK;QACpBS,IAAI,GAAElB,gBAAAA,QAAQkB,IAAI,YAAZlB,gBAAgB;QACtBU,QAAQV,QAAQU,MAAM;QACtBH,QAAQP,QAAQO,MAAM;QACtBY,cAAc,GAAEnB,0BAAAA,QAAQmB,cAAc,YAAtBnB,0BAA0B;OACtCA,QAAQoB,WAAW,CAACC,MAAM,GAC1B;QAAED,aAAapB,QAAQoB,WAAW,CAACE,IAAI,CAAC;IAAK,IAC7C,CAAC,GACDP,OAAO;QAAEJ,QAAQI;IAAK,IAAI,CAAC;AAEnC;AAEA,eAAelB,mBAAkB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
import * as Aglyn from '@aglyn/aglyn/server';
|
|
18
|
+
type NodesMap = Record<string, Aglyn.AglynNodeSchema>;
|
|
19
|
+
interface FallbackCollection {
|
|
20
|
+
slug: string;
|
|
21
|
+
displayName: string;
|
|
22
|
+
/** Category taxonomy (AGL-582) for `categoryId` → name resolution. */
|
|
23
|
+
categories?: Aglyn.CollectionCategory[];
|
|
24
|
+
}
|
|
25
|
+
interface FallbackEntry {
|
|
26
|
+
title?: string;
|
|
27
|
+
slug?: string;
|
|
28
|
+
excerpt?: string;
|
|
29
|
+
/** Per-entry byline (AGL-686); falls back to the site as author. */
|
|
30
|
+
authorName?: string;
|
|
31
|
+
body?: string;
|
|
32
|
+
coverImage?: string;
|
|
33
|
+
/** The featured video (AGL-2956); plays in the cover's place. */
|
|
34
|
+
coverVideo?: string;
|
|
35
|
+
/** Meta description override (AGL-582); falls back to `excerpt`. */
|
|
36
|
+
seoDescription?: string;
|
|
37
|
+
/** Stable taxonomy reference (AGL-582); wins over `category`. */
|
|
38
|
+
categoryId?: string;
|
|
39
|
+
/** Legacy free-typed category (AGL-582); read-only fallback. */
|
|
40
|
+
category?: string;
|
|
41
|
+
tags?: string[];
|
|
42
|
+
publishedAt?: {
|
|
43
|
+
seconds: number;
|
|
44
|
+
} | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Built-in entry article as canvas nodes (AGL-551): when a collection has
|
|
48
|
+
* no entry-template screen, `/{collection}/{entry}` renders these through
|
|
49
|
+
* the normal compose pipeline — site theme, shared layout chrome, and the
|
|
50
|
+
* markdown-rendering Entry body block — instead of the old unthemed HTML.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildCollectionEntryFallbackNodes(collection: FallbackCollection, entry: FallbackEntry,
|
|
53
|
+
/**
|
|
54
|
+
* The site being composed, so an org-scoped reference is host-qualified the
|
|
55
|
+
* way every other render surface qualifies one (AGL-1043/AGL-1407). Optional
|
|
56
|
+
* because a bare org reference still resolves without it; passing it is what
|
|
57
|
+
* lets an asset restricted to this site resolve at all.
|
|
58
|
+
*/
|
|
59
|
+
hostId?: string): NodesMap;
|
|
60
|
+
/** Pagination state for the built-in list (AGL-620). */
|
|
61
|
+
export interface FallbackListPagination {
|
|
62
|
+
page: number;
|
|
63
|
+
perPage: number;
|
|
64
|
+
totalPages: number;
|
|
65
|
+
}
|
|
66
|
+
/** The routed category for a filtered listing (AGL-1321). */
|
|
67
|
+
export interface FallbackListCategory {
|
|
68
|
+
slug: string;
|
|
69
|
+
name: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Built-in entry list as canvas nodes (AGL-551): a heading plus a
|
|
73
|
+
* Collection entries block whose template (title, date, excerpt, Read
|
|
74
|
+
* more) the compose pipeline expands over the published entries — the same
|
|
75
|
+
* block designers drop onto their own list-template screens. With
|
|
76
|
+
* `pagination` (AGL-620) the block renders one page and prev/next nav links
|
|
77
|
+
* to `/{slug}/page/{n}`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildCollectionListFallbackNodes(collection: FallbackCollection, hasEntries: boolean, pagination?: FallbackListPagination, category?: FallbackListCategory): NodesMap;
|
|
80
|
+
/** Entry vs list fallback selection for the routed content (AGL-551). */
|
|
81
|
+
export declare function buildCollectionFallbackNodes(content: {
|
|
82
|
+
collection: FallbackCollection;
|
|
83
|
+
entries: FallbackEntry[];
|
|
84
|
+
entry: FallbackEntry | null;
|
|
85
|
+
pagination?: FallbackListPagination | null;
|
|
86
|
+
category?: FallbackListCategory | null;
|
|
87
|
+
/** The composing site, for host-qualifying a media reference (AGL-1407). */
|
|
88
|
+
hostId?: string;
|
|
89
|
+
}): NodesMap;
|
|
90
|
+
export default buildCollectionFallbackNodes;
|