@nonext/husk 0.2.3
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/CHANGELOG.md +148 -0
- package/LICENSE +166 -0
- package/README.md +140 -0
- package/api/public-api.md +1070 -0
- package/dist/adapter-C_0mhENy.d.ts +320 -0
- package/dist/admin/admin.css +5 -0
- package/dist/admin/index.d.ts +265 -0
- package/dist/admin/index.js +393 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/tokens.css +597 -0
- package/dist/admin/ui.d.ts +3013 -0
- package/dist/admin/ui.js +9783 -0
- package/dist/admin/ui.js.map +1 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.js +9 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth-B2gBtHl5.d.ts +28 -0
- package/dist/can-Chy7zmx7.d.ts +69 -0
- package/dist/chunk-3I6337WJ.js +83 -0
- package/dist/chunk-3I6337WJ.js.map +1 -0
- package/dist/chunk-4Z6P3XCE.js +62 -0
- package/dist/chunk-4Z6P3XCE.js.map +1 -0
- package/dist/chunk-6E3ZS5Z5.js +1007 -0
- package/dist/chunk-6E3ZS5Z5.js.map +1 -0
- package/dist/chunk-77JDRXXU.js +3 -0
- package/dist/chunk-77JDRXXU.js.map +1 -0
- package/dist/chunk-FHYWJCG6.js +171 -0
- package/dist/chunk-FHYWJCG6.js.map +1 -0
- package/dist/chunk-JAE7HHMM.js +506 -0
- package/dist/chunk-JAE7HHMM.js.map +1 -0
- package/dist/chunk-NPDBQBLW.js +68 -0
- package/dist/chunk-NPDBQBLW.js.map +1 -0
- package/dist/chunk-ORMYARXD.js +6 -0
- package/dist/chunk-ORMYARXD.js.map +1 -0
- package/dist/chunk-R4O7APIX.js +51 -0
- package/dist/chunk-R4O7APIX.js.map +1 -0
- package/dist/chunk-ROLKRJWI.js +187 -0
- package/dist/chunk-ROLKRJWI.js.map +1 -0
- package/dist/chunk-SOTQ3WP5.js +31 -0
- package/dist/chunk-SOTQ3WP5.js.map +1 -0
- package/dist/chunk-TCFFHTY3.js +97 -0
- package/dist/chunk-TCFFHTY3.js.map +1 -0
- package/dist/chunk-TS7Y7GEZ.js +7 -0
- package/dist/chunk-TS7Y7GEZ.js.map +1 -0
- package/dist/chunk-VLGR5PZX.js +3 -0
- package/dist/chunk-VLGR5PZX.js.map +1 -0
- package/dist/chunk-ZW2PUSVS.js +40 -0
- package/dist/chunk-ZW2PUSVS.js.map +1 -0
- package/dist/chunk-ZYNBMYJ6.js +392 -0
- package/dist/chunk-ZYNBMYJ6.js.map +1 -0
- package/dist/cli/nonext-husk.js +3360 -0
- package/dist/cli/nonext-husk.js.map +1 -0
- package/dist/cli/templates/firestore.rules +393 -0
- package/dist/cli/templates/storage.rules +165 -0
- package/dist/client/index.d.ts +27 -0
- package/dist/client/index.js +15 -0
- package/dist/client/index.js.map +1 -0
- package/dist/cms-9fSA3uQV.d.ts +158 -0
- package/dist/core/index.d.ts +1267 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/entry-D3kiE8T1.d.ts +163 -0
- package/dist/field-Diyf5siF.d.ts +158 -0
- package/dist/field-type-D25Vojb0.d.ts +157 -0
- package/dist/firebase/index.d.ts +251 -0
- package/dist/firebase/index.js +991 -0
- package/dist/firebase/index.js.map +1 -0
- package/dist/index-5ylaMBUH.d.ts +155 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/permissions/index.d.ts +101 -0
- package/dist/permissions/index.js +7 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/registry-CUC25qay.d.ts +59 -0
- package/dist/richtext/index.d.ts +85 -0
- package/dist/richtext/index.js +127 -0
- package/dist/richtext/index.js.map +1 -0
- package/dist/schema/index.d.ts +50 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/server/index.d.ts +39 -0
- package/dist/server/index.js +25 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types/index.d.ts +36 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/write-limits-Djmm6L3R.d.ts +215 -0
- package/package.json +136 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/firebase/adapter.ts","../../src/firebase/media-upload.ts"],"names":["limitTo","countAggregate"],"mappings":"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","file":"index.js","sourcesContent":["import {\n collection,\n collectionGroup,\n count as countAggregate,\n deleteDoc,\n deleteField,\n doc,\n getAggregateFromServer,\n getCountFromServer,\n getDoc,\n getDocs,\n limit as limitTo,\n onSnapshot,\n orderBy,\n query,\n runTransaction,\n serverTimestamp,\n setDoc,\n startAfter,\n sum,\n updateDoc,\n where,\n writeBatch,\n} from \"firebase/firestore\"\nimport type {\n DocumentData,\n Firestore,\n Query,\n QueryConstraint,\n QueryDocumentSnapshot,\n} from \"firebase/firestore\"\n\nimport type {\n CmsAdapter,\n EntryInputData,\n FieldDataAdapter,\n FieldPurgeResult,\n ListQuery,\n MediaAdapter,\n MediaInput,\n MediaQuery,\n MediaRefreshResult,\n MediaUsage,\n MediaUsageReference,\n MigrationAdapter,\n Page,\n SchemaAdapter,\n StoredEntryRecord,\n SettingsAdapter,\n} from \"../core/adapter.js\"\nimport { MAX_FIELD_PURGE, MEDIA_USAGE_LIMIT, normalizeLimit } from \"../core/adapter.js\"\nimport {\n assertDeletable,\n assertListable,\n assertSingletonCreatable,\n assertSingletonEntryId,\n isSingleton,\n singletonEntryId,\n} from \"../core/content-kind.js\"\nimport { assertValidContentType } from \"../core/define-content-type.js\"\nimport {\n PermissionError,\n SchemaError,\n ValidationError,\n WriteLimitError,\n} from \"../core/errors.js\"\nimport { checkMediaFile } from \"../core/media-constraints.js\"\nimport {\n collectMediaIds,\n MEDIA_REFS_KEY,\n retargetMediaUrls,\n} from \"../core/media-references.js\"\nimport { assertSchemaWritable } from \"../core/registry.js\"\nimport type { Registry } from \"../core/registry.js\"\nimport { assertTypeSlugAvailable } from \"../core/schema-builder.js\"\nimport { nextSchemaVersion, schemaVersionOf } from \"../core/schema-version.js\"\nimport { slugFieldKey } from \"../core/slug-field.js\"\nimport {\n checkWritePayload,\n createWriteRateLimiter,\n MAX_CONTENT_TYPE_FIELDS,\n MAX_ENTRY_FIELDS,\n MAX_SETTINGS_FIELDS,\n} from \"../core/write-limits.js\"\nimport type { WriteLimitRejection, WriteRateLimiter } from \"../core/write-limits.js\"\nimport { can, resolveDecision } from \"../permissions/can.js\"\nimport { normalizePermissionMatrix } from \"../permissions/matrix.js\"\nimport type { FieldTypeRegistry } from \"../schema/field-type.js\"\nimport { uniqueFieldPaths, validateEntry } from \"../schema/validate.js\"\nimport type { Action, AuthUser } from \"../types/auth.js\"\nimport { ENTRY_STATUSES } from \"../types/entry.js\"\nimport type { ContentTypeLike } from \"../types/content-type.js\"\nimport type { Entry, EntryStatus, FieldError } from \"../types/entry.js\"\nimport type { MediaItem } from \"../types/media.js\"\n\nimport { entryFromStored, metaFromStored, toIso, valuesToStored } from \"./converters.js\"\nimport {\n BOOTSTRAP_KEY,\n contentPath,\n ENTRIES,\n entriesPath,\n entryPath,\n MEDIA,\n mediaPath,\n SCHEMAS,\n schemaPath,\n settingsPath,\n} from \"./paths.js\"\n\nexport interface FirestoreAdapterDeps {\n db: Firestore\n /**\n * The live registry. Supplied by the CMS instance, which owns it: the adapter must not\n * build one, because the registry is assembled from code-defined types the adapter cannot\n * see plus stored ones it reads through `schemas`.\n *\n * A function rather than a value, because the database half changes while the admin runs\n * and every call has to see the current merge. It is also what makes the lock guard\n * structural: an adapter cannot be constructed without the registry that says which types\n * are code-defined and which are locked, so `schemas.update` and `schemas.delete` always\n * have the answer available and never have to trust the caller for it.\n */\n registry: () => Registry\n currentUser: () => AuthUser\n /**\n * The session, for read-side permission checks. Optional, and its absence is meaningful.\n *\n * `currentUser` is the write path's account and throws on a connection that has none,\n * which is what proves a public page never asks who is reading. So the read check gets\n * its own accessor: the admin passes one and its `list`, `get` and `count` are refused\n * for a content type this account may not read, while a customer's website omits it and\n * the read path stays free of any question about a session. The security rules refuse a\n * denied read either way; this is the layer that refuses it before the round trip.\n */\n session?: () => AuthUser | null\n /** Field type specs. Optional until M5; the converters fall back to defaults without it. */\n fieldTypes?: FieldTypeRegistry\n /**\n * The burst guard for this adapter, or `null` for a process that is legitimately a bulk\n * writer: the seed loader, a migration run, a one-off import.\n *\n * Passing `null` is a statement, not a convenience. It says \"the thing the burst guard\n * exists to catch is exactly what this process is doing\", and it gives up nothing the\n * security rules enforce, because the rules-side floor is per document and this guard is\n * per process. See core/write-limits.ts.\n */\n rateLimiter?: WriteRateLimiter | null\n}\n\n/**\n * How many referencing entries a replace will rewrite the denormalized url on.\n *\n * Higher than the warning's limit, because this one is doing the work rather than printing\n * a list, and still bounded: a media item used by more entries than this keeps the stale\n * url on the remainder until each is next saved, which is a degraded convenience and not a\n * broken reference.\n */\nconst MAX_MEDIA_REFRESH = 500\n\n/** Default list order: most recently touched first, which is what an editor expects. */\nconst DEFAULT_SORT = { field: \"updatedAt\", direction: \"desc\" } as const\n\n/**\n * Sorts after every printable character, which is what turns a range into a prefix match.\n * The relation picker uses the same bound, so both search boxes in the admin behave alike.\n */\nconst PREFIX_END = \"\"\n\n/**\n * The two range constraints a prefix search is, or none when the term is blank.\n *\n * Throws rather than degrades when the content type names no title field: returning every\n * entry for a term that could not be matched would tell the reader their search found\n * everything, which is the one answer that is certainly wrong.\n */\nfunction searchRange(\n type: ContentTypeLike,\n search: string | undefined,\n): { field: string; term: string } | null {\n const term = (search ?? \"\").trim()\n if (term === \"\") return null\n\n const field = type.titleField\n if (field === undefined) {\n throw new SchemaError(\n `Content type \"${type.slug}\" has no title field, so its entries cannot be searched.`,\n )\n }\n\n return { field, term }\n}\n\n/**\n * A field key a query can actually address.\n *\n * A key nested inside a repeater row is not a top-level document field, so neither the\n * count nor the purge can reach it with one query. Rejected rather than answered with a\n * zero, because \"no entries hold this\" and \"I could not look\" must not print the same.\n */\nfunction assertTopLevelKey(slug: string, key: string): void {\n if (key.includes(\".\") || key.length === 0) {\n throw new SchemaError(\n `Field key \"${key}\" of content type \"${slug}\" is not a top-level field. The values of a field nested inside a repeater are counted and purged with its row, not on their own.`,\n )\n }\n}\n\nfunction fieldError(path: string, rule: string, message: string): FieldError {\n return { path, rule, message }\n}\n\nexport function createFirestoreAdapter(deps: FirestoreAdapterDeps): CmsAdapter {\n const { db, registry, currentUser, fieldTypes } = deps\n const limiter =\n deps.rateLimiter === undefined ? createWriteRateLimiter() : deps.rateLimiter\n\n function resolveType(slug: string): ContentTypeLike {\n return registry().require(slug).type\n }\n\n /**\n * Refuses what `can()` refuses.\n *\n * This is the second of the three layers, and the one that matters when the first is\n * bypassed: a button hidden in the UI is presentation, and a call made straight to the\n * adapter from a console has skipped it entirely. The third layer is firestore.rules,\n * which answers the same question again from the token and the stored matrix, so a\n * caller holding only a Firebase config never reaches this code and is refused anyway.\n */\n function assertCan(\n user: AuthUser,\n action: Action,\n context: { type?: ContentTypeLike; entry?: { createdBy: string } } = {},\n ): void {\n if (can(user, action, context)) return\n throw new PermissionError(action, context.type?.slug)\n }\n\n /**\n * Refuses an action the role can never perform on this type, before anything is read.\n *\n * An ownership-gated action needs the stored entry to be answered, and reading it is\n * itself a read the rules may refuse, so a caller denied the action outright would\n * otherwise learn about it as a failed read rather than as a permission. Checked against\n * the decision alone: \"deny\" is final and no entry can change it, while \"own-entry-only\"\n * still has to be resolved against the document.\n */\n function assertNotDenied(user: AuthUser, action: Action, type: ContentTypeLike): void {\n if (user.role === \"admin\") return\n if (resolveDecision(user.role, action, type) !== \"deny\") return\n throw new PermissionError(action, type.slug)\n }\n\n /** The read-side check, skipped entirely on a connection that declares no session. */\n function assertCanRead(type: ContentTypeLike): void {\n const user = deps.session?.() ?? null\n if (user === null) return\n assertCan(user, \"read\", { type })\n }\n\n function refuse(rejection: WriteLimitRejection | null): void {\n if (rejection !== null) throw new WriteLimitError(rejection)\n }\n\n /**\n * The two adapter-side guards, applied together because every write wants both.\n *\n * Neither repeats a rules-side check. The payload measurement is a byte count no rule can\n * see, and the limiter is a burst across documents that no rule can count. The\n * per-document interval belongs to firestore.rules alone.\n */\n function guardWrite(values: Record<string, unknown>, maxFields: number): void {\n refuse(checkWritePayload(values, { maxFields }))\n if (limiter !== null) refuse(limiter.check())\n }\n\n /**\n * The payload guard for a content type definition.\n *\n * Measured on the fields rather than on the document's top-level keys, because a content\n * type is a dozen keys one of which is a map that can grow without bound. The same cap\n * is in firestore.rules against `schemas/{slug}.fields`.\n */\n function guardSchemaWrite(type: ContentTypeLike): void {\n guardWrite(type.fields as Record<string, unknown>, MAX_CONTENT_TYPE_FIELDS)\n }\n\n /**\n * The server-side half of the media rules. Mirrors storage.rules and firestore.rules.\n *\n * The path check is the one that is not about the file: a media document whose `path`\n * pointed at somebody else's object would make every reference in every entry read the\n * wrong bytes, so the path has to name this document's own id.\n */\n function assertMediaInput(input: MediaInput, mediaId: string): void {\n const rejection = checkMediaFile({\n name: input.name,\n type: input.type,\n size: input.size,\n })\n if (rejection !== null) {\n throw new ValidationError([fieldError(\"file\", rejection.code, rejection.message)])\n }\n\n if (!input.path.startsWith(`${MEDIA}/${mediaId}/`)) {\n throw new ValidationError([\n fieldError(\n \"path\",\n \"media-path\",\n `A media object path must start with \"${MEDIA}/${mediaId}/\", and \"${input.path}\" does not.`,\n ),\n ])\n }\n\n if (input.url.trim() === \"\") {\n throw new ValidationError([\n fieldError(\"url\", \"required\", \"A media document needs a download url.\"),\n ])\n }\n }\n\n /**\n * The stored form of the matrix, or no matrix at all.\n *\n * Normalizing on the way in rather than on the way out means the copy on\n * `content/{slug}` that the security rules read and the copy on `schemas/{slug}` that\n * the Schema Builder reads are the same document, key for key. A matrix that only\n * repeats the role defaults is dropped, because keeping it would silently stop tracking\n * a later change to those defaults.\n */\n function withNormalizedMatrix(type: ContentTypeLike): ContentTypeLike {\n const matrix = normalizePermissionMatrix(type.permissions)\n const { permissions: _dropped, ...rest } = type\n return matrix === undefined ? rest : { ...rest, permissions: matrix }\n }\n\n /** A status a client invented is refused before it reaches a document. */\n function assertStatus(status: string): asserts status is EntryStatus {\n if (!(ENTRY_STATUSES as readonly string[]).includes(status)) {\n throw new SchemaError(\n `Entry status \"${status}\" is not one of: ${ENTRY_STATUSES.join(\", \")}.`,\n )\n }\n }\n\n function entriesCollection(slug: string) {\n return collection(db, entriesPath(slug))\n }\n\n async function snapshotFor(slug: string, entryId: string) {\n return getDoc(doc(db, entryPath(slug, entryId)))\n }\n\n function toEntry(\n type: ContentTypeLike,\n snapshot: QueryDocumentSnapshot<DocumentData>,\n ): Entry {\n return entryFromStored(type, snapshot.id, snapshot.data(), fieldTypes)\n }\n\n function constraintsFor(\n type: ContentTypeLike,\n listQuery: ListQuery,\n ): QueryConstraint[] {\n const constraints: QueryConstraint[] = []\n const status = listQuery.status ?? \"any\"\n\n if (status !== \"any\") {\n constraints.push(where(\"status\", \"==\", status))\n }\n\n for (const filter of listQuery.filters ?? []) {\n constraints.push(where(filter.field, filter.op, filter.value))\n }\n\n const range = searchRange(type, listQuery.search)\n if (range !== null) {\n constraints.push(where(range.field, \">=\", range.term))\n constraints.push(where(range.field, \"<=\", `${range.term}${PREFIX_END}`))\n }\n\n // Firestore puts the first ordering on the field a range filter ranges over, so a\n // search is ordered by title and the caller's sort cannot apply. ListQuery documents\n // this; callers are expected to omit `sort` while searching rather than be surprised.\n const sort =\n range === null\n ? (listQuery.sort ?? DEFAULT_SORT)\n : { field: range.field, direction: \"asc\" as const }\n constraints.push(orderBy(sort.field, sort.direction))\n // A second ordering key makes the page boundary total, so an entry cannot be skipped or\n // repeated when two documents share a sort value.\n if (sort.field !== \"__name__\") {\n constraints.push(orderBy(\"__name__\", sort.direction))\n }\n\n return constraints\n }\n\n /**\n * Checks every field carrying `unique` against the stored entries.\n *\n * This is the half of validation that needs the database, which is why it lives in the\n * adapter rather than in the shared validator. It runs on the server path for every\n * write, so bypassing the form does not bypass the rule.\n */\n async function assertUnique(\n type: ContentTypeLike,\n values: Record<string, unknown>,\n selfId: string | null,\n ): Promise<void> {\n const keys = uniqueFieldPaths(type)\n if (keys.length === 0) return\n\n const errors: FieldError[] = []\n\n for (const key of keys) {\n const value = values[key]\n if (value === null || value === undefined || value === \"\") continue\n\n const found = await getDocs(\n query(entriesCollection(type.slug), where(key, \"==\", value), limitTo(2)),\n )\n\n const taken = found.docs.some((candidate) => candidate.id !== selfId)\n if (taken) {\n const label = type.fields[key]?.label ?? key\n errors.push(\n fieldError(\n key,\n \"unique\",\n `${label} must be unique, and \"${String(value)}\" is taken.`,\n ),\n )\n }\n }\n\n if (errors.length > 0) throw new ValidationError(errors)\n }\n\n /**\n * Provisions the parent of the entries subcollection, once.\n *\n * The record carries the type's stored schema version, which the M10 migration runner\n * reads, and its lock flag, which the security rules read because they cannot see a\n * TypeScript definition. Firestore would let the subcollection exist without a parent\n * document, but then the type would be invisible to a console listing.\n *\n * It is written only when missing. Rewriting it on every entry create would be refused\n * for a locked type, which is exactly what the lock is for, and would turn an ordinary\n * save into a permission error. Only an admin may provision it; an editor working in an\n * already-provisioned project never reaches the write, and in a project where setup has\n * not run, a missing parent document does not stop the entry from being stored.\n */\n async function ensureContentDoc(type: ContentTypeLike): Promise<void> {\n const reference = doc(db, contentPath(type.slug))\n const stored = await getDoc(reference)\n const matrix = normalizePermissionMatrix(type.permissions)\n\n if (stored.exists()) {\n await syncContentMeta(reference, stored.data(), type, matrix)\n return\n }\n\n try {\n await setDoc(reference, {\n slug: type.slug,\n kind: type.kind,\n locked: type.locked ?? false,\n schemaVersion: type.schemaVersion ?? 1,\n // The rules read the matrix from here, not from `schemas/{slug}`, because a\n // code-defined type has no document under `schemas/` at all and its matrix still\n // has to reach the rules. Stored as an empty map rather than omitted so the two\n // states a rule can see, \"not narrowed\" and \"narrowed to nobody\", stay distinct.\n permissions: matrix ?? {},\n updatedAt: serverTimestamp(),\n })\n } catch (thrown) {\n if ((thrown as { code?: string }).code !== \"permission-denied\") throw thrown\n }\n }\n\n /**\n * Keeps the rules' copy of the matrix and the version honest after they change in code.\n *\n * A code-defined type's matrix lives in git and never passes through the Schema Builder,\n * so nothing else would ever rewrite `content/{slug}` after a deploy changed it, and the\n * rules would go on enforcing last month's matrix. Written only when it actually differs,\n * because rewriting the document on every entry create would be refused for a locked type\n * and would turn an ordinary save into a permission error.\n *\n * A permission denial is swallowed for the same reason `ensureContentDoc` swallows one:\n * an editor saving an entry in a project an admin has already provisioned reaches this\n * with nothing to do and no right to do it.\n */\n async function syncContentMeta(\n reference: ReturnType<typeof doc>,\n stored: DocumentData,\n type: ContentTypeLike,\n matrix: ReturnType<typeof normalizePermissionMatrix>,\n ): Promise<void> {\n const next = matrix ?? {}\n if (sameMatrix(stored[\"permissions\"], next)) return\n\n try {\n await updateDoc(reference, { permissions: next, updatedAt: serverTimestamp() })\n } catch (thrown) {\n if ((thrown as { code?: string }).code !== \"permission-denied\") throw thrown\n }\n }\n\n async function writeEntry(\n type: ContentTypeLike,\n entryId: string,\n input: EntryInputData,\n mode: \"create\" | \"update\",\n ): Promise<Entry> {\n const status: string = input.status ?? \"draft\"\n const user = currentUser()\n const reference = doc(db, entryPath(type.slug, entryId))\n\n // Permission first, and before anything is read or validated: the answer does not\n // depend on the values, and a caller who may not write should not learn from the error\n // message whether their payload would have been accepted.\n //\n // An update needs the stored entry to answer the ownership half of the question, which\n // is why it is read here rather than further down. `can()` denies an ownership-gated\n // action when the entry is unknown, so a missing document is refused as a permission\n // rather than reported as not found: an author asking about an entry that is not there\n // has no business learning which of the two it is.\n if (mode === \"update\") assertNotDenied(user, \"update\", type)\n\n const existing = mode === \"update\" ? await getDoc(reference) : null\n\n if (mode === \"create\") {\n assertCan(user, \"create\", { type })\n } else {\n const owner =\n existing?.exists() === true ? metaFromStored(entryId, existing.data()) : undefined\n assertCan(user, \"update\", {\n type,\n ...(owner === undefined ? {} : { entry: { createdBy: owner.createdBy } }),\n })\n }\n\n // A status is metadata, not a field, so the field validator never sees it. Without this\n // an adapter call could store a status no screen can render and no query can find.\n assertStatus(status)\n\n guardWrite(input.values, MAX_ENTRY_FIELDS)\n\n // The registry has to be handed over, or each field type's own validate never runs on\n // the write path and only the shared rules are enforced server-side. A direct adapter\n // call would then accept a malformed date or a blank relation id that the form refuses.\n const errors = validateEntry(type, input.values, {\n skipRequired: status === \"draft\",\n ...(fieldTypes === undefined ? {} : { fieldTypes }),\n })\n if (errors.length > 0) throw new ValidationError(errors)\n\n await assertUnique(type, input.values, mode === \"update\" ? entryId : null)\n\n const stored = valuesToStored(type.fields, input.values, fieldTypes)\n // The reverse index, written in the same document write as the values it summarizes,\n // so it cannot drift from them. Collected from the stored data by shape rather than\n // from the schema, which is why it needs no field type registry and why an entry\n // written by a script is indexed exactly like one written by the admin.\n // See core/media-references.ts.\n const mediaRefs = { [MEDIA_REFS_KEY]: collectMediaIds(stored) }\n\n if (mode === \"create\") {\n await ensureContentDoc(type)\n await setDoc(reference, {\n ...stored,\n ...mediaRefs,\n status,\n schemaVersion: type.schemaVersion ?? 1,\n createdAt: serverTimestamp(),\n createdBy: user.uid,\n updatedAt: serverTimestamp(),\n updatedBy: user.uid,\n publishedAt: status === \"published\" ? serverTimestamp() : null,\n })\n } else {\n if (existing === null || !existing.exists()) {\n throw new SchemaError(\n `Entry \"${entryId}\" of content type \"${type.slug}\" does not exist.`,\n )\n }\n const previous = metaFromStored(entryId, existing.data())\n\n await updateDoc(reference, {\n ...stored,\n ...mediaRefs,\n status,\n schemaVersion: type.schemaVersion ?? 1,\n updatedAt: serverTimestamp(),\n updatedBy: user.uid,\n // First publish stamps the date; republishing keeps the original, and unpublishing\n // clears it, so publishedAt always answers \"since when has this been public\".\n publishedAt:\n status === \"published\"\n ? (previous.publishedAt ?? serverTimestamp())\n : status === \"draft\"\n ? null\n : previous.publishedAt,\n })\n }\n\n const written = await getDoc(reference)\n return entryFromStored(type, entryId, written.data() ?? {}, fieldTypes)\n }\n\n /**\n * The name a usage warning prints, or null when the entry has none.\n *\n * Read straight from the stored document rather than by converting the entry: a warning\n * needs one string, and running every field of every referencing entry through its spec\n * to get it would be work nobody sees. An unknown content type, which is what a stale\n * index or a deleted schema looks like, yields null and the dialog prints the id.\n */\n function usageTitle(slug: string, data: DocumentData): string | null {\n let titleField: string | undefined\n try {\n titleField = resolveType(slug).titleField\n } catch {\n return null\n }\n\n if (titleField === undefined) return null\n const value: unknown = data[titleField]\n if (typeof value !== \"string\" || value.trim() === \"\") return null\n return value.trim()\n }\n\n const media: MediaAdapter = {\n async list(mediaQuery: MediaQuery = {}) {\n const size = normalizeLimit(mediaQuery.limit)\n const constraints: QueryConstraint[] = []\n\n if (mediaQuery.type !== undefined) {\n // A MIME prefix is a range scan, which is the only \"starts with\" Firestore offers.\n constraints.push(where(\"type\", \">=\", mediaQuery.type))\n constraints.push(where(\"type\", \"<\", `${mediaQuery.type}`))\n constraints.push(orderBy(\"type\", \"asc\"))\n }\n\n constraints.push(orderBy(\"createdAt\", \"desc\"), orderBy(\"__name__\", \"desc\"))\n\n if (mediaQuery.cursor) {\n const anchor = await getDoc(doc(db, mediaPath(mediaQuery.cursor)))\n if (anchor.exists()) constraints.push(startAfter(anchor))\n }\n\n constraints.push(limitTo(size + 1))\n\n const found = await getDocs(query(collection(db, MEDIA), ...constraints))\n const docs = found.docs.slice(0, size)\n\n return {\n items: docs.map((snapshot) => mediaFromStored(snapshot.id, snapshot.data())),\n cursor: found.docs.length > size ? (docs.at(-1)?.id ?? null) : null,\n hasMore: found.docs.length > size,\n }\n },\n\n async getById(mediaId) {\n const snapshot = await getDoc(doc(db, mediaPath(mediaId)))\n return snapshot.exists() ? mediaFromStored(snapshot.id, snapshot.data()) : null\n },\n\n async getMany(mediaIds) {\n const found = await Promise.all(mediaIds.map((mediaId) => media.getById(mediaId)))\n return found.filter((item): item is MediaItem => item !== null)\n },\n\n reserveId() {\n return doc(collection(db, MEDIA)).id\n },\n\n async create(input: MediaInput, mediaId?: string) {\n const reference =\n mediaId === undefined ? doc(collection(db, MEDIA)) : doc(db, mediaPath(mediaId))\n const user = currentUser()\n\n // The metadata document is checked on the server path too, not only where the bytes\n // are picked. A media document is cheap to write and can claim any name, type and\n // size it likes: the dashboard sums the size, the library filters on the type, and\n // an entry renders the url. firestore.rules checks the same shape again; this check\n // exists so a caller gets a sentence instead of a permission denial.\n assertMediaInput(input, reference.id)\n if (limiter !== null) refuse(limiter.check())\n\n // Written field by field rather than spread, so a caller cannot add a key. The\n // security rules refuse an unknown field on a media document, and a document the\n // adapter itself composed should never be the thing that trips that rule.\n await setDoc(reference, {\n name: input.name,\n type: input.type,\n size: input.size,\n url: input.url,\n path: input.path,\n alt: input.alt ?? null,\n width: input.width ?? null,\n height: input.height ?? null,\n createdAt: serverTimestamp(),\n createdBy: user.uid,\n })\n\n const written = await getDoc(reference)\n return mediaFromStored(reference.id, written.data() ?? {})\n },\n\n async update(mediaId, patch) {\n const reference = doc(db, mediaPath(mediaId))\n const current = await media.getById(mediaId)\n if (current === null) {\n throw new SchemaError(`Media item \"${mediaId}\" does not exist.`)\n }\n\n // Validated as the document it will become, not as the patch. A patch that is\n // individually harmless can still leave the stored document claiming a type nothing\n // allows, and the rules check the result rather than the difference.\n assertMediaInput({ ...current, ...patch }, mediaId)\n if (limiter !== null) refuse(limiter.check())\n\n await updateDoc(reference, { ...patch })\n const written = await getDoc(reference)\n return mediaFromStored(mediaId, written.data() ?? {})\n },\n\n async delete(mediaId) {\n await deleteDoc(doc(db, mediaPath(mediaId)))\n },\n\n async usage(): Promise<MediaUsage> {\n // One aggregate round trip instead of paging the whole library. `sum` ignores a\n // document whose `size` is missing or not a number rather than failing, which is the\n // same tolerance mediaFromStored applies when it reads one back.\n const snapshot = await getAggregateFromServer(collection(db, MEDIA), {\n count: countAggregate(),\n bytes: sum(\"size\"),\n })\n const data = snapshot.data()\n return { count: data.count, bytes: data.bytes }\n },\n\n /**\n * Entries referencing this media item, from the reverse index.\n *\n * One collection-group query over `entries`, answered by the automatic single-field\n * index on the `mediaRefs` array. The cost is the number of referencing entries, not\n * the size of the database, which is the whole reason the index is written: the\n * alternative was reading every entry of every content type on every delete.\n *\n * The blind spot is named rather than hidden: an entry written before the index\n * existed, or written straight into the console, has no `mediaRefs` field and does not\n * appear here until it is saved once. See core/media-references.ts.\n */\n async usages(mediaId, usageQuery = {}) {\n const size = normalizeLimit(usageQuery.limit ?? MEDIA_USAGE_LIMIT)\n\n const found = await getDocs(\n query(\n collectionGroup(db, ENTRIES),\n where(MEDIA_REFS_KEY, \"array-contains\", mediaId),\n limitTo(size),\n ),\n )\n\n return found.docs.map((snapshot) => {\n // `content/{slug}/entries/{entryId}`: the grandparent document is the content type.\n const contentType = snapshot.ref.parent.parent?.id ?? \"\"\n return {\n contentType,\n entryId: snapshot.id,\n title: usageTitle(contentType, snapshot.data()),\n }\n })\n },\n\n /**\n * Points every referencing entry at the new url after a replace.\n *\n * Best effort by construction. Each entry is its own write, an author may hold no\n * permission over somebody else's entry, and a failure here costs a stale convenience\n * rather than a broken reference, because `mediaId` is the source of truth and the\n * admin resolves through it. What fails is returned by name; nothing is swallowed.\n */\n async refreshReferences(mediaId, url): Promise<MediaRefreshResult> {\n const references = await media.usages(mediaId, { limit: MAX_MEDIA_REFRESH })\n const user = currentUser()\n const failed: MediaUsageReference[] = []\n let updated = 0\n\n for (const reference of references) {\n try {\n const target = doc(db, entryPath(reference.contentType, reference.entryId))\n const snapshot = await getDoc(target)\n if (!snapshot.exists()) continue\n\n const rewritten = retargetMediaUrls(snapshot.data(), mediaId, url)\n // Nothing to do when the entry already carries this url. Writing anyway would\n // move its updatedAt for no change a reader could see.\n if (rewritten === null) continue\n\n await updateDoc(target, {\n ...rewritten,\n updatedAt: serverTimestamp(),\n updatedBy: user.uid,\n })\n updated += 1\n } catch {\n failed.push(reference)\n }\n }\n\n return { updated, failed }\n },\n }\n\n /**\n * Refuses a schema write the Schema Builder would never have offered.\n *\n * Registry first: a code-defined type is always in it, locked or not, so\n * `assertSchemaWritable` answers for every case the lock exists to cover, and it answers\n * the same way for a direct call as for one from the UI.\n *\n * The fallback exists for one honest case. A type created a moment ago is in `schemas/`\n * before the listener that feeds the registry has delivered it, and refusing to edit a\n * type the caller just created would be a race, not a guard. A type that is missing from\n * the registry *and* from `schemas/` is simply unknown. Neither branch can reach a\n * code-defined type, because a code-defined type has no document under `schemas/`.\n */\n async function assertSchemaWrite(slug: string): Promise<void> {\n const current = registry()\n\n if (current.has(slug)) {\n assertSchemaWritable(current, slug)\n return\n }\n\n if (!(await getDoc(doc(db, schemaPath(slug)))).exists()) {\n throw new SchemaError(`Unknown content type \"${slug}\".`)\n }\n }\n\n const schemas: SchemaAdapter = {\n async list() {\n const found = await getDocs(collection(db, SCHEMAS))\n return found.docs.map((snapshot) => snapshot.data() as ContentTypeLike)\n },\n\n async get(slug) {\n const snapshot = await getDoc(doc(db, schemaPath(slug)))\n return snapshot.exists() ? (snapshot.data() as ContentTypeLike) : null\n },\n\n watch(listener, onError) {\n // One listener over the whole collection. See RegistryStore for what it costs and\n // why it beats refetching on navigation.\n return onSnapshot(\n collection(db, SCHEMAS),\n (found) => {\n listener(found.docs.map((snapshot) => snapshot.data() as ContentTypeLike))\n },\n (error) => {\n onError?.(error)\n },\n )\n },\n\n async create(type) {\n assertCan(currentUser(), \"manage-schema\")\n // The same collision check the form ran, against both sources, so a slug taken by a\n // code-defined type is refused here too even though `schemas/` knows nothing of it.\n assertTypeSlugAvailable(registry(), type.slug)\n // Validates the field map, the removed fields, the title field, the sort, and the\n // permission matrix. A definition composed by hand and passed straight to the\n // adapter goes through exactly what the Schema Builder's form output goes through.\n assertValidContentType(type)\n guardSchemaWrite(type)\n\n const reference = doc(db, schemaPath(type.slug))\n if ((await getDoc(reference)).exists()) {\n throw new SchemaError(\n `Content type \"${type.slug}\" already exists in the database.`,\n )\n }\n\n const created: ContentTypeLike = {\n ...withNormalizedMatrix(type),\n schemaVersion: nextSchemaVersion(null, type),\n }\n await setDoc(reference, { ...created, updatedAt: serverTimestamp() })\n await ensureContentDoc(created)\n return created\n },\n\n async update(slug, type) {\n assertCan(currentUser(), \"manage-schema\")\n await assertSchemaWrite(slug)\n assertValidContentType(type)\n guardSchemaWrite(type)\n\n if (type.slug !== slug) {\n // Renaming a slug relocates every entry. That is a migration, not an edit.\n throw new SchemaError(\n `Content type \"${slug}\" cannot be renamed to \"${type.slug}\" in place. A slug change moves every entry and is a migration.`,\n )\n }\n\n const reference = doc(db, schemaPath(slug))\n const stored = await getDoc(reference)\n const previous = stored.exists() ? (stored.data() as ContentTypeLike) : null\n\n // Computed from the two definitions, never taken from the caller, so a client cannot\n // stamp a version that does not describe what it is writing.\n const written: ContentTypeLike = {\n ...withNormalizedMatrix(type),\n schemaVersion: nextSchemaVersion(previous, type),\n }\n\n await setDoc(reference, { ...written, updatedAt: serverTimestamp() })\n await ensureContentDoc(written)\n\n // `content/{slug}` is what the M10 migration runner reads, and `ensureContentDoc`\n // writes only when the document is missing, so a bump has to be carried over\n // explicitly. The lock flag is untouched, which is what the security rules require.\n if (previous !== null && schemaVersionOf(previous) !== written.schemaVersion) {\n await updateDoc(doc(db, contentPath(slug)), {\n schemaVersion: written.schemaVersion,\n updatedAt: serverTimestamp(),\n })\n }\n\n return written\n },\n\n async delete(slug) {\n assertCan(currentUser(), \"manage-schema\")\n await assertSchemaWrite(slug)\n // Only the definition is removed. Entries stay, because M8 promises that removing a\n // structure never destroys the content stored against it.\n await deleteDoc(doc(db, schemaPath(slug)))\n },\n }\n\n /**\n * The values of a removed field. The only entry data a schema operation may reach, and\n * kept off `SchemaAdapter` on purpose so that interface cannot touch an entry at all.\n */\n const fieldData: FieldDataAdapter = {\n async count(slug, key) {\n assertTopLevelKey(slug, key)\n const type = resolveType(slug)\n\n if (!(key in type.fields) && type.removedFields?.[key] === undefined) {\n throw new SchemaError(\n `Content type \"${type.slug}\" has no field \"${key}\", live or removed.`,\n )\n }\n\n // One aggregation, not a walk. `!= null` matches documents that hold a value and\n // skips both explicit nulls and documents written before the field existed, which is\n // exactly the population the warning is about: an entry saved with the field empty\n // stores `null`, and losing a null is not losing content.\n const found = await getCountFromServer(\n query(entriesCollection(slug), where(key, \"!=\", null)),\n )\n return found.data().count\n },\n\n async purge(slug, key, options = {}): Promise<FieldPurgeResult> {\n // Admin only, resolved through the same `can()` the UI and the rules encode. An\n // editor who reached this call directly is refused here.\n const user = currentUser()\n if (!can(user, \"manage-schema\")) {\n throw new PermissionError(\n \"manage-schema\",\n slug,\n `Purging the values of field \"${key}\" requires the manage-schema permission.`,\n )\n }\n\n assertTopLevelKey(slug, key)\n const type = resolveType(slug)\n\n // The guard that stops a purge being the accidental default: only a field that has\n // already been explicitly removed, and is still listed as removed, can be purged. A\n // live key, an unknown key, and a key whose values were already purged are all\n // refused, so no single call can go from \"this field exists\" to \"its data is gone\".\n if (key in type.fields) {\n throw new SchemaError(\n `Field \"${key}\" of content type \"${type.slug}\" is still part of the schema. Remove it first; removing keeps the values, purging destroys them.`,\n )\n }\n\n if (type.removedFields?.[key] === undefined) {\n throw new SchemaError(\n `Content type \"${type.slug}\" has no removed field \"${key}\" to purge.`,\n )\n }\n\n const size = Math.min(\n Math.max(Math.trunc(options.limit ?? MAX_FIELD_PURGE), 1),\n MAX_FIELD_PURGE,\n )\n // The same population `count` reported, so the number in the warning is exactly the\n // number this destroys. An entry holding an explicit null keeps it, which reads the\n // way an empty field always read and saves a second query per call.\n const found = await getDocs(\n query(entriesCollection(slug), where(key, \"!=\", null), limitTo(size + 1)),\n )\n const targets = found.docs.slice(0, size)\n\n if (targets.length === 0) return { purged: 0, remaining: false }\n\n const batch = writeBatch(db)\n for (const snapshot of targets) {\n // `deleteField` removes the key rather than nulling it, so a purged field leaves no\n // trace on the document and a later re-add starts from genuinely absent.\n batch.update(snapshot.ref, {\n [key]: deleteField(),\n updatedAt: serverTimestamp(),\n updatedBy: user.uid,\n })\n }\n await batch.commit()\n\n return { purged: targets.length, remaining: found.docs.length > size }\n },\n }\n\n /**\n * The migration runner's storage port. Admin only, at every method.\n *\n * A migration reads and rewrites every entry of a content type, which is the widest\n * write in the product, so it is gated on `manage-schema` exactly as a purge is: the\n * same permission that allows changing the shape allows moving the data into it. The\n * security rules gate the individual writes on the entry rules, so an editor who called\n * these directly would be refused here and again at every document.\n */\n const migrations: MigrationAdapter = {\n async countBehind(slug, targetVersion) {\n assertCan(currentUser(), \"manage-schema\", { type: resolveType(slug) })\n\n // A range on one field, ordered by that field, is served by Firestore's automatic\n // single-field index, so this query ships no composite index of its own.\n const found = await getCountFromServer(\n query(entriesCollection(slug), where(\"schemaVersion\", \"<\", targetVersion)),\n )\n return found.data().count\n },\n\n async listBehind(slug, targetVersion, migrationQuery = {}) {\n assertCan(currentUser(), \"manage-schema\", { type: resolveType(slug) })\n\n const size = normalizeLimit(migrationQuery.limit)\n // No cursor. The selection is \"still behind\", and a migrated entry leaves it, so the\n // next page is simply the next call: paging with a cursor over a collection that is\n // being rewritten underneath the cursor is how entries get skipped.\n const found = await getDocs(\n query(\n entriesCollection(slug),\n where(\"schemaVersion\", \"<\", targetVersion),\n orderBy(\"schemaVersion\", \"asc\"),\n orderBy(\"__name__\", \"asc\"),\n limitTo(size + 1),\n ),\n )\n\n const docs = found.docs.slice(0, size)\n\n return {\n items: docs.map((snapshot) => toStoredRecord(snapshot.id, snapshot.data())),\n cursor: null,\n hasMore: found.docs.length > size,\n }\n },\n\n async applyMigration(slug, entryId, values, write) {\n const user = currentUser()\n assertCan(user, \"manage-schema\", { type: resolveType(slug) })\n refuse(checkWritePayload(values, { maxFields: MAX_ENTRY_FIELDS }))\n\n const reference = doc(db, entryPath(slug, entryId))\n\n // A transaction, not a plain update, because the version has to still be the one the\n // transform read. Two runners on the same collection, or a person saving the entry\n // in the admin while a migration is running, would otherwise both write and the\n // later one would win with values derived from a shape that no longer applied.\n return await runTransaction(db, async (transaction) => {\n const snapshot = await transaction.get(reference)\n if (!snapshot.exists()) return false\n\n const stored = snapshot.data()\n const version =\n typeof stored[\"schemaVersion\"] === \"number\" ? stored[\"schemaVersion\"] : 1\n if (version !== write.expectedVersion) return false\n\n // A transform that dropped a key has to drop it from the document too. An update\n // only sets the keys it names, so without this a renamed field would keep its old\n // key forever and the entry would carry both shapes at once. Found by the\n // emulator test, not by reading the code.\n const dropped: Record<string, ReturnType<typeof deleteField>> = {}\n for (const key of Object.keys(stored)) {\n if (ADAPTER_OWNED_KEYS.has(key)) continue\n if (!(key in values)) dropped[key] = deleteField()\n }\n\n transaction.update(reference, {\n ...values,\n ...dropped,\n [MEDIA_REFS_KEY]: collectMediaIds(values),\n schemaVersion: write.nextVersion,\n updatedAt: serverTimestamp(),\n updatedBy: user.uid,\n })\n return true\n })\n },\n }\n\n const settings: SettingsAdapter = {\n async get(key) {\n const snapshot = await getDoc(doc(db, settingsPath(key)))\n return snapshot.exists() ? (snapshot.data() as Record<string, unknown>) : null\n },\n\n async set(key, value) {\n assertCan(currentUser(), \"manage-settings\")\n\n // The first-run marker is a settings document by storage only. The rules refuse\n // every write to it, admin included, and refusing it here as well means a caller\n // finds out from a sentence rather than from a permission error on a document they\n // did not know they were touching.\n if (key === BOOTSTRAP_KEY) {\n throw new SchemaError(\n `\"${BOOTSTRAP_KEY}\" is the first-run marker, not a settings document. It is written once at setup and never again.`,\n )\n }\n\n guardWrite(value, MAX_SETTINGS_FIELDS)\n\n await setDoc(\n doc(db, settingsPath(key)),\n { ...value, updatedAt: serverTimestamp() },\n { merge: true },\n )\n },\n }\n\n return {\n async list(slug, listQuery = {}) {\n const type = resolveType(slug)\n assertCanRead(type)\n assertListable(type)\n\n const size = normalizeLimit(listQuery.limit)\n const constraints = constraintsFor(type, listQuery)\n\n if (listQuery.cursor) {\n // The cursor is a document id, so the page boundary survives any sort field without\n // the caller ever seeing a stored value. It costs one extra read per page.\n const anchor = await snapshotFor(slug, listQuery.cursor)\n if (anchor.exists()) constraints.push(startAfter(anchor))\n }\n\n constraints.push(limitTo(size + 1))\n\n const found = await getDocs(\n query(entriesCollection(slug), ...constraints) as Query<DocumentData>,\n )\n const docs = found.docs.slice(0, size)\n const hasMore = found.docs.length > size\n\n return {\n items: docs.map((snapshot) => toEntry(type, snapshot)),\n cursor: hasMore ? (docs.at(-1)?.id ?? null) : null,\n hasMore,\n } satisfies Page<Entry>\n },\n\n async get(slug, entryId) {\n const type = resolveType(slug)\n assertCanRead(type)\n const snapshot = await snapshotFor(slug, entryId)\n return snapshot.exists()\n ? entryFromStored(type, snapshot.id, snapshot.data(), fieldTypes)\n : null\n },\n\n async getBySlug(slug, value, options = {}) {\n const type = resolveType(slug)\n assertCanRead(type)\n const key = slugFieldKey(type)\n\n if (key === null) {\n throw new SchemaError(\n `Content type \"${slug}\" has no slug field, so it cannot be looked up by slug.`,\n )\n }\n\n const status = options.status ?? \"any\"\n const constraints: QueryConstraint[] = [where(key, \"==\", value)]\n // Part of the query rather than a filter over the result. An unauthenticated read\n // from a customer's website is allowed by the security rules only when the query\n // itself is restricted to published entries; the same query without this clause is\n // refused before a document is read.\n if (status !== \"any\") constraints.push(where(\"status\", \"==\", status))\n constraints.push(limitTo(1))\n\n const found = await getDocs(query(entriesCollection(slug), ...constraints))\n const first = found.docs[0]\n return first ? toEntry(type, first) : null\n },\n\n async getSingle(slug) {\n const type = resolveType(slug)\n assertCanRead(type)\n if (!isSingleton(type)) {\n throw new SchemaError(\n `Content type \"${slug}\" is a collection. Read it with list() or get().`,\n )\n }\n\n const entryId = singletonEntryId(slug)\n const snapshot = await snapshotFor(slug, entryId)\n return snapshot.exists()\n ? entryFromStored(type, entryId, snapshot.data(), fieldTypes)\n : null\n },\n\n async create(slug, input) {\n const type = resolveType(slug)\n\n if (isSingleton(type)) {\n const entryId = singletonEntryId(slug)\n const existing = await snapshotFor(slug, entryId)\n assertSingletonCreatable(type, existing.exists())\n return writeEntry(type, entryId, input, \"create\")\n }\n\n const reference = doc(entriesCollection(slug))\n return writeEntry(type, reference.id, input, \"create\")\n },\n\n async update(slug, entryId, input) {\n const type = resolveType(slug)\n assertSingletonEntryId(type, entryId)\n return writeEntry(type, entryId, input, \"update\")\n },\n\n async delete(slug, entryId) {\n const type = resolveType(slug)\n assertDeletable(type)\n\n // One read before the delete, because a delete is an ownership question for every\n // role a matrix can narrow, and `can()` answers it against the entry or not at all.\n // Deleting something that is not there is allowed to be a no-op, so a missing\n // document is answered with the type alone rather than refused outright.\n const user = currentUser()\n assertNotDenied(user, \"delete\", type)\n\n const reference = doc(db, entryPath(slug, entryId))\n const existing = await getDoc(reference)\n\n assertCan(user, \"delete\", {\n type,\n ...(existing.exists()\n ? { entry: { createdBy: metaFromStored(entryId, existing.data()).createdBy } }\n : {}),\n })\n\n await deleteDoc(reference)\n },\n\n async count(slug, countQuery = {}) {\n // resolveType throws for an unknown slug, which is the behaviour every other method\n // has; counting a content type that does not exist should not quietly return 0.\n const type = resolveType(slug)\n assertCanRead(type)\n const constraints: QueryConstraint[] = []\n const status = countQuery.status ?? \"any\"\n\n if (status !== \"any\") constraints.push(where(\"status\", \"==\", status))\n for (const filter of countQuery.filters ?? []) {\n constraints.push(where(filter.field, filter.op, filter.value))\n }\n\n // The same prefix range the list applies, so the total counts the rows the reader\n // is paging through rather than the whole collection behind the search.\n const range = searchRange(type, countQuery.search)\n if (range !== null) {\n constraints.push(where(range.field, \">=\", range.term))\n constraints.push(where(range.field, \"<=\", `${range.term}${PREFIX_END}`))\n }\n\n const snapshot = await getCountFromServer(\n query(entriesCollection(slug), ...constraints),\n )\n return snapshot.data().count\n },\n\n media,\n schemas,\n fieldData,\n migrations,\n settings,\n }\n}\n\n/**\n * Whether a stored matrix already says what the content type says.\n *\n * Compared key by key and role by role rather than by serializing both, because Firestore\n * returns the keys of a map sorted and the content type lists them in action order. A\n * string comparison would therefore report a difference on every read and rewrite\n * `content/{slug}` on every entry create.\n */\nfunction sameMatrix(\n stored: unknown,\n next: Record<string, readonly string[] | undefined>,\n): boolean {\n const current =\n typeof stored === \"object\" && stored !== null && !Array.isArray(stored)\n ? (stored as Record<string, unknown>)\n : {}\n\n const keys = new Set([...Object.keys(current), ...Object.keys(next)])\n\n for (const key of keys) {\n const left = current[key]\n const right = next[key]\n if (!Array.isArray(left) || right === undefined) return false\n if (left.length !== right.length) return false\n if (!right.every((role) => (left as readonly unknown[]).includes(role))) return false\n }\n\n return true\n}\n\n/**\n * A stored entry as the migration runner reads it: the document minus the metadata the\n * adapter owns.\n *\n * Nothing is converted. A migration exists because the stored values do not match the\n * current field specs, so running them through a converter first would be asking the new\n * shape to read the old one, which is the failure the migration is there to prevent.\n */\nconst ADAPTER_OWNED_KEYS: ReadonlySet<string> = new Set([\n \"status\",\n \"schemaVersion\",\n \"createdAt\",\n \"createdBy\",\n \"updatedAt\",\n \"updatedBy\",\n \"publishedAt\",\n MEDIA_REFS_KEY,\n])\n\nfunction toStoredRecord(entryId: string, data: DocumentData): StoredEntryRecord {\n const values: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(data)) {\n if (!ADAPTER_OWNED_KEYS.has(key)) values[key] = value as unknown\n }\n\n return {\n id: entryId,\n schemaVersion: typeof data[\"schemaVersion\"] === \"number\" ? data[\"schemaVersion\"] : 1,\n values,\n }\n}\n\nfunction mediaFromStored(mediaId: string, data: DocumentData): MediaItem {\n return {\n id: mediaId,\n name: typeof data[\"name\"] === \"string\" ? data[\"name\"] : \"\",\n type: typeof data[\"type\"] === \"string\" ? data[\"type\"] : \"application/octet-stream\",\n size: typeof data[\"size\"] === \"number\" ? data[\"size\"] : 0,\n url: typeof data[\"url\"] === \"string\" ? data[\"url\"] : \"\",\n path: typeof data[\"path\"] === \"string\" ? data[\"path\"] : \"\",\n alt: typeof data[\"alt\"] === \"string\" ? data[\"alt\"] : null,\n width: typeof data[\"width\"] === \"number\" ? data[\"width\"] : null,\n height: typeof data[\"height\"] === \"number\" ? data[\"height\"] : null,\n createdAt: toIso(data[\"createdAt\"]) ?? new Date(0).toISOString(),\n createdBy: typeof data[\"createdBy\"] === \"string\" ? data[\"createdBy\"] : \"\",\n }\n}\n","import { deleteObject, getDownloadURL, ref, uploadBytesResumable } from \"firebase/storage\"\nimport type { FirebaseStorage, UploadTask } from \"firebase/storage\"\n\nimport type { MediaAdapter } from \"../core/adapter.js\"\nimport type { MediaRejection } from \"../core/media-constraints.js\"\nimport {\n checkMediaFile,\n mediaTypeRule,\n normalizeMediaType,\n safeFileName,\n} from \"../core/media-constraints.js\"\nimport type { AuthUser } from \"../types/auth.js\"\nimport type { MediaUploader } from \"../admin/media/uploader.js\"\nimport type { MediaItem } from \"../types/media.js\"\n\nimport { storageObjectPath } from \"./paths.js\"\n\n/**\n * Uploading one file into the media library.\n *\n * An upload is two writes that cannot be made atomic: the bytes go to Storage, the metadata\n * document goes to Firestore, and no transaction spans the two. The order is therefore a\n * decision rather than an accident, and it is this one: **bytes first, document last**.\n *\n * - A document written before the bytes would point at an object that does not exist. The\n * library would list it, the picker would offer it, an entry could reference it, and a\n * visitor would get a broken image on the customer's site. The failure would be visible\n * everywhere except where it happened.\n * - An object written before the document is invisible to the CMS, because the CMS lists\n * documents, not bucket contents. It costs storage and nothing else, and M7's orphan\n * sweep is defined precisely as \"an object with no document\".\n *\n * So when the document write fails after the bytes landed, this module tries to delete the\n * object it just wrote (see `removeOrphan`). If that cleanup also fails, the upload does not\n * pretend to have been clean: the thrown `MediaUploadError` carries `orphanedPath`, naming\n * the object still sitting in the bucket, so the admin can say so and the sweep has a\n * starting point. Nothing is swallowed and nothing is guessed.\n *\n * What this module still cannot cover: a browser closed between the two writes. That leaves\n * an orphan no client will ever clean up, which is the reason the sweep exists rather than\n * being a nice-to-have.\n */\n\nexport type UploadPhase =\n \"idle\" | \"uploading\" | \"storing\" | \"done\" | \"cancelled\" | \"failed\"\n\nexport interface UploadProgress {\n phase: UploadPhase\n bytesTransferred: number\n totalBytes: number\n /** 0 to 1. Exactly 1 only once every byte is acknowledged by Storage. */\n fraction: number\n /** 1 for the first run, 2 for the first retry, and so on. */\n attempt: number\n}\n\nexport interface ImageSize {\n width: number\n height: number\n}\n\n/**\n * Reads the pixel size of an image, or returns null when it cannot.\n *\n * Injected rather than imported so the upload path stays testable without a DOM, and so a\n * host that has a better decoder than the browser can supply one.\n */\nexport type ImageSizeReader = (file: Blob, type: string) => Promise<ImageSize | null>\n\nexport interface MediaUploadDeps {\n storage: FirebaseStorage\n media: MediaAdapter\n /** Whose upload this is. Written into the object's custom metadata; see storage.rules. */\n currentUser: () => AuthUser\n /** Defaults to `browserImageSize`. */\n readImageSize?: ImageSizeReader\n}\n\nexport interface MediaUploadOptions {\n onProgress?: (progress: UploadProgress) => void\n /** Alt text captured in the upload form, if any. Editable afterwards. */\n alt?: string | null\n}\n\nexport type MediaUploadErrorCode =\n \"rejected\" | \"cancelled\" | \"storage-failed\" | \"metadata-failed\"\n\nexport class MediaUploadError extends Error {\n override readonly name = \"MediaUploadError\"\n readonly code: MediaUploadErrorCode\n /** Set when the file itself was refused, so the UI can point at the offending rule. */\n readonly rejection: MediaRejection | null\n /**\n * The Storage object that outlived the failed upload, when cleanup could not remove it.\n * Null means nothing was left behind, which is the ordinary case.\n */\n readonly orphanedPath: string | null\n\n constructor(\n code: MediaUploadErrorCode,\n message: string,\n detail: { rejection?: MediaRejection; orphanedPath?: string; cause?: unknown } = {},\n ) {\n super(message, detail.cause === undefined ? undefined : { cause: detail.cause })\n this.code = code\n this.rejection = detail.rejection ?? null\n this.orphanedPath = detail.orphanedPath ?? null\n }\n}\n\nexport interface MediaUpload {\n /** Allocated before anything is written, because the object path contains it. */\n readonly mediaId: string\n /** The Storage object path this upload writes to, on every attempt. */\n readonly path: string\n readonly fileName: string\n progress(): UploadProgress\n /** Runs the upload. Rejects with `MediaUploadError`; resolves with the stored item. */\n start(): Promise<MediaItem>\n /**\n * Runs it again after a failure or a cancellation, to the same id and the same path, so\n * a retried upload overwrites its own remains instead of accumulating them.\n */\n retry(): Promise<MediaItem>\n /** Aborts an attempt in flight. A no-op before the first attempt and after success. */\n cancel(): void\n}\n\n/** Prepares an upload without starting it, which is what gives the caller a cancel handle. */\nexport function createMediaUpload(\n deps: MediaUploadDeps,\n file: File,\n options: MediaUploadOptions = {},\n): MediaUpload {\n return prepare(deps, file, options, null)\n}\n\n/**\n * Replacing the bytes of an item that is already in the library.\n *\n * Replace keeps the media id and the Storage object path, which is what makes it a replace\n * rather than a delete followed by an upload: every entry that references the item goes on\n * referencing the same item, and no reference has to be repaired by hand.\n *\n * Keeping the path has one consequence that is a rule rather than an accident: the\n * replacement must be of the same MIME type as the original. The object path ends in the\n * file name, `storage.rules` requires the name's extension to match the declared content\n * type, and `firestore.rules` forbids a media document's `path` from ever changing. A JPEG\n * cannot therefore take the place of a PNG. That is refused here, with a sentence, rather\n * than left to surface as a permission error.\n *\n * What the caller must do afterwards, and what this function deliberately does not do:\n * overwriting an object mints a new download URL, so the `url` denormalized into every\n * referencing entry is now stale. `media.refreshReferences(mediaId, url)` rewrites them.\n * It is a separate call because it is a batch of entry writes with its own partial-failure\n * story, and burying that inside an upload handle would hide it.\n *\n * The one state it cannot make safe: if the bytes land and the document update fails, the\n * object holds the new file while the record still describes the old one, and the old url\n * is already dead. There is nothing to compensate with, because deleting the object would\n * destroy the item outright. The error says so plainly and asks for another attempt.\n */\nexport function createMediaReplace(\n deps: MediaUploadDeps,\n existing: MediaItem,\n file: File,\n options: MediaUploadOptions = {},\n): MediaUpload {\n return prepare(deps, file, options, existing)\n}\n\nfunction prepare(\n deps: MediaUploadDeps,\n file: File,\n options: MediaUploadOptions,\n existing: MediaItem | null,\n): MediaUpload {\n const readImageSize = deps.readImageSize ?? browserImageSize\n const mediaId = existing?.id ?? deps.media.reserveId()\n const fileName =\n existing === null ? safeFileName(file.name, file.type) : lastSegment(existing.path)\n const path = existing?.path ?? storageObjectPath(mediaId, fileName)\n\n let attempt = 0\n let task: UploadTask | null = null\n let cancelled = false\n let state: UploadProgress = {\n phase: \"idle\",\n bytesTransferred: 0,\n totalBytes: file.size,\n fraction: 0,\n attempt: 0,\n }\n\n function report(next: Partial<UploadProgress>): void {\n state = { ...state, ...next }\n options.onProgress?.(state)\n }\n\n async function run(): Promise<MediaItem> {\n // The client-side check. The rules repeat every part of it, so this is the courtesy\n // that explains the refusal, not the thing that enforces it.\n const rejection = checkMediaFile({\n name: file.name,\n type: file.type,\n size: file.size,\n })\n if (rejection !== null) {\n report({ phase: \"failed\", attempt: attempt + 1 })\n throw new MediaUploadError(\"rejected\", rejection.message, { rejection })\n }\n\n if (existing !== null && normalizeMediaType(file.type) !== existing.type) {\n // The object path is fixed for the life of a media item, and the rules tie the\n // path's extension to the declared type. See the note on createMediaReplace.\n const message = `A ${existing.type} file can only be replaced by another ${existing.type} file. Upload ${file.type || \"this file\"} as a new item instead.`\n report({ phase: \"failed\", attempt: attempt + 1 })\n throw new MediaUploadError(\"rejected\", message, {\n rejection: { code: \"type-not-allowed\", message },\n })\n }\n\n attempt += 1\n cancelled = false\n report({\n phase: \"uploading\",\n bytesTransferred: 0,\n fraction: 0,\n attempt,\n totalBytes: file.size,\n })\n\n // Created synchronously, before the first await, so a cancel() that arrives in the same\n // tick as start() still finds a task to cancel.\n const uploadTask = uploadBytesResumable(ref(deps.storage, path), file, {\n contentType: file.type,\n // The bucket has no idea who wrote an object unless it is told. The rules read this\n // back on update and delete, which is what lets the uploader clean up after\n // themselves without granting an author delete rights over everybody's files.\n customMetadata: { uploadedBy: deps.currentUser().uid, mediaId },\n })\n task = uploadTask\n\n uploadTask.on(\"state_changed\", (snapshot) => {\n const total = snapshot.totalBytes === 0 ? file.size : snapshot.totalBytes\n report({\n phase: \"uploading\",\n bytesTransferred: snapshot.bytesTransferred,\n totalBytes: total,\n fraction: total === 0 ? 0 : snapshot.bytesTransferred / total,\n })\n })\n\n try {\n await uploadTask\n } catch (thrown) {\n task = null\n const code = (thrown as { code?: string }).code ?? \"\"\n if (cancelled || code === \"storage/canceled\") {\n report({ phase: \"cancelled\" })\n throw new MediaUploadError(\"cancelled\", \"The upload was cancelled.\", {\n cause: thrown,\n })\n }\n report({ phase: \"failed\" })\n throw new MediaUploadError(\n \"storage-failed\",\n \"The file could not be uploaded. Nothing was stored, so retrying is safe.\",\n { cause: thrown },\n )\n }\n\n task = null\n report({\n phase: \"storing\",\n bytesTransferred: file.size,\n totalBytes: file.size,\n fraction: 1,\n })\n\n const url = await getDownloadURL(uploadTask.snapshot.ref)\n const size = await imageSize(file, readImageSize)\n\n try {\n const item =\n existing === null\n ? await deps.media.create(\n {\n name: file.name,\n type: file.type,\n size: file.size,\n url,\n path,\n alt: options.alt ?? null,\n width: size?.width ?? null,\n height: size?.height ?? null,\n },\n mediaId,\n )\n : // Alt text is not in the patch. It describes what the picture shows and it is\n // the editor's sentence, not the file's, so a replace keeps it and the library\n // says so, rather than discarding a description nobody asked it to discard.\n await deps.media.update(mediaId, {\n name: file.name,\n size: file.size,\n url,\n width: size?.width ?? null,\n height: size?.height ?? null,\n })\n report({ phase: \"done\" })\n return item\n } catch (thrown) {\n if (existing !== null) {\n report({ phase: \"failed\" })\n throw new MediaUploadError(\n \"metadata-failed\",\n `The new file was stored but the library record still describes the old one, and the old link no longer works. Reload the library and replace \"${existing.name}\" again.`,\n { cause: thrown },\n )\n }\n\n const removed = await removeOrphan(deps.storage, path)\n report({ phase: \"failed\" })\n throw new MediaUploadError(\n \"metadata-failed\",\n removed\n ? \"The file was uploaded but could not be recorded, so the upload was undone. Try again.\"\n : `The file was uploaded but could not be recorded, and the uploaded copy at ${path} could not be removed. It is not in the library and has to be cleaned up.`,\n removed ? { cause: thrown } : { cause: thrown, orphanedPath: path },\n )\n }\n }\n\n return {\n mediaId,\n path,\n fileName,\n progress: () => state,\n start: run,\n retry: run,\n cancel() {\n cancelled = true\n task?.cancel()\n },\n }\n}\n\n/** The file name a media item's object path ends in. Never empty for a stored item. */\nfunction lastSegment(path: string): string {\n return path.slice(path.lastIndexOf(\"/\") + 1)\n}\n\n/** Prepares and starts an upload in one call, for callers with nothing to cancel. */\nexport function uploadMedia(\n deps: MediaUploadDeps,\n file: File,\n options: MediaUploadOptions = {},\n): Promise<MediaItem> {\n return createMediaUpload(deps, file, options).start()\n}\n\n/**\n * Deletes an object left behind by a half-finished upload. Reports whether it is gone.\n *\n * True means the bucket confirmed it: the delete succeeded, or the object was already\n * reported missing. Anything else, a permission denial above all, is false, so the caller\n * names the path instead of claiming a cleanup that did not happen.\n *\n * One asymmetry worth knowing, because it looks like a bug from the outside: the storage\n * rules let an author delete only an object whose custom metadata names them, and a path\n * with no object has no metadata to name anyone. An author asking to remove something that\n * was never there is therefore denied rather than told it is missing, and gets false. That\n * is the safe direction: the only caller is the compensating delete after a failed metadata\n * write, where the object demonstrably exists and belongs to the caller.\n */\nexport async function removeOrphan(\n storage: FirebaseStorage,\n path: string,\n): Promise<boolean> {\n try {\n await deleteObject(ref(storage, path))\n return true\n } catch (thrown) {\n return (thrown as { code?: string }).code === \"storage/object-not-found\"\n }\n}\n\n/**\n * Pixel dimensions, for images only, and never at the cost of the upload.\n *\n * Non-images are not asked, which is why `width` and `height` are null for a PDF or an MP4\n * rather than zero. For an image the answer comes from the browser's own decoder through\n * `createImageBitmap`, which is the only image processing available here: there is no\n * Cloud Function, no service account and no server-side decoder in this product, and\n * adding an image library to a package that ships to customer sites would be a heavy price\n * for two numbers.\n *\n * When the decoder cannot read the file, or the environment has none, both stay null and\n * the upload proceeds. The bytes are fine and the file is what the editor asked for; losing\n * it over a decode failure would be the worse outcome. A null pair is a documented state:\n * `MediaItem.width` and `.height` are nullable, and any consumer that needs a ratio must\n * handle their absence rather than assume it cannot happen.\n */\nasync function imageSize(file: File, reader: ImageSizeReader): Promise<ImageSize | null> {\n if (mediaTypeRule(file.type)?.category !== \"image\") return null\n\n try {\n return await reader(file, file.type)\n } catch {\n return null\n }\n}\n\n/** The default reader. Returns null wherever `createImageBitmap` is unavailable. */\nexport const browserImageSize: ImageSizeReader = async (file) => {\n const decode = (globalThis as { createImageBitmap?: typeof createImageBitmap })\n .createImageBitmap\n if (decode === undefined) return null\n\n const bitmap = await decode(file)\n try {\n return { width: bitmap.width, height: bitmap.height }\n } finally {\n // Frees the decoded surface immediately instead of waiting for a collection that may\n // not come until several more uploads have been decoded.\n bitmap.close()\n }\n}\n\n/**\n * The upload seam the admin is written against.\n *\n * The media library screen starts, cancels and retries uploads, and it lives under\n * `src/admin/**`, where Firebase may not be imported. It therefore takes a `MediaUploader`\n * and the host builds one here, exactly as the relation picker takes a `RelationSource`.\n */\nexport function createMediaUploader(deps: MediaUploadDeps): MediaUploader {\n return {\n create: (file, options) => createMediaUpload(deps, file, options),\n replace: (existing, file, options) =>\n createMediaReplace(deps, existing, file, options),\n }\n}\n"]}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Auth } from 'firebase/auth';
|
|
2
|
+
import { Firestore } from 'firebase/firestore';
|
|
3
|
+
import { e as Role, b as AuthUser } from './auth-B2gBtHl5.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Custom claim sync: how a role reaches `request.auth.token.role`.
|
|
7
|
+
*
|
|
8
|
+
* The security rules read the role from a custom claim so a rule never has to read a user
|
|
9
|
+
* document. Nothing in the Firebase client SDK can set a custom claim: minting one needs a
|
|
10
|
+
* privileged context (the Admin SDK with a service account, a Cloud Function, or the Auth
|
|
11
|
+
* emulator's admin endpoints). This file is therefore an interface plus the one
|
|
12
|
+
* implementation this repository can honestly provide today, and a loud failure for every
|
|
13
|
+
* other context, so the gap is visible rather than silently open.
|
|
14
|
+
*
|
|
15
|
+
* See .docs/decisions/2026-09-18-claim-sync-seam.md for what production has to plug in.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The exact claim shape `firestore.rules` reads. Keep this in step with the `role()` and
|
|
20
|
+
* `enabled()` helpers there: a key renamed on one side locks everyone out on the other.
|
|
21
|
+
*/
|
|
22
|
+
interface ClaimSet {
|
|
23
|
+
role: Role;
|
|
24
|
+
disabled: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The seam. The CMS calls `sync` whenever the stored role or disabled flag changes, or
|
|
28
|
+
* when a session is found carrying a token that disagrees with the user document.
|
|
29
|
+
*
|
|
30
|
+
* An implementation must be idempotent: sign-in reconciles claims on every session, so
|
|
31
|
+
* syncing a value that is already current has to be a no-op rather than an error.
|
|
32
|
+
*/
|
|
33
|
+
interface ClaimSync {
|
|
34
|
+
sync(uid: string, claims: ClaimSet): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/** A claim could not be written, so the role never reached the token the rules read. */
|
|
37
|
+
declare class ClaimSyncError extends Error {
|
|
38
|
+
readonly name = "ClaimSyncError";
|
|
39
|
+
readonly uid: string;
|
|
40
|
+
constructor(uid: string, message: string);
|
|
41
|
+
}
|
|
42
|
+
interface EmulatorClaimSyncOptions {
|
|
43
|
+
/** `host:port` of the Auth emulator, 9099 by default per firebase.json. */
|
|
44
|
+
authHost: string;
|
|
45
|
+
projectId: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Claim sync against the Auth emulator.
|
|
49
|
+
*
|
|
50
|
+
* The emulator exposes the Identity Toolkit admin endpoints to anyone presenting the
|
|
51
|
+
* literal bearer token `owner`, which is how a local process sets a role without a service
|
|
52
|
+
* account. That token means nothing outside the emulator, so this refuses any host that is
|
|
53
|
+
* not loopback: shipping it at a real Identity Toolkit endpoint would send an unauthorized
|
|
54
|
+
* request and, worse, suggest that production claim sync is handled.
|
|
55
|
+
*/
|
|
56
|
+
declare function createEmulatorClaimSync(options: EmulatorClaimSyncOptions): ClaimSync;
|
|
57
|
+
/**
|
|
58
|
+
* The deliberate hole-with-a-sign-on-it, for any host that has no privileged context wired
|
|
59
|
+
* up yet. It fails the write instead of pretending the claim landed, so a deployment that
|
|
60
|
+
* forgot to provide one finds out at the first sign-up rather than at the first breach.
|
|
61
|
+
*
|
|
62
|
+
* Failing here is fail-closed: without the claim the rules see role `none`, and `enabled()`
|
|
63
|
+
* denies every collection.
|
|
64
|
+
*/
|
|
65
|
+
declare function createUnavailableClaimSync(reason?: string): ClaimSync;
|
|
66
|
+
|
|
67
|
+
/** The stored profile, exactly the fields M3 promises, plus the uid the path carries. */
|
|
68
|
+
interface StoredUser {
|
|
69
|
+
uid: string;
|
|
70
|
+
email: string;
|
|
71
|
+
displayName: string | null;
|
|
72
|
+
role: Role;
|
|
73
|
+
createdAt: string;
|
|
74
|
+
disabled: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** Fields a caller may change on an existing profile. */
|
|
77
|
+
interface UserPatch {
|
|
78
|
+
displayName?: string | null;
|
|
79
|
+
role?: Role;
|
|
80
|
+
disabled?: boolean;
|
|
81
|
+
}
|
|
82
|
+
interface NewUser {
|
|
83
|
+
uid: string;
|
|
84
|
+
email: string;
|
|
85
|
+
displayName?: string | null;
|
|
86
|
+
role: Role;
|
|
87
|
+
}
|
|
88
|
+
/** The shape the rest of the CMS asks `can()` about. */
|
|
89
|
+
declare function toAuthUser(stored: StoredUser): AuthUser;
|
|
90
|
+
declare function readUser(db: Firestore, uid: string): Promise<StoredUser | null>;
|
|
91
|
+
/**
|
|
92
|
+
* Creates the profile. `createdAt` is a server timestamp rather than a client clock,
|
|
93
|
+
* because a first-run bootstrap is decided by ordering and a client clock can be wrong by
|
|
94
|
+
* years.
|
|
95
|
+
*/
|
|
96
|
+
declare function writeUser(db: Firestore, user: NewUser): Promise<StoredUser>;
|
|
97
|
+
/**
|
|
98
|
+
* Patches the profile. Role and disabled are included here for completeness, but a caller
|
|
99
|
+
* that changes either without syncing the claim leaves the rules reading a stale token, so
|
|
100
|
+
* go through `setRole` and `setDisabled` on the auth client instead.
|
|
101
|
+
*/
|
|
102
|
+
declare function patchUser(db: Firestore, uid: string, patch: UserPatch): Promise<StoredUser>;
|
|
103
|
+
|
|
104
|
+
/** Every way this layer refuses, as a code a UI can branch on without parsing a message. */
|
|
105
|
+
type AuthErrorCode = "disabled" | "invalid-credentials";
|
|
106
|
+
declare class AuthError extends Error {
|
|
107
|
+
readonly name = "AuthError";
|
|
108
|
+
readonly code: AuthErrorCode;
|
|
109
|
+
constructor(code: AuthErrorCode, message: string);
|
|
110
|
+
}
|
|
111
|
+
interface HuskAuthDeps {
|
|
112
|
+
auth: Auth;
|
|
113
|
+
db: Firestore;
|
|
114
|
+
/**
|
|
115
|
+
* Required, not optional. See claims.ts: an absent claim writer is a security decision,
|
|
116
|
+
* so it has to be made explicitly, even when the decision is
|
|
117
|
+
* `createUnavailableClaimSync()`.
|
|
118
|
+
*/
|
|
119
|
+
claimSync: ClaimSync;
|
|
120
|
+
/**
|
|
121
|
+
* The role every account after the first one gets. The least privileged role by default:
|
|
122
|
+
* a new account should never arrive with more rights than someone had to grant it.
|
|
123
|
+
*
|
|
124
|
+
* Raising it above `author` only works where an admin is doing the provisioning, because
|
|
125
|
+
* firestore.rules pins a self-created profile to `author`. That is the rule doing its
|
|
126
|
+
* job, not a limitation to work around.
|
|
127
|
+
*/
|
|
128
|
+
defaultRole?: Role;
|
|
129
|
+
}
|
|
130
|
+
interface SignUpInput {
|
|
131
|
+
email: string;
|
|
132
|
+
password: string;
|
|
133
|
+
displayName?: string;
|
|
134
|
+
}
|
|
135
|
+
interface HuskAuth {
|
|
136
|
+
/** Signs in, reconciles the claim, and refuses a disabled account. */
|
|
137
|
+
signIn(email: string, password: string): Promise<AuthUser>;
|
|
138
|
+
/** Creates an account. The first one in an empty project becomes admin. */
|
|
139
|
+
signUp(input: SignUpInput): Promise<AuthUser>;
|
|
140
|
+
signOut(): Promise<void>;
|
|
141
|
+
/** The signed-in user resolved against their profile, or null when signed out. */
|
|
142
|
+
currentUser(): Promise<AuthUser | null>;
|
|
143
|
+
/** Subscribes to session changes. Returns the unsubscribe function. */
|
|
144
|
+
onChange(listener: (user: AuthUser | null) => void, onError?: (error: unknown) => void): () => void;
|
|
145
|
+
getUser(uid: string): Promise<StoredUser | null>;
|
|
146
|
+
/** Profile edits that carry no privilege. Role and disabled are not settable here. */
|
|
147
|
+
updateProfile(uid: string, patch: Omit<UserPatch, "role" | "disabled">): Promise<StoredUser>;
|
|
148
|
+
/** Changes a role in the document and in the claim, in that order. */
|
|
149
|
+
setRole(uid: string, role: Role): Promise<StoredUser>;
|
|
150
|
+
/** Disables or re-enables an account, document and claim together. */
|
|
151
|
+
setDisabled(uid: string, disabled: boolean): Promise<StoredUser>;
|
|
152
|
+
}
|
|
153
|
+
declare function createHuskAuth(deps: HuskAuthDeps): HuskAuth;
|
|
154
|
+
|
|
155
|
+
export { AuthError as A, type ClaimSet as C, type EmulatorClaimSyncOptions as E, type HuskAuth as H, type NewUser as N, type SignUpInput as S, type UserPatch as U, type AuthErrorCode as a, type ClaimSync as b, ClaimSyncError as c, type HuskAuthDeps as d, type StoredUser as e, createEmulatorClaimSync as f, createHuskAuth as g, createUnavailableClaimSync as h, patchUser as p, readUser as r, toAuthUser as t, writeUser as w };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { C as CmsAdapter, D as DEFAULT_PAGE_SIZE, E as EntryFilter, a as EntryInputData, F as FieldDataAdapter, b as FieldPurgeOptions, c as FieldPurgeResult, d as FilterOperator, L as ListQuery, M as MAX_FIELD_PURGE, e as MAX_PAGE_SIZE, f as MEDIA_USAGE_LIMIT, g as MediaAdapter, h as MediaInput, i as MediaQuery, j as MediaRefreshResult, k as MediaUsage, l as MediaUsageQuery, m as MediaUsageReference, n as MigrationAdapter, o as MigrationWrite, P as Page, S as SchemaAdapter, p as SettingsAdapter, q as SortSpec, r as StoredEntryRecord, s as normalizeLimit } from './adapter-C_0mhENy.js';
|
|
2
|
+
export { BuildListQueryOptions, ClassifiedError, ColumnAlign, ColumnKind, ENTRY_COLLECTION_GROUP, ERROR_CATEGORIES, EntryMigration, EntryUniqueOptions, ErrorCategory, ErrorContext, ErrorOrigin, ErrorTreatment, FALLBACK_SORT, FieldKeyAvailability, FieldKeyRejection, FieldKeyState, FieldOptionIssue, FieldRow, FirestoreIndex, FirestoreIndexField, FormField, FormIssue, HUSK_TAG_PREFIX, INITIAL_FIELD_KEY, INITIAL_SCHEMA_VERSION, IndexOrder, ListColumn, ListColumnOptions, ListFilterDescriptor, ListFilterOption, ListViewState, LockedTypeError, MAX_ENTRY_INDEXES, MAX_FIELD_COLUMNS, MAX_MEDIA_REFS, MEDIA_REFS_KEY, MIGRATION_BATCH_SIZE, MIGRATION_MAX_BATCHES, MigrationContext, MigrationFailure, MigrationRegistry, MigrationRunDeps, MigrationRunOptions, MigrationRunResult, NewContentTypeInput, PermissionError, RESERVED_TYPE_SLUGS, RegistryStore, RemovalContext, RestoreOptions, STATUS_EFFECT, SchemaError, SingletonError, SlugAvailability, SlugRejection, StatusSubject, StatusTransition, UNTITLED_ENTRY, VALIDATION_RULE_CONTROLS, ValidationError, ValidationRuleControl, ValidationRuleName, WriteLimitError, addField, applyPlan, assertTypeSlugAvailable, assertValidContentType, buildCountQuery, buildListQuery, canSaveEntry, canSearch, checkFieldKey, checkNestedFieldKey, checkTypeSlug, classifyError, collectMediaIds, confirmationMatches, contentTypeTag, createContentType, createMigrationRegistry, createRegistryStore, defaultListSort, defineContentType, deleteConfirmationName, deleteConfirmationPhrase, deniedError, draftIssues, entryChangedSince, entrySlugTag, entryTag, entryTags, entryTitle, entryUniqueCheck, errorsUnder, fieldKeyState, fieldMapSignature, fieldOptionIssues, forgetRemovedField, formFields, formValuesFromEntry, indexCovered, initialFormValues, initialListState, isFilterableField, isHuskTag, isNarrowed, isSearching, listColumns, listFilterQuery, listFilters, mediaTag, mergeIssues, missingEntryIndexes, missingError, moveFieldRow, moveItem, newContentType, newFieldDefinition, nextSchemaVersion, nextSort, normalizeConfirmation, orderedFieldKeys, orderedFields, removeField, removedFieldEntries, requiredEntryIndexes, restoreField, retargetMediaUrls, runMigration, sameDefinition, schemaVersionOf, settingsTag, slugFieldKey, sortableFieldKeys, staleWriteError, statusTransitions, storageShapeChanged, storageSignature, storageSignatureOf, storesTheSameWay, suggestFieldKey, treatmentFor, unroutableIssues, valuesChanged, withFieldOption, withFieldRows, withValidationRule } from './core/index.js';
|
|
3
|
+
export { C as Cms, a as CmsConfig, b as CmsCountQuery, c as CmsQuery, d as CmsReadConfig, e as CollectionReader, f as ContentTypeMap, E as EntryOf, K as KnownSlug, M as MediaReader, S as SettingsReader, g as SingleReader, h as SlugOf, i as assertDeletable, j as assertListable, k as assertSingletonCreatable, l as assertSingletonEntryId, m as createCMS, n as isCollection, o as isSingleton, s as singletonEntryId } from './cms-9fSA3uQV.js';
|
|
4
|
+
export { A as ALLOWED_MEDIA_TYPES, C as Clock, F as FIRESTORE_DOCUMENT_BYTES, M as MAX_ARRAY_ITEMS, a as MAX_CONTENT_TYPE_FIELDS, b as MAX_ENTRY_FIELDS, c as MAX_IMAGE_BYTES, d as MAX_MEDIA_BYTES, e as MAX_NESTING_DEPTH, f as MAX_SETTINGS_FIELDS, g as MAX_WRITE_BYTES, h as MEDIA_ACCEPT_ATTRIBUTE, i as MIN_WRITE_INTERVAL_MS, j as MediaCategory, k as MediaFileFacts, l as MediaRejection, m as MediaRejectionCode, n as MediaTypeRule, P as PayloadLimits, W as WRITE_BURST_LIMIT, o as WRITE_BURST_WINDOW_MS, p as WriteLimitCode, q as WriteLimitRejection, r as WriteRateLimiter, s as approximateBytes, t as checkMediaFile, u as checkWritePayload, v as createWriteRateLimiter, w as fileExtension, x as mediaTypeRule, y as normalizeMediaType, z as safeFileName } from './write-limits-Djmm6L3R.js';
|
|
5
|
+
export { I as InvalidContentType, R as Registry, a as RegistryEntry, b as assertSchemaWritable, c as buildRegistry, i as isSchemaReadOnly } from './registry-CUC25qay.js';
|
|
6
|
+
export { A as ACTIONS, a as ADMIN_ACTIONS, D as Decision, E as EntryOwnership, P as PERMISSION_DEFAULTS, b as PermissionContext, c as can, i as isEntryAction, r as resolveDecision } from './can-Chy7zmx7.js';
|
|
7
|
+
export { MATRIX_ACTIONS, MATRIX_LIMITS, MatrixCell, MatrixDecision, MatrixRow, assertValidPermissionMatrix, defaultRoles, matrixDecision, normalizePermissionMatrix, permissionMatrixRows, withMatrixRole } from './permissions/index.js';
|
|
8
|
+
export { A as AnyFieldTypeSpec, F as FieldEditorSpec, a as FieldInputComponent, b as FieldInputProps, c as FieldOptionControl, d as FieldOptionDescriptor, e as FieldTsType, f as FieldTypeRegistry, g as FieldTypeSpec, N as NestedConverter, T as TsTypeResolver, U as UNKNOWN_TS_TYPE, V as ValidateCtx, r as registerFieldTypes, h as resolveTsType } from './field-type-D25Vojb0.js';
|
|
9
|
+
export { PATTERN_PRESETS, ValidateOptions, uniqueFieldPaths, validateEntry } from './schema/index.js';
|
|
10
|
+
export { A as Action, a as AdminAction, b as AuthUser, E as ENTRY_ACTIONS, c as EntryAction, P as Permission, d as PermissionMatrix, R as ROLES, e as Role } from './auth-B2gBtHl5.js';
|
|
11
|
+
export { B as BooleanField, D as DateField, F as FIELD_TYPES, a as FieldDefinition, b as FieldInputValues, c as FieldMap, d as FieldType, e as FieldValue, f as FieldValues, g as FileField, I as ImageField, M as MediaItem, h as MediaReference, N as NumberField, R as RelationField, i as RepeaterField, j as RichTextValue, k as RichtextField, S as SelectField, l as SelectOption, m as SlugField, T as TextField, n as TextareaField, V as ValidationRules } from './field-Diyf5siF.js';
|
|
12
|
+
export { A as AnyContentTypeShape, C as ContentType, a as ContentTypeInputValues, b as ContentTypeKind, c as ContentTypeLike, d as ContentTypeSource, e as ContentTypeValues, E as ENTRY_STATUSES, f as Entry, g as EntryInput, h as EntryMeta, i as EntryStatus, F as FieldError, R as RESERVED_FIELD_KEYS, j as RemovedField, k as RemovedFieldMap, l as ReservedFieldKey } from './entry-D3kiE8T1.js';
|
|
13
|
+
export { HuskConfig } from './types/index.js';
|
|
14
|
+
import 'react';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { UNKNOWN_TS_TYPE, registerFieldTypes, resolveTsType } from './chunk-ZW2PUSVS.js';
|
|
2
|
+
import './chunk-VLGR5PZX.js';
|
|
3
|
+
export { ENTRY_COLLECTION_GROUP, ERROR_CATEGORIES, FALLBACK_SORT, HUSK_TAG_PREFIX, MAX_ENTRY_INDEXES, MAX_FIELD_COLUMNS, MIGRATION_BATCH_SIZE, MIGRATION_MAX_BATCHES, STATUS_EFFECT, UNTITLED_ENTRY, VALIDATION_RULE_CONTROLS, applyPlan, buildCountQuery, buildListQuery, canSaveEntry, canSearch, checkFieldKey, checkNestedFieldKey, classifyError, confirmationMatches, contentTypeTag, createMigrationRegistry, createRegistryStore, defaultListSort, deleteConfirmationName, deleteConfirmationPhrase, deniedError, draftIssues, entryChangedSince, entrySlugTag, entryTag, entryTags, entryTitle, entryUniqueCheck, errorsUnder, fieldOptionIssues, formFields, formValuesFromEntry, indexCovered, initialFormValues, initialListState, isFilterableField, isHuskTag, isNarrowed, isSearching, listColumns, listFilterQuery, listFilters, mediaTag, mergeIssues, missingEntryIndexes, missingError, moveFieldRow, moveItem, newFieldDefinition, nextSort, normalizeConfirmation, orderedFieldKeys, orderedFields, requiredEntryIndexes, runMigration, sameDefinition, settingsTag, sortableFieldKeys, staleWriteError, statusTransitions, suggestFieldKey, treatmentFor, unroutableIssues, valuesChanged, withFieldOption, withFieldRows, withValidationRule } from './chunk-6E3ZS5Z5.js';
|
|
4
|
+
export { createCMS } from './chunk-3I6337WJ.js';
|
|
5
|
+
export { PATTERN_PRESETS, uniqueFieldPaths, validateEntry } from './chunk-FHYWJCG6.js';
|
|
6
|
+
export { ENTRY_STATUSES } from './chunk-ORMYARXD.js';
|
|
7
|
+
export { ALLOWED_MEDIA_TYPES, DEFAULT_PAGE_SIZE, FIRESTORE_DOCUMENT_BYTES, INITIAL_SCHEMA_VERSION, MAX_ARRAY_ITEMS, MAX_CONTENT_TYPE_FIELDS, MAX_ENTRY_FIELDS, MAX_FIELD_PURGE, MAX_IMAGE_BYTES, MAX_MEDIA_BYTES, MAX_MEDIA_REFS, MAX_NESTING_DEPTH, MAX_PAGE_SIZE, MAX_SETTINGS_FIELDS, MAX_WRITE_BYTES, MEDIA_ACCEPT_ATTRIBUTE, MEDIA_REFS_KEY, MEDIA_USAGE_LIMIT, MIN_WRITE_INTERVAL_MS, WRITE_BURST_LIMIT, WRITE_BURST_WINDOW_MS, addField, approximateBytes, assertSchemaWritable, buildRegistry, checkMediaFile, checkWritePayload, collectMediaIds, createWriteRateLimiter, fieldKeyState, fieldMapSignature, fileExtension, forgetRemovedField, isSchemaReadOnly, mediaTypeRule, nextSchemaVersion, normalizeLimit, normalizeMediaType, removeField, removedFieldEntries, restoreField, retargetMediaUrls, safeFileName, schemaVersionOf, slugFieldKey, storageShapeChanged, storageSignature, storageSignatureOf, storesTheSameWay } from './chunk-JAE7HHMM.js';
|
|
8
|
+
export { INITIAL_FIELD_KEY, RESERVED_TYPE_SLUGS, assertTypeSlugAvailable, assertValidContentType, checkTypeSlug, createContentType, defineContentType, newContentType } from './chunk-ROLKRJWI.js';
|
|
9
|
+
export { FIELD_TYPES, RESERVED_FIELD_KEYS } from './chunk-SOTQ3WP5.js';
|
|
10
|
+
export { assertDeletable, assertListable, assertSingletonCreatable, assertSingletonEntryId, isCollection, isSingleton, singletonEntryId } from './chunk-R4O7APIX.js';
|
|
11
|
+
import './chunk-77JDRXXU.js';
|
|
12
|
+
export { MATRIX_ACTIONS, MATRIX_LIMITS, assertValidPermissionMatrix, defaultRoles, matrixDecision, normalizePermissionMatrix, permissionMatrixRows, withMatrixRole } from './chunk-TCFFHTY3.js';
|
|
13
|
+
export { ACTIONS, ADMIN_ACTIONS, PERMISSION_DEFAULTS, can, isEntryAction, resolveDecision } from './chunk-NPDBQBLW.js';
|
|
14
|
+
export { ENTRY_ACTIONS, ROLES } from './chunk-TS7Y7GEZ.js';
|
|
15
|
+
export { LockedTypeError, PermissionError, SchemaError, SingletonError, ValidationError, WriteLimitError } from './chunk-4Z6P3XCE.js';
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { D as Decision } from '../can-Chy7zmx7.js';
|
|
2
|
+
export { A as ACTIONS, a as ADMIN_ACTIONS, E as EntryOwnership, P as PERMISSION_DEFAULTS, b as PermissionContext, c as can, i as isEntryAction, r as resolveDecision } from '../can-Chy7zmx7.js';
|
|
3
|
+
import { c as EntryAction, e as Role, d as PermissionMatrix } from '../auth-B2gBtHl5.js';
|
|
4
|
+
import { c as ContentTypeLike } from '../entry-D3kiE8T1.js';
|
|
5
|
+
import '../field-Diyf5siF.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The per-content-type permission matrix: the editable half of the truth table.
|
|
9
|
+
*
|
|
10
|
+
* `can()` is the resolver. This module is everything around it that is not a decision: what
|
|
11
|
+
* a stored matrix is allowed to look like, what one means once the role defaults are
|
|
12
|
+
* applied, and how an editing screen turns a checkbox into the next matrix. It is here
|
|
13
|
+
* rather than in `can.ts` so the resolver stays a pure lookup that a render can call in a
|
|
14
|
+
* loop.
|
|
15
|
+
*
|
|
16
|
+
* The one rule that governs the whole module: **a matrix narrows, it never widens.** The
|
|
17
|
+
* effective answer is the intersection of the role default and the matrix, so listing
|
|
18
|
+
* `author` under `delete` does not give an author a delete. That is not a simplification
|
|
19
|
+
* for its own sake. The security rules enforce the role defaults directly from the token,
|
|
20
|
+
* and only consult the matrix to narrow further, because widening in the rules would mean
|
|
21
|
+
* trusting a document to grant a privilege. A matrix that could widen would therefore be
|
|
22
|
+
* accepted by this resolver and refused by the database, which is the one thing a
|
|
23
|
+
* permission system must never do.
|
|
24
|
+
*/
|
|
25
|
+
/** Every action a matrix can speak about. An admin action is never per content type. */
|
|
26
|
+
declare const MATRIX_ACTIONS: readonly EntryAction[];
|
|
27
|
+
/** What a role may do with an action once the defaults and the matrix are both applied. */
|
|
28
|
+
type MatrixDecision = Decision;
|
|
29
|
+
/**
|
|
30
|
+
* The limitations the Developer area has to state rather than imply.
|
|
31
|
+
*
|
|
32
|
+
* A screen that draws a checkbox for something no layer enforces is worse than a screen
|
|
33
|
+
* that omits it: it reads as a guarantee. These are the exact gaps, in the words the
|
|
34
|
+
* screen should use.
|
|
35
|
+
*/
|
|
36
|
+
declare const MATRIX_LIMITS: {
|
|
37
|
+
readonly admin: "An admin is never restricted by a matrix. Removing every other role from an action leaves it admin-only; there is no way to lock an admin out of their own project.";
|
|
38
|
+
readonly ownership: "An author may only ever update or delete entries they created. Ticking an author here grants the action on their own entries, never on somebody else's.";
|
|
39
|
+
readonly narrowing: "A matrix can only take permissions away. Ticking a role that its own role does not grant changes nothing, and the row says so.";
|
|
40
|
+
readonly publicRead: "Denying read hides entries from the admin, not from the public website. A published entry is readable by anyone with its address by design; unpublish it to take it down.";
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Refuses a malformed matrix before it is stored.
|
|
44
|
+
*
|
|
45
|
+
* Called from `assertValidContentType`, so a database-defined type read back out of
|
|
46
|
+
* Firestore goes through exactly the same check as a code-defined one. A matrix that named
|
|
47
|
+
* an unknown role would be read by the security rules as a list that simply never matches,
|
|
48
|
+
* which denies silently; better to refuse the write than to ship a rule nobody can explain.
|
|
49
|
+
*/
|
|
50
|
+
declare function assertValidPermissionMatrix(slug: string, matrix: unknown): void;
|
|
51
|
+
/**
|
|
52
|
+
* The stored form: known actions only, roles deduplicated and in a fixed order.
|
|
53
|
+
*
|
|
54
|
+
* Fixed order because the matrix is written to Firestore and read back by the Schema
|
|
55
|
+
* Builder, and a list whose order changes on every save shows a diff where nothing changed.
|
|
56
|
+
* An action whose list matches the roles the defaults already allow is dropped, so an
|
|
57
|
+
* untouched matrix is stored as absent rather than as a copy of the defaults that would
|
|
58
|
+
* silently stop tracking them.
|
|
59
|
+
*/
|
|
60
|
+
declare function normalizePermissionMatrix(matrix: PermissionMatrix | undefined): PermissionMatrix | undefined;
|
|
61
|
+
/** The roles whose own defaults allow an action at all, ownership aside. */
|
|
62
|
+
declare function defaultRoles(action: EntryAction): readonly Role[];
|
|
63
|
+
/**
|
|
64
|
+
* The effective decision for one role and one action on one content type.
|
|
65
|
+
*
|
|
66
|
+
* Exactly what `can()` resolves, minus the entry lookup, so a screen can label a row
|
|
67
|
+
* without inventing a user. Keep the two in step: a test drives both over the same inputs.
|
|
68
|
+
*/
|
|
69
|
+
declare function matrixDecision(type: Pick<ContentTypeLike, "permissions"> | undefined, role: Role, action: EntryAction): MatrixDecision;
|
|
70
|
+
/** One role's cell in the Developer area's table. */
|
|
71
|
+
interface MatrixCell {
|
|
72
|
+
role: Role;
|
|
73
|
+
/** Whether the checkbox is ticked: the role is listed, or the matrix is silent. */
|
|
74
|
+
listed: boolean;
|
|
75
|
+
/** What the three layers will actually do. */
|
|
76
|
+
decision: MatrixDecision;
|
|
77
|
+
/**
|
|
78
|
+
* True when the checkbox cannot change the outcome: an admin, who is never restricted,
|
|
79
|
+
* or a role whose own defaults deny the action regardless of the matrix. The screen
|
|
80
|
+
* disables the control and says why rather than offering a lie.
|
|
81
|
+
*/
|
|
82
|
+
fixed: boolean;
|
|
83
|
+
}
|
|
84
|
+
interface MatrixRow {
|
|
85
|
+
action: EntryAction;
|
|
86
|
+
cells: readonly MatrixCell[];
|
|
87
|
+
}
|
|
88
|
+
/** The whole table, in a fixed order, ready to render. */
|
|
89
|
+
declare function permissionMatrixRows(type: Pick<ContentTypeLike, "permissions"> | undefined): readonly MatrixRow[];
|
|
90
|
+
/**
|
|
91
|
+
* The next matrix after one checkbox is toggled.
|
|
92
|
+
*
|
|
93
|
+
* Unticking a role while the matrix is silent about the action has to materialize the
|
|
94
|
+
* action first, from the defaults rather than from nothing: an empty list means "nobody but
|
|
95
|
+
* an admin", and a screen that produced one from a single untick would revoke two roles for
|
|
96
|
+
* one click. Toggling a fixed cell returns the matrix unchanged, so a UI bug cannot store a
|
|
97
|
+
* row that claims something no layer will do.
|
|
98
|
+
*/
|
|
99
|
+
declare function withMatrixRole(matrix: PermissionMatrix | undefined, action: EntryAction, role: Role, listed: boolean): PermissionMatrix | undefined;
|
|
100
|
+
|
|
101
|
+
export { Decision, MATRIX_ACTIONS, MATRIX_LIMITS, type MatrixCell, type MatrixDecision, type MatrixRow, assertValidPermissionMatrix, defaultRoles, matrixDecision, normalizePermissionMatrix, permissionMatrixRows, withMatrixRole };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import '../chunk-77JDRXXU.js';
|
|
2
|
+
export { MATRIX_ACTIONS, MATRIX_LIMITS, assertValidPermissionMatrix, defaultRoles, matrixDecision, normalizePermissionMatrix, permissionMatrixRows, withMatrixRole } from '../chunk-TCFFHTY3.js';
|
|
3
|
+
export { ACTIONS, ADMIN_ACTIONS, PERMISSION_DEFAULTS, can, isEntryAction, resolveDecision } from '../chunk-NPDBQBLW.js';
|
|
4
|
+
import '../chunk-TS7Y7GEZ.js';
|
|
5
|
+
import '../chunk-4Z6P3XCE.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|