@aglyn/plugins-data 1.0.0-beta.219 → 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.
Files changed (63) hide show
  1. package/README.md +1 -1
  2. package/package.json +10 -10
  3. package/src/lib/constants/bundle-common.d.ts +2 -0
  4. package/src/lib/constants/bundle-common.js +1 -0
  5. package/src/lib/constants/bundle-common.js.map +1 -1
  6. package/src/lib/declarations.console-server.d.ts +47 -0
  7. package/src/lib/declarations.console-server.js +74 -0
  8. package/src/lib/declarations.console-server.js.map +1 -0
  9. package/src/lib/declarations.server.d.ts +4 -0
  10. package/src/lib/declarations.server.js +11 -1
  11. package/src/lib/declarations.server.js.map +1 -1
  12. package/src/lib/model/dataset-csv.d.ts +36 -0
  13. package/src/lib/model/dataset-csv.js +85 -0
  14. package/src/lib/model/dataset-csv.js.map +1 -0
  15. package/src/lib/model/dataset-io.d.ts +8 -7
  16. package/src/lib/model/dataset-io.js +9 -8
  17. package/src/lib/model/dataset-io.js.map +1 -1
  18. package/src/lib/model/dataset-record-list.d.ts +40 -0
  19. package/src/lib/model/dataset-record-list.js +61 -0
  20. package/src/lib/model/dataset-record-list.js.map +1 -0
  21. package/src/lib/model/dataset-record.d.ts +33 -0
  22. package/src/lib/model/dataset-record.js +68 -0
  23. package/src/lib/model/dataset-record.js.map +1 -0
  24. package/src/lib/plugin.js +3 -0
  25. package/src/lib/plugin.js.map +1 -1
  26. package/src/lib/server/announce-dataset-records.d.ts +37 -0
  27. package/src/lib/server/announce-dataset-records.js +54 -0
  28. package/src/lib/server/announce-dataset-records.js.map +1 -0
  29. package/src/lib/server/api-v1/datasets.d.ts +18 -0
  30. package/src/lib/server/api-v1/datasets.js +646 -0
  31. package/src/lib/server/api-v1/datasets.js.map +1 -0
  32. package/src/lib/server/api-v1/openapi.d.ts +26 -0
  33. package/src/lib/server/api-v1/openapi.js +260 -0
  34. package/src/lib/server/api-v1/openapi.js.map +1 -0
  35. package/src/lib/server/api-v1/usage.d.ts +26 -0
  36. package/src/lib/server/api-v1/usage.js +33 -0
  37. package/src/lib/server/api-v1/usage.js.map +1 -0
  38. package/src/lib/server/custom-field-types.d.ts +39 -0
  39. package/src/lib/server/custom-field-types.js +41 -0
  40. package/src/lib/server/custom-field-types.js.map +1 -0
  41. package/src/lib/server/dataset-figures.d.ts +61 -0
  42. package/src/lib/server/dataset-figures.js +413 -0
  43. package/src/lib/server/dataset-figures.js.map +1 -0
  44. package/src/lib/server/dataset-record-index.d.ts +18 -0
  45. package/src/lib/server/dataset-record-index.js +62 -0
  46. package/src/lib/server/dataset-record-index.js.map +1 -0
  47. package/src/lib/server/dataset-storage-meter.d.ts +29 -0
  48. package/src/lib/server/dataset-storage-meter.js +82 -0
  49. package/src/lib/server/dataset-storage-meter.js.map +1 -0
  50. package/src/lib/server/datasets-export-route.d.ts +57 -0
  51. package/src/lib/server/datasets-export-route.js +229 -0
  52. package/src/lib/server/datasets-export-route.js.map +1 -0
  53. package/src/lib/server/datasets-route.d.ts +38 -0
  54. package/src/lib/server/datasets-route.js +614 -0
  55. package/src/lib/server/datasets-route.js.map +1 -0
  56. package/src/lib/server.d.ts +23 -0
  57. package/src/lib/server.js +54 -0
  58. package/src/lib/server.js.map +1 -0
  59. package/src/lib/site-bundle/datasets-site-bundle.server.js +2 -15
  60. package/src/lib/site-bundle/datasets-site-bundle.server.js.map +1 -1
  61. package/src/lib/usage-axes.d.ts +24 -0
  62. package/src/lib/usage-axes.js +32 -0
  63. package/src/lib/usage-axes.js.map +1 -0
package/README.md CHANGED
@@ -32,7 +32,7 @@ The plugin is console-only and declares one registrar, `registerDataConsole` (th
32
32
 
33
33
  | import | contents |
34
34
  | -- | -- |
35
- | `@aglyn/plugins-data` | `BUNDLE_ID`, `registerDataConsole`, `HostDatasetsCard`, and the import/export model: `parseImportRows`, `mapImportColumns`, plus the CSV helpers re-exported from the core (`parseCsv`, `datasetRecordsToCsv`, `datasetCsvHeader`, `datasetCsvRow`, `datasetRecordToJson`, `serializeDatasetValue`, `countCsvDataRows`, `exportShortfall`) |
35
+ | `@aglyn/plugins-data` | `BUNDLE_ID`, `registerDataConsole`, `HostDatasetsCard`, and the import/export model: `parseImportRows`, `mapImportColumns`, the dataset CSV/JSON writers (`datasetRecordsToCsv`, `datasetCsvHeader`, `datasetCsvRow`, `datasetRecordToJson`, `serializeDatasetValue`), and the platform's CSV helpers re-exported from the core (`parseCsv`, `countCsvDataRows`, `exportShortfall`) |
36
36
  | `@aglyn/plugins-data/*` | any module under `src/lib/`, for example `@aglyn/plugins-data/model/dataset-record-view` (`datasetRecordFields`, `describeDatasetValue`) |
37
37
 
38
38
  ## Usage
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-data",
3
- "version": "1.0.0-beta.219",
3
+ "version": "1.0.0-beta.220",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,15 +25,15 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.219",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.219",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.219",
31
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.219",
32
- "@aglyn/shared-util-http": "1.0.0-beta.219",
33
- "@aglyn/shared-util-timestamp": "1.0.0-beta.219",
34
- "@aglyn/tenant-data-admin": "1.0.0-beta.219",
35
- "@aglyn/tenant-feature-instance": "1.0.0-beta.219",
36
- "@aglyn/tenant-runtime": "1.0.0-beta.219",
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-http": "1.0.0-beta.220",
33
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.220",
34
+ "@aglyn/tenant-data-admin": "1.0.0-beta.220",
35
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.220",
36
+ "@aglyn/tenant-runtime": "1.0.0-beta.220",
37
37
  "@swc/helpers": "0.5.23"
38
38
  },
