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

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 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/model/dataset-models.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { RepeatRowsModel } from '@aglyn/aglyn/app-utils/expand-repeatables'\nimport { validateCustomFieldValue } from '@aglyn/aglyn/plugin-manager/custom-fields'\n\n/**\n * Dataset models (AGL-177): the runtime promotion of the type-level\n * blueprint in `libs/shared/data/types/src/lib/dod.ts` (kept as the\n * referenced design doc — this module is the single runtime source of\n * truth). A model lives on the dataset doc\n * (`orgs/{orgId}/datasets/{id}.model`) and drives the typed editor, import\n * and site-restore validation, and every record write — the console, `/v1`,\n * a bound form's submission and an automation step — through the shared\n * `validateDocument`/`coerceDocumentValues` pair so they can't disagree. The\n * form and the automation step reach the pair through\n * `prepareDatasetRecordWrite` (AGL-2773).\n *\n * Storage conventions (Firestore-safe): timestamps as epoch millis\n * numbers, coordinates as `{ latitude, longitude }`, `sorted` as arrays,\n * `map` as plain objects, references as target-document id strings.\n */\n\n/**\n * A human field entry from the quick creator: the stable id plus the\n * display name shown in table headers and bindings pickers (AGL-558).\n */\nexport interface DatasetFieldEntry {\n id: string\n name: string\n}\n\n/** Display fallback for raw ids: \"roast_preference\" → \"Roast preference\". */\nexport function humanizeDatasetFieldId(id: string): string {\n const words = id.replace(/_/g, ' ').trim()\n return words ? words.charAt(0).toUpperCase() + words.slice(1) : id\n}\n\n/** dod.ts `FT.Tag` vocabulary plus `reference` (AGL-180 builds on it). */\nexport type DatasetFieldType =\n | 'bool'\n | 'bytes'\n | 'timestamp'\n | 'float'\n | 'int32'\n | 'int64'\n | 'nil'\n | 'text'\n | 'coordinates'\n | 'map'\n | 'sorted'\n | 'reference'\n\nexport const DATASET_FIELD_TYPES: DatasetFieldType[] = [\n 'text',\n 'bool',\n 'int32',\n 'int64',\n 'float',\n 'timestamp',\n 'coordinates',\n 'map',\n 'sorted',\n 'bytes',\n 'nil',\n 'reference',\n]\n\n/** Display names, mirroring dod.ts `lbl`. */\nexport const DATASET_FIELD_TYPE_LABELS: Record<DatasetFieldType, string> = {\n text: 'Text',\n bool: 'Boolean',\n int32: 'Integer',\n int64: 'Big integer',\n float: 'Number',\n timestamp: 'Date & time',\n coordinates: 'Coordinates',\n map: 'Map',\n sorted: 'List',\n bytes: 'Bytes',\n nil: 'Null',\n reference: 'Reference',\n}\n\n/**\n * Plain-JSON validation vocabulary (the dod.ts `Eval` shape, extended\n * with min/max and options, serialized flat instead of enum-keyed).\n */\nexport interface DatasetFieldValidation {\n required?: boolean\n /** Source of a RegExp applied to text values. */\n regex?: string\n /** Numbers/timestamps: value bound. Text: length bound. */\n min?: number\n max?: number\n /** Enum: value must be one of these (text fields). */\n options?: string[]\n}\n\nexport interface DatasetFieldDefinition {\n /**\n * Human-facing display label only. The stable reference id is the KEY in\n * `DatasetModel.fields` (and `order`), NOT this name — bindings resolve\n * `{{item.<fieldId>}}` against that key. The default id is derived from\n * this name once at creation but is user-overridable (AGL-578); renaming\n * the label afterwards never changes the id.\n */\n name: string\n type: DatasetFieldType\n /**\n * Plugin-declared custom type riding this base type (AGL-434) — see\n * plugin-manager/custom-fields. Unknown names degrade to the base type.\n */\n customType?: string\n /**\n * A page-address field's source (AGL-3475): the field an EMPTY address\n * fills in from, once — see `fillRecordAddresses` in `record-pages`.\n * Meaningful only on a field of the page-address custom type.\n */\n slugFrom?: string\n description?: string\n required?: boolean\n /** Default applied by editors when creating documents; never coerced. */\n default?: unknown\n validation?: DatasetFieldValidation\n /**\n * Reference fields (AGL-180): target collection + display field id.\n * `multiple` stores a `sorted` array of FKeys (small-cardinality\n * many-to-many per the dod.ts guidance); `onDelete` is the referenced-\n * document delete policy (default `setNull` — never cascade in v2).\n */\n reference?: {\n datasetId: string\n displayFieldId?: string\n multiple?: boolean\n onDelete?: 'restrict' | 'setNull'\n }\n}\n\nexport interface DatasetModel {\n fields: Record<string, DatasetFieldDefinition>\n /** Field ids in display order. */\n order: string[]\n}\n\n/**\n * Migration shim (v1 → v2): datasets created before models carry flat\n * `fields: string[]` — derive a model where every column is an optional\n * text field whose id is the column name, so the editor and AGL-103\n * bindings keep working on unmigrated docs during rollout.\n */\nexport function deriveModelFromFields(fields: string[]): DatasetModel {\n const model: DatasetModel = { fields: {}, order: [] }\n for (const name of fields) {\n // Raw snake_case ids read as titles in table headers and pickers;\n // the id stays the stable key (AGL-558).\n model.fields[name] = { name: humanizeDatasetFieldId(name), type: 'text' }\n model.order.push(name)\n }\n return model\n}\n\n/**\n * Model from quick-creator entries (AGL-558): stable slug ids with the\n * human display names preserved — \"Roast preference\" → id\n * `roast_preference`, name \"Roast preference\".\n */\nexport function modelFromFieldEntries(\n entries: readonly DatasetFieldEntry[],\n): DatasetModel {\n const model: DatasetModel = { fields: {}, order: [] }\n for (const entry of entries) {\n model.fields[entry.id] = { name: entry.name, type: 'text' }\n model.order.push(entry.id)\n }\n return model\n}\n\n/**\n * The part of a dataset's model a repeat reads (AGL-180): each reference\n * field, by the id of the dataset it points into, which is a key the rows map\n * answers under. Every field the model declares, in `order` or not — a hop\n * resolves wherever its target's rows were loaded.\n */\nexport function repeatRowsModelOf(model: DatasetModel): RepeatRowsModel {\n const references: Record<string, string> = {}\n for (const [fieldId, field] of Object.entries(model.fields)) {\n const target = field?.type === 'reference' ? field.reference?.datasetId : undefined\n if (target) references[fieldId] = target\n }\n return { references }\n}\n\n/** The dataset's model, deriving one from v1 `fields` when absent. */\nexport function effectiveDatasetModel(dataset: {\n model?: DatasetModel\n fields?: string[]\n}): DatasetModel {\n if (dataset.model?.fields && dataset.model.order?.length) {\n return dataset.model\n }\n return deriveModelFromFields(dataset.fields ?? [])\n}\n\nconst isFiniteNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value)\n\n/**\n * Validates a document's values against a model, returning fieldId →\n * human-readable error (empty object = valid). Values are expected in\n * storage form (see the module doc); use `coerceDocumentValues` first\n * when starting from user input strings.\n */\nexport function validateDocument(\n model: DatasetModel,\n values: Record<string, unknown>,\n): Record<string, string> {\n const errors: Record<string, string> = {}\n for (const fieldId of model.order) {\n const field = model.fields[fieldId]\n if (!field) continue\n const value = values[fieldId]\n const required = field.required || field.validation?.required\n // An empty list holds nothing, so it answers \"required\" as an empty\n // string does: `,,` typed into a list field coerces to `[]` (AGL-3496).\n const empty =\n value == null ||\n value === '' ||\n (Array.isArray(value) && !value.length && isListField(field))\n if (empty) {\n if (required) errors[fieldId] = `${field.name} is required`\n continue\n }\n const rules = field.validation ?? {}\n switch (field.type) {\n case 'text': {\n if (typeof value !== 'string') {\n errors[fieldId] = `${field.name} must be text`\n break\n }\n if (rules.min != null && value.length < rules.min) {\n errors[fieldId] = `${field.name} must be at least ${rules.min} characters`\n } else if (rules.max != null && value.length > rules.max) {\n errors[fieldId] = `${field.name} must be at most ${rules.max} characters`\n } else if (rules.regex) {\n try {\n if (!new RegExp(rules.regex).test(value)) {\n errors[fieldId] = `${field.name} has an invalid format`\n }\n } catch {\n // Broken pattern in the model never blocks writes.\n }\n }\n if (\n !errors[fieldId] &&\n rules.options?.length &&\n !rules.options.includes(value)\n ) {\n errors[fieldId] = `${field.name} must be one of: ${rules.options.join(', ')}`\n }\n break\n }\n case 'bool':\n if (typeof value !== 'boolean') {\n errors[fieldId] = `${field.name} must be true or false`\n }\n break\n case 'int32':\n case 'int64':\n if (!isFiniteNumber(value) || !Number.isInteger(value)) {\n errors[fieldId] = `${field.name} must be a whole number`\n break\n }\n if (rules.min != null && value < rules.min) {\n errors[fieldId] = `${field.name} must be ≥ ${rules.min}`\n } else if (rules.max != null && value > rules.max) {\n errors[fieldId] = `${field.name} must be ≤ ${rules.max}`\n }\n break\n case 'float':\n case 'timestamp':\n if (!isFiniteNumber(value)) {\n errors[fieldId] =\n field.type === 'timestamp'\n ? `${field.name} must be a date`\n : `${field.name} must be a number`\n break\n }\n if (rules.min != null && value < rules.min) {\n errors[fieldId] = `${field.name} must be ≥ ${rules.min}`\n } else if (rules.max != null && value > rules.max) {\n errors[fieldId] = `${field.name} must be ≤ ${rules.max}`\n }\n break\n case 'coordinates': {\n const coordinates = value as { latitude?: unknown; longitude?: unknown }\n if (\n !isFiniteNumber(coordinates?.latitude) ||\n !isFiniteNumber(coordinates?.longitude) ||\n Math.abs(coordinates.latitude) > 90 ||\n Math.abs(coordinates.longitude) > 180\n ) {\n errors[fieldId] = `${field.name} must be valid coordinates`\n }\n break\n }\n case 'sorted':\n // A list of values: an entry that is itself an object or a list is\n // not one, and `coerceListValue` leaves it here to be refused.\n if (\n !Array.isArray(value) ||\n value.some((entry) => entry !== null && typeof entry === 'object')\n ) {\n errors[fieldId] = `${field.name} must be a list`\n }\n break\n case 'map':\n if (typeof value !== 'object' || Array.isArray(value)) {\n errors[fieldId] = `${field.name} must be a map`\n }\n break\n case 'reference':\n if (field.reference?.multiple) {\n if (\n !Array.isArray(value) ||\n value.some((entry) => typeof entry !== 'string' || !entry)\n ) {\n errors[fieldId] = `${field.name} must reference documents`\n }\n } else if (typeof value !== 'string' || !value) {\n errors[fieldId] = `${field.name} must reference a document`\n }\n break\n case 'bytes':\n case 'nil':\n break\n }\n }\n // Custom field types (AGL-434): plugin validators run after the base\n // checks, only on fields that passed them.\n for (const fieldId of model.order) {\n const field = model.fields[fieldId]\n if (!field?.customType || errors[fieldId]) continue\n const customError = validateCustomFieldValue(\n field.customType,\n values[fieldId],\n )\n if (customError) errors[fieldId] = `${field.name}: ${customError}`\n }\n return errors\n}\n\n/** Storage value → grid display string (AGL-179). */\nexport function formatDatasetValue(\n field: DatasetFieldDefinition,\n value: unknown,\n): string {\n if (value == null || value === '') return ''\n switch (field.type) {\n case 'bool':\n return value === true ? '✓' : value === false ? '—' : String(value)\n case 'timestamp':\n return isFiniteNumber(value)\n ? new Date(value).toISOString().slice(0, 16).replace('T', ' ')\n : String(value)\n case 'coordinates': {\n const coordinates = value as { latitude?: number; longitude?: number }\n return isFiniteNumber(coordinates?.latitude) &&\n isFiniteNumber(coordinates?.longitude)\n ? `${coordinates.latitude}, ${coordinates.longitude}`\n : String(value)\n }\n case 'sorted':\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 * Storage value → form-input string (AGL-179): the inverse of\n * `coerceDocumentValues` for populating type-appropriate inputs\n * (timestamps as `datetime-local` values, coordinates as \"lat, lon\").\n */\nexport function datasetValueToInput(\n field: DatasetFieldDefinition,\n value: unknown,\n): string {\n if (value == null) return ''\n if (field.type === 'timestamp' && isFiniteNumber(value)) {\n return new Date(value).toISOString().slice(0, 16)\n }\n if (field.type === 'bool') {\n return value === true ? 'true' : value === false ? 'false' : String(value)\n }\n if (field.type === 'coordinates' || field.type === 'sorted') {\n return formatDatasetValue(field, value)\n }\n if (field.type === 'map') return formatDatasetValue(field, value)\n return String(value)\n}\n\n/**\n * Whether a field stores a list: `sorted`, and a `reference` that may point at\n * more than one record. Both go through {@link coerceListValue}.\n */\nexport function isListField(field: DatasetFieldDefinition): boolean {\n return (\n field.type === 'sorted' ||\n (field.type === 'reference' && Boolean(field.reference?.multiple))\n )\n}\n\n/**\n * The entries of a list given as an array, each a trimmed string with the\n * empty ones dropped — or `null` when an entry is not a scalar (an object, a\n * nested list), which is not a list of values and is left for\n * `validateDocument` to refuse rather than flattened into `[object Object]`.\n */\nfunction listEntries(input: readonly unknown[]): string[] | null {\n const entries: string[] = []\n for (const entry of input) {\n if (entry == null) continue\n if (\n typeof entry !== 'string' &&\n typeof entry !== 'number' &&\n typeof entry !== 'boolean'\n ) {\n return null\n }\n const text = String(entry).trim()\n if (text) entries.push(text)\n }\n return entries\n}\n\n/**\n * A list field's value in storage form (AGL-3496) — the ONE coercion every\n * writer of a `sorted` or multiple-`reference` field reaches through\n * {@link coerceDocumentValues}: the console's record form and its CSV/JSON\n * import, `/api/orgs/datasets`, `/v1` record create and update, form\n * submissions and workflow steps (`prepareDatasetRecordWrite`).\n *\n * - An array is taken as the list: entries stringified and trimmed, empty\n * ones dropped.\n * - A string that parses as a JSON array of scalars is that array. A JSON\n * import hands a list cell over as `[\"Residential\",\"Commercial\"]`, and so\n * does any API client that serialized one; split on commas, it was stored\n * as `[\"[\\\"Residential\\\"\", \"\\\"Commercial\\\"]\"]` and no filter matched it.\n * The console form round-trips such a record too: it shows the stored\n * fragments joined, which reads back as the JSON array, so re-saving a\n * record written that way repairs it.\n * - Any other string is comma-separated, as typed into the form.\n * - Anything else (a number, an object, an array holding objects) passes\n * through untouched for `validateDocument` to refuse.\n */\nexport function coerceListValue(raw: unknown): unknown {\n if (Array.isArray(raw)) return listEntries(raw) ?? raw\n if (typeof raw !== 'string') return raw\n const text = raw.trim()\n if (text.startsWith('[') && text.endsWith(']')) {\n try {\n const parsed: unknown = JSON.parse(text)\n const entries = Array.isArray(parsed) ? listEntries(parsed) : null\n if (entries) return entries\n } catch {\n // Bracketed but not JSON — `[draft], final` — is a comma list.\n }\n }\n return text\n .split(',')\n .map((part) => part.trim())\n .filter(Boolean)\n}\n\nconst BOOL_TRUE_WORDS = new Set(['true', 'yes', 'on', '1'])\nconst BOOL_FALSE_WORDS = new Set(['false', 'no', 'off', '0'])\n\n/**\n * Coerces user-input strings (form fields, CSV cells) into storage form\n * per the field type. Unparseable input is passed through untouched so\n * `validateDocument` reports it instead of silently mangling it.\n *\n * A list field is coerced whatever shape it arrives in — array or string —\n * through {@link coerceListValue}; every other non-string value is already\n * in storage form and passes through.\n */\nexport function coerceDocumentValues(\n model: DatasetModel,\n input: Record<string, unknown>,\n): Record<string, unknown> {\n const values: Record<string, unknown> = {}\n for (const fieldId of model.order) {\n const field = model.fields[fieldId]\n const raw = input[fieldId]\n if (!field || raw == null || raw === '') continue\n if (isListField(field)) {\n values[fieldId] = coerceListValue(raw)\n continue\n }\n if (typeof raw !== 'string') {\n values[fieldId] = raw\n continue\n }\n switch (field.type) {\n case 'bool': {\n // The words a form control or a CSV cell says yes and no with: a\n // lone checkbox posts `on`, a spreadsheet exports `TRUE` or `1`.\n const word = raw.trim().toLowerCase()\n values[fieldId] = BOOL_TRUE_WORDS.has(word)\n ? true\n : BOOL_FALSE_WORDS.has(word)\n ? false\n : raw\n break\n }\n case 'int32':\n case 'int64': {\n const parsed = Number(raw)\n values[fieldId] = Number.isInteger(parsed) ? parsed : raw\n break\n }\n case 'float': {\n const parsed = Number(raw)\n values[fieldId] = Number.isFinite(parsed) ? parsed : raw\n break\n }\n case 'timestamp': {\n const millis = Date.parse(raw)\n values[fieldId] = Number.isFinite(millis) ? millis : raw\n break\n }\n case 'coordinates': {\n const [latitude, longitude] = raw.split(',').map((part) => Number(part.trim()))\n values[fieldId] =\n Number.isFinite(latitude) && Number.isFinite(longitude)\n ? { latitude, longitude }\n : raw\n break\n }\n case 'map':\n try {\n values[fieldId] = JSON.parse(raw)\n } catch {\n values[fieldId] = raw\n }\n break\n default:\n values[fieldId] = raw\n }\n }\n return values\n}\n\n/**\n * Every reference id a record holds, flattened out of its `reference` fields\n * into one de-duplicated list.\n *\n * A reference field stores either a single target id or — when\n * `reference.multiple` is set — an array of them, and a model may declare\n * several. Both shapes flatten into the same list because the question asked\n * of it is never \"which field\", it is \"does this record still point at that\n * document\".\n *\n * Fields are read from `order` UNIONED with the keys of `fields`. Every other\n * pass over a model walks `order` alone, which is right for anything the user\n * sees — but a reference declared without a display slot still holds a real\n * FKey, and an integrity index that skipped it would under-report.\n */\nexport function datasetReferencedIds(\n model: DatasetModel,\n values: Record<string, unknown> | undefined,\n): string[] {\n const ids = new Set<string>()\n const fieldIds = new Set([\n ...(model.order ?? []),\n ...Object.keys(model.fields ?? {}),\n ])\n for (const fieldId of fieldIds) {\n if (model.fields?.[fieldId]?.type !== 'reference') continue\n const stored = values?.[fieldId]\n for (const entry of Array.isArray(stored) ? stored : [stored]) {\n const id = typeof entry === 'string' ? entry.trim() : ''\n if (id) ids.add(id)\n }\n }\n return [...ids]\n}\n\n/**\n * The integrity index a record write carries, so referential integrity can be\n * decided by a QUERY rather than by the rows a listener happened to fetch.\n *\n * Deleting a record has to answer \"is anything still pointing at this?\", and\n * `restrict` refuses the delete on a yes while `setNull` strips the FKey. That\n * question cannot be asked where the answer is stored: dataset fields are\n * user-defined, so `records.values` carries a deliberate index exemption in\n * `cloud/firebase-firestore.indexes.json` — auto-indexing an unbounded map\n * blows Firestore's per-document index-entry limit — and\n * `where('values.<fieldId>', '==', id)` is therefore FAILED_PRECONDITION.\n * Reading a page of records and filtering them in the browser answers only for\n * the rows in that page, which is a browse window standing in for a\n * correctness check: a reference held outside it reads as no reference at all,\n * the delete proceeds, and `restrict` silently fails to restrict.\n *\n * `referencedIds` is that same reference, denormalized to a top-level array\n * Firestore will index, so one `array-contains` reaches every record.\n *\n * ⚠️ OMITTED when a record references nothing, rather than written as `[]`.\n * `isNotEmpty` is served as `!= null` and an empty array is not null, so a\n * record stamped `[]` answers \"holds a reference\" for a dataset that holds\n * none.\n *\n * ⚠️ Spread this at EVERY write that sets a record's `values`. A write that\n * sets values without it leaves the index describing the PREVIOUS values, and\n * a stale integrity index is the exact failure it exists to prevent — with the\n * UI reporting the record as safe to delete. On a merging write use\n * `datasetIntegrityUpdate` instead: omission leaves a stale array in place\n * where clearing the last reference has to remove the field.\n */\nexport function datasetIntegrityFields(\n model: DatasetModel,\n values: Record<string, unknown> | undefined,\n): {\n referencedIds?: string[]\n filterKeys?: string[]\n filterValues?: DatasetFilterValues\n} {\n const referencedIds = datasetReferencedIds(model, values)\n // The records table's filter fields (`datasetFilterKeys`,\n // `datasetFilterValues`) travel on the same writes for the same reason: a\n // record written without them is invisible to every filter the query\n // serves. Omitted when empty, like `referencedIds`.\n const filterKeys = datasetFilterKeys(model, values)\n const filterValues = datasetFilterValues(model, values)\n return {\n ...(referencedIds.length ? { referencedIds } : {}),\n ...(filterKeys.length ? { filterKeys } : {}),\n ...(Object.keys(filterValues).length ? { filterValues } : {}),\n }\n}\n\n/**\n * `datasetIntegrityFields` for a MERGING write (`update`, `set` with\n * `mergeFields`), where leaving the field out preserves what is stored\n * rather than clearing it.\n *\n * A record whose last reference is cleared has to have the field REMOVED, or\n * it goes on answering the `array-contains` for a document it no longer points\n * at — a `restrict` that refuses a delete nothing is holding. `clear` is the\n * caller's delete sentinel: `deleteField()` in the web SDK,\n * `FieldValue.delete()` in the Admin SDK. It is passed in rather than imported\n * because this module is shared by both and must stay SDK-free. The filter\n * fields are cleared the same way, so a record emptied of every filterable\n * value stops answering the filters its old values did.\n *\n * ⚠️ `filterValues` is a MAP, and it is written WHOLE: `update` and\n * `mergeFields` replace a field outright, so a value cleared since the last\n * write leaves the map with it. `set(…, { merge: true })` does NOT — it merges\n * maps key by key and would keep the cleared value answering its old\n * equality — so a record write never uses it; list the fields in\n * `mergeFields` instead.\n */\nexport function datasetIntegrityUpdate<TClear>(\n model: DatasetModel,\n values: Record<string, unknown> | undefined,\n clear: TClear,\n): {\n referencedIds: string[] | TClear\n filterKeys: string[] | TClear\n filterValues: DatasetFilterValues | TClear\n} {\n const referencedIds = datasetReferencedIds(model, values)\n const filterKeys = datasetFilterKeys(model, values)\n const filterValues = datasetFilterValues(model, values)\n return {\n referencedIds: referencedIds.length ? referencedIds : clear,\n filterKeys: filterKeys.length ? filterKeys : clear,\n filterValues: Object.keys(filterValues).length ? filterValues : clear,\n }\n}\n\n/**\n * A dataset record's FILTER TOKENS: every word-level question the records\n * table can ask of a record, flattened into one array of strings Firestore\n * indexes on its own. Its twin, {@link datasetFilterValues}, holds the\n * equalities.\n *\n * ## Why these fields, and not the values\n *\n * A record's fields live under `values`, a user-defined, unbounded map that\n * the index configuration exempts from indexing — auto-indexing it can exceed\n * Firestore's per-document index-entry limit — so `where('values.<field>', …)`\n * is refused. A composite index per dataset would need an admin call per\n * dataset, runs into the project's composite-index cap, and is deleted by the\n * next `firestore:indexes` deploy of a file that does not list it.\n *\n * `filterKeys` and `filterValues` need none of that. Firestore's AUTOMATIC\n * single-field indexes serve `where('filterKeys', 'array-contains', token)`\n * and `where('filterValues.<field>', '==', value)`, and every automatic index\n * ends in the document name — so any number of those equalities and ONE\n * `array-contains`, under `orderBy(documentId())`, compose by index merging,\n * with no composite index and no index deploy, on any dataset of any project\n * (AGL-3321).\n *\n * ## The token grammar\n *\n * `f:<fieldId>=<value>` a field equals a value. Enum (a text field with\n * `validation.options`) values are kept exactly;\n * plain text is lower-cased, trimmed and clipped to\n * {@link DATASET_FILTER_VALUE_MAX} characters;\n * booleans are `true`/`false`; numbers are\n * `String(number)`; a `sorted` list contributes one\n * token per member.\n * `f:<fieldId>^<prefix>` a word of a plain text field starts with this.\n * `s:<prefix>` a word of ANY text value (enums and list members\n * included) starts with this — the quick search.\n *\n * Timestamps, references, maps, bytes, coordinates and nil fields carry no\n * field tokens: a timestamp is asked in ranges, which one token cannot serve,\n * and the rest are not asked about by value.\n *\n * The array is sorted, de-duplicated and capped at\n * {@link DATASET_FILTER_KEYS_MAX}. Past the cap, the `=` tokens are kept\n * first, then the per-field word prefixes, then the search prefixes, so a\n * very long record loses search reach before it loses exact matches.\n */\nexport const DATASET_FILTER_KEYS_MAX = 500\n\n/** Plain text values are clipped to this many characters in an `=` token. */\nexport const DATASET_FILTER_VALUE_MAX = 64\n/** Word prefixes run from 1 to this many characters. */\nexport const DATASET_FILTER_PREFIX_MAX = 12\n/** Only the first this-many words of a value contribute prefixes. */\nexport const DATASET_FILTER_WORDS_MAX = 40\n\n/** One clause of the records table's filter: `{ field, op, value }`. */\nexport interface DatasetFilterClause {\n field: string\n op: string\n value: string\n}\n\n/**\n * The words of a text value: split on anything that is not a letter or a\n * digit, lower-cased, at most {@link DATASET_FILTER_WORDS_MAX} of them.\n */\nexport function datasetFilterWords(text: string): string[] {\n return String(text)\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean)\n .slice(0, DATASET_FILTER_WORDS_MAX)\n}\n\n/** A word's prefixes, 1 to {@link DATASET_FILTER_PREFIX_MAX} characters. */\nfunction prefixes(word: string): string[] {\n const chars = Array.from(word).slice(0, DATASET_FILTER_PREFIX_MAX)\n return chars.map((_char, at) => chars.slice(0, at + 1).join(''))\n}\n\n/** A word clipped the way its stored prefixes are. */\nfunction clipWord(word: string): string {\n return Array.from(word).slice(0, DATASET_FILTER_PREFIX_MAX).join('')\n}\n\nconst isEnum = (field: DatasetFieldDefinition): boolean =>\n field.type === 'text' && (field.validation?.options?.length ?? 0) > 0\n\nconst isNumeric = (field: DatasetFieldDefinition): boolean =>\n field.type === 'int32' || field.type === 'int64' || field.type === 'float'\n\n/**\n * A stored value as text, or null. Numbers are accepted because a form or an\n * automation stores every value as text and an import may do the reverse.\n */\nfunction asText(value: unknown): string | null {\n if (typeof value === 'string') return value\n if (typeof value === 'number' && Number.isFinite(value)) return String(value)\n return null\n}\n\n/**\n * Plain text as its stored key — its `=` token's value and its\n * `filterValues` entry: lower-cased, trimmed, clipped to\n * {@link DATASET_FILTER_VALUE_MAX} characters. What the records table asks a\n * typed value to equal.\n */\nexport function datasetFilterTextKey(value: string): string {\n return Array.from(value.trim().toLowerCase())\n .slice(0, DATASET_FILTER_VALUE_MAX)\n .join('')\n}\nconst textKey = datasetFilterTextKey\n\n/** A stored number (or its text form) as its `=` token's value. */\nfunction numberKey(value: unknown): string | null {\n const number =\n typeof value === 'number'\n ? value\n : typeof value === 'string' && value.trim() !== ''\n ? Number(value)\n : Number.NaN\n return Number.isFinite(number) ? String(number) : null\n}\n\n/** A stored boolean (or its text form) as its `=` token's value. */\nfunction boolKey(value: unknown): string | null {\n if (typeof value === 'boolean') return String(value)\n if (value === 'true' || value === 'false') return value\n return null\n}\n\n/** Every field a model declares, in display order and then the rest. */\nfunction modelFieldIds(model: DatasetModel): string[] {\n return [...new Set([...(model.order ?? []), ...Object.keys(model.fields ?? {})])]\n}\n\n/** The tokens described in {@link DATASET_FILTER_KEYS_MAX}'s comment. */\nexport function datasetFilterKeys(\n model: DatasetModel,\n values: Record<string, unknown> | undefined,\n): string[] {\n const exact: string[] = []\n const fieldWords: string[] = []\n const search: string[] = []\n const searchable = (text: string) => {\n for (const word of datasetFilterWords(text)) {\n for (const prefix of prefixes(word)) search.push(`s:${prefix}`)\n }\n }\n for (const fieldId of modelFieldIds(model)) {\n const field = model.fields?.[fieldId]\n const stored = values?.[fieldId]\n if (!field || stored == null) continue\n if (field.type === 'text') {\n const text = asText(stored)\n if (text == null || !text.trim()) continue\n if (isEnum(field)) {\n exact.push(`f:${fieldId}=${text}`)\n } else {\n exact.push(`f:${fieldId}=${textKey(text)}`)\n for (const word of datasetFilterWords(text)) {\n for (const prefix of prefixes(word)) {\n fieldWords.push(`f:${fieldId}^${prefix}`)\n }\n }\n }\n searchable(text)\n } else if (field.type === 'bool') {\n const key = boolKey(stored)\n if (key) exact.push(`f:${fieldId}=${key}`)\n } else if (isNumeric(field)) {\n const key = numberKey(stored)\n if (key) exact.push(`f:${fieldId}=${key}`)\n } else if (field.type === 'sorted') {\n for (const member of Array.isArray(stored) ? stored : [stored]) {\n const text = asText(member)?.trim()\n if (!text) continue\n exact.push(`f:${fieldId}=${text}`)\n searchable(text)\n }\n }\n }\n const kept = [...new Set([...exact, ...fieldWords, ...search])].slice(\n 0,\n DATASET_FILTER_KEYS_MAX,\n )\n return kept.sort()\n}\n\n/** A record's `filterValues`: field id → the value an equality asks for. */\nexport type DatasetFilterValues = Record<string, string | number | boolean>\n\n/**\n * Field ids a query can name as `filterValues.<id>`. A path string splits on\n * `.`, and the SDKs refuse `~ * / [ ]` in one; the model editor allows only\n * letters, digits and underscores, but a v1 dataset's column names were\n * never checked. Backticks are refused too, as the escape a path would need.\n */\nconst FILTER_VALUE_ID = /^[^.~*/[\\]`]+$/\n\n/**\n * The query path of a field's `filterValues` entry, or null for a field id\n * no path can name — such a field is stamped no entry, and the records table\n * offers it no equality (its `contains`, served by `filterKeys`, stands).\n */\nexport function datasetFilterValuePath(fieldId: string): string | null {\n return FILTER_VALUE_ID.test(fieldId) && !/^__.*__$/.test(fieldId)\n ? `filterValues.${fieldId}`\n : null\n}\n\n/**\n * A dataset record's FILTER VALUES: `{ [fieldId]: value }`, one scalar per\n * field an equality can be asked of, so the records table puts every\n * equality on its query (AGL-3321).\n *\n * enum text (`validation.options`) the value exactly, as its `=` token;\n * plain text {@link datasetFilterTextKey};\n * bool a boolean;\n * int32 / int64 / float a number.\n *\n * A form or an automation stores every value as text, so `'19.50'` is the\n * number 19.5 and `'true'` the boolean, as in {@link datasetFilterKeys}.\n * Fields with no value, and timestamps, lists, references, maps, bytes,\n * coordinates and nil fields, carry no entry: a list is asked by member\n * (`f:<id>=<member>` in `filterKeys`), and the rest are not asked about by\n * value.\n *\n * ## What it costs\n *\n * Unlike `values`, this map is NOT exempt from indexing — that is its whole\n * point. Each entry is a scalar and costs two automatic index entries\n * (ascending, descending), so a record costs at most two per model field —\n * bounded by the model's field count (the `/v1` API caps a model at 100\n * fields), far inside Firestore's 40,000 entries per document, where\n * `values` was unbounded because a `map` or `sorted` value may hold anything.\n */\nexport function datasetFilterValues(\n model: DatasetModel,\n values: Record<string, unknown> | undefined,\n): DatasetFilterValues {\n const out: DatasetFilterValues = {}\n for (const fieldId of modelFieldIds(model)) {\n const field = model.fields?.[fieldId]\n const stored = values?.[fieldId]\n if (!field || stored == null || !datasetFilterValuePath(fieldId)) continue\n if (field.type === 'text') {\n const text = asText(stored)\n if (text == null || !text.trim()) continue\n out[fieldId] = isEnum(field) ? text : textKey(text)\n } else if (field.type === 'bool') {\n const key = boolKey(stored)\n if (key) out[fieldId] = key === 'true'\n } else if (isNumeric(field)) {\n const key = numberKey(stored)\n if (key) out[fieldId] = Number(key)\n }\n }\n return out\n}\n\n/**\n * The `filterKeys` token a word-level clause asks for, or null when it is\n * not one:\n *\n * - plain text `contains` → the `^` token of the value's FIRST word, so a\n * `contains` is a word-prefix match, not a mid-string one, and one word\n * of several is asked (the records table says so);\n * - list `contains` (and `equals`) → the member's `=` token;\n * - enum, boolean, number and plain text `equals` → the `=` token of the\n * value. The records table asks these of `filterValues` instead, which\n * composes with a `contains`; the token remains what the quick search and\n * older readers match.\n * - everything else — ranges, dates, `isEmpty`, `isAnyOf`, negations — null.\n *\n * `clause.field` is the field id. The operator names are the grid's: a\n * select's `equals`, a number's `=`, a boolean's `is`, a list's `contains`.\n */\nexport function datasetFilterToken(\n model: DatasetModel,\n clause: DatasetFilterClause,\n): string | null {\n const field = model.fields?.[clause.field]\n if (!field) return null\n const { op } = clause\n const value = String(clause.value ?? '')\n if (!value.trim()) return null\n const equals = op === 'equals' || op === '=' || op === 'is'\n if (field.type === 'text') {\n if (isEnum(field)) return equals ? `f:${clause.field}=${value}` : null\n if (equals) return `f:${clause.field}=${textKey(value)}`\n if (op === 'contains') {\n const [first] = datasetFilterWords(value)\n return first ? `f:${clause.field}^${clipWord(first)}` : null\n }\n return null\n }\n if (field.type === 'bool') {\n const key = boolKey(value.trim())\n return equals && key ? `f:${clause.field}=${key}` : null\n }\n if (isNumeric(field)) {\n const key = numberKey(value)\n return equals && key ? `f:${clause.field}=${key}` : null\n }\n if (field.type === 'sorted') {\n return equals || op === 'contains' ? `f:${clause.field}=${value.trim()}` : null\n }\n return null\n}\n\n/**\n * The quick-search token for one typed word, or null for a word with no\n * letters or digits. A word with punctuation inside it is asked by its first\n * run — split the search text with {@link datasetFilterWords} to ask for\n * every run.\n */\nexport function datasetSearchToken(word: string): string | null {\n const [first] = datasetFilterWords(word)\n return first ? `s:${clipWord(first)}` : null\n}\n"],"names":["validateCustomFieldValue","humanizeDatasetFieldId","id","words","replace","trim","charAt","toUpperCase","slice","DATASET_FIELD_TYPES","DATASET_FIELD_TYPE_LABELS","text","bool","int32","int64","float","timestamp","coordinates","map","sorted","bytes","nil","reference","deriveModelFromFields","fields","model","order","name","type","push","modelFromFieldEntries","entries","entry","repeatRowsModelOf","references","fieldId","field","Object","target","datasetId","undefined","effectiveDatasetModel","dataset","length","isFiniteNumber","value","Number","isFinite","validateDocument","values","errors","required","validation","empty","Array","isArray","isListField","rules","min","max","regex","RegExp","test","options","includes","join","isInteger","latitude","longitude","Math","abs","some","multiple","customType","customError","formatDatasetValue","String","Date","toISOString","JSON","stringify","datasetValueToInput","Boolean","listEntries","input","coerceListValue","raw","startsWith","endsWith","parsed","parse","split","part","filter","BOOL_TRUE_WORDS","Set","BOOL_FALSE_WORDS","coerceDocumentValues","word","toLowerCase","has","millis","datasetReferencedIds","ids","fieldIds","keys","stored","add","datasetIntegrityFields","referencedIds","filterKeys","datasetFilterKeys","filterValues","datasetFilterValues","datasetIntegrityUpdate","clear","DATASET_FILTER_KEYS_MAX","DATASET_FILTER_VALUE_MAX","DATASET_FILTER_PREFIX_MAX","DATASET_FILTER_WORDS_MAX","datasetFilterWords","prefixes","chars","from","_char","at","clipWord","isEnum","isNumeric","asText","datasetFilterTextKey","textKey","numberKey","number","NaN","boolKey","modelFieldIds","exact","fieldWords","search","searchable","prefix","key","member","kept","sort","FILTER_VALUE_ID","datasetFilterValuePath","out","datasetFilterToken","clause","op","equals","first","datasetSearchToken"],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type DatasetFieldDefinition, type DatasetFieldType, type DatasetModel } from '@aglyn/aglyn/app-utils/dataset-models';
17
+ import { type DatasetFieldDefinition, type DatasetFieldType, type DatasetModel } from './dataset-models';
18
18
  /**
19
19
  * Reading a whole record, as opposed to skimming a row of it.
20
20
  *
@@ -14,8 +14,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { formatDatasetValue } from "@aglyn/aglyn/app-utils/dataset-models";
18
- import { humanizeDatasetFieldId } from "@aglyn/aglyn/app-utils/datasets";
17
+ */ import { formatDatasetValue, humanizeDatasetFieldId } from "./dataset-models.js";
19
18
  const NO_TEXT = {
20
19
  text: '',
21
20
  block: false
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/model/dataset-record-view.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 type DatasetFieldDefinition,\n type DatasetFieldType,\n type DatasetModel,\n formatDatasetValue,\n} from '@aglyn/aglyn/app-utils/dataset-models'\nimport { humanizeDatasetFieldId } from '@aglyn/aglyn/app-utils/datasets'\n\n/**\n * Reading a whole record, as opposed to skimming a row of it.\n *\n * `formatDatasetValue` is the GRID formatter and is right for a grid: it\n * answers one short string per cell, and every state that has no short string\n * collapses to `''`, which the table then prints as `--`. A cell is one line\n * tall and one column wide, so that collapse is the only affordable answer.\n *\n * A record view has room to be exact, and the difference matters as soon as\n * anybody acts on what they read:\n *\n * * A field the record does not carry AT ALL, a field storing `null`, and a\n * field storing a real empty string are three different facts. They all\n * format to `''` and print as `--`. \"The address is blank\" and \"this\n * record predates the address field\" are not the same finding, and a CSV\n * export makes the difference visible where the table did not.\n * * A reference whose target cannot be resolved formats as the raw target\n * ID, which is indistinguishable from a resolved reference whose label\n * happens to look like an ID. One is a working link, the other is a\n * dangling one — a `setNull` that never ran, a target outside the picker's\n * window, or a record pointing at a deleted document.\n * * `bytes` reaches `String(value)`. A `Uint8Array` prints as its comma-\n * joined byte values and a Firestore `Bytes` prints `[object Object]`.\n * Neither is the value; both look like one.\n * * A `map` whose value cannot be serialized (a cycle) falls through\n * `JSON.stringify`'s throw to `String(value)`, which is `[object Object]`\n * again — a placeholder wearing the costume of data.\n *\n * So this module answers with a DESCRIPTOR rather than a string, and the\n * renderer decides how each state should look. Nothing here formats a value\n * a second time: a real value's text still comes from `formatDatasetValue`,\n * so the record view and the table can never disagree about one.\n */\n\n/** What a field slot actually holds — the four \"no value\" states are distinct. */\nexport type DatasetDisplayKind =\n /** The record has no such key. */\n | 'absent'\n /** The key is present and stores `null`. */\n | 'null'\n /** A real, stored empty string. */\n | 'empty-text'\n /** A real, stored empty array. */\n | 'empty-list'\n /** A real, stored empty object. */\n | 'empty-map'\n /** A value, rendered in `text`. */\n | 'value'\n /**\n * A value that exists and cannot be shown honestly as text — binary, or a\n * structure that will not serialize. `text` says which, and says so as a\n * description of the value rather than as the value.\n */\n | 'opaque'\n\n/** One target of a reference field, and whether it could be resolved. */\nexport interface DatasetDisplayReference {\n id: string\n /**\n * The target's display label, or `null` when this ID resolved to nothing.\n * An unresolved reference is not an error to hide: the ID is still shown,\n * marked as unresolved, because the ID is the only fact available.\n */\n label: string | null\n}\n\nexport interface DatasetDisplayValue {\n kind: DatasetDisplayKind\n /**\n * The rendered value for `value`, and the reason for `opaque`. Empty for\n * every other kind — those carry no text of their own, and the renderer\n * supplies the placeholder so a placeholder can never be mistaken for\n * stored content.\n */\n text: string\n /**\n * Pre-formatted across several lines. Nested JSON is pretty-printed, which\n * is only readable in a block that preserves its whitespace.\n */\n block: boolean\n /** Present only for a `reference` field, one entry per target ID. */\n references?: DatasetDisplayReference[]\n /**\n * A list field's items, one entry each.\n *\n * `formatDatasetValue` joins a `sorted` array with `\", \"`, which is the only\n * thing a cell has room for and is ambiguous in both directions: the\n * one-item list `['a, b']` and the two-item list `['a', 'b']` produce the\n * same string, and nothing in it says which was stored. A record view has a\n * line per item, so it renders the items rather than the join. `text` keeps\n * the joined form for anywhere the structure is flattened.\n */\n items?: string[]\n}\n\n/** A field slot in a record view: what it is called, and what it holds. */\nexport interface DatasetRecordField {\n fieldId: string\n /** Display label — the model's name, or a humanized ID for an extra. */\n label: string\n /** The declared type; absent for a stored value the model does not declare. */\n type?: DatasetFieldType\n description?: string\n required?: boolean\n /**\n * `extra` marks a stored value with no field in the model. It is not\n * corruption and not rare: a type or field removed from a model never\n * rewrites documents (the record editor strips orphans lazily, on the next\n * save), and the import and API legs accept any key the model names at the\n * time of the write. The table cannot show these — its columns come from\n * the model — which is exactly why a record view must.\n */\n source: 'model' | 'extra'\n value: DatasetDisplayValue\n}\n\n/** Resolves one reference target to its label, or `null` when unresolved. */\nexport type DatasetReferenceResolver = (\n fieldId: string,\n id: string,\n) => string | null\n\nconst NO_TEXT = { text: '', block: false } as const\n\n/** Firestore `Bytes`, `Uint8Array`, `Buffer` — anything holding raw octets. */\nfunction byteLength(value: unknown): number | null {\n if (value instanceof Uint8Array) return value.byteLength\n if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {\n return value.byteLength\n }\n // Firestore's `Bytes` wrapper exposes its octets only through a method.\n const asBytes = value as { toUint8Array?: () => Uint8Array }\n if (typeof asBytes?.toUint8Array === 'function') {\n try {\n return asBytes.toUint8Array().byteLength\n } catch {\n return null\n }\n }\n return null\n}\n\nfunction describeBytes(value: unknown): DatasetDisplayValue {\n const length = byteLength(value)\n return {\n kind: 'opaque',\n text: length == null ? 'Binary value' : `Binary value · ${length} bytes`,\n block: false,\n }\n}\n\n/**\n * Nested structures, pretty-printed.\n *\n * A one-line `JSON.stringify` is what the table shows; a record view has the\n * height to indent it, and an indented object is the difference between\n * reading a nested value and squinting at one. A structure that will not\n * serialize is reported as such rather than falling through to\n * `String(value)`.\n */\nfunction describeStructure(value: unknown): DatasetDisplayValue {\n try {\n const text = JSON.stringify(value, null, 2)\n // `undefined` in, `undefined` out — `JSON.stringify` answers that for a\n // function or a symbol without throwing.\n if (typeof text !== 'string') {\n return { kind: 'opaque', text: 'Value cannot be displayed', block: false }\n }\n return { kind: 'value', text, block: text.includes('\\n') }\n } catch {\n // A cycle, or a `toJSON` that throws.\n return { kind: 'opaque', text: 'Value cannot be displayed', block: false }\n }\n}\n\n/**\n * A list's items, kept apart instead of joined.\n *\n * The join `formatDatasetValue` performs is lossy: `['a, b']` and\n * `['a', 'b']` render to the identical string, so a reader cannot tell a\n * one-item list holding a comma from a two-item list. One line per item\n * removes the ambiguity, and `text` keeps the joined form for a flattened\n * context such as a copy to the clipboard.\n *\n * An item that is itself a structure is serialized on its own; a list of\n * objects reads as a list of objects rather than as `[object Object]`\n * repeated.\n */\nfunction describeList(\n field: DatasetFieldDefinition | undefined,\n value: unknown[],\n): DatasetDisplayValue {\n const items = value.map((item) => {\n if (typeof item === 'string') return item\n if (item === null) return 'null'\n if (typeof item === 'object') {\n const described = describeStructure(item)\n return described.kind === 'value' ? described.text : described.text\n }\n return String(item)\n })\n return {\n kind: 'value',\n text: field ? formatDatasetValue(field, value) : items.join(', '),\n block: false,\n items,\n }\n}\n\n/**\n * The reference field's targets, each carrying whether it resolved.\n *\n * Both storage shapes flatten to the same list — a single ID string, or the\n * `sorted` array a `multiple` reference holds — because the question asked of\n * each entry is the same one.\n */\nfunction describeReference(\n fieldId: string,\n value: unknown,\n resolve: DatasetReferenceResolver | undefined,\n): DatasetDisplayValue {\n const raw = Array.isArray(value) ? value : [value]\n const references: DatasetDisplayReference[] = raw\n .map((entry) => (typeof entry === 'string' ? entry.trim() : ''))\n .filter((id) => id.length > 0)\n .map((id) => ({ id, label: resolve?.(fieldId, id) ?? null }))\n if (!references.length) {\n // An array that held nothing but blanks is an empty list, not a value;\n // a non-string entry never was a reference and is reported by\n // `validateDocument`, which the record view runs alongside this.\n return { kind: Array.isArray(value) ? 'empty-list' : 'null', ...NO_TEXT }\n }\n return {\n kind: 'value',\n // The label where there is one, the bare ID where there is not. The\n // renderer marks the unresolved entries; this text is the fallback for\n // anywhere the structure is flattened, such as a copy to the clipboard.\n text: references\n .map((reference) => reference.label ?? reference.id)\n .join(', '),\n block: false,\n references,\n }\n}\n\n/**\n * What one field of one record holds, as a descriptor the renderer can be\n * honest with.\n *\n * `values` and `fieldId` are taken separately rather than as one already-read\n * value because \"the key is missing\" and \"the key is present and empty\" are\n * two of the states being distinguished, and a read value cannot tell them\n * apart.\n *\n * `field` may be undefined — a stored value the model does not declare. The\n * value is then described from its own runtime shape, since there is no\n * declared type to describe it by.\n */\nexport function describeDatasetValue(\n field: DatasetFieldDefinition | undefined,\n values: Record<string, unknown> | undefined,\n fieldId: string,\n resolve?: DatasetReferenceResolver,\n): DatasetDisplayValue {\n const present =\n values != null &&\n Object.prototype.hasOwnProperty.call(values, fieldId) &&\n values[fieldId] !== undefined\n if (!present) return { kind: 'absent', ...NO_TEXT }\n const value = values[fieldId]\n if (value === null) return { kind: 'null', ...NO_TEXT }\n if (value === '') return { kind: 'empty-text', ...NO_TEXT }\n if (Array.isArray(value) && value.length === 0) {\n return { kind: 'empty-list', ...NO_TEXT }\n }\n\n if (field?.type === 'reference') {\n return describeReference(fieldId, value, resolve)\n }\n if (field?.type === 'bytes') return describeBytes(value)\n\n // An object with no keys is an empty map whether or not the model says\n // `map` — an undeclared field has no declared type to consult.\n const plainObject =\n typeof value === 'object' &&\n !Array.isArray(value) &&\n byteLength(value) == null\n if (plainObject && Object.keys(value as object).length === 0) {\n return { kind: 'empty-map', ...NO_TEXT }\n }\n // Raw octets stored against a field the model does not declare as `bytes`.\n if (typeof value === 'object' && byteLength(value) != null) {\n return describeBytes(value)\n }\n // A list is rendered per item rather than joined. An array stored against a\n // field declared `map` is not one — `validateDocument` reports the mismatch\n // and the value is pretty-printed as the structure it is.\n if (Array.isArray(value) && (!field || field.type === 'sorted')) {\n return describeList(field, value)\n }\n\n if (field) {\n // `map` is the only declared type `formatDatasetValue` serializes, and it\n // serializes to one line. Everything else it formats is already the short\n // form a record view wants.\n if (field.type === 'map') return describeStructure(value)\n const text = formatDatasetValue(field, value)\n // A non-empty value that formats to nothing would print as a blank line\n // and read as an empty string. `formatDatasetValue` returns `''` only for\n // the states already handled above, so this is a guard against a future\n // formatter, not a state reachable today.\n if (text === '')\n return { kind: 'opaque', text: 'Value cannot be displayed', block: false }\n return { kind: 'value', text, block: false }\n }\n\n // Undeclared: describe the runtime shape rather than guessing a type.\n if (typeof value === 'object') return describeStructure(value)\n if (typeof value === 'boolean') {\n return { kind: 'value', text: value ? 'true' : 'false', block: false }\n }\n if (typeof value === 'number' || typeof value === 'string') {\n return { kind: 'value', text: String(value), block: false }\n }\n return { kind: 'opaque', text: 'Value cannot be displayed', block: false }\n}\n\n/**\n * Every field of a record, in reading order: the model's declared order\n * first, then anything the model declares but does not display, then every\n * stored value the model does not declare at all.\n *\n * The union is the point. `model.order` is what the table's columns are built\n * from, so a view built from `order` alone would be a wider row rather than\n * the record — it would silently omit a reference field declared without a\n * display slot, and every value left behind by a field the model no longer\n * has. Both are real states of a stored document, and a viewer that drops\n * them tells the reader the record is smaller than it is.\n */\nexport function datasetRecordFields(\n model: DatasetModel,\n values: Record<string, unknown> | undefined,\n resolve?: DatasetReferenceResolver,\n): DatasetRecordField[] {\n const order = model.order ?? []\n const declared = model.fields ?? {}\n const seen = new Set<string>()\n const fields: DatasetRecordField[] = []\n\n const pushDeclared = (fieldId: string) => {\n if (seen.has(fieldId)) return\n const field = declared[fieldId]\n if (!field) return\n seen.add(fieldId)\n fields.push({\n fieldId,\n label: field.name || humanizeDatasetFieldId(fieldId),\n type: field.type,\n ...(field.description ? { description: field.description } : {}),\n ...(field.required || field.validation?.required\n ? { required: true }\n : {}),\n source: 'model',\n value: describeDatasetValue(field, values, fieldId, resolve),\n })\n }\n\n for (const fieldId of order) pushDeclared(fieldId)\n for (const fieldId of Object.keys(declared)) pushDeclared(fieldId)\n\n for (const fieldId of Object.keys(values ?? {})) {\n if (seen.has(fieldId)) continue\n seen.add(fieldId)\n fields.push({\n fieldId,\n label: humanizeDatasetFieldId(fieldId),\n source: 'extra',\n value: describeDatasetValue(undefined, values, fieldId, resolve),\n })\n }\n return fields\n}\n"],"names":["formatDatasetValue","humanizeDatasetFieldId","NO_TEXT","text","block","byteLength","value","Uint8Array","ArrayBuffer","asBytes","toUint8Array","describeBytes","length","kind","describeStructure","JSON","stringify","includes","describeList","field","items","map","item","described","String","join","describeReference","fieldId","resolve","raw","Array","isArray","references","entry","trim","filter","id","label","reference","describeDatasetValue","values","present","Object","prototype","hasOwnProperty","call","undefined","type","plainObject","keys","datasetRecordFields","model","order","declared","fields","seen","Set","pushDeclared","has","add","push","name","description","required","validation","source"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/model/dataset-record-view.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 type DatasetFieldDefinition,\n type DatasetFieldType,\n type DatasetModel,\n formatDatasetValue,\n humanizeDatasetFieldId,\n} from './dataset-models'\n\n/**\n * Reading a whole record, as opposed to skimming a row of it.\n *\n * `formatDatasetValue` is the GRID formatter and is right for a grid: it\n * answers one short string per cell, and every state that has no short string\n * collapses to `''`, which the table then prints as `--`. A cell is one line\n * tall and one column wide, so that collapse is the only affordable answer.\n *\n * A record view has room to be exact, and the difference matters as soon as\n * anybody acts on what they read:\n *\n * * A field the record does not carry AT ALL, a field storing `null`, and a\n * field storing a real empty string are three different facts. They all\n * format to `''` and print as `--`. \"The address is blank\" and \"this\n * record predates the address field\" are not the same finding, and a CSV\n * export makes the difference visible where the table did not.\n * * A reference whose target cannot be resolved formats as the raw target\n * ID, which is indistinguishable from a resolved reference whose label\n * happens to look like an ID. One is a working link, the other is a\n * dangling one — a `setNull` that never ran, a target outside the picker's\n * window, or a record pointing at a deleted document.\n * * `bytes` reaches `String(value)`. A `Uint8Array` prints as its comma-\n * joined byte values and a Firestore `Bytes` prints `[object Object]`.\n * Neither is the value; both look like one.\n * * A `map` whose value cannot be serialized (a cycle) falls through\n * `JSON.stringify`'s throw to `String(value)`, which is `[object Object]`\n * again — a placeholder wearing the costume of data.\n *\n * So this module answers with a DESCRIPTOR rather than a string, and the\n * renderer decides how each state should look. Nothing here formats a value\n * a second time: a real value's text still comes from `formatDatasetValue`,\n * so the record view and the table can never disagree about one.\n */\n\n/** What a field slot actually holds — the four \"no value\" states are distinct. */\nexport type DatasetDisplayKind =\n /** The record has no such key. */\n | 'absent'\n /** The key is present and stores `null`. */\n | 'null'\n /** A real, stored empty string. */\n | 'empty-text'\n /** A real, stored empty array. */\n | 'empty-list'\n /** A real, stored empty object. */\n | 'empty-map'\n /** A value, rendered in `text`. */\n | 'value'\n /**\n * A value that exists and cannot be shown honestly as text — binary, or a\n * structure that will not serialize. `text` says which, and says so as a\n * description of the value rather than as the value.\n */\n | 'opaque'\n\n/** One target of a reference field, and whether it could be resolved. */\nexport interface DatasetDisplayReference {\n id: string\n /**\n * The target's display label, or `null` when this ID resolved to nothing.\n * An unresolved reference is not an error to hide: the ID is still shown,\n * marked as unresolved, because the ID is the only fact available.\n */\n label: string | null\n}\n\nexport interface DatasetDisplayValue {\n kind: DatasetDisplayKind\n /**\n * The rendered value for `value`, and the reason for `opaque`. Empty for\n * every other kind — those carry no text of their own, and the renderer\n * supplies the placeholder so a placeholder can never be mistaken for\n * stored content.\n */\n text: string\n /**\n * Pre-formatted across several lines. Nested JSON is pretty-printed, which\n * is only readable in a block that preserves its whitespace.\n */\n block: boolean\n /** Present only for a `reference` field, one entry per target ID. */\n references?: DatasetDisplayReference[]\n /**\n * A list field's items, one entry each.\n *\n * `formatDatasetValue` joins a `sorted` array with `\", \"`, which is the only\n * thing a cell has room for and is ambiguous in both directions: the\n * one-item list `['a, b']` and the two-item list `['a', 'b']` produce the\n * same string, and nothing in it says which was stored. A record view has a\n * line per item, so it renders the items rather than the join. `text` keeps\n * the joined form for anywhere the structure is flattened.\n */\n items?: string[]\n}\n\n/** A field slot in a record view: what it is called, and what it holds. */\nexport interface DatasetRecordField {\n fieldId: string\n /** Display label — the model's name, or a humanized ID for an extra. */\n label: string\n /** The declared type; absent for a stored value the model does not declare. */\n type?: DatasetFieldType\n description?: string\n required?: boolean\n /**\n * `extra` marks a stored value with no field in the model. It is not\n * corruption and not rare: a type or field removed from a model never\n * rewrites documents (the record editor strips orphans lazily, on the next\n * save), and the import and API legs accept any key the model names at the\n * time of the write. The table cannot show these — its columns come from\n * the model — which is exactly why a record view must.\n */\n source: 'model' | 'extra'\n value: DatasetDisplayValue\n}\n\n/** Resolves one reference target to its label, or `null` when unresolved. */\nexport type DatasetReferenceResolver = (\n fieldId: string,\n id: string,\n) => string | null\n\nconst NO_TEXT = { text: '', block: false } as const\n\n/** Firestore `Bytes`, `Uint8Array`, `Buffer` — anything holding raw octets. */\nfunction byteLength(value: unknown): number | null {\n if (value instanceof Uint8Array) return value.byteLength\n if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {\n return value.byteLength\n }\n // Firestore's `Bytes` wrapper exposes its octets only through a method.\n const asBytes = value as { toUint8Array?: () => Uint8Array }\n if (typeof asBytes?.toUint8Array === 'function') {\n try {\n return asBytes.toUint8Array().byteLength\n } catch {\n return null\n }\n }\n return null\n}\n\nfunction describeBytes(value: unknown): DatasetDisplayValue {\n const length = byteLength(value)\n return {\n kind: 'opaque',\n text: length == null ? 'Binary value' : `Binary value · ${length} bytes`,\n block: false,\n }\n}\n\n/**\n * Nested structures, pretty-printed.\n *\n * A one-line `JSON.stringify` is what the table shows; a record view has the\n * height to indent it, and an indented object is the difference between\n * reading a nested value and squinting at one. A structure that will not\n * serialize is reported as such rather than falling through to\n * `String(value)`.\n */\nfunction describeStructure(value: unknown): DatasetDisplayValue {\n try {\n const text = JSON.stringify(value, null, 2)\n // `undefined` in, `undefined` out — `JSON.stringify` answers that for a\n // function or a symbol without throwing.\n if (typeof text !== 'string') {\n return { kind: 'opaque', text: 'Value cannot be displayed', block: false }\n }\n return { kind: 'value', text, block: text.includes('\\n') }\n } catch {\n // A cycle, or a `toJSON` that throws.\n return { kind: 'opaque', text: 'Value cannot be displayed', block: false }\n }\n}\n\n/**\n * A list's items, kept apart instead of joined.\n *\n * The join `formatDatasetValue` performs is lossy: `['a, b']` and\n * `['a', 'b']` render to the identical string, so a reader cannot tell a\n * one-item list holding a comma from a two-item list. One line per item\n * removes the ambiguity, and `text` keeps the joined form for a flattened\n * context such as a copy to the clipboard.\n *\n * An item that is itself a structure is serialized on its own; a list of\n * objects reads as a list of objects rather than as `[object Object]`\n * repeated.\n */\nfunction describeList(\n field: DatasetFieldDefinition | undefined,\n value: unknown[],\n): DatasetDisplayValue {\n const items = value.map((item) => {\n if (typeof item === 'string') return item\n if (item === null) return 'null'\n if (typeof item === 'object') {\n const described = describeStructure(item)\n return described.kind === 'value' ? described.text : described.text\n }\n return String(item)\n })\n return {\n kind: 'value',\n text: field ? formatDatasetValue(field, value) : items.join(', '),\n block: false,\n items,\n }\n}\n\n/**\n * The reference field's targets, each carrying whether it resolved.\n *\n * Both storage shapes flatten to the same list — a single ID string, or the\n * `sorted` array a `multiple` reference holds — because the question asked of\n * each entry is the same one.\n */\nfunction describeReference(\n fieldId: string,\n value: unknown,\n resolve: DatasetReferenceResolver | undefined,\n): DatasetDisplayValue {\n const raw = Array.isArray(value) ? value : [value]\n const references: DatasetDisplayReference[] = raw\n .map((entry) => (typeof entry === 'string' ? entry.trim() : ''))\n .filter((id) => id.length > 0)\n .map((id) => ({ id, label: resolve?.(fieldId, id) ?? null }))\n if (!references.length) {\n // An array that held nothing but blanks is an empty list, not a value;\n // a non-string entry never was a reference and is reported by\n // `validateDocument`, which the record view runs alongside this.\n return { kind: Array.isArray(value) ? 'empty-list' : 'null', ...NO_TEXT }\n }\n return {\n kind: 'value',\n // The label where there is one, the bare ID where there is not. The\n // renderer marks the unresolved entries; this text is the fallback for\n // anywhere the structure is flattened, such as a copy to the clipboard.\n text: references\n .map((reference) => reference.label ?? reference.id)\n .join(', '),\n block: false,\n references,\n }\n}\n\n/**\n * What one field of one record holds, as a descriptor the renderer can be\n * honest with.\n *\n * `values` and `fieldId` are taken separately rather than as one already-read\n * value because \"the key is missing\" and \"the key is present and empty\" are\n * two of the states being distinguished, and a read value cannot tell them\n * apart.\n *\n * `field` may be undefined — a stored value the model does not declare. The\n * value is then described from its own runtime shape, since there is no\n * declared type to describe it by.\n */\nexport function describeDatasetValue(\n field: DatasetFieldDefinition | undefined,\n values: Record<string, unknown> | undefined,\n fieldId: string,\n resolve?: DatasetReferenceResolver,\n): DatasetDisplayValue {\n const present =\n values != null &&\n Object.prototype.hasOwnProperty.call(values, fieldId) &&\n values[fieldId] !== undefined\n if (!present) return { kind: 'absent', ...NO_TEXT }\n const value = values[fieldId]\n if (value === null) return { kind: 'null', ...NO_TEXT }\n if (value === '') return { kind: 'empty-text', ...NO_TEXT }\n if (Array.isArray(value) && value.length === 0) {\n return { kind: 'empty-list', ...NO_TEXT }\n }\n\n if (field?.type === 'reference') {\n return describeReference(fieldId, value, resolve)\n }\n if (field?.type === 'bytes') return describeBytes(value)\n\n // An object with no keys is an empty map whether or not the model says\n // `map` — an undeclared field has no declared type to consult.\n const plainObject =\n typeof value === 'object' &&\n !Array.isArray(value) &&\n byteLength(value) == null\n if (plainObject && Object.keys(value as object).length === 0) {\n return { kind: 'empty-map', ...NO_TEXT }\n }\n // Raw octets stored against a field the model does not declare as `bytes`.\n if (typeof value === 'object' && byteLength(value) != null) {\n return describeBytes(value)\n }\n // A list is rendered per item rather than joined. An array stored against a\n // field declared `map` is not one — `validateDocument` reports the mismatch\n // and the value is pretty-printed as the structure it is.\n if (Array.isArray(value) && (!field || field.type === 'sorted')) {\n return describeList(field, value)\n }\n\n if (field) {\n // `map` is the only declared type `formatDatasetValue` serializes, and it\n // serializes to one line. Everything else it formats is already the short\n // form a record view wants.\n if (field.type === 'map') return describeStructure(value)\n const text = formatDatasetValue(field, value)\n // A non-empty value that formats to nothing would print as a blank line\n // and read as an empty string. `formatDatasetValue` returns `''` only for\n // the states already handled above, so this is a guard against a future\n // formatter, not a state reachable today.\n if (text === '')\n return { kind: 'opaque', text: 'Value cannot be displayed', block: false }\n return { kind: 'value', text, block: false }\n }\n\n // Undeclared: describe the runtime shape rather than guessing a type.\n if (typeof value === 'object') return describeStructure(value)\n if (typeof value === 'boolean') {\n return { kind: 'value', text: value ? 'true' : 'false', block: false }\n }\n if (typeof value === 'number' || typeof value === 'string') {\n return { kind: 'value', text: String(value), block: false }\n }\n return { kind: 'opaque', text: 'Value cannot be displayed', block: false }\n}\n\n/**\n * Every field of a record, in reading order: the model's declared order\n * first, then anything the model declares but does not display, then every\n * stored value the model does not declare at all.\n *\n * The union is the point. `model.order` is what the table's columns are built\n * from, so a view built from `order` alone would be a wider row rather than\n * the record — it would silently omit a reference field declared without a\n * display slot, and every value left behind by a field the model no longer\n * has. Both are real states of a stored document, and a viewer that drops\n * them tells the reader the record is smaller than it is.\n */\nexport function datasetRecordFields(\n model: DatasetModel,\n values: Record<string, unknown> | undefined,\n resolve?: DatasetReferenceResolver,\n): DatasetRecordField[] {\n const order = model.order ?? []\n const declared = model.fields ?? {}\n const seen = new Set<string>()\n const fields: DatasetRecordField[] = []\n\n const pushDeclared = (fieldId: string) => {\n if (seen.has(fieldId)) return\n const field = declared[fieldId]\n if (!field) return\n seen.add(fieldId)\n fields.push({\n fieldId,\n label: field.name || humanizeDatasetFieldId(fieldId),\n type: field.type,\n ...(field.description ? { description: field.description } : {}),\n ...(field.required || field.validation?.required\n ? { required: true }\n : {}),\n source: 'model',\n value: describeDatasetValue(field, values, fieldId, resolve),\n })\n }\n\n for (const fieldId of order) pushDeclared(fieldId)\n for (const fieldId of Object.keys(declared)) pushDeclared(fieldId)\n\n for (const fieldId of Object.keys(values ?? {})) {\n if (seen.has(fieldId)) continue\n seen.add(fieldId)\n fields.push({\n fieldId,\n label: humanizeDatasetFieldId(fieldId),\n source: 'extra',\n value: describeDatasetValue(undefined, values, fieldId, resolve),\n })\n }\n return fields\n}\n"],"names":["formatDatasetValue","humanizeDatasetFieldId","NO_TEXT","text","block","byteLength","value","Uint8Array","ArrayBuffer","asBytes","toUint8Array","describeBytes","length","kind","describeStructure","JSON","stringify","includes","describeList","field","items","map","item","described","String","join","describeReference","fieldId","resolve","raw","Array","isArray","references","entry","trim","filter","id","label","reference","describeDatasetValue","values","present","Object","prototype","hasOwnProperty","call","undefined","type","plainObject","keys","datasetRecordFields","model","order","declared","fields","seen","Set","pushDeclared","has","add","push","name","description","required","validation","source"],"mappings":"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"}
@@ -13,8 +13,8 @@
13
13
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
- */ import { effectiveDatasetModel } from "@aglyn/aglyn/app-utils/dataset-models";
17
- import { datasetDisplayName } from "@aglyn/aglyn/app-utils/datasets";
16
+ */ import { effectiveDatasetModel } from "./dataset-models.js";
17
+ import { datasetDisplayName } from "./datasets.js";
18
18
  /**
19
19
  * One stored dataset as this plugin shares it with another (AGL-3080) — the
20
20
  * one shape its server index (`dataset-record-index.ts`) and its console list
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/model/dataset-record.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 { effectiveDatasetModel, type DatasetModel } from '@aglyn/aglyn/app-utils/dataset-models'\nimport { datasetDisplayName } from '@aglyn/aglyn/app-utils/datasets'\nimport type { PluginIndexedRecord } from '@aglyn/aglyn/plugin-manager/plugin-record-index'\n\n/**\n * One stored dataset as this plugin shares it with another (AGL-3080) — the\n * one shape its server index (`dataset-record-index.ts`) and its console list\n * source (`dataset-record-list.ts`) both answer, so a reader on either side\n * reads the same record.\n *\n * `null` for a deleted dataset. Otherwise its id, its name as the Data page\n * shows it, and `facts`:\n * `fields` — its fields in the dataset page's order, each\n * `{ id, name, type }`; `visibleTo` — the scope tokens it is shared\n * with, so a reader acting for a member can apply the platform's own\n * visibility rule to them; and `installedFrom` — `{ listingId, version }`\n * for a dataset installed from a listing (`version` as the install\n * stamped it), `null` for one the workspace made. Never a record of it.\n */\nexport function datasetIndexedRecord(\n id: string,\n data: Readonly<Record<string, unknown>> | undefined,\n): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null) return null\n const model = effectiveDatasetModel(data as { model?: DatasetModel; fields?: string[] })\n const text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n return {\n id,\n name: datasetDisplayName(data) || id,\n facts: {\n fields: model.order.map((fieldId) => ({\n id: fieldId,\n name: text(model.fields[fieldId]?.name) || fieldId,\n type: String(model.fields[fieldId]?.type ?? 'text'),\n })),\n visibleTo: Array.isArray(data['visibleTo']) ? (data['visibleTo'] as string[]) : [],\n installedFrom: installedFrom(data['source']),\n },\n }\n}\n\n/**\n * Where an installed dataset came from. Read from its `source` stamp, which\n * every dataset install has written — the ones from before install\n * provenance (AGL-1015) included, which carry no `installedFrom`.\n */\nfunction installedFrom(source: unknown): { listingId: string; version: unknown } | null {\n const stamp = (source ?? {}) as Record<string, unknown>\n const listingId = typeof stamp['listingId'] === 'string' ? stamp['listingId'] : ''\n return listingId ? { listingId, version: stamp['version'] ?? null } : null\n}\n"],"names":["effectiveDatasetModel","datasetDisplayName","datasetIndexedRecord","id","data","model","text","value","trim","name","facts","fields","order","map","fieldId","type","String","visibleTo","Array","isArray","installedFrom","source","stamp","listingId","version"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,qBAAqB,QAA2B,wCAAuC;AAChG,SAASC,kBAAkB,QAAQ,kCAAiC;AAGpE;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,qBACdC,EAAU,EACVC,IAAmD;IAEnD,IAAI,CAACA,QAAQA,IAAI,CAAC,YAAY,IAAI,MAAM,OAAO;IAC/C,MAAMC,QAAQL,sBAAsBI;IACpC,MAAME,OAAO,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;IACrF,OAAO;QACLL;QACAM,MAAMR,mBAAmBG,SAASD;QAClCO,OAAO;YACLC,QAAQN,MAAMO,KAAK,CAACC,GAAG,CAAC,CAACC;;oBAEZT,uBACEA;uBAHuB;oBACpCF,IAAIW;oBACJL,MAAMH,MAAKD,wBAAAA,MAAMM,MAAM,CAACG,QAAQ,qBAArBT,sBAAuBI,IAAI,KAAKK;oBAC3CC,MAAMC,gBAAOX,yBAAAA,MAAMM,MAAM,CAACG,QAAQ,qBAArBT,uBAAuBU,IAAI,mBAAI;gBAC9C;;YACAE,WAAWC,MAAMC,OAAO,CAACf,IAAI,CAAC,YAAY,IAAKA,IAAI,CAAC,YAAY,GAAgB,EAAE;YAClFgB,eAAeA,cAAchB,IAAI,CAAC,SAAS;QAC7C;IACF;AACF;AAEA;;;;CAIC,GACD,SAASgB,cAAcC,MAAe;QAGKC;IAFzC,MAAMA,QAASD,iBAAAA,SAAU,CAAC;IAC1B,MAAME,YAAY,OAAOD,KAAK,CAAC,YAAY,KAAK,WAAWA,KAAK,CAAC,YAAY,GAAG;IAChF,OAAOC,YAAY;QAAEA;QAAWC,OAAO,GAAEF,iBAAAA,KAAK,CAAC,UAAU,YAAhBA,iBAAoB;IAAK,IAAI;AACxE"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/model/dataset-record.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginIndexedRecord } from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { type DatasetModel, effectiveDatasetModel } from './dataset-models'\nimport { datasetDisplayName } from './datasets'\n\n/**\n * One stored dataset as this plugin shares it with another (AGL-3080) — the\n * one shape its server index (`dataset-record-index.ts`) and its console list\n * source (`dataset-record-list.ts`) both answer, so a reader on either side\n * reads the same record.\n *\n * `null` for a deleted dataset. Otherwise its id, its name as the Data page\n * shows it, and `facts`:\n * `fields` — its fields in the dataset page's order, each\n * `{ id, name, type }`; `visibleTo` — the scope tokens it is shared\n * with, so a reader acting for a member can apply the platform's own\n * visibility rule to them; and `installedFrom` — `{ listingId, version }`\n * for a dataset installed from a listing (`version` as the install\n * stamped it), `null` for one the workspace made. Never a record of it.\n */\nexport function datasetIndexedRecord(\n id: string,\n data: Readonly<Record<string, unknown>> | undefined,\n): PluginIndexedRecord | null {\n if (!data || data['deletedAt'] != null) return null\n const model = effectiveDatasetModel(data as { model?: DatasetModel; fields?: string[] })\n const text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n return {\n id,\n name: datasetDisplayName(data) || id,\n facts: {\n fields: model.order.map((fieldId) => ({\n id: fieldId,\n name: text(model.fields[fieldId]?.name) || fieldId,\n type: String(model.fields[fieldId]?.type ?? 'text'),\n })),\n visibleTo: Array.isArray(data['visibleTo']) ? (data['visibleTo'] as string[]) : [],\n installedFrom: installedFrom(data['source']),\n },\n }\n}\n\n/**\n * Where an installed dataset came from. Read from its `source` stamp, which\n * every dataset install has written — the ones from before install\n * provenance (AGL-1015) included, which carry no `installedFrom`.\n */\nfunction installedFrom(source: unknown): { listingId: string; version: unknown } | null {\n const stamp = (source ?? {}) as Record<string, unknown>\n const listingId = typeof stamp['listingId'] === 'string' ? stamp['listingId'] : ''\n return listingId ? { listingId, version: stamp['version'] ?? null } : null\n}\n"],"names":["effectiveDatasetModel","datasetDisplayName","datasetIndexedRecord","id","data","model","text","value","trim","name","facts","fields","order","map","fieldId","type","String","visibleTo","Array","isArray","installedFrom","source","stamp","listingId","version"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAA4BA,qBAAqB,QAAQ,sBAAkB;AAC3E,SAASC,kBAAkB,QAAQ,gBAAY;AAE/C;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,qBACdC,EAAU,EACVC,IAAmD;IAEnD,IAAI,CAACA,QAAQA,IAAI,CAAC,YAAY,IAAI,MAAM,OAAO;IAC/C,MAAMC,QAAQL,sBAAsBI;IACpC,MAAME,OAAO,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;IACrF,OAAO;QACLL;QACAM,MAAMR,mBAAmBG,SAASD;QAClCO,OAAO;YACLC,QAAQN,MAAMO,KAAK,CAACC,GAAG,CAAC,CAACC;;oBAEZT,uBACEA;uBAHuB;oBACpCF,IAAIW;oBACJL,MAAMH,MAAKD,wBAAAA,MAAMM,MAAM,CAACG,QAAQ,qBAArBT,sBAAuBI,IAAI,KAAKK;oBAC3CC,MAAMC,gBAAOX,yBAAAA,MAAMM,MAAM,CAACG,QAAQ,qBAArBT,uBAAuBU,IAAI,mBAAI;gBAC9C;;YACAE,WAAWC,MAAMC,OAAO,CAACf,IAAI,CAAC,YAAY,IAAKA,IAAI,CAAC,YAAY,GAAgB,EAAE;YAClFgB,eAAeA,cAAchB,IAAI,CAAC,SAAS;QAC7C;IACF;AACF;AAEA;;;;CAIC,GACD,SAASgB,cAAcC,MAAe;QAGKC;IAFzC,MAAMA,QAASD,iBAAAA,SAAU,CAAC;IAC1B,MAAME,YAAY,OAAOD,KAAK,CAAC,YAAY,KAAK,WAAWA,KAAK,CAAC,YAAY,GAAG;IAChF,OAAOC,YAAY;QAAEA;QAAWC,OAAO,GAAEF,iBAAAA,KAAK,CAAC,UAAU,YAAhBA,iBAAoB;IAAK,IAAI;AACxE"}
@@ -0,0 +1,162 @@
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
+ * Datasets (AGL-102): tabular data at `orgs/{orgId}/datasets/{id}` with a
19
+ * `records` subcollection, edited in the console Data card and consumed by
20
+ * repeatable components (AGL-103) via `{{item.field}}` bindings.
21
+ */
22
+ import type { ScopeToken } from '@aglyn/aglyn/foundation/definitions/platform.types';
23
+ import { type DatasetFieldEntry, type DatasetModel } from './dataset-models';
24
+ /** Dataset field name: starts with a letter; letters/digits/underscores. */
25
+ export declare const DATASET_FIELD_PATTERN: RegExp;
26
+ export interface HostDataset {
27
+ $id?: string;
28
+ displayName?: string;
29
+ /** Column names, in display order (v1; superseded by `model`). */
30
+ fields?: string[];
31
+ /** Typed model (AGL-177); when absent, derive from `fields`. */
32
+ model?: import('./dataset-models').DatasetModel;
33
+ /** dod.ts Schema.Name shape (AGL-178). */
34
+ names?: {
35
+ singular?: string;
36
+ plural?: string;
37
+ };
38
+ /**
39
+ * Which sites may see this dataset (AGL-1037). Absent = org-wide, until
40
+ * the AGL-1040 backfill stamps it. See `app-utils/scope-tokens`.
41
+ */
42
+ visibleTo?: ScopeToken[];
43
+ }
44
+ export interface HostDatasetRecord {
45
+ $id?: string;
46
+ /**
47
+ * Field → value, in storage form per the field's type (see
48
+ * `dataset-models`). Rows written by a form or an automation step before
49
+ * AGL-2773 hold every value as text; readers accept both.
50
+ */
51
+ values?: Record<string, unknown>;
52
+ /** Row position in the editor and in repeated output. */
53
+ order?: number;
54
+ }
55
+ /**
56
+ * Stable field id from a human name: "Roast preference" → "roast_preference".
57
+ * Mirrors DATASET_FIELD_PATTERN; returns '' when nothing salvageable.
58
+ */
59
+ export declare function slugifyDatasetFieldId(name: string): string;
60
+ /**
61
+ * Default reference id for a new field (AGL-578): the `slugifyDatasetFieldId`
62
+ * slug, suffixed (`_2`, `_3`, …) to stay unique within the dataset. `taken`
63
+ * holds the ids already in use (compared case-insensitively). Returns '' when
64
+ * the name yields nothing valid. The user can override the result.
65
+ */
66
+ export declare function defaultDatasetFieldId(name: string, taken: ReadonlySet<string>): string;
67
+ /**
68
+ * Validate a user-entered reference id (AGL-578): non-empty, matches
69
+ * `DATASET_FIELD_PATTERN`, and unique within the dataset (`taken` = ids already
70
+ * used, compared case-insensitively). Returns an error message, or null when
71
+ * the id is usable.
72
+ */
73
+ export declare function validateDatasetFieldId(id: string, taken: ReadonlySet<string>): string | null;
74
+ /**
75
+ * Parses a comma/newline separated list of HUMAN field names into
76
+ * {id, name} entries — "Roast preference" keeps its pretty name and gets
77
+ * the stable id `roast_preference` (AGL-558). Plain snake_case keys
78
+ * still work and pick up a humanized display name. Duplicate ids and
79
+ * unsalvageable entries are dropped rather than failing the set.
80
+ */
81
+ export declare function parseDatasetFieldEntries(input: string): DatasetFieldEntry[];
82
+ /**
83
+ * Parses a comma/newline separated field list into valid unique names;
84
+ * invalid entries are dropped rather than failing the whole set.
85
+ */
86
+ export declare function parseDatasetFields(input: string): string[];
87
+ /** Record values restricted to the dataset's declared fields. */
88
+ export declare function sanitizeRecordValues(fields: string[], input: Record<string, unknown> | null | undefined): Record<string, string>;
89
+ /**
90
+ * The submitted values a dataset write would store, picked by field id and
91
+ * left as they arrived (AGL-556). With a `fieldMap` (submitted key → stable
92
+ * model fieldId), mapped values land under the mapped fieldId — so renamed
93
+ * schema fields keep receiving data — and entries whose fieldId isn't in the
94
+ * model are dropped. Submitted keys without a mapping (and calls without a
95
+ * map) fall back to the legacy name-intersection against the model's field
96
+ * ids, matching `sanitizeRecordValues`.
97
+ */
98
+ export declare function pickDatasetRecordInput(dataset: {
99
+ model?: DatasetModel;
100
+ fields?: string[];
101
+ }, input: Record<string, unknown> | null | undefined, fieldMap?: Record<string, string> | null): Record<string, unknown>;
102
+ /** A dataset record write, checked against the dataset's model. */
103
+ export interface DatasetRecordWrite {
104
+ /**
105
+ * The model fields this write supplied a value for, before coercion. Empty
106
+ * means nothing submitted matched a field of the dataset.
107
+ */
108
+ matched: string[];
109
+ /**
110
+ * The values to store, in storage form (see `dataset-models`): numbers,
111
+ * booleans, epoch millis and so on per the field's type. For a merge, the
112
+ * existing values with this write's laid over them.
113
+ */
114
+ values: Record<string, unknown>;
115
+ /** fieldId → the reason it was refused. Empty when the write may land. */
116
+ errors: Record<string, string>;
117
+ }
118
+ /**
119
+ * A record write from a form submission or an automation step, coerced and
120
+ * validated against the dataset's model (AGL-2773, option B).
121
+ *
122
+ * These writes used to store `String(value)` for every field and check
123
+ * nothing, so a form could put `abc` in a number column and skip a required
124
+ * one. They now run the same `coerceDocumentValues` + `validateDocument` pair
125
+ * the console and `/v1` do: a value is stored in its field's type, and a
126
+ * record with any refused field is not written at all. The caller decides
127
+ * what a refusal costs — a form keeps the Inbox submission, a run records a
128
+ * failed step.
129
+ *
130
+ * `existing` makes it a merge (update-or-append's update leg): this write's
131
+ * values are laid over the stored ones, and only the fields THIS write
132
+ * supplied are held to the model. A stored row written as text before this
133
+ * change keeps its text values and is not refused for them — the reads have
134
+ * always rendered them, and a merge that touched `email` must not fail over a
135
+ * legacy `price` of `"12"` it never sent.
136
+ *
137
+ * Custom field types validate only once registered: call
138
+ * `ensureDeclaredCustomFieldTypes(model)` before this on a server path.
139
+ */
140
+ export declare function prepareDatasetRecordWrite(dataset: {
141
+ model?: DatasetModel;
142
+ fields?: string[];
143
+ }, input: Record<string, unknown> | null | undefined, options?: {
144
+ fieldMap?: Record<string, string> | null;
145
+ existing?: Record<string, unknown> | null;
146
+ }): DatasetRecordWrite;
147
+ /**
148
+ * A refused write's errors as one line, in the model's field order:
149
+ * `Stars must be a whole number; Email is required`.
150
+ */
151
+ export declare function describeDatasetRecordErrors(errors: Record<string, string>): string;
152
+ /**
153
+ * The name a dataset is shown under: `displayName`, which is what every create
154
+ * path writes (AGL-536), then the pre-migration `name`. Blank when the document
155
+ * carries neither.
156
+ */
157
+ export declare function datasetDisplayName(dataset: {
158
+ displayName?: unknown;
159
+ name?: unknown;
160
+ } | null | undefined): string;
161
+ /** Records sorted by their editor order (then id for stability). */
162
+ export declare function sortDatasetRecords<T extends HostDatasetRecord>(records: T[]): T[];