@aglyn/plugins-data 1.0.0-beta.221 → 1.0.0-beta.222

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +9 -6
  2. package/package.json +10 -10
  3. package/src/index.d.ts +1 -1
  4. package/src/index.js +1 -1
  5. package/src/index.js.map +1 -1
  6. package/src/lib/artifact/dataset-schema-artifact.server.d.ts +57 -0
  7. package/src/lib/artifact/dataset-schema-artifact.server.js +308 -0
  8. package/src/lib/artifact/dataset-schema-artifact.server.js.map +1 -0
  9. package/src/lib/artifact/dataset-schema.d.ts +136 -0
  10. package/src/lib/artifact/dataset-schema.js +232 -0
  11. package/src/lib/artifact/dataset-schema.js.map +1 -0
  12. package/src/lib/components/dataset-record-dialog.component.d.ts +1 -1
  13. package/src/lib/components/dataset-record-dialog.component.js +1 -1
  14. package/src/lib/components/dataset-record-dialog.component.js.map +1 -1
  15. package/src/lib/components/dataset-record-filter.d.ts +1 -1
  16. package/src/lib/components/dataset-record-filter.js +2 -2
  17. package/src/lib/components/dataset-record-filter.js.map +1 -1
  18. package/src/lib/components/dataset-schema-dialog.component.d.ts +1 -1
  19. package/src/lib/components/dataset-schema-dialog.component.js +55 -23
  20. package/src/lib/components/dataset-schema-dialog.component.js.map +1 -1
  21. package/src/lib/components/host-datasets-card.component.js +91 -403
  22. package/src/lib/components/host-datasets-card.component.js.map +1 -1
  23. package/src/lib/constants/bundle-common.d.ts +12 -0
  24. package/src/lib/constants/bundle-common.js +13 -0
  25. package/src/lib/constants/bundle-common.js.map +1 -1
  26. package/src/lib/declarations.console-server.d.ts +16 -1
  27. package/src/lib/declarations.console-server.js +34 -2
  28. package/src/lib/declarations.console-server.js.map +1 -1
  29. package/src/lib/declarations.server.d.ts +13 -0
  30. package/src/lib/declarations.server.js +48 -2
  31. package/src/lib/declarations.server.js.map +1 -1
  32. package/src/lib/form-target/dataset-form-record-target.server.js +5 -3
  33. package/src/lib/form-target/dataset-form-record-target.server.js.map +1 -1
  34. package/src/lib/model/dataset-models.d.ts +392 -0
  35. package/src/lib/model/dataset-models.js +806 -0
  36. package/src/lib/model/dataset-models.js.map +1 -0
  37. package/src/lib/model/dataset-record-view.d.ts +1 -1
  38. package/src/lib/model/dataset-record-view.js +1 -2
  39. package/src/lib/model/dataset-record-view.js.map +1 -1
  40. package/src/lib/model/dataset-record.js +2 -2
  41. package/src/lib/model/dataset-record.js.map +1 -1
  42. package/src/lib/model/datasets.d.ts +162 -0
  43. package/src/lib/model/datasets.js +227 -0
  44. package/src/lib/model/datasets.js.map +1 -0
  45. package/src/lib/plugin.d.ts +4 -0
  46. package/src/lib/plugin.js +40 -0
  47. package/src/lib/plugin.js.map +1 -1
  48. package/src/lib/record-pages/record-page-live-paths.server.d.ts +55 -0
  49. package/src/lib/record-pages/record-page-live-paths.server.js +83 -0
  50. package/src/lib/record-pages/record-page-live-paths.server.js.map +1 -0
  51. package/src/lib/record-pages/record-page-read.server.d.ts +70 -0
  52. package/src/lib/record-pages/record-page-read.server.js +175 -0
  53. package/src/lib/record-pages/record-page-read.server.js.map +1 -0
  54. package/src/lib/record-pages/record-page-resolver.server.d.ts +37 -0
  55. package/src/lib/record-pages/record-page-resolver.server.js +131 -0
  56. package/src/lib/record-pages/record-page-resolver.server.js.map +1 -0
  57. package/src/lib/record-pages/record-page-sitemap.server.d.ts +32 -0
  58. package/src/lib/record-pages/record-page-sitemap.server.js +102 -0
  59. package/src/lib/record-pages/record-page-sitemap.server.js.map +1 -0
  60. package/src/lib/record-pages/record-page-source.d.ts +42 -0
  61. package/src/lib/record-pages/record-page-source.js +176 -0
  62. package/src/lib/record-pages/record-page-source.js.map +1 -0
  63. package/src/lib/record-pages/record-pages-route.d.ts +42 -0
  64. package/src/lib/record-pages/record-pages-route.js +348 -0
  65. package/src/lib/record-pages/record-pages-route.js.map +1 -0
  66. package/src/lib/record-pages/record-pages.d.ts +218 -0
  67. package/src/lib/record-pages/record-pages.js +351 -0
  68. package/src/lib/record-pages/record-pages.js.map +1 -0
  69. package/src/lib/record-pages/record-template-row-chip.component.d.ts +28 -0
  70. package/src/lib/record-pages/record-template-row-chip.component.js +87 -0
  71. package/src/lib/record-pages/record-template-row-chip.component.js.map +1 -0
  72. package/src/lib/record-pages/record-template-section.component.d.ts +30 -0
  73. package/src/lib/record-pages/record-template-section.component.js +491 -0
  74. package/src/lib/record-pages/record-template-section.component.js.map +1 -0
  75. package/src/lib/repeat/dataset-repeat-rows.d.ts +2 -1
  76. package/src/lib/repeat/dataset-repeat-rows.js +30 -12
  77. package/src/lib/repeat/dataset-repeat-rows.js.map +1 -1
  78. package/src/lib/repeat/dataset-repeat-rows.server.d.ts +1 -1
  79. package/src/lib/repeat/dataset-repeat-rows.server.js +23 -12
  80. package/src/lib/repeat/dataset-repeat-rows.server.js.map +1 -1
  81. package/src/lib/repeat/repeat-record-pages.d.ts +48 -0
  82. package/src/lib/repeat/repeat-record-pages.js +64 -0
  83. package/src/lib/repeat/repeat-record-pages.js.map +1 -0
  84. package/src/lib/server/announce-dataset-records.d.ts +1 -1
  85. package/src/lib/server/announce-dataset-records.js +1 -1
  86. package/src/lib/server/announce-dataset-records.js.map +1 -1
  87. package/src/lib/server/api-v1/datasets.js +5 -3
  88. package/src/lib/server/api-v1/datasets.js.map +1 -1
  89. package/src/lib/server/dataset-figures.js +2 -2
  90. package/src/lib/server/dataset-figures.js.map +1 -1
  91. package/src/lib/server/dataset-live-pages.d.ts +147 -0
  92. package/src/lib/server/dataset-live-pages.js +436 -0
  93. package/src/lib/server/dataset-live-pages.js.map +1 -0
  94. package/src/lib/server/dataset-steps.server.d.ts +45 -0
  95. package/src/lib/server/dataset-steps.server.js +268 -0
  96. package/src/lib/server/dataset-steps.server.js.map +1 -0
  97. package/src/lib/server/datasets-route.d.ts +2 -0
  98. package/src/lib/server/datasets-route.js +102 -6
  99. package/src/lib/server/datasets-route.js.map +1 -1
  100. package/src/lib/server/resolve-dataset.d.ts +40 -0
  101. package/src/lib/server/resolve-dataset.js +64 -0
  102. package/src/lib/server/resolve-dataset.js.map +1 -0
  103. package/src/lib/server.d.ts +3 -2
  104. package/src/lib/server.js +8 -9
  105. package/src/lib/server.js.map +1 -1
  106. package/src/lib/site-bundle/datasets-package.d.ts +42 -0
  107. package/src/lib/site-bundle/datasets-package.js +95 -0
  108. package/src/lib/site-bundle/datasets-package.js.map +1 -0
  109. package/src/lib/site-bundle/datasets-site-bundle.server.js +2 -2
  110. package/src/lib/site-bundle/datasets-site-bundle.server.js.map +1 -1
  111. package/src/lib/{model/index.js → tenant-api.d.ts} +12 -7
  112. package/src/lib/tenant-api.js +30 -0
  113. package/src/lib/tenant-api.js.map +1 -0
  114. package/src/lib/transfer/dataset-transfer-key.d.ts +30 -0
  115. package/src/lib/transfer/dataset-transfer-key.js +31 -0
  116. package/src/lib/transfer/dataset-transfer-key.js.map +1 -0
  117. package/src/lib/transfer/dataset-transfer-model.d.ts +146 -0
  118. package/src/lib/transfer/dataset-transfer-model.js +536 -0
  119. package/src/lib/transfer/dataset-transfer-model.js.map +1 -0
  120. package/src/lib/{model/index.d.ts → transfer/dataset-transfer-resource.d.ts} +2 -6
  121. package/src/lib/transfer/dataset-transfer-resource.js +41 -0
  122. package/src/lib/transfer/dataset-transfer-resource.js.map +1 -0
  123. package/src/lib/transfer/dataset-transfer.server.d.ts +69 -0
  124. package/src/lib/transfer/dataset-transfer.server.js +930 -0
  125. package/src/lib/transfer/dataset-transfer.server.js.map +1 -0
  126. package/src/lib/model/dataset-csv.d.ts +0 -36
  127. package/src/lib/model/dataset-csv.js +0 -85
  128. package/src/lib/model/dataset-csv.js.map +0 -1
  129. package/src/lib/model/dataset-io.d.ts +0 -44
  130. package/src/lib/model/dataset-io.js +0 -85
  131. package/src/lib/model/dataset-io.js.map +0 -1
  132. package/src/lib/model/index.js.map +0 -1
  133. package/src/lib/server/datasets-export-route.d.ts +0 -57
  134. package/src/lib/server/datasets-export-route.js +0 -229
  135. package/src/lib/server/datasets-export-route.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/constants/bundle-common.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 */\n\nexport const BUNDLE_ID = 'data'\n\n/**\n * The id datasets are a repeat source under — the editor's source\n * (`repeat-sources.ts`) and the published page's reader (`repeat-rows.ts`)\n * alike, and the `repeatSource` this plugin declares in `plugins.config.json`.\n */\nexport const DATASET_REPEAT_SOURCE_ID = 'dataset'\n\n/** The dataset storage meter in the monthly usage sweep (`server/dataset-storage-meter.ts`). */\nexport const DATASET_STORAGE_METER_ID = 'dataset-storage'\n"],"names":["BUNDLE_ID","DATASET_REPEAT_SOURCE_ID","DATASET_STORAGE_METER_ID"],"mappings":"AAAA;;;;;;CAMC,GAED,OAAO,MAAMA,YAAY,OAAM;AAE/B;;;;CAIC,GACD,OAAO,MAAMC,2BAA2B,UAAS;AAEjD,8FAA8F,GAC9F,OAAO,MAAMC,2BAA2B,kBAAiB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/constants/bundle-common.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 */\n\nexport const BUNDLE_ID = 'data'\n\n/**\n * The id datasets are a repeat source under — the editor's source\n * (`repeat-sources.ts`) and the published page's reader (`repeat-rows.ts`)\n * alike, and the `repeatSource` this plugin declares in `plugins.config.json`.\n */\nexport const DATASET_REPEAT_SOURCE_ID = 'dataset'\n\n/** The dataset storage meter in the monthly usage sweep (`server/dataset-storage-meter.ts`). */\nexport const DATASET_STORAGE_METER_ID = 'dataset-storage'\n\n/**\n * The installable artifact type a dataset's schema is published and installed\n * as: the `artifactTypes` this plugin declares in `plugins.config.json`, and\n * the key a listing's version stores the schema under.\n */\nexport const DATASET_SCHEMA_ARTIFACT_TYPE = 'datasetSchema'\n\n/**\n * The automation steps this plugin runs for the workflows engine\n * (`server/dataset-steps.server.ts`), as `plugins.config.json` declares them\n * under `serverSteps`.\n */\nexport const DATASET_STEP_TYPES = ['datasetAppend', 'updateDataset'] as const\n"],"names":["BUNDLE_ID","DATASET_REPEAT_SOURCE_ID","DATASET_STORAGE_METER_ID","DATASET_SCHEMA_ARTIFACT_TYPE","DATASET_STEP_TYPES"],"mappings":"AAAA;;;;;;CAMC,GAED,OAAO,MAAMA,YAAY,OAAM;AAE/B;;;;CAIC,GACD,OAAO,MAAMC,2BAA2B,UAAS;AAEjD,8FAA8F,GAC9F,OAAO,MAAMC,2BAA2B,kBAAiB;AAEzD;;;;CAIC,GACD,OAAO,MAAMC,+BAA+B,gBAAe;AAE3D;;;;CAIC,GACD,OAAO,MAAMC,qBAAqB;IAAC;IAAiB;CAAgB,CAAS"}
