@aglyn/plugins-data 1.0.0-beta.246 → 1.0.0-beta.249

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/server/dataset-drafts.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { memberCanSee } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkDatasetQuota, checkEntitlement, checkQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { defaultScopeForNewResource, newResourceScopeFields } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n registerPluginResourceDraftWriter,\n type PluginDraftCheck,\n type PluginDraftContext,\n type PluginDraftRecord,\n type PluginDraftRefusal,\n type PluginDraftWrite,\n type PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { dataStorageRefusal } from '@aglyn/tenant-data-admin/server/data-storage-gate'\nimport {\n memberHasOrgPermission,\n resolveOrgIdForHost,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin/server/organizations'\nimport { isServerReleaseFlagOnForOrg } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n coerceDocumentValues,\n datasetIntegrityFields,\n defaultDatasetFieldId,\n validateDocument,\n type DatasetFieldType,\n type DatasetModel,\n} from '../model/dataset-models'\nimport { DATASET_IMAGE_FIELD_TYPE, isDatasetImageValue } from '../model/dataset-image-field'\nimport {\n RECORD_PAGE_ADDRESS_FIELD_TYPE,\n RECORD_PAGE_ADDRESS_MAX,\n uniqueRecordAddresses,\n} from '../record-pages/record-pages'\n\n/**\n * A DATASET ANOTHER PLUGIN ASKS FOR (AGL-3616).\n *\n * The draft writer this plugin registers for the `dataset` resource on the\n * core's resource-drafts seam. Aglyn AI builds a menu, a team, a list of\n * services or a portfolio as a dataset, the way the docs teach a person to,\n * and asks for the writer by name, so every rule is this plugin's:\n *\n * - THE DOCUMENT is the one the Data card's create makes: a display name,\n * the flat `fields` mirror and a typed model, the org's Default sharing\n * for the site it is made from (`defaultScopeForNewResource`), and the\n * seeded records, each with the integrity and filter index every record\n * write carries. Where a record page is wanted the model gains a \"Page\n * address\" field that fills from a text field, and every seeded record a\n * unique address (`uniqueRecordAddresses`), so the member's Record pages\n * save has nothing left to fill.\n * - THE ROOM is `datasetsPerOrg` (add-ons included, `checkDatasetQuota`),\n * counted INSIDE the transaction that creates, as the datasets route\n * counts it; the records are held to `recordsPerDataset` and the dataset\n * storage band.\n * - THE PLAN is `dataStore` (Starter and up), THE FLAG `release_data_store`,\n * and THE ROLE an org member who may edit org data (owner, admin or\n * editor) with `data.manage`, whose access reaches the site — refused in\n * the route's own words.\n * - THE CHECK holds every record to the model it is written under\n * (`validateDocument`), so what is seeded is what a person could type.\n * - A PHOTO is an `image` field (AGL-3616): text storage riding the\n * \"Image\" custom type, each value an address the site's Image element\n * shows (`isDatasetImageValue`), so a portfolio's pieces or a team keep\n * the photo the page that lists them binds.\n *\n * Nothing is published. The writer touches the one new dataset and its\n * records; a page shows them only where a person (or a job's draft page)\n * places a repeat over it.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The resource name this writer is registered under. */\nexport const DATASET_DRAFT_RESOURCE = 'dataset'\n\n/** The longest dataset name, as the datasets route keeps it. */\nexport const DATASET_DRAFT_NAME_MAX = 120\n/** The most fields a drafted dataset is made with, and the longest name. */\nexport const DATASET_DRAFT_FIELDS_MAX = 16\nexport const DATASET_DRAFT_FIELD_NAME_MAX = 60\n/** The most records a draft is seeded with. */\nexport const DATASET_DRAFT_RECORDS_MAX = 24\n/** The longest text value a seeded record keeps. */\nexport const DATASET_DRAFT_VALUE_MAX = 2_000\n/** The most entries a seeded list value keeps. */\nexport const DATASET_DRAFT_LIST_MAX = 12\n\n/**\n * The field types a caller names, in words a person reads, and the type\n * each one is stored as. A date or a time is written as text the way a\n * visitor reads it (\"Saturday 8 November, 7 pm\"): a stored timestamp renders\n * as a number in a repeat's `{{item.<field>}}`. An image is text holding a\n * photo's address, under the \"Image\" custom type (AGL-3616).\n */\nexport const DATASET_DRAFT_FIELD_TYPES = {\n text: 'text',\n number: 'float',\n integer: 'int32',\n boolean: 'bool',\n list: 'sorted',\n image: 'text',\n} as const satisfies Record<string, DatasetFieldType>\n\nexport type DatasetDraftFieldType = keyof typeof DATASET_DRAFT_FIELD_TYPES\n\nconst DRAFT_TYPES = Object.keys(DATASET_DRAFT_FIELD_TYPES) as DatasetDraftFieldType[]\n\n/** The datasets route's refusals, word for word. */\nexport const DATASET_DRAFT_ROLE_REFUSAL = 'Editing org data requires the editor role'\nexport const DATASET_DRAFT_PERMISSION_REFUSAL = 'Your organization role does not allow editing organization data'\nexport const DATASET_DRAFT_PLAN_REFUSAL = 'Datasets require a Starter plan or higher'\nexport const DATASET_DRAFT_RELEASE_REFUSAL = 'Datasets are not available for this workspace yet'\nexport const DATASET_DRAFT_SCOPE_REFUSAL = 'Your access to this organization does not reach that site'\nexport const DATASET_DRAFT_STORAGE_REFUSAL = 'Dataset storage is full on this plan — upgrade in Billing'\n\n/** The route's refusal at the plan's dataset allowance. */\nexport function datasetDraftLimitRefusal(quota: ReturnType<typeof checkDatasetQuota>): string {\n return quota.upgradeRequired\n ? `Dataset limit reached (${quota.limit}) — upgrade in Billing`\n : `Dataset limit reached (${quota.limit}) — add extra datasets for $${quota.addonPriceUsd}/mo each or upgrade in Billing`\n}\n\n/** The route's refusal at the plan's record allowance. */\nexport function datasetDraftRecordLimitRefusal(limit: number): string {\n return `Record limit reached (${limit}) — upgrade in Billing`\n}\n\n/** One field as a caller names it. */\nexport interface DatasetDraftField {\n name: string\n /** Absent is text. */\n type?: DatasetDraftFieldType\n required?: boolean\n}\n\n/**\n * What a caller sends as `content`. Each record's values are keyed by the\n * field NAME the caller gave, since the caller has no ids yet; the writer\n * mints the ids (`roast_preference` from \"Roast preference\").\n */\nexport interface DatasetDraftContent {\n /** The dataset's name; the request's `name` stands in when absent. */\n name?: string\n fields: DatasetDraftField[]\n records?: Array<Record<string, unknown>>\n /**\n * A text field, by name, a \"Page address\" field fills from (AGL-3475):\n * asked for where a record page will show each record at its own address.\n */\n pageAddressFrom?: string\n}\n\n/** What the writer reports about a dataset. */\nexport interface DatasetDraftFacts {\n fields: Array<{ id: string; name: string; type: DatasetFieldType }>\n records: number\n /** The page-address field's id, where the dataset has one. */\n addressField: string | null\n /** The first photo field's id, where the dataset has one (AGL-3616). */\n imageField: string | null\n}\n\n/** The draft as the writer will store it. */\nexport interface DatasetDraftRead {\n name: string\n model: DatasetModel\n /** Each record's values by field id, coerced and addressed. */\n records: Array<Record<string, unknown>>\n addressField: string | null\n}\n\nexport type DatasetDraftContentRead =\n | { ok: true; value: DatasetDraftRead }\n | { ok: false; problems: string[] }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Text on one line: controls and runs of space folded to one space. */\nfunction line(value: unknown): string {\n return typeof value === 'string' ? value.replace(/[\\p{Cc}\\s]+/gu, ' ').trim() : ''\n}\n\n/** A value as a seeded record keeps it, before the model coerces it. */\nfunction seedValue(value: unknown): unknown {\n if (typeof value === 'string') return value.replace(/\\r\\n?/g, '\\n').trim().slice(0, DATASET_DRAFT_VALUE_MAX)\n if (Array.isArray(value)) {\n return value\n .map((entry) => (typeof entry === 'string' ? line(entry) : entry))\n .filter((entry) => entry !== '' && entry !== null && entry !== undefined)\n .slice(0, DATASET_DRAFT_LIST_MAX)\n }\n return value\n}\n\n/** The content as the writer reads it, or every problem that stops it. Pure. */\nexport function readDatasetDraftContent(content: Readonly<Record<string, unknown>>): DatasetDraftContentRead {\n const problems: string[] = []\n const name = line(content['name'])\n if (!name) problems.push('The dataset needs a name')\n else if (name.length > DATASET_DRAFT_NAME_MAX) {\n problems.push(`The name is longer than ${DATASET_DRAFT_NAME_MAX} characters`)\n }\n\n const rawFields = content['fields']\n const model: DatasetModel = { fields: {}, order: [] }\n /** Field name, lowercased → id. */\n const byName = new Map<string, string>()\n if (!Array.isArray(rawFields) || !rawFields.length) {\n problems.push('The dataset needs at least one field')\n } else if (rawFields.length > DATASET_DRAFT_FIELDS_MAX) {\n problems.push(`A dataset is made with at most ${DATASET_DRAFT_FIELDS_MAX} fields`)\n } else {\n for (const raw of rawFields) {\n const fieldName = isRecord(raw) ? line(raw['name']) : ''\n const rawType = (isRecord(raw) ? raw['type'] : undefined) ?? 'text'\n if (!fieldName) {\n problems.push('Each field needs a name')\n continue\n }\n if (fieldName.length > DATASET_DRAFT_FIELD_NAME_MAX) {\n problems.push(`The field \"${fieldName.slice(0, 20)}…\" has a name longer than ${DATASET_DRAFT_FIELD_NAME_MAX} characters`)\n continue\n }\n if (!(DRAFT_TYPES as readonly unknown[]).includes(rawType)) {\n problems.push(`The field \"${fieldName}\" is one of ${DRAFT_TYPES.join(', ')}`)\n continue\n }\n if (byName.has(fieldName.toLowerCase())) {\n problems.push(`Two fields are both called \"${fieldName}\"`)\n continue\n }\n const id = defaultDatasetFieldId(fieldName, new Set(model.order))\n if (!id) {\n problems.push(`The field \"${fieldName}\" needs a name with a letter in it`)\n continue\n }\n model.fields[id] = {\n name: fieldName,\n type: DATASET_DRAFT_FIELD_TYPES[rawType as DatasetDraftFieldType],\n ...(rawType === 'image' ? { customType: DATASET_IMAGE_FIELD_TYPE } : {}),\n ...(isRecord(raw) && raw['required'] === true ? { required: true } : {}),\n }\n model.order.push(id)\n byName.set(fieldName.toLowerCase(), id)\n }\n }\n\n // The page address: a text field the address fills from, once.\n let addressField: string | null = null\n const rawAddress = content['pageAddressFrom']\n if (rawAddress !== undefined && rawAddress !== null && rawAddress !== '') {\n const sourceId = byName.get(line(rawAddress).toLowerCase())\n if (!sourceId || model.fields[sourceId]?.type !== 'text' || model.fields[sourceId]?.customType) {\n problems.push('A page address fills from one of the dataset’s text fields')\n } else {\n addressField = defaultDatasetFieldId('slug', new Set(model.order))\n model.fields[addressField] = {\n name: 'Page address',\n type: 'text',\n customType: RECORD_PAGE_ADDRESS_FIELD_TYPE,\n slugFrom: sourceId,\n }\n model.order.push(addressField)\n }\n }\n\n const rawRecords = content['records'] ?? []\n const records: Array<Record<string, unknown>> = []\n if (!Array.isArray(rawRecords)) {\n problems.push('Records are a list of values by field name')\n } else if (rawRecords.length > DATASET_DRAFT_RECORDS_MAX) {\n problems.push(`A dataset is seeded with at most ${DATASET_DRAFT_RECORDS_MAX} records`)\n } else if (!problems.length) {\n rawRecords.forEach((raw, index) => {\n if (!isRecord(raw)) {\n problems.push(`Record ${index + 1} is not a set of values`)\n return\n }\n const input: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(raw)) {\n const id = byName.get(line(key).toLowerCase())\n if (!id) {\n problems.push(`Record ${index + 1} names \"${line(key).slice(0, 40)}\", which is not a field`)\n continue\n }\n input[id] = seedValue(value)\n }\n const values = coerceDocumentValues(model, input)\n // An empty value is no value: it is not stored, as the card stores none.\n for (const [id, value] of Object.entries(values)) {\n if (value === '' || value === null || value === undefined || (Array.isArray(value) && !value.length)) {\n delete values[id]\n }\n }\n const errors = validateDocument(model, values)\n // A photo is an address the site's Image shows, whether or not the\n // \"Image\" type's check is registered in this process (AGL-3616).\n for (const [id, value] of Object.entries(values)) {\n const field = model.fields[id]\n if (field?.customType === DATASET_IMAGE_FIELD_TYPE && !errors[id] && !isDatasetImageValue(value)) {\n errors[id] = `${field.name} is not a photo from the media library or an https link`\n }\n }\n for (const error of Object.values(errors)) problems.push(`Record ${index + 1}: ${error}`)\n if (!Object.keys(values).length) problems.push(`Record ${index + 1} has no values`)\n records.push(values)\n })\n }\n if (problems.length) return { ok: false, problems: [...new Set(problems)] }\n\n // Every seeded record its own address, unique within the dataset.\n if (addressField) {\n const source = model.fields[addressField]?.slugFrom as string\n const assigned = uniqueRecordAddresses(records.map((values, index) => ({ id: String(index), source: values[source] })))\n records.forEach((values, index) => {\n const address = assigned.get(String(index))\n if (address) values[addressField as string] = address.slice(0, RECORD_PAGE_ADDRESS_MAX)\n })\n }\n return { ok: true, value: { name, model, records, addressField } }\n}\n\n/** The first photo field of a model, by id. */\nfunction imageFieldOf(model: DatasetModel): string | null {\n return model.order.find((id) => model.fields[id]?.customType === DATASET_IMAGE_FIELD_TYPE) ?? null\n}\n\nfunction factsOf(model: DatasetModel, records: number, addressField: string | null): DatasetDraftFacts {\n return {\n fields: model.order.map((id) => ({ id, name: model.fields[id]?.name ?? id, type: model.fields[id]?.type ?? 'text' })),\n records,\n addressField,\n imageField: imageFieldOf(model),\n }\n}\n\n/** Whether content is a dataset this plugin would store, with what it says about it. Pure. */\nexport function checkDatasetDraftContent(content: Readonly<Record<string, unknown>>): PluginDraftCheck {\n const read = readDatasetDraftContent(content)\n if (read.ok === false) return read\n return {\n ok: true,\n facts: factsOf(read.value.model, read.value.records.length, read.value.addressField) as unknown as Readonly<\n Record<string, unknown>\n >,\n }\n}\n\n/** Who the member is in the org, as the writer asks: their role, and whether they may manage data. */\nexport interface DatasetDraftMember {\n role: string\n canManageData: boolean\n /** The membership itself, for whether their access reaches a site. */\n member: Readonly<Record<string, unknown>>\n}\n\nexport interface DatasetDraftWriterDeps {\n /** The Admin SDK handle; specs hand in a double. */\n firestore?: () => Firestore\n /** The org a site belongs to; the platform's lookup otherwise. */\n orgIdForHost?: (hostId: string) => Promise<string | null>\n /** The member, or `null` when they are not one; the platform's otherwise. */\n member?: (uid: string, orgId: string) => Promise<DatasetDraftMember | null>\n /** Whether the data store is released for the org; the platform's flag otherwise. */\n released?: (orgId: string) => Promise<boolean>\n /** Whether dataset bytes may grow; the platform's band otherwise. */\n storageFull?: (org: unknown, orgRef: FirebaseFirestore.DocumentReference) => Promise<boolean>\n}\n\n/** Roles allowed to create org data — the datasets route's, mirroring the rules' `canWriteOrgData()`. */\nconst WRITER_ROLES = new Set(['owner', 'admin', 'editor'])\n\nasync function platformMember(uid: string, orgId: string): Promise<DatasetDraftMember | null> {\n const membership = await resolveOrgMembership(uid, orgId)\n const member = membership?.member\n if (!member) return null\n return {\n role: String(member.role ?? ''),\n canManageData: await memberHasOrgPermission(orgId, member, 'data.manage'),\n member: member as unknown as Readonly<Record<string, unknown>>,\n }\n}\n\nexport function createDatasetDraftWriter(deps: DatasetDraftWriterDeps = {}): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const orgIdForHost = deps.orgIdForHost ?? ((hostId: string) => resolveOrgIdForHost(hostId))\n const memberOf = deps.member ?? platformMember\n const released = deps.released ?? ((orgId: string) => isServerReleaseFlagOnForOrg('release_data_store', orgId))\n const storageFull =\n deps.storageFull ?? (async (org: unknown, orgRef: FirebaseFirestore.DocumentReference) => Boolean(await dataStorageRefusal(org, orgRef)))\n\n /** Everything but the room: the site is the org's, the member's role, the flag, the plan, the scope. */\n const early = async (context: PluginDraftContext): Promise<PluginDraftRefusal | null> => {\n if ((await orgIdForHost(context.hostId)) !== context.orgId) return { status: 404, error: 'Unknown site' }\n const member = await memberOf(context.uid, context.orgId)\n if (!member || !WRITER_ROLES.has(member.role)) return { status: 403, error: DATASET_DRAFT_ROLE_REFUSAL }\n if (!member.canManageData) return { status: 403, error: DATASET_DRAFT_PERMISSION_REFUSAL }\n if (!(await released(context.orgId))) return { status: 403, error: DATASET_DRAFT_RELEASE_REFUSAL }\n if (!checkEntitlement(context.org as never, 'dataStore')) return { status: 403, error: DATASET_DRAFT_PLAN_REFUSAL }\n // A member scoped to some sites must be able to see what they create.\n if (!memberCanSee(member.member as never, scopeOf(context))) {\n return { status: 403, error: DATASET_DRAFT_SCOPE_REFUSAL }\n }\n return null\n }\n\n return {\n refusal: async (context) => {\n const refused = await early(context)\n if (refused) return refused\n const used = (await firestore().collection('orgs').doc(context.orgId).collection('datasets').count().get()).data().count\n const quota = checkDatasetQuota(context.org as never, Number(used) || 0)\n return quota.allowed ? null : { status: 403, error: datasetDraftLimitRefusal(quota) }\n },\n\n check: (content) => checkDatasetDraftContent(content),\n\n read: async ({ hostId, id }) => {\n const orgId = await orgIdForHost(hostId)\n if (!orgId) return null\n const dataset = await firestore().collection('orgs').doc(orgId).collection('datasets').doc(id).get()\n return dataset.exists ? recordOf(dataset) : null\n },\n\n write: async (request): Promise<PluginDraftWrite> => {\n // The request's name is the one asked for; the content's stands in.\n const asked = line(request.name)\n const read = readDatasetDraftContent(asked ? { ...request.content, name: asked } : request.content)\n if (read.ok === false) return { ok: false, status: 400, error: read.problems[0] }\n const refused = await early(request)\n if (refused) return { ok: false, ...refused }\n const db = firestore()\n const orgRef = db.collection('orgs').doc(request.orgId)\n const datasets = orgRef.collection('datasets')\n const datasetRef = datasets.doc(request.id)\n const { name, model, records, addressField } = read.value\n const recordQuota = checkQuota(request.org as never, 'recordsPerDataset', Math.max(0, records.length - 1))\n if (records.length && !recordQuota.allowed) {\n return { ok: false, status: 403, error: datasetDraftRecordLimitRefusal(recordQuota.limit) }\n }\n if (records.length && (await storageFull(request.org, orgRef))) {\n return { ok: false, status: 403, error: DATASET_DRAFT_STORAGE_REFUSAL }\n }\n const visibleTo = scopeOf(request)\n // Minted before the transaction, so a retried attempt writes the same rows.\n const recordIds = records.map(() => createResourceUid())\n return db.runTransaction(async (tx): Promise<PluginDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const existing = await tx.get(datasetRef)\n // Asked again under the same id: the dataset it already wrote.\n if (existing.exists) return { ok: true, replayed: true, ...recordOf(existing) }\n const live = (await tx.get(datasets.count())).data().count\n const quota = checkDatasetQuota(request.org as never, Number(live) || 0)\n if (!quota.allowed) return { ok: false, status: 403, error: datasetDraftLimitRefusal(quota) }\n tx.create(datasetRef, {\n displayName: name,\n fields: [...model.order],\n model,\n ...newResourceScopeFields(visibleTo),\n createdAt: request.now,\n createdBy: request.uid,\n })\n records.forEach((values, order) => {\n tx.create(datasetRef.collection('records').doc(recordIds[order]), {\n values,\n ...datasetIntegrityFields(model, values),\n order,\n createdAt: request.now,\n updatedAt: request.now,\n })\n })\n return {\n ok: true,\n replayed: false,\n id: request.id,\n name,\n versionId: null,\n facts: factsOf(model, records.length, addressField) as unknown as Readonly<Record<string, unknown>>,\n }\n })\n },\n }\n}\n\n/** The scope a dataset made from this site takes: the org's Default sharing for it. */\nfunction scopeOf(context: Pick<PluginDraftContext, 'org' | 'hostId'>) {\n return defaultScopeForNewResource({\n defaultResourceScope: (context.org as { defaultResourceScope?: 'org' | 'host' } | null)?.defaultResourceScope,\n hostId: context.hostId,\n })\n}\n\nfunction recordOf(dataset: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n const data = (dataset.data() ?? {}) as { displayName?: unknown; model?: DatasetModel }\n const model: DatasetModel = data.model?.order ? data.model : { fields: {}, order: [] }\n const addressField = model.order.find((id) => model.fields[id]?.customType === RECORD_PAGE_ADDRESS_FIELD_TYPE) ?? null\n return {\n id: dataset.id,\n name: String(data.displayName ?? ''),\n versionId: null,\n // A replay reports the fields; how many records it was seeded with is the first write's to say.\n facts: factsOf(model, 0, addressField) as unknown as Readonly<Record<string, unknown>>,\n }\n}\n\nexport const datasetDraftWriter = createDatasetDraftWriter()\n\n/**\n * Registers the writer; the console surface calls it, since only the console\n * runs AI jobs (AGL-3026). Idempotent: a second call replaces the first.\n */\nexport function registerDatasetDraftWriter(): void {\n registerPluginResourceDraftWriter(DATASET_DRAFT_RESOURCE, datasetDraftWriter, { pluginId: BUNDLE_ID })\n}\n"],"names":["createResourceUid","memberCanSee","checkDatasetQuota","checkEntitlement","checkQuota","defaultScopeForNewResource","newResourceScopeFields","registerPluginResourceDraftWriter","firebaseAdmin","dataStorageRefusal","memberHasOrgPermission","resolveOrgIdForHost","resolveOrgMembership","isServerReleaseFlagOnForOrg","BUNDLE_ID","coerceDocumentValues","datasetIntegrityFields","defaultDatasetFieldId","validateDocument","DATASET_IMAGE_FIELD_TYPE","isDatasetImageValue","RECORD_PAGE_ADDRESS_FIELD_TYPE","RECORD_PAGE_ADDRESS_MAX","uniqueRecordAddresses","DATASET_DRAFT_RESOURCE","DATASET_DRAFT_NAME_MAX","DATASET_DRAFT_FIELDS_MAX","DATASET_DRAFT_FIELD_NAME_MAX","DATASET_DRAFT_RECORDS_MAX","DATASET_DRAFT_VALUE_MAX","DATASET_DRAFT_LIST_MAX","DATASET_DRAFT_FIELD_TYPES","text","number","integer","boolean","list","image","DRAFT_TYPES","Object","keys","DATASET_DRAFT_ROLE_REFUSAL","DATASET_DRAFT_PERMISSION_REFUSAL","DATASET_DRAFT_PLAN_REFUSAL","DATASET_DRAFT_RELEASE_REFUSAL","DATASET_DRAFT_SCOPE_REFUSAL","DATASET_DRAFT_STORAGE_REFUSAL","datasetDraftLimitRefusal","quota","upgradeRequired","limit","addonPriceUsd","datasetDraftRecordLimitRefusal","isRecord","value","Array","isArray","line","replace","trim","seedValue","slice","map","entry","filter","undefined","readDatasetDraftContent","content","problems","name","push","length","rawFields","model","fields","order","byName","Map","raw","fieldName","rawType","includes","join","has","toLowerCase","id","Set","type","customType","required","set","addressField","rawAddress","sourceId","get","slugFrom","rawRecords","records","forEach","index","input","key","entries","values","errors","field","error","ok","source","assigned","String","address","imageFieldOf","find","factsOf","imageField","checkDatasetDraftContent","read","facts","WRITER_ROLES","platformMember","uid","orgId","member","membership","role","canManageData","createDatasetDraftWriter","deps","firestore","app","orgIdForHost","hostId","memberOf","released","storageFull","org","orgRef","Boolean","early","context","status","scopeOf","refusal","refused","used","collection","doc","count","data","Number","allowed","check","dataset","exists","recordOf","write","request","asked","db","datasets","datasetRef","recordQuota","Math","max","visibleTo","recordIds","runTransaction","tx","existing","replayed","live","create","displayName","createdAt","now","createdBy","updatedAt","versionId","defaultResourceScope","datasetDraftWriter","registerDatasetDraftWriter","pluginId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/server/dataset-drafts.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { memberCanSee } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkDatasetQuota, checkEntitlement, checkQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { defaultScopeForNewResource, newResourceScopeFields } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n registerPluginResourceDraftWriter,\n type PluginDraftCheck,\n type PluginDraftContext,\n type PluginDraftRecord,\n type PluginDraftRefusal,\n type PluginDraftWrite,\n type PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { dataStorageRefusal } from '@aglyn/tenant-data-admin/server/data-storage-gate'\nimport {\n memberHasOrgPermission,\n resolveOrgIdForHost,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin/server/organizations'\nimport { isServerReleaseFlagOnForOrg } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n coerceDocumentValues,\n datasetIntegrityFields,\n defaultDatasetFieldId,\n validateDocument,\n type DatasetFieldType,\n type DatasetModel,\n} from '../model/dataset-models'\nimport { DATASET_IMAGE_FIELD_TYPE, isDatasetImageValue } from '../model/dataset-image-field'\nimport { DATASET_PAGE_FIELD_TYPE, isDatasetPageValue } from '../model/dataset-page-field'\nimport {\n RECORD_PAGE_ADDRESS_FIELD_TYPE,\n RECORD_PAGE_ADDRESS_MAX,\n uniqueRecordAddresses,\n} from '../record-pages/record-pages'\n\n/**\n * A DATASET ANOTHER PLUGIN ASKS FOR (AGL-3616).\n *\n * The draft writer this plugin registers for the `dataset` resource on the\n * core's resource-drafts seam. Aglyn AI builds a menu, a team, a list of\n * services or a portfolio as a dataset, the way the docs teach a person to,\n * and asks for the writer by name, so every rule is this plugin's:\n *\n * - THE DOCUMENT is the one the Data card's create makes: a display name,\n * the flat `fields` mirror and a typed model, the org's Default sharing\n * for the site it is made from (`defaultScopeForNewResource`), and the\n * seeded records, each with the integrity and filter index every record\n * write carries. Where a record page is wanted the model gains a \"Page\n * address\" field that fills from a text field, and every seeded record a\n * unique address (`uniqueRecordAddresses`), so the member's Record pages\n * save has nothing left to fill.\n * - THE ROOM is `datasetsPerOrg` (add-ons included, `checkDatasetQuota`),\n * counted INSIDE the transaction that creates, as the datasets route\n * counts it; the records are held to `recordsPerDataset` and the dataset\n * storage band.\n * - THE PLAN is `dataStore` (Starter and up), THE FLAG `release_data_store`,\n * and THE ROLE an org member who may edit org data (owner, admin or\n * editor) with `data.manage`, whose access reaches the site — refused in\n * the route's own words.\n * - THE CHECK holds every record to the model it is written under\n * (`validateDocument`), so what is seeded is what a person could type.\n * - A PHOTO is an `image` field (AGL-3616): text storage riding the\n * \"Image\" custom type, each value an address the site's Image element\n * shows (`isDatasetImageValue`), so a portfolio's pieces or a team keep\n * the photo the page that lists them binds.\n * - A PAGE is a `page` field (AGL-3763): text storage riding the \"Page\"\n * custom type, each value one of the site's pages as `screen:{id}`\n * (`isDatasetPageValue`), which a Button or Link binds.\n *\n * Nothing is published. The writer touches the one new dataset and its\n * records; a page shows them only where a person (or a job's draft page)\n * places a repeat over it.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The resource name this writer is registered under. */\nexport const DATASET_DRAFT_RESOURCE = 'dataset'\n\n/** The longest dataset name, as the datasets route keeps it. */\nexport const DATASET_DRAFT_NAME_MAX = 120\n/** The most fields a drafted dataset is made with, and the longest name. */\nexport const DATASET_DRAFT_FIELDS_MAX = 16\nexport const DATASET_DRAFT_FIELD_NAME_MAX = 60\n/** The most records a draft is seeded with. */\nexport const DATASET_DRAFT_RECORDS_MAX = 24\n/** The longest text value a seeded record keeps. */\nexport const DATASET_DRAFT_VALUE_MAX = 2_000\n/** The most entries a seeded list value keeps. */\nexport const DATASET_DRAFT_LIST_MAX = 12\n\n/**\n * The field types a caller names, in words a person reads, and the type\n * each one is stored as. A date or a time is written as text the way a\n * visitor reads it (\"Saturday 8 November, 7 pm\"): a stored timestamp renders\n * as a number in a repeat's `{{item.<field>}}`. An image is text holding a\n * photo's address, under the \"Image\" custom type (AGL-3616), and a page is\n * text holding a page's reference, under the \"Page\" custom type (AGL-3763).\n */\nexport const DATASET_DRAFT_FIELD_TYPES = {\n text: 'text',\n number: 'float',\n integer: 'int32',\n boolean: 'bool',\n list: 'sorted',\n image: 'text',\n page: 'text',\n} as const satisfies Record<string, DatasetFieldType>\n\nexport type DatasetDraftFieldType = keyof typeof DATASET_DRAFT_FIELD_TYPES\n\nconst DRAFT_TYPES = Object.keys(DATASET_DRAFT_FIELD_TYPES) as DatasetDraftFieldType[]\n\n/** The datasets route's refusals, word for word. */\nexport const DATASET_DRAFT_ROLE_REFUSAL = 'Editing org data requires the editor role'\nexport const DATASET_DRAFT_PERMISSION_REFUSAL = 'Your organization role does not allow editing organization data'\nexport const DATASET_DRAFT_PLAN_REFUSAL = 'Datasets require a Starter plan or higher'\nexport const DATASET_DRAFT_RELEASE_REFUSAL = 'Datasets are not available for this workspace yet'\nexport const DATASET_DRAFT_SCOPE_REFUSAL = 'Your access to this organization does not reach that site'\nexport const DATASET_DRAFT_STORAGE_REFUSAL = 'Dataset storage is full on this plan — upgrade in Billing'\n\n/** The route's refusal at the plan's dataset allowance. */\nexport function datasetDraftLimitRefusal(quota: ReturnType<typeof checkDatasetQuota>): string {\n return quota.upgradeRequired\n ? `Dataset limit reached (${quota.limit}) — upgrade in Billing`\n : `Dataset limit reached (${quota.limit}) — add extra datasets for $${quota.addonPriceUsd}/mo each or upgrade in Billing`\n}\n\n/** The route's refusal at the plan's record allowance. */\nexport function datasetDraftRecordLimitRefusal(limit: number): string {\n return `Record limit reached (${limit}) — upgrade in Billing`\n}\n\n/** One field as a caller names it. */\nexport interface DatasetDraftField {\n name: string\n /** Absent is text. */\n type?: DatasetDraftFieldType\n required?: boolean\n}\n\n/**\n * What a caller sends as `content`. Each record's values are keyed by the\n * field NAME the caller gave, since the caller has no ids yet; the writer\n * mints the ids (`roast_preference` from \"Roast preference\").\n */\nexport interface DatasetDraftContent {\n /** The dataset's name; the request's `name` stands in when absent. */\n name?: string\n fields: DatasetDraftField[]\n records?: Array<Record<string, unknown>>\n /**\n * A text field, by name, a \"Page address\" field fills from (AGL-3475):\n * asked for where a record page will show each record at its own address.\n */\n pageAddressFrom?: string\n}\n\n/** What the writer reports about a dataset. */\nexport interface DatasetDraftFacts {\n fields: Array<{ id: string; name: string; type: DatasetFieldType }>\n records: number\n /** The page-address field's id, where the dataset has one. */\n addressField: string | null\n /** The first photo field's id, where the dataset has one (AGL-3616). */\n imageField: string | null\n}\n\n/** The draft as the writer will store it. */\nexport interface DatasetDraftRead {\n name: string\n model: DatasetModel\n /** Each record's values by field id, coerced and addressed. */\n records: Array<Record<string, unknown>>\n addressField: string | null\n}\n\nexport type DatasetDraftContentRead =\n | { ok: true; value: DatasetDraftRead }\n | { ok: false; problems: string[] }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Text on one line: controls and runs of space folded to one space. */\nfunction line(value: unknown): string {\n return typeof value === 'string' ? value.replace(/[\\p{Cc}\\s]+/gu, ' ').trim() : ''\n}\n\n/** A value as a seeded record keeps it, before the model coerces it. */\nfunction seedValue(value: unknown): unknown {\n if (typeof value === 'string') return value.replace(/\\r\\n?/g, '\\n').trim().slice(0, DATASET_DRAFT_VALUE_MAX)\n if (Array.isArray(value)) {\n return value\n .map((entry) => (typeof entry === 'string' ? line(entry) : entry))\n .filter((entry) => entry !== '' && entry !== null && entry !== undefined)\n .slice(0, DATASET_DRAFT_LIST_MAX)\n }\n return value\n}\n\n/** The content as the writer reads it, or every problem that stops it. Pure. */\nexport function readDatasetDraftContent(content: Readonly<Record<string, unknown>>): DatasetDraftContentRead {\n const problems: string[] = []\n const name = line(content['name'])\n if (!name) problems.push('The dataset needs a name')\n else if (name.length > DATASET_DRAFT_NAME_MAX) {\n problems.push(`The name is longer than ${DATASET_DRAFT_NAME_MAX} characters`)\n }\n\n const rawFields = content['fields']\n const model: DatasetModel = { fields: {}, order: [] }\n /** Field name, lowercased → id. */\n const byName = new Map<string, string>()\n if (!Array.isArray(rawFields) || !rawFields.length) {\n problems.push('The dataset needs at least one field')\n } else if (rawFields.length > DATASET_DRAFT_FIELDS_MAX) {\n problems.push(`A dataset is made with at most ${DATASET_DRAFT_FIELDS_MAX} fields`)\n } else {\n for (const raw of rawFields) {\n const fieldName = isRecord(raw) ? line(raw['name']) : ''\n const rawType = (isRecord(raw) ? raw['type'] : undefined) ?? 'text'\n if (!fieldName) {\n problems.push('Each field needs a name')\n continue\n }\n if (fieldName.length > DATASET_DRAFT_FIELD_NAME_MAX) {\n problems.push(`The field \"${fieldName.slice(0, 20)}…\" has a name longer than ${DATASET_DRAFT_FIELD_NAME_MAX} characters`)\n continue\n }\n if (!(DRAFT_TYPES as readonly unknown[]).includes(rawType)) {\n problems.push(`The field \"${fieldName}\" is one of ${DRAFT_TYPES.join(', ')}`)\n continue\n }\n if (byName.has(fieldName.toLowerCase())) {\n problems.push(`Two fields are both called \"${fieldName}\"`)\n continue\n }\n const id = defaultDatasetFieldId(fieldName, new Set(model.order))\n if (!id) {\n problems.push(`The field \"${fieldName}\" needs a name with a letter in it`)\n continue\n }\n model.fields[id] = {\n name: fieldName,\n type: DATASET_DRAFT_FIELD_TYPES[rawType as DatasetDraftFieldType],\n ...(rawType === 'image' ? { customType: DATASET_IMAGE_FIELD_TYPE } : {}),\n ...(rawType === 'page' ? { customType: DATASET_PAGE_FIELD_TYPE } : {}),\n ...(isRecord(raw) && raw['required'] === true ? { required: true } : {}),\n }\n model.order.push(id)\n byName.set(fieldName.toLowerCase(), id)\n }\n }\n\n // The page address: a text field the address fills from, once.\n let addressField: string | null = null\n const rawAddress = content['pageAddressFrom']\n if (rawAddress !== undefined && rawAddress !== null && rawAddress !== '') {\n const sourceId = byName.get(line(rawAddress).toLowerCase())\n if (!sourceId || model.fields[sourceId]?.type !== 'text' || model.fields[sourceId]?.customType) {\n problems.push('A page address fills from one of the dataset’s text fields')\n } else {\n addressField = defaultDatasetFieldId('slug', new Set(model.order))\n model.fields[addressField] = {\n name: 'Page address',\n type: 'text',\n customType: RECORD_PAGE_ADDRESS_FIELD_TYPE,\n slugFrom: sourceId,\n }\n model.order.push(addressField)\n }\n }\n\n const rawRecords = content['records'] ?? []\n const records: Array<Record<string, unknown>> = []\n if (!Array.isArray(rawRecords)) {\n problems.push('Records are a list of values by field name')\n } else if (rawRecords.length > DATASET_DRAFT_RECORDS_MAX) {\n problems.push(`A dataset is seeded with at most ${DATASET_DRAFT_RECORDS_MAX} records`)\n } else if (!problems.length) {\n rawRecords.forEach((raw, index) => {\n if (!isRecord(raw)) {\n problems.push(`Record ${index + 1} is not a set of values`)\n return\n }\n const input: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(raw)) {\n const id = byName.get(line(key).toLowerCase())\n if (!id) {\n problems.push(`Record ${index + 1} names \"${line(key).slice(0, 40)}\", which is not a field`)\n continue\n }\n input[id] = seedValue(value)\n }\n const values = coerceDocumentValues(model, input)\n // An empty value is no value: it is not stored, as the card stores none.\n for (const [id, value] of Object.entries(values)) {\n if (value === '' || value === null || value === undefined || (Array.isArray(value) && !value.length)) {\n delete values[id]\n }\n }\n const errors = validateDocument(model, values)\n // A photo is an address the site's Image shows, whether or not the\n // \"Image\" type's check is registered in this process (AGL-3616).\n for (const [id, value] of Object.entries(values)) {\n const field = model.fields[id]\n if (field?.customType === DATASET_IMAGE_FIELD_TYPE && !errors[id] && !isDatasetImageValue(value)) {\n errors[id] = `${field.name} is not a photo from the media library or an https link`\n }\n // …and a page is one of the site's pages, by reference (AGL-3763).\n if (field?.customType === DATASET_PAGE_FIELD_TYPE && !errors[id] && !isDatasetPageValue(value)) {\n errors[id] = `${field.name} is not one of the site’s pages`\n }\n }\n for (const error of Object.values(errors)) problems.push(`Record ${index + 1}: ${error}`)\n if (!Object.keys(values).length) problems.push(`Record ${index + 1} has no values`)\n records.push(values)\n })\n }\n if (problems.length) return { ok: false, problems: [...new Set(problems)] }\n\n // Every seeded record its own address, unique within the dataset.\n if (addressField) {\n const source = model.fields[addressField]?.slugFrom as string\n const assigned = uniqueRecordAddresses(records.map((values, index) => ({ id: String(index), source: values[source] })))\n records.forEach((values, index) => {\n const address = assigned.get(String(index))\n if (address) values[addressField as string] = address.slice(0, RECORD_PAGE_ADDRESS_MAX)\n })\n }\n return { ok: true, value: { name, model, records, addressField } }\n}\n\n/** The first photo field of a model, by id. */\nfunction imageFieldOf(model: DatasetModel): string | null {\n return model.order.find((id) => model.fields[id]?.customType === DATASET_IMAGE_FIELD_TYPE) ?? null\n}\n\nfunction factsOf(model: DatasetModel, records: number, addressField: string | null): DatasetDraftFacts {\n return {\n fields: model.order.map((id) => ({ id, name: model.fields[id]?.name ?? id, type: model.fields[id]?.type ?? 'text' })),\n records,\n addressField,\n imageField: imageFieldOf(model),\n }\n}\n\n/** Whether content is a dataset this plugin would store, with what it says about it. Pure. */\nexport function checkDatasetDraftContent(content: Readonly<Record<string, unknown>>): PluginDraftCheck {\n const read = readDatasetDraftContent(content)\n if (read.ok === false) return read\n return {\n ok: true,\n facts: factsOf(read.value.model, read.value.records.length, read.value.addressField) as unknown as Readonly<\n Record<string, unknown>\n >,\n }\n}\n\n/** Who the member is in the org, as the writer asks: their role, and whether they may manage data. */\nexport interface DatasetDraftMember {\n role: string\n canManageData: boolean\n /** The membership itself, for whether their access reaches a site. */\n member: Readonly<Record<string, unknown>>\n}\n\nexport interface DatasetDraftWriterDeps {\n /** The Admin SDK handle; specs hand in a double. */\n firestore?: () => Firestore\n /** The org a site belongs to; the platform's lookup otherwise. */\n orgIdForHost?: (hostId: string) => Promise<string | null>\n /** The member, or `null` when they are not one; the platform's otherwise. */\n member?: (uid: string, orgId: string) => Promise<DatasetDraftMember | null>\n /** Whether the data store is released for the org; the platform's flag otherwise. */\n released?: (orgId: string) => Promise<boolean>\n /** Whether dataset bytes may grow; the platform's band otherwise. */\n storageFull?: (org: unknown, orgRef: FirebaseFirestore.DocumentReference) => Promise<boolean>\n}\n\n/** Roles allowed to create org data — the datasets route's, mirroring the rules' `canWriteOrgData()`. */\nconst WRITER_ROLES = new Set(['owner', 'admin', 'editor'])\n\nasync function platformMember(uid: string, orgId: string): Promise<DatasetDraftMember | null> {\n const membership = await resolveOrgMembership(uid, orgId)\n const member = membership?.member\n if (!member) return null\n return {\n role: String(member.role ?? ''),\n canManageData: await memberHasOrgPermission(orgId, member, 'data.manage'),\n member: member as unknown as Readonly<Record<string, unknown>>,\n }\n}\n\nexport function createDatasetDraftWriter(deps: DatasetDraftWriterDeps = {}): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const orgIdForHost = deps.orgIdForHost ?? ((hostId: string) => resolveOrgIdForHost(hostId))\n const memberOf = deps.member ?? platformMember\n const released = deps.released ?? ((orgId: string) => isServerReleaseFlagOnForOrg('release_data_store', orgId))\n const storageFull =\n deps.storageFull ?? (async (org: unknown, orgRef: FirebaseFirestore.DocumentReference) => Boolean(await dataStorageRefusal(org, orgRef)))\n\n /** Everything but the room: the site is the org's, the member's role, the flag, the plan, the scope. */\n const early = async (context: PluginDraftContext): Promise<PluginDraftRefusal | null> => {\n if ((await orgIdForHost(context.hostId)) !== context.orgId) return { status: 404, error: 'Unknown site' }\n const member = await memberOf(context.uid, context.orgId)\n if (!member || !WRITER_ROLES.has(member.role)) return { status: 403, error: DATASET_DRAFT_ROLE_REFUSAL }\n if (!member.canManageData) return { status: 403, error: DATASET_DRAFT_PERMISSION_REFUSAL }\n if (!(await released(context.orgId))) return { status: 403, error: DATASET_DRAFT_RELEASE_REFUSAL }\n if (!checkEntitlement(context.org as never, 'dataStore')) return { status: 403, error: DATASET_DRAFT_PLAN_REFUSAL }\n // A member scoped to some sites must be able to see what they create.\n if (!memberCanSee(member.member as never, scopeOf(context))) {\n return { status: 403, error: DATASET_DRAFT_SCOPE_REFUSAL }\n }\n return null\n }\n\n return {\n refusal: async (context) => {\n const refused = await early(context)\n if (refused) return refused\n const used = (await firestore().collection('orgs').doc(context.orgId).collection('datasets').count().get()).data().count\n const quota = checkDatasetQuota(context.org as never, Number(used) || 0)\n return quota.allowed ? null : { status: 403, error: datasetDraftLimitRefusal(quota) }\n },\n\n check: (content) => checkDatasetDraftContent(content),\n\n read: async ({ hostId, id }) => {\n const orgId = await orgIdForHost(hostId)\n if (!orgId) return null\n const dataset = await firestore().collection('orgs').doc(orgId).collection('datasets').doc(id).get()\n return dataset.exists ? recordOf(dataset) : null\n },\n\n write: async (request): Promise<PluginDraftWrite> => {\n // The request's name is the one asked for; the content's stands in.\n const asked = line(request.name)\n const read = readDatasetDraftContent(asked ? { ...request.content, name: asked } : request.content)\n if (read.ok === false) return { ok: false, status: 400, error: read.problems[0] }\n const refused = await early(request)\n if (refused) return { ok: false, ...refused }\n const db = firestore()\n const orgRef = db.collection('orgs').doc(request.orgId)\n const datasets = orgRef.collection('datasets')\n const datasetRef = datasets.doc(request.id)\n const { name, model, records, addressField } = read.value\n const recordQuota = checkQuota(request.org as never, 'recordsPerDataset', Math.max(0, records.length - 1))\n if (records.length && !recordQuota.allowed) {\n return { ok: false, status: 403, error: datasetDraftRecordLimitRefusal(recordQuota.limit) }\n }\n if (records.length && (await storageFull(request.org, orgRef))) {\n return { ok: false, status: 403, error: DATASET_DRAFT_STORAGE_REFUSAL }\n }\n const visibleTo = scopeOf(request)\n // Minted before the transaction, so a retried attempt writes the same rows.\n const recordIds = records.map(() => createResourceUid())\n return db.runTransaction(async (tx): Promise<PluginDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const existing = await tx.get(datasetRef)\n // Asked again under the same id: the dataset it already wrote.\n if (existing.exists) return { ok: true, replayed: true, ...recordOf(existing) }\n const live = (await tx.get(datasets.count())).data().count\n const quota = checkDatasetQuota(request.org as never, Number(live) || 0)\n if (!quota.allowed) return { ok: false, status: 403, error: datasetDraftLimitRefusal(quota) }\n tx.create(datasetRef, {\n displayName: name,\n fields: [...model.order],\n model,\n ...newResourceScopeFields(visibleTo),\n createdAt: request.now,\n createdBy: request.uid,\n })\n records.forEach((values, order) => {\n tx.create(datasetRef.collection('records').doc(recordIds[order]), {\n values,\n ...datasetIntegrityFields(model, values),\n order,\n createdAt: request.now,\n updatedAt: request.now,\n })\n })\n return {\n ok: true,\n replayed: false,\n id: request.id,\n name,\n versionId: null,\n facts: factsOf(model, records.length, addressField) as unknown as Readonly<Record<string, unknown>>,\n }\n })\n },\n }\n}\n\n/** The scope a dataset made from this site takes: the org's Default sharing for it. */\nfunction scopeOf(context: Pick<PluginDraftContext, 'org' | 'hostId'>) {\n return defaultScopeForNewResource({\n defaultResourceScope: (context.org as { defaultResourceScope?: 'org' | 'host' } | null)?.defaultResourceScope,\n hostId: context.hostId,\n })\n}\n\nfunction recordOf(dataset: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n const data = (dataset.data() ?? {}) as { displayName?: unknown; model?: DatasetModel }\n const model: DatasetModel = data.model?.order ? data.model : { fields: {}, order: [] }\n const addressField = model.order.find((id) => model.fields[id]?.customType === RECORD_PAGE_ADDRESS_FIELD_TYPE) ?? null\n return {\n id: dataset.id,\n name: String(data.displayName ?? ''),\n versionId: null,\n // A replay reports the fields; how many records it was seeded with is the first write's to say.\n facts: factsOf(model, 0, addressField) as unknown as Readonly<Record<string, unknown>>,\n }\n}\n\nexport const datasetDraftWriter = createDatasetDraftWriter()\n\n/**\n * Registers the writer; the console surface calls it, since only the console\n * runs AI jobs (AGL-3026). Idempotent: a second call replaces the first.\n */\nexport function registerDatasetDraftWriter(): void {\n registerPluginResourceDraftWriter(DATASET_DRAFT_RESOURCE, datasetDraftWriter, { pluginId: BUNDLE_ID })\n}\n"],"names":["createResourceUid","memberCanSee","checkDatasetQuota","checkEntitlement","checkQuota","defaultScopeForNewResource","newResourceScopeFields","registerPluginResourceDraftWriter","firebaseAdmin","dataStorageRefusal","memberHasOrgPermission","resolveOrgIdForHost","resolveOrgMembership","isServerReleaseFlagOnForOrg","BUNDLE_ID","coerceDocumentValues","datasetIntegrityFields","defaultDatasetFieldId","validateDocument","DATASET_IMAGE_FIELD_TYPE","isDatasetImageValue","DATASET_PAGE_FIELD_TYPE","isDatasetPageValue","RECORD_PAGE_ADDRESS_FIELD_TYPE","RECORD_PAGE_ADDRESS_MAX","uniqueRecordAddresses","DATASET_DRAFT_RESOURCE","DATASET_DRAFT_NAME_MAX","DATASET_DRAFT_FIELDS_MAX","DATASET_DRAFT_FIELD_NAME_MAX","DATASET_DRAFT_RECORDS_MAX","DATASET_DRAFT_VALUE_MAX","DATASET_DRAFT_LIST_MAX","DATASET_DRAFT_FIELD_TYPES","text","number","integer","boolean","list","image","page","DRAFT_TYPES","Object","keys","DATASET_DRAFT_ROLE_REFUSAL","DATASET_DRAFT_PERMISSION_REFUSAL","DATASET_DRAFT_PLAN_REFUSAL","DATASET_DRAFT_RELEASE_REFUSAL","DATASET_DRAFT_SCOPE_REFUSAL","DATASET_DRAFT_STORAGE_REFUSAL","datasetDraftLimitRefusal","quota","upgradeRequired","limit","addonPriceUsd","datasetDraftRecordLimitRefusal","isRecord","value","Array","isArray","line","replace","trim","seedValue","slice","map","entry","filter","undefined","readDatasetDraftContent","content","problems","name","push","length","rawFields","model","fields","order","byName","Map","raw","fieldName","rawType","includes","join","has","toLowerCase","id","Set","type","customType","required","set","addressField","rawAddress","sourceId","get","slugFrom","rawRecords","records","forEach","index","input","key","entries","values","errors","field","error","ok","source","assigned","String","address","imageFieldOf","find","factsOf","imageField","checkDatasetDraftContent","read","facts","WRITER_ROLES","platformMember","uid","orgId","member","membership","role","canManageData","createDatasetDraftWriter","deps","firestore","app","orgIdForHost","hostId","memberOf","released","storageFull","org","orgRef","Boolean","early","context","status","scopeOf","refusal","refused","used","collection","doc","count","data","Number","allowed","check","dataset","exists","recordOf","write","request","asked","db","datasets","datasetRef","recordQuota","Math","max","visibleTo","recordIds","runTransaction","tx","existing","replayed","live","create","displayName","createdAt","now","createdBy","updatedAt","versionId","defaultResourceScope","datasetDraftWriter","registerDatasetDraftWriter","pluginId"],"mappings":"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"}