@aglyn/plugins-logic 1.0.0-beta.218 → 1.0.0-beta.220
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/README.md +5 -3
- package/package.json +8 -7
- package/src/lib/components/host-reference-health-card.component.js +36 -21
- package/src/lib/components/host-reference-health-card.component.js.map +1 -1
- package/src/lib/components/host-variables-card.component.js +12 -6
- package/src/lib/components/host-variables-card.component.js.map +1 -1
- package/src/lib/components/where-used-dialog.component.js +3 -3
- package/src/lib/components/where-used-dialog.component.js.map +1 -1
- package/src/lib/declarations.server.d.ts +25 -0
- package/src/lib/declarations.server.js +36 -0
- package/src/lib/declarations.server.js.map +1 -0
- package/src/lib/model/reference-audit.d.ts +23 -7
- package/src/lib/model/reference-audit.js.map +1 -1
- package/src/lib/server/variable-dependents.d.ts +31 -0
- package/src/lib/server/variable-dependents.js +60 -0
- package/src/lib/server/variable-dependents.js.map +1 -0
- package/src/lib/testing/stand-in-dataset-list.d.ts +28 -0
- package/src/lib/testing/stand-in-dataset-list.js +55 -0
- package/src/lib/testing/stand-in-dataset-list.js.map +1 -0
- package/src/lib/testing/stand-in-workflow-list.d.ts +28 -0
- package/src/lib/testing/stand-in-workflow-list.js +48 -0
- package/src/lib/testing/stand-in-workflow-list.js.map +1 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Peer dependencies: `react`, `@mui/material`, `firebase`.
|
|
|
12
12
|
|
|
13
13
|
## What's in it
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Variables and functions resolve when a page renders, through the tenant runtime's compose pipeline, so the plugin adds no element to a published site and registers no API route.
|
|
16
16
|
|
|
17
17
|
**Console** (`registerLogicConsole`, the `console` registrar in `plugins.config.json`):
|
|
18
18
|
|
|
@@ -20,11 +20,13 @@ The plugin is console-only. Variables and functions resolve when a page renders,
|
|
|
20
20
|
- Two widgets in the `besignerFunctions` slot, the variables card and the functions card, which the Besigner's functions drawer draws.
|
|
21
21
|
- A widget in the `workflowUsage` slot: the "where used" dialog, drawn in the zone the Automation page hosts for one workflow's dependents.
|
|
22
22
|
|
|
23
|
+
**Server declarations** (`registerLogicServerDeclarations`, the `serverDeclarations` registrar): a dependents source on the core's `plugin-dependents` seam, which answers the platform's "Used by" scan with the variables computed from a workflow. The workflows plugin asks that scan before a workflow is renamed or deleted, and neither plugin imports the other. The reader loads with the first question.
|
|
24
|
+
|
|
23
25
|
**Exports from `.`**
|
|
24
26
|
|
|
25
27
|
- `registerLogicConsole` and `BUNDLE_ID` (`'logic'`).
|
|
26
28
|
- `HostVariablesCard`, `HostFunctionsCard` and their props types, for an app that opens the cards itself.
|
|
27
|
-
- `auditHostReferences` with `ReferenceIssue`, `ReferenceAuditInput` and `ScreenAuditEntry`: a pure check of every reference an action, workflow, computed variable or screen holds against the ids and names that still exist.
|
|
29
|
+
- `auditHostReferences` with `ReferenceIssue`, `ReferenceAuditInput`, `AuditedWorkflow`, `AuditedVariable` and `ScreenAuditEntry`: a pure check of every reference an action, workflow, computed variable or screen holds against the ids and names that still exist.
|
|
28
30
|
- `parseParameterOptions` and `formatParameterOptions`: a function parameter's choice list as one line of text. The grammar itself lives in `@aglyn/aglyn`; these are the function builder's names for it.
|
|
29
31
|
|
|
30
32
|
## Usage
|
|
@@ -45,7 +47,7 @@ import { auditHostReferences, type ReferenceIssue } from '@aglyn/plugins-logic'
|
|
|
45
47
|
|
|
46
48
|
## How it fits
|
|
47
49
|
|
|
48
|
-
A plugin package (`scope:plugin`). It depends on the core (`@aglyn/aglyn`), the tenant client hooks (`@aglyn/tenant-feature-instance`) and generic `@aglyn/shared-*` packages. It imports no other plugin: what it shares with the workflows plugin goes through a core seam and a console zone. The core never imports it; the console reaches it only through the loader manifest and the slots it fills.
|
|
50
|
+
A plugin package (`scope:plugin`). It depends on the core (`@aglyn/aglyn`), the tenant client hooks (`@aglyn/tenant-feature-instance`), the tenant data layer's Admin SDK for its dependents source (`@aglyn/tenant-data-admin`) and generic `@aglyn/shared-*` packages. It imports no other plugin: what it shares with the workflows plugin goes through a core seam and a console zone. The core never imports it; the console reaches it only through the loader manifest and the slots it fills.
|
|
49
51
|
|
|
50
52
|
## License
|
|
51
53
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-logic",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.220",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,12 +25,13 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
33
|
-
"@aglyn/tenant-
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.220",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.220",
|
|
30
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.220",
|
|
31
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.220",
|
|
32
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.220",
|
|
33
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.220",
|
|
34
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.220",
|
|
34
35
|
"@swc/helpers": "0.5.23"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
18
|
import { pluginDocsHelp, scopeTokensForHost } from "@aglyn/aglyn";
|
|
19
|
+
import { pluginContainerKind } from "@aglyn/aglyn/plugin-manager/plugin-containers";
|
|
19
20
|
import { auditHostReferences } from "../model/index.js";
|
|
20
21
|
import { CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
22
|
import { Alert, Chip, Stack, Typography } from "@mui/material";
|
|
22
|
-
import { collection,
|
|
23
|
+
import { collection, query, where } from "firebase/firestore";
|
|
23
24
|
import { useMemo } from "react";
|
|
24
25
|
import { useFirestore, useFirestoreCollection, useOrgDataScope } from "@aglyn/tenant-feature-instance";
|
|
25
26
|
/*
|
|
@@ -28,6 +29,7 @@ import { useFirestore, useFirestoreCollection, useOrgDataScope } from "@aglyn/te
|
|
|
28
29
|
* Firestore hooks, and a query builder imported through that barrel disappears
|
|
29
30
|
* under the mock. Neither of these is a hook.
|
|
30
31
|
*/ import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
|
|
32
|
+
import { pluginRecordListQuery, pluginRecordsFromRows } from "@aglyn/aglyn/plugin-manager/plugin-record-lists";
|
|
31
33
|
/**
|
|
32
34
|
* How much of each collection the audit judges against.
|
|
33
35
|
*
|
|
@@ -44,6 +46,8 @@ import { useFirestore, useFirestoreCollection, useOrgDataScope } from "@aglyn/te
|
|
|
44
46
|
* and lists the dangling ones, so broken wiring surfaces here instead
|
|
45
47
|
* of failing silently on a visitor's pageview.
|
|
46
48
|
*/ export function HostReferenceHealthCard(props) {
|
|
49
|
+
var _ref;
|
|
50
|
+
var _pluginContainerKind;
|
|
47
51
|
const { hostId } = props;
|
|
48
52
|
const firestore = useFirestore();
|
|
49
53
|
// The org lookup is async (AGL-1061). `dataScope` is null until it
|
|
@@ -98,7 +102,7 @@ import { useFirestore, useFirestoreCollection, useOrgDataScope } from "@aglyn/te
|
|
|
98
102
|
// Scoped (AGL-1044) — see the note in host-actions-card: an unfiltered
|
|
99
103
|
// list is rejected, not filtered.
|
|
100
104
|
// Scoped to the HOST, not the viewer (AGL-1044): the audit speaks for this host, so it must judge against what THIS host can reach — an org-wide
|
|
101
|
-
// admin would otherwise be offered
|
|
105
|
+
// admin would otherwise be offered org records that resolve to nothing at
|
|
102
106
|
// render time. Filtering by the host's tokens also satisfies the
|
|
103
107
|
// AGL-1041 rules, since they are a subset of any viewer's who can reach
|
|
104
108
|
// this host at all.
|
|
@@ -107,36 +111,47 @@ import { useFirestore, useFirestoreCollection, useOrgDataScope } from "@aglyn/te
|
|
|
107
111
|
const scopeTokens = useMemo(()=>scopeTokensForHost(hostId), [
|
|
108
112
|
hostId
|
|
109
113
|
]);
|
|
110
|
-
// The `visibleTo` filter is now unconditional: the only scope this hook
|
|
111
|
-
// hands out is an org one, and org datasets are all scoped (AGL-1041).
|
|
112
|
-
// It used to be conditional on having an org, for the host fallback's
|
|
113
|
-
// sake — those rows carried no `visibleTo` and the filter would have
|
|
114
|
-
// matched nothing.
|
|
115
114
|
/*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
* The datasets are the data plugin's, listed through the source it
|
|
116
|
+
* publishes (AGL-3080): the ones shared with THIS site, which is the set a
|
|
117
|
+
* step on this site can reach. Shaped as the other windows are, so the
|
|
118
|
+
* audit reads every known set the same way.
|
|
119
|
+
*/ const datasetOrgId = (_ref = dataScope == null ? void 0 : dataScope[1]) != null ? _ref : null;
|
|
120
|
+
const { data: datasetRead } = useFirestoreCollection(()=>datasetOrgId ? pluginRecordListQuery('dataset', firestore, {
|
|
121
|
+
orgId: datasetOrgId,
|
|
122
|
+
hostId,
|
|
123
|
+
limit: REFERENCE_CEILING + 1
|
|
124
|
+
}) : null, [
|
|
122
125
|
firestore,
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
datasetOrgId,
|
|
127
|
+
hostId
|
|
125
128
|
], {
|
|
126
129
|
idField: '$id'
|
|
127
130
|
});
|
|
128
|
-
const datasetDocs = useMemo(()=>
|
|
131
|
+
const datasetDocs = useMemo(()=>{
|
|
132
|
+
const { rows, truncated } = ceilingedWindow(datasetRead, REFERENCE_CEILING);
|
|
133
|
+
return {
|
|
134
|
+
rows: pluginRecordsFromRows('dataset', rows).map((dataset)=>({
|
|
135
|
+
$id: dataset.id,
|
|
136
|
+
name: dataset.name
|
|
137
|
+
})),
|
|
138
|
+
truncated
|
|
139
|
+
};
|
|
140
|
+
}, [
|
|
129
141
|
datasetRead
|
|
130
142
|
]);
|
|
131
143
|
/*
|
|
132
144
|
* The campaign containers an "Assign to a campaign" step runs against
|
|
133
|
-
* (AGL-3052)
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
|
|
145
|
+
* (AGL-3052): the `campaign` container kind, read where its declaration
|
|
146
|
+
* says they are stored, and nothing where no plugin keeps the kind. They
|
|
147
|
+
* are the org's, and the audit judges against the ones placed on THIS
|
|
148
|
+
* site, by the same host tokens as the datasets above.
|
|
149
|
+
*/ const campaignCollection = (_pluginContainerKind = pluginContainerKind('campaign')) == null ? void 0 : _pluginContainerKind.orgCollection;
|
|
150
|
+
const { data: campaignRead } = useFirestoreCollection(()=>dataScope && campaignCollection ? collectionCeiling(query(collection(firestore, dataScope[0], dataScope[1], campaignCollection), where('visibleTo', 'array-contains-any', scopeTokens)), REFERENCE_CEILING) : null, [
|
|
137
151
|
firestore,
|
|
138
152
|
dataScope,
|
|
139
|
-
scopeTokens
|
|
153
|
+
scopeTokens,
|
|
154
|
+
campaignCollection
|
|
140
155
|
], {
|
|
141
156
|
idField: '$id'
|
|
142
157
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/components/host-reference-health-card.component.tsx"],"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'use client'\n\nimport { pluginDocsHelp, scopeTokensForHost } from '@aglyn/aglyn'\nimport { auditHostReferences } from '../model'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Alert, Chip, Stack, Typography } from '@mui/material'\nimport {\n collection,\n documentId,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useOrgDataScope,\n} from '@aglyn/tenant-feature-instance'\n/*\n * The MODULE, not the barrel, for the two PURE helpers — the specs that render\n * this card mock `@aglyn/tenant-feature-instance` wholesale to stage their\n * Firestore hooks, and a query builder imported through that barrel disappears\n * under the mock. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\n\nexport interface HostReferenceHealthCardProps {\n hostId: string\n}\n\n/**\n * How much of each collection the audit judges against.\n *\n * Thirteen collections at this ceiling is what an open of this page costs, so\n * the number is a bill as much as a bound. It is stated once because every one\n * of those windows has to agree: the audit compares references drawn from\n * three of them against names drawn from the other ten, and a wider window on\n * one side than the other would report the difference as broken wiring.\n */\nconst REFERENCE_CEILING = 100\n\n/**\n * Reference health (wave v7): id references are rename-safe (AGL-261)\n * but not delete-safe — this card cross-checks every automation,\n * workflow, and computed-variable reference against what still exists\n * and lists the dangling ones, so broken wiring surfaces here instead\n * of failing silently on a visitor's pageview.\n */\nexport function HostReferenceHealthCard(props: HostReferenceHealthCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n // The org lookup is async (AGL-1061). `dataScope` is null until it\n // settles — and stays null for a host with no owning org — so the two\n // org-data queries below simply are not issued rather than falling back\n // to a host path (AGL-1050).\n const { scope: dataScope } = useOrgDataScope({ hostId })\n\n /*\n * ORDERED AND CEILINGED (AGL-2501), the same decision the actions and\n * workflows cards reached.\n *\n * `limit(100)` alone is answered in DOCUMENT-ID order, so an unnamed window\n * is a pseudo-random hundred — and on THIS card that is worse than a\n * mis-drawn list. The audit asks whether a reference resolves and answers\n * from the windows, so a workflow that exists but falls outside the\n * `workflows` hundred is indistinguishable from one that was deleted, and\n * the card reports live wiring as broken. `collectionCeiling` does not\n * change WHICH hundred — document-id order is what a bare cap returns\n * anyway. What it changes is that the order is NAMED, which is what makes\n * the probe row meaningful and catches the obvious next edit: ordering on\n * `name` would HIDE every row written without one rather than mis-sort\n * anything.\n */\n const useHostCollection = (name: string) => {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const { data } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, name),\n REFERENCE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n // Memoised so the window keeps one identity while the rows do. The audit\n // below is eleven set builds and a sweep of three collections; a fresh\n // `slice` every render would re-run all of it every render.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useMemo(() => ceilingedWindow<any>(data, REFERENCE_CEILING), [data])\n }\n const actionDocs = useHostCollection('actions')\n const workflowDocs = useHostCollection('workflows')\n const variableDocs = useHostCollection('variables')\n const functionDocs = useHostCollection('functions')\n const overlayDocs = useHostCollection('overlays')\n const webhookDocs = useHostCollection('webhooks')\n // New reference kinds (AGL-345): screen links, commerce entities.\n const screenDocs = useHostCollection('screens')\n const productDocs = useHostCollection('products')\n const collectionDocs = useHostCollection('collections')\n const categoryDocs = useHostCollection('productCategories')\n // Scoped (AGL-1044) — see the note in host-actions-card: an unfiltered\n // list is rejected, not filtered.\n // Scoped to the HOST, not the viewer (AGL-1044): the audit speaks for this host, so it must judge against what THIS host can reach — an org-wide\n // admin would otherwise be offered datasets that resolve to nothing at\n // render time. Filtering by the host's tokens also satisfies the\n // AGL-1041 rules, since they are a subset of any viewer's who can reach\n // this host at all.\n // Memoised: this is a listener DEPENDENCY, and a fresh array each\n // render tears the subscription down and clears its data every time.\n const scopeTokens = useMemo(() => scopeTokensForHost(hostId), [hostId])\n // The `visibleTo` filter is now unconditional: the only scope this hook\n // hands out is an org one, and org datasets are all scoped (AGL-1041).\n // It used to be conditional on having an org, for the host fallback's\n // sake — those rows carried no `visibleTo` and the filter would have\n // matched nothing.\n /*\n * `documentId()` rather than a field, for the reason the audience sweep in\n * `campaign-send.ts` gives: Firestore's automatic single-field index for an\n * array member is keyed on the value and the document name, so\n * `array-contains-any` plus `orderBy(__name__)` is served by it. Ordering on\n * anything else here would need a composite index that does not exist.\n */\n const { data: datasetRead } = useFirestoreCollection<any>(\n () =>\n dataScope\n ? query(\n collection(firestore, dataScope[0], dataScope[1], 'datasets'),\n where('visibleTo', 'array-contains-any', scopeTokens),\n orderBy(documentId()),\n limit(REFERENCE_CEILING + 1),\n )\n : null,\n [firestore, dataScope, scopeTokens],\n { idField: '$id' },\n )\n const datasetDocs = useMemo(\n () => ceilingedWindow<any>(datasetRead, REFERENCE_CEILING),\n [datasetRead],\n )\n /*\n * The campaign containers an \"Assign to a campaign\" step runs against\n * (AGL-3052); `campaigns` holds the email sends, which no step names.\n * They are the org's, and the audit judges against the ones placed on\n * THIS site, by the same host tokens as the datasets above.\n */\n const { data: campaignRead } = useFirestoreCollection<any>(\n () =>\n dataScope\n ? collectionCeiling(\n query(\n collection(firestore, dataScope[0], dataScope[1], 'emailCampaigns'),\n where('visibleTo', 'array-contains-any', scopeTokens),\n ),\n REFERENCE_CEILING,\n )\n : null,\n [firestore, dataScope, scopeTokens],\n { idField: '$id' },\n )\n const campaignDocs = useMemo(\n () => ceilingedWindow<any>(campaignRead, REFERENCE_CEILING),\n [campaignRead],\n )\n const { data: listRead } = useFirestoreCollection<any>(\n () =>\n dataScope\n ? collectionCeiling(\n collection(firestore, dataScope[0], dataScope[1], 'lists'),\n REFERENCE_CEILING,\n )\n : null,\n [firestore, dataScope],\n { idField: '$id' },\n )\n const listDocs = useMemo(\n () => ceilingedWindow<any>(listRead, REFERENCE_CEILING),\n [listRead],\n )\n\n /**\n * At least one collection holds more than the audit read.\n *\n * The verdict this card renders is computed entirely from the windows, so a\n * ceiling that bit anywhere makes both halves of it unsafe: a reference into\n * the unread part of a `known` collection is reported broken, and a\n * reference held by an unread action, workflow or variable is not checked at\n * all. Neither shows up as a gap in the list, which is why it has to be said\n * outright.\n */\n const truncated = [\n actionDocs,\n workflowDocs,\n variableDocs,\n functionDocs,\n campaignDocs,\n overlayDocs,\n webhookDocs,\n screenDocs,\n productDocs,\n collectionDocs,\n categoryDocs,\n datasetDocs,\n listDocs,\n ].some((window) => window.truncated)\n\n const issues = useMemo(() => {\n const alive = (docs: any[] | undefined) =>\n (docs ?? []).filter((doc: any) => !doc.deletedAt)\n const knownSet = (docs: any[] | undefined, nameKey = 'name') => {\n const set = new Set<string>()\n for (const doc of alive(docs)) {\n set.add(doc.$id)\n const name = doc[nameKey]\n if (typeof name === 'string' && name.trim()) set.add(name.trim())\n }\n return set\n }\n return auditHostReferences({\n actions: alive(actionDocs.rows),\n workflows: alive(workflowDocs.rows),\n variables: alive(variableDocs.rows),\n known: {\n workflows: knownSet(workflowDocs.rows),\n functions: knownSet(functionDocs.rows),\n datasets: knownSet(datasetDocs.rows),\n lists: knownSet(listDocs.rows),\n campaigns: knownSet(campaignDocs.rows),\n overlays: knownSet(overlayDocs.rows),\n webhooks: knownSet(webhookDocs.rows),\n screens: knownSet(screenDocs.rows, 'displayName'),\n products: knownSet(productDocs.rows),\n collections: knownSet(collectionDocs.rows),\n categories: knownSet(categoryDocs.rows),\n },\n })\n }, [\n actionDocs,\n workflowDocs,\n variableDocs,\n functionDocs,\n datasetDocs,\n listDocs,\n campaignDocs,\n overlayDocs,\n webhookDocs,\n screenDocs,\n productDocs,\n collectionDocs,\n categoryDocs,\n ])\n\n return (\n <CardDisplay\n header={'Reference health'}\n help={pluginDocsHelp('bindings', { anchor: '#where-used--safety' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n <Stack spacing={1}>\n {truncated ? (\n <Alert severity=\"info\">\n {`Audited against the first ${REFERENCE_CEILING} rows of each ` +\n 'collection, ordered by id. At least one of them holds more, ' +\n 'so a step pointing past that window is listed here as broken ' +\n 'when it is not, and a step held past it is not checked at all.'}\n </Alert>\n ) : null}\n {issues.length === 0 ? (\n <Alert severity=\"success\">\n {truncated\n ? 'Every reference the audit read resolves.'\n : 'Every automation, workflow, and variable reference resolves.'}\n </Alert>\n ) : (\n <Stack spacing={1}>\n <Alert severity=\"warning\">\n {`${issues.length} broken reference${\n issues.length === 1 ? '' : 's'\n } — these steps do nothing until re-pointed or removed.`}\n </Alert>\n {issues.map((issue, index) => (\n <Stack\n key={`${issue.sourceId}:${index}`}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Chip size=\"small\" label={issue.source} />\n <Typography variant=\"body2\" noWrap sx={{ maxWidth: '40%' }}>\n {issue.sourceName}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`→ missing ${issue.refType} `}\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"error\"\n sx={{ fontFamily: 'monospace' }}\n >\n {issue.missing || '(empty)'}\n </Typography>\n </Stack>\n ))}\n </Stack>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nHostReferenceHealthCard.displayName = 'HostReferenceHealthCard'\n\nexport default HostReferenceHealthCard\n"],"names":["pluginDocsHelp","scopeTokensForHost","auditHostReferences","CardDisplay","Alert","Chip","Stack","Typography","collection","documentId","limit","orderBy","query","where","useMemo","useFirestore","useFirestoreCollection","useOrgDataScope","ceilingedWindow","collectionCeiling","REFERENCE_CEILING","HostReferenceHealthCard","props","hostId","firestore","scope","dataScope","useHostCollection","name","data","idField","actionDocs","workflowDocs","variableDocs","functionDocs","overlayDocs","webhookDocs","screenDocs","productDocs","collectionDocs","categoryDocs","scopeTokens","datasetRead","datasetDocs","campaignRead","campaignDocs","listRead","listDocs","truncated","some","window","issues","alive","docs","filter","doc","deletedAt","knownSet","nameKey","set","Set","add","$id","trim","actions","rows","workflows","variables","known","functions","datasets","lists","campaigns","overlays","webhooks","screens","products","collections","categories","header","help","anchor","contentGutterX","contentGutterY","contentBordered","spacing","severity","length","map","issue","index","direction","sx","alignItems","size","label","source","variant","noWrap","maxWidth","sourceName","color","refType","fontFamily","missing","sourceId","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/components/host-reference-health-card.component.tsx"],"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'use client'\n\nimport { pluginDocsHelp, scopeTokensForHost } from '@aglyn/aglyn'\nimport { pluginContainerKind } from '@aglyn/aglyn/plugin-manager/plugin-containers'\nimport { auditHostReferences } from '../model'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Alert, Chip, Stack, Typography } from '@mui/material'\nimport { collection, query, where } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useOrgDataScope,\n} from '@aglyn/tenant-feature-instance'\n/*\n * The MODULE, not the barrel, for the two PURE helpers — the specs that render\n * this card mock `@aglyn/tenant-feature-instance` wholesale to stage their\n * Firestore hooks, and a query builder imported through that barrel disappears\n * under the mock. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport {\n pluginRecordListQuery,\n pluginRecordsFromRows,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\n\nexport interface HostReferenceHealthCardProps {\n hostId: string\n}\n\n/**\n * How much of each collection the audit judges against.\n *\n * Thirteen collections at this ceiling is what an open of this page costs, so\n * the number is a bill as much as a bound. It is stated once because every one\n * of those windows has to agree: the audit compares references drawn from\n * three of them against names drawn from the other ten, and a wider window on\n * one side than the other would report the difference as broken wiring.\n */\nconst REFERENCE_CEILING = 100\n\n/**\n * Reference health (wave v7): id references are rename-safe (AGL-261)\n * but not delete-safe — this card cross-checks every automation,\n * workflow, and computed-variable reference against what still exists\n * and lists the dangling ones, so broken wiring surfaces here instead\n * of failing silently on a visitor's pageview.\n */\nexport function HostReferenceHealthCard(props: HostReferenceHealthCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n // The org lookup is async (AGL-1061). `dataScope` is null until it\n // settles — and stays null for a host with no owning org — so the two\n // org-data queries below simply are not issued rather than falling back\n // to a host path (AGL-1050).\n const { scope: dataScope } = useOrgDataScope({ hostId })\n\n /*\n * ORDERED AND CEILINGED (AGL-2501), the same decision the actions and\n * workflows cards reached.\n *\n * `limit(100)` alone is answered in DOCUMENT-ID order, so an unnamed window\n * is a pseudo-random hundred — and on THIS card that is worse than a\n * mis-drawn list. The audit asks whether a reference resolves and answers\n * from the windows, so a workflow that exists but falls outside the\n * `workflows` hundred is indistinguishable from one that was deleted, and\n * the card reports live wiring as broken. `collectionCeiling` does not\n * change WHICH hundred — document-id order is what a bare cap returns\n * anyway. What it changes is that the order is NAMED, which is what makes\n * the probe row meaningful and catches the obvious next edit: ordering on\n * `name` would HIDE every row written without one rather than mis-sort\n * anything.\n */\n const useHostCollection = (name: string) => {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const { data } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, name),\n REFERENCE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n // Memoised so the window keeps one identity while the rows do. The audit\n // below is eleven set builds and a sweep of three collections; a fresh\n // `slice` every render would re-run all of it every render.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useMemo(() => ceilingedWindow<any>(data, REFERENCE_CEILING), [data])\n }\n const actionDocs = useHostCollection('actions')\n const workflowDocs = useHostCollection('workflows')\n const variableDocs = useHostCollection('variables')\n const functionDocs = useHostCollection('functions')\n const overlayDocs = useHostCollection('overlays')\n const webhookDocs = useHostCollection('webhooks')\n // New reference kinds (AGL-345): screen links, commerce entities.\n const screenDocs = useHostCollection('screens')\n const productDocs = useHostCollection('products')\n const collectionDocs = useHostCollection('collections')\n const categoryDocs = useHostCollection('productCategories')\n // Scoped (AGL-1044) — see the note in host-actions-card: an unfiltered\n // list is rejected, not filtered.\n // Scoped to the HOST, not the viewer (AGL-1044): the audit speaks for this host, so it must judge against what THIS host can reach — an org-wide\n // admin would otherwise be offered org records that resolve to nothing at\n // render time. Filtering by the host's tokens also satisfies the\n // AGL-1041 rules, since they are a subset of any viewer's who can reach\n // this host at all.\n // Memoised: this is a listener DEPENDENCY, and a fresh array each\n // render tears the subscription down and clears its data every time.\n const scopeTokens = useMemo(() => scopeTokensForHost(hostId), [hostId])\n /*\n * The datasets are the data plugin's, listed through the source it\n * publishes (AGL-3080): the ones shared with THIS site, which is the set a\n * step on this site can reach. Shaped as the other windows are, so the\n * audit reads every known set the same way.\n */\n const datasetOrgId = dataScope?.[1] ?? null\n const { data: datasetRead } = useFirestoreCollection<any>(\n () =>\n datasetOrgId\n ? pluginRecordListQuery('dataset', firestore, {\n orgId: datasetOrgId,\n hostId,\n limit: REFERENCE_CEILING + 1,\n })\n : null,\n [firestore, datasetOrgId, hostId],\n { idField: '$id' },\n )\n const datasetDocs = useMemo(() => {\n const { rows, truncated } = ceilingedWindow<any>(datasetRead, REFERENCE_CEILING)\n return {\n rows: pluginRecordsFromRows('dataset', rows).map((dataset) => ({\n $id: dataset.id,\n name: dataset.name,\n })),\n truncated,\n }\n }, [datasetRead])\n /*\n * The campaign containers an \"Assign to a campaign\" step runs against\n * (AGL-3052): the `campaign` container kind, read where its declaration\n * says they are stored, and nothing where no plugin keeps the kind. They\n * are the org's, and the audit judges against the ones placed on THIS\n * site, by the same host tokens as the datasets above.\n */\n const campaignCollection = pluginContainerKind('campaign')?.orgCollection\n const { data: campaignRead } = useFirestoreCollection<any>(\n () =>\n dataScope && campaignCollection\n ? collectionCeiling(\n query(\n collection(firestore, dataScope[0], dataScope[1], campaignCollection),\n where('visibleTo', 'array-contains-any', scopeTokens),\n ),\n REFERENCE_CEILING,\n )\n : null,\n [firestore, dataScope, scopeTokens, campaignCollection],\n { idField: '$id' },\n )\n const campaignDocs = useMemo(\n () => ceilingedWindow<any>(campaignRead, REFERENCE_CEILING),\n [campaignRead],\n )\n const { data: listRead } = useFirestoreCollection<any>(\n () =>\n dataScope\n ? collectionCeiling(\n collection(firestore, dataScope[0], dataScope[1], 'lists'),\n REFERENCE_CEILING,\n )\n : null,\n [firestore, dataScope],\n { idField: '$id' },\n )\n const listDocs = useMemo(\n () => ceilingedWindow<any>(listRead, REFERENCE_CEILING),\n [listRead],\n )\n\n /**\n * At least one collection holds more than the audit read.\n *\n * The verdict this card renders is computed entirely from the windows, so a\n * ceiling that bit anywhere makes both halves of it unsafe: a reference into\n * the unread part of a `known` collection is reported broken, and a\n * reference held by an unread action, workflow or variable is not checked at\n * all. Neither shows up as a gap in the list, which is why it has to be said\n * outright.\n */\n const truncated = [\n actionDocs,\n workflowDocs,\n variableDocs,\n functionDocs,\n campaignDocs,\n overlayDocs,\n webhookDocs,\n screenDocs,\n productDocs,\n collectionDocs,\n categoryDocs,\n datasetDocs,\n listDocs,\n ].some((window) => window.truncated)\n\n const issues = useMemo(() => {\n const alive = (docs: any[] | undefined) =>\n (docs ?? []).filter((doc: any) => !doc.deletedAt)\n const knownSet = (docs: any[] | undefined, nameKey = 'name') => {\n const set = new Set<string>()\n for (const doc of alive(docs)) {\n set.add(doc.$id)\n const name = doc[nameKey]\n if (typeof name === 'string' && name.trim()) set.add(name.trim())\n }\n return set\n }\n return auditHostReferences({\n actions: alive(actionDocs.rows),\n workflows: alive(workflowDocs.rows),\n variables: alive(variableDocs.rows),\n known: {\n workflows: knownSet(workflowDocs.rows),\n functions: knownSet(functionDocs.rows),\n datasets: knownSet(datasetDocs.rows),\n lists: knownSet(listDocs.rows),\n campaigns: knownSet(campaignDocs.rows),\n overlays: knownSet(overlayDocs.rows),\n webhooks: knownSet(webhookDocs.rows),\n screens: knownSet(screenDocs.rows, 'displayName'),\n products: knownSet(productDocs.rows),\n collections: knownSet(collectionDocs.rows),\n categories: knownSet(categoryDocs.rows),\n },\n })\n }, [\n actionDocs,\n workflowDocs,\n variableDocs,\n functionDocs,\n datasetDocs,\n listDocs,\n campaignDocs,\n overlayDocs,\n webhookDocs,\n screenDocs,\n productDocs,\n collectionDocs,\n categoryDocs,\n ])\n\n return (\n <CardDisplay\n header={'Reference health'}\n help={pluginDocsHelp('bindings', { anchor: '#where-used--safety' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n <Stack spacing={1}>\n {truncated ? (\n <Alert severity=\"info\">\n {`Audited against the first ${REFERENCE_CEILING} rows of each ` +\n 'collection, ordered by id. At least one of them holds more, ' +\n 'so a step pointing past that window is listed here as broken ' +\n 'when it is not, and a step held past it is not checked at all.'}\n </Alert>\n ) : null}\n {issues.length === 0 ? (\n <Alert severity=\"success\">\n {truncated\n ? 'Every reference the audit read resolves.'\n : 'Every automation, workflow, and variable reference resolves.'}\n </Alert>\n ) : (\n <Stack spacing={1}>\n <Alert severity=\"warning\">\n {`${issues.length} broken reference${\n issues.length === 1 ? '' : 's'\n } — these steps do nothing until re-pointed or removed.`}\n </Alert>\n {issues.map((issue, index) => (\n <Stack\n key={`${issue.sourceId}:${index}`}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Chip size=\"small\" label={issue.source} />\n <Typography variant=\"body2\" noWrap sx={{ maxWidth: '40%' }}>\n {issue.sourceName}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`→ missing ${issue.refType} `}\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"error\"\n sx={{ fontFamily: 'monospace' }}\n >\n {issue.missing || '(empty)'}\n </Typography>\n </Stack>\n ))}\n </Stack>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nHostReferenceHealthCard.displayName = 'HostReferenceHealthCard'\n\nexport default HostReferenceHealthCard\n"],"names":["pluginDocsHelp","scopeTokensForHost","pluginContainerKind","auditHostReferences","CardDisplay","Alert","Chip","Stack","Typography","collection","query","where","useMemo","useFirestore","useFirestoreCollection","useOrgDataScope","ceilingedWindow","collectionCeiling","pluginRecordListQuery","pluginRecordsFromRows","REFERENCE_CEILING","HostReferenceHealthCard","props","hostId","firestore","scope","dataScope","useHostCollection","name","data","idField","actionDocs","workflowDocs","variableDocs","functionDocs","overlayDocs","webhookDocs","screenDocs","productDocs","collectionDocs","categoryDocs","scopeTokens","datasetOrgId","datasetRead","orgId","limit","datasetDocs","rows","truncated","map","dataset","$id","id","campaignCollection","orgCollection","campaignRead","campaignDocs","listRead","listDocs","some","window","issues","alive","docs","filter","doc","deletedAt","knownSet","nameKey","set","Set","add","trim","actions","workflows","variables","known","functions","datasets","lists","campaigns","overlays","webhooks","screens","products","collections","categories","header","help","anchor","contentGutterX","contentGutterY","contentBordered","spacing","severity","length","issue","index","direction","sx","alignItems","size","label","source","variant","noWrap","maxWidth","sourceName","color","refType","fontFamily","missing","sourceId","displayName"],"mappings":"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"}
|
|
@@ -24,6 +24,7 @@ import { checkQuota, formatVariableValue, HOST_VARIABLE_TYPE_LABELS, pluginDocsH
|
|
|
24
24
|
* under the mock — which fails as "not a function" in the component rather than
|
|
25
25
|
* as anything about the test's subject. Neither of these is a hook.
|
|
26
26
|
*/ import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
|
|
27
|
+
import { pluginRecordListQuery, pluginRecordsFromRows } from "@aglyn/aglyn/plugin-manager/plugin-record-lists";
|
|
27
28
|
import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
28
29
|
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
29
30
|
import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
@@ -186,7 +187,8 @@ import { fetchWhereUsed, summarizeDependents } from "@aglyn/aglyn/app-utils/wher
|
|
|
186
187
|
if (draft && !editorOpened) setEditorOpened(true);
|
|
187
188
|
/*
|
|
188
189
|
* Workflow picker options (AGL-261): computed variables reference the
|
|
189
|
-
* workflow by doc id instead of a typed name.
|
|
190
|
+
* workflow by doc id instead of a typed name. The workflows are the
|
|
191
|
+
* workflows plugin's, listed through the source it publishes (AGL-3080).
|
|
190
192
|
*
|
|
191
193
|
* READ WHEN THE EDITOR OPENS, not when the card mounts (AGL-2501). Nothing
|
|
192
194
|
* outside the dialog below touches these rows — the table shows a computed
|
|
@@ -198,7 +200,10 @@ import { fetchWhereUsed, summarizeDependents } from "@aglyn/aglyn/app-utils/wher
|
|
|
198
200
|
* `limit` is answered in document-id order, so the `localeCompare` below
|
|
199
201
|
* would arrange a pseudo-random hundred into a convincing alphabet and a
|
|
200
202
|
* workflow past it could not be picked, with nothing saying why.
|
|
201
|
-
*/ const { data: workflowDocs } = useFirestoreCollection(()=>editorOpened ?
|
|
203
|
+
*/ const { data: workflowDocs } = useFirestoreCollection(()=>editorOpened ? pluginRecordListQuery('workflow', firestore, {
|
|
204
|
+
hostId,
|
|
205
|
+
limit: WORKFLOW_OPTION_CEILING + 1
|
|
206
|
+
}) : null, [
|
|
202
207
|
firestore,
|
|
203
208
|
hostId,
|
|
204
209
|
editorOpened
|
|
@@ -206,9 +211,10 @@ import { fetchWhereUsed, summarizeDependents } from "@aglyn/aglyn/app-utils/wher
|
|
|
206
211
|
idField: '$id'
|
|
207
212
|
});
|
|
208
213
|
const { rows: readWorkflows, truncated: workflowOptionsTruncated } = ceilingedWindow(workflowDocs, WORKFLOW_OPTION_CEILING);
|
|
209
|
-
// Sorting the whole ceiling, not a page of it.
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
// Sorting the whole ceiling, not a page of it. The workflows plugin answers
|
|
215
|
+
// live, named workflows only.
|
|
216
|
+
const workflowOptions = pluginRecordsFromRows('workflow', readWorkflows).map((workflow)=>({
|
|
217
|
+
id: workflow.id,
|
|
212
218
|
name: workflow.name
|
|
213
219
|
})).sort((a, b)=>a.name.localeCompare(b.name));
|
|
214
220
|
/*
|
|
@@ -390,7 +396,7 @@ import { fetchWhereUsed, summarizeDependents } from "@aglyn/aglyn/app-utils/wher
|
|
|
390
396
|
});
|
|
391
397
|
const confirmed = await confirm({
|
|
392
398
|
title: 'Delete this variable?',
|
|
393
|
-
description: scan.total ? `"${variable.name}" is used in ${summarizeDependents(scan)}. ` + 'Those bindings will render as empty or literal tokens after ' + 'the next publish.' : `"${variable.name}" is not referenced by any published ` + '
|
|
399
|
+
description: scan.total ? `"${variable.name}" is used in ${summarizeDependents(scan)}. ` + 'Those bindings will render as empty or literal tokens after ' + 'the next publish.' : `"${variable.name}" is not referenced by any published ` + 'page, layout, or workflow.',
|
|
394
400
|
confirmationText: 'Delete',
|
|
395
401
|
confirmationButtonProps: {
|
|
396
402
|
color: 'error'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/components/host-variables-card.component.tsx"],"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'use client'\n\nimport {\n type AglynOrgBilling,\n checkQuota,\n formatVariableValue,\n HOST_VARIABLE_TYPE_LABELS,\n type HostVariable,\n type HostVariableType,\n pluginDocsHelp,\n VARIABLE_NAME_PATTERN,\n} from '@aglyn/aglyn'\n/*\n * The MODULE, not the barrel, for the two PURE helpers. Every spec that renders\n * this card mocks `@aglyn/tenant-feature-instance` wholesale to stage its\n * Firestore hooks, and a query builder imported through that barrel disappears\n * under the mock — which fails as \"not a function\" in the component rather than\n * as anything about the test's subject. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, getCountFromServer } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostResourceApi,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\nimport WhereUsedDialog from './where-used-dialog.component'\nimport {\n fetchWhereUsed,\n summarizeDependents,\n type WhereUsedResult,\n} from '@aglyn/aglyn/app-utils/where-used'\n\n/**\n * How many variables the card reads.\n *\n * A CEILING, not a page size — see the query, which explains why this list\n * cannot be paged by the server without making the duplicate-name check lie.\n */\nconst VARIABLE_CEILING = 100\n\n/**\n * How many workflows the computed-variable picker offers.\n *\n * Paid for only while the editor is open, which is what makes a ceiling this\n * size affordable: it is one reader mid-edit rather than every visitor to the\n * page.\n */\nconst WORKFLOW_OPTION_CEILING = 100\n\nexport interface HostVariablesCardProps {\n hostId: string\n /** Resolved entitlement source for quota checks (AGL-395). */\n org?: Partial<AglynOrgBilling>\n}\n\ninterface VariableDraft {\n /** Computed-source workflow doc id (AGL-261); name is the display hint. */\n workflowId: string\n id: string | null\n name: string\n type: HostVariableType\n value: string\n /** Workflow (by name) computing this variable at render (AGL-129). */\n workflowName: string\n}\n\n/** Per-type value editor: mirrors the mockup's type list. */\nfunction VariableValueField(props: {\n draft: VariableDraft\n onChange: (value: string) => void\n}) {\n const { draft, onChange } = props\n switch (draft.type) {\n case 'boolean':\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Switch\n checked={draft.value === 'true'}\n onChange={(event) =>\n onChange(event.target.checked ? 'true' : 'false')\n }\n />\n <Typography variant=\"body2\">\n {draft.value === 'true' ? 'True' : 'False'}\n </Typography>\n </Stack>\n )\n case 'date':\n case 'time':\n return (\n <TextField\n label=\"Value\"\n type={draft.type}\n value={draft.value}\n onChange={(event) => onChange(event.target.value)}\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n />\n )\n case 'number':\n return (\n <TextField\n label=\"Value\"\n value={draft.value}\n onChange={(event) =>\n onChange(event.target.value.replace(/[^0-9.eE+-]/g, ''))\n }\n size=\"small\"\n />\n )\n case 'dictionary':\n case 'collection':\n return (\n <TextField\n label=\"Value (JSON)\"\n placeholder={\n draft.type === 'dictionary' ? '{\"key\": \"value\"}' : '[\"a\", \"b\"]'\n }\n value={draft.value}\n onChange={(event) => onChange(event.target.value)}\n size=\"small\"\n multiline\n minRows={3}\n />\n )\n default:\n return (\n <TextField\n label=\"Value\"\n value={draft.value}\n onChange={(event) => onChange(event.target.value)}\n size=\"small\"\n multiline\n />\n )\n }\n}\n\n/**\n * Host variables (Component Builder, AGL-91): the mockup's Edit Variable\n * dialog — name + typed value — persisted to `hosts/{hostId}/variables`.\n * Any string prop can reference a variable with `{{name}}`; the org\n * compose pipeline resolves it at render. Soft delete keeps old tokens\n * rendering literally rather than breaking published pages.\n */\nexport function HostVariablesCard(props: HostVariablesCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const createHostResource = useHostResourceApi()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const { org } = props\n // Where-used dependents dialog (AGL-187).\n const [usage, setUsage] = useState<{\n name: string\n result: WhereUsedResult\n } | null>(null)\n const [usageLoading, setUsageLoading] = useState<string | null>(null)\n const {\n data: variableDocs,\n status: variablesStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing copies a whole stored variable into `draft` and\n * writes all of it back, so `merge: true` protects nothing — every field\n * is in the payload. `value` is the literal every `{{name}}` binding on\n * every published page resolves to, and `workflowId` is what rebinds a\n * computed variable to its source (AGL-261), so a cached seed can point\n * a live binding back at a value or a workflow the author replaced.\n */\n fromCache: variablesFromCache,\n } = useFirestoreCollection<any>(\n /*\n * ORDERED AND CEILINGED, deliberately not paged by the query (AGL-2501).\n *\n * `limit(100)` alone is answered in DOCUMENT-ID order, so the window is a\n * pseudo-random hundred that the `localeCompare` below arranges into a\n * convincing A-to-Z list. `variablesPerHost` runs to 5,000, so a Scale\n * site with three hundred variables sees a hundred of them and nothing\n * said so.\n *\n * `collectionCeiling` does not change WHICH hundred — document-id order is\n * what the bare cap already returned. What it changes is that the order is\n * NAMED, so the obvious next edit is caught: ordering on `name` would not\n * mis-sort this list, it would HIDE from it every variable written without\n * one, and `/api/hosts/resources` validates no field for presence while\n * `IMPORTABLE_FIELDS.variables` copies a name only if the exported\n * document carried it. The document name cannot be absent.\n *\n * The QUERY is not paged because `nameTaken` below tests the draft against\n * these rows. Case-insensitive uniqueness is what keeps legacy `{{name}}`\n * token resolution unambiguous (AGL-185), and on a ten-row server page\n * that check would compare a new variable against a tenth of the site and\n * cheerfully create the duplicate it exists to prevent.\n *\n * The check is still only as complete as the ceiling, and that is now\n * SAID rather than assumed: the notice under the list appears exactly when\n * the probe finds a variable this card did not read.\n */\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'variables'),\n VARIABLE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: readVariables, truncated: variablesTruncated } =\n ceilingedWindow<any>(variableDocs, VARIABLE_CEILING)\n const [draft, setDraft] = useState<VariableDraft | null>(null)\n /**\n * The editor has been opened at least once in this session.\n *\n * A LATCH rather than `draft` itself, because the workflow picker below keys its\n * listeners on it. Tracking the dialog would tear that subscription down\n * on Cancel and pay for them again on the next Edit, so a merchant working\n * through ten variables would buy the same window ten times — worse than\n * the mount-time read this replaces. Latched, a reader who never edits pays\n * nothing and a reader who edits pays once.\n */\n const [editorOpened, setEditorOpened] = useState(false)\n if (draft && !editorOpened) setEditorOpened(true)\n /*\n * Workflow picker options (AGL-261): computed variables reference the\n * workflow by doc id instead of a typed name.\n *\n * READ WHEN THE EDITOR OPENS, not when the card mounts (AGL-2501). Nothing\n * outside the dialog below touches these rows — the table shows a computed\n * variable's stored `workflowName`, never a name looked up here — so an\n * unconditional listener would charge every visitor to this page for a\n * hundred workflows to fill a select most of them never see.\n *\n * Ceilinged and ordered for the same reason as the variables above: a bare\n * `limit` is answered in document-id order, so the `localeCompare` below\n * would arrange a pseudo-random hundred into a convincing alphabet and a\n * workflow past it could not be picked, with nothing saying why.\n */\n const { data: workflowDocs } = useFirestoreCollection<any>(\n () =>\n editorOpened\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'workflows'),\n WORKFLOW_OPTION_CEILING,\n )\n : null,\n [firestore, hostId, editorOpened],\n { idField: '$id' },\n )\n const { rows: readWorkflows, truncated: workflowOptionsTruncated } =\n ceilingedWindow<any>(workflowDocs, WORKFLOW_OPTION_CEILING)\n // Sorting the whole ceiling, not a page of it.\n const workflowOptions = readWorkflows\n .filter((workflow: any) => !workflow.deletedAt && workflow.name)\n .map((workflow: any) => ({\n id: workflow.$id as string,\n name: workflow.name as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n /*\n * Sorting is safe HERE in a way it is not on a paged list: these rows are\n * the whole collection below the ceiling, not a slice of one, so the order\n * on screen is the order of everything the card holds.\n */\n const variables = useMemo(\n () =>\n readVariables\n .filter((variable: any) => !variable.deletedAt)\n .sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n ),\n [readVariables],\n )\n // The page is a SLICE, because the rows are already in hand and `nameTaken`\n // needs all of them. What the card lacked was not a cheaper read but a\n // control: a hundred variables rendered as one wall.\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const visibleVariables = useMemo(\n () => variables.slice(page * pageSize, page * pageSize + pageSize),\n [variables, page, pageSize],\n )\n /**\n * The variable HEAD-COUNT is a server aggregate, not the length of the\n * capped listener (AGL-1716, the AGL-1706 shape).\n *\n * The listener is `limit(100)` and `variables.length` was handed to\n * `checkQuota(org, 'variablesPerHost', …)`. The bands are 3 / 25 / 100 /\n * 1,000 / 5,000, so the window ties Pro's 100 and hides Business's 1,000\n * and Scale's 5,000 entirely: on those plans the gate compared 100 against\n * thousands and could never refuse, while `api/hosts/resources` counted\n * the collection and did. The card offered headroom that did not exist and\n * then failed the create.\n *\n * UNFILTERED, matching the enforcing route: it runs a plain\n * `collection('variables').count()` for this quota — its `softDeletes`\n * branch governs only the flat per-host webhook cap — so a soft-deleted\n * variable has always counted toward `variablesPerHost` server-side while\n * this card excluded it. The card now asks the route's question in the\n * route's terms, which is the only thing that makes a console gate a\n * useful prediction of the route's verdict.\n *\n * THE LIST KEEPS ITS CAP. Only a create moves the number (deletes here are\n * soft, and the route counts them), so that is where it is re-read.\n */\n const [variableCountEpoch, setVariableCountEpoch] = useState(0)\n const [serverVariableCount, setServerVariableCount] = useState<number | null>(\n null,\n )\n useEffect(() => {\n let active = true\n void getCountFromServer(collection(firestore, 'hosts', hostId, 'variables'))\n .then((snapshot) => {\n if (active) setServerVariableCount(snapshot.data().count)\n })\n .catch(() => {\n // Falls back to the loaded rows below — a LOWER bound, and this\n // card's prior behaviour. Deliberately not 0: `checkQuota` answers\n // from whatever it is handed, and 0 used is a confident wrong\n // number in the flattering direction.\n })\n return () => {\n active = false\n }\n }, [firestore, hostId, variableCountEpoch])\n const variableCount = serverVariableCount ?? variables.length\n\n const validName = VARIABLE_NAME_PATTERN.test(draft?.name ?? '')\n // Case-insensitive (AGL-185): names must stay unambiguous for legacy\n // {{name}} token resolution and picker display.\n const nameTaken = Boolean(\n draft &&\n variables.some(\n (variable: any) =>\n String(variable.name ?? '').toLowerCase() ===\n draft.name.trim().toLowerCase() && variable.$id !== draft.id,\n ),\n )\n\n const handleSave = useCallback(async () => {\n if (!draft || !validName || nameTaken) return\n const fields = {\n name: draft.name,\n type: draft.type,\n value: draft.value,\n workflowId: draft.workflowId.trim(),\n workflowName: draft.workflowName.trim(),\n }\n try {\n if (draft.id) {\n /**\n * Edit stays client-direct (no quota consumed) — and is refused when\n * its seed was never confirmed by the server (AGL-1358). `merge:\n * true` protects nothing here: `fields` is every editable key of the\n * stored row, copied off the listener whether or not it was touched.\n *\n * The guard WRAPS the write. An early return is a shape you can keep\n * while losing the protection; here the write is only reachable\n * through the verdict.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'variable',\n unreadable: variablesStatus === 'error',\n fromCache: variablesFromCache,\n },\n async () => {\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'variables', draft.id),\n { ...fields, updatedAt: Timestamp.now() },\n { merge: true },\n ),\n })\n },\n )\n // Before `setDraft(null)`, so a refusal keeps the dialog open with\n // what was typed. A save that silently does nothing sends the user\n // back to retype a form that will be refused again just as quietly.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n } else {\n // New variable rides the quota-enforcing resources API (AGL-473).\n await createHostResource({ hostId, resource: 'variable', data: fields })\n // A create moves the count and the aggregate is a one-shot — the\n // listener refreshes the ROWS for free, the count has to be asked\n // again or the cap drifts stale for the rest of the session.\n setVariableCountEpoch((epoch) => epoch + 1)\n }\n setDraft(null)\n enqueueSnackbar(\n `Saved — use {{${draft.name}}} in any text to bind it`,\n { variant: 'success', persist: false },\n )\n } catch (error: any) {\n console.error(error)\n enqueueSnackbar(error?.message ?? 'An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [\n user,\n draft,\n validName,\n nameTaken,\n firestore,\n hostId,\n createHostResource,\n enqueueSnackbar,\n variablesStatus,\n variablesFromCache,\n ])\n\n const handleShowUsage = useCallback(\n (variable: any) => async () => {\n setUsageLoading(variable.$id)\n const result = await fetchWhereUsed(user as any, {\n hostId,\n kind: 'variable',\n id: variable.$id,\n name: variable.name,\n })\n setUsageLoading(null)\n setUsage({ name: variable.name, result })\n },\n [user, hostId],\n )\n\n const handleDelete = useCallback(\n (variable: any) => async () => {\n // Real dependents warning (AGL-187) instead of the generic copy.\n const scan = await fetchWhereUsed(user as any, {\n hostId,\n kind: 'variable',\n id: variable.$id,\n name: variable.name,\n })\n const confirmed = await confirm({\n title: 'Delete this variable?',\n description: scan.total\n ? `\"${variable.name}\" is used in ${summarizeDependents(scan)}. ` +\n 'Those bindings will render as empty or literal tokens after ' +\n 'the next publish.'\n : `\"${variable.name}\" is not referenced by any published ` +\n 'screen, layout, or workflow.',\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.update(\n doc(firestore, 'hosts', hostId, 'variables', variable.$id),\n { deletedAt: Timestamp.now() },\n ),\n })\n enqueueSnackbar('Variable deleted', {\n variant: 'success',\n persist: false,\n })\n },\n [user, confirm, firestore, hostId, enqueueSnackbar],\n )\n\n return (\n <CardDisplay\n header={'Variables'}\n help={pluginDocsHelp('bindings', { anchor: '#typed-variables' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {variables.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Define typed values once and bind them anywhere with ' +\n '{{name}} — change the variable, every page updates on the ' +\n 'next publish.'}\n </Typography>\n ) : (\n visibleVariables.map((variable: any) => (\n <Stack\n key={variable.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`{{${variable.name}}}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`${HOST_VARIABLE_TYPE_LABELS[variable.type as HostVariableType] ?? variable.type} · ${\n formatVariableValue(variable as HostVariable) || '—'\n }`}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n disabled={usageLoading === variable.$id}\n onClick={handleShowUsage(variable)}\n >\n {usageLoading === variable.$id ? 'Scanning…' : 'Usage'}\n </Button>\n <Button\n size=\"small\"\n onClick={() =>\n setDraft({\n id: variable.$id,\n name: variable.name ?? '',\n type: variable.type ?? 'text',\n value: variable.value ?? '',\n workflowId: variable.workflowId ?? '',\n workflowName: variable.workflowName ?? '',\n })\n }\n >\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleDelete(variable)}\n >\n {'Delete'}\n </Button>\n </Stack>\n ))\n )}\n {variables.length === 0 ? null : (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={visibleVariables.length}\n // The variables the card HOLDS. Not `variableCount`, which is the\n // site's total including soft-deleted rows and answers the quota's\n // question rather than the reader's.\n count={variables.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n {variablesTruncated ? (\n <Alert severity=\"info\">\n {`Showing ${VARIABLE_CEILING} variables, ordered by id. This site ` +\n 'has more — the duplicate-name check below only covers the ' +\n 'ones listed here, so a name may already be taken by one that ' +\n 'is not.'}\n </Alert>\n ) : null}\n <Button\n size=\"small\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n // Plan cap (AGL-99): dark-launch — plan-less workspaces uncapped.\n // The site's variables, not this page of them (AGL-1716).\n const quota = checkQuota(org, 'variablesPerHost', variableCount)\n if (!quota.allowed) {\n return void enqueueSnackbar(\n `Variable limit reached (${quota.limit}) — upgrade in Billing`,\n { variant: 'warning', persist: false },\n )\n }\n setDraft({\n id: null,\n name: '',\n type: 'text',\n value: '',\n workflowId: '',\n workflowName: '',\n })\n }}\n >\n {'Add variable'}\n </Button>\n </Stack>\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {draft?.id ? 'Edit Variable' : 'Add Variable'}\n </DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <TextField\n label=\"Name\"\n helperText={\n nameTaken\n ? 'A variable with this name already exists'\n : 'Used to identify the variable'\n }\n error={(Boolean(draft?.name) && !validName) || nameTaken}\n value={draft?.name ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n name: event.target.value.replace(\n /[^a-zA-Z0-9_]/g,\n '',\n ),\n }\n : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n <TextField\n label=\"Type\"\n value={draft?.type ?? 'text'}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n type: event.target.value as HostVariableType,\n value: '',\n }\n : prev,\n )\n }\n size=\"small\"\n select\n >\n {Object.entries(HOST_VARIABLE_TYPE_LABELS).map(\n ([value, label]) => (\n <MenuItem key={value} value={value}>\n {label}\n </MenuItem>\n ),\n )}\n </TextField>\n {draft ? (\n <VariableValueField\n draft={draft}\n onChange={(value) =>\n setDraft((prev) => (prev ? { ...prev, value } : prev))\n }\n />\n ) : null}\n <TextField\n select\n label=\"Computed from workflow (optional)\"\n helperText={\n workflowOptionsTruncated\n ? `Showing ${WORKFLOW_OPTION_CEILING} workflows, ordered by ` +\n 'id. This site has more, so one of them is not offered here.'\n : 'Pick a workflow — its result becomes this ' +\n 'variable\\u2019s value at render; the value above is the ' +\n 'fallback (AGL-129)'\n }\n value={\n draft?.workflowId ||\n workflowOptions.find(\n (option) => option.name === draft?.workflowName,\n )?.id ||\n ''\n }\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n workflowId: event.target.value,\n workflowName:\n workflowOptions.find(\n (option) => option.id === event.target.value,\n )?.name ?? '',\n }\n : prev,\n )\n }\n size=\"small\"\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {workflowOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!validName || nameTaken}\n onClick={handleSave}\n >\n {'Save variable'}\n </Button>\n </DialogActions>\n </Dialog>\n <WhereUsedDialog\n hostId={hostId}\n usage={usage}\n onClose={() => setUsage(null)}\n />\n </CardDisplay>\n )\n}\nHostVariablesCard.displayName = 'HostVariablesCard'\n\nexport default HostVariablesCard\n"],"names":["checkQuota","formatVariableValue","HOST_VARIABLE_TYPE_LABELS","pluginDocsHelp","VARIABLE_NAME_PATTERN","ceilingedWindow","collectionCeiling","CardDisplay","useConfirmationContext","ListPagination","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Timestamp","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","Switch","TextField","Typography","collection","doc","getCountFromServer","useCallback","useEffect","useMemo","useState","useFirestore","useFirestoreCollection","useHostResourceApi","useUser","writeGuardedBySeed","writeSiteWideChange","WhereUsedDialog","fetchWhereUsed","summarizeDependents","VARIABLE_CEILING","WORKFLOW_OPTION_CEILING","VariableValueField","props","draft","onChange","type","direction","spacing","sx","alignItems","checked","value","event","target","variant","label","size","slotProps","inputLabel","shrink","replace","placeholder","multiline","minRows","HostVariablesCard","workflowOptions","hostId","firestore","data","user","createHostResource","enqueueSnackbar","confirm","org","usage","setUsage","usageLoading","setUsageLoading","variableDocs","status","variablesStatus","fromCache","variablesFromCache","idField","rows","readVariables","truncated","variablesTruncated","setDraft","editorOpened","setEditorOpened","workflowDocs","readWorkflows","workflowOptionsTruncated","filter","workflow","deletedAt","name","map","id","$id","sort","a","b","localeCompare","variables","variable","String","page","setPage","pageSize","setPageSize","visibleVariables","slice","variableCountEpoch","setVariableCountEpoch","serverVariableCount","setServerVariableCount","active","then","snapshot","count","catch","variableCount","length","validName","test","nameTaken","Boolean","some","toLowerCase","trim","handleSave","fields","workflowId","workflowName","verdict","subject","unreadable","write","batch","set","updatedAt","now","merge","ok","message","persist","resource","epoch","error","console","allowDuplicate","handleShowUsage","result","kind","handleDelete","scan","confirmed","title","description","total","confirmationText","confirmationButtonProps","color","update","header","help","anchor","contentGutterX","contentGutterY","flex","minWidth","noWrap","disabled","onClick","rowCount","onPageChange","onPageSizeChange","severity","alignSelf","quota","allowed","limit","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","helperText","prev","autoFocus","mt","select","Object","entries","find","option","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/components/host-variables-card.component.tsx"],"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'use client'\n\nimport {\n type AglynOrgBilling,\n checkQuota,\n formatVariableValue,\n HOST_VARIABLE_TYPE_LABELS,\n type HostVariable,\n type HostVariableType,\n pluginDocsHelp,\n VARIABLE_NAME_PATTERN,\n} from '@aglyn/aglyn'\n/*\n * The MODULE, not the barrel, for the two PURE helpers. Every spec that renders\n * this card mocks `@aglyn/tenant-feature-instance` wholesale to stage its\n * Firestore hooks, and a query builder imported through that barrel disappears\n * under the mock — which fails as \"not a function\" in the component rather than\n * as anything about the test's subject. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport {\n pluginRecordListQuery,\n pluginRecordsFromRows,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, getCountFromServer } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostResourceApi,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\nimport WhereUsedDialog from './where-used-dialog.component'\nimport {\n fetchWhereUsed,\n summarizeDependents,\n type WhereUsedResult,\n} from '@aglyn/aglyn/app-utils/where-used'\n\n/**\n * How many variables the card reads.\n *\n * A CEILING, not a page size — see the query, which explains why this list\n * cannot be paged by the server without making the duplicate-name check lie.\n */\nconst VARIABLE_CEILING = 100\n\n/**\n * How many workflows the computed-variable picker offers.\n *\n * Paid for only while the editor is open, which is what makes a ceiling this\n * size affordable: it is one reader mid-edit rather than every visitor to the\n * page.\n */\nconst WORKFLOW_OPTION_CEILING = 100\n\nexport interface HostVariablesCardProps {\n hostId: string\n /** Resolved entitlement source for quota checks (AGL-395). */\n org?: Partial<AglynOrgBilling>\n}\n\ninterface VariableDraft {\n /** Computed-source workflow doc id (AGL-261); name is the display hint. */\n workflowId: string\n id: string | null\n name: string\n type: HostVariableType\n value: string\n /** Workflow (by name) computing this variable at render (AGL-129). */\n workflowName: string\n}\n\n/** Per-type value editor: mirrors the mockup's type list. */\nfunction VariableValueField(props: {\n draft: VariableDraft\n onChange: (value: string) => void\n}) {\n const { draft, onChange } = props\n switch (draft.type) {\n case 'boolean':\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Switch\n checked={draft.value === 'true'}\n onChange={(event) =>\n onChange(event.target.checked ? 'true' : 'false')\n }\n />\n <Typography variant=\"body2\">\n {draft.value === 'true' ? 'True' : 'False'}\n </Typography>\n </Stack>\n )\n case 'date':\n case 'time':\n return (\n <TextField\n label=\"Value\"\n type={draft.type}\n value={draft.value}\n onChange={(event) => onChange(event.target.value)}\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n />\n )\n case 'number':\n return (\n <TextField\n label=\"Value\"\n value={draft.value}\n onChange={(event) =>\n onChange(event.target.value.replace(/[^0-9.eE+-]/g, ''))\n }\n size=\"small\"\n />\n )\n case 'dictionary':\n case 'collection':\n return (\n <TextField\n label=\"Value (JSON)\"\n placeholder={\n draft.type === 'dictionary' ? '{\"key\": \"value\"}' : '[\"a\", \"b\"]'\n }\n value={draft.value}\n onChange={(event) => onChange(event.target.value)}\n size=\"small\"\n multiline\n minRows={3}\n />\n )\n default:\n return (\n <TextField\n label=\"Value\"\n value={draft.value}\n onChange={(event) => onChange(event.target.value)}\n size=\"small\"\n multiline\n />\n )\n }\n}\n\n/**\n * Host variables (Component Builder, AGL-91): the mockup's Edit Variable\n * dialog — name + typed value — persisted to `hosts/{hostId}/variables`.\n * Any string prop can reference a variable with `{{name}}`; the org\n * compose pipeline resolves it at render. Soft delete keeps old tokens\n * rendering literally rather than breaking published pages.\n */\nexport function HostVariablesCard(props: HostVariablesCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const createHostResource = useHostResourceApi()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const { org } = props\n // Where-used dependents dialog (AGL-187).\n const [usage, setUsage] = useState<{\n name: string\n result: WhereUsedResult\n } | null>(null)\n const [usageLoading, setUsageLoading] = useState<string | null>(null)\n const {\n data: variableDocs,\n status: variablesStatus,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing copies a whole stored variable into `draft` and\n * writes all of it back, so `merge: true` protects nothing — every field\n * is in the payload. `value` is the literal every `{{name}}` binding on\n * every published page resolves to, and `workflowId` is what rebinds a\n * computed variable to its source (AGL-261), so a cached seed can point\n * a live binding back at a value or a workflow the author replaced.\n */\n fromCache: variablesFromCache,\n } = useFirestoreCollection<any>(\n /*\n * ORDERED AND CEILINGED, deliberately not paged by the query (AGL-2501).\n *\n * `limit(100)` alone is answered in DOCUMENT-ID order, so the window is a\n * pseudo-random hundred that the `localeCompare` below arranges into a\n * convincing A-to-Z list. `variablesPerHost` runs to 5,000, so a Scale\n * site with three hundred variables sees a hundred of them and nothing\n * said so.\n *\n * `collectionCeiling` does not change WHICH hundred — document-id order is\n * what the bare cap already returned. What it changes is that the order is\n * NAMED, so the obvious next edit is caught: ordering on `name` would not\n * mis-sort this list, it would HIDE from it every variable written without\n * one, and `/api/hosts/resources` validates no field for presence while\n * `IMPORTABLE_FIELDS.variables` copies a name only if the exported\n * document carried it. The document name cannot be absent.\n *\n * The QUERY is not paged because `nameTaken` below tests the draft against\n * these rows. Case-insensitive uniqueness is what keeps legacy `{{name}}`\n * token resolution unambiguous (AGL-185), and on a ten-row server page\n * that check would compare a new variable against a tenth of the site and\n * cheerfully create the duplicate it exists to prevent.\n *\n * The check is still only as complete as the ceiling, and that is now\n * SAID rather than assumed: the notice under the list appears exactly when\n * the probe finds a variable this card did not read.\n */\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'variables'),\n VARIABLE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: readVariables, truncated: variablesTruncated } =\n ceilingedWindow<any>(variableDocs, VARIABLE_CEILING)\n const [draft, setDraft] = useState<VariableDraft | null>(null)\n /**\n * The editor has been opened at least once in this session.\n *\n * A LATCH rather than `draft` itself, because the workflow picker below keys its\n * listeners on it. Tracking the dialog would tear that subscription down\n * on Cancel and pay for them again on the next Edit, so a merchant working\n * through ten variables would buy the same window ten times — worse than\n * the mount-time read this replaces. Latched, a reader who never edits pays\n * nothing and a reader who edits pays once.\n */\n const [editorOpened, setEditorOpened] = useState(false)\n if (draft && !editorOpened) setEditorOpened(true)\n /*\n * Workflow picker options (AGL-261): computed variables reference the\n * workflow by doc id instead of a typed name. The workflows are the\n * workflows plugin's, listed through the source it publishes (AGL-3080).\n *\n * READ WHEN THE EDITOR OPENS, not when the card mounts (AGL-2501). Nothing\n * outside the dialog below touches these rows — the table shows a computed\n * variable's stored `workflowName`, never a name looked up here — so an\n * unconditional listener would charge every visitor to this page for a\n * hundred workflows to fill a select most of them never see.\n *\n * Ceilinged and ordered for the same reason as the variables above: a bare\n * `limit` is answered in document-id order, so the `localeCompare` below\n * would arrange a pseudo-random hundred into a convincing alphabet and a\n * workflow past it could not be picked, with nothing saying why.\n */\n const { data: workflowDocs } = useFirestoreCollection<any>(\n () =>\n editorOpened\n ? pluginRecordListQuery('workflow', firestore, {\n hostId,\n limit: WORKFLOW_OPTION_CEILING + 1,\n })\n : null,\n [firestore, hostId, editorOpened],\n { idField: '$id' },\n )\n const { rows: readWorkflows, truncated: workflowOptionsTruncated } =\n ceilingedWindow<any>(workflowDocs, WORKFLOW_OPTION_CEILING)\n // Sorting the whole ceiling, not a page of it. The workflows plugin answers\n // live, named workflows only.\n const workflowOptions = pluginRecordsFromRows('workflow', readWorkflows)\n .map((workflow) => ({ id: workflow.id, name: workflow.name }))\n .sort((a, b) => a.name.localeCompare(b.name))\n /*\n * Sorting is safe HERE in a way it is not on a paged list: these rows are\n * the whole collection below the ceiling, not a slice of one, so the order\n * on screen is the order of everything the card holds.\n */\n const variables = useMemo(\n () =>\n readVariables\n .filter((variable: any) => !variable.deletedAt)\n .sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n ),\n [readVariables],\n )\n // The page is a SLICE, because the rows are already in hand and `nameTaken`\n // needs all of them. What the card lacked was not a cheaper read but a\n // control: a hundred variables rendered as one wall.\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const visibleVariables = useMemo(\n () => variables.slice(page * pageSize, page * pageSize + pageSize),\n [variables, page, pageSize],\n )\n /**\n * The variable HEAD-COUNT is a server aggregate, not the length of the\n * capped listener (AGL-1716, the AGL-1706 shape).\n *\n * The listener is `limit(100)` and `variables.length` was handed to\n * `checkQuota(org, 'variablesPerHost', …)`. The bands are 3 / 25 / 100 /\n * 1,000 / 5,000, so the window ties Pro's 100 and hides Business's 1,000\n * and Scale's 5,000 entirely: on those plans the gate compared 100 against\n * thousands and could never refuse, while `api/hosts/resources` counted\n * the collection and did. The card offered headroom that did not exist and\n * then failed the create.\n *\n * UNFILTERED, matching the enforcing route: it runs a plain\n * `collection('variables').count()` for this quota — its `softDeletes`\n * branch governs only the flat per-host webhook cap — so a soft-deleted\n * variable has always counted toward `variablesPerHost` server-side while\n * this card excluded it. The card now asks the route's question in the\n * route's terms, which is the only thing that makes a console gate a\n * useful prediction of the route's verdict.\n *\n * THE LIST KEEPS ITS CAP. Only a create moves the number (deletes here are\n * soft, and the route counts them), so that is where it is re-read.\n */\n const [variableCountEpoch, setVariableCountEpoch] = useState(0)\n const [serverVariableCount, setServerVariableCount] = useState<number | null>(\n null,\n )\n useEffect(() => {\n let active = true\n void getCountFromServer(collection(firestore, 'hosts', hostId, 'variables'))\n .then((snapshot) => {\n if (active) setServerVariableCount(snapshot.data().count)\n })\n .catch(() => {\n // Falls back to the loaded rows below — a LOWER bound, and this\n // card's prior behaviour. Deliberately not 0: `checkQuota` answers\n // from whatever it is handed, and 0 used is a confident wrong\n // number in the flattering direction.\n })\n return () => {\n active = false\n }\n }, [firestore, hostId, variableCountEpoch])\n const variableCount = serverVariableCount ?? variables.length\n\n const validName = VARIABLE_NAME_PATTERN.test(draft?.name ?? '')\n // Case-insensitive (AGL-185): names must stay unambiguous for legacy\n // {{name}} token resolution and picker display.\n const nameTaken = Boolean(\n draft &&\n variables.some(\n (variable: any) =>\n String(variable.name ?? '').toLowerCase() ===\n draft.name.trim().toLowerCase() && variable.$id !== draft.id,\n ),\n )\n\n const handleSave = useCallback(async () => {\n if (!draft || !validName || nameTaken) return\n const fields = {\n name: draft.name,\n type: draft.type,\n value: draft.value,\n workflowId: draft.workflowId.trim(),\n workflowName: draft.workflowName.trim(),\n }\n try {\n if (draft.id) {\n /**\n * Edit stays client-direct (no quota consumed) — and is refused when\n * its seed was never confirmed by the server (AGL-1358). `merge:\n * true` protects nothing here: `fields` is every editable key of the\n * stored row, copied off the listener whether or not it was touched.\n *\n * The guard WRAPS the write. An early return is a shape you can keep\n * while losing the protection; here the write is only reachable\n * through the verdict.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'variable',\n unreadable: variablesStatus === 'error',\n fromCache: variablesFromCache,\n },\n async () => {\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'variables', draft.id),\n { ...fields, updatedAt: Timestamp.now() },\n { merge: true },\n ),\n })\n },\n )\n // Before `setDraft(null)`, so a refusal keeps the dialog open with\n // what was typed. A save that silently does nothing sends the user\n // back to retype a form that will be refused again just as quietly.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n } else {\n // New variable rides the quota-enforcing resources API (AGL-473).\n await createHostResource({ hostId, resource: 'variable', data: fields })\n // A create moves the count and the aggregate is a one-shot — the\n // listener refreshes the ROWS for free, the count has to be asked\n // again or the cap drifts stale for the rest of the session.\n setVariableCountEpoch((epoch) => epoch + 1)\n }\n setDraft(null)\n enqueueSnackbar(\n `Saved — use {{${draft.name}}} in any text to bind it`,\n { variant: 'success', persist: false },\n )\n } catch (error: any) {\n console.error(error)\n enqueueSnackbar(error?.message ?? 'An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [\n user,\n draft,\n validName,\n nameTaken,\n firestore,\n hostId,\n createHostResource,\n enqueueSnackbar,\n variablesStatus,\n variablesFromCache,\n ])\n\n const handleShowUsage = useCallback(\n (variable: any) => async () => {\n setUsageLoading(variable.$id)\n const result = await fetchWhereUsed(user as any, {\n hostId,\n kind: 'variable',\n id: variable.$id,\n name: variable.name,\n })\n setUsageLoading(null)\n setUsage({ name: variable.name, result })\n },\n [user, hostId],\n )\n\n const handleDelete = useCallback(\n (variable: any) => async () => {\n // Real dependents warning (AGL-187) instead of the generic copy.\n const scan = await fetchWhereUsed(user as any, {\n hostId,\n kind: 'variable',\n id: variable.$id,\n name: variable.name,\n })\n const confirmed = await confirm({\n title: 'Delete this variable?',\n description: scan.total\n ? `\"${variable.name}\" is used in ${summarizeDependents(scan)}. ` +\n 'Those bindings will render as empty or literal tokens after ' +\n 'the next publish.'\n : `\"${variable.name}\" is not referenced by any published ` +\n 'page, layout, or workflow.',\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.update(\n doc(firestore, 'hosts', hostId, 'variables', variable.$id),\n { deletedAt: Timestamp.now() },\n ),\n })\n enqueueSnackbar('Variable deleted', {\n variant: 'success',\n persist: false,\n })\n },\n [user, confirm, firestore, hostId, enqueueSnackbar],\n )\n\n return (\n <CardDisplay\n header={'Variables'}\n help={pluginDocsHelp('bindings', { anchor: '#typed-variables' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {variables.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Define typed values once and bind them anywhere with ' +\n '{{name}} — change the variable, every page updates on the ' +\n 'next publish.'}\n </Typography>\n ) : (\n visibleVariables.map((variable: any) => (\n <Stack\n key={variable.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`{{${variable.name}}}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`${HOST_VARIABLE_TYPE_LABELS[variable.type as HostVariableType] ?? variable.type} · ${\n formatVariableValue(variable as HostVariable) || '—'\n }`}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n disabled={usageLoading === variable.$id}\n onClick={handleShowUsage(variable)}\n >\n {usageLoading === variable.$id ? 'Scanning…' : 'Usage'}\n </Button>\n <Button\n size=\"small\"\n onClick={() =>\n setDraft({\n id: variable.$id,\n name: variable.name ?? '',\n type: variable.type ?? 'text',\n value: variable.value ?? '',\n workflowId: variable.workflowId ?? '',\n workflowName: variable.workflowName ?? '',\n })\n }\n >\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={handleDelete(variable)}\n >\n {'Delete'}\n </Button>\n </Stack>\n ))\n )}\n {variables.length === 0 ? null : (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={visibleVariables.length}\n // The variables the card HOLDS. Not `variableCount`, which is the\n // site's total including soft-deleted rows and answers the quota's\n // question rather than the reader's.\n count={variables.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n {variablesTruncated ? (\n <Alert severity=\"info\">\n {`Showing ${VARIABLE_CEILING} variables, ordered by id. This site ` +\n 'has more — the duplicate-name check below only covers the ' +\n 'ones listed here, so a name may already be taken by one that ' +\n 'is not.'}\n </Alert>\n ) : null}\n <Button\n size=\"small\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n // Plan cap (AGL-99): dark-launch — plan-less workspaces uncapped.\n // The site's variables, not this page of them (AGL-1716).\n const quota = checkQuota(org, 'variablesPerHost', variableCount)\n if (!quota.allowed) {\n return void enqueueSnackbar(\n `Variable limit reached (${quota.limit}) — upgrade in Billing`,\n { variant: 'warning', persist: false },\n )\n }\n setDraft({\n id: null,\n name: '',\n type: 'text',\n value: '',\n workflowId: '',\n workflowName: '',\n })\n }}\n >\n {'Add variable'}\n </Button>\n </Stack>\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>\n {draft?.id ? 'Edit Variable' : 'Add Variable'}\n </DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <TextField\n label=\"Name\"\n helperText={\n nameTaken\n ? 'A variable with this name already exists'\n : 'Used to identify the variable'\n }\n error={(Boolean(draft?.name) && !validName) || nameTaken}\n value={draft?.name ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n name: event.target.value.replace(\n /[^a-zA-Z0-9_]/g,\n '',\n ),\n }\n : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n <TextField\n label=\"Type\"\n value={draft?.type ?? 'text'}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n type: event.target.value as HostVariableType,\n value: '',\n }\n : prev,\n )\n }\n size=\"small\"\n select\n >\n {Object.entries(HOST_VARIABLE_TYPE_LABELS).map(\n ([value, label]) => (\n <MenuItem key={value} value={value}>\n {label}\n </MenuItem>\n ),\n )}\n </TextField>\n {draft ? (\n <VariableValueField\n draft={draft}\n onChange={(value) =>\n setDraft((prev) => (prev ? { ...prev, value } : prev))\n }\n />\n ) : null}\n <TextField\n select\n label=\"Computed from workflow (optional)\"\n helperText={\n workflowOptionsTruncated\n ? `Showing ${WORKFLOW_OPTION_CEILING} workflows, ordered by ` +\n 'id. This site has more, so one of them is not offered here.'\n : 'Pick a workflow — its result becomes this ' +\n 'variable\\u2019s value at render; the value above is the ' +\n 'fallback (AGL-129)'\n }\n value={\n draft?.workflowId ||\n workflowOptions.find(\n (option) => option.name === draft?.workflowName,\n )?.id ||\n ''\n }\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n workflowId: event.target.value,\n workflowName:\n workflowOptions.find(\n (option) => option.id === event.target.value,\n )?.name ?? '',\n }\n : prev,\n )\n }\n size=\"small\"\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {workflowOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!validName || nameTaken}\n onClick={handleSave}\n >\n {'Save variable'}\n </Button>\n </DialogActions>\n </Dialog>\n <WhereUsedDialog\n hostId={hostId}\n usage={usage}\n onClose={() => setUsage(null)}\n />\n </CardDisplay>\n )\n}\nHostVariablesCard.displayName = 'HostVariablesCard'\n\nexport default HostVariablesCard\n"],"names":["checkQuota","formatVariableValue","HOST_VARIABLE_TYPE_LABELS","pluginDocsHelp","VARIABLE_NAME_PATTERN","ceilingedWindow","collectionCeiling","pluginRecordListQuery","pluginRecordsFromRows","CardDisplay","useConfirmationContext","ListPagination","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Timestamp","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","Switch","TextField","Typography","collection","doc","getCountFromServer","useCallback","useEffect","useMemo","useState","useFirestore","useFirestoreCollection","useHostResourceApi","useUser","writeGuardedBySeed","writeSiteWideChange","WhereUsedDialog","fetchWhereUsed","summarizeDependents","VARIABLE_CEILING","WORKFLOW_OPTION_CEILING","VariableValueField","props","draft","onChange","type","direction","spacing","sx","alignItems","checked","value","event","target","variant","label","size","slotProps","inputLabel","shrink","replace","placeholder","multiline","minRows","HostVariablesCard","workflowOptions","hostId","firestore","data","user","createHostResource","enqueueSnackbar","confirm","org","usage","setUsage","usageLoading","setUsageLoading","variableDocs","status","variablesStatus","fromCache","variablesFromCache","idField","rows","readVariables","truncated","variablesTruncated","setDraft","editorOpened","setEditorOpened","workflowDocs","limit","readWorkflows","workflowOptionsTruncated","map","workflow","id","name","sort","a","b","localeCompare","variables","filter","variable","deletedAt","String","page","setPage","pageSize","setPageSize","visibleVariables","slice","variableCountEpoch","setVariableCountEpoch","serverVariableCount","setServerVariableCount","active","then","snapshot","count","catch","variableCount","length","validName","test","nameTaken","Boolean","some","toLowerCase","trim","$id","handleSave","fields","workflowId","workflowName","verdict","subject","unreadable","write","batch","set","updatedAt","now","merge","ok","message","persist","resource","epoch","error","console","allowDuplicate","handleShowUsage","result","kind","handleDelete","scan","confirmed","title","description","total","confirmationText","confirmationButtonProps","color","update","header","help","anchor","contentGutterX","contentGutterY","flex","minWidth","noWrap","disabled","onClick","rowCount","onPageChange","onPageSizeChange","severity","alignSelf","quota","allowed","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","helperText","prev","autoFocus","mt","select","Object","entries","find","option","displayName"],"mappings":"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"}
|
|
@@ -47,7 +47,7 @@ const layoutBesignerHref = (hostId, layoutId, versionId)=>`${hostId}/layouts/${l
|
|
|
47
47
|
children: (usage == null ? void 0 : usage.result.total) === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
48
48
|
variant: "body2",
|
|
49
49
|
color: "text.secondary",
|
|
50
|
-
children: 'Not referenced by any published
|
|
50
|
+
children: 'Not referenced by any published page, layout, or workflow. ' + 'Unpublished drafts are not scanned.'
|
|
51
51
|
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
52
52
|
spacing: 1,
|
|
53
53
|
children: [
|
|
@@ -75,7 +75,7 @@ const layoutBesignerHref = (hostId, layoutId, versionId)=>`${hostId}/layouts/${l
|
|
|
75
75
|
/*#__PURE__*/ _jsx(Typography, {
|
|
76
76
|
variant: "caption",
|
|
77
77
|
color: "text.secondary",
|
|
78
|
-
children: dependent.type + (dependent.via.includes('name') ? ' · legacy token' : '')
|
|
78
|
+
children: (dependent.type === 'screen' ? 'page' : dependent.type) + (dependent.via.includes('name') ? ' · legacy token' : '')
|
|
79
79
|
})
|
|
80
80
|
]
|
|
81
81
|
}, `${dependent.type}-${dependent.id}`);
|
|
@@ -83,7 +83,7 @@ const layoutBesignerHref = (hostId, layoutId, versionId)=>`${hostId}/layouts/${l
|
|
|
83
83
|
(usage == null ? void 0 : usage.result.legacyCount) ? /*#__PURE__*/ _jsx(Typography, {
|
|
84
84
|
variant: "caption",
|
|
85
85
|
color: "warning.main",
|
|
86
|
-
children: 'Legacy tokens reference this by name and break if it is ' + 'renamed — re-publish those
|
|
86
|
+
children: 'Legacy tokens reference this by name and break if it is ' + 'renamed — re-publish those pages to upgrade them.'
|
|
87
87
|
}) : null
|
|
88
88
|
]
|
|
89
89
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/components/where-used-dialog.component.tsx"],"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'use client'\n\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { useConsoleHostRoute } from '@aglyn/tenant-feature-instance'\nimport type { WhereUsedResult } from '@aglyn/aglyn/app-utils/where-used'\n\n/**\n * Besigner deep-links for a scanned screen/layout version. The routes live\n * in the console app's table; the patterns are stable, so the plugin builds\n * them directly rather than importing app-only route constants.\n */\nconst screenBesignerHref = (\n // Console route base, not a host doc id (AGL-673).\n hostId: string,\n screenId: string,\n versionId: string,\n) => `${hostId}/screens/${screenId}/versions/${versionId}/besigner`\nconst layoutBesignerHref = (\n hostId: string,\n layoutId: string,\n versionId: string,\n) => `${hostId}/layouts/${layoutId}/versions/${versionId}/besigner`\n\nexport interface WhereUsedDialogProps {\n hostId: string\n usage: { name: string; result: WhereUsedResult } | null\n onClose: () => void\n}\n\n/**\n * Shared dependents dialog (AGL-187/193): where a variable, function, or\n * workflow is referenced — screen/layout rows deep-link into the besigner\n * of the scanned published version; legacy-token references are flagged\n * since renames break them.\n */\nexport function WhereUsedDialog(props: WhereUsedDialogProps) {\n const { hostId, usage, onClose } = props\n const consoleRoute = useConsoleHostRoute(hostId)\n const base = consoleRoute.base\n return (\n <Dialog open={Boolean(usage)} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{`Where \"${usage?.name}\" is used`}</DialogTitle>\n <DialogContent>\n {usage?.result.total === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Not referenced by any published
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/components/where-used-dialog.component.tsx"],"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'use client'\n\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { useConsoleHostRoute } from '@aglyn/tenant-feature-instance'\nimport type { WhereUsedResult } from '@aglyn/aglyn/app-utils/where-used'\n\n/**\n * Besigner deep-links for a scanned screen/layout version. The routes live\n * in the console app's table; the patterns are stable, so the plugin builds\n * them directly rather than importing app-only route constants.\n */\nconst screenBesignerHref = (\n // Console route base, not a host doc id (AGL-673).\n hostId: string,\n screenId: string,\n versionId: string,\n) => `${hostId}/screens/${screenId}/versions/${versionId}/besigner`\nconst layoutBesignerHref = (\n hostId: string,\n layoutId: string,\n versionId: string,\n) => `${hostId}/layouts/${layoutId}/versions/${versionId}/besigner`\n\nexport interface WhereUsedDialogProps {\n hostId: string\n usage: { name: string; result: WhereUsedResult } | null\n onClose: () => void\n}\n\n/**\n * Shared dependents dialog (AGL-187/193): where a variable, function, or\n * workflow is referenced — screen/layout rows deep-link into the besigner\n * of the scanned published version; legacy-token references are flagged\n * since renames break them.\n */\nexport function WhereUsedDialog(props: WhereUsedDialogProps) {\n const { hostId, usage, onClose } = props\n const consoleRoute = useConsoleHostRoute(hostId)\n const base = consoleRoute.base\n return (\n <Dialog open={Boolean(usage)} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{`Where \"${usage?.name}\" is used`}</DialogTitle>\n <DialogContent>\n {usage?.result.total === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Not referenced by any published page, layout, or workflow. ' +\n 'Unpublished drafts are not scanned.'}\n </Typography>\n ) : (\n <Stack spacing={1}>\n {usage?.result.dependents.map((dependent) => {\n // No resolved base means no valid link — the branch below\n // already renders plain text, which beats a link to nowhere.\n const href =\n base && dependent.versionId && dependent.type === 'screen'\n ? screenBesignerHref(base, dependent.id, dependent.versionId)\n : base && dependent.versionId && dependent.type === 'layout'\n ? layoutBesignerHref(base, dependent.id, dependent.versionId)\n : null\n return (\n <Stack\n key={`${dependent.type}-${dependent.id}`}\n direction=\"row\"\n spacing={1}\n sx={{ justifyContent: 'space-between' }}\n >\n {href ? (\n <AppLink href={href} variant=\"body2\" noWrap>\n {dependent.name}\n </AppLink>\n ) : (\n <Typography variant=\"body2\" noWrap>\n {dependent.name}\n </Typography>\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {/* The stored type stays `screen`; a person reads \"page\". */}\n {(dependent.type === 'screen' ? 'page' : dependent.type) +\n (dependent.via.includes('name') ? ' · legacy token' : '')}\n </Typography>\n </Stack>\n )\n })}\n {usage?.result.legacyCount ? (\n <Typography variant=\"caption\" color=\"warning.main\">\n {'Legacy tokens reference this by name and break if it is ' +\n 'renamed — re-publish those pages to upgrade them.'}\n </Typography>\n ) : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Close'}</Button>\n </DialogActions>\n </Dialog>\n )\n}\nWhereUsedDialog.displayName = 'WhereUsedDialog'\n\nexport default WhereUsedDialog\n"],"names":["Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","AppLink","useConsoleHostRoute","screenBesignerHref","hostId","screenId","versionId","layoutBesignerHref","layoutId","WhereUsedDialog","props","usage","onClose","consoleRoute","base","open","Boolean","maxWidth","fullWidth","name","result","total","variant","color","spacing","dependents","map","dependent","href","type","id","direction","sx","justifyContent","noWrap","via","includes","legacyCount","onClick","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SACEA,MAAM,EACNC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,KAAK,EACLC,UAAU,QACL,gBAAe;AACtB,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SAASC,mBAAmB,QAAQ,iCAAgC;AAGpE;;;;CAIC,GACD,MAAMC,qBAAqB,CACzB,mDAAmD;AACnDC,QACAC,UACAC,YACG,GAAGF,OAAO,SAAS,EAAEC,SAAS,UAAU,EAAEC,UAAU,SAAS,CAAC;AACnE,MAAMC,qBAAqB,CACzBH,QACAI,UACAF,YACG,GAAGF,OAAO,SAAS,EAAEI,SAAS,UAAU,EAAEF,UAAU,SAAS,CAAC;AAQnE;;;;;CAKC,GACD,OAAO,SAASG,gBAAgBC,KAA2B;IACzD,MAAM,EAAEN,MAAM,EAAEO,KAAK,EAAEC,OAAO,EAAE,GAAGF;IACnC,MAAMG,eAAeX,oBAAoBE;IACzC,MAAMU,OAAOD,aAAaC,IAAI;IAC9B,qBACE,MAACnB;QAAOoB,MAAMC,QAAQL;QAAQC,SAASA;QAASK,UAAS;QAAKC,SAAS;;0BACrE,KAACpB;0BAAa,CAAC,OAAO,EAAEa,yBAAAA,MAAOQ,IAAI,CAAC,SAAS,CAAC;;0BAC9C,KAACtB;0BACEc,CAAAA,yBAAAA,MAAOS,MAAM,CAACC,KAAK,MAAK,kBACvB,KAACrB;oBAAWsB,SAAQ;oBAAQC,OAAM;8BAC/B,gEACC;mCAGJ,MAACxB;oBAAMyB,SAAS;;wBACbb,yBAAAA,MAAOS,MAAM,CAACK,UAAU,CAACC,GAAG,CAAC,CAACC;4BAC7B,0DAA0D;4BAC1D,6DAA6D;4BAC7D,MAAMC,OACJd,QAAQa,UAAUrB,SAAS,IAAIqB,UAAUE,IAAI,KAAK,WAC9C1B,mBAAmBW,MAAMa,UAAUG,EAAE,EAAEH,UAAUrB,SAAS,IAC1DQ,QAAQa,UAAUrB,SAAS,IAAIqB,UAAUE,IAAI,KAAK,WAChDtB,mBAAmBO,MAAMa,UAAUG,EAAE,EAAEH,UAAUrB,SAAS,IAC1D;4BACR,qBACE,MAACP;gCAECgC,WAAU;gCACVP,SAAS;gCACTQ,IAAI;oCAAEC,gBAAgB;gCAAgB;;oCAErCL,qBACC,KAAC3B;wCAAQ2B,MAAMA;wCAAMN,SAAQ;wCAAQY,MAAM;kDACxCP,UAAUR,IAAI;uDAGjB,KAACnB;wCAAWsB,SAAQ;wCAAQY,MAAM;kDAC/BP,UAAUR,IAAI;;kDAGnB,KAACnB;wCAAWsB,SAAQ;wCAAUC,OAAM;kDAEjC,AAACI,CAAAA,UAAUE,IAAI,KAAK,WAAW,SAASF,UAAUE,IAAI,AAAD,IACnDF,CAAAA,UAAUQ,GAAG,CAACC,QAAQ,CAAC,UAAU,oBAAoB,EAAC;;;+BAjBtD,GAAGT,UAAUE,IAAI,CAAC,CAAC,EAAEF,UAAUG,EAAE,EAAE;wBAqB9C;wBACCnB,CAAAA,yBAAAA,MAAOS,MAAM,CAACiB,WAAW,kBACxB,KAACrC;4BAAWsB,SAAQ;4BAAUC,OAAM;sCACjC,6DACC;6BAEF;;;;0BAIV,KAAC3B;0BACC,cAAA,KAACF;oBAAO4C,SAAS1B;8BAAU;;;;;AAInC;AACAH,gBAAgB8B,WAAW,GAAG;AAE9B,eAAe9B,gBAAe"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The plugin's SERVER declarations: what a server process must know before
|
|
19
|
+
* any surface of the plugin loads.
|
|
20
|
+
*
|
|
21
|
+
* The variables computed from a workflow, which the "Used by" scan asks for
|
|
22
|
+
* when somebody is about to rename or delete one. The reader and the Admin
|
|
23
|
+
* SDK arrive with the first question, not with the boot.
|
|
24
|
+
*/
|
|
25
|
+
export declare function registerLogicServerDeclarations(): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
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 { registerPluginDependentsSource } from "@aglyn/aglyn/plugin-manager/plugin-dependents";
|
|
17
|
+
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
18
|
+
/**
|
|
19
|
+
* The plugin's SERVER declarations: what a server process must know before
|
|
20
|
+
* any surface of the plugin loads.
|
|
21
|
+
*
|
|
22
|
+
* The variables computed from a workflow, which the "Used by" scan asks for
|
|
23
|
+
* when somebody is about to rename or delete one. The reader and the Admin
|
|
24
|
+
* SDK arrive with the first question, not with the boot.
|
|
25
|
+
*/ export function registerLogicServerDeclarations() {
|
|
26
|
+
registerPluginDependentsSource({
|
|
27
|
+
kinds: [
|
|
28
|
+
'workflow'
|
|
29
|
+
],
|
|
30
|
+
find: async (request)=>(await import("./server/variable-dependents.js")).findWorkflowDependents(request)
|
|
31
|
+
}, {
|
|
32
|
+
pluginId: BUNDLE_ID
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=declarations.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/logic/src/lib/declarations.server.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 { registerPluginDependentsSource } from '@aglyn/aglyn/plugin-manager/plugin-dependents'\nimport { BUNDLE_ID } from './constants/bundle-common'\n\n/**\n * The plugin's SERVER declarations: what a server process must know before\n * any surface of the plugin loads.\n *\n * The variables computed from a workflow, which the \"Used by\" scan asks for\n * when somebody is about to rename or delete one. The reader and the Admin\n * SDK arrive with the first question, not with the boot.\n */\nexport function registerLogicServerDeclarations(): void {\n registerPluginDependentsSource(\n {\n kinds: ['workflow'],\n find: async (request) =>\n (await import('./server/variable-dependents')).findWorkflowDependents(request),\n },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["registerPluginDependentsSource","BUNDLE_ID","registerLogicServerDeclarations","kinds","find","request","findWorkflowDependents","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,8BAA8B,QAAQ,gDAA+C;AAC9F,SAASC,SAAS,QAAQ,+BAA2B;AAErD;;;;;;;CAOC,GACD,OAAO,SAASC;IACdF,+BACE;QACEG,OAAO;YAAC;SAAW;QACnBC,MAAM,OAAOC,UACX,AAAC,CAAA,MAAM,MAAM,CAAC,kCAA8B,EAAGC,sBAAsB,CAACD;IAC1E,GACA;QAAEE,UAAUN;IAAU;AAE1B"}
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { HostAction } from '@aglyn/aglyn';
|
|
18
18
|
import type { HostVariable } from '@aglyn/aglyn';
|
|
19
|
-
import type { HostWorkflow } from '@aglyn/aglyn';
|
|
20
19
|
/**
|
|
21
20
|
* Reference-integrity audit (wave v7): id references (AGL-261) are
|
|
22
21
|
* rename-safe but not delete-safe — deleting a workflow leaves actions
|
|
@@ -44,16 +43,33 @@ export interface ScreenAuditEntry {
|
|
|
44
43
|
props?: Record<string, unknown>;
|
|
45
44
|
} | undefined>;
|
|
46
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* A stored workflow, as far as the audit reads one: its name and the function
|
|
48
|
+
* each step calls, by id and by the name it had when the step was written.
|
|
49
|
+
*/
|
|
50
|
+
export interface AuditedWorkflow {
|
|
51
|
+
$id: string;
|
|
52
|
+
name?: string;
|
|
53
|
+
steps?: Array<{
|
|
54
|
+
functionId?: string;
|
|
55
|
+
functionName?: string;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A site variable as the audit reads one: computed variables also carry the
|
|
60
|
+
* workflow that computes them, by id and by the name it had when picked.
|
|
61
|
+
*/
|
|
62
|
+
export interface AuditedVariable extends HostVariable {
|
|
63
|
+
$id: string;
|
|
64
|
+
workflowId?: string;
|
|
65
|
+
workflowName?: string;
|
|
66
|
+
}
|
|
47
67
|
export interface ReferenceAuditInput {
|
|
48
68
|
actions?: Array<HostAction & {
|
|
49
69
|
$id: string;
|
|
50
70
|
}>;
|
|
51
|
-
workflows?:
|
|
52
|
-
|
|
53
|
-
}>;
|
|
54
|
-
variables?: Array<HostVariable & {
|
|
55
|
-
$id: string;
|
|
56
|
-
}>;
|
|
71
|
+
workflows?: AuditedWorkflow[];
|
|
72
|
+
variables?: AuditedVariable[];
|
|
57
73
|
/** Screen node maps to scan for entity/screen-link references. */
|
|
58
74
|
screens?: ScreenAuditEntry[];
|
|
59
75
|
/** Known-good targets: doc ids AND display names both resolve. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/model/reference-audit.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 { HostAction } from '@aglyn/aglyn'\nimport type { HostVariable } from '@aglyn/aglyn'\nimport type { HostWorkflow } from '@aglyn/aglyn'\n\n/**\n * Reference-integrity audit (wave v7): id references (AGL-261) are\n * rename-safe but not delete-safe — deleting a workflow leaves actions\n * pointing at nothing, and the executors fail silently at run time.\n * This pure audit cross-checks every reference an automation, workflow,\n * or computed variable holds against the ids/names that still exist,\n * so the console can surface broken wiring before a visitor hits it.\n */\nexport interface ReferenceIssue {\n /** What holds the broken reference. */\n source: 'action' | 'workflow' | 'variable' | 'screen'\n sourceId: string\n /** Display name of the holder. */\n sourceName: string\n /** What the reference points at (\"workflow\", \"dataset\", …). */\n refType: string\n /** The dangling id or name as stored. */\n missing: string\n}\n\n/** A screen's composed nodes, for canvas-reference auditing (AGL-345). */\nexport interface ScreenAuditEntry {\n $id: string\n name?: string\n nodes: Record<string, { props?: Record<string, unknown> } | undefined>\n}\n\nexport interface ReferenceAuditInput {\n actions?: Array<HostAction & { $id: string }>\n workflows?: Array<HostWorkflow & { $id: string }>\n variables?: Array<HostVariable & { $id: string }>\n /** Screen node maps to scan for entity/screen-link references. */\n screens?: ScreenAuditEntry[]\n /** Known-good targets: doc ids AND display names both resolve. */\n known: {\n workflows?: Set<string>\n functions?: Set<string>\n datasets?: Set<string>\n lists?: Set<string>\n campaigns?: Set<string>\n overlays?: Set<string>\n webhooks?: Set<string>\n /** New reference kinds (AGL-345). */\n screens?: Set<string>\n products?: Set<string>\n collections?: Set<string>\n categories?: Set<string>\n }\n}\n\nconst resolves = (\n known: Set<string> | undefined,\n id?: string,\n name?: string,\n): boolean => {\n const cleanId = id?.trim()\n const cleanName = name?.trim()\n if (!cleanId && !cleanName) return true // Nothing referenced.\n if (!known) return true // Target kind not loaded — don't cry wolf.\n return Boolean(\n (cleanId && known.has(cleanId)) || (cleanName && known.has(cleanName)),\n )\n}\n\nexport function auditHostReferences(\n input: ReferenceAuditInput,\n): ReferenceIssue[] {\n const issues: ReferenceIssue[] = []\n const { known } = input\n\n for (const action of input.actions ?? []) {\n const push = (refType: string, missing: string) =>\n issues.push({\n source: 'action',\n sourceId: action.$id,\n sourceName: action.name ?? action.$id,\n refType,\n missing,\n })\n for (const step of action.steps ?? []) {\n if (step.type === 'runWorkflow') {\n if (!resolves(known.workflows, step.workflowId, step.workflowName)) {\n push('workflow', step.workflowId || step.workflowName || '')\n }\n } else if (\n step.type === 'datasetAppend' ||\n step.type === 'updateDataset'\n ) {\n if (!resolves(known.datasets, step.datasetId, step.datasetName)) {\n push('dataset', step.datasetId || step.datasetName || '')\n }\n } else if (step.type === 'enrollList') {\n if (!resolves(known.lists, step.listId, step.listName)) {\n push('list', step.listId || step.listName || '')\n }\n } else if (step.type === 'assignCampaign') {\n if (!resolves(known.campaigns, step.campaignId, step.campaignName)) {\n push('campaign', step.campaignId || step.campaignName || '')\n }\n } else if (step.type === 'showOverlay') {\n if (!resolves(known.overlays, step.overlayId, step.overlayName)) {\n push('overlay', step.overlayId || step.overlayName || '')\n }\n } else if (step.type === 'webhookPost') {\n if (!resolves(known.webhooks, step.webhookId, step.webhookName)) {\n push('webhook', step.webhookId || step.webhookName || '')\n }\n } else if (step.type === 'redirect') {\n // Screen-id redirects (AGL-339) go stale when the screen is\n // deleted/unpublished.\n if (!resolves(known.screens, (step as any).screenId)) {\n push('screen', (step as any).screenId || '')\n }\n }\n }\n }\n\n for (const workflow of input.workflows ?? []) {\n for (const step of workflow.steps ?? []) {\n if (!resolves(known.functions, step.functionId, step.functionName)) {\n issues.push({\n source: 'workflow',\n sourceId: workflow.$id,\n sourceName: workflow.name ?? workflow.$id,\n refType: 'function',\n missing: step.functionId || step.functionName || '',\n })\n }\n }\n }\n\n for (const variable of input.variables ?? []) {\n if (!resolves(known.workflows, variable.workflowId, variable.workflowName)) {\n issues.push({\n source: 'variable',\n sourceId: variable.$id,\n sourceName: variable.name ?? variable.$id,\n refType: 'workflow',\n missing: variable.workflowId || variable.workflowName || '',\n })\n }\n }\n\n // Canvas node references (AGL-345): screen links, commerce entities,\n // and repeat datasets persisted in component props.\n const NODE_PROP_TARGETS: Array<{\n prop: string\n refType: string\n known: Set<string> | undefined\n }> = [\n { prop: 'screenId', refType: 'screen', known: known.screens },\n { prop: 'productId', refType: 'product', known: known.products },\n { prop: 'collectionId', refType: 'collection', known: known.collections },\n { prop: 'categoryId', refType: 'category', known: known.categories },\n { prop: 'repeatDataset', refType: 'dataset', known: known.datasets },\n ]\n for (const screen of input.screens ?? []) {\n for (const node of Object.values(screen.nodes ?? {})) {\n const props = node?.props ?? {}\n for (const target of NODE_PROP_TARGETS) {\n const value = props[target.prop]\n if (typeof value !== 'string' || !value.trim()) continue\n if (!resolves(target.known, value)) {\n issues.push({\n source: 'screen',\n sourceId: screen.$id,\n sourceName: screen.name ?? screen.$id,\n refType: target.refType,\n missing: value,\n })\n }\n }\n }\n }\n\n return issues\n}\n"],"names":["resolves","known","id","name","cleanId","trim","cleanName","Boolean","has","auditHostReferences","input","issues","action","actions","push","refType","missing","source","sourceId","$id","sourceName","step","steps","type","workflows","workflowId","workflowName","datasets","datasetId","datasetName","lists","listId","listName","campaigns","campaignId","campaignName","overlays","overlayId","overlayName","webhooks","webhookId","webhookName","screens","screenId","workflow","functions","functionId","functionName","variable","variables","NODE_PROP_TARGETS","prop","products","collections","categories","screen","node","Object","values","nodes","props","target","value"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/model/reference-audit.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 { HostAction } from '@aglyn/aglyn'\nimport type { HostVariable } from '@aglyn/aglyn'\n\n/**\n * Reference-integrity audit (wave v7): id references (AGL-261) are\n * rename-safe but not delete-safe — deleting a workflow leaves actions\n * pointing at nothing, and the executors fail silently at run time.\n * This pure audit cross-checks every reference an automation, workflow,\n * or computed variable holds against the ids/names that still exist,\n * so the console can surface broken wiring before a visitor hits it.\n */\nexport interface ReferenceIssue {\n /** What holds the broken reference. */\n source: 'action' | 'workflow' | 'variable' | 'screen'\n sourceId: string\n /** Display name of the holder. */\n sourceName: string\n /** What the reference points at (\"workflow\", \"dataset\", …). */\n refType: string\n /** The dangling id or name as stored. */\n missing: string\n}\n\n/** A screen's composed nodes, for canvas-reference auditing (AGL-345). */\nexport interface ScreenAuditEntry {\n $id: string\n name?: string\n nodes: Record<string, { props?: Record<string, unknown> } | undefined>\n}\n\n/**\n * A stored workflow, as far as the audit reads one: its name and the function\n * each step calls, by id and by the name it had when the step was written.\n */\nexport interface AuditedWorkflow {\n $id: string\n name?: string\n steps?: Array<{ functionId?: string; functionName?: string }>\n}\n\n/**\n * A site variable as the audit reads one: computed variables also carry the\n * workflow that computes them, by id and by the name it had when picked.\n */\nexport interface AuditedVariable extends HostVariable {\n $id: string\n workflowId?: string\n workflowName?: string\n}\n\nexport interface ReferenceAuditInput {\n actions?: Array<HostAction & { $id: string }>\n workflows?: AuditedWorkflow[]\n variables?: AuditedVariable[]\n /** Screen node maps to scan for entity/screen-link references. */\n screens?: ScreenAuditEntry[]\n /** Known-good targets: doc ids AND display names both resolve. */\n known: {\n workflows?: Set<string>\n functions?: Set<string>\n datasets?: Set<string>\n lists?: Set<string>\n campaigns?: Set<string>\n overlays?: Set<string>\n webhooks?: Set<string>\n /** New reference kinds (AGL-345). */\n screens?: Set<string>\n products?: Set<string>\n collections?: Set<string>\n categories?: Set<string>\n }\n}\n\nconst resolves = (\n known: Set<string> | undefined,\n id?: string,\n name?: string,\n): boolean => {\n const cleanId = id?.trim()\n const cleanName = name?.trim()\n if (!cleanId && !cleanName) return true // Nothing referenced.\n if (!known) return true // Target kind not loaded — don't cry wolf.\n return Boolean(\n (cleanId && known.has(cleanId)) || (cleanName && known.has(cleanName)),\n )\n}\n\nexport function auditHostReferences(\n input: ReferenceAuditInput,\n): ReferenceIssue[] {\n const issues: ReferenceIssue[] = []\n const { known } = input\n\n for (const action of input.actions ?? []) {\n const push = (refType: string, missing: string) =>\n issues.push({\n source: 'action',\n sourceId: action.$id,\n sourceName: action.name ?? action.$id,\n refType,\n missing,\n })\n for (const step of action.steps ?? []) {\n if (step.type === 'runWorkflow') {\n if (!resolves(known.workflows, step.workflowId, step.workflowName)) {\n push('workflow', step.workflowId || step.workflowName || '')\n }\n } else if (\n step.type === 'datasetAppend' ||\n step.type === 'updateDataset'\n ) {\n if (!resolves(known.datasets, step.datasetId, step.datasetName)) {\n push('dataset', step.datasetId || step.datasetName || '')\n }\n } else if (step.type === 'enrollList') {\n if (!resolves(known.lists, step.listId, step.listName)) {\n push('list', step.listId || step.listName || '')\n }\n } else if (step.type === 'assignCampaign') {\n if (!resolves(known.campaigns, step.campaignId, step.campaignName)) {\n push('campaign', step.campaignId || step.campaignName || '')\n }\n } else if (step.type === 'showOverlay') {\n if (!resolves(known.overlays, step.overlayId, step.overlayName)) {\n push('overlay', step.overlayId || step.overlayName || '')\n }\n } else if (step.type === 'webhookPost') {\n if (!resolves(known.webhooks, step.webhookId, step.webhookName)) {\n push('webhook', step.webhookId || step.webhookName || '')\n }\n } else if (step.type === 'redirect') {\n // Screen-id redirects (AGL-339) go stale when the screen is\n // deleted/unpublished.\n if (!resolves(known.screens, (step as any).screenId)) {\n push('screen', (step as any).screenId || '')\n }\n }\n }\n }\n\n for (const workflow of input.workflows ?? []) {\n for (const step of workflow.steps ?? []) {\n if (!resolves(known.functions, step.functionId, step.functionName)) {\n issues.push({\n source: 'workflow',\n sourceId: workflow.$id,\n sourceName: workflow.name ?? workflow.$id,\n refType: 'function',\n missing: step.functionId || step.functionName || '',\n })\n }\n }\n }\n\n for (const variable of input.variables ?? []) {\n if (!resolves(known.workflows, variable.workflowId, variable.workflowName)) {\n issues.push({\n source: 'variable',\n sourceId: variable.$id,\n sourceName: variable.name ?? variable.$id,\n refType: 'workflow',\n missing: variable.workflowId || variable.workflowName || '',\n })\n }\n }\n\n // Canvas node references (AGL-345): screen links, commerce entities,\n // and repeat datasets persisted in component props.\n const NODE_PROP_TARGETS: Array<{\n prop: string\n refType: string\n known: Set<string> | undefined\n }> = [\n { prop: 'screenId', refType: 'screen', known: known.screens },\n { prop: 'productId', refType: 'product', known: known.products },\n { prop: 'collectionId', refType: 'collection', known: known.collections },\n { prop: 'categoryId', refType: 'category', known: known.categories },\n { prop: 'repeatDataset', refType: 'dataset', known: known.datasets },\n ]\n for (const screen of input.screens ?? []) {\n for (const node of Object.values(screen.nodes ?? {})) {\n const props = node?.props ?? {}\n for (const target of NODE_PROP_TARGETS) {\n const value = props[target.prop]\n if (typeof value !== 'string' || !value.trim()) continue\n if (!resolves(target.known, value)) {\n issues.push({\n source: 'screen',\n sourceId: screen.$id,\n sourceName: screen.name ?? screen.$id,\n refType: target.refType,\n missing: value,\n })\n }\n }\n }\n }\n\n return issues\n}\n"],"names":["resolves","known","id","name","cleanId","trim","cleanName","Boolean","has","auditHostReferences","input","issues","action","actions","push","refType","missing","source","sourceId","$id","sourceName","step","steps","type","workflows","workflowId","workflowName","datasets","datasetId","datasetName","lists","listId","listName","campaigns","campaignId","campaignName","overlays","overlayId","overlayName","webhooks","webhookId","webhookName","screens","screenId","workflow","functions","functionId","functionName","variable","variables","NODE_PROP_TARGETS","prop","products","collections","categories","screen","node","Object","values","nodes","props","target","value"],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
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 { PluginDependentsAnswer, PluginDependentsRequest } from '@aglyn/aglyn/plugin-manager/plugin-dependents';
|
|
18
|
+
/**
|
|
19
|
+
* Which of a site's variables are computed from a workflow, for the "Used by"
|
|
20
|
+
* scan — what the workflows plugin asks before a workflow is renamed or
|
|
21
|
+
* deleted, since each of these falls back to its stored value once the
|
|
22
|
+
* workflow is gone.
|
|
23
|
+
*
|
|
24
|
+
* A computed variable names its workflow by document id and, as a display
|
|
25
|
+
* hint, by the name it had when it was picked (AGL-261); one computed before
|
|
26
|
+
* ids existed carries the name alone. The id survives a rename, the name does
|
|
27
|
+
* not. A deleted variable is left out.
|
|
28
|
+
*/
|
|
29
|
+
/** How many of a site's variables one scan reads. */
|
|
30
|
+
export declare const WORKFLOW_DEPENDENTS_VARIABLES_READ = 100;
|
|
31
|
+
export declare function findWorkflowDependents(request: PluginDependentsRequest): Promise<PluginDependentsAnswer>;
|
|
@@ -0,0 +1,60 @@
|
|
|
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 } from "@aglyn/tenant-data-admin/server/firebase-admin";
|
|
17
|
+
/**
|
|
18
|
+
* Which of a site's variables are computed from a workflow, for the "Used by"
|
|
19
|
+
* scan — what the workflows plugin asks before a workflow is renamed or
|
|
20
|
+
* deleted, since each of these falls back to its stored value once the
|
|
21
|
+
* workflow is gone.
|
|
22
|
+
*
|
|
23
|
+
* A computed variable names its workflow by document id and, as a display
|
|
24
|
+
* hint, by the name it had when it was picked (AGL-261); one computed before
|
|
25
|
+
* ids existed carries the name alone. The id survives a rename, the name does
|
|
26
|
+
* not. A deleted variable is left out.
|
|
27
|
+
*/ /** How many of a site's variables one scan reads. */ export const WORKFLOW_DEPENDENTS_VARIABLES_READ = 100;
|
|
28
|
+
const text = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
29
|
+
export async function findWorkflowDependents(request) {
|
|
30
|
+
const id = request.id.trim();
|
|
31
|
+
const name = text(request.name);
|
|
32
|
+
if (!request.hostId || !id && !name) return {
|
|
33
|
+
dependents: [],
|
|
34
|
+
truncated: false
|
|
35
|
+
};
|
|
36
|
+
// One more than is read, so `truncated` is a fact rather than a guess.
|
|
37
|
+
const snapshot = await firebaseAdmin.app().firestore().collection('hosts').doc(request.hostId).collection('variables').limit(WORKFLOW_DEPENDENTS_VARIABLES_READ + 1).get();
|
|
38
|
+
const dependents = [];
|
|
39
|
+
for (const doc of snapshot.docs.slice(0, WORKFLOW_DEPENDENTS_VARIABLES_READ)){
|
|
40
|
+
var _doc_data;
|
|
41
|
+
const data = (_doc_data = doc.data()) != null ? _doc_data : {};
|
|
42
|
+
if (data['deletedAt'] != null) continue;
|
|
43
|
+
const via = [];
|
|
44
|
+
if (id && text(data['workflowId']) === id) via.push('id');
|
|
45
|
+
if (name && text(data['workflowName']) === name) via.push('name');
|
|
46
|
+
if (!via.length) continue;
|
|
47
|
+
dependents.push({
|
|
48
|
+
type: 'variable',
|
|
49
|
+
id: doc.id,
|
|
50
|
+
name: text(data['name']) || doc.id,
|
|
51
|
+
via
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
dependents,
|
|
56
|
+
truncated: snapshot.size > WORKFLOW_DEPENDENTS_VARIABLES_READ
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=variable-dependents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/server/variable-dependents.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 { BindingRefVia } from '@aglyn/aglyn/app-utils/binding-tokens'\nimport type {\n PluginDependent,\n PluginDependentsAnswer,\n PluginDependentsRequest,\n} from '@aglyn/aglyn/plugin-manager/plugin-dependents'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\n\n/**\n * Which of a site's variables are computed from a workflow, for the \"Used by\"\n * scan — what the workflows plugin asks before a workflow is renamed or\n * deleted, since each of these falls back to its stored value once the\n * workflow is gone.\n *\n * A computed variable names its workflow by document id and, as a display\n * hint, by the name it had when it was picked (AGL-261); one computed before\n * ids existed carries the name alone. The id survives a rename, the name does\n * not. A deleted variable is left out.\n */\n\n/** How many of a site's variables one scan reads. */\nexport const WORKFLOW_DEPENDENTS_VARIABLES_READ = 100\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nexport async function findWorkflowDependents(\n request: PluginDependentsRequest,\n): Promise<PluginDependentsAnswer> {\n const id = request.id.trim()\n const name = text(request.name)\n if (!request.hostId || (!id && !name)) return { dependents: [], truncated: false }\n // One more than is read, so `truncated` is a fact rather than a guess.\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(request.hostId)\n .collection('variables')\n .limit(WORKFLOW_DEPENDENTS_VARIABLES_READ + 1)\n .get()\n const dependents: PluginDependent[] = []\n for (const doc of snapshot.docs.slice(0, WORKFLOW_DEPENDENTS_VARIABLES_READ)) {\n const data = doc.data() ?? {}\n if (data['deletedAt'] != null) continue\n const via: BindingRefVia[] = []\n if (id && text(data['workflowId']) === id) via.push('id')\n if (name && text(data['workflowName']) === name) via.push('name')\n if (!via.length) continue\n dependents.push({\n type: 'variable',\n id: doc.id,\n name: text(data['name']) || doc.id,\n via,\n })\n }\n return { dependents, truncated: snapshot.size > WORKFLOW_DEPENDENTS_VARIABLES_READ }\n}\n"],"names":["firebaseAdmin","WORKFLOW_DEPENDENTS_VARIABLES_READ","text","value","trim","findWorkflowDependents","request","id","name","hostId","dependents","truncated","snapshot","app","firestore","collection","doc","limit","get","docs","slice","data","via","push","length","type","size"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAQD,SAASA,aAAa,QAAQ,iDAAgD;AAE9E;;;;;;;;;;CAUC,GAED,mDAAmD,GACnD,OAAO,MAAMC,qCAAqC,IAAG;AAErD,MAAMC,OAAO,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAErF,OAAO,eAAeC,uBACpBC,OAAgC;IAEhC,MAAMC,KAAKD,QAAQC,EAAE,CAACH,IAAI;IAC1B,MAAMI,OAAON,KAAKI,QAAQE,IAAI;IAC9B,IAAI,CAACF,QAAQG,MAAM,IAAK,CAACF,MAAM,CAACC,MAAO,OAAO;QAAEE,YAAY,EAAE;QAAEC,WAAW;IAAM;IACjF,uEAAuE;IACvE,MAAMC,WAAW,MAAMZ,cACpBa,GAAG,GACHC,SAAS,GACTC,UAAU,CAAC,SACXC,GAAG,CAACV,QAAQG,MAAM,EAClBM,UAAU,CAAC,aACXE,KAAK,CAAChB,qCAAqC,GAC3CiB,GAAG;IACN,MAAMR,aAAgC,EAAE;IACxC,KAAK,MAAMM,OAAOJ,SAASO,IAAI,CAACC,KAAK,CAAC,GAAGnB,oCAAqC;YAC/De;QAAb,MAAMK,QAAOL,YAAAA,IAAIK,IAAI,cAARL,YAAc,CAAC;QAC5B,IAAIK,IAAI,CAAC,YAAY,IAAI,MAAM;QAC/B,MAAMC,MAAuB,EAAE;QAC/B,IAAIf,MAAML,KAAKmB,IAAI,CAAC,aAAa,MAAMd,IAAIe,IAAIC,IAAI,CAAC;QACpD,IAAIf,QAAQN,KAAKmB,IAAI,CAAC,eAAe,MAAMb,MAAMc,IAAIC,IAAI,CAAC;QAC1D,IAAI,CAACD,IAAIE,MAAM,EAAE;QACjBd,WAAWa,IAAI,CAAC;YACdE,MAAM;YACNlB,IAAIS,IAAIT,EAAE;YACVC,MAAMN,KAAKmB,IAAI,CAAC,OAAO,KAAKL,IAAIT,EAAE;YAClCe;QACF;IACF;IACA,OAAO;QAAEZ;QAAYC,WAAWC,SAASc,IAAI,GAAGzB;IAAmC;AACrF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The `dataset` list source the plugin that keeps the workspace's datasets
|
|
19
|
+
* publishes (AGL-3080), stood in for this plugin's specs — this plugin may
|
|
20
|
+
* not load the data plugin. It builds the query that plugin's
|
|
21
|
+
* `dataset-record-list.ts` builds (a site's answer narrowed by its scope
|
|
22
|
+
* tokens, ordered by document id) from whatever `firebase/firestore` the spec
|
|
23
|
+
* stages, and reads a row as a live dataset named as the Data page names it.
|
|
24
|
+
* What the data plugin itself answers is held in its own spec.
|
|
25
|
+
*/
|
|
26
|
+
export declare function standInDatasetList(): void;
|
|
27
|
+
/** Forgets it, so a spec can ask what happens where no plugin keeps datasets. */
|
|
28
|
+
export declare function removeStandInDatasetList(): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { datasetDisplayName } from "@aglyn/aglyn/app-utils/datasets";
|
|
17
|
+
import { scopeTokensForHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
18
|
+
import { registerPluginRecordListSource } from "@aglyn/aglyn/plugin-manager/plugin-record-lists";
|
|
19
|
+
import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-services";
|
|
20
|
+
import { collection, documentId, limit, orderBy, query, where } from "firebase/firestore";
|
|
21
|
+
/**
|
|
22
|
+
* The `dataset` list source the plugin that keeps the workspace's datasets
|
|
23
|
+
* publishes (AGL-3080), stood in for this plugin's specs — this plugin may
|
|
24
|
+
* not load the data plugin. It builds the query that plugin's
|
|
25
|
+
* `dataset-record-list.ts` builds (a site's answer narrowed by its scope
|
|
26
|
+
* tokens, ordered by document id) from whatever `firebase/firestore` the spec
|
|
27
|
+
* stages, and reads a row as a live dataset named as the Data page names it.
|
|
28
|
+
* What the data plugin itself answers is held in its own spec.
|
|
29
|
+
*/ export function standInDatasetList() {
|
|
30
|
+
registerPluginRecordListSource('dataset', {
|
|
31
|
+
query (firestore, request) {
|
|
32
|
+
if (!request.orgId) return null;
|
|
33
|
+
const datasets = collection(firestore, 'orgs', request.orgId, 'datasets');
|
|
34
|
+
return request.hostId ? query(datasets, where('visibleTo', 'array-contains-any', scopeTokensForHost(request.hostId)), orderBy(documentId()), limit(request.limit)) : query(datasets, orderBy(documentId()), limit(request.limit));
|
|
35
|
+
},
|
|
36
|
+
record (id, data) {
|
|
37
|
+
if (!data || data['deletedAt'] != null) return null;
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
name: datasetDisplayName(data) || id,
|
|
41
|
+
facts: {
|
|
42
|
+
fields: [],
|
|
43
|
+
visibleTo: Array.isArray(data['visibleTo']) ? data['visibleTo'] : []
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}, {
|
|
48
|
+
pluginId: 'data'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** Forgets it, so a spec can ask what happens where no plugin keeps datasets. */ export function removeStandInDatasetList() {
|
|
52
|
+
unregisterPluginServices('data');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=stand-in-dataset-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/testing/stand-in-dataset-list.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 { datasetDisplayName } from '@aglyn/aglyn/app-utils/datasets'\nimport { scopeTokensForHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { registerPluginRecordListSource } from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\nimport { collection, documentId, limit, orderBy, query, where } from 'firebase/firestore'\n\n/**\n * The `dataset` list source the plugin that keeps the workspace's datasets\n * publishes (AGL-3080), stood in for this plugin's specs — this plugin may\n * not load the data plugin. It builds the query that plugin's\n * `dataset-record-list.ts` builds (a site's answer narrowed by its scope\n * tokens, ordered by document id) from whatever `firebase/firestore` the spec\n * stages, and reads a row as a live dataset named as the Data page names it.\n * What the data plugin itself answers is held in its own spec.\n */\nexport function standInDatasetList(): void {\n registerPluginRecordListSource(\n 'dataset',\n {\n query(firestore, request) {\n if (!request.orgId) return null\n const datasets = collection(firestore, 'orgs', request.orgId, 'datasets')\n return request.hostId\n ? query(\n datasets,\n where('visibleTo', 'array-contains-any', scopeTokensForHost(request.hostId)),\n orderBy(documentId()),\n limit(request.limit),\n )\n : query(datasets, orderBy(documentId()), limit(request.limit))\n },\n record(id, data) {\n if (!data || data['deletedAt'] != null) return null\n return {\n id,\n name: datasetDisplayName(data) || id,\n facts: { fields: [], visibleTo: Array.isArray(data['visibleTo']) ? data['visibleTo'] : [] },\n }\n },\n },\n { pluginId: 'data' },\n )\n}\n\n/** Forgets it, so a spec can ask what happens where no plugin keeps datasets. */\nexport function removeStandInDatasetList(): void {\n unregisterPluginServices('data')\n}\n"],"names":["datasetDisplayName","scopeTokensForHost","registerPluginRecordListSource","unregisterPluginServices","collection","documentId","limit","orderBy","query","where","standInDatasetList","firestore","request","orgId","datasets","hostId","record","id","data","name","facts","fields","visibleTo","Array","isArray","pluginId","removeStandInDatasetList"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,kBAAkB,QAAQ,kCAAiC;AACpE,SAASC,kBAAkB,QAAQ,sCAAqC;AACxE,SAASC,8BAA8B,QAAQ,kDAAiD;AAChG,SAASC,wBAAwB,QAAQ,8CAA6C;AACtF,SAASC,UAAU,EAAEC,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,KAAK,EAAEC,KAAK,QAAQ,qBAAoB;AAEzF;;;;;;;;CAQC,GACD,OAAO,SAASC;IACdR,+BACE,WACA;QACEM,OAAMG,SAAS,EAAEC,OAAO;YACtB,IAAI,CAACA,QAAQC,KAAK,EAAE,OAAO;YAC3B,MAAMC,WAAWV,WAAWO,WAAW,QAAQC,QAAQC,KAAK,EAAE;YAC9D,OAAOD,QAAQG,MAAM,GACjBP,MACEM,UACAL,MAAM,aAAa,sBAAsBR,mBAAmBW,QAAQG,MAAM,IAC1ER,QAAQF,eACRC,MAAMM,QAAQN,KAAK,KAErBE,MAAMM,UAAUP,QAAQF,eAAeC,MAAMM,QAAQN,KAAK;QAChE;QACAU,QAAOC,EAAE,EAAEC,IAAI;YACb,IAAI,CAACA,QAAQA,IAAI,CAAC,YAAY,IAAI,MAAM,OAAO;YAC/C,OAAO;gBACLD;gBACAE,MAAMnB,mBAAmBkB,SAASD;gBAClCG,OAAO;oBAAEC,QAAQ,EAAE;oBAAEC,WAAWC,MAAMC,OAAO,CAACN,IAAI,CAAC,YAAY,IAAIA,IAAI,CAAC,YAAY,GAAG,EAAE;gBAAC;YAC5F;QACF;IACF,GACA;QAAEO,UAAU;IAAO;AAEvB;AAEA,+EAA+E,GAC/E,OAAO,SAASC;IACdvB,yBAAyB;AAC3B"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The `workflow` list source the plugin that keeps a site's automations
|
|
19
|
+
* publishes (AGL-3080), stood in for this plugin's specs — this plugin may
|
|
20
|
+
* not load the workflows plugin. It builds the query that plugin's
|
|
21
|
+
* `workflows-record-lists.ts` builds (the site's workflows, ordered by
|
|
22
|
+
* document id) from whatever `firebase/firestore` the spec stages, and reads
|
|
23
|
+
* a row as a live, named workflow. What the workflows plugin itself answers
|
|
24
|
+
* is held in its own spec.
|
|
25
|
+
*/
|
|
26
|
+
export declare function standInWorkflowList(): void;
|
|
27
|
+
/** Forgets it, so a spec can ask what happens where no plugin keeps workflows. */
|
|
28
|
+
export declare function removeStandInWorkflowList(): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
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 { registerPluginRecordListSource } from "@aglyn/aglyn/plugin-manager/plugin-record-lists";
|
|
17
|
+
import { unregisterPluginServices } from "@aglyn/aglyn/plugin-manager/plugin-services";
|
|
18
|
+
import { collection, documentId, limit, orderBy, query } from "firebase/firestore";
|
|
19
|
+
/**
|
|
20
|
+
* The `workflow` list source the plugin that keeps a site's automations
|
|
21
|
+
* publishes (AGL-3080), stood in for this plugin's specs — this plugin may
|
|
22
|
+
* not load the workflows plugin. It builds the query that plugin's
|
|
23
|
+
* `workflows-record-lists.ts` builds (the site's workflows, ordered by
|
|
24
|
+
* document id) from whatever `firebase/firestore` the spec stages, and reads
|
|
25
|
+
* a row as a live, named workflow. What the workflows plugin itself answers
|
|
26
|
+
* is held in its own spec.
|
|
27
|
+
*/ export function standInWorkflowList() {
|
|
28
|
+
registerPluginRecordListSource('workflow', {
|
|
29
|
+
query (firestore, request) {
|
|
30
|
+
return request.hostId ? query(collection(firestore, 'hosts', request.hostId, 'workflows'), orderBy(documentId()), limit(request.limit)) : null;
|
|
31
|
+
},
|
|
32
|
+
record (id, data) {
|
|
33
|
+
const name = typeof (data == null ? void 0 : data['name']) === 'string' ? data['name'].trim() : '';
|
|
34
|
+
return data && data['deletedAt'] == null && name ? {
|
|
35
|
+
id,
|
|
36
|
+
name,
|
|
37
|
+
facts: {}
|
|
38
|
+
} : null;
|
|
39
|
+
}
|
|
40
|
+
}, {
|
|
41
|
+
pluginId: 'workflows'
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** Forgets it, so a spec can ask what happens where no plugin keeps workflows. */ export function removeStandInWorkflowList() {
|
|
45
|
+
unregisterPluginServices('workflows');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=stand-in-workflow-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/logic/src/lib/testing/stand-in-workflow-list.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 { registerPluginRecordListSource } from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport { unregisterPluginServices } from '@aglyn/aglyn/plugin-manager/plugin-services'\nimport { collection, documentId, limit, orderBy, query } from 'firebase/firestore'\n\n/**\n * The `workflow` list source the plugin that keeps a site's automations\n * publishes (AGL-3080), stood in for this plugin's specs — this plugin may\n * not load the workflows plugin. It builds the query that plugin's\n * `workflows-record-lists.ts` builds (the site's workflows, ordered by\n * document id) from whatever `firebase/firestore` the spec stages, and reads\n * a row as a live, named workflow. What the workflows plugin itself answers\n * is held in its own spec.\n */\nexport function standInWorkflowList(): void {\n registerPluginRecordListSource(\n 'workflow',\n {\n query(firestore, request) {\n return request.hostId\n ? query(\n collection(firestore, 'hosts', request.hostId, 'workflows'),\n orderBy(documentId()),\n limit(request.limit),\n )\n : null\n },\n record(id, data) {\n const name = typeof data?.['name'] === 'string' ? data['name'].trim() : ''\n return data && data['deletedAt'] == null && name ? { id, name, facts: {} } : null\n },\n },\n { pluginId: 'workflows' },\n )\n}\n\n/** Forgets it, so a spec can ask what happens where no plugin keeps workflows. */\nexport function removeStandInWorkflowList(): void {\n unregisterPluginServices('workflows')\n}\n"],"names":["registerPluginRecordListSource","unregisterPluginServices","collection","documentId","limit","orderBy","query","standInWorkflowList","firestore","request","hostId","record","id","data","name","trim","facts","pluginId","removeStandInWorkflowList"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,8BAA8B,QAAQ,kDAAiD;AAChG,SAASC,wBAAwB,QAAQ,8CAA6C;AACtF,SAASC,UAAU,EAAEC,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,KAAK,QAAQ,qBAAoB;AAElF;;;;;;;;CAQC,GACD,OAAO,SAASC;IACdP,+BACE,YACA;QACEM,OAAME,SAAS,EAAEC,OAAO;YACtB,OAAOA,QAAQC,MAAM,GACjBJ,MACEJ,WAAWM,WAAW,SAASC,QAAQC,MAAM,EAAE,cAC/CL,QAAQF,eACRC,MAAMK,QAAQL,KAAK,KAErB;QACN;QACAO,QAAOC,EAAE,EAAEC,IAAI;YACb,MAAMC,OAAO,QAAOD,wBAAAA,IAAM,CAAC,OAAO,MAAK,WAAWA,IAAI,CAAC,OAAO,CAACE,IAAI,KAAK;YACxE,OAAOF,QAAQA,IAAI,CAAC,YAAY,IAAI,QAAQC,OAAO;gBAAEF;gBAAIE;gBAAME,OAAO,CAAC;YAAE,IAAI;QAC/E;IACF,GACA;QAAEC,UAAU;IAAY;AAE5B;AAEA,gFAAgF,GAChF,OAAO,SAASC;IACdjB,yBAAyB;AAC3B"}
|