@@ -37,9 +37,24 @@
37
37
  * through the `dataset` record index, and through the dataset figure readers
38
38
  * an insight asks.
39
39
  *
40
+ * And a dataset's schema is an installable artifact: published from a
41
+ * dataset, installed as a new one, updated in place. The installer that
42
+ * sells it asks this plugin for each of those through the artifact type it
43
+ * declares (`artifactTypes`), so it never reads the datasets collection. A
44
+ * boot that skipped this refuses the install rather than reading it as a type
45
+ * nobody keeps.
46
+ *
47
+ * And a dataset's records are imported and exported through the platform's
48
+ * transfer routes (`/api/transfer/*`, AGL-3530): this registers the server
49
+ * half of the `data.dataset` resource `plugins.config.json` declares
50
+ * (`transferResources`), so a boot that skipped it fails the wizard rather
51
+ * than reading as a dataset with nothing to move.
52
+ *
40
53
  * Light at boot: the API handler is imported with its first request, the
41
54
  * description when the document is first built, the usage reader with the
42
- * first usage call and the index's reader with its first read; the figure
55
+ * first usage call, the index's reader with its first read and the schema's
56
+ * answers with the first publish, install or update, the transfer hooks with
57
+ * the first import or export; the figure
43
58
  * readers reach Firestore only when one is asked. Registering again replaces
44
59
  * this plugin's own entries, so a second call (a hot reload, a spec) is
45
60
  * harmless.
@@ -16,11 +16,15 @@
16
16
  */ // The registries' own modules, not the data layer's or the plugin-manager's
17
17
  // barrel: boot needs these registries, not the whole server surface or the
18
18
  // console's client contexts.
19
+ import { registerArtifactTypeOwner } from "@aglyn/aglyn/plugin-manager/plugin-artifact-types";
19
20
  import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
20
21
  import { registerApiV1Resource, registerApiV1UsageFigures } from "@aglyn/tenant-data-admin/server/api-v1-resources";
22
+ import { registerPluginTransferResource } from "@aglyn/aglyn/plugin-manager/plugin-transfer-resources";
21
23
  import { firebaseAdmin } from "@aglyn/tenant-data-admin/server/firebase-admin";
22
- import { BUNDLE_ID } from "./constants/bundle-common.js";
24
+ import { BUNDLE_ID, DATASET_SCHEMA_ARTIFACT_TYPE } from "./constants/bundle-common.js";
23
25
  import { registerDatasetFigureReaders } from "./server/dataset-figures.js";
