@aglyn/plugins-data 1.0.0-beta.221 → 1.0.0-beta.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/package.json +10 -10
- package/src/index.d.ts +1 -1
- package/src/index.js +1 -1
- package/src/index.js.map +1 -1
- package/src/lib/artifact/dataset-schema-artifact.server.d.ts +57 -0
- package/src/lib/artifact/dataset-schema-artifact.server.js +308 -0
- package/src/lib/artifact/dataset-schema-artifact.server.js.map +1 -0
- package/src/lib/artifact/dataset-schema.d.ts +136 -0
- package/src/lib/artifact/dataset-schema.js +232 -0
- package/src/lib/artifact/dataset-schema.js.map +1 -0
- package/src/lib/components/dataset-record-dialog.component.d.ts +1 -1
- package/src/lib/components/dataset-record-dialog.component.js +1 -1
- package/src/lib/components/dataset-record-dialog.component.js.map +1 -1
- package/src/lib/components/dataset-record-filter.d.ts +1 -1
- package/src/lib/components/dataset-record-filter.js +2 -2
- package/src/lib/components/dataset-record-filter.js.map +1 -1
- package/src/lib/components/dataset-schema-dialog.component.d.ts +1 -1
- package/src/lib/components/dataset-schema-dialog.component.js +55 -23
- package/src/lib/components/dataset-schema-dialog.component.js.map +1 -1
- package/src/lib/components/host-datasets-card.component.js +91 -403
- package/src/lib/components/host-datasets-card.component.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +12 -0
- package/src/lib/constants/bundle-common.js +13 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +16 -1
- package/src/lib/declarations.console-server.js +34 -2
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.server.d.ts +13 -0
- package/src/lib/declarations.server.js +48 -2
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/form-target/dataset-form-record-target.server.js +5 -3
- package/src/lib/form-target/dataset-form-record-target.server.js.map +1 -1
- package/src/lib/model/dataset-models.d.ts +392 -0
- package/src/lib/model/dataset-models.js +806 -0
- package/src/lib/model/dataset-models.js.map +1 -0
- package/src/lib/model/dataset-record-view.d.ts +1 -1
- package/src/lib/model/dataset-record-view.js +1 -2
- package/src/lib/model/dataset-record-view.js.map +1 -1
- package/src/lib/model/dataset-record.js +2 -2
- package/src/lib/model/dataset-record.js.map +1 -1
- package/src/lib/model/datasets.d.ts +162 -0
- package/src/lib/model/datasets.js +227 -0
- package/src/lib/model/datasets.js.map +1 -0
- package/src/lib/plugin.d.ts +4 -0
- package/src/lib/plugin.js +40 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/record-pages/record-page-live-paths.server.d.ts +55 -0
- package/src/lib/record-pages/record-page-live-paths.server.js +83 -0
- package/src/lib/record-pages/record-page-live-paths.server.js.map +1 -0
- package/src/lib/record-pages/record-page-read.server.d.ts +70 -0
- package/src/lib/record-pages/record-page-read.server.js +175 -0
- package/src/lib/record-pages/record-page-read.server.js.map +1 -0
- package/src/lib/record-pages/record-page-resolver.server.d.ts +37 -0
- package/src/lib/record-pages/record-page-resolver.server.js +131 -0
- package/src/lib/record-pages/record-page-resolver.server.js.map +1 -0
- package/src/lib/record-pages/record-page-sitemap.server.d.ts +32 -0
- package/src/lib/record-pages/record-page-sitemap.server.js +102 -0
- package/src/lib/record-pages/record-page-sitemap.server.js.map +1 -0
- package/src/lib/record-pages/record-page-source.d.ts +42 -0
- package/src/lib/record-pages/record-page-source.js +176 -0
- package/src/lib/record-pages/record-page-source.js.map +1 -0
- package/src/lib/record-pages/record-pages-route.d.ts +42 -0
- package/src/lib/record-pages/record-pages-route.js +348 -0
- package/src/lib/record-pages/record-pages-route.js.map +1 -0
- package/src/lib/record-pages/record-pages.d.ts +218 -0
- package/src/lib/record-pages/record-pages.js +351 -0
- package/src/lib/record-pages/record-pages.js.map +1 -0
- package/src/lib/record-pages/record-template-row-chip.component.d.ts +28 -0
- package/src/lib/record-pages/record-template-row-chip.component.js +87 -0
- package/src/lib/record-pages/record-template-row-chip.component.js.map +1 -0
- package/src/lib/record-pages/record-template-section.component.d.ts +30 -0
- package/src/lib/record-pages/record-template-section.component.js +491 -0
- package/src/lib/record-pages/record-template-section.component.js.map +1 -0
- package/src/lib/repeat/dataset-repeat-rows.d.ts +2 -1
- package/src/lib/repeat/dataset-repeat-rows.js +30 -12
- package/src/lib/repeat/dataset-repeat-rows.js.map +1 -1
- package/src/lib/repeat/dataset-repeat-rows.server.d.ts +1 -1
- package/src/lib/repeat/dataset-repeat-rows.server.js +23 -12
- package/src/lib/repeat/dataset-repeat-rows.server.js.map +1 -1
- package/src/lib/repeat/repeat-record-pages.d.ts +48 -0
- package/src/lib/repeat/repeat-record-pages.js +64 -0
- package/src/lib/repeat/repeat-record-pages.js.map +1 -0
- package/src/lib/server/announce-dataset-records.d.ts +1 -1
- package/src/lib/server/announce-dataset-records.js +1 -1
- package/src/lib/server/announce-dataset-records.js.map +1 -1
- package/src/lib/server/api-v1/datasets.js +5 -3
- package/src/lib/server/api-v1/datasets.js.map +1 -1
- package/src/lib/server/dataset-figures.js +2 -2
- package/src/lib/server/dataset-figures.js.map +1 -1
- package/src/lib/server/dataset-live-pages.d.ts +147 -0
- package/src/lib/server/dataset-live-pages.js +436 -0
- package/src/lib/server/dataset-live-pages.js.map +1 -0
- package/src/lib/server/dataset-steps.server.d.ts +45 -0
- package/src/lib/server/dataset-steps.server.js +268 -0
- package/src/lib/server/dataset-steps.server.js.map +1 -0
- package/src/lib/server/datasets-route.d.ts +2 -0
- package/src/lib/server/datasets-route.js +102 -6
- package/src/lib/server/datasets-route.js.map +1 -1
- package/src/lib/server/resolve-dataset.d.ts +40 -0
- package/src/lib/server/resolve-dataset.js +64 -0
- package/src/lib/server/resolve-dataset.js.map +1 -0
- package/src/lib/server.d.ts +3 -2
- package/src/lib/server.js +8 -9
- package/src/lib/server.js.map +1 -1
- package/src/lib/site-bundle/datasets-package.d.ts +42 -0
- package/src/lib/site-bundle/datasets-package.js +95 -0
- package/src/lib/site-bundle/datasets-package.js.map +1 -0
- package/src/lib/site-bundle/datasets-site-bundle.server.js +2 -2
- package/src/lib/site-bundle/datasets-site-bundle.server.js.map +1 -1
- package/src/lib/{model/index.js → tenant-api.d.ts} +12 -7
- package/src/lib/tenant-api.js +30 -0
- package/src/lib/tenant-api.js.map +1 -0
- package/src/lib/transfer/dataset-transfer-key.d.ts +30 -0
- package/src/lib/transfer/dataset-transfer-key.js +31 -0
- package/src/lib/transfer/dataset-transfer-key.js.map +1 -0
- package/src/lib/transfer/dataset-transfer-model.d.ts +146 -0
- package/src/lib/transfer/dataset-transfer-model.js +536 -0
- package/src/lib/transfer/dataset-transfer-model.js.map +1 -0
- package/src/lib/{model/index.d.ts → transfer/dataset-transfer-resource.d.ts} +2 -6
- package/src/lib/transfer/dataset-transfer-resource.js +41 -0
- package/src/lib/transfer/dataset-transfer-resource.js.map +1 -0
- package/src/lib/transfer/dataset-transfer.server.d.ts +69 -0
- package/src/lib/transfer/dataset-transfer.server.js +930 -0
- package/src/lib/transfer/dataset-transfer.server.js.map +1 -0
- package/src/lib/model/dataset-csv.d.ts +0 -36
- package/src/lib/model/dataset-csv.js +0 -85
- package/src/lib/model/dataset-csv.js.map +0 -1
- package/src/lib/model/dataset-io.d.ts +0 -44
- package/src/lib/model/dataset-io.js +0 -85
- package/src/lib/model/dataset-io.js.map +0 -1
- package/src/lib/model/index.js.map +0 -1
- package/src/lib/server/datasets-export-route.d.ts +0 -57
- package/src/lib/server/datasets-export-route.js +0 -229
- package/src/lib/server/datasets-export-route.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/site-bundle/datasets-site-bundle.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n datasetIntegrityFields,\n effectiveDatasetModel,\n validateDocument,\n} from '@aglyn/aglyn/app-utils/dataset-models'\nimport { checkDatasetQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { hostScopeToken, newResourceScopeFields } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type {\n SiteBundleImportRequest,\n SiteBundleItem,\n SiteBundleReportRow,\n SiteBundleRequest,\n SiteBundleRestoreRequest,\n} from '@aglyn/aglyn/plugin-manager/plugin-site-bundle'\nimport { firebaseAdmin, scopedToHost } from '@aglyn/tenant-data-admin'\nimport { loadCustomFieldTypes } from '../server/custom-field-types'\n\n/**\n * A site's datasets in its whole-site backup (AGL-163), answered by the plugin\n * that keeps them (AGL-3080).\n *\n * Datasets are ORG-owned (AGL-237) and shared with sites by scope, so a site's\n * backup carries the datasets that site may see (AGL-1046) — each with its\n * records — and a restore writes them back into the owning organization,\n * scoped to the site it restores into.\n */\n\n/** The records one dataset carries in a bundle, and the most a restore writes back. */\nexport const DATASET_BUNDLE_RECORD_LIMIT = 1000\n\n/**\n * The keys a restore writes back on a dataset (AGL-1382): every key a live\n * dataset carries, because the restore writes with `merge: false` and a key\n * left off is ERASED. `visibleTo` is never on it — the restore assigns a fresh\n * scope for the site it restores into, since a bundle is portable and an\n * embedded `['org']` would publish one organization's data across another's\n * whole client roster.\n */\nexport const DATASET_RESTORE_FIELDS: readonly string[] = [\n 'displayName',\n // Pre-AGL-536 human name, still read as a fallback by site search.\n 'name',\n 'fields',\n // The typed DatasetModel, and exactly what `effectiveDatasetModel` reads\n // below. Drop it and every restored dataset silently degrades to the\n // derived all-text v1 model, losing types, `required`, `validation`,\n // `default` and every `reference` link (AGL-180).\n 'model',\n 'names',\n 'description',\n 'source',\n 'installedFrom',\n 'detachedFrom',\n]\n\n/**\n * The keys a restore writes back on a record. `order` is what\n * `sortDatasetRecords` sorts by, so dropping it reorders every repeatable on\n * the restored site. The integrity index is DERIVED on the way in, never\n * carried (see {@link importSiteDatasets}).\n */\nexport const RECORD_RESTORE_FIELDS: readonly string[] = ['values', 'order']\n\n/** One of a bundle's documents, reduced to the keys a restore may write. */\nfunction restorable(fields: readonly string[], input: SiteBundleItem): Record<string, unknown> {\n const allowed = new Set(fields)\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(input ?? {})) {\n // Only `undefined` is absence — Firestore rejects it outright.\n if (allowed.has(key) && value !== undefined) out[key] = value\n }\n return out\n}\n\n/** The ids a bundle would write — distinct, since two items sharing one are one document. */\nfunction bundleIds(items: readonly SiteBundleItem[]): Set<string> {\n const ids = new Set<string>()\n for (const item of items) if (item?.$id) ids.add(String(item.$id))\n return ids\n}\n\nconst datasetsOf = (orgId: string) =>\n firebaseAdmin.app().firestore().collection('orgs').doc(orgId).collection('datasets')\n\n/**\n * The site's share: the datasets the site may see, each with its records.\n *\n * A site with no organization genuinely has no datasets — a known-empty\n * answer. Everything else THROWS: a catch-all would ship a silently empty,\n * still-successful \"backup\" that nobody discovers until they restore it. An\n * export that fails loudly is recoverable; one that lies is not.\n *\n * `visibleTo` is stripped on the way out. Its `host:` tokens name sites of\n * THIS organization, so they are noise at best and a dangling reference once\n * the bundle is restored elsewhere; the restore assigns a fresh scope.\n */\nexport async function exportSiteDatasets({\n hostId,\n orgId,\n limit,\n}: SiteBundleRequest): Promise<SiteBundleItem[]> {\n if (!orgId) return []\n const snapshot = await scopedToHost(datasetsOf(orgId), hostId).limit(limit).get()\n const live = snapshot.docs.filter((doc) => !doc.get('deletedAt'))\n return Promise.all(\n live.map(async (doc) => {\n const { visibleTo: _visibleTo, ...data } = doc.data()\n const records = await doc.ref.collection('records').limit(DATASET_BUNDLE_RECORD_LIMIT).get()\n return {\n $id: doc.id,\n ...data,\n records: records.docs.map((record) => ({ $id: record.id, ...record.data() })),\n }\n }),\n )\n}\n\n/**\n * Refuse a bundle that would put the workspace over the datasets it may hold\n * (AGL-1403) — decided before the restore writes anything.\n *\n * Datasets are SOLD: the plan includes some and the workspace buys more as\n * add-ons, so `checkDatasetQuota` decides, never the plan's included number —\n * a workspace that paid for extra datasets is entitled to them, and refusing\n * its own backup after taking its money for the room would be wrong.\n *\n * A restore is identified by id COLLISION, never by the bundle's own claim of\n * where it came from (an unsigned string in a file the metered party\n * uploads). The meter is per organization, so a collision here means the\n * WORKSPACE already holds the dataset: restoring into a sibling site of the\n * same workspace rewrites datasets it already pays for and raises nothing,\n * while a copy into a different workspace collides on nothing and is refused\n * when it lands that workspace over. What is refused is the RAISE, never the\n * state of being over — a workspace already over its limit may still restore\n * its own backup.\n */\nexport async function siteDatasetsRefusal({\n orgId,\n org,\n items,\n}: SiteBundleRestoreRequest): Promise<string | null> {\n const bundled = bundleIds(items)\n if (!orgId || !bundled.size) return null\n if (!Number.isFinite(checkDatasetQuota(org as any, 0).limit)) return null\n\n // A field mask with no fields projects to the document id alone.\n const held = await datasetsOf(orgId).select().get()\n const existing = new Set(held.docs.map((doc) => doc.id))\n const next = new Set([...existing, ...bundled]).size\n // `allowed` asks \"may I add one more to N\", so the post state N is checked\n // as `N - 1` — the idiom the dataset routes' own bulk paths use.\n const quota = checkDatasetQuota(org as any, next - 1)\n if (next <= existing.size || quota.allowed) return null\n\n return (\n `This backup holds ${bundled.size} datasets and this workspace ` +\n `has ${existing.size}, which would put it at ${next} of ${quota.limit} ` +\n 'datasets. Nothing was imported — ' +\n (quota.upgradeRequired\n ? 'upgrade in Billing.'\n : `add extra datasets for $${quota.addonPriceUsd}/mo each, or ` +\n 'upgrade in Billing.')\n )\n}\n\n/**\n * Writes the bundle's datasets into the owning organization, scoped to the\n * site restored into (AGL-237, AGL-1046), each with its records.\n *\n * Non-conforming records are restored AND reported (AGL-182): data is never\n * silently dropped, and the report tells the owner what to fix. A bundle from\n * before typed models validates through the derived text model, as the live\n * migration does, so everything in it passes.\n */\nexport async function importSiteDatasets(\n request: SiteBundleImportRequest,\n): Promise<SiteBundleReportRow[]> {\n const { hostId, orgId, items, write, stamps } = request\n if (!orgId) return []\n // Through the scoped-create gate (AGL-1478, AGL-1484): a restore CREATES\n // datasets, and a dataset is born scoped to the site it lands in.\n const scope = newResourceScopeFields([hostScopeToken(hostId)])\n const report: SiteBundleReportRow[] = []\n for (const item of items) {\n if (!item?.$id) continue\n const path = `orgs/${orgId}/datasets/${String(item.$id)}`\n await write(path, { ...restorable(DATASET_RESTORE_FIELDS, item), ...stamps(), ...scope })\n const model = effectiveDatasetModel(item)\n await loadCustomFieldTypes(model, request.loadPluginSurfaces)\n const records: SiteBundleItem[] = Array.isArray(item['records']) ? item['records'] : []\n for (const record of records.slice(0, DATASET_BUNDLE_RECORD_LIMIT)) {\n if (!record?.$id) continue\n const errors = validateDocument(model, record['values'] ?? {})\n if (Object.keys(errors).length) {\n report.push({ datasetId: String(item.$id), recordId: String(record.$id), errors })\n }\n await write(`${path}/records/${String(record.$id)}`, {\n ...restorable(RECORD_RESTORE_FIELDS, record),\n ...stamps(),\n // DERIVED here rather than carried in the bundle. The integrity\n // index the delete check queries must describe the values just\n // restored, and a bundle written before the field existed carries\n // none — accepting the bundle's copy would restore records the check\n // cannot reach. Records keep their original ids, so the ids it holds\n // still resolve.\n ...datasetIntegrityFields(model, record['values'] ?? {}),\n })\n }\n }\n return report\n}\n"],"names":["datasetIntegrityFields","effectiveDatasetModel","validateDocument","checkDatasetQuota","hostScopeToken","newResourceScopeFields","firebaseAdmin","scopedToHost","loadCustomFieldTypes","DATASET_BUNDLE_RECORD_LIMIT","DATASET_RESTORE_FIELDS","RECORD_RESTORE_FIELDS","restorable","fields","input","allowed","Set","out","key","value","Object","entries","has","undefined","bundleIds","items","ids","item","$id","add","String","datasetsOf","orgId","app","firestore","collection","doc","exportSiteDatasets","hostId","limit","snapshot","get","live","docs","filter","Promise","all","map","data","visibleTo","_visibleTo","records","ref","id","record","siteDatasetsRefusal","org","bundled","size","Number","isFinite","held","select","existing","next","quota","upgradeRequired","addonPriceUsd","importSiteDatasets","request","write","stamps","scope","report","path","model","loadPluginSurfaces","Array","isArray","slice","errors","keys","length","push","datasetId","recordId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/site-bundle/datasets-site-bundle.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkDatasetQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { hostScopeToken, newResourceScopeFields } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type {\n SiteBundleImportRequest,\n SiteBundleItem,\n SiteBundleReportRow,\n SiteBundleRequest,\n SiteBundleRestoreRequest,\n} from '@aglyn/aglyn/plugin-manager/plugin-site-bundle'\nimport { firebaseAdmin, scopedToHost } from '@aglyn/tenant-data-admin'\nimport { loadCustomFieldTypes } from '../server/custom-field-types'\nimport { datasetIntegrityFields, effectiveDatasetModel, validateDocument } from '../model/dataset-models'\n\n/**\n * A site's datasets in its whole-site backup (AGL-163), answered by the plugin\n * that keeps them (AGL-3080).\n *\n * Datasets are ORG-owned (AGL-237) and shared with sites by scope, so a site's\n * backup carries the datasets that site may see (AGL-1046) — each with its\n * records — and a restore writes them back into the owning organization,\n * scoped to the site it restores into.\n */\n\n/** The records one dataset carries in a bundle, and the most a restore writes back. */\nexport const DATASET_BUNDLE_RECORD_LIMIT = 1000\n\n/**\n * The keys a restore writes back on a dataset (AGL-1382): every key a live\n * dataset carries, because the restore writes with `merge: false` and a key\n * left off is ERASED. `visibleTo` is never on it — the restore assigns a fresh\n * scope for the site it restores into, since a bundle is portable and an\n * embedded `['org']` would publish one organization's data across another's\n * whole client roster.\n */\nexport const DATASET_RESTORE_FIELDS: readonly string[] = [\n 'displayName',\n // Pre-AGL-536 human name, still read as a fallback by site search.\n 'name',\n 'fields',\n // The typed DatasetModel, and exactly what `effectiveDatasetModel` reads\n // below. Drop it and every restored dataset silently degrades to the\n // derived all-text v1 model, losing types, `required`, `validation`,\n // `default` and every `reference` link (AGL-180).\n 'model',\n 'names',\n 'description',\n 'source',\n 'installedFrom',\n 'detachedFrom',\n]\n\n/**\n * The keys a restore writes back on a record. `order` is what\n * `sortDatasetRecords` sorts by, so dropping it reorders every repeatable on\n * the restored site. The integrity index is DERIVED on the way in, never\n * carried (see {@link importSiteDatasets}).\n */\nexport const RECORD_RESTORE_FIELDS: readonly string[] = ['values', 'order']\n\n/** One of a bundle's documents, reduced to the keys a restore may write. */\nfunction restorable(fields: readonly string[], input: SiteBundleItem): Record<string, unknown> {\n const allowed = new Set(fields)\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(input ?? {})) {\n // Only `undefined` is absence — Firestore rejects it outright.\n if (allowed.has(key) && value !== undefined) out[key] = value\n }\n return out\n}\n\n/** The ids a bundle would write — distinct, since two items sharing one are one document. */\nfunction bundleIds(items: readonly SiteBundleItem[]): Set<string> {\n const ids = new Set<string>()\n for (const item of items) if (item?.$id) ids.add(String(item.$id))\n return ids\n}\n\nconst datasetsOf = (orgId: string) =>\n firebaseAdmin.app().firestore().collection('orgs').doc(orgId).collection('datasets')\n\n/**\n * The site's share: the datasets the site may see, each with its records.\n *\n * A site with no organization genuinely has no datasets — a known-empty\n * answer. Everything else THROWS: a catch-all would ship a silently empty,\n * still-successful \"backup\" that nobody discovers until they restore it. An\n * export that fails loudly is recoverable; one that lies is not.\n *\n * `visibleTo` is stripped on the way out. Its `host:` tokens name sites of\n * THIS organization, so they are noise at best and a dangling reference once\n * the bundle is restored elsewhere; the restore assigns a fresh scope.\n */\nexport async function exportSiteDatasets({\n hostId,\n orgId,\n limit,\n}: SiteBundleRequest): Promise<SiteBundleItem[]> {\n if (!orgId) return []\n const snapshot = await scopedToHost(datasetsOf(orgId), hostId).limit(limit).get()\n const live = snapshot.docs.filter((doc) => !doc.get('deletedAt'))\n return Promise.all(\n live.map(async (doc) => {\n const { visibleTo: _visibleTo, ...data } = doc.data()\n const records = await doc.ref.collection('records').limit(DATASET_BUNDLE_RECORD_LIMIT).get()\n return {\n $id: doc.id,\n ...data,\n records: records.docs.map((record) => ({ $id: record.id, ...record.data() })),\n }\n }),\n )\n}\n\n/**\n * Refuse a bundle that would put the workspace over the datasets it may hold\n * (AGL-1403) — decided before the restore writes anything.\n *\n * Datasets are SOLD: the plan includes some and the workspace buys more as\n * add-ons, so `checkDatasetQuota` decides, never the plan's included number —\n * a workspace that paid for extra datasets is entitled to them, and refusing\n * its own backup after taking its money for the room would be wrong.\n *\n * A restore is identified by id COLLISION, never by the bundle's own claim of\n * where it came from (an unsigned string in a file the metered party\n * uploads). The meter is per organization, so a collision here means the\n * WORKSPACE already holds the dataset: restoring into a sibling site of the\n * same workspace rewrites datasets it already pays for and raises nothing,\n * while a copy into a different workspace collides on nothing and is refused\n * when it lands that workspace over. What is refused is the RAISE, never the\n * state of being over — a workspace already over its limit may still restore\n * its own backup.\n */\nexport async function siteDatasetsRefusal({\n orgId,\n org,\n items,\n}: SiteBundleRestoreRequest): Promise<string | null> {\n const bundled = bundleIds(items)\n if (!orgId || !bundled.size) return null\n if (!Number.isFinite(checkDatasetQuota(org as any, 0).limit)) return null\n\n // A field mask with no fields projects to the document id alone.\n const held = await datasetsOf(orgId).select().get()\n const existing = new Set(held.docs.map((doc) => doc.id))\n const next = new Set([...existing, ...bundled]).size\n // `allowed` asks \"may I add one more to N\", so the post state N is checked\n // as `N - 1` — the idiom the dataset routes' own bulk paths use.\n const quota = checkDatasetQuota(org as any, next - 1)\n if (next <= existing.size || quota.allowed) return null\n\n return (\n `This backup holds ${bundled.size} datasets and this workspace ` +\n `has ${existing.size}, which would put it at ${next} of ${quota.limit} ` +\n 'datasets. Nothing was imported — ' +\n (quota.upgradeRequired\n ? 'upgrade in Billing.'\n : `add extra datasets for $${quota.addonPriceUsd}/mo each, or ` +\n 'upgrade in Billing.')\n )\n}\n\n/**\n * Writes the bundle's datasets into the owning organization, scoped to the\n * site restored into (AGL-237, AGL-1046), each with its records.\n *\n * Non-conforming records are restored AND reported (AGL-182): data is never\n * silently dropped, and the report tells the owner what to fix. A bundle from\n * before typed models validates through the derived text model, as the live\n * migration does, so everything in it passes.\n */\nexport async function importSiteDatasets(\n request: SiteBundleImportRequest,\n): Promise<SiteBundleReportRow[]> {\n const { hostId, orgId, items, write, stamps } = request\n if (!orgId) return []\n // Through the scoped-create gate (AGL-1478, AGL-1484): a restore CREATES\n // datasets, and a dataset is born scoped to the site it lands in.\n const scope = newResourceScopeFields([hostScopeToken(hostId)])\n const report: SiteBundleReportRow[] = []\n for (const item of items) {\n if (!item?.$id) continue\n const path = `orgs/${orgId}/datasets/${String(item.$id)}`\n await write(path, { ...restorable(DATASET_RESTORE_FIELDS, item), ...stamps(), ...scope })\n const model = effectiveDatasetModel(item)\n await loadCustomFieldTypes(model, request.loadPluginSurfaces)\n const records: SiteBundleItem[] = Array.isArray(item['records']) ? item['records'] : []\n for (const record of records.slice(0, DATASET_BUNDLE_RECORD_LIMIT)) {\n if (!record?.$id) continue\n const errors = validateDocument(model, record['values'] ?? {})\n if (Object.keys(errors).length) {\n report.push({ datasetId: String(item.$id), recordId: String(record.$id), errors })\n }\n await write(`${path}/records/${String(record.$id)}`, {\n ...restorable(RECORD_RESTORE_FIELDS, record),\n ...stamps(),\n // DERIVED here rather than carried in the bundle. The integrity\n // index the delete check queries must describe the values just\n // restored, and a bundle written before the field existed carries\n // none — accepting the bundle's copy would restore records the check\n // cannot reach. Records keep their original ids, so the ids it holds\n // still resolve.\n ...datasetIntegrityFields(model, record['values'] ?? {}),\n })\n }\n }\n return report\n}\n"],"names":["checkDatasetQuota","hostScopeToken","newResourceScopeFields","firebaseAdmin","scopedToHost","loadCustomFieldTypes","datasetIntegrityFields","effectiveDatasetModel","validateDocument","DATASET_BUNDLE_RECORD_LIMIT","DATASET_RESTORE_FIELDS","RECORD_RESTORE_FIELDS","restorable","fields","input","allowed","Set","out","key","value","Object","entries","has","undefined","bundleIds","items","ids","item","$id","add","String","datasetsOf","orgId","app","firestore","collection","doc","exportSiteDatasets","hostId","limit","snapshot","get","live","docs","filter","Promise","all","map","data","visibleTo","_visibleTo","records","ref","id","record","siteDatasetsRefusal","org","bundled","size","Number","isFinite","held","select","existing","next","quota","upgradeRequired","addonPriceUsd","importSiteDatasets","request","write","stamps","scope","report","path","model","loadPluginSurfaces","Array","isArray","slice","errors","keys","length","push","datasetId","recordId"],"mappings":"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"}
|
|
@@ -13,10 +13,15 @@
|
|
|
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
|
-
*/
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The data plugin's published-site surface (AGL-3475): record pages, served
|
|
19
|
+
* through the platform's site page resolver seam.
|
|
20
|
+
*
|
|
21
|
+
* Its own module (`modules.tenantApi` in `plugins.config.json`) rather than
|
|
22
|
+
* the package's `/server` entry, which carries the console's datasets
|
|
23
|
+
* handlers: the tenant loads this on every page render, and what it loads
|
|
24
|
+
* here is one registration. The resolver itself, and the Admin SDK reads
|
|
25
|
+
* behind it, load on the first request no published page claims.
|
|
26
|
+
*/
|
|
27
|
+
export declare function registerDataTenantApi(): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { registerSitePageResolver } from "@aglyn/aglyn/server";
|
|
17
|
+
/**
|
|
18
|
+
* The data plugin's published-site surface (AGL-3475): record pages, served
|
|
19
|
+
* through the platform's site page resolver seam.
|
|
20
|
+
*
|
|
21
|
+
* Its own module (`modules.tenantApi` in `plugins.config.json`) rather than
|
|
22
|
+
* the package's `/server` entry, which carries the console's datasets
|
|
23
|
+
* handlers: the tenant loads this on every page render, and what it loads
|
|
24
|
+
* here is one registration. The resolver itself, and the Admin SDK reads
|
|
25
|
+
* behind it, load on the first request no published page claims.
|
|
26
|
+
*/ export function registerDataTenantApi() {
|
|
27
|
+
registerSitePageResolver(async (context)=>(await import("./record-pages/record-page-resolver.server.js")).recordPageResolver(context));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=tenant-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/data/src/lib/tenant-api.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 { registerSitePageResolver } from '@aglyn/aglyn/server'\n\n/**\n * The data plugin's published-site surface (AGL-3475): record pages, served\n * through the platform's site page resolver seam.\n *\n * Its own module (`modules.tenantApi` in `plugins.config.json`) rather than\n * the package's `/server` entry, which carries the console's datasets\n * handlers: the tenant loads this on every page render, and what it loads\n * here is one registration. The resolver itself, and the Admin SDK reads\n * behind it, load on the first request no published page claims.\n */\nexport function registerDataTenantApi(): void {\n registerSitePageResolver(async (context) =>\n (await import('./record-pages/record-page-resolver.server')).recordPageResolver(\n context,\n ),\n )\n}\n"],"names":["registerSitePageResolver","registerDataTenantApi","context","recordPageResolver"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,wBAAwB,QAAQ,sBAAqB;AAE9D;;;;;;;;;CASC,GACD,OAAO,SAASC;IACdD,yBAAyB,OAAOE,UAC9B,AAAC,CAAA,MAAM,MAAM,CAAC,gDAA4C,EAAGC,kBAAkB,CAC7ED;AAGN"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The transfer resource a dataset's records move as (AGL-3530): declared once
|
|
19
|
+
* in `plugins.config.json` as `data.dataset` with `instances`, and reached
|
|
20
|
+
* per dataset as `data.dataset:<datasetId>` — so a job, a person's remembered
|
|
21
|
+
* export fields and the one-running-import rule each belong to one dataset.
|
|
22
|
+
*
|
|
23
|
+
* Its own module so the Data card can name a dataset's resource without
|
|
24
|
+
* loading the transfer core: the key is the core's instance key
|
|
25
|
+
* (`transferResourceInstanceKey` in `@aglyn/aglyn/data-transfer`), spelled out
|
|
26
|
+
* here, and `dataset-transfer-key.spec.ts` holds the two to the same answer.
|
|
27
|
+
*/
|
|
28
|
+
export declare const DATASET_TRANSFER_RESOURCE = "data.dataset";
|
|
29
|
+
/** The resource key a dataset's records are imported and exported under. */
|
|
30
|
+
export declare function datasetTransferResourceKey(datasetId: string): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The transfer resource a dataset's records move as (AGL-3530): declared once
|
|
18
|
+
* in `plugins.config.json` as `data.dataset` with `instances`, and reached
|
|
19
|
+
* per dataset as `data.dataset:<datasetId>` — so a job, a person's remembered
|
|
20
|
+
* export fields and the one-running-import rule each belong to one dataset.
|
|
21
|
+
*
|
|
22
|
+
* Its own module so the Data card can name a dataset's resource without
|
|
23
|
+
* loading the transfer core: the key is the core's instance key
|
|
24
|
+
* (`transferResourceInstanceKey` in `@aglyn/aglyn/data-transfer`), spelled out
|
|
25
|
+
* here, and `dataset-transfer-key.spec.ts` holds the two to the same answer.
|
|
26
|
+
*/ export const DATASET_TRANSFER_RESOURCE = 'data.dataset';
|
|
27
|
+
/** The resource key a dataset's records are imported and exported under. */ export function datasetTransferResourceKey(datasetId) {
|
|
28
|
+
return `${DATASET_TRANSFER_RESOURCE}:${datasetId}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=dataset-transfer-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/transfer/dataset-transfer-key.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The transfer resource a dataset's records move as (AGL-3530): declared once\n * in `plugins.config.json` as `data.dataset` with `instances`, and reached\n * per dataset as `data.dataset:<datasetId>` — so a job, a person's remembered\n * export fields and the one-running-import rule each belong to one dataset.\n *\n * Its own module so the Data card can name a dataset's resource without\n * loading the transfer core: the key is the core's instance key\n * (`transferResourceInstanceKey` in `@aglyn/aglyn/data-transfer`), spelled out\n * here, and `dataset-transfer-key.spec.ts` holds the two to the same answer.\n */\nexport const DATASET_TRANSFER_RESOURCE = 'data.dataset'\n\n/** The resource key a dataset's records are imported and exported under. */\nexport function datasetTransferResourceKey(datasetId: string): string {\n return `${DATASET_TRANSFER_RESOURCE}:${datasetId}`\n}\n"],"names":["DATASET_TRANSFER_RESOURCE","datasetTransferResourceKey","datasetId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;CAUC,GACD,OAAO,MAAMA,4BAA4B,eAAc;AAEvD,0EAA0E,GAC1E,OAAO,SAASC,2BAA2BC,SAAiB;IAC1D,OAAO,GAAGF,0BAA0B,CAAC,EAAEE,WAAW;AACpD"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type MatchKeySpec, type PlannedTransferRow, type TransferCatalogInput, type TransferField, type TransferFieldType, type TransferPlan } from '@aglyn/aglyn/data-transfer';
|
|
18
|
+
import type { PicklistSpec, PicklistValueSet } from '@aglyn/aglyn/app-utils/picklists';
|
|
19
|
+
import type { TransferMatchKeyOffer } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources';
|
|
20
|
+
import { type DatasetFieldDefinition, type DatasetModel } from '../model/dataset-models';
|
|
21
|
+
/** The group a dataset's own fields are listed under. */
|
|
22
|
+
export declare const DATASET_TRANSFER_FIELDS_GROUP = "fields";
|
|
23
|
+
/** The record's created time, exported and never written. */
|
|
24
|
+
export declare const DATASET_TRANSFER_CREATED_FIELD = "record:createdAt";
|
|
25
|
+
/** The record's last-updated time, exported and never written. */
|
|
26
|
+
export declare const DATASET_TRANSFER_UPDATED_FIELD = "record:updatedAt";
|
|
27
|
+
/** A dataset field's id in the catalog. */
|
|
28
|
+
export declare function datasetTransferFieldId(fieldId: string): string;
|
|
29
|
+
/** The dataset field a catalog id names, or `null` for the Aglyn ID and the record's times. */
|
|
30
|
+
export declare function datasetFieldIdOf(transferFieldId: string): string | null;
|
|
31
|
+
/** The picklist a text field with a fixed list of options is matched against. */
|
|
32
|
+
export declare function datasetOptionsPicklistId(fieldId: string): string;
|
|
33
|
+
/** The field a picklist id from {@link datasetOptionsPicklistId} belongs to, or `null`. */
|
|
34
|
+
export declare function datasetFieldOfPicklist(picklistId: string): string | null;
|
|
35
|
+
/** A text field whose values must be one of its options. */
|
|
36
|
+
export declare function isOptionsField(field: DatasetFieldDefinition | undefined): boolean;
|
|
37
|
+
/** A field a file may write: everything but the bytes and null placeholders, which hold nothing a cell can say. */
|
|
38
|
+
export declare function isWritableDatasetField(field: DatasetFieldDefinition | undefined): boolean;
|
|
39
|
+
/** What a dataset field means to the wizard. */
|
|
40
|
+
export declare function datasetTransferType(field: DatasetFieldDefinition): TransferFieldType;
|
|
41
|
+
/**
|
|
42
|
+
* What the person may do with a reference's target dataset, as the server
|
|
43
|
+
* read it for them: a reference to a dataset they cannot see is exported by
|
|
44
|
+
* ID and never imported, since nothing in it could be looked up.
|
|
45
|
+
*/
|
|
46
|
+
export interface DatasetReferenceTarget {
|
|
47
|
+
readable: boolean;
|
|
48
|
+
/** The target's model, when it is readable: whether the import may create a record in it by name. */
|
|
49
|
+
model?: DatasetModel;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether the target's model accepts a record holding nothing but a name in
|
|
53
|
+
* `displayFieldId` (its page addresses filled in from it): the display field
|
|
54
|
+
* is plain text and no other field is required. Each name is still held to
|
|
55
|
+
* the model when the import creates it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function datasetAcceptsNamedRecord(model: DatasetModel, displayFieldId: string | undefined): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* One dataset field as the catalog lists it. `target` is what the server read
|
|
60
|
+
* of a reference's target dataset; without it a reference is a lookup that
|
|
61
|
+
* creates nothing.
|
|
62
|
+
*/
|
|
63
|
+
export declare function datasetTransferField(fieldId: string, field: DatasetFieldDefinition, target?: DatasetReferenceTarget): TransferField;
|
|
64
|
+
/**
|
|
65
|
+
* The dataset's catalog: its fields in the dataset's order under one group
|
|
66
|
+
* (named after the dataset when `label` is given), then the record's Aglyn ID (added by the core) and its created and updated
|
|
67
|
+
* times. `targets` is what the server read of each referenced dataset, by id.
|
|
68
|
+
*/
|
|
69
|
+
export declare function datasetTransferCatalog(model: DatasetModel, label?: string, targets?: Readonly<Record<string, DatasetReferenceTarget>>): TransferCatalogInput;
|
|
70
|
+
/** An options field's values as a picklist the values step can match against. */
|
|
71
|
+
export declare function datasetOptionsPicklist(field: DatasetFieldDefinition): {
|
|
72
|
+
spec: PicklistSpec;
|
|
73
|
+
set: PicklistValueSet;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* A stored record as transfer values, by catalog id: every field the model
|
|
77
|
+
* lists that the record holds, its Aglyn ID, and its created and updated
|
|
78
|
+
* times when it carries them. A field the record does not hold is absent,
|
|
79
|
+
* which the plan reads as blank.
|
|
80
|
+
*/
|
|
81
|
+
export declare function datasetRecordTransferValues(model: DatasetModel, record: {
|
|
82
|
+
id?: string;
|
|
83
|
+
values?: Readonly<Record<string, unknown>> | null;
|
|
84
|
+
createdAt?: unknown;
|
|
85
|
+
updatedAt?: unknown;
|
|
86
|
+
}): Record<string, unknown>;
|
|
87
|
+
/**
|
|
88
|
+
* Transfer values in storage form, through `coerceDocumentValues` — the
|
|
89
|
+
* coercion every record write runs. `values` holds what is written and
|
|
90
|
+
* `cleared` the fields a blank value empties. The Aglyn ID, the record's
|
|
91
|
+
* times, unknown ids and fields a file never writes are ignored.
|
|
92
|
+
*/
|
|
93
|
+
export declare function datasetStorageValues(model: DatasetModel, transfer: Readonly<Record<string, unknown>>): {
|
|
94
|
+
values: Record<string, unknown>;
|
|
95
|
+
cleared: string[];
|
|
96
|
+
};
|
|
97
|
+
/** How a dataset field compares as a match key, or `null` for one no query can find a record by. */
|
|
98
|
+
export declare function datasetMatchKeyFor(fieldId: string, field: DatasetFieldDefinition | undefined): MatchKeySpec | null;
|
|
99
|
+
/**
|
|
100
|
+
* The keys a row may find its record by: the Aglyn ID, then every text and
|
|
101
|
+
* number field the records' filter values can be asked by, in the dataset's
|
|
102
|
+
* order. A person starts with the Aglyn ID and the dataset's page address —
|
|
103
|
+
* the one field a dataset holds unique — and may add any other.
|
|
104
|
+
*/
|
|
105
|
+
export declare function datasetMatchKeyOffer(model: DatasetModel): TransferMatchKeyOffer;
|
|
106
|
+
/**
|
|
107
|
+
* The `filterValues` equality that finds the records holding a normalized
|
|
108
|
+
* key value: the path, and the value as the records store it. Plain text is
|
|
109
|
+
* stored as its lower-cased key clipped to 64 characters, so two long values
|
|
110
|
+
* can share it; the caller compares what it found against the whole value.
|
|
111
|
+
*/
|
|
112
|
+
export declare function datasetMatchKeyQuery(model: DatasetModel, key: MatchKeySpec, normalized: string): {
|
|
113
|
+
path: string;
|
|
114
|
+
value: string | number;
|
|
115
|
+
} | null;
|
|
116
|
+
/** Whether a record's transfer values hold a normalized key value — the check a clipped query answer is held to. */
|
|
117
|
+
export declare function datasetHoldsKey(view: Readonly<Record<string, unknown>>, key: MatchKeySpec, normalized: string): boolean;
|
|
118
|
+
/** Field id → message, for the fields a write of these values would refuse. */
|
|
119
|
+
export declare function datasetWriteErrors(model: DatasetModel, values: Record<string, unknown>, only?: ReadonlySet<string>): Record<string, string>;
|
|
120
|
+
/**
|
|
121
|
+
* The values a planned row leaves a record holding, in storage form: a new
|
|
122
|
+
* record's from the row alone, an existing one's from what it holds now
|
|
123
|
+
* (`before`, transfer values) with the row's changes over it. Page addresses
|
|
124
|
+
* are filled in as every record write fills them. `changed` names the dataset
|
|
125
|
+
* fields the row writes.
|
|
126
|
+
*/
|
|
127
|
+
export declare function datasetPlannedValues(model: DatasetModel, row: Pick<PlannedTransferRow, 'verdict' | 'diff'>, before: Readonly<Record<string, unknown>> | null | undefined): {
|
|
128
|
+
values: Record<string, unknown>;
|
|
129
|
+
changed: Set<string>;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* The plan with every row the model would refuse failed, naming the fields
|
|
133
|
+
* (`missing`, read with the reason "A value refuses the row"): a value outside
|
|
134
|
+
* a field's options, past its bounds, not matching its pattern, or refused by
|
|
135
|
+
* a custom field type's validator. An update is held only on the fields it
|
|
136
|
+
* writes, so a record stored before a rule existed is not refused for a value
|
|
137
|
+
* the file never touched.
|
|
138
|
+
*/
|
|
139
|
+
export declare function refuseInvalidDatasetRows(model: DatasetModel, plan: TransferPlan, existing: ReadonlyMap<string, Readonly<Record<string, unknown>>>): TransferPlan;
|
|
140
|
+
/**
|
|
141
|
+
* The plan with every new record past `maxCreates` failed as past what the
|
|
142
|
+
* plan allows, under the `planLimit` warning the person acknowledges. Run
|
|
143
|
+
* after {@link refuseInvalidDatasetRows}, so a row the model refuses never
|
|
144
|
+
* takes the room a valid row could have had.
|
|
145
|
+
*/
|
|
146
|
+
export declare function limitDatasetCreates(plan: TransferPlan, maxCreates: number | undefined): TransferPlan;
|