@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
@@ -0,0 +1,536 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { TRANSFER_ID_FIELD, isBlankTransferValue, normalizeMatchValue } from "@aglyn/aglyn/data-transfer";
18
+ import { coerceDocumentValues, coerceListValue, datasetFilterTextKey, datasetFilterValuePath, humanizeDatasetFieldId, validateDocument } from "../model/dataset-models.js";
19
+ import { fillRecordAddresses, isRecordAddressField } from "../record-pages/record-pages.js";
20
+ import { datasetTransferResourceKey } from "./dataset-transfer-key.js";
21
+ /*==========================================
22
+ * A DATASET AS A TRANSFER RESOURCE (AGL-3530) — the pure half.
23
+ *
24
+ * The import wizard and the export dialog speak in TRANSFER FIELDS and
25
+ * TRANSFER VALUES (`@aglyn/aglyn/data-transfer`); a dataset speaks in its own
26
+ * model and storage form (`dataset-models.ts`). This module is the one place
27
+ * the two are translated, so the export, the dry run, the writes and undo all
28
+ * read a record the same way:
29
+ *
30
+ * - {@link datasetTransferCatalog}: the dataset's fields as the catalog the
31
+ * picker and the mapping list, plus the record's Aglyn ID and its created
32
+ * and updated times;
33
+ * - {@link datasetRecordTransferValues}: a stored record as transfer values
34
+ * — what an export writes, what the plan compares a file against, and what
35
+ * undo reads back;
36
+ * - {@link datasetStorageValues}: transfer values back into storage form,
37
+ * through the same `coerceDocumentValues` every record write uses;
38
+ * - {@link datasetMatchKeyOffer}: which fields a row may find its record by;
39
+ * - {@link refuseInvalidDatasetRows}: the dry run's rows held to the model's
40
+ * validation, so a row the write would refuse fails in the dry run instead.
41
+ *
42
+ * A reference field is a LOOKUP of its target dataset (`data.dataset:<id>`,
43
+ * by the record's ID or the field the reference displays), a multi-reference
44
+ * a `list` of them (AGL-3556): the job engine resolves each value through the
45
+ * target's own hooks, offers records named like the ones it cannot find, and
46
+ * asks the person to use one, create it, leave it blank or refuse the row.
47
+ * Nothing here resolves a reference; a value the person chose to create
48
+ * reaches the writes as `transferLookupNewValue(name)`.
49
+ *
50
+ * The two must be inverses on every value a record holds: an import of an
51
+ * export changes nothing, and undo compares a record's values now with the
52
+ * values the import wrote, in one form.
53
+ *=========================================*/ /** The group a dataset's own fields are listed under. */ export const DATASET_TRANSFER_FIELDS_GROUP = 'fields';
54
+ /** The record's created time, exported and never written. */ export const DATASET_TRANSFER_CREATED_FIELD = 'record:createdAt';
55
+ /** The record's last-updated time, exported and never written. */ export const DATASET_TRANSFER_UPDATED_FIELD = 'record:updatedAt';
56
+ /**
57
+ * A dataset field named `id` would be read as the record's Aglyn ID, which
58
+ * every catalog carries under that id; it moves as `field:id` instead.
59
+ */ const SHADOWED_PREFIX = 'field:';
60
+ /** A dataset field's id in the catalog. */ export function datasetTransferFieldId(fieldId) {
61
+ return fieldId === TRANSFER_ID_FIELD ? `${SHADOWED_PREFIX}${fieldId}` : fieldId;
62
+ }
63
+ /** The dataset field a catalog id names, or `null` for the Aglyn ID and the record's times. */ export function datasetFieldIdOf(transferFieldId) {
64
+ if (transferFieldId === TRANSFER_ID_FIELD) return null;
65
+ if (transferFieldId === DATASET_TRANSFER_CREATED_FIELD || transferFieldId === DATASET_TRANSFER_UPDATED_FIELD) return null;
66
+ return transferFieldId === `${SHADOWED_PREFIX}${TRANSFER_ID_FIELD}` ? TRANSFER_ID_FIELD : transferFieldId;
67
+ }
68
+ /** The picklist a text field with a fixed list of options is matched against. */ export function datasetOptionsPicklistId(fieldId) {
69
+ return `options:${fieldId}`;
70
+ }
71
+ /** The field a picklist id from {@link datasetOptionsPicklistId} belongs to, or `null`. */ export function datasetFieldOfPicklist(picklistId) {
72
+ return picklistId.startsWith('options:') ? picklistId.slice('options:'.length) || null : null;
73
+ }
74
+ /** A text field whose values must be one of its options. */ export function isOptionsField(field) {
75
+ var _ref;
76
+ var _field_validation_options, _field_validation;
77
+ return (field == null ? void 0 : field.type) === 'text' && ((_ref = (_field_validation = field.validation) == null ? void 0 : (_field_validation_options = _field_validation.options) == null ? void 0 : _field_validation_options.length) != null ? _ref : 0) > 0;
78
+ }
79
+ /** A field a file may write: everything but the bytes and null placeholders, which hold nothing a cell can say. */ export function isWritableDatasetField(field) {
80
+ return Boolean(field) && (field == null ? void 0 : field.type) !== 'bytes' && (field == null ? void 0 : field.type) !== 'nil';
81
+ }
82
+ /** What a dataset field means to the wizard. */ export function datasetTransferType(field) {
83
+ switch(field.type){
84
+ case 'bool':
85
+ return 'boolean';
86
+ case 'int32':
87
+ case 'int64':
88
+ return 'integer';
89
+ case 'float':
90
+ return 'number';
91
+ case 'timestamp':
92
+ return 'datetime';
93
+ case 'map':
94
+ return 'json';
95
+ case 'sorted':
96
+ return 'list';
97
+ case 'reference':
98
+ var _field_reference, _field_reference1;
99
+ // A reference with no target dataset names nothing a lookup could find.
100
+ if ((_field_reference = field.reference) == null ? void 0 : _field_reference.multiple) return 'list';
101
+ return ((_field_reference1 = field.reference) == null ? void 0 : _field_reference1.datasetId) ? 'lookup' : 'text';
102
+ case 'text':
103
+ return isOptionsField(field) ? 'picklist' : 'text';
104
+ default:
105
+ // Coordinates read and write as `latitude, longitude`; bytes and nil
106
+ // export as text and are never written.
107
+ return 'text';
108
+ }
109
+ }
110
+ /** Whether a new record needs this field — a page address fills itself in from its source field, so it never does. */ function isRequiredField(field) {
111
+ var _field_validation;
112
+ if (isRecordAddressField(field) && field.slugFrom) return false;
113
+ return Boolean(field.required || ((_field_validation = field.validation) == null ? void 0 : _field_validation.required));
114
+ }
115
+ /**
116
+ * Whether the target's model accepts a record holding nothing but a name in
117
+ * `displayFieldId` (its page addresses filled in from it): the display field
118
+ * is plain text and no other field is required. Each name is still held to
119
+ * the model when the import creates it.
120
+ */ export function datasetAcceptsNamedRecord(model, displayFieldId) {
121
+ var _model_fields;
122
+ const display = displayFieldId ? (_model_fields = model.fields) == null ? void 0 : _model_fields[displayFieldId] : undefined;
123
+ if (!displayFieldId || (display == null ? void 0 : display.type) !== 'text' || isOptionsField(display)) return false;
124
+ const values = fillRecordAddresses(model, coerceDocumentValues(model, {
125
+ [displayFieldId]: 'New record'
126
+ }));
127
+ return Object.keys(datasetWriteErrors(model, values)).every((fieldId)=>fieldId === displayFieldId);
128
+ }
129
+ /**
130
+ * One dataset field as the catalog lists it. `target` is what the server read
131
+ * of a reference's target dataset; without it a reference is a lookup that
132
+ * creates nothing.
133
+ */ export function datasetTransferField(fieldId, field, target) {
134
+ var _field_name, _field_description;
135
+ var _field_reference, _field_reference1, _field_reference2, _field_reference3;
136
+ const id = datasetTransferFieldId(fieldId);
137
+ const label = String((_field_name = field.name) != null ? _field_name : '').trim() || humanizeDatasetFieldId(fieldId);
138
+ const type = datasetTransferType(field);
139
+ const targetId = field.type === 'reference' ? (_field_reference = field.reference) == null ? void 0 : _field_reference.datasetId : undefined;
140
+ const hidden = Boolean(targetId && target && !target.readable);
141
+ const description = hidden ? 'Records of a dataset that is not shared with you: exported by ID, never imported.' : field.type === 'reference' ? `${((_field_reference1 = field.reference) == null ? void 0 : _field_reference1.multiple) ? 'Records' : 'A record'} of another dataset, by its ID or its name.` : field.type === 'coordinates' ? 'Latitude and longitude, separated by a comma.' : String((_field_description = field.description) != null ? _field_description : '').trim();
142
+ return _extends({
143
+ id,
144
+ label,
145
+ group: DATASET_TRANSFER_FIELDS_GROUP,
146
+ type
147
+ }, isRequiredField(field) ? {
148
+ required: true
149
+ } : {}, isWritableDatasetField(field) && !hidden ? {} : {
150
+ readOnly: true
151
+ }, label.toLowerCase() !== fieldId.toLowerCase() ? {
152
+ aliases: [
153
+ fieldId
154
+ ]
155
+ } : {}, type === 'picklist' ? {
156
+ picklistId: datasetOptionsPicklistId(fieldId)
157
+ } : {}, targetId ? {
158
+ lookup: _extends({
159
+ resource: datasetTransferResourceKey(targetId),
160
+ by: [
161
+ TRANSFER_ID_FIELD,
162
+ ...((_field_reference2 = field.reference) == null ? void 0 : _field_reference2.displayFieldId) ? [
163
+ datasetTransferFieldId(field.reference.displayFieldId)
164
+ ] : []
165
+ ]
166
+ }, (target == null ? void 0 : target.readable) && target.model && datasetAcceptsNamedRecord(target.model, (_field_reference3 = field.reference) == null ? void 0 : _field_reference3.displayFieldId) ? {
167
+ creatable: true
168
+ } : {})
169
+ } : {}, description ? {
170
+ description
171
+ } : {});
172
+ }
173
+ /**
174
+ * The dataset's catalog: its fields in the dataset's order under one group
175
+ * (named after the dataset when `label` is given), then the record's Aglyn ID (added by the core) and its created and updated
176
+ * times. `targets` is what the server read of each referenced dataset, by id.
177
+ */ export function datasetTransferCatalog(model, label = 'Fields', targets = {}) {
178
+ var _model_order;
179
+ return {
180
+ standard: ((_model_order = model.order) != null ? _model_order : []).filter((fieldId)=>{
181
+ var _model_fields;
182
+ return (_model_fields = model.fields) == null ? void 0 : _model_fields[fieldId];
183
+ }).map((fieldId)=>{
184
+ var _field_reference;
185
+ const field = model.fields[fieldId];
186
+ const targetId = field.type === 'reference' ? (_field_reference = field.reference) == null ? void 0 : _field_reference.datasetId : undefined;
187
+ return datasetTransferField(fieldId, field, targetId ? targets[targetId] : undefined);
188
+ }),
189
+ system: [
190
+ {
191
+ id: DATASET_TRANSFER_CREATED_FIELD,
192
+ label: 'Created',
193
+ type: 'datetime',
194
+ readOnly: true
195
+ },
196
+ {
197
+ id: DATASET_TRANSFER_UPDATED_FIELD,
198
+ label: 'Updated',
199
+ type: 'datetime',
200
+ readOnly: true
201
+ }
202
+ ],
203
+ groups: [
204
+ {
205
+ id: DATASET_TRANSFER_FIELDS_GROUP,
206
+ label
207
+ }
208
+ ]
209
+ };
210
+ }
211
+ /** An options field's values as a picklist the values step can match against. */ export function datasetOptionsPicklist(field) {
212
+ var _ref;
213
+ var _field_validation;
214
+ const options = [
215
+ ...new Set(((_ref = (_field_validation = field.validation) == null ? void 0 : _field_validation.options) != null ? _ref : []).map((option)=>String(option)).filter(Boolean))
216
+ ];
217
+ return {
218
+ // Restricted: `validateDocument` refuses a value the options do not hold.
219
+ spec: {
220
+ restricted: true,
221
+ standardValues: options.map((option)=>({
222
+ id: option,
223
+ label: option
224
+ }))
225
+ },
226
+ set: {
227
+ values: options.map((option)=>({
228
+ id: option,
229
+ label: option,
230
+ active: true
231
+ })),
232
+ defaultValueId: null
233
+ }
234
+ };
235
+ }
236
+ /*------------------------------------------
237
+ * Values: storage form ↔ transfer form
238
+ *-----------------------------------------*/ const finite = (value)=>typeof value === 'number' && Number.isFinite(value);
239
+ /** A stored time — epoch milliseconds, a Firestore Timestamp, a date string — as ISO-8601, or `null`. */ function isoOf(value) {
240
+ if (finite(value)) return new Date(value).toISOString();
241
+ const timestamp = value;
242
+ if (timestamp && typeof timestamp.toMillis === 'function') return new Date(timestamp.toMillis()).toISOString();
243
+ if (typeof value === 'string' && value.trim()) {
244
+ const millis = Date.parse(value);
245
+ return Number.isFinite(millis) ? new Date(millis).toISOString() : null;
246
+ }
247
+ return null;
248
+ }
249
+ /** One stored value as the wizard reads it, or `undefined` for a value the record does not hold. */ function transferValueOf(field, stored) {
250
+ if (stored === null || stored === undefined || stored === '') return undefined;
251
+ switch(field.type){
252
+ case 'timestamp':
253
+ var _isoOf;
254
+ return (_isoOf = isoOf(stored)) != null ? _isoOf : String(stored);
255
+ case 'bool':
256
+ return stored === 'true' ? true : stored === 'false' ? false : stored;
257
+ case 'int32':
258
+ case 'int64':
259
+ case 'float':
260
+ {
261
+ // A form or an automation stored numbers as text before AGL-2773.
262
+ const number = typeof stored === 'string' && stored.trim() ? Number(stored) : stored;
263
+ return finite(number) ? number : stored;
264
+ }
265
+ case 'coordinates':
266
+ {
267
+ const point = stored;
268
+ return finite(point == null ? void 0 : point.latitude) && finite(point == null ? void 0 : point.longitude) ? `${point.latitude}, ${point.longitude}` : stored;
269
+ }
270
+ case 'sorted':
271
+ case 'reference':
272
+ {
273
+ var _field_reference;
274
+ if (field.type === 'reference' && !((_field_reference = field.reference) == null ? void 0 : _field_reference.multiple)) return stored;
275
+ const list = coerceListValue(stored);
276
+ return Array.isArray(list) ? list.length ? list : undefined : stored;
277
+ }
278
+ case 'text':
279
+ return finite(stored) || typeof stored === 'boolean' ? String(stored) : stored;
280
+ default:
281
+ return stored;
282
+ }
283
+ }
284
+ /**
285
+ * A stored record as transfer values, by catalog id: every field the model
286
+ * lists that the record holds, its Aglyn ID, and its created and updated
287
+ * times when it carries them. A field the record does not hold is absent,
288
+ * which the plan reads as blank.
289
+ */ export function datasetRecordTransferValues(model, record) {
290
+ var _record_values, _model_order;
291
+ const out = {};
292
+ const values = (_record_values = record.values) != null ? _record_values : {};
293
+ for (const fieldId of (_model_order = model.order) != null ? _model_order : []){
294
+ var _model_fields;
295
+ const field = (_model_fields = model.fields) == null ? void 0 : _model_fields[fieldId];
296
+ if (!field) continue;
297
+ const value = transferValueOf(field, values[fieldId]);
298
+ if (value !== undefined) out[datasetTransferFieldId(fieldId)] = value;
299
+ }
300
+ // After the fields: a record is named by the first text it holds (the
301
+ // wizard's matches and lookups), which is its first field, not its ID.
302
+ if (record.id) out[TRANSFER_ID_FIELD] = record.id;
303
+ const created = isoOf(record.createdAt);
304
+ const updated = isoOf(record.updatedAt);
305
+ if (created) out[DATASET_TRANSFER_CREATED_FIELD] = created;
306
+ if (updated) out[DATASET_TRANSFER_UPDATED_FIELD] = updated;
307
+ return out;
308
+ }
309
+ /**
310
+ * Transfer values in storage form, through `coerceDocumentValues` — the
311
+ * coercion every record write runs. `values` holds what is written and
312
+ * `cleared` the fields a blank value empties. The Aglyn ID, the record's
313
+ * times, unknown ids and fields a file never writes are ignored.
314
+ */ export function datasetStorageValues(model, transfer) {
315
+ const input = {};
316
+ const cleared = [];
317
+ for (const [transferId, value] of Object.entries(transfer)){
318
+ var _model_fields;
319
+ const fieldId = datasetFieldIdOf(transferId);
320
+ const field = fieldId ? (_model_fields = model.fields) == null ? void 0 : _model_fields[fieldId] : undefined;
321
+ if (!fieldId || !field || !isWritableDatasetField(field)) continue;
322
+ if (isBlankTransferValue(value)) {
323
+ cleared.push(fieldId);
324
+ continue;
325
+ }
326
+ // Text read from a JSON file's number is still the text it spells.
327
+ input[fieldId] = field.type === 'text' && (finite(value) || typeof value === 'boolean') ? String(value) : value;
328
+ }
329
+ return {
330
+ values: coerceDocumentValues(model, input),
331
+ cleared
332
+ };
333
+ }
334
+ /*------------------------------------------
335
+ * Matching
336
+ *-----------------------------------------*/ /** How a dataset field compares as a match key, or `null` for one no query can find a record by. */ export function datasetMatchKeyFor(fieldId, field) {
337
+ if (!field || !datasetFilterValuePath(fieldId)) return null;
338
+ const id = datasetTransferFieldId(fieldId);
339
+ if (field.type === 'text') return {
340
+ fieldId: id,
341
+ normalizer: isOptionsField(field) ? 'trim' : 'caseless'
342
+ };
343
+ if (field.type === 'int32' || field.type === 'int64' || field.type === 'float') return {
344
+ fieldId: id,
345
+ normalizer: 'exact'
346
+ };
347
+ return null;
348
+ }
349
+ /**
350
+ * The keys a row may find its record by: the Aglyn ID, then every text and
351
+ * number field the records' filter values can be asked by, in the dataset's
352
+ * order. A person starts with the Aglyn ID and the dataset's page address —
353
+ * the one field a dataset holds unique — and may add any other.
354
+ */ export function datasetMatchKeyOffer(model) {
355
+ var _model_order;
356
+ const keys = [
357
+ {
358
+ fieldId: TRANSFER_ID_FIELD,
359
+ normalizer: 'aglynId'
360
+ }
361
+ ];
362
+ const defaults = [
363
+ TRANSFER_ID_FIELD
364
+ ];
365
+ for (const fieldId of (_model_order = model.order) != null ? _model_order : []){
366
+ var _model_fields;
367
+ const field = (_model_fields = model.fields) == null ? void 0 : _model_fields[fieldId];
368
+ const key = datasetMatchKeyFor(fieldId, field);
369
+ if (!key) continue;
370
+ keys.push(key);
371
+ if (isRecordAddressField(field)) defaults.push(key.fieldId);
372
+ }
373
+ return {
374
+ keys,
375
+ defaults
376
+ };
377
+ }
378
+ /**
379
+ * The `filterValues` equality that finds the records holding a normalized
380
+ * key value: the path, and the value as the records store it. Plain text is
381
+ * stored as its lower-cased key clipped to 64 characters, so two long values
382
+ * can share it; the caller compares what it found against the whole value.
383
+ */ export function datasetMatchKeyQuery(model, key, normalized) {
384
+ var _model_fields;
385
+ const fieldId = datasetFieldIdOf(key.fieldId);
386
+ const field = fieldId ? (_model_fields = model.fields) == null ? void 0 : _model_fields[fieldId] : undefined;
387
+ const path = fieldId ? datasetFilterValuePath(fieldId) : null;
388
+ if (!field || !path) return null;
389
+ if (field.type === 'int32' || field.type === 'int64' || field.type === 'float') {
390
+ const number = Number(normalized);
391
+ return Number.isFinite(number) ? {
392
+ path,
393
+ value: number
394
+ } : null;
395
+ }
396
+ if (field.type !== 'text') return null;
397
+ return {
398
+ path,
399
+ value: isOptionsField(field) ? normalized : datasetFilterTextKey(normalized)
400
+ };
401
+ }
402
+ /** Whether a record's transfer values hold a normalized key value — the check a clipped query answer is held to. */ export function datasetHoldsKey(view, key, normalized) {
403
+ return normalizeMatchValue(key.normalizer, view[key.fieldId]) === normalized;
404
+ }
405
+ /*------------------------------------------
406
+ * The dry run, held to the model
407
+ *-----------------------------------------*/ /** Field id → message, for the fields a write of these values would refuse. */ export function datasetWriteErrors(model, values, only) {
408
+ const errors = validateDocument(model, values);
409
+ if (!only) return errors;
410
+ return Object.fromEntries(Object.entries(errors).filter(([fieldId])=>only.has(fieldId)));
411
+ }
412
+ /**
413
+ * The values a planned row leaves a record holding, in storage form: a new
414
+ * record's from the row alone, an existing one's from what it holds now
415
+ * (`before`, transfer values) with the row's changes over it. Page addresses
416
+ * are filled in as every record write fills them. `changed` names the dataset
417
+ * fields the row writes.
418
+ */ export function datasetPlannedValues(model, row, before) {
419
+ const after = row.verdict === 'update' ? _extends({}, before != null ? before : {}) : {};
420
+ const changed = new Set();
421
+ for (const change of row.diff){
422
+ after[change.fieldId] = change.after;
423
+ const fieldId = datasetFieldIdOf(change.fieldId);
424
+ if (fieldId) changed.add(fieldId);
425
+ }
426
+ const { values } = datasetStorageValues(model, after);
427
+ return {
428
+ values: fillRecordAddresses(model, values),
429
+ changed
430
+ };
431
+ }
432
+ /**
433
+ * The plan with every row the model would refuse failed, naming the fields
434
+ * (`missing`, read with the reason "A value refuses the row"): a value outside
435
+ * a field's options, past its bounds, not matching its pattern, or refused by
436
+ * a custom field type's validator. An update is held only on the fields it
437
+ * writes, so a record stored before a rule existed is not refused for a value
438
+ * the file never touched.
439
+ */ export function refuseInvalidDatasetRows(model, plan, existing) {
440
+ let refused = 0;
441
+ const rows = plan.rows.map((row)=>{
442
+ if (row.verdict !== 'create' && row.verdict !== 'update') return row;
443
+ const before = row.recordId ? existing.get(row.recordId) : null;
444
+ const { values, changed } = datasetPlannedValues(model, row, before);
445
+ const errors = datasetWriteErrors(model, values, row.verdict === 'update' ? changed : undefined);
446
+ const fields = Object.keys(errors);
447
+ if (!fields.length) return row;
448
+ refused += 1;
449
+ return _extends({}, row, {
450
+ verdict: 'fail',
451
+ reason: 'refusedValue',
452
+ missing: fields.map(datasetTransferFieldId),
453
+ diff: []
454
+ });
455
+ });
456
+ if (!refused) return plan;
457
+ const summary = {
458
+ create: 0,
459
+ update: 0,
460
+ unchanged: 0,
461
+ skip: 0,
462
+ fail: 0,
463
+ total: rows.length
464
+ };
465
+ for (const row of rows)summary[row.verdict] += 1;
466
+ return _extends({}, plan, {
467
+ rows,
468
+ summary
469
+ });
470
+ }
471
+ /**
472
+ * The plan with every new record past `maxCreates` failed as past what the
473
+ * plan allows, under the `planLimit` warning the person acknowledges. Run
474
+ * after {@link refuseInvalidDatasetRows}, so a row the model refuses never
475
+ * takes the room a valid row could have had.
476
+ */ export function limitDatasetCreates(plan, maxCreates) {
477
+ if (maxCreates === undefined || plan.summary.create <= maxCreates) return plan;
478
+ let room = Math.max(0, maxCreates);
479
+ const over = [];
480
+ const rows = plan.rows.map((row)=>{
481
+ if (row.verdict !== 'create') return row;
482
+ if (room > 0) {
483
+ room -= 1;
484
+ return row;
485
+ }
486
+ over.push(row.index);
487
+ return _extends({}, row, {
488
+ verdict: 'fail',
489
+ reason: 'planLimit',
490
+ diff: [],
491
+ warnings: [
492
+ ...new Set([
493
+ ...row.warnings,
494
+ 'planLimit'
495
+ ])
496
+ ]
497
+ });
498
+ });
499
+ const summary = {
500
+ create: 0,
501
+ update: 0,
502
+ unchanged: 0,
503
+ skip: 0,
504
+ fail: 0,
505
+ total: rows.length
506
+ };
507
+ for (const row of rows)summary[row.verdict] += 1;
508
+ const limit = {
509
+ class: 'planLimit',
510
+ count: over.length,
511
+ rows: over.length,
512
+ fieldIds: [],
513
+ samples: over.slice(0, 5).map((row)=>({
514
+ row,
515
+ detail: 'Past the records this plan allows in a dataset'
516
+ })),
517
+ requiresAcknowledgement: true
518
+ };
519
+ const warnings = [
520
+ ...plan.warnings.filter((warning)=>warning.class !== 'planLimit'),
521
+ limit
522
+ ];
523
+ return {
524
+ rows,
525
+ summary,
526
+ warnings,
527
+ acknowledgementsRequired: [
528
+ ...new Set([
529
+ ...plan.acknowledgementsRequired,
530
+ 'planLimit'
531
+ ])
532
+ ]
533
+ };
534
+ }
535
+
536
+ //# sourceMappingURL=dataset-transfer-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/transfer/dataset-transfer-model.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 TRANSFER_ID_FIELD,\n isBlankTransferValue,\n normalizeMatchValue,\n type MatchKeySpec,\n type PlannedTransferRow,\n type TransferCatalogInput,\n type TransferField,\n type TransferFieldType,\n type TransferPlan,\n type TransferWarning,\n} from '@aglyn/aglyn/data-transfer'\nimport type { PicklistSpec, PicklistValueSet } from '@aglyn/aglyn/app-utils/picklists'\nimport type { TransferMatchKeyOffer } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport {\n coerceDocumentValues,\n coerceListValue,\n datasetFilterTextKey,\n datasetFilterValuePath,\n humanizeDatasetFieldId,\n validateDocument,\n type DatasetFieldDefinition,\n type DatasetModel,\n} from '../model/dataset-models'\nimport { fillRecordAddresses, isRecordAddressField } from '../record-pages/record-pages'\nimport { datasetTransferResourceKey } from './dataset-transfer-key'\n\n/*==========================================\n * A DATASET AS A TRANSFER RESOURCE (AGL-3530) — the pure half.\n *\n * The import wizard and the export dialog speak in TRANSFER FIELDS and\n * TRANSFER VALUES (`@aglyn/aglyn/data-transfer`); a dataset speaks in its own\n * model and storage form (`dataset-models.ts`). This module is the one place\n * the two are translated, so the export, the dry run, the writes and undo all\n * read a record the same way:\n *\n * - {@link datasetTransferCatalog}: the dataset's fields as the catalog the\n * picker and the mapping list, plus the record's Aglyn ID and its created\n * and updated times;\n * - {@link datasetRecordTransferValues}: a stored record as transfer values\n * — what an export writes, what the plan compares a file against, and what\n * undo reads back;\n * - {@link datasetStorageValues}: transfer values back into storage form,\n * through the same `coerceDocumentValues` every record write uses;\n * - {@link datasetMatchKeyOffer}: which fields a row may find its record by;\n * - {@link refuseInvalidDatasetRows}: the dry run's rows held to the model's\n * validation, so a row the write would refuse fails in the dry run instead.\n *\n * A reference field is a LOOKUP of its target dataset (`data.dataset:<id>`,\n * by the record's ID or the field the reference displays), a multi-reference\n * a `list` of them (AGL-3556): the job engine resolves each value through the\n * target's own hooks, offers records named like the ones it cannot find, and\n * asks the person to use one, create it, leave it blank or refuse the row.\n * Nothing here resolves a reference; a value the person chose to create\n * reaches the writes as `transferLookupNewValue(name)`.\n *\n * The two must be inverses on every value a record holds: an import of an\n * export changes nothing, and undo compares a record's values now with the\n * values the import wrote, in one form.\n *=========================================*/\n\n/** The group a dataset's own fields are listed under. */\nexport const DATASET_TRANSFER_FIELDS_GROUP = 'fields'\n\n/** The record's created time, exported and never written. */\nexport const DATASET_TRANSFER_CREATED_FIELD = 'record:createdAt'\n\n/** The record's last-updated time, exported and never written. */\nexport const DATASET_TRANSFER_UPDATED_FIELD = 'record:updatedAt'\n\n/**\n * A dataset field named `id` would be read as the record's Aglyn ID, which\n * every catalog carries under that id; it moves as `field:id` instead.\n */\nconst SHADOWED_PREFIX = 'field:'\n\n/** A dataset field's id in the catalog. */\nexport function datasetTransferFieldId(fieldId: string): string {\n return fieldId === TRANSFER_ID_FIELD ? `${SHADOWED_PREFIX}${fieldId}` : fieldId\n}\n\n/** The dataset field a catalog id names, or `null` for the Aglyn ID and the record's times. */\nexport function datasetFieldIdOf(transferFieldId: string): string | null {\n if (transferFieldId === TRANSFER_ID_FIELD) return null\n if (transferFieldId === DATASET_TRANSFER_CREATED_FIELD || transferFieldId === DATASET_TRANSFER_UPDATED_FIELD) return null\n return transferFieldId === `${SHADOWED_PREFIX}${TRANSFER_ID_FIELD}` ? TRANSFER_ID_FIELD : transferFieldId\n}\n\n/** The picklist a text field with a fixed list of options is matched against. */\nexport function datasetOptionsPicklistId(fieldId: string): string {\n return `options:${fieldId}`\n}\n\n/** The field a picklist id from {@link datasetOptionsPicklistId} belongs to, or `null`. */\nexport function datasetFieldOfPicklist(picklistId: string): string | null {\n return picklistId.startsWith('options:') ? picklistId.slice('options:'.length) || null : null\n}\n\n/** A text field whose values must be one of its options. */\nexport function isOptionsField(field: DatasetFieldDefinition | undefined): boolean {\n return field?.type === 'text' && (field.validation?.options?.length ?? 0) > 0\n}\n\n/** A field a file may write: everything but the bytes and null placeholders, which hold nothing a cell can say. */\nexport function isWritableDatasetField(field: DatasetFieldDefinition | undefined): boolean {\n return Boolean(field) && field?.type !== 'bytes' && field?.type !== 'nil'\n}\n\n/** What a dataset field means to the wizard. */\nexport function datasetTransferType(field: DatasetFieldDefinition): TransferFieldType {\n switch (field.type) {\n case 'bool':\n return 'boolean'\n case 'int32':\n case 'int64':\n return 'integer'\n case 'float':\n return 'number'\n case 'timestamp':\n return 'datetime'\n case 'map':\n return 'json'\n case 'sorted':\n return 'list'\n case 'reference':\n // A reference with no target dataset names nothing a lookup could find.\n if (field.reference?.multiple) return 'list'\n return field.reference?.datasetId ? 'lookup' : 'text'\n case 'text':\n return isOptionsField(field) ? 'picklist' : 'text'\n default:\n // Coordinates read and write as `latitude, longitude`; bytes and nil\n // export as text and are never written.\n return 'text'\n }\n}\n\n/** Whether a new record needs this field — a page address fills itself in from its source field, so it never does. */\nfunction isRequiredField(field: DatasetFieldDefinition): boolean {\n if (isRecordAddressField(field) && field.slugFrom) return false\n return Boolean(field.required || field.validation?.required)\n}\n\n/**\n * What the person may do with a reference's target dataset, as the server\n * read it for them: a reference to a dataset they cannot see is exported by\n * ID and never imported, since nothing in it could be looked up.\n */\nexport interface DatasetReferenceTarget {\n readable: boolean\n /** The target's model, when it is readable: whether the import may create a record in it by name. */\n model?: DatasetModel\n}\n\n/**\n * Whether the target's model accepts a record holding nothing but a name in\n * `displayFieldId` (its page addresses filled in from it): the display field\n * is plain text and no other field is required. Each name is still held to\n * the model when the import creates it.\n */\nexport function datasetAcceptsNamedRecord(model: DatasetModel, displayFieldId: string | undefined): boolean {\n const display = displayFieldId ? model.fields?.[displayFieldId] : undefined\n if (!displayFieldId || display?.type !== 'text' || isOptionsField(display)) return false\n const values = fillRecordAddresses(model, coerceDocumentValues(model, { [displayFieldId]: 'New record' }))\n return Object.keys(datasetWriteErrors(model, values)).every((fieldId) => fieldId === displayFieldId)\n}\n\n/**\n * One dataset field as the catalog lists it. `target` is what the server read\n * of a reference's target dataset; without it a reference is a lookup that\n * creates nothing.\n */\nexport function datasetTransferField(\n fieldId: string,\n field: DatasetFieldDefinition,\n target?: DatasetReferenceTarget,\n): TransferField {\n const id = datasetTransferFieldId(fieldId)\n const label = String(field.name ?? '').trim() || humanizeDatasetFieldId(fieldId)\n const type = datasetTransferType(field)\n const targetId = field.type === 'reference' ? field.reference?.datasetId : undefined\n const hidden = Boolean(targetId && target && !target.readable)\n const description = hidden\n ? 'Records of a dataset that is not shared with you: exported by ID, never imported.'\n : field.type === 'reference'\n ? `${field.reference?.multiple ? 'Records' : 'A record'} of another dataset, by its ID or its name.`\n : field.type === 'coordinates'\n ? 'Latitude and longitude, separated by a comma.'\n : String(field.description ?? '').trim()\n return {\n id,\n label,\n group: DATASET_TRANSFER_FIELDS_GROUP,\n type,\n ...(isRequiredField(field) ? { required: true } : {}),\n ...(isWritableDatasetField(field) && !hidden ? {} : { readOnly: true }),\n // An export from before AGL-3530 headed every column with the field's id,\n // so a file like that still maps itself.\n ...(label.toLowerCase() !== fieldId.toLowerCase() ? { aliases: [fieldId] } : {}),\n ...(type === 'picklist' ? { picklistId: datasetOptionsPicklistId(fieldId) } : {}),\n ...(targetId\n ? {\n lookup: {\n resource: datasetTransferResourceKey(targetId),\n by: [\n TRANSFER_ID_FIELD,\n ...(field.reference?.displayFieldId ? [datasetTransferFieldId(field.reference.displayFieldId)] : []),\n ],\n ...(target?.readable && target.model && datasetAcceptsNamedRecord(target.model, field.reference?.displayFieldId)\n ? { creatable: true }\n : {}),\n },\n }\n : {}),\n ...(description ? { description } : {}),\n }\n}\n\n/**\n * The dataset's catalog: its fields in the dataset's order under one group\n * (named after the dataset when `label` is given), then the record's Aglyn ID (added by the core) and its created and updated\n * times. `targets` is what the server read of each referenced dataset, by id.\n */\nexport function datasetTransferCatalog(\n model: DatasetModel,\n label = 'Fields',\n targets: Readonly<Record<string, DatasetReferenceTarget>> = {},\n): TransferCatalogInput {\n return {\n standard: (model.order ?? [])\n .filter((fieldId) => model.fields?.[fieldId])\n .map((fieldId) => {\n const field = model.fields[fieldId] as DatasetFieldDefinition\n const targetId = field.type === 'reference' ? field.reference?.datasetId : undefined\n return datasetTransferField(fieldId, field, targetId ? targets[targetId] : undefined)\n }),\n system: [\n { id: DATASET_TRANSFER_CREATED_FIELD, label: 'Created', type: 'datetime', readOnly: true },\n { id: DATASET_TRANSFER_UPDATED_FIELD, label: 'Updated', type: 'datetime', readOnly: true },\n ],\n groups: [{ id: DATASET_TRANSFER_FIELDS_GROUP, label }],\n }\n}\n\n/** An options field's values as a picklist the values step can match against. */\nexport function datasetOptionsPicklist(field: DatasetFieldDefinition): { spec: PicklistSpec; set: PicklistValueSet } {\n const options = [...new Set((field.validation?.options ?? []).map((option) => String(option)).filter(Boolean))]\n return {\n // Restricted: `validateDocument` refuses a value the options do not hold.\n spec: { restricted: true, standardValues: options.map((option) => ({ id: option, label: option })) },\n set: { values: options.map((option) => ({ id: option, label: option, active: true })), defaultValueId: null },\n }\n}\n\n/*------------------------------------------\n * Values: storage form ↔ transfer form\n *-----------------------------------------*/\n\nconst finite = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value)\n\n/** A stored time — epoch milliseconds, a Firestore Timestamp, a date string — as ISO-8601, or `null`. */\nfunction isoOf(value: unknown): string | null {\n if (finite(value)) return new Date(value).toISOString()\n const timestamp = value as { toMillis?: () => number } | null\n if (timestamp && typeof timestamp.toMillis === 'function') return new Date(timestamp.toMillis()).toISOString()\n if (typeof value === 'string' && value.trim()) {\n const millis = Date.parse(value)\n return Number.isFinite(millis) ? new Date(millis).toISOString() : null\n }\n return null\n}\n\n/** One stored value as the wizard reads it, or `undefined` for a value the record does not hold. */\nfunction transferValueOf(field: DatasetFieldDefinition, stored: unknown): unknown {\n if (stored === null || stored === undefined || stored === '') return undefined\n switch (field.type) {\n case 'timestamp':\n return isoOf(stored) ?? String(stored)\n case 'bool':\n return stored === 'true' ? true : stored === 'false' ? false : stored\n case 'int32':\n case 'int64':\n case 'float': {\n // A form or an automation stored numbers as text before AGL-2773.\n const number = typeof stored === 'string' && stored.trim() ? Number(stored) : stored\n return finite(number) ? number : stored\n }\n case 'coordinates': {\n const point = stored as { latitude?: unknown; longitude?: unknown }\n return finite(point?.latitude) && finite(point?.longitude) ? `${point.latitude}, ${point.longitude}` : stored\n }\n case 'sorted':\n case 'reference': {\n if (field.type === 'reference' && !field.reference?.multiple) return stored\n const list = coerceListValue(stored)\n return Array.isArray(list) ? (list.length ? list : undefined) : stored\n }\n case 'text':\n return finite(stored) || typeof stored === 'boolean' ? String(stored) : stored\n default:\n return stored\n }\n}\n\n/**\n * A stored record as transfer values, by catalog id: every field the model\n * lists that the record holds, its Aglyn ID, and its created and updated\n * times when it carries them. A field the record does not hold is absent,\n * which the plan reads as blank.\n */\nexport function datasetRecordTransferValues(\n model: DatasetModel,\n record: { id?: string; values?: Readonly<Record<string, unknown>> | null; createdAt?: unknown; updatedAt?: unknown },\n): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n const values = record.values ?? {}\n for (const fieldId of model.order ?? []) {\n const field = model.fields?.[fieldId]\n if (!field) continue\n const value = transferValueOf(field, values[fieldId])\n if (value !== undefined) out[datasetTransferFieldId(fieldId)] = value\n }\n // After the fields: a record is named by the first text it holds (the\n // wizard's matches and lookups), which is its first field, not its ID.\n if (record.id) out[TRANSFER_ID_FIELD] = record.id\n const created = isoOf(record.createdAt)\n const updated = isoOf(record.updatedAt)\n if (created) out[DATASET_TRANSFER_CREATED_FIELD] = created\n if (updated) out[DATASET_TRANSFER_UPDATED_FIELD] = updated\n return out\n}\n\n/**\n * Transfer values in storage form, through `coerceDocumentValues` — the\n * coercion every record write runs. `values` holds what is written and\n * `cleared` the fields a blank value empties. The Aglyn ID, the record's\n * times, unknown ids and fields a file never writes are ignored.\n */\nexport function datasetStorageValues(\n model: DatasetModel,\n transfer: Readonly<Record<string, unknown>>,\n): { values: Record<string, unknown>; cleared: string[] } {\n const input: Record<string, unknown> = {}\n const cleared: string[] = []\n for (const [transferId, value] of Object.entries(transfer)) {\n const fieldId = datasetFieldIdOf(transferId)\n const field = fieldId ? model.fields?.[fieldId] : undefined\n if (!fieldId || !field || !isWritableDatasetField(field)) continue\n if (isBlankTransferValue(value)) {\n cleared.push(fieldId)\n continue\n }\n // Text read from a JSON file's number is still the text it spells.\n input[fieldId] = field.type === 'text' && (finite(value) || typeof value === 'boolean') ? String(value) : value\n }\n return { values: coerceDocumentValues(model, input), cleared }\n}\n\n/*------------------------------------------\n * Matching\n *-----------------------------------------*/\n\n/** How a dataset field compares as a match key, or `null` for one no query can find a record by. */\nexport function datasetMatchKeyFor(fieldId: string, field: DatasetFieldDefinition | undefined): MatchKeySpec | null {\n if (!field || !datasetFilterValuePath(fieldId)) return null\n const id = datasetTransferFieldId(fieldId)\n if (field.type === 'text') return { fieldId: id, normalizer: isOptionsField(field) ? 'trim' : 'caseless' }\n if (field.type === 'int32' || field.type === 'int64' || field.type === 'float') return { fieldId: id, normalizer: 'exact' }\n return null\n}\n\n/**\n * The keys a row may find its record by: the Aglyn ID, then every text and\n * number field the records' filter values can be asked by, in the dataset's\n * order. A person starts with the Aglyn ID and the dataset's page address —\n * the one field a dataset holds unique — and may add any other.\n */\nexport function datasetMatchKeyOffer(model: DatasetModel): TransferMatchKeyOffer {\n const keys: MatchKeySpec[] = [{ fieldId: TRANSFER_ID_FIELD, normalizer: 'aglynId' }]\n const defaults = [TRANSFER_ID_FIELD]\n for (const fieldId of model.order ?? []) {\n const field = model.fields?.[fieldId]\n const key = datasetMatchKeyFor(fieldId, field)\n if (!key) continue\n keys.push(key)\n if (isRecordAddressField(field)) defaults.push(key.fieldId)\n }\n return { keys, defaults }\n}\n\n/**\n * The `filterValues` equality that finds the records holding a normalized\n * key value: the path, and the value as the records store it. Plain text is\n * stored as its lower-cased key clipped to 64 characters, so two long values\n * can share it; the caller compares what it found against the whole value.\n */\nexport function datasetMatchKeyQuery(\n model: DatasetModel,\n key: MatchKeySpec,\n normalized: string,\n): { path: string; value: string | number } | null {\n const fieldId = datasetFieldIdOf(key.fieldId)\n const field = fieldId ? model.fields?.[fieldId] : undefined\n const path = fieldId ? datasetFilterValuePath(fieldId) : null\n if (!field || !path) return null\n if (field.type === 'int32' || field.type === 'int64' || field.type === 'float') {\n const number = Number(normalized)\n return Number.isFinite(number) ? { path, value: number } : null\n }\n if (field.type !== 'text') return null\n return { path, value: isOptionsField(field) ? normalized : datasetFilterTextKey(normalized) }\n}\n\n/** Whether a record's transfer values hold a normalized key value — the check a clipped query answer is held to. */\nexport function datasetHoldsKey(view: Readonly<Record<string, unknown>>, key: MatchKeySpec, normalized: string): boolean {\n return normalizeMatchValue(key.normalizer, view[key.fieldId]) === normalized\n}\n\n/*------------------------------------------\n * The dry run, held to the model\n *-----------------------------------------*/\n\n/** Field id → message, for the fields a write of these values would refuse. */\nexport function datasetWriteErrors(\n model: DatasetModel,\n values: Record<string, unknown>,\n only?: ReadonlySet<string>,\n): Record<string, string> {\n const errors = validateDocument(model, values)\n if (!only) return errors\n return Object.fromEntries(Object.entries(errors).filter(([fieldId]) => only.has(fieldId)))\n}\n\n/**\n * The values a planned row leaves a record holding, in storage form: a new\n * record's from the row alone, an existing one's from what it holds now\n * (`before`, transfer values) with the row's changes over it. Page addresses\n * are filled in as every record write fills them. `changed` names the dataset\n * fields the row writes.\n */\nexport function datasetPlannedValues(\n model: DatasetModel,\n row: Pick<PlannedTransferRow, 'verdict' | 'diff'>,\n before: Readonly<Record<string, unknown>> | null | undefined,\n): { values: Record<string, unknown>; changed: Set<string> } {\n const after: Record<string, unknown> = row.verdict === 'update' ? { ...(before ?? {}) } : {}\n const changed = new Set<string>()\n for (const change of row.diff) {\n after[change.fieldId] = change.after\n const fieldId = datasetFieldIdOf(change.fieldId)\n if (fieldId) changed.add(fieldId)\n }\n const { values } = datasetStorageValues(model, after)\n return { values: fillRecordAddresses(model, values), changed }\n}\n\n/**\n * The plan with every row the model would refuse failed, naming the fields\n * (`missing`, read with the reason \"A value refuses the row\"): a value outside\n * a field's options, past its bounds, not matching its pattern, or refused by\n * a custom field type's validator. An update is held only on the fields it\n * writes, so a record stored before a rule existed is not refused for a value\n * the file never touched.\n */\nexport function refuseInvalidDatasetRows(\n model: DatasetModel,\n plan: TransferPlan,\n existing: ReadonlyMap<string, Readonly<Record<string, unknown>>>,\n): TransferPlan {\n let refused = 0\n const rows = plan.rows.map((row): PlannedTransferRow => {\n if (row.verdict !== 'create' && row.verdict !== 'update') return row\n const before = row.recordId ? existing.get(row.recordId) : null\n const { values, changed } = datasetPlannedValues(model, row, before)\n const errors = datasetWriteErrors(model, values, row.verdict === 'update' ? changed : undefined)\n const fields = Object.keys(errors)\n if (!fields.length) return row\n refused += 1\n return { ...row, verdict: 'fail', reason: 'refusedValue', missing: fields.map(datasetTransferFieldId), diff: [] }\n })\n if (!refused) return plan\n const summary = { create: 0, update: 0, unchanged: 0, skip: 0, fail: 0, total: rows.length }\n for (const row of rows) summary[row.verdict] += 1\n return { ...plan, rows, summary }\n}\n\n/**\n * The plan with every new record past `maxCreates` failed as past what the\n * plan allows, under the `planLimit` warning the person acknowledges. Run\n * after {@link refuseInvalidDatasetRows}, so a row the model refuses never\n * takes the room a valid row could have had.\n */\nexport function limitDatasetCreates(plan: TransferPlan, maxCreates: number | undefined): TransferPlan {\n if (maxCreates === undefined || plan.summary.create <= maxCreates) return plan\n let room = Math.max(0, maxCreates)\n const over: number[] = []\n const rows = plan.rows.map((row): PlannedTransferRow => {\n if (row.verdict !== 'create') return row\n if (room > 0) {\n room -= 1\n return row\n }\n over.push(row.index)\n return { ...row, verdict: 'fail', reason: 'planLimit', diff: [], warnings: [...new Set([...row.warnings, 'planLimit' as const])] }\n })\n const summary = { create: 0, update: 0, unchanged: 0, skip: 0, fail: 0, total: rows.length }\n for (const row of rows) summary[row.verdict] += 1\n const limit: TransferWarning = {\n class: 'planLimit',\n count: over.length,\n rows: over.length,\n fieldIds: [],\n samples: over.slice(0, 5).map((row) => ({ row, detail: 'Past the records this plan allows in a dataset' })),\n requiresAcknowledgement: true,\n }\n const warnings = [...plan.warnings.filter((warning) => warning.class !== 'planLimit'), limit]\n return {\n rows,\n summary,\n warnings,\n acknowledgementsRequired: [...new Set([...plan.acknowledgementsRequired, 'planLimit' as const])],\n }\n}\n"],"names":["TRANSFER_ID_FIELD","isBlankTransferValue","normalizeMatchValue","coerceDocumentValues","coerceListValue","datasetFilterTextKey","datasetFilterValuePath","humanizeDatasetFieldId","validateDocument","fillRecordAddresses","isRecordAddressField","datasetTransferResourceKey","DATASET_TRANSFER_FIELDS_GROUP","DATASET_TRANSFER_CREATED_FIELD","DATASET_TRANSFER_UPDATED_FIELD","SHADOWED_PREFIX","datasetTransferFieldId","fieldId","datasetFieldIdOf","transferFieldId","datasetOptionsPicklistId","datasetFieldOfPicklist","picklistId","startsWith","slice","length","isOptionsField","field","type","validation","options","isWritableDatasetField","Boolean","datasetTransferType","reference","multiple","datasetId","isRequiredField","slugFrom","required","datasetAcceptsNamedRecord","model","displayFieldId","display","fields","undefined","values","Object","keys","datasetWriteErrors","every","datasetTransferField","target","id","label","String","name","trim","targetId","hidden","readable","description","group","readOnly","toLowerCase","aliases","lookup","resource","by","creatable","datasetTransferCatalog","targets","standard","order","filter","map","system","groups","datasetOptionsPicklist","Set","option","spec","restricted","standardValues","set","active","defaultValueId","finite","value","Number","isFinite","isoOf","Date","toISOString","timestamp","toMillis","millis","parse","transferValueOf","stored","number","point","latitude","longitude","list","Array","isArray","datasetRecordTransferValues","record","out","created","createdAt","updated","updatedAt","datasetStorageValues","transfer","input","cleared","transferId","entries","push","datasetMatchKeyFor","normalizer","datasetMatchKeyOffer","defaults","key","datasetMatchKeyQuery","normalized","path","datasetHoldsKey","view","only","errors","fromEntries","has","datasetPlannedValues","row","before","after","verdict","changed","change","diff","add","refuseInvalidDatasetRows","plan","existing","refused","rows","recordId","get","reason","missing","summary","create","update","unchanged","skip","fail","total","limitDatasetCreates","maxCreates","room","Math","max","over","index","warnings","limit","class","count","fieldIds","samples","detail","requiresAcknowledgement","warning","acknowledgementsRequired"],"mappings":"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"}
@@ -14,9 +14,5 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- /**
18
- * Dataset io model (AGL-413): CSV serialize/parse for dataset records relocated from core app-utils.
19
- * Context-free — importable by client components, /server handlers, and
20
- * other plugins/apps via `@aglyn/plugins-data/model`.
21
- */
22
- export * from './dataset-io';
17
+ import type { PluginTransferResource } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources';
18
+ export declare const DATASET_TRANSFER_HOOKS: PluginTransferResource;