26
+ import { DATASET_TRANSFER_RESOURCE } from "./transfer/dataset-transfer-key.js";
27
+ import { DATASET_TRANSFER_HOOKS } from "./transfer/dataset-transfer-resource.js";
24
28
  /**
25
29
  * The data plugin's CONSOLE-ONLY server declarations, named under
26
30
  * `consoleServerDeclarations` in `plugins.config.json` and run at the
@@ -44,9 +48,24 @@ import { registerDatasetFigureReaders } from "./server/dataset-figures.js";
44
48
  * through the `dataset` record index, and through the dataset figure readers
45
49
  * an insight asks.
46
50
  *
51
+ * And a dataset's schema is an installable artifact: published from a
52
+ * dataset, installed as a new one, updated in place. The installer that
53
+ * sells it asks this plugin for each of those through the artifact type it
54
+ * declares (`artifactTypes`), so it never reads the datasets collection. A
55
+ * boot that skipped this refuses the install rather than reading it as a type
56
+ * nobody keeps.
57
+ *
58
+ * And a dataset's records are imported and exported through the platform's
59
+ * transfer routes (`/api/transfer/*`, AGL-3530): this registers the server
60
+ * half of the `data.dataset` resource `plugins.config.json` declares
61
+ * (`transferResources`), so a boot that skipped it fails the wizard rather
62
+ * than reading as a dataset with nothing to move.
63
+ *
47
64
  * Light at boot: the API handler is imported with its first request, the
48
65
  * description when the document is first built, the usage reader with the
49
- * first usage call and the index's reader with its first read; the figure
66
+ * first usage call, the index's reader with its first read and the schema's
67
+ * answers with the first publish, install or update, the transfer hooks with
68
+ * the first import or export; the figure
50
69
  * readers reach Firestore only when one is asked. Registering again replaces
51
70
  * this plugin's own entries, so a second call (a hot reload, a spec) is
52
71
  * harmless.
@@ -64,11 +83,24 @@ import { registerDatasetFigureReaders } from "./server/dataset-figures.js";
64
83
  pluginId: BUNDLE_ID
65
84
  });
66
85
  registerDatasetFigureReaders(()=>firebaseAdmin.app().firestore());
86
+ registerArtifactTypeOwner(DATASET_SCHEMA_ARTIFACT_TYPE, lazyDatasetSchemaOwner, {
87
+ pluginId: BUNDLE_ID
88
+ });
89
+ registerPluginTransferResource(DATASET_TRANSFER_RESOURCE, DATASET_TRANSFER_HOOKS, {
90
+ pluginId: BUNDLE_ID
91
+ });
67
92
  }
68
93
  const loadDatasetIndex = async ()=>(await import("./server/dataset-record-index.js")).datasetRecordIndex;
69
94
  const lazyDatasetIndex = {
70
95
  list: async (request)=>(await loadDatasetIndex()).list(request),
71
96
  get: async (request)=>(await loadDatasetIndex()).get(request)
72
97
  };
98
+ const loadDatasetSchemaOwner = async ()=>(await import("./artifact/dataset-schema-artifact.server.js")).datasetSchemaArtifactOwner;
99
+ const lazyDatasetSchemaOwner = {
100
+ snapshot: async (request)=>(await loadDatasetSchemaOwner()).snapshot(request),
101
+ admits: async (workspace)=>(await loadDatasetSchemaOwner()).admits(workspace),
102
+ prepare: async (request)=>(await loadDatasetSchemaOwner()).prepare(request),
103
+ locate: async (request)=>(await loadDatasetSchemaOwner()).locate(request)
104
+ };
73
105
 
74
106
  //# sourceMappingURL=declarations.console-server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/data/src/lib/declarations.console-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\n// The registries' own modules, not the data layer's or the plugin-manager's\n// barrel: boot needs these registries, not the whole server surface or the\n// console's client contexts.\nimport {\n registerPluginRecordIndex,\n type PluginRecordIndex,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport {\n registerApiV1Resource,\n registerApiV1UsageFigures,\n} from '@aglyn/tenant-data-admin/server/api-v1-resources'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { registerDatasetFigureReaders } from './server/dataset-figures'\n\n/**\n * The data plugin's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. Everything here is the console's alone, so the tenant\n * runtime does not register any of it.\n *\n * It serves the organization's datasets on the customer REST API,\n * `/v1/datasets/…`: the console's router owns the pipeline in front — the\n * key, the plan's API access, the quota, the rate limit, the error envelope —\n * and hands every request under `/v1/datasets` here once the key is\n * authenticated. No plan feature is named on the registration: the handler\n * answers reads on every plan and refuses a create the plan does not carry\n * (`dataStore`) with its own sentence, as it always has. The resource brings\n * its description for the API's OpenAPI document, and the datasets band\n * joins `GET /v1/usage`.\n *\n * And another plugin that works over the workspace's datasets — an AI job\n * naming a dataset in an automation, a planner listing what a site already\n * has, an insight summarizing a dataset's fields — runs on the console's\n * server, and reads them here rather than from the datasets collection:\n * through the `dataset` record index, and through the dataset figure readers\n * an insight asks.\n *\n * Light at boot: the API handler is imported with its first request, the\n * description when the document is first built, the usage reader with the\n * first usage call and the index's reader with its first read; the figure\n * readers reach Firestore only when one is asked. Registering again replaces\n * this plugin's own entries, so a second call (a hot reload, a spec) is\n * harmless.\n */\nexport function registerDataConsoleServerDeclarations(): void {\n registerApiV1Resource(\n 'datasets',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/datasets')).handleDatasets(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).DATASETS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1UsageFigures(\n async (ctx) => (await import('./server/api-v1/usage')).datasetUsageFigures(ctx),\n { pluginId: BUNDLE_ID },\n )\n registerPluginRecordIndex('dataset', lazyDatasetIndex, { pluginId: BUNDLE_ID })\n registerDatasetFigureReaders(() => firebaseAdmin.app().firestore())\n}\n\nconst loadDatasetIndex = async () => (await import('./server/dataset-record-index')).datasetRecordIndex\n\nconst lazyDatasetIndex: PluginRecordIndex = {\n list: async (request) => (await loadDatasetIndex()).list(request),\n get: async (request) => (await loadDatasetIndex()).get(request),\n}\n"],"names":["registerPluginRecordIndex","registerApiV1Resource","registerApiV1UsageFigures","firebaseAdmin","BUNDLE_ID","registerDatasetFigureReaders","registerDataConsoleServerDeclarations","handle","args","handleDatasets","describe","DATASETS_API_V1_DESCRIPTION","pluginId","ctx","datasetUsageFigures","lazyDatasetIndex","app","firestore","loadDatasetIndex","datasetRecordIndex","list","request","get"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,4EAA4E;AAC5E,2EAA2E;AAC3E,6BAA6B;AAC7B,SACEA,yBAAyB,QAEpB,kDAAiD;AACxD,SACEC,qBAAqB,EACrBC,yBAAyB,QACpB,mDAAkD;AACzD,SAASC,aAAa,QAAQ,iDAAgD;AAC9E,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,4BAA4B,QAAQ,8BAA0B;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD,OAAO,SAASC;IACdL,sBACE,YACA;QACEM,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,8BAA0B,EAAGC,cAAc,IAAID;QAC/DE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGC,2BAA2B;IACzE,GACA;QAAEC,UAAUR;IAAU;IAExBF,0BACE,OAAOW,MAAQ,AAAC,CAAA,MAAM,MAAM,CAAC,2BAAuB,EAAGC,mBAAmB,CAACD,MAC3E;QAAED,UAAUR;IAAU;IAExBJ,0BAA0B,WAAWe,kBAAkB;QAAEH,UAAUR;IAAU;IAC7EC,6BAA6B,IAAMF,cAAca,GAAG,GAAGC,SAAS;AAClE;AAEA,MAAMC,mBAAmB,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGC,kBAAkB;AAEvG,MAAMJ,mBAAsC;IAC1CK,MAAM,OAAOC,UAAY,AAAC,CAAA,MAAMH,kBAAiB,EAAGE,IAAI,CAACC;IACzDC,KAAK,OAAOD,UAAY,AAAC,CAAA,MAAMH,kBAAiB,EAAGI,GAAG,CAACD;AACzD"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/data/src/lib/declarations.console-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\n// The registries' own modules, not the data layer's or the plugin-manager's\n// barrel: boot needs these registries, not the whole server surface or the\n// console's client contexts.\nimport {\n registerArtifactTypeOwner,\n type PluginArtifactOwner,\n} from '@aglyn/aglyn/plugin-manager/plugin-artifact-types'\nimport {\n registerPluginRecordIndex,\n type PluginRecordIndex,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport {\n registerApiV1Resource,\n registerApiV1UsageFigures,\n} from '@aglyn/tenant-data-admin/server/api-v1-resources'\nimport { registerPluginTransferResource } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { BUNDLE_ID, DATASET_SCHEMA_ARTIFACT_TYPE } from './constants/bundle-common'\nimport { registerDatasetFigureReaders } from './server/dataset-figures'\nimport { DATASET_TRANSFER_RESOURCE } from './transfer/dataset-transfer-key'\nimport { DATASET_TRANSFER_HOOKS } from './transfer/dataset-transfer-resource'\n\n/**\n * The data plugin's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. Everything here is the console's alone, so the tenant\n * runtime does not register any of it.\n *\n * It serves the organization's datasets on the customer REST API,\n * `/v1/datasets/…`: the console's router owns the pipeline in front — the\n * key, the plan's API access, the quota, the rate limit, the error envelope —\n * and hands every request under `/v1/datasets` here once the key is\n * authenticated. No plan feature is named on the registration: the handler\n * answers reads on every plan and refuses a create the plan does not carry\n * (`dataStore`) with its own sentence, as it always has. The resource brings\n * its description for the API's OpenAPI document, and the datasets band\n * joins `GET /v1/usage`.\n *\n * And another plugin that works over the workspace's datasets — an AI job\n * naming a dataset in an automation, a planner listing what a site already\n * has, an insight summarizing a dataset's fields — runs on the console's\n * server, and reads them here rather than from the datasets collection:\n * through the `dataset` record index, and through the dataset figure readers\n * an insight asks.\n *\n * And a dataset's schema is an installable artifact: published from a\n * dataset, installed as a new one, updated in place. The installer that\n * sells it asks this plugin for each of those through the artifact type it\n * declares (`artifactTypes`), so it never reads the datasets collection. A\n * boot that skipped this refuses the install rather than reading it as a type\n * nobody keeps.\n *\n * And a dataset's records are imported and exported through the platform's\n * transfer routes (`/api/transfer/*`, AGL-3530): this registers the server\n * half of the `data.dataset` resource `plugins.config.json` declares\n * (`transferResources`), so a boot that skipped it fails the wizard rather\n * than reading as a dataset with nothing to move.\n *\n * Light at boot: the API handler is imported with its first request, the\n * description when the document is first built, the usage reader with the\n * first usage call, the index's reader with its first read and the schema's\n * answers with the first publish, install or update, the transfer hooks with\n * the first import or export; the figure\n * readers reach Firestore only when one is asked. Registering again replaces\n * this plugin's own entries, so a second call (a hot reload, a spec) is\n * harmless.\n */\nexport function registerDataConsoleServerDeclarations(): void {\n registerApiV1Resource(\n 'datasets',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/datasets')).handleDatasets(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).DATASETS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1UsageFigures(\n async (ctx) => (await import('./server/api-v1/usage')).datasetUsageFigures(ctx),\n { pluginId: BUNDLE_ID },\n )\n registerPluginRecordIndex('dataset', lazyDatasetIndex, { pluginId: BUNDLE_ID })\n registerDatasetFigureReaders(() => firebaseAdmin.app().firestore())\n registerArtifactTypeOwner(DATASET_SCHEMA_ARTIFACT_TYPE, lazyDatasetSchemaOwner, {\n pluginId: BUNDLE_ID,\n })\n registerPluginTransferResource(DATASET_TRANSFER_RESOURCE, DATASET_TRANSFER_HOOKS, {\n pluginId: BUNDLE_ID,\n })\n}\n\nconst loadDatasetIndex = async () => (await import('./server/dataset-record-index')).datasetRecordIndex\n\nconst lazyDatasetIndex: PluginRecordIndex = {\n list: async (request) => (await loadDatasetIndex()).list(request),\n get: async (request) => (await loadDatasetIndex()).get(request),\n}\n\nconst loadDatasetSchemaOwner = async () =>\n (await import('./artifact/dataset-schema-artifact.server')).datasetSchemaArtifactOwner\n\nconst lazyDatasetSchemaOwner: PluginArtifactOwner = {\n snapshot: async (request) => (await loadDatasetSchemaOwner()).snapshot(request),\n admits: async (workspace) => (await loadDatasetSchemaOwner()).admits(workspace),\n prepare: async (request) => (await loadDatasetSchemaOwner()).prepare(request),\n locate: async (request) => (await loadDatasetSchemaOwner()).locate(request),\n}\n"],"names":["registerArtifactTypeOwner","registerPluginRecordIndex","registerApiV1Resource","registerApiV1UsageFigures","registerPluginTransferResource","firebaseAdmin","BUNDLE_ID","DATASET_SCHEMA_ARTIFACT_TYPE","registerDatasetFigureReaders","DATASET_TRANSFER_RESOURCE","DATASET_TRANSFER_HOOKS","registerDataConsoleServerDeclarations","handle","args","handleDatasets","describe","DATASETS_API_V1_DESCRIPTION","pluginId","ctx","datasetUsageFigures","lazyDatasetIndex","app","firestore","lazyDatasetSchemaOwner","loadDatasetIndex","datasetRecordIndex","list","request","get","loadDatasetSchemaOwner","datasetSchemaArtifactOwner","snapshot","admits","workspace","prepare","locate"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,4EAA4E;AAC5E,2EAA2E;AAC3E,6BAA6B;AAC7B,SACEA,yBAAyB,QAEpB,oDAAmD;AAC1D,SACEC,yBAAyB,QAEpB,kDAAiD;AACxD,SACEC,qBAAqB,EACrBC,yBAAyB,QACpB,mDAAkD;AACzD,SAASC,8BAA8B,QAAQ,wDAAuD;AACtG,SAASC,aAAa,QAAQ,iDAAgD;AAC9E,SAASC,SAAS,EAAEC,4BAA4B,QAAQ,+BAA2B;AACnF,SAASC,4BAA4B,QAAQ,8BAA0B;AACvE,SAASC,yBAAyB,QAAQ,qCAAiC;AAC3E,SAASC,sBAAsB,QAAQ,0CAAsC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CC,GACD,OAAO,SAASC;IACdT,sBACE,YACA;QACEU,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,8BAA0B,EAAGC,cAAc,IAAID;QAC/DE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGC,2BAA2B;IACzE,GACA;QAAEC,UAAUX;IAAU;IAExBH,0BACE,OAAOe,MAAQ,AAAC,CAAA,MAAM,MAAM,CAAC,2BAAuB,EAAGC,mBAAmB,CAACD,MAC3E;QAAED,UAAUX;IAAU;IAExBL,0BAA0B,WAAWmB,kBAAkB;QAAEH,UAAUX;IAAU;IAC7EE,6BAA6B,IAAMH,cAAcgB,GAAG,GAAGC,SAAS;IAChEtB,0BAA0BO,8BAA8BgB,wBAAwB;QAC9EN,UAAUX;IACZ;IACAF,+BAA+BK,2BAA2BC,wBAAwB;QAChFO,UAAUX;IACZ;AACF;AAEA,MAAMkB,mBAAmB,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGC,kBAAkB;AAEvG,MAAML,mBAAsC;IAC1CM,MAAM,OAAOC,UAAY,AAAC,CAAA,MAAMH,kBAAiB,EAAGE,IAAI,CAACC;IACzDC,KAAK,OAAOD,UAAY,AAAC,CAAA,MAAMH,kBAAiB,EAAGI,GAAG,CAACD;AACzD;AAEA,MAAME,yBAAyB,UAC7B,AAAC,CAAA,MAAM,MAAM,CAAC,+CAA2C,EAAGC,0BAA0B;AAExF,MAAMP,yBAA8C;IAClDQ,UAAU,OAAOJ,UAAY,AAAC,CAAA,MAAME,wBAAuB,EAAGE,QAAQ,CAACJ;IACvEK,QAAQ,OAAOC,YAAc,AAAC,CAAA,MAAMJ,wBAAuB,EAAGG,MAAM,CAACC;IACrEC,SAAS,OAAOP,UAAY,AAAC,CAAA,MAAME,wBAAuB,EAAGK,OAAO,CAACP;IACrEQ,QAAQ,OAAOR,UAAY,AAAC,CAAA,MAAME,wBAAuB,EAAGM,MAAM,CAACR;AACrE"}
@@ -39,5 +39,18 @@
39
39
  * And the monthly usage sweep, a core cron, measures what the datasets store
40
40
  * through this plugin's meter — declared in `usageAxes`, so the sweep refuses
41
41
  * to bill a month without it. Its reads load with the first sweep.
42
+ *
43
+ * An automation that writes a dataset record runs that step through this
44
+ * plugin: the workflows engine reaches it through the platform's server-step
45
+ * seam, in whichever process runs the automation. Declared too (`serverSteps`),
46
+ * so a boot that skipped this fails the step with its reason rather than
47
+ * reporting a record that was never written; the writes load with the first
48
+ * step.
49
+ *
50
+ * And the "Page address" field type a record template reads a record's page
51
+ * from, so every write path holds an address to the shape a URL can carry, and
52
+ * the reader that lists record pages in the sitemap — declared too
53
+ * (`sitemapReaders`), so a boot that skipped this degrades the sitemap rather
54
+ * than dropping every record page from it.
42
55
  */
43
56
  export declare function registerDataServerDeclarations(): void;
@@ -19,7 +19,13 @@ import { registerFormRecordTarget } from "@aglyn/aglyn/plugin-manager/submission
19
19
  import { registerRepeatRowReader } from "@aglyn/aglyn/plugin-manager/repeat-rows";
20
20
  import { registerPluginSiteBundleSection } from "@aglyn/aglyn/plugin-manager/plugin-site-bundle";
21
21
  import { registerPluginUsageMeter } from "@aglyn/aglyn/plugin-manager/plugin-usage-meters";
22
- import { BUNDLE_ID, DATASET_REPEAT_SOURCE_ID, DATASET_STORAGE_METER_ID } from "./constants/bundle-common.js";
22
+ import { registerServerStepExecutor } from "@aglyn/aglyn/plugin-manager/plugin-server-steps";
23
+ import { registerCustomFieldType } from "@aglyn/aglyn/plugin-manager/custom-fields";
24
+ import { registerPluginSitemapReader } from "@aglyn/aglyn/plugin-manager/plugin-sitemap-readers";
25
+ import { registerPluginLivePaths } from "@aglyn/aglyn/plugin-manager/plugin-live-paths";
26
+ import { BUNDLE_ID, DATASET_REPEAT_SOURCE_ID, DATASET_STEP_TYPES, DATASET_STORAGE_METER_ID } from "./constants/bundle-common.js";
27
+ import { RECORD_PAGE_ADDRESS_FIELD } from "./record-pages/record-pages.js";
28
+ import { datasetsSitePackage } from "./site-bundle/datasets-package.js";
23
29
  /**
24
30
  * The data plugin's server declarations: the light registrations core reads
25
31
  * at boot, before any surface loads.
@@ -45,6 +51,19 @@ import { BUNDLE_ID, DATASET_REPEAT_SOURCE_ID, DATASET_STORAGE_METER_ID } from ".
45
51
  * And the monthly usage sweep, a core cron, measures what the datasets store
46
52
  * through this plugin's meter — declared in `usageAxes`, so the sweep refuses
47
53
  * to bill a month without it. Its reads load with the first sweep.
54
+ *
55
+ * An automation that writes a dataset record runs that step through this
56
+ * plugin: the workflows engine reaches it through the platform's server-step
57
+ * seam, in whichever process runs the automation. Declared too (`serverSteps`),
58
+ * so a boot that skipped this fails the step with its reason rather than
59
+ * reporting a record that was never written; the writes load with the first
60
+ * step.
61
+ *
62
+ * And the "Page address" field type a record template reads a record's page
63
+ * from, so every write path holds an address to the shape a URL can carry, and
64
+ * the reader that lists record pages in the sitemap — declared too
65
+ * (`sitemapReaders`), so a boot that skipped this degrades the sitemap rather
66
+ * than dropping every record page from it.
48
67
  */ export function registerDataServerDeclarations() {
49
68
  registerRepeatRowReader(DATASET_REPEAT_SOURCE_ID, async (request)=>(await import("./repeat/dataset-repeat-rows.server.js")).readPublishedDatasetRows(request), {
50
69
  pluginId: BUNDLE_ID
@@ -59,7 +78,8 @@ import { BUNDLE_ID, DATASET_REPEAT_SOURCE_ID, DATASET_STORAGE_METER_ID } from ".
59
78
  registerPluginSiteBundleSection('datasets', {
60
79
  export: async (request)=>(await datasets()).exportSiteDatasets(request),
61
80
  refusal: async (request)=>(await datasets()).siteDatasetsRefusal(request),
62
- import: async (request)=>(await datasets()).importSiteDatasets(request)
81
+ import: async (request)=>(await datasets()).importSiteDatasets(request),
82
+ package: datasetsSitePackage
63
83
  }, {
64
84
  pluginId: BUNDLE_ID
65
85
  });
@@ -68,6 +88,32 @@ import { BUNDLE_ID, DATASET_REPEAT_SOURCE_ID, DATASET_STORAGE_METER_ID } from ".
68
88
  id: DATASET_STORAGE_METER_ID,
69
89
  measure: async (context)=>(await import("./server/dataset-storage-meter.js")).measureDatasetStorage(context)
70
90
  });
91
+ registerServerStepExecutor(DATASET_STEP_TYPES, async (request)=>(await import("./server/dataset-steps.server.js")).runDatasetStep(request), {
92
+ pluginId: BUNDLE_ID
93
+ });
94
+ // A record's page address (AGL-3475) is checked on every server path that
95
+ // writes a record, the tenant's form and automation writes included.
96
+ registerCustomFieldType(RECORD_PAGE_ADDRESS_FIELD);
97
+ // Record pages in the sitemap and `/llms.txt` (AGL-3475), declared as the
98
+ // `records` family in `plugins.config.json`; the reads load with the first
99
+ // sitemap a crawler asks for.
100
+ const sitemap = async ()=>(await import("./record-pages/record-page-sitemap.server.js")).recordPagesSitemapReader;
101
+ registerPluginSitemapReader('records', {
102
+ children: async (request)=>(await sitemap()).children(request),
103
+ urls: async (request)=>(await sitemap()).urls(request),
104
+ listings: async (request)=>{
105
+ var _ref;
106
+ var _listings, _this;
107
+ return (_ref = (_listings = (_this = await sitemap()).listings) == null ? void 0 : _listings.call(_this, request)) != null ? _ref : [];
108
+ }
109
+ }, {
110
+ pluginId: BUNDLE_ID
111
+ });
112
+ // And the record pages a publish makes stale, which no routing-map entry
113
+ // names (AGL-3475).
114
+ registerPluginLivePaths(async (request)=>(await import("./record-pages/record-page-live-paths.server.js")).recordPageLivePaths(request), {
115
+ pluginId: BUNDLE_ID
116
+ });
71
117
  }
72
118
 
73
119
  //# sourceMappingURL=declarations.server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/data/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\n// The seam from its own module, not the plugin-manager barrel: boot needs the\n// registry and nothing else, and the barrel reaches the client contexts.\nimport { registerFormRecordTarget } from '@aglyn/aglyn/plugin-manager/submission-record-target'\nimport { registerRepeatRowReader } from '@aglyn/aglyn/plugin-manager/repeat-rows'\nimport { registerPluginSiteBundleSection } from '@aglyn/aglyn/plugin-manager/plugin-site-bundle'\nimport { registerPluginUsageMeter } from '@aglyn/aglyn/plugin-manager/plugin-usage-meters'\nimport {\n BUNDLE_ID,\n DATASET_REPEAT_SOURCE_ID,\n DATASET_STORAGE_METER_ID,\n} from './constants/bundle-common'\n\n/**\n * The data plugin's server declarations: the light registrations core reads\n * at boot, before any surface loads.\n *\n * A published page that repeats an element over a dataset asks the platform\n * for the rows, and this is who answers — in every process that composes a\n * page, the tenant's and the console's previews alike. The reader, and the\n * Admin SDK with it, loads on the first page that repeats, not at boot.\n * `plugins.config.json` declares the same source (`repeatSource`), so a boot\n * that skipped this is refused rather than rendered as \"no rows\".\n *\n * A form can also file every submission as a dataset record, and this is the\n * target that stamps such a form with its signed binding when a page renders\n * and writes the record when the submission comes back. Declared the same way\n * (`formRecordTarget`), and just as light: both halves load on first use.\n *\n * A site's datasets are also its section of the whole-site backup: the\n * console's export and restore ask this plugin for them. Declared too\n * (`siteBundleSections`), so a boot that skipped this fails the export rather\n * than shipping a backup without them; the answers load when a backup is made\n * or restored.\n *\n * And the monthly usage sweep, a core cron, measures what the datasets store\n * through this plugin's meter — declared in `usageAxes`, so the sweep refuses\n * to bill a month without it. Its reads load with the first sweep.\n */\nexport function registerDataServerDeclarations(): void {\n registerRepeatRowReader(\n DATASET_REPEAT_SOURCE_ID,\n async (request) =>\n (await import('./repeat/dataset-repeat-rows.server')).readPublishedDatasetRows(\n request,\n ),\n { pluginId: BUNDLE_ID },\n )\n registerFormRecordTarget(\n {\n stamp: async (nodes, hostId) =>\n (await import('./form-target/stamp-form-dataset-bindings')).stampFormDatasetBindings(\n nodes,\n hostId,\n ),\n write: async (request) =>\n (await import('./form-target/dataset-form-record-target.server')).writeFormSubmissionRecord(\n request,\n ),\n },\n { pluginId: BUNDLE_ID },\n )\n const datasets = () => import('./site-bundle/datasets-site-bundle.server')\n registerPluginSiteBundleSection(\n 'datasets',\n {\n export: async (request) => (await datasets()).exportSiteDatasets(request),\n refusal: async (request) => (await datasets()).siteDatasetsRefusal(request),\n import: async (request) => (await datasets()).importSiteDatasets(request),\n },\n { pluginId: BUNDLE_ID },\n )\n registerPluginUsageMeter({\n pluginId: BUNDLE_ID,\n id: DATASET_STORAGE_METER_ID,\n measure: async (context) =>\n (await import('./server/dataset-storage-meter')).measureDatasetStorage(context),\n })\n}\n"],"names":["registerFormRecordTarget","registerRepeatRowReader","registerPluginSiteBundleSection","registerPluginUsageMeter","BUNDLE_ID","DATASET_REPEAT_SOURCE_ID","DATASET_STORAGE_METER_ID","registerDataServerDeclarations","request","readPublishedDatasetRows","pluginId","stamp","nodes","hostId","stampFormDatasetBindings","write","writeFormSubmissionRecord","datasets","export","exportSiteDatasets","refusal","siteDatasetsRefusal","import","importSiteDatasets","id","measure","context","measureDatasetStorage"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,yEAAyE;AACzE,SAASA,wBAAwB,QAAQ,uDAAsD;AAC/F,SAASC,uBAAuB,QAAQ,0CAAyC;AACjF,SAASC,+BAA+B,QAAQ,iDAAgD;AAChG,SAASC,wBAAwB,QAAQ,kDAAiD;AAC1F,SACEC,SAAS,EACTC,wBAAwB,EACxBC,wBAAwB,QACnB,+BAA2B;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,SAASC;IACdN,wBACEI,0BACA,OAAOG,UACL,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGC,wBAAwB,CAC5ED,UAEJ;QAAEE,UAAUN;IAAU;IAExBJ,yBACE;QACEW,OAAO,OAAOC,OAAOC,SACnB,AAAC,CAAA,MAAM,MAAM,CAAC,+CAA2C,EAAGC,wBAAwB,CAClFF,OACAC;QAEJE,OAAO,OAAOP,UACZ,AAAC,CAAA,MAAM,MAAM,CAAC,qDAAiD,EAAGQ,yBAAyB,CACzFR;IAEN,GACA;QAAEE,UAAUN;IAAU;IAExB,MAAMa,WAAW,IAAM,MAAM,CAAC;IAC9Bf,gCACE,YACA;QACEgB,QAAQ,OAAOV,UAAY,AAAC,CAAA,MAAMS,UAAS,EAAGE,kBAAkB,CAACX;QACjEY,SAAS,OAAOZ,UAAY,AAAC,CAAA,MAAMS,UAAS,EAAGI,mBAAmB,CAACb;QACnEc,QAAQ,OAAOd,UAAY,AAAC,CAAA,MAAMS,UAAS,EAAGM,kBAAkB,CAACf;IACnE,GACA;QAAEE,UAAUN;IAAU;IAExBD,yBAAyB;QACvBO,UAAUN;QACVoB,IAAIlB;QACJmB,SAAS,OAAOC,UACd,AAAC,CAAA,MAAM,MAAM,CAAC,oCAAgC,EAAGC,qBAAqB,CAACD;IAC3E;AACF"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/data/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\n// The seam from its own module, not the plugin-manager barrel: boot needs the\n// registry and nothing else, and the barrel reaches the client contexts.\nimport { registerFormRecordTarget } from '@aglyn/aglyn/plugin-manager/submission-record-target'\nimport { registerRepeatRowReader } from '@aglyn/aglyn/plugin-manager/repeat-rows'\nimport { registerPluginSiteBundleSection } from '@aglyn/aglyn/plugin-manager/plugin-site-bundle'\nimport { registerPluginUsageMeter } from '@aglyn/aglyn/plugin-manager/plugin-usage-meters'\nimport { registerServerStepExecutor } from '@aglyn/aglyn/plugin-manager/plugin-server-steps'\nimport { registerCustomFieldType } from '@aglyn/aglyn/plugin-manager/custom-fields'\nimport { registerPluginSitemapReader } from '@aglyn/aglyn/plugin-manager/plugin-sitemap-readers'\nimport { registerPluginLivePaths } from '@aglyn/aglyn/plugin-manager/plugin-live-paths'\nimport {\n BUNDLE_ID,\n DATASET_REPEAT_SOURCE_ID,\n DATASET_STEP_TYPES,\n DATASET_STORAGE_METER_ID,\n} from './constants/bundle-common'\nimport { RECORD_PAGE_ADDRESS_FIELD } from './record-pages/record-pages'\nimport { datasetsSitePackage } from './site-bundle/datasets-package'\n\n/**\n * The data plugin's server declarations: the light registrations core reads\n * at boot, before any surface loads.\n *\n * A published page that repeats an element over a dataset asks the platform\n * for the rows, and this is who answers — in every process that composes a\n * page, the tenant's and the console's previews alike. The reader, and the\n * Admin SDK with it, loads on the first page that repeats, not at boot.\n * `plugins.config.json` declares the same source (`repeatSource`), so a boot\n * that skipped this is refused rather than rendered as \"no rows\".\n *\n * A form can also file every submission as a dataset record, and this is the\n * target that stamps such a form with its signed binding when a page renders\n * and writes the record when the submission comes back. Declared the same way\n * (`formRecordTarget`), and just as light: both halves load on first use.\n *\n * A site's datasets are also its section of the whole-site backup: the\n * console's export and restore ask this plugin for them. Declared too\n * (`siteBundleSections`), so a boot that skipped this fails the export rather\n * than shipping a backup without them; the answers load when a backup is made\n * or restored.\n *\n * And the monthly usage sweep, a core cron, measures what the datasets store\n * through this plugin's meter — declared in `usageAxes`, so the sweep refuses\n * to bill a month without it. Its reads load with the first sweep.\n *\n * An automation that writes a dataset record runs that step through this\n * plugin: the workflows engine reaches it through the platform's server-step\n * seam, in whichever process runs the automation. Declared too (`serverSteps`),\n * so a boot that skipped this fails the step with its reason rather than\n * reporting a record that was never written; the writes load with the first\n * step.\n *\n * And the \"Page address\" field type a record template reads a record's page\n * from, so every write path holds an address to the shape a URL can carry, and\n * the reader that lists record pages in the sitemap — declared too\n * (`sitemapReaders`), so a boot that skipped this degrades the sitemap rather\n * than dropping every record page from it.\n */\nexport function registerDataServerDeclarations(): void {\n registerRepeatRowReader(\n DATASET_REPEAT_SOURCE_ID,\n async (request) =>\n (await import('./repeat/dataset-repeat-rows.server')).readPublishedDatasetRows(\n request,\n ),\n { pluginId: BUNDLE_ID },\n )\n registerFormRecordTarget(\n {\n stamp: async (nodes, hostId) =>\n (await import('./form-target/stamp-form-dataset-bindings')).stampFormDatasetBindings(\n nodes,\n hostId,\n ),\n write: async (request) =>\n (await import('./form-target/dataset-form-record-target.server')).writeFormSubmissionRecord(\n request,\n ),\n },\n { pluginId: BUNDLE_ID },\n )\n const datasets = () => import('./site-bundle/datasets-site-bundle.server')\n registerPluginSiteBundleSection(\n 'datasets',\n {\n export: async (request) => (await datasets()).exportSiteDatasets(request),\n refusal: async (request) => (await datasets()).siteDatasetsRefusal(request),\n import: async (request) => (await datasets()).importSiteDatasets(request),\n package: datasetsSitePackage,\n },\n { pluginId: BUNDLE_ID },\n )\n registerPluginUsageMeter({\n pluginId: BUNDLE_ID,\n id: DATASET_STORAGE_METER_ID,\n measure: async (context) =>\n (await import('./server/dataset-storage-meter')).measureDatasetStorage(context),\n })\n registerServerStepExecutor(\n DATASET_STEP_TYPES,\n async (request) => (await import('./server/dataset-steps.server')).runDatasetStep(request),\n { pluginId: BUNDLE_ID },\n )\n // A record's page address (AGL-3475) is checked on every server path that\n // writes a record, the tenant's form and automation writes included.\n registerCustomFieldType(RECORD_PAGE_ADDRESS_FIELD)\n // Record pages in the sitemap and `/llms.txt` (AGL-3475), declared as the\n // `records` family in `plugins.config.json`; the reads load with the first\n // sitemap a crawler asks for.\n const sitemap = async () =>\n (await import('./record-pages/record-page-sitemap.server')).recordPagesSitemapReader\n registerPluginSitemapReader(\n 'records',\n {\n children: async (request) => (await sitemap()).children(request),\n urls: async (request) => (await sitemap()).urls(request),\n listings: async (request) => (await sitemap()).listings?.(request) ?? [],\n },\n { pluginId: BUNDLE_ID },\n )\n // And the record pages a publish makes stale, which no routing-map entry\n // names (AGL-3475).\n registerPluginLivePaths(\n async (request) =>\n (await import('./record-pages/record-page-live-paths.server')).recordPageLivePaths(\n request,\n ),\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["registerFormRecordTarget","registerRepeatRowReader","registerPluginSiteBundleSection","registerPluginUsageMeter","registerServerStepExecutor","registerCustomFieldType","registerPluginSitemapReader","registerPluginLivePaths","BUNDLE_ID","DATASET_REPEAT_SOURCE_ID","DATASET_STEP_TYPES","DATASET_STORAGE_METER_ID","RECORD_PAGE_ADDRESS_FIELD","datasetsSitePackage","registerDataServerDeclarations","request","readPublishedDatasetRows","pluginId","stamp","nodes","hostId","stampFormDatasetBindings","write","writeFormSubmissionRecord","datasets","export","exportSiteDatasets","refusal","siteDatasetsRefusal","import","importSiteDatasets","package","id","measure","context","measureDatasetStorage","runDatasetStep","sitemap","recordPagesSitemapReader","children","urls","listings","recordPageLivePaths"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,yEAAyE;AACzE,SAASA,wBAAwB,QAAQ,uDAAsD;AAC/F,SAASC,uBAAuB,QAAQ,0CAAyC;AACjF,SAASC,+BAA+B,QAAQ,iDAAgD;AAChG,SAASC,wBAAwB,QAAQ,kDAAiD;AAC1F,SAASC,0BAA0B,QAAQ,kDAAiD;AAC5F,SAASC,uBAAuB,QAAQ,4CAA2C;AACnF,SAASC,2BAA2B,QAAQ,qDAAoD;AAChG,SAASC,uBAAuB,QAAQ,gDAA+C;AACvF,SACEC,SAAS,EACTC,wBAAwB,EACxBC,kBAAkB,EAClBC,wBAAwB,QACnB,+BAA2B;AAClC,SAASC,yBAAyB,QAAQ,iCAA6B;AACvE,SAASC,mBAAmB,QAAQ,oCAAgC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCC,GACD,OAAO,SAASC;IACdb,wBACEQ,0BACA,OAAOM,UACL,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGC,wBAAwB,CAC5ED,UAEJ;QAAEE,UAAUT;IAAU;IAExBR,yBACE;QACEkB,OAAO,OAAOC,OAAOC,SACnB,AAAC,CAAA,MAAM,MAAM,CAAC,+CAA2C,EAAGC,wBAAwB,CAClFF,OACAC;QAEJE,OAAO,OAAOP,UACZ,AAAC,CAAA,MAAM,MAAM,CAAC,qDAAiD,EAAGQ,yBAAyB,CACzFR;IAEN,GACA;QAAEE,UAAUT;IAAU;IAExB,MAAMgB,WAAW,IAAM,MAAM,CAAC;IAC9BtB,gCACE,YACA;QACEuB,QAAQ,OAAOV,UAAY,AAAC,CAAA,MAAMS,UAAS,EAAGE,kBAAkB,CAACX;QACjEY,SAAS,OAAOZ,UAAY,AAAC,CAAA,MAAMS,UAAS,EAAGI,mBAAmB,CAACb;QACnEc,QAAQ,OAAOd,UAAY,AAAC,CAAA,MAAMS,UAAS,EAAGM,kBAAkB,CAACf;QACjEgB,SAASlB;IACX,GACA;QAAEI,UAAUT;IAAU;IAExBL,yBAAyB;QACvBc,UAAUT;QACVwB,IAAIrB;QACJsB,SAAS,OAAOC,UACd,AAAC,CAAA,MAAM,MAAM,CAAC,oCAAgC,EAAGC,qBAAqB,CAACD;IAC3E;IACA9B,2BACEM,oBACA,OAAOK,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGqB,cAAc,CAACrB,UAClF;QAAEE,UAAUT;IAAU;IAExB,0EAA0E;IAC1E,qEAAqE;IACrEH,wBAAwBO;IACxB,0EAA0E;IAC1E,2EAA2E;IAC3E,8BAA8B;IAC9B,MAAMyB,UAAU,UACd,AAAC,CAAA,MAAM,MAAM,CAAC,+CAA2C,EAAGC,wBAAwB;IACtFhC,4BACE,WACA;QACEiC,UAAU,OAAOxB,UAAY,AAAC,CAAA,MAAMsB,SAAQ,EAAGE,QAAQ,CAACxB;QACxDyB,MAAM,OAAOzB,UAAY,AAAC,CAAA,MAAMsB,SAAQ,EAAGG,IAAI,CAACzB;QAChD0B,UAAU,OAAO1B;;gBAAY,WAAC;4BAAD,YAAA,CAAC,QAAA,MAAMsB,WAAWI,QAAQ,qBAA1B,eAAC,OAA4B1B,2BAAY,EAAE;;IAC1E,GACA;QAAEE,UAAUT;IAAU;IAExB,yEAAyE;IACzE,oBAAoB;IACpBD,wBACE,OAAOQ,UACL,AAAC,CAAA,MAAM,MAAM,CAAC,kDAA8C,EAAG2B,mBAAmB,CAChF3B,UAEJ;QAAEE,UAAUT;IAAU;AAE1B"}
@@ -14,13 +14,15 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { checkQuota, datasetIntegrityFields, effectiveDatasetModel, ensureDeclaredCustomFieldTypes, prepareDatasetRecordWrite } from "@aglyn/aglyn/server";
17
+ */ import { checkQuota, ensureDeclaredCustomFieldTypes } from "@aglyn/aglyn/server";
18
18
  import { dataStorageRefusal, firebaseAdmin, orgDataCollectionForHost } from "@aglyn/tenant-data-admin";
19
- import { announceDatasetRecordChange } from "@aglyn/tenant-data-admin/server/dataset-live-pages";
20
- import { resolveDatasetDoc } from "@aglyn/tenant-runtime/resolve-dataset";
19
+ import { announceDatasetRecordChange } from "../server/dataset-live-pages.js";
20
+ import { resolveDatasetDoc } from "../server/resolve-dataset.js";
21
21
  import { FieldValue } from "firebase-admin/firestore";
22
22
  import { verifyFormDatasetBinding } from "./form-dataset-binding-token.js";
23
23
  import { FORM_DATASET_BINDING_FIELD } from "./stamp-form-dataset-bindings.js";
24
+ import { datasetIntegrityFields, effectiveDatasetModel } from "../model/dataset-models.js";
25
+ import { prepareDatasetRecordWrite } from "../model/datasets.js";
24
26
  /**
25
27
  * THE DATASET A SUBMISSION ALSO WRITES A RECORD TO (AGL-141/556), decided on
26
28
  * the server — this plugin's answer to the platform's contract
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/form-target/dataset-form-record-target.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 {\n checkQuota,\n datasetIntegrityFields,\n effectiveDatasetModel,\n ensureDeclaredCustomFieldTypes,\n prepareDatasetRecordWrite,\n} from '@aglyn/aglyn/server'\nimport type {\n FormRecordWriteOutcome,\n FormRecordWriteRequest,\n} from '@aglyn/aglyn/plugin-manager/submission-record-target'\nimport {\n dataStorageRefusal,\n firebaseAdmin,\n orgDataCollectionForHost,\n} from '@aglyn/tenant-data-admin'\nimport { announceDatasetRecordChange } from '@aglyn/tenant-data-admin/server/dataset-live-pages'\nimport { resolveDatasetDoc } from '@aglyn/tenant-runtime/resolve-dataset'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { verifyFormDatasetBinding } from './form-dataset-binding-token'\nimport { FORM_DATASET_BINDING_FIELD } from './stamp-form-dataset-bindings'\n\n/**\n * THE DATASET A SUBMISSION ALSO WRITES A RECORD TO (AGL-141/556), decided on\n * the server — this plugin's answer to the platform's contract\n * (`plugin-manager/submission-record-target.ts`), asked by the submit route\n * after the submission itself is stored.\n *\n * The body is public and unauthenticated, so nothing in it may choose where a\n * record lands: a dataset id or field map taken from it let anyone add rows to\n * any dataset the site can see and send any submitted value into any of its\n * fields. The binding is the one the page's compose read off this form and\n * signed (`stampFormDatasetBindings`): for this site only, and exactly what the\n * form's own props and fields declare, so a form writes where it always wrote.\n * Without a valid signature there is no record. The Inbox copy is canonical\n * either way.\n *\n * What it answers is the submission's `routing`: `dataset` — where the record\n * went — or `datasetRefused` — which values the dataset could not hold. Both\n * are read by the Inbox.\n *\n * Best-effort: a missing dataset or a full record quota never fails the\n * submission, and never claims a row that was not written.\n */\nexport async function writeFormSubmissionRecord(\n request: FormRecordWriteRequest,\n): Promise<FormRecordWriteOutcome> {\n const { hostId, orgId, fields } = request\n const orgBilling = request.orgBilling as Record<string, unknown> | undefined\n const binding = verifyFormDatasetBinding(\n hostId,\n request.body[FORM_DATASET_BINDING_FIELD],\n )\n if (!binding) return {}\n try {\n const firestore = firebaseAdmin.app().firestore()\n // Org-scoped datasets (AGL-237): the form's dataset resolves against the\n // org so every host shares it.\n const datasetsRef = await orgDataCollectionForHost(hostId, 'datasets')\n const datasetDoc = await resolveDatasetDoc(\n datasetsRef,\n { datasetId: binding.datasetId, datasetName: binding.datasetName },\n hostId,\n )\n if (!datasetDoc?.exists || datasetDoc.get('deletedAt')) return {}\n const bound = {\n model: datasetDoc.get('model'),\n fields: Array.isArray(datasetDoc.get('fields'))\n ? datasetDoc.get('fields')\n : [],\n }\n const boundModel = effectiveDatasetModel(bound)\n // `displayName` first, then the legacy `name`, then the name the form was\n // bound by — the same precedence `findDatasetByName` resolves in. Reading\n // only `name` would leave every modern dataset's chip unnamed.\n const datasetLabel = String(\n datasetDoc.get('displayName') ??\n datasetDoc.get('name') ??\n binding.datasetName ??\n '',\n ).slice(0, 60)\n /*\n * CHECKED AGAINST THE MODEL (AGL-2773, option B). Each value is coerced to\n * its field's type and held to the field's rules, the same pair the\n * console and `/v1` run, custom field types included — so a plugin's\n * validator has to be registered first.\n *\n * A refused record is not written, and the submission is kept: the Inbox\n * copy is the visitor's, and a value the dataset cannot hold is no reason\n * to lose it. The refusal is noted on the submission, per field, so the\n * Inbox can say why the row is missing instead of leaving the owner to\n * guess.\n */\n await ensureDeclaredCustomFieldTypes(boundModel)\n const write = prepareDatasetRecordWrite(bound, fields, {\n fieldMap: binding.fieldMap,\n })\n if (Object.keys(write.errors).length) {\n return {\n routing: {\n datasetRefused: {\n id: datasetDoc.id,\n name: datasetLabel,\n errors: write.errors,\n },\n },\n }\n }\n const values = write.values\n if (!Object.keys(values).length) return {}\n const recordCount = (\n await datasetDoc.ref.collection('records').count().get()\n ).data().count\n if (\n !checkQuota(orgBilling as never, 'recordsPerDataset', recordCount).allowed\n ) {\n return {}\n }\n /*\n * BYTES, not just rows (AGL-2253). This leg checked `recordsPerDataset`\n * and never `dataStorageMbPerOrg`, so a public form wired to a dataset\n * wrote past a byte band the console route hard-blocks at — the one\n * dataset-writing path a visitor can drive without an account, and\n * therefore the one where the volume is not the customer's to control.\n *\n * Costs nothing on a metered plan: `dataStorageRefusal` answers `null`\n * with no read whenever the plan carries an `extraDataGbMonthlyUsd` rate,\n * which is every plan that sells the data store. The read is paid only on\n * the two unmetered shapes.\n *\n * Refused like the row quota above — the submission still lands in the\n * Inbox, and `routing.dataset` is not stamped. A lost lead is the worse\n * error here.\n */\n if (\n orgId &&\n (await dataStorageRefusal(\n orgBilling as never,\n firestore.collection('orgs').doc(orgId),\n ))\n ) {\n return {}\n }\n const recordRef = await datasetDoc.ref.collection('records').add({\n values,\n // The integrity index the console's delete check queries. A submission\n // can land in a reference field through a bound form, so this leg\n // carries it like every other write that sets `values`.\n ...datasetIntegrityFields(boundModel, values),\n createdAt: FieldValue.serverTimestamp(),\n })\n /*\n * The pages repeating over the dataset are refreshed (AGL-3113).\n *\n * This is the path the Datasets page's own claim rests on — bind an\n * element to a dataset, publish, and a record written later shows within\n * seconds. Best effort: a refusal from the cache must never turn a stored\n * lead into a lost one.\n */\n if (orgId) {\n await announceDatasetRecordChange({\n firestore,\n orgId,\n datasetId: datasetDoc.id,\n })\n }\n /*\n * Provenance (AGL-2168). `/product/forms`'s hero mockup shows the detail\n * pane carrying `Added to \"Leads\" dataset` under the fields, so the record\n * is named on the submission — only on this path, where a record was\n * really created. A chip claiming a row that a deleted dataset or a full\n * quota refused would be worse than the silence it replaced.\n */\n return {\n routing: {\n dataset: {\n id: datasetDoc.id,\n name: datasetLabel,\n recordId: recordRef.id,\n },\n },\n }\n } catch (error) {\n console.error('form dataset append failed', error)\n return {}\n }\n}\n"],"names":["checkQuota","datasetIntegrityFields","effectiveDatasetModel","ensureDeclaredCustomFieldTypes","prepareDatasetRecordWrite","dataStorageRefusal","firebaseAdmin","orgDataCollectionForHost","announceDatasetRecordChange","resolveDatasetDoc","FieldValue","verifyFormDatasetBinding","FORM_DATASET_BINDING_FIELD","writeFormSubmissionRecord","request","hostId","orgId","fields","orgBilling","binding","body","datasetDoc","firestore","app","datasetsRef","datasetId","datasetName","exists","get","bound","model","Array","isArray","boundModel","datasetLabel","String","slice","write","fieldMap","Object","keys","errors","length","routing","datasetRefused","id","name","values","recordCount","ref","collection","count","data","allowed","doc","recordRef","add","createdAt","serverTimestamp","dataset","recordId","error","console"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/form-target/dataset-form-record-target.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 { checkQuota, ensureDeclaredCustomFieldTypes } from '@aglyn/aglyn/server'\nimport type {\n FormRecordWriteOutcome,\n FormRecordWriteRequest,\n} from '@aglyn/aglyn/plugin-manager/submission-record-target'\nimport {\n dataStorageRefusal,\n firebaseAdmin,\n orgDataCollectionForHost,\n} from '@aglyn/tenant-data-admin'\nimport { announceDatasetRecordChange } from '../server/dataset-live-pages'\nimport { resolveDatasetDoc } from '../server/resolve-dataset'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { verifyFormDatasetBinding } from './form-dataset-binding-token'\nimport { FORM_DATASET_BINDING_FIELD } from './stamp-form-dataset-bindings'\nimport { datasetIntegrityFields, effectiveDatasetModel } from '../model/dataset-models'\nimport { prepareDatasetRecordWrite } from '../model/datasets'\n\n/**\n * THE DATASET A SUBMISSION ALSO WRITES A RECORD TO (AGL-141/556), decided on\n * the server — this plugin's answer to the platform's contract\n * (`plugin-manager/submission-record-target.ts`), asked by the submit route\n * after the submission itself is stored.\n *\n * The body is public and unauthenticated, so nothing in it may choose where a\n * record lands: a dataset id or field map taken from it let anyone add rows to\n * any dataset the site can see and send any submitted value into any of its\n * fields. The binding is the one the page's compose read off this form and\n * signed (`stampFormDatasetBindings`): for this site only, and exactly what the\n * form's own props and fields declare, so a form writes where it always wrote.\n * Without a valid signature there is no record. The Inbox copy is canonical\n * either way.\n *\n * What it answers is the submission's `routing`: `dataset` — where the record\n * went — or `datasetRefused` — which values the dataset could not hold. Both\n * are read by the Inbox.\n *\n * Best-effort: a missing dataset or a full record quota never fails the\n * submission, and never claims a row that was not written.\n */\nexport async function writeFormSubmissionRecord(\n request: FormRecordWriteRequest,\n): Promise<FormRecordWriteOutcome> {\n const { hostId, orgId, fields } = request\n const orgBilling = request.orgBilling as Record<string, unknown> | undefined\n const binding = verifyFormDatasetBinding(\n hostId,\n request.body[FORM_DATASET_BINDING_FIELD],\n )\n if (!binding) return {}\n try {\n const firestore = firebaseAdmin.app().firestore()\n // Org-scoped datasets (AGL-237): the form's dataset resolves against the\n // org so every host shares it.\n const datasetsRef = await orgDataCollectionForHost(hostId, 'datasets')\n const datasetDoc = await resolveDatasetDoc(\n datasetsRef,\n { datasetId: binding.datasetId, datasetName: binding.datasetName },\n hostId,\n )\n if (!datasetDoc?.exists || datasetDoc.get('deletedAt')) return {}\n const bound = {\n model: datasetDoc.get('model'),\n fields: Array.isArray(datasetDoc.get('fields'))\n ? datasetDoc.get('fields')\n : [],\n }\n const boundModel = effectiveDatasetModel(bound)\n // `displayName` first, then the legacy `name`, then the name the form was\n // bound by — the same precedence `findDatasetByName` resolves in. Reading\n // only `name` would leave every modern dataset's chip unnamed.\n const datasetLabel = String(\n datasetDoc.get('displayName') ??\n datasetDoc.get('name') ??\n binding.datasetName ??\n '',\n ).slice(0, 60)\n /*\n * CHECKED AGAINST THE MODEL (AGL-2773, option B). Each value is coerced to\n * its field's type and held to the field's rules, the same pair the\n * console and `/v1` run, custom field types included — so a plugin's\n * validator has to be registered first.\n *\n * A refused record is not written, and the submission is kept: the Inbox\n * copy is the visitor's, and a value the dataset cannot hold is no reason\n * to lose it. The refusal is noted on the submission, per field, so the\n * Inbox can say why the row is missing instead of leaving the owner to\n * guess.\n */\n await ensureDeclaredCustomFieldTypes(boundModel)\n const write = prepareDatasetRecordWrite(bound, fields, {\n fieldMap: binding.fieldMap,\n })\n if (Object.keys(write.errors).length) {\n return {\n routing: {\n datasetRefused: {\n id: datasetDoc.id,\n name: datasetLabel,\n errors: write.errors,\n },\n },\n }\n }\n const values = write.values\n if (!Object.keys(values).length) return {}\n const recordCount = (\n await datasetDoc.ref.collection('records').count().get()\n ).data().count\n if (\n !checkQuota(orgBilling as never, 'recordsPerDataset', recordCount).allowed\n ) {\n return {}\n }\n /*\n * BYTES, not just rows (AGL-2253). This leg checked `recordsPerDataset`\n * and never `dataStorageMbPerOrg`, so a public form wired to a dataset\n * wrote past a byte band the console route hard-blocks at — the one\n * dataset-writing path a visitor can drive without an account, and\n * therefore the one where the volume is not the customer's to control.\n *\n * Costs nothing on a metered plan: `dataStorageRefusal` answers `null`\n * with no read whenever the plan carries an `extraDataGbMonthlyUsd` rate,\n * which is every plan that sells the data store. The read is paid only on\n * the two unmetered shapes.\n *\n * Refused like the row quota above — the submission still lands in the\n * Inbox, and `routing.dataset` is not stamped. A lost lead is the worse\n * error here.\n */\n if (\n orgId &&\n (await dataStorageRefusal(\n orgBilling as never,\n firestore.collection('orgs').doc(orgId),\n ))\n ) {\n return {}\n }\n const recordRef = await datasetDoc.ref.collection('records').add({\n values,\n // The integrity index the console's delete check queries. A submission\n // can land in a reference field through a bound form, so this leg\n // carries it like every other write that sets `values`.\n ...datasetIntegrityFields(boundModel, values),\n createdAt: FieldValue.serverTimestamp(),\n })\n /*\n * The pages repeating over the dataset are refreshed (AGL-3113).\n *\n * This is the path the Datasets page's own claim rests on — bind an\n * element to a dataset, publish, and a record written later shows within\n * seconds. Best effort: a refusal from the cache must never turn a stored\n * lead into a lost one.\n */\n if (orgId) {\n await announceDatasetRecordChange({\n firestore,\n orgId,\n datasetId: datasetDoc.id,\n })\n }\n /*\n * Provenance (AGL-2168). `/product/forms`'s hero mockup shows the detail\n * pane carrying `Added to \"Leads\" dataset` under the fields, so the record\n * is named on the submission — only on this path, where a record was\n * really created. A chip claiming a row that a deleted dataset or a full\n * quota refused would be worse than the silence it replaced.\n */\n return {\n routing: {\n dataset: {\n id: datasetDoc.id,\n name: datasetLabel,\n recordId: recordRef.id,\n },\n },\n }\n } catch (error) {\n console.error('form dataset append failed', error)\n return {}\n }\n}\n"],"names":["checkQuota","ensureDeclaredCustomFieldTypes","dataStorageRefusal","firebaseAdmin","orgDataCollectionForHost","announceDatasetRecordChange","resolveDatasetDoc","FieldValue","verifyFormDatasetBinding","FORM_DATASET_BINDING_FIELD","datasetIntegrityFields","effectiveDatasetModel","prepareDatasetRecordWrite","writeFormSubmissionRecord","request","hostId","orgId","fields","orgBilling","binding","body","datasetDoc","firestore","app","datasetsRef","datasetId","datasetName","exists","get","bound","model","Array","isArray","boundModel","datasetLabel","String","slice","write","fieldMap","Object","keys","errors","length","routing","datasetRefused","id","name","values","recordCount","ref","collection","count","data","allowed","doc","recordRef","add","createdAt","serverTimestamp","dataset","recordId","error","console"],"mappings":"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"}