39
39
  "peerDependencies": {
@@ -12,3 +12,5 @@ export declare const BUNDLE_ID = "data";
12
12
  * alike, and the `repeatSource` this plugin declares in `plugins.config.json`.
13
13
  */
14
14
  export declare const DATASET_REPEAT_SOURCE_ID = "dataset";
15
+ /** The dataset storage meter in the monthly usage sweep (`server/dataset-storage-meter.ts`). */
16
+ export declare const DATASET_STORAGE_METER_ID = "dataset-storage";
@@ -10,5 +10,6 @@
10
10
  * (`repeat-sources.ts`) and the published page's reader (`repeat-rows.ts`)
11
11
  * alike, and the `repeatSource` this plugin declares in `plugins.config.json`.
12
12
  */ export const DATASET_REPEAT_SOURCE_ID = 'dataset';
13
+ /** The dataset storage meter in the monthly usage sweep (`server/dataset-storage-meter.ts`). */ export const DATASET_STORAGE_METER_ID = 'dataset-storage';
13
14
 
14
15
  //# sourceMappingURL=bundle-common.js.map
@@ -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"],"names":["BUNDLE_ID","DATASET_REPEAT_SOURCE_ID"],"mappings":"AAAA;;;;;;CAMC,GAED,OAAO,MAAMA,YAAY,OAAM;AAE/B;;;;CAIC,GACD,OAAO,MAAMC,2BAA2B,UAAS"}
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"}
@@ -0,0 +1,47 @@
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 data plugin's CONSOLE-ONLY server declarations, named under
19
+ * `consoleServerDeclarations` in `plugins.config.json` and run at the
20
+ * console's boot. Everything here is the console's alone, so the tenant
21
+ * runtime does not register any of it.
22
+ *
23
+ * It serves the organization's datasets on the customer REST API,
24
+ * `/v1/datasets/…`: the console's router owns the pipeline in front — the
25
+ * key, the plan's API access, the quota, the rate limit, the error envelope —
26
+ * and hands every request under `/v1/datasets` here once the key is
27
+ * authenticated. No plan feature is named on the registration: the handler
28
+ * answers reads on every plan and refuses a create the plan does not carry
29
+ * (`dataStore`) with its own sentence, as it always has. The resource brings
30
+ * its description for the API's OpenAPI document, and the datasets band
31
+ * joins `GET /v1/usage`.
32
+ *
33
+ * And another plugin that works over the workspace's datasets — an AI job
34
+ * naming a dataset in an automation, a planner listing what a site already
35
+ * has, an insight summarizing a dataset's fields — runs on the console's
36
+ * server, and reads them here rather than from the datasets collection:
37
+ * through the `dataset` record index, and through the dataset figure readers
38
+ * an insight asks.
39
+ *
40
+ * Light at boot: the API handler is imported with its first request, the
41
+ * 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
43
+ * readers reach Firestore only when one is asked. Registering again replaces
44
+ * this plugin's own entries, so a second call (a hot reload, a spec) is
45
+ * harmless.
46
+ */
47
+ export declare function registerDataConsoleServerDeclarations(): void;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ // The registries' own modules, not the data layer's or the plugin-manager's
17
+ // barrel: boot needs these registries, not the whole server surface or the
18
+ // console's client contexts.
19
+ import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
20
+ import { registerApiV1Resource, registerApiV1UsageFigures } from "@aglyn/tenant-data-admin/server/api-v1-resources";
21
+ import { firebaseAdmin } from "@aglyn/tenant-data-admin/server/firebase-admin";
22
+ import { BUNDLE_ID } from "./constants/bundle-common.js";
23
+ import { registerDatasetFigureReaders } from "./server/dataset-figures.js";
24
+ /**
25
+ * The data plugin's CONSOLE-ONLY server declarations, named under
26
+ * `consoleServerDeclarations` in `plugins.config.json` and run at the
27
+ * console's boot. Everything here is the console's alone, so the tenant
28
+ * runtime does not register any of it.
29
+ *
30
+ * It serves the organization's datasets on the customer REST API,
31
+ * `/v1/datasets/…`: the console's router owns the pipeline in front — the
32
+ * key, the plan's API access, the quota, the rate limit, the error envelope —
33
+ * and hands every request under `/v1/datasets` here once the key is
34
+ * authenticated. No plan feature is named on the registration: the handler
35
+ * answers reads on every plan and refuses a create the plan does not carry
36
+ * (`dataStore`) with its own sentence, as it always has. The resource brings
37
+ * its description for the API's OpenAPI document, and the datasets band
38
+ * joins `GET /v1/usage`.
39
+ *
40
+ * And another plugin that works over the workspace's datasets — an AI job
41
+ * naming a dataset in an automation, a planner listing what a site already
42
+ * has, an insight summarizing a dataset's fields — runs on the console's
43
+ * server, and reads them here rather than from the datasets collection:
44
+ * through the `dataset` record index, and through the dataset figure readers
45
+ * an insight asks.
46
+ *
47
+ * Light at boot: the API handler is imported with its first request, the
48
+ * 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
50
+ * readers reach Firestore only when one is asked. Registering again replaces
51
+ * this plugin's own entries, so a second call (a hot reload, a spec) is
52
+ * harmless.
53
+ */ export function registerDataConsoleServerDeclarations() {
54
+ registerApiV1Resource('datasets', {
55
+ handle: async (...args)=>(await import("./server/api-v1/datasets.js")).handleDatasets(...args),
56
+ describe: async ()=>(await import("./server/api-v1/openapi.js")).DATASETS_API_V1_DESCRIPTION
57
+ }, {
58
+ pluginId: BUNDLE_ID
59
+ });
60
+ registerApiV1UsageFigures(async (ctx)=>(await import("./server/api-v1/usage.js")).datasetUsageFigures(ctx), {
61
+ pluginId: BUNDLE_ID
62
+ });
63
+ registerPluginRecordIndex('dataset', lazyDatasetIndex, {
64
+ pluginId: BUNDLE_ID
65
+ });
66
+ registerDatasetFigureReaders(()=>firebaseAdmin.app().firestore());
67
+ }
68
+ const loadDatasetIndex = async ()=>(await import("./server/dataset-record-index.js")).datasetRecordIndex;
69
+ const lazyDatasetIndex = {
70
+ list: async (request)=>(await loadDatasetIndex()).list(request),
71
+ get: async (request)=>(await loadDatasetIndex()).get(request)
72
+ };
73
+
74
+ //# sourceMappingURL=declarations.console-server.js.map
@@ -0,0 +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"}
@@ -35,5 +35,9 @@
35
35
  * (`siteBundleSections`), so a boot that skipped this fails the export rather
36
36
  * than shipping a backup without them; the answers load when a backup is made
37
37
  * or restored.
38
+ *
39
+ * And the monthly usage sweep, a core cron, measures what the datasets store
40
+ * through this plugin's meter — declared in `usageAxes`, so the sweep refuses
41
+ * to bill a month without it. Its reads load with the first sweep.
38
42
  */
39
43
  export declare function registerDataServerDeclarations(): void;
@@ -18,7 +18,8 @@
18
18
  import { registerFormRecordTarget } from "@aglyn/aglyn/plugin-manager/submission-record-target";
19
19
  import { registerRepeatRowReader } from "@aglyn/aglyn/plugin-manager/repeat-rows";
20
20
  import { registerPluginSiteBundleSection } from "@aglyn/aglyn/plugin-manager/plugin-site-bundle";
21
- import { BUNDLE_ID, DATASET_REPEAT_SOURCE_ID } from "./constants/bundle-common.js";
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
23
  /**
23
24
  * The data plugin's server declarations: the light registrations core reads
24
25
  * at boot, before any surface loads.
@@ -40,6 +41,10 @@ import { BUNDLE_ID, DATASET_REPEAT_SOURCE_ID } from "./constants/bundle-common.j
40
41
  * (`siteBundleSections`), so a boot that skipped this fails the export rather
41
42
  * than shipping a backup without them; the answers load when a backup is made
42
43
  * or restored.
44
+ *
45
+ * And the monthly usage sweep, a core cron, measures what the datasets store
46
+ * through this plugin's meter — declared in `usageAxes`, so the sweep refuses
47
+ * to bill a month without it. Its reads load with the first sweep.
43
48
  */ export function registerDataServerDeclarations() {
44
49
  registerRepeatRowReader(DATASET_REPEAT_SOURCE_ID, async (request)=>(await import("./repeat/dataset-repeat-rows.server.js")).readPublishedDatasetRows(request), {
45
50
  pluginId: BUNDLE_ID
@@ -58,6 +63,11 @@ import { BUNDLE_ID, DATASET_REPEAT_SOURCE_ID } from "./constants/bundle-common.j
58
63
  }, {
59
64
  pluginId: BUNDLE_ID
60
65
  });
66
+ registerPluginUsageMeter({
67
+ pluginId: BUNDLE_ID,
68
+ id: DATASET_STORAGE_METER_ID,
69
+ measure: async (context)=>(await import("./server/dataset-storage-meter.js")).measureDatasetStorage(context)
70
+ });
61
71
  }
62
72
 
63
73
  //# 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 { BUNDLE_ID, DATASET_REPEAT_SOURCE_ID } 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 */\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}\n"],"names":["registerFormRecordTarget","registerRepeatRowReader","registerPluginSiteBundleSection","BUNDLE_ID","DATASET_REPEAT_SOURCE_ID","registerDataServerDeclarations","request","readPublishedDatasetRows","pluginId","stamp","nodes","hostId","stampFormDatasetBindings","write","writeFormSubmissionRecord","datasets","export","exportSiteDatasets","refusal","siteDatasetsRefusal","import","importSiteDatasets"],"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,SAAS,EAAEC,wBAAwB,QAAQ,+BAA2B;AAE/E;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,SAASC;IACdJ,wBACEG,0BACA,OAAOE,UACL,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGC,wBAAwB,CAC5ED,UAEJ;QAAEE,UAAUL;IAAU;IAExBH,yBACE;QACES,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,UAAUL;IAAU;IAExB,MAAMY,WAAW,IAAM,MAAM,CAAC;IAC9Bb,gCACE,YACA;QACEc,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,UAAUL;IAAU;AAE1B"}
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"}
@@ -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
+ */
17
+ import type { DatasetFieldDefinition, DatasetModel } from '@aglyn/aglyn/app-utils/dataset-models';
18
+ /** Storage value → portable string (CSV cell / JSON value). */
19
+ export declare function serializeDatasetValue(field: DatasetFieldDefinition, value: unknown): string;
20
+ /**
21
+ * The header line — field ids, in model order (AGL-2335).
22
+ *
23
+ * Split out of {@link datasetRecordsToCsv} so a server export can emit the
24
+ * file a page at a time. The whole point of that export is that it is not
25
+ * bounded by what one process can hold in memory, and a helper that only
26
+ * takes a complete `rows` array cannot serve it. Everything below shares
27
+ * one escaper: there are already five divergent hand-rolled CSV escapers in
28
+ * this repo and this is not becoming the sixth.
29
+ */
30
+ export declare function datasetCsvHeader(model: DatasetModel): string;
31
+ /** One record's values (storage form) → one CSV line, no terminator. */
32
+ export declare function datasetCsvRow(model: DatasetModel, row: Record<string, unknown>): string;
33
+ /** One record's values → the portable JSON object the JSON export emits. */
34
+ export declare function datasetRecordToJson(model: DatasetModel, row: Record<string, unknown>): Record<string, string>;
35
+ /** Rows (storage-form value maps) → CSV with a fieldId header row. */
36
+ export declare function datasetRecordsToCsv(model: DatasetModel, rows: Array<Record<string, unknown>>): string;
@@ -0,0 +1,85 @@
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
+ * Dataset CSV/JSON serialization (AGL-182). The predictable inverse of
18
+ * `coerceDocumentValues`: ISO dates, `lat, lon` coordinates, comma-joined
19
+ * lists, JSON maps, reference ids — so an export re-imports losslessly
20
+ * through the same coercion.
21
+ *
22
+ * This plugin's own: the export route that streams a dataset a page at a
23
+ * time and the hub's client-side download both write through it. The
24
+ * escaper, the parser and the row-count check are the platform's
25
+ * (`app-utils/csv`), because the email, commerce and CRM imports read files
26
+ * too and may not reach this plugin.
27
+ */ import { escapeCsvCell as csvEscape } from "@aglyn/aglyn/app-utils/csv";
28
+ /** Storage value → portable string (CSV cell / JSON value). */ export function serializeDatasetValue(field, value) {
29
+ if (value == null) return '';
30
+ switch(field.type){
31
+ case 'timestamp':
32
+ return typeof value === 'number' && Number.isFinite(value) ? new Date(value).toISOString() : String(value);
33
+ case 'coordinates':
34
+ {
35
+ const coordinates = value;
36
+ return coordinates && typeof coordinates.latitude === 'number' && typeof coordinates.longitude === 'number' ? `${coordinates.latitude}, ${coordinates.longitude}` : String(value);
37
+ }
38
+ case 'sorted':
39
+ case 'reference':
40
+ return Array.isArray(value) ? value.join(', ') : String(value);
41
+ case 'map':
42
+ try {
43
+ return JSON.stringify(value);
44
+ } catch (unused) {
45
+ return String(value);
46
+ }
47
+ default:
48
+ return String(value);
49
+ }
50
+ }
51
+ /**
52
+ * The header line — field ids, in model order (AGL-2335).
53
+ *
54
+ * Split out of {@link datasetRecordsToCsv} so a server export can emit the
55
+ * file a page at a time. The whole point of that export is that it is not
56
+ * bounded by what one process can hold in memory, and a helper that only
57
+ * takes a complete `rows` array cannot serve it. Everything below shares
58
+ * one escaper: there are already five divergent hand-rolled CSV escapers in
59
+ * this repo and this is not becoming the sixth.
60
+ */ export function datasetCsvHeader(model) {
61
+ return model.order.map(csvEscape).join(',');
62
+ }
63
+ /** One record's values (storage form) → one CSV line, no terminator. */ export function datasetCsvRow(model, row) {
64
+ return model.order.map((fieldId)=>{
65
+ const field = model.fields[fieldId];
66
+ return csvEscape(field ? serializeDatasetValue(field, row[fieldId]) : '');
67
+ }).join(',');
68
+ }
69
+ /** One record's values → the portable JSON object the JSON export emits. */ export function datasetRecordToJson(model, row) {
70
+ return Object.fromEntries(model.order.map((fieldId)=>{
71
+ const field = model.fields[fieldId];
72
+ return [
73
+ fieldId,
74
+ field ? serializeDatasetValue(field, row[fieldId]) : ''
75
+ ];
76
+ }));
77
+ }
78
+ /** Rows (storage-form value maps) → CSV with a fieldId header row. */ export function datasetRecordsToCsv(model, rows) {
79
+ return [
80
+ datasetCsvHeader(model),
81
+ ...rows.map((row)=>datasetCsvRow(model, row))
82
+ ].join('\n');
83
+ }
84
+
85
+ //# sourceMappingURL=dataset-csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/model/dataset-csv.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Dataset CSV/JSON serialization (AGL-182). The predictable inverse of\n * `coerceDocumentValues`: ISO dates, `lat, lon` coordinates, comma-joined\n * lists, JSON maps, reference ids — so an export re-imports losslessly\n * through the same coercion.\n *\n * This plugin's own: the export route that streams a dataset a page at a\n * time and the hub's client-side download both write through it. The\n * escaper, the parser and the row-count check are the platform's\n * (`app-utils/csv`), because the email, commerce and CRM imports read files\n * too and may not reach this plugin.\n */\n\nimport { escapeCsvCell as csvEscape } from '@aglyn/aglyn/app-utils/csv'\nimport type {\n DatasetFieldDefinition,\n DatasetModel,\n} from '@aglyn/aglyn/app-utils/dataset-models'\n\n\n/** Storage value → portable string (CSV cell / JSON value). */\nexport function serializeDatasetValue(\n field: DatasetFieldDefinition,\n value: unknown,\n): string {\n if (value == null) return ''\n switch (field.type) {\n case 'timestamp':\n return typeof value === 'number' && Number.isFinite(value)\n ? new Date(value).toISOString()\n : String(value)\n case 'coordinates': {\n const coordinates = value as { latitude?: number; longitude?: number }\n return coordinates &&\n typeof coordinates.latitude === 'number' &&\n typeof coordinates.longitude === 'number'\n ? `${coordinates.latitude}, ${coordinates.longitude}`\n : String(value)\n }\n case 'sorted':\n case 'reference':\n return Array.isArray(value) ? value.join(', ') : String(value)\n case 'map':\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n default:\n return String(value)\n }\n}\n\n/**\n * The header line — field ids, in model order (AGL-2335).\n *\n * Split out of {@link datasetRecordsToCsv} so a server export can emit the\n * file a page at a time. The whole point of that export is that it is not\n * bounded by what one process can hold in memory, and a helper that only\n * takes a complete `rows` array cannot serve it. Everything below shares\n * one escaper: there are already five divergent hand-rolled CSV escapers in\n * this repo and this is not becoming the sixth.\n */\nexport function datasetCsvHeader(model: DatasetModel): string {\n return model.order.map(csvEscape).join(',')\n}\n\n/** One record's values (storage form) → one CSV line, no terminator. */\nexport function datasetCsvRow(\n model: DatasetModel,\n row: Record<string, unknown>,\n): string {\n return model.order\n .map((fieldId) => {\n const field = model.fields[fieldId]\n return csvEscape(field ? serializeDatasetValue(field, row[fieldId]) : '')\n })\n .join(',')\n}\n\n/** One record's values → the portable JSON object the JSON export emits. */\nexport function datasetRecordToJson(\n model: DatasetModel,\n row: Record<string, unknown>,\n): Record<string, string> {\n return Object.fromEntries(\n model.order.map((fieldId) => {\n const field = model.fields[fieldId]\n return [fieldId, field ? serializeDatasetValue(field, row[fieldId]) : '']\n }),\n )\n}\n\n/** Rows (storage-form value maps) → CSV with a fieldId header row. */\nexport function datasetRecordsToCsv(\n model: DatasetModel,\n rows: Array<Record<string, unknown>>,\n): string {\n return [\n datasetCsvHeader(model),\n ...rows.map((row) => datasetCsvRow(model, row)),\n ].join('\\n')\n}\n"],"names":["escapeCsvCell","csvEscape","serializeDatasetValue","field","value","type","Number","isFinite","Date","toISOString","String","coordinates","latitude","longitude","Array","isArray","join","JSON","stringify","datasetCsvHeader","model","order","map","datasetCsvRow","row","fieldId","fields","datasetRecordToJson","Object","fromEntries","datasetRecordsToCsv","rows"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;CAWC,GAED,SAASA,iBAAiBC,SAAS,QAAQ,6BAA4B;AAOvE,6DAA6D,GAC7D,OAAO,SAASC,sBACdC,KAA6B,EAC7BC,KAAc;IAEd,IAAIA,SAAS,MAAM,OAAO;IAC1B,OAAQD,MAAME,IAAI;QAChB,KAAK;YACH,OAAO,OAAOD,UAAU,YAAYE,OAAOC,QAAQ,CAACH,SAChD,IAAII,KAAKJ,OAAOK,WAAW,KAC3BC,OAAON;QACb,KAAK;YAAe;gBAClB,MAAMO,cAAcP;gBACpB,OAAOO,eACL,OAAOA,YAAYC,QAAQ,KAAK,YAChC,OAAOD,YAAYE,SAAS,KAAK,WAC/B,GAAGF,YAAYC,QAAQ,CAAC,EAAE,EAAED,YAAYE,SAAS,EAAE,GACnDH,OAAON;YACb;QACA,KAAK;QACL,KAAK;YACH,OAAOU,MAAMC,OAAO,CAACX,SAASA,MAAMY,IAAI,CAAC,QAAQN,OAAON;QAC1D,KAAK;YACH,IAAI;gBACF,OAAOa,KAAKC,SAAS,CAACd;YACxB,EAAE,eAAM;gBACN,OAAOM,OAAON;YAChB;QACF;YACE,OAAOM,OAAON;IAClB;AACF;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASe,iBAAiBC,KAAmB;IAClD,OAAOA,MAAMC,KAAK,CAACC,GAAG,CAACrB,WAAWe,IAAI,CAAC;AACzC;AAEA,sEAAsE,GACtE,OAAO,SAASO,cACdH,KAAmB,EACnBI,GAA4B;IAE5B,OAAOJ,MAAMC,KAAK,CACfC,GAAG,CAAC,CAACG;QACJ,MAAMtB,QAAQiB,MAAMM,MAAM,CAACD,QAAQ;QACnC,OAAOxB,UAAUE,QAAQD,sBAAsBC,OAAOqB,GAAG,CAACC,QAAQ,IAAI;IACxE,GACCT,IAAI,CAAC;AACV;AAEA,0EAA0E,GAC1E,OAAO,SAASW,oBACdP,KAAmB,EACnBI,GAA4B;IAE5B,OAAOI,OAAOC,WAAW,CACvBT,MAAMC,KAAK,CAACC,GAAG,CAAC,CAACG;QACf,MAAMtB,QAAQiB,MAAMM,MAAM,CAACD,QAAQ;QACnC,OAAO;YAACA;YAAStB,QAAQD,sBAAsBC,OAAOqB,GAAG,CAACC,QAAQ,IAAI;SAAG;IAC3E;AAEJ;AAEA,oEAAoE,GACpE,OAAO,SAASK,oBACdV,KAAmB,EACnBW,IAAoC;IAEpC,OAAO;QACLZ,iBAAiBC;WACdW,KAAKT,GAAG,CAAC,CAACE,MAAQD,cAAcH,OAAOI;KAC3C,CAACR,IAAI,CAAC;AACT"}
@@ -17,15 +17,16 @@
17
17
  /**
18
18
  * Dataset CSV/JSON round-tripping (AGL-182) — the IMPORT half.
19
19
  *
20
- * Both the serialization half and the CSV parser live in `@aglyn/aglyn`'s
21
- * `dataset-csv` (AGL-2335) so that the server export route and the email
22
- * list importer can share one escaper and one parser; both are re-exported
23
- * here unchanged, so `@aglyn/plugins-data/model` remains the one import site
24
- * for everything dataset-io. What stays here is the part that is about
25
- * DATASETS — mapping a file's columns onto a `DatasetModel`.
20
+ * The serialization half is `dataset-csv.ts` beside this, and the CSV parser
21
+ * and the row-count check are the platform's `app-utils/csv` (AGL-2335), so
22
+ * the email, commerce and CRM imports read files with the same parser; all of
23
+ * them are re-exported here, so `@aglyn/plugins-data/model` remains the one
24
+ * import site for everything dataset-io. What stays here is the part that is
25
+ * about DATASETS — mapping a file's columns onto a `DatasetModel`.
26
26
  */
27
27
  import type { DatasetModel } from '@aglyn/aglyn';
28
- export { countCsvDataRows, datasetCsvHeader, datasetCsvRow, datasetRecordToJson, datasetRecordsToCsv, exportShortfall, parseCsv, serializeDatasetValue, } from '@aglyn/aglyn/app-utils/dataset-csv';
28
+ export { countCsvDataRows, exportShortfall, parseCsv, } from '@aglyn/aglyn/app-utils/csv';
29
+ export { datasetCsvHeader, datasetCsvRow, datasetRecordToJson, datasetRecordsToCsv, serializeDatasetValue, } from './dataset-csv';
29
30
  /**
30
31
  * Parses pasted/uploaded import text — a JSON array of objects or CSV
31
32
  * with a header row — into raw string maps keyed by the source column
@@ -16,14 +16,15 @@
16
16
  */ /**
17
17
  * Dataset CSV/JSON round-tripping (AGL-182) — the IMPORT half.
18
18
  *
19
- * Both the serialization half and the CSV parser live in `@aglyn/aglyn`'s
20
- * `dataset-csv` (AGL-2335) so that the server export route and the email
21
- * list importer can share one escaper and one parser; both are re-exported
22
- * here unchanged, so `@aglyn/plugins-data/model` remains the one import site
23
- * for everything dataset-io. What stays here is the part that is about
24
- * DATASETS — mapping a file's columns onto a `DatasetModel`.
25
- */ import { parseCsv } from "@aglyn/aglyn/app-utils/dataset-csv";
26
- export { countCsvDataRows, datasetCsvHeader, datasetCsvRow, datasetRecordToJson, datasetRecordsToCsv, exportShortfall, parseCsv, serializeDatasetValue } from "@aglyn/aglyn/app-utils/dataset-csv";
19
+ * The serialization half is `dataset-csv.ts` beside this, and the CSV parser
20
+ * and the row-count check are the platform's `app-utils/csv` (AGL-2335), so
21
+ * the email, commerce and CRM imports read files with the same parser; all of
22
+ * them are re-exported here, so `@aglyn/plugins-data/model` remains the one
23
+ * import site for everything dataset-io. What stays here is the part that is
24
+ * about DATASETS — mapping a file's columns onto a `DatasetModel`.
25
+ */ import { parseCsv } from "@aglyn/aglyn/app-utils/csv";
26
+ export { countCsvDataRows, exportShortfall, parseCsv } from "@aglyn/aglyn/app-utils/csv";
27
+ export { datasetCsvHeader, datasetCsvRow, datasetRecordToJson, datasetRecordsToCsv, serializeDatasetValue } from "./dataset-csv.js";
27
28
  /**
28
29
  * Parses pasted/uploaded import text — a JSON array of objects or CSV
29
30
  * with a header row — into raw string maps keyed by the source column
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/model/dataset-io.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Dataset CSV/JSON round-tripping (AGL-182) — the IMPORT half.\n *\n * Both the serialization half and the CSV parser live in `@aglyn/aglyn`'s\n * `dataset-csv` (AGL-2335) so that the server export route and the email\n * list importer can share one escaper and one parser; both are re-exported\n * here unchanged, so `@aglyn/plugins-data/model` remains the one import site\n * for everything dataset-io. What stays here is the part that is about\n * DATASETS — mapping a file's columns onto a `DatasetModel`.\n */\n\nimport type { DatasetModel } from '@aglyn/aglyn'\nimport { parseCsv } from '@aglyn/aglyn/app-utils/dataset-csv'\nexport {\n countCsvDataRows,\n datasetCsvHeader,\n datasetCsvRow,\n datasetRecordToJson,\n datasetRecordsToCsv,\n exportShortfall,\n parseCsv,\n serializeDatasetValue,\n} from '@aglyn/aglyn/app-utils/dataset-csv'\n\n/**\n * Parses pasted/uploaded import text — a JSON array of objects or CSV\n * with a header row — into raw string maps keyed by the source column\n * names. Returns null when nothing parseable is found.\n */\nexport function parseImportRows(\n text: string,\n): Array<Record<string, string>> | null {\n const trimmed = String(text ?? '').trim()\n if (!trimmed) return null\n if (trimmed.startsWith('[') || trimmed.startsWith('{')) {\n try {\n const parsed = JSON.parse(trimmed)\n const list = Array.isArray(parsed) ? parsed : [parsed]\n return list\n .filter((entry) => entry && typeof entry === 'object')\n .map((entry) =>\n Object.fromEntries(\n Object.entries(entry as Record<string, unknown>).map(\n ([key, value]) => [\n key,\n typeof value === 'string' ? value : JSON.stringify(value),\n ],\n ),\n ),\n )\n } catch {\n return null\n }\n }\n const rows = parseCsv(trimmed)\n if (rows.length < 2) return null\n const [header, ...body] = rows\n return body.map((cells) =>\n Object.fromEntries(\n header.map((column, index) => [column.trim(), cells[index] ?? '']),\n ),\n )\n}\n\n/**\n * Maps raw import columns to model fieldIds: exact fieldId match first,\n * then case-insensitive display-name match. Unmatched columns are\n * reported, never silently dropped.\n */\nexport function mapImportColumns(\n model: DatasetModel,\n columns: string[],\n): { mapping: Record<string, string>; unmatched: string[] } {\n const byName = new Map<string, string>()\n for (const fieldId of model.order) {\n byName.set(fieldId.toLowerCase(), fieldId)\n const display = model.fields[fieldId]?.name\n if (display) byName.set(display.trim().toLowerCase(), fieldId)\n }\n const mapping: Record<string, string> = {}\n const unmatched: string[] = []\n for (const column of columns) {\n const fieldId = byName.get(column.trim().toLowerCase())\n if (fieldId) mapping[column] = fieldId\n else unmatched.push(column)\n }\n return { mapping, unmatched }\n}\n"],"names":["parseCsv","countCsvDataRows","datasetCsvHeader","datasetCsvRow","datasetRecordToJson","datasetRecordsToCsv","exportShortfall","serializeDatasetValue","parseImportRows","text","trimmed","String","trim","startsWith","parsed","JSON","parse","list","Array","isArray","filter","entry","map","Object","fromEntries","entries","key","value","stringify","rows","length","header","body","cells","column","index","mapImportColumns","model","columns","byName","Map","fieldId","order","set","toLowerCase","display","fields","name","mapping","unmatched","get","push"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;CASC,GAGD,SAASA,QAAQ,QAAQ,qCAAoC;AAC7D,SACEC,gBAAgB,EAChBC,gBAAgB,EAChBC,aAAa,EACbC,mBAAmB,EACnBC,mBAAmB,EACnBC,eAAe,EACfN,QAAQ,EACRO,qBAAqB,QAChB,qCAAoC;AAE3C;;;;CAIC,GACD,OAAO,SAASC,gBACdC,IAAY;IAEZ,MAAMC,UAAUC,OAAOF,eAAAA,OAAQ,IAAIG,IAAI;IACvC,IAAI,CAACF,SAAS,OAAO;IACrB,IAAIA,QAAQG,UAAU,CAAC,QAAQH,QAAQG,UAAU,CAAC,MAAM;QACtD,IAAI;YACF,MAAMC,SAASC,KAAKC,KAAK,CAACN;YAC1B,MAAMO,OAAOC,MAAMC,OAAO,CAACL,UAAUA,SAAS;gBAACA;aAAO;YACtD,OAAOG,KACJG,MAAM,CAAC,CAACC,QAAUA,SAAS,OAAOA,UAAU,UAC5CC,GAAG,CAAC,CAACD,QACJE,OAAOC,WAAW,CAChBD,OAAOE,OAAO,CAACJ,OAAkCC,GAAG,CAClD,CAAC,CAACI,KAAKC,MAAM,GAAK;wBAChBD;wBACA,OAAOC,UAAU,WAAWA,QAAQZ,KAAKa,SAAS,CAACD;qBACpD;QAIX,EAAE,eAAM;YACN,OAAO;QACT;IACF;IACA,MAAME,OAAO7B,SAASU;IACtB,IAAImB,KAAKC,MAAM,GAAG,GAAG,OAAO;IAC5B,MAAM,CAACC,QAAQ,GAAGC,KAAK,GAAGH;IAC1B,OAAOG,KAAKV,GAAG,CAAC,CAACW,QACfV,OAAOC,WAAW,CAChBO,OAAOT,GAAG,CAAC,CAACY,QAAQC;gBAA0BF;mBAAhB;gBAACC,OAAOtB,IAAI;iBAAIqB,eAAAA,KAAK,CAACE,MAAM,YAAZF,eAAgB;aAAG;;AAGvE;AAEA;;;;CAIC,GACD,OAAO,SAASG,iBACdC,KAAmB,EACnBC,OAAiB;IAEjB,MAAMC,SAAS,IAAIC;IACnB,KAAK,MAAMC,WAAWJ,MAAMK,KAAK,CAAE;YAEjBL;QADhBE,OAAOI,GAAG,CAACF,QAAQG,WAAW,IAAIH;QAClC,MAAMI,WAAUR,wBAAAA,MAAMS,MAAM,CAACL,QAAQ,qBAArBJ,sBAAuBU,IAAI;QAC3C,IAAIF,SAASN,OAAOI,GAAG,CAACE,QAAQjC,IAAI,GAAGgC,WAAW,IAAIH;IACxD;IACA,MAAMO,UAAkC,CAAC;IACzC,MAAMC,YAAsB,EAAE;IAC9B,KAAK,MAAMf,UAAUI,QAAS;QAC5B,MAAMG,UAAUF,OAAOW,GAAG,CAAChB,OAAOtB,IAAI,GAAGgC,WAAW;QACpD,IAAIH,SAASO,OAAO,CAACd,OAAO,GAAGO;aAC1BQ,UAAUE,IAAI,CAACjB;IACtB;IACA,OAAO;QAAEc;QAASC;IAAU;AAC9B"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/model/dataset-io.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Dataset CSV/JSON round-tripping (AGL-182) — the IMPORT half.\n *\n * The serialization half is `dataset-csv.ts` beside this, and the CSV parser\n * and the row-count check are the platform's `app-utils/csv` (AGL-2335), so\n * the email, commerce and CRM imports read files with the same parser; all of\n * them are re-exported here, so `@aglyn/plugins-data/model` remains the one\n * import site for everything dataset-io. What stays here is the part that is\n * about DATASETS — mapping a file's columns onto a `DatasetModel`.\n */\n\nimport type { DatasetModel } from '@aglyn/aglyn'\nimport { parseCsv } from '@aglyn/aglyn/app-utils/csv'\nexport {\n countCsvDataRows,\n exportShortfall,\n parseCsv,\n} from '@aglyn/aglyn/app-utils/csv'\nexport {\n datasetCsvHeader,\n datasetCsvRow,\n datasetRecordToJson,\n datasetRecordsToCsv,\n serializeDatasetValue,\n} from './dataset-csv'\n\n/**\n * Parses pasted/uploaded import text — a JSON array of objects or CSV\n * with a header row — into raw string maps keyed by the source column\n * names. Returns null when nothing parseable is found.\n */\nexport function parseImportRows(\n text: string,\n): Array<Record<string, string>> | null {\n const trimmed = String(text ?? '').trim()\n if (!trimmed) return null\n if (trimmed.startsWith('[') || trimmed.startsWith('{')) {\n try {\n const parsed = JSON.parse(trimmed)\n const list = Array.isArray(parsed) ? parsed : [parsed]\n return list\n .filter((entry) => entry && typeof entry === 'object')\n .map((entry) =>\n Object.fromEntries(\n Object.entries(entry as Record<string, unknown>).map(\n ([key, value]) => [\n key,\n typeof value === 'string' ? value : JSON.stringify(value),\n ],\n ),\n ),\n )\n } catch {\n return null\n }\n }\n const rows = parseCsv(trimmed)\n if (rows.length < 2) return null\n const [header, ...body] = rows\n return body.map((cells) =>\n Object.fromEntries(\n header.map((column, index) => [column.trim(), cells[index] ?? '']),\n ),\n )\n}\n\n/**\n * Maps raw import columns to model fieldIds: exact fieldId match first,\n * then case-insensitive display-name match. Unmatched columns are\n * reported, never silently dropped.\n */\nexport function mapImportColumns(\n model: DatasetModel,\n columns: string[],\n): { mapping: Record<string, string>; unmatched: string[] } {\n const byName = new Map<string, string>()\n for (const fieldId of model.order) {\n byName.set(fieldId.toLowerCase(), fieldId)\n const display = model.fields[fieldId]?.name\n if (display) byName.set(display.trim().toLowerCase(), fieldId)\n }\n const mapping: Record<string, string> = {}\n const unmatched: string[] = []\n for (const column of columns) {\n const fieldId = byName.get(column.trim().toLowerCase())\n if (fieldId) mapping[column] = fieldId\n else unmatched.push(column)\n }\n return { mapping, unmatched }\n}\n"],"names":["parseCsv","countCsvDataRows","exportShortfall","datasetCsvHeader","datasetCsvRow","datasetRecordToJson","datasetRecordsToCsv","serializeDatasetValue","parseImportRows","text","trimmed","String","trim","startsWith","parsed","JSON","parse","list","Array","isArray","filter","entry","map","Object","fromEntries","entries","key","value","stringify","rows","length","header","body","cells","column","index","mapImportColumns","model","columns","byName","Map","fieldId","order","set","toLowerCase","display","fields","name","mapping","unmatched","get","push"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;CASC,GAGD,SAASA,QAAQ,QAAQ,6BAA4B;AACrD,SACEC,gBAAgB,EAChBC,eAAe,EACfF,QAAQ,QACH,6BAA4B;AACnC,SACEG,gBAAgB,EAChBC,aAAa,EACbC,mBAAmB,EACnBC,mBAAmB,EACnBC,qBAAqB,QAChB,mBAAe;AAEtB;;;;CAIC,GACD,OAAO,SAASC,gBACdC,IAAY;IAEZ,MAAMC,UAAUC,OAAOF,eAAAA,OAAQ,IAAIG,IAAI;IACvC,IAAI,CAACF,SAAS,OAAO;IACrB,IAAIA,QAAQG,UAAU,CAAC,QAAQH,QAAQG,UAAU,CAAC,MAAM;QACtD,IAAI;YACF,MAAMC,SAASC,KAAKC,KAAK,CAACN;YAC1B,MAAMO,OAAOC,MAAMC,OAAO,CAACL,UAAUA,SAAS;gBAACA;aAAO;YACtD,OAAOG,KACJG,MAAM,CAAC,CAACC,QAAUA,SAAS,OAAOA,UAAU,UAC5CC,GAAG,CAAC,CAACD,QACJE,OAAOC,WAAW,CAChBD,OAAOE,OAAO,CAACJ,OAAkCC,GAAG,CAClD,CAAC,CAACI,KAAKC,MAAM,GAAK;wBAChBD;wBACA,OAAOC,UAAU,WAAWA,QAAQZ,KAAKa,SAAS,CAACD;qBACpD;QAIX,EAAE,eAAM;YACN,OAAO;QACT;IACF;IACA,MAAME,OAAO7B,SAASU;IACtB,IAAImB,KAAKC,MAAM,GAAG,GAAG,OAAO;IAC5B,MAAM,CAACC,QAAQ,GAAGC,KAAK,GAAGH;IAC1B,OAAOG,KAAKV,GAAG,CAAC,CAACW,QACfV,OAAOC,WAAW,CAChBO,OAAOT,GAAG,CAAC,CAACY,QAAQC;gBAA0BF;mBAAhB;gBAACC,OAAOtB,IAAI;iBAAIqB,eAAAA,KAAK,CAACE,MAAM,YAAZF,eAAgB;aAAG;;AAGvE;AAEA;;;;CAIC,GACD,OAAO,SAASG,iBACdC,KAAmB,EACnBC,OAAiB;IAEjB,MAAMC,SAAS,IAAIC;IACnB,KAAK,MAAMC,WAAWJ,MAAMK,KAAK,CAAE;YAEjBL;QADhBE,OAAOI,GAAG,CAACF,QAAQG,WAAW,IAAIH;QAClC,MAAMI,WAAUR,wBAAAA,MAAMS,MAAM,CAACL,QAAQ,qBAArBJ,sBAAuBU,IAAI;QAC3C,IAAIF,SAASN,OAAOI,GAAG,CAACE,QAAQjC,IAAI,GAAGgC,WAAW,IAAIH;IACxD;IACA,MAAMO,UAAkC,CAAC;IACzC,MAAMC,YAAsB,EAAE;IAC9B,KAAK,MAAMf,UAAUI,QAAS;QAC5B,MAAMG,UAAUF,OAAOW,GAAG,CAAChB,OAAOtB,IAAI,GAAGgC,WAAW;QACpD,IAAIH,SAASO,OAAO,CAACd,OAAO,GAAGO;aAC1BQ,UAAUE,IAAI,CAACjB;IACtB;IACA,OAAO;QAAEc;QAASC;IAAU;AAC9B"}
@@ -0,0 +1,40 @@
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 PluginRecordListSource } from '@aglyn/aglyn/plugin-manager/plugin-record-lists';
18
+ /**
19
+ * The workspace's datasets, listed in the console for another plugin's picker
20
+ * or check (AGL-3080) — an automation step choosing the dataset it writes to,
21
+ * a reference check confirming one still exists.
22
+ *
23
+ * Narrowed to a site (`hostId`), the query asks only for the datasets shared
24
+ * with it, by the site's scope tokens: an automation runs on the site, so it
25
+ * may only reach datasets the site can use, and the filter is the one the
26
+ * security rules require of a member scoped to the site. With no site, a
27
+ * member who is not organization-wide (`memberScope`) is asked for the ones
28
+ * shared with any site they reach — the rules refuse them an unfiltered
29
+ * list — and an organization-wide reader lists the organization's own.
30
+ * `installedFrom` keeps the ones installed from that listing, by the
31
+ * `source.listingId` every dataset install stamps.
32
+ *
33
+ * Ordered by document id, which the automatic index serves beside the
34
+ * `visibleTo` filter, and the composite index on (`source.listingId`,
35
+ * `visibleTo`) beside both. Each record is `datasetIndexedRecord`'s, the
36
+ * server index's shape.
37
+ */
38
+ export declare const datasetRecordListSource: PluginRecordListSource;
39
+ /** Called from the console registrar, owner named for a spec that calls it directly. */
40
+ export declare function registerDatasetRecordList(): void;
@@ -0,0 +1,61 @@
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 { scopeTokensForHost } from "@aglyn/aglyn/app-utils/scope-tokens";
17
+ import { registerPluginRecordListSource } from "@aglyn/aglyn/plugin-manager/plugin-record-lists";
18
+ import { collection, documentId, limit, orderBy, query, where } from "firebase/firestore";
19
+ import { BUNDLE_ID } from "../constants/bundle-common.js";
20
+ import { datasetIndexedRecord } from "./dataset-record.js";
21
+ /**
22
+ * The workspace's datasets, listed in the console for another plugin's picker
23
+ * or check (AGL-3080) — an automation step choosing the dataset it writes to,
24
+ * a reference check confirming one still exists.
25
+ *
26
+ * Narrowed to a site (`hostId`), the query asks only for the datasets shared
27
+ * with it, by the site's scope tokens: an automation runs on the site, so it
28
+ * may only reach datasets the site can use, and the filter is the one the
29
+ * security rules require of a member scoped to the site. With no site, a
30
+ * member who is not organization-wide (`memberScope`) is asked for the ones
31
+ * shared with any site they reach — the rules refuse them an unfiltered
32
+ * list — and an organization-wide reader lists the organization's own.
33
+ * `installedFrom` keeps the ones installed from that listing, by the
34
+ * `source.listingId` every dataset install stamps.
35
+ *
36
+ * Ordered by document id, which the automatic index serves beside the
37
+ * `visibleTo` filter, and the composite index on (`source.listingId`,
38
+ * `visibleTo`) beside both. Each record is `datasetIndexedRecord`'s, the
39
+ * server index's shape.
40
+ */ export const datasetRecordListSource = {
41
+ query (firestore, request) {
42
+ var _request_memberScope;
43
+ if (!request.orgId) return null;
44
+ const scope = request.hostId ? scopeTokensForHost(request.hostId) : (_request_memberScope = request.memberScope) != null ? _request_memberScope : null;
45
+ return query(collection(firestore, 'orgs', request.orgId, 'datasets'), ...request.installedFrom ? [
46
+ where('source.listingId', '==', request.installedFrom)
47
+ ] : [], ...scope ? [
48
+ where('visibleTo', 'array-contains-any', [
49
+ ...scope
50
+ ])
51
+ ] : [], orderBy(documentId()), limit(request.limit));
52
+ },
53
+ record: datasetIndexedRecord
54
+ };
55
+ /** Called from the console registrar, owner named for a spec that calls it directly. */ export function registerDatasetRecordList() {
56
+ registerPluginRecordListSource('dataset', datasetRecordListSource, {
57
+ pluginId: BUNDLE_ID
58
+ });
59
+ }
60
+
61
+ //# sourceMappingURL=dataset-record-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/model/dataset-record-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 { scopeTokensForHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n registerPluginRecordListSource,\n type PluginRecordListSource,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport {\n collection,\n documentId,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { datasetIndexedRecord } from './dataset-record'\n\n/**\n * The workspace's datasets, listed in the console for another plugin's picker\n * or check (AGL-3080) — an automation step choosing the dataset it writes to,\n * a reference check confirming one still exists.\n *\n * Narrowed to a site (`hostId`), the query asks only for the datasets shared\n * with it, by the site's scope tokens: an automation runs on the site, so it\n * may only reach datasets the site can use, and the filter is the one the\n * security rules require of a member scoped to the site. With no site, a\n * member who is not organization-wide (`memberScope`) is asked for the ones\n * shared with any site they reach — the rules refuse them an unfiltered\n * list — and an organization-wide reader lists the organization's own.\n * `installedFrom` keeps the ones installed from that listing, by the\n * `source.listingId` every dataset install stamps.\n *\n * Ordered by document id, which the automatic index serves beside the\n * `visibleTo` filter, and the composite index on (`source.listingId`,\n * `visibleTo`) beside both. Each record is `datasetIndexedRecord`'s, the\n * server index's shape.\n */\nexport const datasetRecordListSource: PluginRecordListSource = {\n query(firestore, request) {\n if (!request.orgId) return null\n const scope = request.hostId ? scopeTokensForHost(request.hostId) : (request.memberScope ?? null)\n return query(\n collection(firestore, 'orgs', request.orgId, 'datasets'),\n ...(request.installedFrom ? [where('source.listingId', '==', request.installedFrom)] : []),\n ...(scope ? [where('visibleTo', 'array-contains-any', [...scope])] : []),\n orderBy(documentId()),\n limit(request.limit),\n )\n },\n record: datasetIndexedRecord,\n}\n\n/** Called from the console registrar, owner named for a spec that calls it directly. */\nexport function registerDatasetRecordList(): void {\n registerPluginRecordListSource('dataset', datasetRecordListSource, { pluginId: BUNDLE_ID })\n}\n"],"names":["scopeTokensForHost","registerPluginRecordListSource","collection","documentId","limit","orderBy","query","where","BUNDLE_ID","datasetIndexedRecord","datasetRecordListSource","firestore","request","orgId","scope","hostId","memberScope","installedFrom","record","registerDatasetRecordList","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,kBAAkB,QAAQ,sCAAqC;AACxE,SACEC,8BAA8B,QAEzB,kDAAiD;AACxD,SACEC,UAAU,EACVC,UAAU,EACVC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAC3B,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,oBAAoB,QAAQ,sBAAkB;AAEvD;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,MAAMC,0BAAkD;IAC7DJ,OAAMK,SAAS,EAAEC,OAAO;YAE+CA;QADrE,IAAI,CAACA,QAAQC,KAAK,EAAE,OAAO;QAC3B,MAAMC,QAAQF,QAAQG,MAAM,GAAGf,mBAAmBY,QAAQG,MAAM,KAAKH,uBAAAA,QAAQI,WAAW,YAAnBJ,uBAAuB;QAC5F,OAAON,MACLJ,WAAWS,WAAW,QAAQC,QAAQC,KAAK,EAAE,gBACzCD,QAAQK,aAAa,GAAG;YAACV,MAAM,oBAAoB,MAAMK,QAAQK,aAAa;SAAE,GAAG,EAAE,KACrFH,QAAQ;YAACP,MAAM,aAAa,sBAAsB;mBAAIO;aAAM;SAAE,GAAG,EAAE,EACvET,QAAQF,eACRC,MAAMQ,QAAQR,KAAK;IAEvB;IACAc,QAAQT;AACV,EAAC;AAED,sFAAsF,GACtF,OAAO,SAASU;IACdlB,+BAA+B,WAAWS,yBAAyB;QAAEU,UAAUZ;IAAU;AAC3F"}