@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,116 @@
|
|
|
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 { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
18
|
+
import { PUBLISHED_SITE_DATA_TTL_SECONDS, tenantDataTag, withRenderCache } from "@aglyn/tenant-data-admin/render-cache";
|
|
19
|
+
/**
|
|
20
|
+
* Cached exactly as the components read is (AGL-1302): a form publish busts
|
|
21
|
+
* `tenant-data:{hostId}` through the console revalidate route, and the TTL is
|
|
22
|
+
* only the backstop for writes that never announce themselves.
|
|
23
|
+
*/ const FORMS_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS;
|
|
24
|
+
/**
|
|
25
|
+
* How many form documents ONE RENDER reads.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately below `Aglyn.FORMS_MAX_PER_HOST`, which bounds the catalog
|
|
28
|
+
* listings, and equal to the components bound this read is otherwise a copy
|
|
29
|
+
* of: a catalog is read when a person opens a page in the console, while this
|
|
30
|
+
* is on the hot path of every published page that places a form. The two
|
|
31
|
+
* numbers answer different questions and must not be collapsed into one.
|
|
32
|
+
*
|
|
33
|
+
* Unordered, like the components query, so past this bound the page read is an
|
|
34
|
+
* arbitrary subset rather than the first N by any rule. That is a backstop,
|
|
35
|
+
* not a plan: a site with more forms than this needs the read narrowed to the
|
|
36
|
+
* ids the page actually places, not a larger constant.
|
|
37
|
+
*/ const FORMS_PER_RENDER = 200;
|
|
38
|
+
/**
|
|
39
|
+
* The host's form entities keyed by id, in the shape the placed-form graft
|
|
40
|
+
* consumes (`Aglyn.placedFormPlacement`).
|
|
41
|
+
*
|
|
42
|
+
* Only forms with a design are returned: a `rootId` that is in the form's own
|
|
43
|
+
* `nodes`. That is not the same as a PUBLISHED design. The Forms page creates a
|
|
44
|
+
* form with its design seeded, so a form nobody has published yet still
|
|
45
|
+
* renders wherever a page places it; placing it on a published page is what
|
|
46
|
+
* puts it live. A form with no `rootId`/`nodes`, or whose root is missing from
|
|
47
|
+
* its own nodes, has no tree to graft — both are "this entity contributes
|
|
48
|
+
* nothing", and the graft leaves such a placement rendering the fields drawn on
|
|
49
|
+
* the page itself. Filtering here rather than in the graft keeps the payload
|
|
50
|
+
* to what can actually be rendered.
|
|
51
|
+
*
|
|
52
|
+
* An ARCHIVED form is deliberately still returned. Archiving retires an entity
|
|
53
|
+
* from the catalog the console lists; it is not a tombstone and it says nothing
|
|
54
|
+
* about publication. A page that still places the form keeps rendering it,
|
|
55
|
+
* because the alternative is a live page quietly losing its form the moment
|
|
56
|
+
* someone tidies up a list.
|
|
57
|
+
*
|
|
58
|
+
* Fail-open like every other compose read: on error an empty map, so every
|
|
59
|
+
* placement falls back to its own inline content and the page still serves.
|
|
60
|
+
*/ async function readForms(hostId) {
|
|
61
|
+
const data = {
|
|
62
|
+
forms: {},
|
|
63
|
+
error: null
|
|
64
|
+
};
|
|
65
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
66
|
+
await firestore.collection('hosts').doc(hostId).collection('forms').limit(FORMS_PER_RENDER).get().then((res)=>{
|
|
67
|
+
for (const docSnapshot of res.docs){
|
|
68
|
+
const value = docSnapshot.data();
|
|
69
|
+
if (!(value == null ? void 0 : value.nodes) || !(value == null ? void 0 : value.rootId)) continue;
|
|
70
|
+
// BOTH stored forms, on the hot path (AGL-1151). A form's published
|
|
71
|
+
// tree is written by the same promotion path a component's is, so it
|
|
72
|
+
// is msgpack for anything published since compression landed and a
|
|
73
|
+
// plain map for anything older. Skipping the decode would hand the
|
|
74
|
+
// graft a `Buffer`, which has no `rootId` entry — every placement
|
|
75
|
+
// would render an empty form, and the result would be stored under the
|
|
76
|
+
// render cache for the rest of its TTL.
|
|
77
|
+
const nodes = Aglyn.decodeStoredNodes(value.nodes);
|
|
78
|
+
// An undecodable design is skipped rather than grafted empty: the
|
|
79
|
+
// placement then keeps its own fields, and `decodeStoredNodes` logs
|
|
80
|
+
// why the entity did not resolve.
|
|
81
|
+
if (!(nodes == null ? void 0 : nodes[value.rootId])) continue;
|
|
82
|
+
data.forms[docSnapshot.id] = {
|
|
83
|
+
rootId: value.rootId,
|
|
84
|
+
nodes
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}).catch((error)=>{
|
|
88
|
+
console.error(error);
|
|
89
|
+
data.error = error;
|
|
90
|
+
});
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
export async function getForms(options) {
|
|
94
|
+
const { hostId } = options;
|
|
95
|
+
try {
|
|
96
|
+
return await withRenderCache({
|
|
97
|
+
key: [
|
|
98
|
+
'tenant-forms',
|
|
99
|
+
hostId
|
|
100
|
+
],
|
|
101
|
+
revalidate: FORMS_TTL_SECONDS,
|
|
102
|
+
tags: [
|
|
103
|
+
tenantDataTag(hostId)
|
|
104
|
+
],
|
|
105
|
+
read: ()=>readForms(hostId),
|
|
106
|
+
// A failed read keeps its fail-open empty map per-request only.
|
|
107
|
+
store: (value)=>!value.error
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error(error);
|
|
111
|
+
return readForms(hostId);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export default getForms;
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=get-forms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/get-forms.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 { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n PUBLISHED_SITE_DATA_TTL_SECONDS,\n tenantDataTag,\n withRenderCache,\n} from '@aglyn/tenant-data-admin/render-cache'\n\n/**\n * Cached exactly as the components read is (AGL-1302): a form publish busts\n * `tenant-data:{hostId}` through the console revalidate route, and the TTL is\n * only the backstop for writes that never announce themselves.\n */\nconst FORMS_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS\n\n/**\n * How many form documents ONE RENDER reads.\n *\n * Deliberately below `Aglyn.FORMS_MAX_PER_HOST`, which bounds the catalog\n * listings, and equal to the components bound this read is otherwise a copy\n * of: a catalog is read when a person opens a page in the console, while this\n * is on the hot path of every published page that places a form. The two\n * numbers answer different questions and must not be collapsed into one.\n *\n * Unordered, like the components query, so past this bound the page read is an\n * arbitrary subset rather than the first N by any rule. That is a backstop,\n * not a plan: a site with more forms than this needs the read narrowed to the\n * ids the page actually places, not a larger constant.\n */\nconst FORMS_PER_RENDER = 200\n\n/**\n * The host's form entities keyed by id, in the shape the placed-form graft\n * consumes (`Aglyn.placedFormPlacement`).\n *\n * Only forms with a design are returned: a `rootId` that is in the form's own\n * `nodes`. That is not the same as a PUBLISHED design. The Forms page creates a\n * form with its design seeded, so a form nobody has published yet still\n * renders wherever a page places it; placing it on a published page is what\n * puts it live. A form with no `rootId`/`nodes`, or whose root is missing from\n * its own nodes, has no tree to graft — both are \"this entity contributes\n * nothing\", and the graft leaves such a placement rendering the fields drawn on\n * the page itself. Filtering here rather than in the graft keeps the payload\n * to what can actually be rendered.\n *\n * An ARCHIVED form is deliberately still returned. Archiving retires an entity\n * from the catalog the console lists; it is not a tombstone and it says nothing\n * about publication. A page that still places the form keeps rendering it,\n * because the alternative is a live page quietly losing its form the moment\n * someone tidies up a list.\n *\n * Fail-open like every other compose read: on error an empty map, so every\n * placement falls back to its own inline content and the page still serves.\n */\nasync function readForms(hostId: Aglyn.HostUid) {\n const data = {\n forms: {} as Record<string, Aglyn.PlacedFormDesign>,\n error: null as unknown,\n }\n const firestore = firebaseAdmin.app().firestore()\n\n await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('forms')\n .limit(FORMS_PER_RENDER)\n .get()\n .then((res) => {\n for (const docSnapshot of res.docs) {\n const value = docSnapshot.data() as Aglyn.FormDocument\n if (!value?.nodes || !value?.rootId) continue\n // BOTH stored forms, on the hot path (AGL-1151). A form's published\n // tree is written by the same promotion path a component's is, so it\n // is msgpack for anything published since compression landed and a\n // plain map for anything older. Skipping the decode would hand the\n // graft a `Buffer`, which has no `rootId` entry — every placement\n // would render an empty form, and the result would be stored under the\n // render cache for the rest of its TTL.\n const nodes = Aglyn.decodeStoredNodes<\n NonNullable<Aglyn.FormDocument['nodes']>\n >(value.nodes)\n // An undecodable design is skipped rather than grafted empty: the\n // placement then keeps its own fields, and `decodeStoredNodes` logs\n // why the entity did not resolve.\n if (!nodes?.[value.rootId]) continue\n data.forms[docSnapshot.id] = { rootId: value.rootId, nodes }\n }\n })\n .catch((error) => {\n console.error(error)\n data.error = error\n })\n\n return data\n}\n\nexport async function getForms(options: { hostId: Aglyn.HostUid }) {\n const { hostId } = options\n try {\n return await withRenderCache({\n key: ['tenant-forms', hostId as string],\n revalidate: FORMS_TTL_SECONDS,\n tags: [tenantDataTag(hostId as string)],\n read: () => readForms(hostId),\n // A failed read keeps its fail-open empty map per-request only.\n store: (value) => !value.error,\n })\n } catch (error) {\n console.error(error)\n return readForms(hostId)\n }\n}\n\nexport default getForms\n"],"names":["Aglyn","firebaseAdmin","PUBLISHED_SITE_DATA_TTL_SECONDS","tenantDataTag","withRenderCache","FORMS_TTL_SECONDS","FORMS_PER_RENDER","readForms","hostId","data","forms","error","firestore","app","collection","doc","limit","get","then","res","docSnapshot","docs","value","nodes","rootId","decodeStoredNodes","id","catch","console","getForms","options","key","revalidate","tags","read","store"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,sBAAqB;AAC5C,SAASC,aAAa,QAAQ,2BAA0B;AACxD,SACEC,+BAA+B,EAC/BC,aAAa,EACbC,eAAe,QACV,wCAAuC;AAE9C;;;;CAIC,GACD,MAAMC,oBAAoBH;AAE1B;;;;;;;;;;;;;CAaC,GACD,MAAMI,mBAAmB;AAEzB;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,eAAeC,UAAUC,MAAqB;IAC5C,MAAMC,OAAO;QACXC,OAAO,CAAC;QACRC,OAAO;IACT;IACA,MAAMC,YAAYX,cAAcY,GAAG,GAAGD,SAAS;IAE/C,MAAMA,UACHE,UAAU,CAAC,SACXC,GAAG,CAACP,QACJM,UAAU,CAAC,SACXE,KAAK,CAACV,kBACNW,GAAG,GACHC,IAAI,CAAC,CAACC;QACL,KAAK,MAAMC,eAAeD,IAAIE,IAAI,CAAE;YAClC,MAAMC,QAAQF,YAAYX,IAAI;YAC9B,IAAI,EAACa,yBAAAA,MAAOC,KAAK,KAAI,EAACD,yBAAAA,MAAOE,MAAM,GAAE;YACrC,oEAAoE;YACpE,qEAAqE;YACrE,mEAAmE;YACnE,mEAAmE;YACnE,kEAAkE;YAClE,uEAAuE;YACvE,wCAAwC;YACxC,MAAMD,QAAQvB,MAAMyB,iBAAiB,CAEnCH,MAAMC,KAAK;YACb,kEAAkE;YAClE,oEAAoE;YACpE,kCAAkC;YAClC,IAAI,EAACA,yBAAAA,KAAO,CAACD,MAAME,MAAM,CAAC,GAAE;YAC5Bf,KAAKC,KAAK,CAACU,YAAYM,EAAE,CAAC,GAAG;gBAAEF,QAAQF,MAAME,MAAM;gBAAED;YAAM;QAC7D;IACF,GACCI,KAAK,CAAC,CAAChB;QACNiB,QAAQjB,KAAK,CAACA;QACdF,KAAKE,KAAK,GAAGA;IACf;IAEF,OAAOF;AACT;AAEA,OAAO,eAAeoB,SAASC,OAAkC;IAC/D,MAAM,EAAEtB,MAAM,EAAE,GAAGsB;IACnB,IAAI;QACF,OAAO,MAAM1B,gBAAgB;YAC3B2B,KAAK;gBAAC;gBAAgBvB;aAAiB;YACvCwB,YAAY3B;YACZ4B,MAAM;gBAAC9B,cAAcK;aAAkB;YACvC0B,MAAM,IAAM3B,UAAUC;YACtB,gEAAgE;YAChE2B,OAAO,CAACb,QAAU,CAACA,MAAMX,KAAK;QAChC;IACF,EAAE,OAAOA,OAAO;QACdiB,QAAQjB,KAAK,CAACA;QACd,OAAOJ,UAAUC;IACnB;AACF;AAEA,eAAeqB,SAAQ"}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
export declare function getPublishedLayoutVersion(options: {
|
|
19
|
+
hostId: Aglyn.HostUid;
|
|
20
|
+
layoutId: Aglyn.LayoutUid;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
layout: Aglyn.AglynLayout | undefined;
|
|
23
|
+
version: Aglyn.AglynLayoutVersion | undefined;
|
|
24
|
+
error: unknown;
|
|
25
|
+
}>;
|
|
26
|
+
export default getPublishedLayoutVersion;
|
|
@@ -0,0 +1,99 @@
|
|
|
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 { firebaseAdmin, layoutConverter, layoutVersionConverter } from "@aglyn/tenant-data-admin";
|
|
17
|
+
import { PUBLISHED_SITE_DATA_TTL_SECONDS, tenantDataTag, withRenderCache } from "@aglyn/tenant-data-admin/render-cache";
|
|
18
|
+
import applyDuePublishSchedule from "./apply-publish-schedule.js";
|
|
19
|
+
/**
|
|
20
|
+
* Backstop TTL only (AGL-1302): a layout publish goes through the console's
|
|
21
|
+
* `/api/screens/revalidate`, which busts `tenant-data:{hostId}` on the
|
|
22
|
+
* tenant alongside dropping every dependent page — so publishes stay
|
|
23
|
+
* instant and 60s only bounds unannounced writes, exactly like the page's
|
|
24
|
+
* own ISR window did.
|
|
25
|
+
*/ const LAYOUT_VERSION_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS;
|
|
26
|
+
/**
|
|
27
|
+
* Fetches a layout's published version (the `versionId` pointer on the
|
|
28
|
+
* layout doc). Errors and missing docs resolve to `version: undefined` so a
|
|
29
|
+
* broken layout never 404s a published screen — composition falls back to
|
|
30
|
+
* the bare screen.
|
|
31
|
+
*
|
|
32
|
+
* The layout chain runs on EVERY page of a site that carries chrome, which
|
|
33
|
+
* made this pair of reads one of the most amplified in the render
|
|
34
|
+
* (AGL-1302). The doc+version pair is cached whole; a layout carrying a
|
|
35
|
+
* PENDING publish schedule is served but never stored, because
|
|
36
|
+
* `applyDuePublishSchedule` needs the live `Timestamp` (`publishAt.seconds`
|
|
37
|
+
* does not survive the cache's JSON round trip) and the flip it performs is
|
|
38
|
+
* a write this cache must not suppress.
|
|
39
|
+
*/ async function readPublishedLayoutVersion(hostId, layoutId) {
|
|
40
|
+
const data = {
|
|
41
|
+
layout: undefined,
|
|
42
|
+
version: undefined,
|
|
43
|
+
error: null
|
|
44
|
+
};
|
|
45
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
46
|
+
const layoutRef = firestore.collection('hosts').doc(hostId).collection('layouts').doc(layoutId);
|
|
47
|
+
try {
|
|
48
|
+
const layoutSnapshot = await layoutRef.withConverter(layoutConverter).get();
|
|
49
|
+
if (!layoutSnapshot.exists) return data;
|
|
50
|
+
data.layout = layoutSnapshot.data();
|
|
51
|
+
// Scheduled publishing applies to layouts too (AGL-61).
|
|
52
|
+
const versionId = await applyDuePublishSchedule({
|
|
53
|
+
hostId,
|
|
54
|
+
collectionName: 'layouts',
|
|
55
|
+
docId: layoutId,
|
|
56
|
+
parent: data.layout
|
|
57
|
+
});
|
|
58
|
+
if (!versionId) return data;
|
|
59
|
+
const versionSnapshot = await layoutRef.collection('versions').withConverter(layoutVersionConverter).doc(versionId).get();
|
|
60
|
+
if (!versionSnapshot.exists) return data;
|
|
61
|
+
data.version = versionSnapshot.data();
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(error);
|
|
64
|
+
data.error = error;
|
|
65
|
+
}
|
|
66
|
+
return data;
|
|
67
|
+
}
|
|
68
|
+
export async function getPublishedLayoutVersion(options) {
|
|
69
|
+
const hostId = options == null ? void 0 : options.hostId;
|
|
70
|
+
const layoutId = options == null ? void 0 : options.layoutId;
|
|
71
|
+
try {
|
|
72
|
+
return await withRenderCache({
|
|
73
|
+
key: [
|
|
74
|
+
'tenant-layout-version',
|
|
75
|
+
hostId,
|
|
76
|
+
layoutId
|
|
77
|
+
],
|
|
78
|
+
revalidate: LAYOUT_VERSION_TTL_SECONDS,
|
|
79
|
+
tags: [
|
|
80
|
+
tenantDataTag(hostId)
|
|
81
|
+
],
|
|
82
|
+
read: ()=>readPublishedLayoutVersion(hostId, layoutId),
|
|
83
|
+
// Errors, missing layouts and pending schedules are served fresh each
|
|
84
|
+
// time — only a clean published read is worth replaying.
|
|
85
|
+
store: (value)=>{
|
|
86
|
+
var _value_layout_publishSchedule;
|
|
87
|
+
return !value.error && value.layout !== undefined && ((_value_layout_publishSchedule = value.layout.publishSchedule) == null ? void 0 : _value_layout_publishSchedule.status) !== 'pending';
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
// The cache wrapper itself failed; degrade to the uncached read so a
|
|
92
|
+
// caching problem can never 404 a published screen.
|
|
93
|
+
console.error(error);
|
|
94
|
+
return readPublishedLayoutVersion(hostId, layoutId);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export default getPublishedLayoutVersion;
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=get-layout-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/get-layout-version.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 {\n firebaseAdmin,\n layoutConverter,\n layoutVersionConverter,\n} from '@aglyn/tenant-data-admin'\nimport {\n PUBLISHED_SITE_DATA_TTL_SECONDS,\n tenantDataTag,\n withRenderCache,\n} from '@aglyn/tenant-data-admin/render-cache'\nimport applyDuePublishSchedule from './apply-publish-schedule'\n\n/**\n * Backstop TTL only (AGL-1302): a layout publish goes through the console's\n * `/api/screens/revalidate`, which busts `tenant-data:{hostId}` on the\n * tenant alongside dropping every dependent page — so publishes stay\n * instant and 60s only bounds unannounced writes, exactly like the page's\n * own ISR window did.\n */\nconst LAYOUT_VERSION_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS\n\n/**\n * Fetches a layout's published version (the `versionId` pointer on the\n * layout doc). Errors and missing docs resolve to `version: undefined` so a\n * broken layout never 404s a published screen — composition falls back to\n * the bare screen.\n *\n * The layout chain runs on EVERY page of a site that carries chrome, which\n * made this pair of reads one of the most amplified in the render\n * (AGL-1302). The doc+version pair is cached whole; a layout carrying a\n * PENDING publish schedule is served but never stored, because\n * `applyDuePublishSchedule` needs the live `Timestamp` (`publishAt.seconds`\n * does not survive the cache's JSON round trip) and the flip it performs is\n * a write this cache must not suppress.\n */\nasync function readPublishedLayoutVersion(hostId: string, layoutId: string) {\n const data = {\n layout: undefined as Aglyn.AglynLayout | undefined,\n version: undefined as Aglyn.AglynLayoutVersion | undefined,\n error: null as unknown,\n }\n const firestore = firebaseAdmin.app().firestore()\n const layoutRef = firestore\n .collection('hosts')\n .doc(hostId)\n .collection('layouts')\n .doc(layoutId)\n\n try {\n const layoutSnapshot = await layoutRef\n .withConverter(layoutConverter)\n .get()\n if (!layoutSnapshot.exists) return data\n data.layout = layoutSnapshot.data() as Aglyn.AglynLayout\n\n // Scheduled publishing applies to layouts too (AGL-61).\n const versionId = await applyDuePublishSchedule({\n hostId,\n collectionName: 'layouts',\n docId: layoutId,\n parent: data.layout,\n })\n if (!versionId) return data\n\n const versionSnapshot = await layoutRef\n .collection('versions')\n .withConverter(layoutVersionConverter)\n .doc(versionId)\n .get()\n if (!versionSnapshot.exists) return data\n data.version = versionSnapshot.data() as Aglyn.AglynLayoutVersion\n } catch (error) {\n console.error(error)\n data.error = error\n }\n\n return data\n}\n\nexport async function getPublishedLayoutVersion(options: {\n hostId: Aglyn.HostUid\n layoutId: Aglyn.LayoutUid\n}) {\n const hostId = options?.hostId as string\n const layoutId = options?.layoutId as string\n try {\n return await withRenderCache({\n key: ['tenant-layout-version', hostId, layoutId],\n revalidate: LAYOUT_VERSION_TTL_SECONDS,\n tags: [tenantDataTag(hostId)],\n read: () => readPublishedLayoutVersion(hostId, layoutId),\n // Errors, missing layouts and pending schedules are served fresh each\n // time — only a clean published read is worth replaying.\n store: (value) =>\n !value.error &&\n value.layout !== undefined &&\n (value.layout as { publishSchedule?: { status?: string } })\n .publishSchedule?.status !== 'pending',\n })\n } catch (error) {\n // The cache wrapper itself failed; degrade to the uncached read so a\n // caching problem can never 404 a published screen.\n console.error(error)\n return readPublishedLayoutVersion(hostId, layoutId)\n }\n}\n\nexport default getPublishedLayoutVersion\n"],"names":["firebaseAdmin","layoutConverter","layoutVersionConverter","PUBLISHED_SITE_DATA_TTL_SECONDS","tenantDataTag","withRenderCache","applyDuePublishSchedule","LAYOUT_VERSION_TTL_SECONDS","readPublishedLayoutVersion","hostId","layoutId","data","layout","undefined","version","error","firestore","app","layoutRef","collection","doc","layoutSnapshot","withConverter","get","exists","versionId","collectionName","docId","parent","versionSnapshot","console","getPublishedLayoutVersion","options","key","revalidate","tags","read","store","value","publishSchedule","status"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SACEA,aAAa,EACbC,eAAe,EACfC,sBAAsB,QACjB,2BAA0B;AACjC,SACEC,+BAA+B,EAC/BC,aAAa,EACbC,eAAe,QACV,wCAAuC;AAC9C,OAAOC,6BAA6B,8BAA0B;AAE9D;;;;;;CAMC,GACD,MAAMC,6BAA6BJ;AAEnC;;;;;;;;;;;;;CAaC,GACD,eAAeK,2BAA2BC,MAAc,EAAEC,QAAgB;IACxE,MAAMC,OAAO;QACXC,QAAQC;QACRC,SAASD;QACTE,OAAO;IACT;IACA,MAAMC,YAAYhB,cAAciB,GAAG,GAAGD,SAAS;IAC/C,MAAME,YAAYF,UACfG,UAAU,CAAC,SACXC,GAAG,CAACX,QACJU,UAAU,CAAC,WACXC,GAAG,CAACV;IAEP,IAAI;QACF,MAAMW,iBAAiB,MAAMH,UAC1BI,aAAa,CAACrB,iBACdsB,GAAG;QACN,IAAI,CAACF,eAAeG,MAAM,EAAE,OAAOb;QACnCA,KAAKC,MAAM,GAAGS,eAAeV,IAAI;QAEjC,wDAAwD;QACxD,MAAMc,YAAY,MAAMnB,wBAAwB;YAC9CG;YACAiB,gBAAgB;YAChBC,OAAOjB;YACPkB,QAAQjB,KAAKC,MAAM;QACrB;QACA,IAAI,CAACa,WAAW,OAAOd;QAEvB,MAAMkB,kBAAkB,MAAMX,UAC3BC,UAAU,CAAC,YACXG,aAAa,CAACpB,wBACdkB,GAAG,CAACK,WACJF,GAAG;QACN,IAAI,CAACM,gBAAgBL,MAAM,EAAE,OAAOb;QACpCA,KAAKG,OAAO,GAAGe,gBAAgBlB,IAAI;IACrC,EAAE,OAAOI,OAAO;QACde,QAAQf,KAAK,CAACA;QACdJ,KAAKI,KAAK,GAAGA;IACf;IAEA,OAAOJ;AACT;AAEA,OAAO,eAAeoB,0BAA0BC,OAG/C;IACC,MAAMvB,SAASuB,2BAAAA,QAASvB,MAAM;IAC9B,MAAMC,WAAWsB,2BAAAA,QAAStB,QAAQ;IAClC,IAAI;QACF,OAAO,MAAML,gBAAgB;YAC3B4B,KAAK;gBAAC;gBAAyBxB;gBAAQC;aAAS;YAChDwB,YAAY3B;YACZ4B,MAAM;gBAAC/B,cAAcK;aAAQ;YAC7B2B,MAAM,IAAM5B,2BAA2BC,QAAQC;YAC/C,sEAAsE;YACtE,yDAAyD;YACzD2B,OAAO,CAACC;oBAGN;uBAFA,CAACA,MAAMvB,KAAK,IACZuB,MAAM1B,MAAM,KAAKC,aACjB,EAAA,gCAAA,AAACyB,MAAM1B,MAAM,CACV2B,eAAe,qBADlB,8BACoBC,MAAM,MAAK;;QACnC;IACF,EAAE,OAAOzB,OAAO;QACd,qEAAqE;QACrE,oDAAoD;QACpDe,QAAQf,KAAK,CAACA;QACd,OAAOP,2BAA2BC,QAAQC;IAC5C;AACF;AAEA,eAAeqB,0BAAyB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 { MediaRef } from '@aglyn/aglyn/server';
|
|
18
|
+
import { type MediaAssetFacts } from '@aglyn/aglyn/app-utils/media-asset-facts';
|
|
19
|
+
/**
|
|
20
|
+
* How many documents ONE composition reads facts for.
|
|
21
|
+
*
|
|
22
|
+
* A backstop sized well above what pages place, not a budget they are
|
|
23
|
+
* expected to approach. A page placing more keeps the pick-time copy on the
|
|
24
|
+
* rest, which is how every one of them rendered before these facts were read.
|
|
25
|
+
* Which ones are read is decided by the order the composition hands them
|
|
26
|
+
* over: the social card's references first (AGL-2850), then the placements in
|
|
27
|
+
* the order `mediaAssetRefs` lists them, films, then images, each from the top
|
|
28
|
+
* of the page down.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MEDIA_ASSET_FACTS_PER_RENDER = 100;
|
|
31
|
+
/**
|
|
32
|
+
* The current facts of each placed image and film this page may be shown, and
|
|
33
|
+
* of each asset its social card names, keyed by `mediaAssetFactsKey`
|
|
34
|
+
* (AGL-2807, AGL-2833, AGL-2850). `media-asset-facts.ts` in `@aglyn/aglyn`
|
|
35
|
+
* owns what is done with a placement's, and `social-image-facts.ts` beside
|
|
36
|
+
* this reader owns a card's.
|
|
37
|
+
*
|
|
38
|
+
* ## One read for the whole page
|
|
39
|
+
*
|
|
40
|
+
* Every document goes out in ONE `getAll`, projected to
|
|
41
|
+
* `MEDIA_ASSET_FACT_FIELDS`, however many elements and scope spellings place
|
|
42
|
+
* it, and a document is read once however many nodes name it. Firestore bills
|
|
43
|
+
* per document, so the cost of a composition is the number of distinct assets
|
|
44
|
+
* it places and its social card names, counted together and once each, up to
|
|
45
|
+
* {@link MEDIA_ASSET_FACTS_PER_RENDER}. A card names at most three of them (an
|
|
46
|
+
* entry's cover, a screen's image, the site default), and none the page does
|
|
47
|
+
* not already place when the card is one of its own pictures.
|
|
48
|
+
*
|
|
49
|
+
* ## Why this read is not behind the render cache
|
|
50
|
+
*
|
|
51
|
+
* Every other compose read is busted by the publish announce
|
|
52
|
+
* (`tenant-data:{hostId}`) and falls back to an hour's TTL for a write that
|
|
53
|
+
* never announces itself. A replace is exactly such a write: it goes through
|
|
54
|
+
* the console's `/api/media/replace` and tells the tenant nothing. Cached
|
|
55
|
+
* here, a replaced asset's facts could outlive the page's own ISR window by a
|
|
56
|
+
* second hour.
|
|
57
|
+
*
|
|
58
|
+
* ## What it answers for, and what it leaves to the node
|
|
59
|
+
*
|
|
60
|
+
* `mediaAssetFactsFromDocument` decides, per placement, from the projected
|
|
61
|
+
* document: an entry exists only for an asset that is live, not private, and
|
|
62
|
+
* visible to this site under the host-qualified scope the page renders, the
|
|
63
|
+
* verdict the CDN reaches when the browser asks for the bytes. The console's
|
|
64
|
+
* canvas and Preview decide through the same function, so the editor and the
|
|
65
|
+
* page agree about which assets answer. Anything else gets no entry and keeps
|
|
66
|
+
* its stored props, and so does every asset when the read fails. That is
|
|
67
|
+
* fail-open like every other compose read, because a Firestore fault must not
|
|
68
|
+
* strip the images and players on a live page of their shape.
|
|
69
|
+
*/
|
|
70
|
+
export declare function getMediaAssetFacts(options: {
|
|
71
|
+
hostId: string;
|
|
72
|
+
refs: readonly MediaRef[];
|
|
73
|
+
}): Promise<Map<string, MediaAssetFacts>>;
|
|
74
|
+
export default getMediaAssetFacts;
|
|
@@ -0,0 +1,116 @@
|
|
|
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
|
+
*/ // By path rather than through a barrel: every `@aglyn/aglyn` barrel
|
|
17
|
+
// re-exports `app-utils/server` into published pages, and the overlay stays
|
|
18
|
+
// out of them.
|
|
19
|
+
import { MEDIA_ASSET_FACT_FIELDS, mediaAssetDocumentPath, mediaAssetFactsFromDocument, mediaAssetFactsKey } from "@aglyn/aglyn/app-utils/media-asset-facts";
|
|
20
|
+
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
21
|
+
/**
|
|
22
|
+
* How many documents ONE composition reads facts for.
|
|
23
|
+
*
|
|
24
|
+
* A backstop sized well above what pages place, not a budget they are
|
|
25
|
+
* expected to approach. A page placing more keeps the pick-time copy on the
|
|
26
|
+
* rest, which is how every one of them rendered before these facts were read.
|
|
27
|
+
* Which ones are read is decided by the order the composition hands them
|
|
28
|
+
* over: the social card's references first (AGL-2850), then the placements in
|
|
29
|
+
* the order `mediaAssetRefs` lists them, films, then images, each from the top
|
|
30
|
+
* of the page down.
|
|
31
|
+
*/ export const MEDIA_ASSET_FACTS_PER_RENDER = 100;
|
|
32
|
+
/**
|
|
33
|
+
* The current facts of each placed image and film this page may be shown, and
|
|
34
|
+
* of each asset its social card names, keyed by `mediaAssetFactsKey`
|
|
35
|
+
* (AGL-2807, AGL-2833, AGL-2850). `media-asset-facts.ts` in `@aglyn/aglyn`
|
|
36
|
+
* owns what is done with a placement's, and `social-image-facts.ts` beside
|
|
37
|
+
* this reader owns a card's.
|
|
38
|
+
*
|
|
39
|
+
* ## One read for the whole page
|
|
40
|
+
*
|
|
41
|
+
* Every document goes out in ONE `getAll`, projected to
|
|
42
|
+
* `MEDIA_ASSET_FACT_FIELDS`, however many elements and scope spellings place
|
|
43
|
+
* it, and a document is read once however many nodes name it. Firestore bills
|
|
44
|
+
* per document, so the cost of a composition is the number of distinct assets
|
|
45
|
+
* it places and its social card names, counted together and once each, up to
|
|
46
|
+
* {@link MEDIA_ASSET_FACTS_PER_RENDER}. A card names at most three of them (an
|
|
47
|
+
* entry's cover, a screen's image, the site default), and none the page does
|
|
48
|
+
* not already place when the card is one of its own pictures.
|
|
49
|
+
*
|
|
50
|
+
* ## Why this read is not behind the render cache
|
|
51
|
+
*
|
|
52
|
+
* Every other compose read is busted by the publish announce
|
|
53
|
+
* (`tenant-data:{hostId}`) and falls back to an hour's TTL for a write that
|
|
54
|
+
* never announces itself. A replace is exactly such a write: it goes through
|
|
55
|
+
* the console's `/api/media/replace` and tells the tenant nothing. Cached
|
|
56
|
+
* here, a replaced asset's facts could outlive the page's own ISR window by a
|
|
57
|
+
* second hour.
|
|
58
|
+
*
|
|
59
|
+
* ## What it answers for, and what it leaves to the node
|
|
60
|
+
*
|
|
61
|
+
* `mediaAssetFactsFromDocument` decides, per placement, from the projected
|
|
62
|
+
* document: an entry exists only for an asset that is live, not private, and
|
|
63
|
+
* visible to this site under the host-qualified scope the page renders, the
|
|
64
|
+
* verdict the CDN reaches when the browser asks for the bytes. The console's
|
|
65
|
+
* canvas and Preview decide through the same function, so the editor and the
|
|
66
|
+
* page agree about which assets answer. Anything else gets no entry and keeps
|
|
67
|
+
* its stored props, and so does every asset when the read fails. That is
|
|
68
|
+
* fail-open like every other compose read, because a Firestore fault must not
|
|
69
|
+
* strip the images and players on a live page of their shape.
|
|
70
|
+
*/ export async function getMediaAssetFacts(options) {
|
|
71
|
+
const { hostId, refs } = options;
|
|
72
|
+
const facts = new Map();
|
|
73
|
+
/** One read per DOCUMENT, however many scope spellings place it. */ const documents = new Map();
|
|
74
|
+
for (const ref of refs){
|
|
75
|
+
const path = mediaAssetDocumentPath(ref);
|
|
76
|
+
if (!path) continue;
|
|
77
|
+
const placements = documents.get(path);
|
|
78
|
+
if (placements) placements.push(ref);
|
|
79
|
+
else if (documents.size < MEDIA_ASSET_FACTS_PER_RENDER) {
|
|
80
|
+
documents.set(path, [
|
|
81
|
+
ref
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!documents.size) return facts;
|
|
86
|
+
try {
|
|
87
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
88
|
+
const paths = [
|
|
89
|
+
...documents.keys()
|
|
90
|
+
];
|
|
91
|
+
const snapshots = await firestore.getAll(...paths.map((path)=>firestore.doc(path)), {
|
|
92
|
+
fieldMask: [
|
|
93
|
+
...MEDIA_ASSET_FACT_FIELDS
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
snapshots.forEach((snapshot, index)=>{
|
|
97
|
+
var _documents_get;
|
|
98
|
+
if (!snapshot.exists) return;
|
|
99
|
+
const document = {};
|
|
100
|
+
for (const field of MEDIA_ASSET_FACT_FIELDS){
|
|
101
|
+
document[field] = snapshot.get(field);
|
|
102
|
+
}
|
|
103
|
+
for (const ref of (_documents_get = documents.get(paths[index])) != null ? _documents_get : []){
|
|
104
|
+
const found = mediaAssetFactsFromDocument(document, ref, hostId);
|
|
105
|
+
if (found) facts.set(mediaAssetFactsKey(ref), found);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return facts;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('[media-asset-facts] read failed; placements keep their stored props', error);
|
|
111
|
+
return new Map();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export default getMediaAssetFacts;
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=get-media-asset-facts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/get-media-asset-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\nimport type { MediaRef } from '@aglyn/aglyn/server'\n// By path rather than through a barrel: every `@aglyn/aglyn` barrel\n// re-exports `app-utils/server` into published pages, and the overlay stays\n// out of them.\nimport {\n MEDIA_ASSET_FACT_FIELDS,\n type MediaAssetDocument,\n type MediaAssetFacts,\n mediaAssetDocumentPath,\n mediaAssetFactsFromDocument,\n mediaAssetFactsKey,\n} from '@aglyn/aglyn/app-utils/media-asset-facts'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\n\n/**\n * How many documents ONE composition reads facts for.\n *\n * A backstop sized well above what pages place, not a budget they are\n * expected to approach. A page placing more keeps the pick-time copy on the\n * rest, which is how every one of them rendered before these facts were read.\n * Which ones are read is decided by the order the composition hands them\n * over: the social card's references first (AGL-2850), then the placements in\n * the order `mediaAssetRefs` lists them, films, then images, each from the top\n * of the page down.\n */\nexport const MEDIA_ASSET_FACTS_PER_RENDER = 100\n\n/**\n * The current facts of each placed image and film this page may be shown, and\n * of each asset its social card names, keyed by `mediaAssetFactsKey`\n * (AGL-2807, AGL-2833, AGL-2850). `media-asset-facts.ts` in `@aglyn/aglyn`\n * owns what is done with a placement's, and `social-image-facts.ts` beside\n * this reader owns a card's.\n *\n * ## One read for the whole page\n *\n * Every document goes out in ONE `getAll`, projected to\n * `MEDIA_ASSET_FACT_FIELDS`, however many elements and scope spellings place\n * it, and a document is read once however many nodes name it. Firestore bills\n * per document, so the cost of a composition is the number of distinct assets\n * it places and its social card names, counted together and once each, up to\n * {@link MEDIA_ASSET_FACTS_PER_RENDER}. A card names at most three of them (an\n * entry's cover, a screen's image, the site default), and none the page does\n * not already place when the card is one of its own pictures.\n *\n * ## Why this read is not behind the render cache\n *\n * Every other compose read is busted by the publish announce\n * (`tenant-data:{hostId}`) and falls back to an hour's TTL for a write that\n * never announces itself. A replace is exactly such a write: it goes through\n * the console's `/api/media/replace` and tells the tenant nothing. Cached\n * here, a replaced asset's facts could outlive the page's own ISR window by a\n * second hour.\n *\n * ## What it answers for, and what it leaves to the node\n *\n * `mediaAssetFactsFromDocument` decides, per placement, from the projected\n * document: an entry exists only for an asset that is live, not private, and\n * visible to this site under the host-qualified scope the page renders, the\n * verdict the CDN reaches when the browser asks for the bytes. The console's\n * canvas and Preview decide through the same function, so the editor and the\n * page agree about which assets answer. Anything else gets no entry and keeps\n * its stored props, and so does every asset when the read fails. That is\n * fail-open like every other compose read, because a Firestore fault must not\n * strip the images and players on a live page of their shape.\n */\nexport async function getMediaAssetFacts(options: {\n hostId: string\n refs: readonly MediaRef[]\n}): Promise<Map<string, MediaAssetFacts>> {\n const { hostId, refs } = options\n const facts = new Map<string, MediaAssetFacts>()\n /** One read per DOCUMENT, however many scope spellings place it. */\n const documents = new Map<string, MediaRef[]>()\n for (const ref of refs) {\n const path = mediaAssetDocumentPath(ref)\n if (!path) continue\n const placements = documents.get(path)\n if (placements) placements.push(ref)\n else if (documents.size < MEDIA_ASSET_FACTS_PER_RENDER) {\n documents.set(path, [ref])\n }\n }\n if (!documents.size) return facts\n try {\n const firestore = firebaseAdmin.app().firestore()\n const paths = [...documents.keys()]\n const snapshots = await firestore.getAll(\n ...paths.map((path) => firestore.doc(path)),\n { fieldMask: [...MEDIA_ASSET_FACT_FIELDS] },\n )\n snapshots.forEach((snapshot, index) => {\n if (!snapshot.exists) return\n const document: MediaAssetDocument = {}\n for (const field of MEDIA_ASSET_FACT_FIELDS) {\n document[field] = snapshot.get(field)\n }\n for (const ref of documents.get(paths[index]) ?? []) {\n const found = mediaAssetFactsFromDocument(document, ref, hostId)\n if (found) facts.set(mediaAssetFactsKey(ref), found)\n }\n })\n return facts\n } catch (error) {\n console.error(\n '[media-asset-facts] read failed; placements keep their stored props',\n error,\n )\n return new Map()\n }\n}\n\nexport default getMediaAssetFacts\n"],"names":["MEDIA_ASSET_FACT_FIELDS","mediaAssetDocumentPath","mediaAssetFactsFromDocument","mediaAssetFactsKey","firebaseAdmin","MEDIA_ASSET_FACTS_PER_RENDER","getMediaAssetFacts","options","hostId","refs","facts","Map","documents","ref","path","placements","get","push","size","set","firestore","app","paths","keys","snapshots","getAll","map","doc","fieldMask","forEach","snapshot","index","exists","document","field","found","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,oEAAoE;AACpE,4EAA4E;AAC5E,eAAe;AACf,SACEA,uBAAuB,EAGvBC,sBAAsB,EACtBC,2BAA2B,EAC3BC,kBAAkB,QACb,2CAA0C;AACjD,SAASC,aAAa,QAAQ,2BAA0B;AAExD;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,+BAA+B,IAAG;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCC,GACD,OAAO,eAAeC,mBAAmBC,OAGxC;IACC,MAAM,EAAEC,MAAM,EAAEC,IAAI,EAAE,GAAGF;IACzB,MAAMG,QAAQ,IAAIC;IAClB,kEAAkE,GAClE,MAAMC,YAAY,IAAID;IACtB,KAAK,MAAME,OAAOJ,KAAM;QACtB,MAAMK,OAAOb,uBAAuBY;QACpC,IAAI,CAACC,MAAM;QACX,MAAMC,aAAaH,UAAUI,GAAG,CAACF;QACjC,IAAIC,YAAYA,WAAWE,IAAI,CAACJ;aAC3B,IAAID,UAAUM,IAAI,GAAGb,8BAA8B;YACtDO,UAAUO,GAAG,CAACL,MAAM;gBAACD;aAAI;QAC3B;IACF;IACA,IAAI,CAACD,UAAUM,IAAI,EAAE,OAAOR;IAC5B,IAAI;QACF,MAAMU,YAAYhB,cAAciB,GAAG,GAAGD,SAAS;QAC/C,MAAME,QAAQ;eAAIV,UAAUW,IAAI;SAAG;QACnC,MAAMC,YAAY,MAAMJ,UAAUK,MAAM,IACnCH,MAAMI,GAAG,CAAC,CAACZ,OAASM,UAAUO,GAAG,CAACb,QACrC;YAAEc,WAAW;mBAAI5B;aAAwB;QAAC;QAE5CwB,UAAUK,OAAO,CAAC,CAACC,UAAUC;gBAMTnB;YALlB,IAAI,CAACkB,SAASE,MAAM,EAAE;YACtB,MAAMC,WAA+B,CAAC;YACtC,KAAK,MAAMC,SAASlC,wBAAyB;gBAC3CiC,QAAQ,CAACC,MAAM,GAAGJ,SAASd,GAAG,CAACkB;YACjC;YACA,KAAK,MAAMrB,QAAOD,iBAAAA,UAAUI,GAAG,CAACM,KAAK,CAACS,MAAM,aAA1BnB,iBAA+B,EAAE,CAAE;gBACnD,MAAMuB,QAAQjC,4BAA4B+B,UAAUpB,KAAKL;gBACzD,IAAI2B,OAAOzB,MAAMS,GAAG,CAAChB,mBAAmBU,MAAMsB;YAChD;QACF;QACA,OAAOzB;IACT,EAAE,OAAO0B,OAAO;QACdC,QAAQD,KAAK,CACX,uEACAA;QAEF,OAAO,IAAIzB;IACb;AACF;AAEA,eAAeL,mBAAkB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
/**
|
|
19
|
+
* Resolves a host's pinned plugin installs (AGL-45) into the compose-time
|
|
20
|
+
* shape `attachPluginInstalls` stamps onto `marketplacePlugin` nodes, keyed
|
|
21
|
+
* by listing id. The kill switch is folded in per install: a revoked pin
|
|
22
|
+
* renders the disabled placeholder. Fail-open: on error an empty map is
|
|
23
|
+
* returned and plugin nodes show a safe "not installed" placeholder rather
|
|
24
|
+
* than taking the published screen down.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getPluginInstalls(options: {
|
|
27
|
+
hostId: string;
|
|
28
|
+
}): Promise<Record<string, Aglyn.ResolvedPluginInstall>>;
|
|
29
|
+
export default getPluginInstalls;
|
|
@@ -0,0 +1,94 @@
|
|
|
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 { resolveOrgIdForHost, firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
18
|
+
import { PUBLISHED_SITE_DATA_TTL_SECONDS, tenantDataTag, withRenderCache } from "@aglyn/tenant-data-admin/render-cache";
|
|
19
|
+
/**
|
|
20
|
+
* SECURITY-RELEVANT TTL (AGL-1302): the kill switch (`revocations/{id}`) is
|
|
21
|
+
* folded into each resolved install, so this cache bounds how long a revoked
|
|
22
|
+
* plugin keeps rendering — 60s, on par with the page ISR window that already
|
|
23
|
+
* bounded it. Do not raise it.
|
|
24
|
+
*/ const PLUGIN_INSTALLS_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS;
|
|
25
|
+
/**
|
|
26
|
+
* Resolves a host's pinned plugin installs (AGL-45) into the compose-time
|
|
27
|
+
* shape `attachPluginInstalls` stamps onto `marketplacePlugin` nodes, keyed
|
|
28
|
+
* by listing id. The kill switch is folded in per install: a revoked pin
|
|
29
|
+
* renders the disabled placeholder. Fail-open: on error an empty map is
|
|
30
|
+
* returned and plugin nodes show a safe "not installed" placeholder rather
|
|
31
|
+
* than taking the published screen down.
|
|
32
|
+
*/ export async function getPluginInstalls(options) {
|
|
33
|
+
try {
|
|
34
|
+
return await withRenderCache({
|
|
35
|
+
key: [
|
|
36
|
+
'tenant-plugin-installs',
|
|
37
|
+
options.hostId
|
|
38
|
+
],
|
|
39
|
+
revalidate: PLUGIN_INSTALLS_TTL_SECONDS,
|
|
40
|
+
tags: [
|
|
41
|
+
tenantDataTag(options.hostId)
|
|
42
|
+
],
|
|
43
|
+
read: ()=>readPluginInstalls(options)
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(error);
|
|
47
|
+
return readPluginInstalls(options);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function readPluginInstalls(options) {
|
|
51
|
+
const installs = {};
|
|
52
|
+
try {
|
|
53
|
+
var _ref;
|
|
54
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
55
|
+
// Org-tier installs apply to every host in the org (AGL-237); a
|
|
56
|
+
// host-level pin of the same listing wins (host docs merge second).
|
|
57
|
+
const orgId = await resolveOrgIdForHost(options.hostId);
|
|
58
|
+
const [orgSnapshot, snapshot] = await Promise.all([
|
|
59
|
+
orgId ? firestore.collection('orgs').doc(orgId).collection('installs').limit(50).get() : Promise.resolve(null),
|
|
60
|
+
firestore.collection('hosts').doc(options.hostId).collection('installs').limit(50).get()
|
|
61
|
+
]);
|
|
62
|
+
const merged = new Map();
|
|
63
|
+
for (const docSnapshot of (_ref = orgSnapshot == null ? void 0 : orgSnapshot.docs) != null ? _ref : []){
|
|
64
|
+
merged.set(docSnapshot.id, docSnapshot);
|
|
65
|
+
}
|
|
66
|
+
for (const docSnapshot of snapshot.docs){
|
|
67
|
+
merged.set(docSnapshot.id, docSnapshot);
|
|
68
|
+
}
|
|
69
|
+
if (merged.size === 0) return installs;
|
|
70
|
+
await Promise.all([
|
|
71
|
+
...merged.values()
|
|
72
|
+
].map(async (docSnapshot)=>{
|
|
73
|
+
var _docSnapshot_get, _docSnapshot_get1;
|
|
74
|
+
var _docSnapshot_get2;
|
|
75
|
+
const version = String((_docSnapshot_get = docSnapshot.get('version')) != null ? _docSnapshot_get : '');
|
|
76
|
+
const sha256 = String((_docSnapshot_get1 = docSnapshot.get('sha256')) != null ? _docSnapshot_get1 : '');
|
|
77
|
+
if (!version || !sha256) return;
|
|
78
|
+
const revocation = (await firestore.collection('revocations').doc(docSnapshot.id).get()).data();
|
|
79
|
+
installs[docSnapshot.id] = {
|
|
80
|
+
listingId: docSnapshot.id,
|
|
81
|
+
version,
|
|
82
|
+
sha256,
|
|
83
|
+
capabilities: (_docSnapshot_get2 = docSnapshot.get('manifest')) == null ? void 0 : _docSnapshot_get2.capabilities,
|
|
84
|
+
revoked: Aglyn.isPluginRevoked(revocation, version)
|
|
85
|
+
};
|
|
86
|
+
}));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(error);
|
|
89
|
+
}
|
|
90
|
+
return installs;
|
|
91
|
+
}
|
|
92
|
+
export default getPluginInstalls;
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=get-plugin-installs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/get-plugin-installs.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 {\n resolveOrgIdForHost, firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n PUBLISHED_SITE_DATA_TTL_SECONDS,\n tenantDataTag,\n withRenderCache,\n} from '@aglyn/tenant-data-admin/render-cache'\n\n/**\n * SECURITY-RELEVANT TTL (AGL-1302): the kill switch (`revocations/{id}`) is\n * folded into each resolved install, so this cache bounds how long a revoked\n * plugin keeps rendering — 60s, on par with the page ISR window that already\n * bounded it. Do not raise it.\n */\nconst PLUGIN_INSTALLS_TTL_SECONDS = PUBLISHED_SITE_DATA_TTL_SECONDS\n\n/**\n * Resolves a host's pinned plugin installs (AGL-45) into the compose-time\n * shape `attachPluginInstalls` stamps onto `marketplacePlugin` nodes, keyed\n * by listing id. The kill switch is folded in per install: a revoked pin\n * renders the disabled placeholder. Fail-open: on error an empty map is\n * returned and plugin nodes show a safe \"not installed\" placeholder rather\n * than taking the published screen down.\n */\nexport async function getPluginInstalls(options: {\n hostId: string\n}): Promise<Record<string, Aglyn.ResolvedPluginInstall>> {\n try {\n return await withRenderCache({\n key: ['tenant-plugin-installs', options.hostId],\n revalidate: PLUGIN_INSTALLS_TTL_SECONDS,\n tags: [tenantDataTag(options.hostId)],\n read: () => readPluginInstalls(options),\n })\n } catch (error) {\n console.error(error)\n return readPluginInstalls(options)\n }\n}\n\nasync function readPluginInstalls(options: {\n hostId: string\n}): Promise<Record<string, Aglyn.ResolvedPluginInstall>> {\n const installs: Record<string, Aglyn.ResolvedPluginInstall> = {}\n try {\n const firestore = firebaseAdmin.app().firestore()\n // Org-tier installs apply to every host in the org (AGL-237); a\n // host-level pin of the same listing wins (host docs merge second).\n const orgId = await resolveOrgIdForHost(options.hostId)\n const [orgSnapshot, snapshot] = await Promise.all([\n orgId\n ? firestore\n .collection('orgs')\n .doc(orgId)\n .collection('installs')\n .limit(50)\n .get()\n : Promise.resolve(null),\n firestore\n .collection('hosts')\n .doc(options.hostId)\n .collection('installs')\n .limit(50)\n .get(),\n ])\n const merged = new Map()\n for (const docSnapshot of orgSnapshot?.docs ?? []) {\n merged.set(docSnapshot.id, docSnapshot)\n }\n for (const docSnapshot of snapshot.docs) {\n merged.set(docSnapshot.id, docSnapshot)\n }\n if (merged.size === 0) return installs\n\n await Promise.all(\n [...merged.values()].map(async (docSnapshot) => {\n const version = String(docSnapshot.get('version') ?? '')\n const sha256 = String(docSnapshot.get('sha256') ?? '')\n if (!version || !sha256) return\n const revocation = (\n await firestore\n .collection('revocations')\n .doc(docSnapshot.id)\n .get()\n ).data() as Aglyn.PluginRevocation | undefined\n installs[docSnapshot.id] = {\n listingId: docSnapshot.id,\n version,\n sha256,\n capabilities: docSnapshot.get('manifest')?.capabilities,\n revoked: Aglyn.isPluginRevoked(revocation, version),\n }\n }),\n )\n } catch (error) {\n console.error(error)\n }\n return installs\n}\n\nexport default getPluginInstalls\n"],"names":["Aglyn","resolveOrgIdForHost","firebaseAdmin","PUBLISHED_SITE_DATA_TTL_SECONDS","tenantDataTag","withRenderCache","PLUGIN_INSTALLS_TTL_SECONDS","getPluginInstalls","options","key","hostId","revalidate","tags","read","readPluginInstalls","error","console","installs","firestore","app","orgId","orgSnapshot","snapshot","Promise","all","collection","doc","limit","get","resolve","merged","Map","docSnapshot","docs","set","id","size","values","map","version","String","sha256","revocation","data","listingId","capabilities","revoked","isPluginRevoked"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,sBAAqB;AAC5C,SACEC,mBAAmB,EAAEC,aAAa,QAAQ,2BAA0B;AACtE,SACEC,+BAA+B,EAC/BC,aAAa,EACbC,eAAe,QACV,wCAAuC;AAE9C;;;;;CAKC,GACD,MAAMC,8BAA8BH;AAEpC;;;;;;;CAOC,GACD,OAAO,eAAeI,kBAAkBC,OAEvC;IACC,IAAI;QACF,OAAO,MAAMH,gBAAgB;YAC3BI,KAAK;gBAAC;gBAA0BD,QAAQE,MAAM;aAAC;YAC/CC,YAAYL;YACZM,MAAM;gBAACR,cAAcI,QAAQE,MAAM;aAAE;YACrCG,MAAM,IAAMC,mBAAmBN;QACjC;IACF,EAAE,OAAOO,OAAO;QACdC,QAAQD,KAAK,CAACA;QACd,OAAOD,mBAAmBN;IAC5B;AACF;AAEA,eAAeM,mBAAmBN,OAEjC;IACC,MAAMS,WAAwD,CAAC;IAC/D,IAAI;;QACF,MAAMC,YAAYhB,cAAciB,GAAG,GAAGD,SAAS;QAC/C,gEAAgE;QAChE,oEAAoE;QACpE,MAAME,QAAQ,MAAMnB,oBAAoBO,QAAQE,MAAM;QACtD,MAAM,CAACW,aAAaC,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAChDJ,QACIF,UACGO,UAAU,CAAC,QACXC,GAAG,CAACN,OACJK,UAAU,CAAC,YACXE,KAAK,CAAC,IACNC,GAAG,KACNL,QAAQM,OAAO,CAAC;YACpBX,UACGO,UAAU,CAAC,SACXC,GAAG,CAAClB,QAAQE,MAAM,EAClBe,UAAU,CAAC,YACXE,KAAK,CAAC,IACNC,GAAG;SACP;QACD,MAAME,SAAS,IAAIC;QACnB,KAAK,MAAMC,uBAAeX,+BAAAA,YAAaY,IAAI,mBAAI,EAAE,CAAE;YACjDH,OAAOI,GAAG,CAACF,YAAYG,EAAE,EAAEH;QAC7B;QACA,KAAK,MAAMA,eAAeV,SAASW,IAAI,CAAE;YACvCH,OAAOI,GAAG,CAACF,YAAYG,EAAE,EAAEH;QAC7B;QACA,IAAIF,OAAOM,IAAI,KAAK,GAAG,OAAOnB;QAE9B,MAAMM,QAAQC,GAAG,CACf;eAAIM,OAAOO,MAAM;SAAG,CAACC,GAAG,CAAC,OAAON;gBACPA,kBACDA;gBAYNA;YAbhB,MAAMO,UAAUC,QAAOR,mBAAAA,YAAYJ,GAAG,CAAC,sBAAhBI,mBAA8B;YACrD,MAAMS,SAASD,QAAOR,oBAAAA,YAAYJ,GAAG,CAAC,qBAAhBI,oBAA6B;YACnD,IAAI,CAACO,WAAW,CAACE,QAAQ;YACzB,MAAMC,aAAa,AACjB,CAAA,MAAMxB,UACHO,UAAU,CAAC,eACXC,GAAG,CAACM,YAAYG,EAAE,EAClBP,GAAG,EAAC,EACPe,IAAI;YACN1B,QAAQ,CAACe,YAAYG,EAAE,CAAC,GAAG;gBACzBS,WAAWZ,YAAYG,EAAE;gBACzBI;gBACAE;gBACAI,YAAY,GAAEb,oBAAAA,YAAYJ,GAAG,CAAC,gCAAhBI,kBAA6Ba,YAAY;gBACvDC,SAAS9C,MAAM+C,eAAe,CAACL,YAAYH;YAC7C;QACF;IAEJ,EAAE,OAAOxB,OAAO;QACdC,QAAQD,KAAK,CAACA;IAChB;IACA,OAAOE;AACT;AAEA,eAAeV,kBAAiB"}
|