@vielzeug/archive 26.11.4 → 26.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"collection.cjs","names":["archiveAuthorSchema","hex64Schema","s","uuidSchema","isoTimestampSchema","ArchiveDefinitionError","ARCHIVE_COLLECTION_NAME_PATTERN","ARCHIVE_LIMITS","isWellFormedText","canonicalObjectJson","ArchiveDerivationError","sha256Hex","require__crypto","require_contract","require__json","require_errors","_vielzeug_spell","contentVersion","definition","index","payloadSchema","canonicalize","filters","tags","name","value","values","kind","filterValues","tagValues","stage","raw","parsed","parsedJson","canonical","normalizedJson","revalidated","canonicalJson","repeatedPayload","repeated","rawTags","rawTitle","title","filter","uniqueTags","tag","cause","entrySchema","ownedEntrySchema","contract"],"sources":["../src/collection.ts"],"sourcesContent":["import { type Schema, s } from '@vielzeug/spell';\nimport { sha256Hex } from './_crypto';\nimport { canonicalObjectJson } from './_json';\nimport {\n ARCHIVE_COLLECTION_NAME_PATTERN,\n ARCHIVE_CONTRACT_VERSION,\n ARCHIVE_LIMITS,\n type ArchivePage,\n archiveAuthorSchema,\n hex64Schema,\n isoTimestampSchema,\n isWellFormedText,\n uuidSchema,\n} from './contract';\nimport { ArchiveDefinitionError, ArchiveDerivationError, type ArchiveDerivationStage } from './errors';\n\n/**\n * Stored payloads are canonical by construction: `derive` validates, normalizes and\n * byte-pins every payload before it is stored, and the package's schema policy is\n * to reset/reseed databases when canonicalization semantics change. The emitted\n * entry schemas therefore embed the plain payload schema — re-parsing a stored\n * payload re-runs its (write-time-stable, hence idempotent) schema, never a\n * parallel re-canonicalization.\n */\n\n/**\n * The one seam between a deployment's content and the archive core. A collection\n * declares its payload schema and derives its list dimensions; the core owns\n * everything else — parsing, canonical serialization, the content hash, the emitted\n * envelope schemas, and every storage bound. Collections never hash, never size,\n * and never hand-write the envelope.\n */\n\n/** What `index` derives from one validated payload: the list dimensions plus the FTS title. */\nexport interface ArchiveIndexing<F extends string = string, T extends string = string> {\n /** One scalar-filter value from the declared vocabulary, or null for \"no filter\". */\n filter: F | null;\n /** Tag values from the declared vocabulary; duplicates collapse in first-seen order. */\n tags: readonly T[];\n /** The searchable title; the core trims and bounds it. */\n title: string;\n}\n\n/** The definition a deployment writes; `defineArchiveCollection` derives the rest. */\nexport interface ArchiveCollectionDefinition<\n P extends object,\n C extends number,\n F extends string = never,\n T extends string = never,\n> {\n /**\n * Normalizes a parsed payload to its storage form — fixed key order, sorted\n * order-insensitive lists — so equivalent content hashes identically. Omit when\n * the payload is already canonical; a result that leaves the payload schema is\n * a derivation fault at derive time, never stored. Must be idempotent.\n */\n canonicalize?(payload: P): P;\n /** The collection's payload-format version; stored and re-emitted with every entry. */\n contentVersion: C;\n /** Valid scalar-filter values; a query param outside the vocabulary is 400. */\n filters?: readonly F[];\n /** Derives the list dimensions and the searchable title from the canonical payload. */\n index(payload: P): ArchiveIndexing<NoInfer<F>, NoInfer<T>>;\n /** The URL + storage dimension this collection owns: lowercase `[a-z0-9-]`, at most 32 chars. */\n name: string;\n /** The strict payload schema: the whole content contract, parsed before anything derives. */\n payloadSchema: Schema<P, unknown>;\n /** Valid tag values. */\n tags?: readonly T[];\n}\n\n/** Everything the archive stores and re-identifies by, derived once per publish and update. */\nexport interface ArchiveDerivation {\n /** The stored bytes: the canonical payload, serialized with fixed key order. */\n canonicalJson: string;\n /** SHA-256 hex over `{contentVersion, payload}`: identical content hashes identically. */\n contentHash: string;\n /** The collection's declared payload-format version. */\n contentVersion: number;\n filter: string | null;\n tags: readonly string[];\n title: string;\n}\n\n/** The envelope fields every emitted entry carries; the payload stays the collection's. */\nexport interface ArchiveEntry<P = unknown, C extends number = number> {\n author: { id: string; name: string };\n contentHash: string;\n /** The collection's payload-format version, literal-pinned by the definition. */\n contentVersion: C;\n id: string;\n likeCount: number;\n payload: P;\n publishedAt: string;\n revision: number;\n /** The archive wire-contract version, pinned by the core. */\n schemaVersion: typeof ARCHIVE_CONTRACT_VERSION;\n updatedAt: string;\n}\n\n/** The private owner view: the emitted entry plus the create retry key and moderation state. */\nexport interface ArchiveOwnedEntry<P = unknown, C extends number = number> extends ArchiveEntry<P, C> {\n /** The create retry key; public routes never emit it. */\n clientKey: string;\n /** True while an operator has removed the entry from public view (still owned, frozen for edits). */\n hidden: boolean;\n}\n\n/**\n * The wire schemas one collection shares with its client: every request and response\n * envelope for this collection's routes, composed from the payload schema, so the\n * client and the Worker parse the same bytes with the same rules.\n */\nexport interface ArchiveContract<P = unknown, C extends number = number> {\n /** The public entry emitted by list, detail and publish/update responses. */\n entry: Schema<ArchiveEntry<P, C>>;\n /** The own-publications response: `{ entries: ArchiveOwnedEntry[] }`. */\n ownedEntries: Schema<{ entries: ArchiveOwnedEntry<P, C>[] }>;\n /** The private owner entry — the public entry plus the create retry key. */\n ownedEntry: Schema<ArchiveOwnedEntry<P, C>>;\n /** One list page: entries plus the forward/backward cursors around it. */\n page: Schema<ArchivePage<ArchiveEntry<P, C>>>;\n /** The publish request: `{ clientKey, payload }`. */\n publishInput: Schema<{ clientKey: string; payload: P }, unknown>;\n /** The update request: `{ expectedRevision, payload }`. */\n updateInput: Schema<{ expectedRevision: number; payload: P }, unknown>;\n}\n\n/** One registered collection: the derived runtime seam the Worker consumes. */\nexport interface ArchiveCollection<P extends object = object, C extends number = number> {\n readonly contentVersion: C;\n /** The collection's wire schemas, shared with its client. */\n readonly contract: ArchiveContract<P, C>;\n /**\n * Validates an untrusted payload and derives everything the archive stores.\n * Null rejects invalid content: JSON/schema failures, title/tag bounds or the\n * canonical payload byte ceiling. Routes answer 422 `invalid-payload` for null.\n * Callback failures and deployment invariant violations throw ArchiveDerivationError.\n */\n readonly derive: (raw: unknown) => Promise<ArchiveDerivation | null>;\n readonly filters: readonly string[];\n readonly name: string;\n readonly tags: readonly string[];\n}\n\n/** The envelope shape shared by the public and owned compositions. */\nconst envelopeShape = <P, C extends number>(payload: Schema<P>, contentVersion: C) =>\n ({\n author: archiveAuthorSchema,\n contentHash: hex64Schema,\n contentVersion: s.literal(contentVersion),\n id: uuidSchema,\n likeCount: s.number().int().min(0).max(Number.MAX_SAFE_INTEGER),\n payload,\n publishedAt: isoTimestampSchema,\n revision: s.number().int().min(1).max(Number.MAX_SAFE_INTEGER),\n schemaVersion: s.literal(ARCHIVE_CONTRACT_VERSION),\n updatedAt: isoTimestampSchema,\n }) as const;\n\n/**\n * Builds one collection from its definition. Malformed definitions fail here, at\n * registration time — never mid-request: the name must be a routable `[a-z0-9-]`\n * segment, the content version a positive integer, and every declared filter and\n * tag value well-formed text of `1..vocabChars` chars — the bound cursors and the\n * meta route both rely on it. The core derives everything else per payload:\n * parsing, canonical serialization, hashing over the collection's versioned\n * envelope, the emitted-entry schemas, and every storage bound.\n */\nexport function defineArchiveCollection<\n P extends object,\n const C extends number,\n const F extends string = never,\n const T extends string = never,\n>(definition: ArchiveCollectionDefinition<P, C, F, T>): ArchiveCollection<P, C> {\n const { canonicalize, contentVersion, index, name, payloadSchema } = definition;\n if (\n typeof index !== 'function' ||\n !payloadSchema ||\n typeof payloadSchema.safeParse !== 'function' ||\n (canonicalize !== undefined && typeof canonicalize !== 'function')\n ) {\n throw new ArchiveDefinitionError(\n 'Collection requires a payload schema and synchronous indexing/canonicalization functions.',\n );\n }\n if (\n (definition.filters !== undefined && !Array.isArray(definition.filters)) ||\n (definition.tags !== undefined && !Array.isArray(definition.tags))\n ) {\n throw new ArchiveDefinitionError('Collection vocabularies must be arrays.');\n }\n const filters = Object.freeze([...(definition.filters ?? [])]);\n const tags = Object.freeze([...(definition.tags ?? [])]);\n if (typeof name !== 'string' || !ARCHIVE_COLLECTION_NAME_PATTERN.test(name)) {\n throw new ArchiveDefinitionError(`Archive collection name must match [a-z0-9-]{1,32}: ${JSON.stringify(name)}`);\n }\n if (!Number.isSafeInteger(contentVersion) || contentVersion < 1) {\n throw new ArchiveDefinitionError(`Archive collection ${name}: contentVersion must be a safe integer >= 1.`);\n }\n const validateVocabulary = (kind: 'filters' | 'tags', values: readonly string[]): void => {\n for (const value of values) {\n if (\n typeof value !== 'string' ||\n value.length === 0 ||\n value.length > ARCHIVE_LIMITS.vocabChars ||\n !isWellFormedText(value)\n ) {\n throw new ArchiveDefinitionError(\n `Archive collection ${name}: every ${kind} value must be well-formed text of 1..${ARCHIVE_LIMITS.vocabChars} chars.`,\n );\n }\n }\n };\n validateVocabulary('filters', filters);\n validateVocabulary('tags', tags);\n const filterValues = new Set<string>(filters);\n const tagValues = new Set<string>(tags);\n\n const derive = async (raw: unknown): Promise<ArchiveDerivation | null> => {\n let stage: ArchiveDerivationStage = 'schema';\n try {\n if (canonicalObjectJson(raw) === null) return null;\n const parsed = payloadSchema.safeParse(raw);\n if (!parsed.success) return null;\n const parsedJson = canonicalObjectJson(parsed.data, true, raw);\n if (parsedJson === null)\n throw new ArchiveDerivationError(stage, 'Payload schema produced invalid or lossy JSON.');\n stage = 'canonicalize';\n const canonical = canonicalize === undefined ? parsed.data : canonicalize(parsed.data);\n // A canonicalize step that leaves the payload schema is a collection bug, not data to store.\n const normalizedJson = canonicalObjectJson(canonical, true, JSON.parse(parsedJson));\n if (normalizedJson === null) throw new ArchiveDerivationError(stage, 'Canonicalization produced invalid JSON.');\n stage = 'schema';\n const revalidated = payloadSchema.safeParse(JSON.parse(normalizedJson));\n if (!revalidated.success)\n throw new ArchiveDerivationError(\n canonicalize === undefined ? 'schema' : 'canonicalize',\n 'Canonical payload is outside the payload schema.',\n { cause: revalidated.error },\n );\n const canonicalJson = canonicalObjectJson(revalidated.data, true, JSON.parse(normalizedJson));\n if (canonicalJson === null || canonicalJson !== normalizedJson)\n throw new ArchiveDerivationError(stage, 'Payload schema is not stable on canonical JSON.');\n if (canonicalize !== undefined) {\n const repeatedPayload = payloadSchema.parse(JSON.parse(canonicalJson));\n stage = 'canonicalize';\n const repeated = canonicalize(repeatedPayload);\n if (canonicalObjectJson(repeated, true) !== canonicalJson)\n throw new ArchiveDerivationError(stage, 'Canonicalization is not idempotent.');\n }\n\n stage = 'index';\n const { filter, tags: rawTags, title: rawTitle } = index(revalidated.data);\n // Indexing may read the final parsed value, but must not change the stored content.\n if (canonicalObjectJson(revalidated.data, true) !== canonicalJson)\n throw new ArchiveDerivationError(stage, 'Indexing mutated the canonical payload.');\n if (typeof rawTitle !== 'string' || !Array.isArray(rawTags))\n throw new ArchiveDerivationError(stage, 'Indexing must return a string title and a tags array.');\n const title = rawTitle.trim();\n if (title.length === 0 || title.length > ARCHIVE_LIMITS.titleChars || !isWellFormedText(title)) return null;\n if (filter !== null && (typeof filter !== 'string' || !isWellFormedText(filter)))\n throw new ArchiveDerivationError(stage, 'Indexing returned an invalid filter.');\n const uniqueTags: string[] = [];\n for (const tag of rawTags) {\n if (typeof tag !== 'string' || !isWellFormedText(tag))\n throw new ArchiveDerivationError(stage, 'Indexing returned an invalid tag.');\n if (!uniqueTags.includes(tag)) uniqueTags.push(tag);\n }\n // A derivation outside the declared vocabulary would store a row no list query\n // can reach — the query route 400s the very value the collection derived.\n if ((filter !== null && !filterValues.has(filter)) || uniqueTags.some((tag) => !tagValues.has(tag)))\n throw new ArchiveDerivationError(stage, 'Indexing returned a value outside the declared vocabularies.');\n if (uniqueTags.length > ARCHIVE_LIMITS.tagsPerEntry) return null;\n\n if (new TextEncoder().encode(canonicalJson).byteLength > ARCHIVE_LIMITS.payloadBytes) return null;\n stage = 'hash';\n const contentHash = await sha256Hex(`{\"contentVersion\":${contentVersion},\"payload\":${canonicalJson}}`);\n return { canonicalJson, contentHash, contentVersion, filter, tags: uniqueTags, title };\n } catch (cause) {\n if (cause instanceof ArchiveDerivationError) throw cause;\n throw new ArchiveDerivationError(stage, 'Collection content derivation failed.', { cause });\n }\n };\n\n // The cast pins the envelope's payload member to the collection's output type:\n // `payloadSchema` is `Schema<P, unknown>` (input unknown), and inference through\n // the generic shape helper would widen the member to `unknown`.\n const entrySchema: Schema<ArchiveEntry<P, C>> = s.object(envelopeShape(payloadSchema as Schema<P>, contentVersion));\n const ownedEntrySchema: Schema<ArchiveOwnedEntry<P, C>> = s.object({\n ...envelopeShape(payloadSchema as Schema<P>, contentVersion),\n clientKey: uuidSchema,\n hidden: s.boolean(),\n });\n const contract: ArchiveContract<P, C> = {\n entry: entrySchema,\n ownedEntries: s.object({ entries: s.array(ownedEntrySchema) }),\n ownedEntry: ownedEntrySchema,\n page: s.object({\n cursor: s.string().nullable(),\n entries: s.array(entrySchema),\n }),\n publishInput: s.object({ clientKey: uuidSchema, payload: payloadSchema }),\n updateInput: s.object({\n expectedRevision: s.number().int().min(1).max(Number.MAX_SAFE_INTEGER),\n payload: payloadSchema,\n }),\n };\n\n return Object.freeze({ contentVersion, contract: Object.freeze(contract), derive, filters, name, tags });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"collection.cjs","names":["archiveAuthorSchema","hex64Schema","s","uuidSchema","isoTimestampSchema","ArchiveDefinitionError","ARCHIVE_COLLECTION_NAME_PATTERN","ARCHIVE_LIMITS","isWellFormedText","canonicalObjectJson","ArchiveDerivationError","sha256Hex","require__crypto","require__json","require_contract","require_errors","_vielzeug_spell","contentVersion","definition","index","payloadSchema","canonicalize","filters","tags","name","value","values","kind","filterValues","tagValues","stage","raw","parsed","parsedJson","canonical","normalizedJson","revalidated","canonicalJson","repeatedPayload","repeated","rawTags","rawTitle","title","filter","uniqueTags","tag","cause","entrySchema","ownedEntrySchema","contract"],"sources":["../src/collection.ts"],"sourcesContent":["import { type Schema, s } from '@vielzeug/spell';\nimport { sha256Hex } from './_crypto';\nimport { canonicalObjectJson } from './_json';\nimport {\n ARCHIVE_COLLECTION_NAME_PATTERN,\n ARCHIVE_CONTRACT_VERSION,\n ARCHIVE_LIMITS,\n type ArchivePage,\n archiveAuthorSchema,\n hex64Schema,\n isoTimestampSchema,\n isWellFormedText,\n uuidSchema,\n} from './contract';\nimport { ArchiveDefinitionError, ArchiveDerivationError, type ArchiveDerivationStage } from './errors';\n\n/**\n * Stored payloads are canonical by construction: `derive` validates, normalizes and\n * byte-pins every payload before it is stored, and the package's schema policy is\n * to reset/reseed databases when canonicalization semantics change. The emitted\n * entry schemas therefore embed the plain payload schema — re-parsing a stored\n * payload re-runs its (write-time-stable, hence idempotent) schema, never a\n * parallel re-canonicalization.\n */\n\n/**\n * The one seam between a deployment's content and the archive core. A collection\n * declares its payload schema and derives its list dimensions; the core owns\n * everything else — parsing, canonical serialization, the content hash, the emitted\n * envelope schemas, and every storage bound. Collections never hash, never size,\n * and never hand-write the envelope.\n */\n\n/** What `index` derives from one validated payload: the list dimensions plus the FTS title. */\nexport interface ArchiveIndexing<F extends string = string, T extends string = string> {\n /** One scalar-filter value from the declared vocabulary, or null for \"no filter\". */\n filter: F | null;\n /** Tag values from the declared vocabulary; duplicates collapse in first-seen order. */\n tags: readonly T[];\n /** The searchable title; the core trims and bounds it. */\n title: string;\n}\n\n/** The definition a deployment writes; `defineArchiveCollection` derives the rest. */\nexport interface ArchiveCollectionDefinition<\n P extends object,\n C extends number,\n F extends string = never,\n T extends string = never,\n> {\n /**\n * Normalizes a parsed payload to its storage form — fixed key order, sorted\n * order-insensitive lists — so equivalent content hashes identically. Omit when\n * the payload is already canonical; a result that leaves the payload schema is\n * a derivation fault at derive time, never stored. Must be idempotent.\n */\n canonicalize?(payload: P): P;\n /** The collection's payload-format version; stored and re-emitted with every entry. */\n contentVersion: C;\n /** Valid scalar-filter values; a query param outside the vocabulary is 400. */\n filters?: readonly F[];\n /** Derives the list dimensions and the searchable title from the canonical payload. */\n index(payload: P): ArchiveIndexing<NoInfer<F>, NoInfer<T>>;\n /** The URL + storage dimension this collection owns: lowercase `[a-z0-9-]`, at most 32 chars. */\n name: string;\n /** The strict payload schema: the whole content contract, parsed before anything derives. */\n payloadSchema: Schema<P, unknown>;\n /** Valid tag values. */\n tags?: readonly T[];\n}\n\n/** Everything the archive stores and re-identifies by, derived once per publish and update. */\nexport interface ArchiveDerivation {\n /** The stored bytes: the canonical payload, serialized with fixed key order. */\n canonicalJson: string;\n /** SHA-256 hex over `{contentVersion, payload}`: identical content hashes identically. */\n contentHash: string;\n /** The collection's declared payload-format version. */\n contentVersion: number;\n filter: string | null;\n tags: readonly string[];\n title: string;\n}\n\n/** The envelope fields every emitted entry carries; the payload stays the collection's. */\nexport interface ArchiveEntry<P = unknown, C extends number = number> {\n author: { id: string; name: string };\n contentHash: string;\n /** The collection's payload-format version, literal-pinned by the definition. */\n contentVersion: C;\n id: string;\n likeCount: number;\n payload: P;\n publishedAt: string;\n revision: number;\n /** The archive wire-contract version, pinned by the core. */\n schemaVersion: typeof ARCHIVE_CONTRACT_VERSION;\n updatedAt: string;\n}\n\n/** The private owner view: the emitted entry plus the create retry key and moderation state. */\nexport interface ArchiveOwnedEntry<P = unknown, C extends number = number> extends ArchiveEntry<P, C> {\n /** The create retry key; public routes never emit it. */\n clientKey: string;\n /** True while an operator has removed the entry from public view (still owned, frozen for edits). */\n hidden: boolean;\n}\n\n/**\n * The wire schemas one collection shares with its client: every request and response\n * envelope for this collection's routes, composed from the payload schema, so the\n * client and the Worker parse the same bytes with the same rules.\n */\nexport interface ArchiveContract<P = unknown, C extends number = number> {\n /** The public entry emitted by list, detail and publish/update responses. */\n entry: Schema<ArchiveEntry<P, C>>;\n /** The own-publications response: `{ entries: ArchiveOwnedEntry[] }`. */\n ownedEntries: Schema<{ entries: ArchiveOwnedEntry<P, C>[] }>;\n /** The private owner entry — the public entry plus the create retry key. */\n ownedEntry: Schema<ArchiveOwnedEntry<P, C>>;\n /** One list page: entries plus the forward/backward cursors around it. */\n page: Schema<ArchivePage<ArchiveEntry<P, C>>>;\n /** The publish request: `{ clientKey, payload }`. */\n publishInput: Schema<{ clientKey: string; payload: P }, unknown>;\n /** The update request: `{ expectedRevision, payload }`. */\n updateInput: Schema<{ expectedRevision: number; payload: P }, unknown>;\n}\n\n/** One registered collection: the derived runtime seam the Worker consumes. */\nexport interface ArchiveCollection<P extends object = object, C extends number = number> {\n readonly contentVersion: C;\n /** The collection's wire schemas, shared with its client. */\n readonly contract: ArchiveContract<P, C>;\n /**\n * Validates an untrusted payload and derives everything the archive stores.\n * Null rejects invalid content: JSON/schema failures, title/tag bounds or the\n * canonical payload byte ceiling. Routes answer 422 `invalid-payload` for null.\n * Callback failures and deployment invariant violations throw ArchiveDerivationError.\n */\n readonly derive: (raw: unknown) => Promise<ArchiveDerivation | null>;\n readonly filters: readonly string[];\n readonly name: string;\n readonly tags: readonly string[];\n}\n\n/** The envelope shape shared by the public and owned compositions. */\nconst envelopeShape = <P, C extends number>(payload: Schema<P>, contentVersion: C) =>\n ({\n author: archiveAuthorSchema,\n contentHash: hex64Schema,\n contentVersion: s.literal(contentVersion),\n id: uuidSchema,\n likeCount: s.number().int().min(0).max(Number.MAX_SAFE_INTEGER),\n payload,\n publishedAt: isoTimestampSchema,\n revision: s.number().int().min(1).max(Number.MAX_SAFE_INTEGER),\n schemaVersion: s.literal(ARCHIVE_CONTRACT_VERSION),\n updatedAt: isoTimestampSchema,\n }) as const;\n\n/**\n * Builds one collection from its definition. Malformed definitions fail here, at\n * registration time — never mid-request: the name must be a routable `[a-z0-9-]`\n * segment, the content version a positive integer, and every declared filter and\n * tag value well-formed text of `1..vocabChars` chars — the bound cursors and the\n * meta route both rely on it. The core derives everything else per payload:\n * parsing, canonical serialization, hashing over the collection's versioned\n * envelope, the emitted-entry schemas, and every storage bound.\n */\nexport function defineArchiveCollection<\n P extends object,\n const C extends number,\n const F extends string = never,\n const T extends string = never,\n>(definition: ArchiveCollectionDefinition<P, C, F, T>): ArchiveCollection<P, C> {\n const { canonicalize, contentVersion, index, name, payloadSchema } = definition;\n if (\n typeof index !== 'function' ||\n !payloadSchema ||\n typeof payloadSchema.safeParse !== 'function' ||\n (canonicalize !== undefined && typeof canonicalize !== 'function')\n ) {\n throw new ArchiveDefinitionError(\n 'Collection requires a payload schema and synchronous indexing/canonicalization functions.',\n );\n }\n if (\n (definition.filters !== undefined && !Array.isArray(definition.filters)) ||\n (definition.tags !== undefined && !Array.isArray(definition.tags))\n ) {\n throw new ArchiveDefinitionError('Collection vocabularies must be arrays.');\n }\n const filters = Object.freeze([...(definition.filters ?? [])]);\n const tags = Object.freeze([...(definition.tags ?? [])]);\n if (typeof name !== 'string' || !ARCHIVE_COLLECTION_NAME_PATTERN.test(name)) {\n throw new ArchiveDefinitionError(`Archive collection name must match [a-z0-9-]{1,32}: ${JSON.stringify(name)}`);\n }\n if (!Number.isSafeInteger(contentVersion) || contentVersion < 1) {\n throw new ArchiveDefinitionError(`Archive collection ${name}: contentVersion must be a safe integer >= 1.`);\n }\n const validateVocabulary = (kind: 'filters' | 'tags', values: readonly string[]): void => {\n for (const value of values) {\n if (\n typeof value !== 'string' ||\n value.length === 0 ||\n value.length > ARCHIVE_LIMITS.vocabChars ||\n !isWellFormedText(value)\n ) {\n throw new ArchiveDefinitionError(\n `Archive collection ${name}: every ${kind} value must be well-formed text of 1..${ARCHIVE_LIMITS.vocabChars} chars.`,\n );\n }\n }\n };\n validateVocabulary('filters', filters);\n validateVocabulary('tags', tags);\n const filterValues = new Set<string>(filters);\n const tagValues = new Set<string>(tags);\n\n const derive = async (raw: unknown): Promise<ArchiveDerivation | null> => {\n let stage: ArchiveDerivationStage = 'schema';\n try {\n if (canonicalObjectJson(raw) === null) return null;\n const parsed = payloadSchema.safeParse(raw);\n if (!parsed.success) return null;\n const parsedJson = canonicalObjectJson(parsed.data, true, raw);\n if (parsedJson === null)\n throw new ArchiveDerivationError(stage, 'Payload schema produced invalid or lossy JSON.');\n stage = 'canonicalize';\n const canonical = canonicalize === undefined ? parsed.data : canonicalize(parsed.data);\n // A canonicalize step that leaves the payload schema is a collection bug, not data to store.\n const normalizedJson = canonicalObjectJson(canonical, true, JSON.parse(parsedJson));\n if (normalizedJson === null) throw new ArchiveDerivationError(stage, 'Canonicalization produced invalid JSON.');\n stage = 'schema';\n const revalidated = payloadSchema.safeParse(JSON.parse(normalizedJson));\n if (!revalidated.success)\n throw new ArchiveDerivationError(\n canonicalize === undefined ? 'schema' : 'canonicalize',\n 'Canonical payload is outside the payload schema.',\n { cause: revalidated.error },\n );\n const canonicalJson = canonicalObjectJson(revalidated.data, true, JSON.parse(normalizedJson));\n if (canonicalJson === null || canonicalJson !== normalizedJson)\n throw new ArchiveDerivationError(stage, 'Payload schema is not stable on canonical JSON.');\n if (canonicalize !== undefined) {\n const repeatedPayload = payloadSchema.parse(JSON.parse(canonicalJson));\n stage = 'canonicalize';\n const repeated = canonicalize(repeatedPayload);\n if (canonicalObjectJson(repeated, true) !== canonicalJson)\n throw new ArchiveDerivationError(stage, 'Canonicalization is not idempotent.');\n }\n\n stage = 'index';\n const { filter, tags: rawTags, title: rawTitle } = index(revalidated.data);\n // Indexing may read the final parsed value, but must not change the stored content.\n if (canonicalObjectJson(revalidated.data, true) !== canonicalJson)\n throw new ArchiveDerivationError(stage, 'Indexing mutated the canonical payload.');\n if (typeof rawTitle !== 'string' || !Array.isArray(rawTags))\n throw new ArchiveDerivationError(stage, 'Indexing must return a string title and a tags array.');\n const title = rawTitle.trim();\n if (title.length === 0 || title.length > ARCHIVE_LIMITS.titleChars || !isWellFormedText(title)) return null;\n if (filter !== null && (typeof filter !== 'string' || !isWellFormedText(filter)))\n throw new ArchiveDerivationError(stage, 'Indexing returned an invalid filter.');\n const uniqueTags: string[] = [];\n for (const tag of rawTags) {\n if (typeof tag !== 'string' || !isWellFormedText(tag))\n throw new ArchiveDerivationError(stage, 'Indexing returned an invalid tag.');\n if (!uniqueTags.includes(tag)) uniqueTags.push(tag);\n }\n // A derivation outside the declared vocabulary would store a row no list query\n // can reach — the query route 400s the very value the collection derived.\n if ((filter !== null && !filterValues.has(filter)) || uniqueTags.some((tag) => !tagValues.has(tag)))\n throw new ArchiveDerivationError(stage, 'Indexing returned a value outside the declared vocabularies.');\n if (uniqueTags.length > ARCHIVE_LIMITS.tagsPerEntry) return null;\n\n if (new TextEncoder().encode(canonicalJson).byteLength > ARCHIVE_LIMITS.payloadBytes) return null;\n stage = 'hash';\n const contentHash = await sha256Hex(`{\"contentVersion\":${contentVersion},\"payload\":${canonicalJson}}`);\n return { canonicalJson, contentHash, contentVersion, filter, tags: uniqueTags, title };\n } catch (cause) {\n if (cause instanceof ArchiveDerivationError) throw cause;\n throw new ArchiveDerivationError(stage, 'Collection content derivation failed.', { cause });\n }\n };\n\n // The cast pins the envelope's payload member to the collection's output type:\n // `payloadSchema` is `Schema<P, unknown>` (input unknown), and inference through\n // the generic shape helper would widen the member to `unknown`.\n const entrySchema: Schema<ArchiveEntry<P, C>> = s.object(envelopeShape(payloadSchema as Schema<P>, contentVersion));\n const ownedEntrySchema: Schema<ArchiveOwnedEntry<P, C>> = s.object({\n ...envelopeShape(payloadSchema as Schema<P>, contentVersion),\n clientKey: uuidSchema,\n hidden: s.boolean(),\n });\n const contract: ArchiveContract<P, C> = {\n entry: entrySchema,\n ownedEntries: s.object({ entries: s.array(ownedEntrySchema) }),\n ownedEntry: ownedEntrySchema,\n page: s.object({\n cursor: s.string().nullable(),\n entries: s.array(entrySchema),\n }),\n publishInput: s.object({ clientKey: uuidSchema, payload: payloadSchema }),\n updateInput: s.object({\n expectedRevision: s.number().int().min(1).max(Number.MAX_SAFE_INTEGER),\n payload: payloadSchema,\n }),\n };\n\n return Object.freeze({ contentVersion, contract: Object.freeze(contract), derive, filters, name, tags });\n}\n"],"mappings":"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"}
@@ -1,81 +1,81 @@
1
1
  import { sha256Hex as e } from "./_crypto.js";
2
- import { ARCHIVE_COLLECTION_NAME_PATTERN as t, ARCHIVE_LIMITS as n, archiveAuthorSchema as r, hex64Schema as i, isWellFormedText as a, isoTimestampSchema as o, uuidSchema as s } from "./contract.js";
3
- import { canonicalObjectJson as c } from "./_json.js";
2
+ import { canonicalObjectJson as t } from "./_json.js";
3
+ import { ARCHIVE_COLLECTION_NAME_PATTERN as n, ARCHIVE_LIMITS as r, archiveAuthorSchema as i, hex64Schema as a, isWellFormedText as o, isoTimestampSchema as s, uuidSchema as c } from "./contract.js";
4
4
  import { ArchiveDefinitionError as l, ArchiveDerivationError as u } from "./errors.js";
5
5
  import { s as d } from "@vielzeug/spell";
6
- //#region src/collection.ts
6
+ // src/collection.ts
7
7
  var envelopeShape = (e, t) => ({
8
- author: r,
9
- contentHash: i,
8
+ author: i,
9
+ contentHash: a,
10
10
  contentVersion: d.literal(t),
11
- id: s,
11
+ id: c,
12
12
  likeCount: d.number().int().min(0).max(2 ** 53 - 1),
13
13
  payload: e,
14
- publishedAt: o,
14
+ publishedAt: s,
15
15
  revision: d.number().int().min(1).max(2 ** 53 - 1),
16
16
  schemaVersion: d.literal(1),
17
- updatedAt: o
17
+ updatedAt: s
18
18
  });
19
- function defineArchiveCollection(r) {
20
- let { canonicalize: i, contentVersion: o, index: f, name: p, payloadSchema: m } = r;
21
- if (typeof f != "function" || !m || typeof m.safeParse != "function" || i !== void 0 && typeof i != "function") throw new l("Collection requires a payload schema and synchronous indexing/canonicalization functions.");
22
- if (r.filters !== void 0 && !Array.isArray(r.filters) || r.tags !== void 0 && !Array.isArray(r.tags)) throw new l("Collection vocabularies must be arrays.");
23
- let h = Object.freeze([...r.filters ?? []]), g = Object.freeze([...r.tags ?? []]);
24
- if (typeof p != "string" || !t.test(p)) throw new l(`Archive collection name must match [a-z0-9-]{1,32}: ${JSON.stringify(p)}`);
25
- if (!Number.isSafeInteger(o) || o < 1) throw new l(`Archive collection ${p}: contentVersion must be a safe integer >= 1.`);
19
+ function defineArchiveCollection(i) {
20
+ let { canonicalize: a, contentVersion: s, index: f, name: p, payloadSchema: m } = i;
21
+ if (typeof f != "function" || !m || typeof m.safeParse != "function" || a !== void 0 && typeof a != "function") throw new l("Collection requires a payload schema and synchronous indexing/canonicalization functions.");
22
+ if (i.filters !== void 0 && !Array.isArray(i.filters) || i.tags !== void 0 && !Array.isArray(i.tags)) throw new l("Collection vocabularies must be arrays.");
23
+ let h = Object.freeze([...i.filters ?? []]), g = Object.freeze([...i.tags ?? []]);
24
+ if (typeof p != "string" || !n.test(p)) throw new l(`Archive collection name must match [a-z0-9-]{1,32}: ${JSON.stringify(p)}`);
25
+ if (!Number.isSafeInteger(s) || s < 1) throw new l(`Archive collection ${p}: contentVersion must be a safe integer >= 1.`);
26
26
  let validateVocabulary = (e, t) => {
27
- for (let r of t) if (typeof r != "string" || r.length === 0 || r.length > n.vocabChars || !a(r)) throw new l(`Archive collection ${p}: every ${e} value must be well-formed text of 1..${n.vocabChars} chars.`);
27
+ for (let n of t) if (typeof n != "string" || n.length === 0 || n.length > r.vocabChars || !o(n)) throw new l(`Archive collection ${p}: every ${e} value must be well-formed text of 1..${r.vocabChars} chars.`);
28
28
  };
29
29
  validateVocabulary("filters", h), validateVocabulary("tags", g);
30
- let _ = new Set(h), v = new Set(g), derive = async (t) => {
31
- let r = "schema";
30
+ let _ = new Set(h), v = new Set(g), derive = async (n) => {
31
+ let i = "schema";
32
32
  try {
33
- if (c(t) === null) return null;
34
- let s = m.safeParse(t);
35
- if (!s.success) return null;
36
- let l = c(s.data, !0, t);
37
- if (l === null) throw new u(r, "Payload schema produced invalid or lossy JSON.");
38
- r = "canonicalize";
39
- let d = i === void 0 ? s.data : i(s.data), p = c(d, !0, JSON.parse(l));
40
- if (p === null) throw new u(r, "Canonicalization produced invalid JSON.");
41
- r = "schema";
33
+ if (t(n) === null) return null;
34
+ let c = m.safeParse(n);
35
+ if (!c.success) return null;
36
+ let l = t(c.data, !0, n);
37
+ if (l === null) throw new u(i, "Payload schema produced invalid or lossy JSON.");
38
+ i = "canonicalize";
39
+ let d = a === void 0 ? c.data : a(c.data), p = t(d, !0, JSON.parse(l));
40
+ if (p === null) throw new u(i, "Canonicalization produced invalid JSON.");
41
+ i = "schema";
42
42
  let h = m.safeParse(JSON.parse(p));
43
- if (!h.success) throw new u(i === void 0 ? "schema" : "canonicalize", "Canonical payload is outside the payload schema.", { cause: h.error });
44
- let g = c(h.data, !0, JSON.parse(p));
45
- if (g === null || g !== p) throw new u(r, "Payload schema is not stable on canonical JSON.");
46
- if (i !== void 0) {
43
+ if (!h.success) throw new u(a === void 0 ? "schema" : "canonicalize", "Canonical payload is outside the payload schema.", { cause: h.error });
44
+ let g = t(h.data, !0, JSON.parse(p));
45
+ if (g === null || g !== p) throw new u(i, "Payload schema is not stable on canonical JSON.");
46
+ if (a !== void 0) {
47
47
  let e = m.parse(JSON.parse(g));
48
- r = "canonicalize";
49
- let t = i(e);
50
- if (c(t, !0) !== g) throw new u(r, "Canonicalization is not idempotent.");
48
+ i = "canonicalize";
49
+ let n = a(e);
50
+ if (t(n, !0) !== g) throw new u(i, "Canonicalization is not idempotent.");
51
51
  }
52
- r = "index";
52
+ i = "index";
53
53
  let { filter: y, tags: b, title: x } = f(h.data);
54
- if (c(h.data, !0) !== g) throw new u(r, "Indexing mutated the canonical payload.");
55
- if (typeof x != "string" || !Array.isArray(b)) throw new u(r, "Indexing must return a string title and a tags array.");
54
+ if (t(h.data, !0) !== g) throw new u(i, "Indexing mutated the canonical payload.");
55
+ if (typeof x != "string" || !Array.isArray(b)) throw new u(i, "Indexing must return a string title and a tags array.");
56
56
  let S = x.trim();
57
- if (S.length === 0 || S.length > n.titleChars || !a(S)) return null;
58
- if (y !== null && (typeof y != "string" || !a(y))) throw new u(r, "Indexing returned an invalid filter.");
57
+ if (S.length === 0 || S.length > r.titleChars || !o(S)) return null;
58
+ if (y !== null && (typeof y != "string" || !o(y))) throw new u(i, "Indexing returned an invalid filter.");
59
59
  let C = [];
60
60
  for (let e of b) {
61
- if (typeof e != "string" || !a(e)) throw new u(r, "Indexing returned an invalid tag.");
61
+ if (typeof e != "string" || !o(e)) throw new u(i, "Indexing returned an invalid tag.");
62
62
  C.includes(e) || C.push(e);
63
63
  }
64
- if (y !== null && !_.has(y) || C.some((e) => !v.has(e))) throw new u(r, "Indexing returned a value outside the declared vocabularies.");
65
- return C.length > n.tagsPerEntry || new TextEncoder().encode(g).byteLength > n.payloadBytes ? null : (r = "hash", {
64
+ if (y !== null && !_.has(y) || C.some((e) => !v.has(e))) throw new u(i, "Indexing returned a value outside the declared vocabularies.");
65
+ return C.length > r.tagsPerEntry || new TextEncoder().encode(g).byteLength > r.payloadBytes ? null : (i = "hash", {
66
66
  canonicalJson: g,
67
- contentHash: await e(`{"contentVersion":${o},"payload":${g}}`),
68
- contentVersion: o,
67
+ contentHash: await e(`{"contentVersion":${s},"payload":${g}}`),
68
+ contentVersion: s,
69
69
  filter: y,
70
70
  tags: C,
71
71
  title: S
72
72
  });
73
73
  } catch (e) {
74
- throw e instanceof u ? e : new u(r, "Collection content derivation failed.", { cause: e });
74
+ throw e instanceof u ? e : new u(i, "Collection content derivation failed.", { cause: e });
75
75
  }
76
- }, y = d.object(envelopeShape(m, o)), b = d.object({
77
- ...envelopeShape(m, o),
78
- clientKey: s,
76
+ }, y = d.object(envelopeShape(m, s)), b = d.object({
77
+ ...envelopeShape(m, s),
78
+ clientKey: c,
79
79
  hidden: d.boolean()
80
80
  }), x = {
81
81
  entry: y,
@@ -86,7 +86,7 @@ function defineArchiveCollection(r) {
86
86
  entries: d.array(y)
87
87
  }),
88
88
  publishInput: d.object({
89
- clientKey: s,
89
+ clientKey: c,
90
90
  payload: m
91
91
  }),
92
92
  updateInput: d.object({
@@ -95,7 +95,7 @@ function defineArchiveCollection(r) {
95
95
  })
96
96
  };
97
97
  return Object.freeze({
98
- contentVersion: o,
98
+ contentVersion: s,
99
99
  contract: Object.freeze(x),
100
100
  derive,
101
101
  filters: h,
@@ -103,7 +103,6 @@ function defineArchiveCollection(r) {
103
103
  tags: g
104
104
  });
105
105
  }
106
- //#endregion
107
106
  export { defineArchiveCollection };
108
107
 
109
108
  //# sourceMappingURL=collection.js.map
package/dist/contract.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { s as e } from "@vielzeug/spell";
2
- //#region src/contract.ts
2
+ // src/contract.ts
3
3
  var t = "archive_session", n = "/api/v1", r = 2592e3, i = 3600, sessionCookieValue = (e, r) => `${t}=${e}; HttpOnly; SameSite=Strict; Path=${n}; Max-Age=${r.maxAgeSeconds}${r.secure ? "; Secure" : ""}`, a = "x-archive-publisher", o = "X-Archive-Account", s = "x-archive-request", c = 1, l = 1, u = 1, d = "archive-operator", f = /^[a-z0-9-]{1,32}$/, p = e.string().uuid(), m = e.string().isoDateTime(), h = e.string().regex(/^[0-9a-f]{64}$/), g = /[\p{Cs}\p{Cc}]/u, isWellFormedText = (e) => !g.test(e), textString = () => e.string().check(isWellFormedText), _ = textString().trim().max(24), v = e.object({
4
4
  id: p,
5
5
  name: _
@@ -139,7 +139,6 @@ var t = "archive_session", n = "/api/v1", r = 2592e3, i = 3600, sessionCookieVal
139
139
  })),
140
140
  cursor: e.string().nullable()
141
141
  }), I = e.enum(["liked", "recent"]);
142
- //#endregion
143
142
  export { o as ACCOUNT_HEADER, f as ARCHIVE_COLLECTION_NAME_PATTERN, c as ARCHIVE_CONTRACT_VERSION, O as ARCHIVE_ERROR_CODES, w as ARCHIVE_LIMITS, P as ARCHIVE_MODERATION_ACTIONS, d as ARCHIVE_OPERATOR_ROLE, l as ARCHIVE_SCHEMA_VERSION, u as AUTH_SCHEMA_VERSION, g as ILL_FORMED_TEXT, i as OPERATOR_SESSION_MAX_AGE_SECONDS, a as PUBLISHER_HEADER, s as REQUEST_MARKER_HEADER, t as SESSION_COOKIE_NAME, n as SESSION_COOKIE_PATH, r as SESSION_MAX_AGE_SECONDS, b as archiveAccountSchema, _ as archiveAliasSchema, D as archiveAuthHealthSchema, v as archiveAuthorSchema, j as archiveCollectionMetaSchema, k as archiveErrorResponseSchema, E as archiveHealthSchema, y as archiveIdentitySchema, C as archiveLikeStateSchema, x as archiveLikedIdsSchema, S as archiveLikesPageSchema, T as archiveLimitsSchema, F as archiveModerationLogSchema, A as archiveOkSchema, N as archiveOwnReportsSchema, M as archiveReportsSchema, I as archiveSortSchema, h as hex64Schema, isWellFormedText, m as isoTimestampSchema, sessionCookieValue, p as uuidSchema };
144
143
 
145
144
  //# sourceMappingURL=contract.js.map
package/dist/errors.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/errors.ts
1
+ // src/errors.ts
2
2
  var ArchiveError = class extends Error {
3
3
  constructor(e, t) {
4
4
  super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
@@ -9,7 +9,6 @@ var ArchiveError = class extends Error {
9
9
  super(t, n), this.stage = e;
10
10
  }
11
11
  };
12
- //#endregion
13
12
  export { ArchiveDefinitionError, ArchiveDerivationError, ArchiveError };
14
13
 
15
14
  //# sourceMappingURL=errors.js.map
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./contract.cjs"),t=require("./errors.cjs"),n=require("./collection.cjs");exports.ACCOUNT_HEADER=e.ACCOUNT_HEADER,exports.ARCHIVE_COLLECTION_NAME_PATTERN=e.ARCHIVE_COLLECTION_NAME_PATTERN,exports.ARCHIVE_CONTRACT_VERSION=e.ARCHIVE_CONTRACT_VERSION,exports.ARCHIVE_ERROR_CODES=e.ARCHIVE_ERROR_CODES,exports.ARCHIVE_LIMITS=e.ARCHIVE_LIMITS,exports.ARCHIVE_MODERATION_ACTIONS=e.ARCHIVE_MODERATION_ACTIONS,exports.ARCHIVE_OPERATOR_ROLE=e.ARCHIVE_OPERATOR_ROLE,exports.ARCHIVE_SCHEMA_VERSION=e.ARCHIVE_SCHEMA_VERSION,exports.AUTH_SCHEMA_VERSION=e.AUTH_SCHEMA_VERSION,exports.ArchiveDefinitionError=t.ArchiveDefinitionError,exports.ArchiveDerivationError=t.ArchiveDerivationError,exports.ArchiveError=t.ArchiveError,exports.ILL_FORMED_TEXT=e.ILL_FORMED_TEXT,exports.OPERATOR_SESSION_MAX_AGE_SECONDS=e.OPERATOR_SESSION_MAX_AGE_SECONDS,exports.PUBLISHER_HEADER=e.PUBLISHER_HEADER,exports.REQUEST_MARKER_HEADER=e.REQUEST_MARKER_HEADER,exports.SESSION_COOKIE_NAME=e.SESSION_COOKIE_NAME,exports.SESSION_COOKIE_PATH=e.SESSION_COOKIE_PATH,exports.SESSION_MAX_AGE_SECONDS=e.SESSION_MAX_AGE_SECONDS,exports.archiveAccountSchema=e.archiveAccountSchema,exports.archiveAliasSchema=e.archiveAliasSchema,exports.archiveAuthHealthSchema=e.archiveAuthHealthSchema,exports.archiveAuthorSchema=e.archiveAuthorSchema,exports.archiveCollectionMetaSchema=e.archiveCollectionMetaSchema,exports.archiveErrorResponseSchema=e.archiveErrorResponseSchema,exports.archiveHealthSchema=e.archiveHealthSchema,exports.archiveIdentitySchema=e.archiveIdentitySchema,exports.archiveLikeStateSchema=e.archiveLikeStateSchema,exports.archiveLikedIdsSchema=e.archiveLikedIdsSchema,exports.archiveLikesPageSchema=e.archiveLikesPageSchema,exports.archiveLimitsSchema=e.archiveLimitsSchema,exports.archiveModerationLogSchema=e.archiveModerationLogSchema,exports.archiveOkSchema=e.archiveOkSchema,exports.archiveOwnReportsSchema=e.archiveOwnReportsSchema,exports.archiveReportsSchema=e.archiveReportsSchema,exports.archiveSortSchema=e.archiveSortSchema,exports.defineArchiveCollection=n.defineArchiveCollection,exports.hex64Schema=e.hex64Schema,exports.isWellFormedText=e.isWellFormedText,exports.isoTimestampSchema=e.isoTimestampSchema,exports.sessionCookieValue=e.sessionCookieValue,exports.uuidSchema=e.uuidSchema;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./collection.cjs"),t=require("./contract.cjs"),n=require("./errors.cjs");exports.ACCOUNT_HEADER=t.ACCOUNT_HEADER,exports.ARCHIVE_COLLECTION_NAME_PATTERN=t.ARCHIVE_COLLECTION_NAME_PATTERN,exports.ARCHIVE_CONTRACT_VERSION=t.ARCHIVE_CONTRACT_VERSION,exports.ARCHIVE_ERROR_CODES=t.ARCHIVE_ERROR_CODES,exports.ARCHIVE_LIMITS=t.ARCHIVE_LIMITS,exports.ARCHIVE_MODERATION_ACTIONS=t.ARCHIVE_MODERATION_ACTIONS,exports.ARCHIVE_OPERATOR_ROLE=t.ARCHIVE_OPERATOR_ROLE,exports.ARCHIVE_SCHEMA_VERSION=t.ARCHIVE_SCHEMA_VERSION,exports.AUTH_SCHEMA_VERSION=t.AUTH_SCHEMA_VERSION,exports.ArchiveDefinitionError=n.ArchiveDefinitionError,exports.ArchiveDerivationError=n.ArchiveDerivationError,exports.ArchiveError=n.ArchiveError,exports.ILL_FORMED_TEXT=t.ILL_FORMED_TEXT,exports.OPERATOR_SESSION_MAX_AGE_SECONDS=t.OPERATOR_SESSION_MAX_AGE_SECONDS,exports.PUBLISHER_HEADER=t.PUBLISHER_HEADER,exports.REQUEST_MARKER_HEADER=t.REQUEST_MARKER_HEADER,exports.SESSION_COOKIE_NAME=t.SESSION_COOKIE_NAME,exports.SESSION_COOKIE_PATH=t.SESSION_COOKIE_PATH,exports.SESSION_MAX_AGE_SECONDS=t.SESSION_MAX_AGE_SECONDS,exports.archiveAccountSchema=t.archiveAccountSchema,exports.archiveAliasSchema=t.archiveAliasSchema,exports.archiveAuthHealthSchema=t.archiveAuthHealthSchema,exports.archiveAuthorSchema=t.archiveAuthorSchema,exports.archiveCollectionMetaSchema=t.archiveCollectionMetaSchema,exports.archiveErrorResponseSchema=t.archiveErrorResponseSchema,exports.archiveHealthSchema=t.archiveHealthSchema,exports.archiveIdentitySchema=t.archiveIdentitySchema,exports.archiveLikeStateSchema=t.archiveLikeStateSchema,exports.archiveLikedIdsSchema=t.archiveLikedIdsSchema,exports.archiveLikesPageSchema=t.archiveLikesPageSchema,exports.archiveLimitsSchema=t.archiveLimitsSchema,exports.archiveModerationLogSchema=t.archiveModerationLogSchema,exports.archiveOkSchema=t.archiveOkSchema,exports.archiveOwnReportsSchema=t.archiveOwnReportsSchema,exports.archiveReportsSchema=t.archiveReportsSchema,exports.archiveSortSchema=t.archiveSortSchema,exports.defineArchiveCollection=e.defineArchiveCollection,exports.hex64Schema=t.hex64Schema,exports.isWellFormedText=t.isWellFormedText,exports.isoTimestampSchema=t.isoTimestampSchema,exports.sessionCookieValue=t.sessionCookieValue,exports.uuidSchema=t.uuidSchema;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ACCOUNT_HEADER as e, ARCHIVE_COLLECTION_NAME_PATTERN as t, ARCHIVE_CONTRACT_VERSION as n, ARCHIVE_ERROR_CODES as r, ARCHIVE_LIMITS as i, ARCHIVE_MODERATION_ACTIONS as a, ARCHIVE_OPERATOR_ROLE as o, ARCHIVE_SCHEMA_VERSION as s, AUTH_SCHEMA_VERSION as c, ILL_FORMED_TEXT as l, OPERATOR_SESSION_MAX_AGE_SECONDS as u, PUBLISHER_HEADER as d, REQUEST_MARKER_HEADER as f, SESSION_COOKIE_NAME as p, SESSION_COOKIE_PATH as m, SESSION_MAX_AGE_SECONDS as h, archiveAccountSchema as g, archiveAliasSchema as _, archiveAuthHealthSchema as v, archiveAuthorSchema as y, archiveCollectionMetaSchema as b, archiveErrorResponseSchema as x, archiveHealthSchema as S, archiveIdentitySchema as C, archiveLikeStateSchema as w, archiveLikedIdsSchema as T, archiveLikesPageSchema as E, archiveLimitsSchema as D, archiveModerationLogSchema as O, archiveOkSchema as k, archiveOwnReportsSchema as A, archiveReportsSchema as j, archiveSortSchema as M, hex64Schema as N, isWellFormedText as P, isoTimestampSchema as F, sessionCookieValue as I, uuidSchema as L } from "./contract.js";
2
- import { ArchiveDefinitionError as R, ArchiveDerivationError as z, ArchiveError as B } from "./errors.js";
3
- import { defineArchiveCollection as V } from "./collection.js";
4
- export { e as ACCOUNT_HEADER, t as ARCHIVE_COLLECTION_NAME_PATTERN, n as ARCHIVE_CONTRACT_VERSION, r as ARCHIVE_ERROR_CODES, i as ARCHIVE_LIMITS, a as ARCHIVE_MODERATION_ACTIONS, o as ARCHIVE_OPERATOR_ROLE, s as ARCHIVE_SCHEMA_VERSION, c as AUTH_SCHEMA_VERSION, R as ArchiveDefinitionError, z as ArchiveDerivationError, B as ArchiveError, l as ILL_FORMED_TEXT, u as OPERATOR_SESSION_MAX_AGE_SECONDS, d as PUBLISHER_HEADER, f as REQUEST_MARKER_HEADER, p as SESSION_COOKIE_NAME, m as SESSION_COOKIE_PATH, h as SESSION_MAX_AGE_SECONDS, g as archiveAccountSchema, _ as archiveAliasSchema, v as archiveAuthHealthSchema, y as archiveAuthorSchema, b as archiveCollectionMetaSchema, x as archiveErrorResponseSchema, S as archiveHealthSchema, C as archiveIdentitySchema, w as archiveLikeStateSchema, T as archiveLikedIdsSchema, E as archiveLikesPageSchema, D as archiveLimitsSchema, O as archiveModerationLogSchema, k as archiveOkSchema, A as archiveOwnReportsSchema, j as archiveReportsSchema, M as archiveSortSchema, V as defineArchiveCollection, N as hex64Schema, P as isWellFormedText, F as isoTimestampSchema, I as sessionCookieValue, L as uuidSchema };
1
+ import { defineArchiveCollection as e } from "./collection.js";
2
+ import { ACCOUNT_HEADER as t, ARCHIVE_COLLECTION_NAME_PATTERN as n, ARCHIVE_CONTRACT_VERSION as r, ARCHIVE_ERROR_CODES as i, ARCHIVE_LIMITS as a, ARCHIVE_MODERATION_ACTIONS as o, ARCHIVE_OPERATOR_ROLE as s, ARCHIVE_SCHEMA_VERSION as c, AUTH_SCHEMA_VERSION as l, ILL_FORMED_TEXT as u, OPERATOR_SESSION_MAX_AGE_SECONDS as d, PUBLISHER_HEADER as f, REQUEST_MARKER_HEADER as p, SESSION_COOKIE_NAME as m, SESSION_COOKIE_PATH as h, SESSION_MAX_AGE_SECONDS as g, archiveAccountSchema as _, archiveAliasSchema as v, archiveAuthHealthSchema as y, archiveAuthorSchema as b, archiveCollectionMetaSchema as x, archiveErrorResponseSchema as S, archiveHealthSchema as C, archiveIdentitySchema as w, archiveLikeStateSchema as T, archiveLikedIdsSchema as E, archiveLikesPageSchema as D, archiveLimitsSchema as O, archiveModerationLogSchema as k, archiveOkSchema as A, archiveOwnReportsSchema as j, archiveReportsSchema as M, archiveSortSchema as N, hex64Schema as P, isWellFormedText as F, isoTimestampSchema as I, sessionCookieValue as L, uuidSchema as R } from "./contract.js";
3
+ import { ArchiveDefinitionError as z, ArchiveDerivationError as B, ArchiveError as V } from "./errors.js";
4
+ export { t as ACCOUNT_HEADER, n as ARCHIVE_COLLECTION_NAME_PATTERN, r as ARCHIVE_CONTRACT_VERSION, i as ARCHIVE_ERROR_CODES, a as ARCHIVE_LIMITS, o as ARCHIVE_MODERATION_ACTIONS, s as ARCHIVE_OPERATOR_ROLE, c as ARCHIVE_SCHEMA_VERSION, l as AUTH_SCHEMA_VERSION, z as ArchiveDefinitionError, B as ArchiveDerivationError, V as ArchiveError, u as ILL_FORMED_TEXT, d as OPERATOR_SESSION_MAX_AGE_SECONDS, f as PUBLISHER_HEADER, p as REQUEST_MARKER_HEADER, m as SESSION_COOKIE_NAME, h as SESSION_COOKIE_PATH, g as SESSION_MAX_AGE_SECONDS, _ as archiveAccountSchema, v as archiveAliasSchema, y as archiveAuthHealthSchema, b as archiveAuthorSchema, x as archiveCollectionMetaSchema, S as archiveErrorResponseSchema, C as archiveHealthSchema, w as archiveIdentitySchema, T as archiveLikeStateSchema, E as archiveLikedIdsSchema, D as archiveLikesPageSchema, O as archiveLimitsSchema, k as archiveModerationLogSchema, A as archiveOkSchema, j as archiveOwnReportsSchema, M as archiveReportsSchema, N as archiveSortSchema, e as defineArchiveCollection, P as hex64Schema, F as isWellFormedText, I as isoTimestampSchema, L as sessionCookieValue, R as uuidSchema };
@@ -1,7 +1,7 @@
1
1
  import { ARCHIVE_LIMITS as e, isoTimestampSchema as t, uuidSchema as n } from "../contract.js";
2
2
  import { badRequest as r, queryParams as i } from "./http.js";
3
3
  import { s as a } from "@vielzeug/spell";
4
- //#region src/worker/_pagination.ts
4
+ // src/worker/_pagination.ts
5
5
  var encodeCursorValue = (e) => {
6
6
  let t = new TextEncoder().encode(JSON.stringify(e));
7
7
  return btoa(Array.from(t, (e) => String.fromCharCode(e)).join("")).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
@@ -50,7 +50,6 @@ var nextCursor = (e, t) => encodeCursorValue({
50
50
  ...e,
51
51
  context: t
52
52
  });
53
- //#endregion
54
53
  export { decodeCursorValue, encodeCursorValue, forwardPage, forwardPageParams, nextCursor };
55
54
 
56
55
  //# sourceMappingURL=_pagination.js.map
@@ -1,4 +1,4 @@
1
- //#region src/worker/_service.ts
1
+ // src/worker/_service.ts
2
2
  async function serviceRequest(e, t, n) {
3
3
  let r = new AbortController(), i;
4
4
  try {
@@ -11,7 +11,6 @@ async function serviceRequest(e, t, n) {
11
11
  i !== void 0 && clearTimeout(i);
12
12
  }
13
13
  }
14
- //#endregion
15
14
  export { serviceRequest };
16
15
 
17
16
  //# sourceMappingURL=_service.js.map
@@ -3,7 +3,7 @@ import { serviceRequest as i } from "./_service.js";
3
3
  import { ApiError as a, sessionToken as o, unauthenticated as s } from "./http.js";
4
4
  import { SQL as c } from "./sql.js";
5
5
  import { errors as l, jwtVerify as u } from "jose";
6
- //#region src/worker/auth.ts
6
+ // src/worker/auth.ts
7
7
  var d = e, f = 256, authUnavailable = () => new a(503, "auth-unavailable", "Sign-in is currently unavailable.");
8
8
  async function verifySession(e, p, m, h = !1) {
9
9
  let g = o(e);
@@ -63,7 +63,6 @@ function requireSession(e) {
63
63
  if (e === null) throw s();
64
64
  return e;
65
65
  }
66
- //#endregion
67
66
  export { requireSession, verifySession };
68
67
 
69
68
  //# sourceMappingURL=auth.js.map
@@ -1,6 +1,6 @@
1
1
  import { ApiError as e } from "./http.js";
2
2
  import { createRemoteJWKSet as t, customFetch as n } from "jose";
3
- //#region src/worker/env.ts
3
+ // src/worker/env.ts
4
4
  var r = /* @__PURE__ */ new Set([
5
5
  "127.0.0.1",
6
6
  "localhost",
@@ -71,7 +71,6 @@ function apiConfig(e) {
71
71
  };
72
72
  return a.set(e, u), u;
73
73
  }
74
- //#endregion
75
74
  export { apiConfig };
76
75
 
77
76
  //# sourceMappingURL=env.js.map
@@ -1,5 +1,5 @@
1
1
  import { ARCHIVE_LIMITS as e, PUBLISHER_HEADER as t, REQUEST_MARKER_HEADER as n, sessionCookieValue as r } from "../contract.js";
2
- //#region src/worker/http.ts
2
+ // src/worker/http.ts
3
3
  var ApiError = class extends Error {
4
4
  code;
5
5
  retryAfterSeconds;
@@ -113,7 +113,6 @@ var expiredSessionCookie = (e) => r("", {
113
113
  maxAgeSeconds: 0,
114
114
  secure: e
115
115
  });
116
- //#endregion
117
116
  export { ApiError, a as HTTP_BODY_TIMEOUT_MS, badRequest, conflict, corsHeaders, errorBody, expiredSessionCookie, forbidden, invalid, json, i as noStore, notFound, preflight, queryParams, readJsonBody, requireMutationGuard, sessionToken, toErrorResponse, unauthenticated };
118
117
 
119
118
  //# sourceMappingURL=http.js.map
@@ -1,2 +1,2 @@
1
- const e=require("../contract.cjs"),t=require("./http.cjs"),n=require("./sql.cjs"),r=require("./_pagination.cjs");let i=require("@vielzeug/spell");async function databaseCall(e){try{return await e()}catch(e){throw new t.ApiError(500,`internal`,`The service is not available.`,{cause:e,diagnostic:{kind:`database`}})}}var first=(e,t,...n)=>databaseCall(()=>e.DB.prepare(t).bind(...n).first()),all=(e,t,...n)=>databaseCall(async()=>{let r=await e.DB.prepare(t).bind(...n).all();if(!Array.isArray(r.results))throw Error(`Missing database query results`);return r.results}),run=(e,t,...n)=>databaseCall(()=>e.DB.prepare(t).bind(...n).run()),uuid=()=>crypto.randomUUID(),now=()=>new Date().toISOString(),recordUnavailable=()=>new t.ApiError(503,`record-unavailable`,`The record is not available.`,{diagnostic:{kind:`record`}}),a=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,rowToEntry=(e,t)=>{let n;try{n=JSON.parse(t.payload_json)}catch{throw recordUnavailable()}let r=e.contract.entry.safeParse({author:{id:t.owner_id,name:t.author_name},contentHash:t.content_hash,contentVersion:t.content_version,id:t.id,likeCount:t.like_count,payload:n,publishedAt:t.published_at,revision:t.revision,schemaVersion:t.schema_version,updatedAt:t.updated_at});if(!r.success)throw recordUnavailable();return r.data},rowToOwnedEntry=(t,n)=>{let r=rowToEntry(t,n);if(!e.uuidSchema.safeParse(n.client_key).success)throw recordUnavailable();return{...r,clientKey:n.client_key,hidden:n.hidden_at!==null}},entryById=async(e,t,r)=>{let i=await first(t,n.SQL.publication,r,e.name);return i===null?null:rowToEntry(e,i)},searchMatchFor=n=>{if(n===null)return;if(n.length>e.ARCHIVE_LIMITS.searchChars)throw t.badRequest();let r=(n.match(/[\p{L}\p{N}]+/gu)??[]).slice(0,e.ARCHIVE_LIMITS.searchTerms);if(r.length!==0)return r.map(e=>`"${e}"*`).join(` `)},entryCursorContext=e=>JSON.stringify([e.collection,e.sort,e.author??``,e.filters,e.tags,e.searchMatch??``]),cursorFromRow=e=>({createdAt:e.published_at,id:e.id,likeCount:e.like_count}),o=16,vocabValues=(e,n,r)=>{let i=e.getAll(n);if(i.length>o)throw t.badRequest();let a=[...new Set(i)].sort();if(a.some(e=>!r.includes(e)))throw t.badRequest();return a};async function listEntries(i,o,s){let c=new URL(o.url),l=t.queryParams(c,[`author`,`cursor`,`filter`,`limit`,`search`,`sort`,`tag`],[`filter`,`tag`]),u=l.get(`sort`)??`recent`,d=e.archiveSortSchema.safeParse(u);if(!d.success)throw t.badRequest();let f=vocabValues(l,`filter`,i.filters),p=vocabValues(l,`tag`,i.tags),m=l.get(`author`);if(m!==null&&!a.test(m))throw t.badRequest();let h=searchMatchFor(l.get(`search`)),g=entryCursorContext({author:m??void 0,collection:i.name,filters:f,searchMatch:h,sort:d.data,tags:p}),{cursor:_,limit:v}=r.forwardPageParams(l,g),y={author:m??void 0,collection:i.name,cursor:_===null?void 0:{createdAt:_.createdAt,id:_.id,likeCount:_.likeCount??0},filters:f,limit:v+1,searchMatch:h,sort:d.data,tags:p},b=n.listPublicationsSql(y),x=await all(s,b.sql,...b.parameters),S=x.slice(0,v),C=S.at(-1),w={cursor:x.length>v&&C!==void 0?r.nextCursor(cursorFromRow(C),g):null,entries:S.map(e=>rowToEntry(i,e))};return t.json(200,w,t.noStore)}function collectionMeta(n){let r=e.archiveCollectionMetaSchema.safeParse({filters:[...n.filters].sort(),name:n.name,tags:[...n.tags].sort()});if(!r.success)throw recordUnavailable();return t.json(200,r.data,t.noStore)}async function getEntry(e,n,r){let i=await entryById(e,n,r);if(i===null)throw t.notFound();return t.json(200,i,t.noStore)}async function healthJson(r,i){let a=null,o=null;try{a=(await first(i,n.SQL.schemaVersion))?.applied??0}catch{o=`database-unavailable`}o===null&&a!==1&&(o=`schema-mismatch`);let s=e.archiveHealthSchema.safeParse({dbSchemaVersion:a,features:{likes:r.likesEnabled,publishing:r.publishingEnabled},limits:e.ARCHIVE_LIMITS,reason:o,schemaVersion:1,status:o===null?`ok`:`degraded`});if(!s.success)throw recordUnavailable();return t.json(o===null?200:503,s.data,t.noStore)}var identityJson=(n,r)=>{let i=e.archiveIdentitySchema.safeParse(r);if(!i.success)throw recordUnavailable();return t.json(n,i.data,t.noStore)};function getMe(e){return identityJson(200,e?.publisher?{id:e.publisher.id,name:e.publisher.displayName,operator:e.operator}:null)}async function registerMe(e,r,i){let a=await t.readJsonBody(e),o=s.safeParse(a);if(!o.success)throw t.invalid();let c=o.data.name??``,l=await first(r,n.SQL.publisher,i.subject.issuer,i.subject.subject);if(l!==null)return identityJson(200,{id:l.id,name:l.display_name,operator:i.operator});let u=uuid();await run(r,n.SQL.insertPublisher,u,i.subject.issuer,i.subject.subject,c,now(),i.subject.issuer,i.subject.subject,Date.now());let d=await first(r,n.SQL.publisher,i.subject.issuer,i.subject.subject);if(d===null)throw await first(r,n.SQL.revokedSubject,i.subject.issuer,i.subject.subject,Date.now())===null?t.conflict():new t.ApiError(401,`unauthenticated`,`Sign-in is required.`);return identityJson(201,{id:d.id,name:d.display_name,operator:i.operator})}async function renameMe(e,r,i){let a=requirePublisherRow(i),o=await t.readJsonBody(e),s=c.safeParse(o);if(!s.success)throw t.invalid();return s.data.name!==a.displayName&&await run(r,n.SQL.renamePublisher,s.data.name,a.id,s.data.name),identityJson(200,{id:a.id,name:s.data.name,operator:i.operator})}async function deleteProfile(e,t){return t.publisher!==null&&await run(e,n.SQL.deletePublisher,t.publisher.id),new Response(null,{headers:{"cache-control":`no-store`},status:204})}async function deleteAccountProfile(r,i){let{issuer:a,subject:o}=i.subject;return await databaseCall(()=>r.DB.batch([r.DB.prepare(n.SQL.revokeSubject).bind(a,o,Date.now()+(e.SESSION_MAX_AGE_SECONDS+10)*1e3),r.DB.prepare(n.SQL.deleteSubjectPublisher).bind(a,o)])),new Response(null,{headers:t.noStore,status:204})}async function erasePublisher(e,r,i){let a=requireOperatorRow(r);return await databaseCall(()=>e.DB.batch([e.DB.prepare(n.SQL.auditErasure).bind(uuid(),a.id,now(),i),e.DB.prepare(n.SQL.deletePublisher).bind(i)])),new Response(null,{headers:t.noStore,status:204})}async function listModerationLog(i,a,o){requireOperatorRow(o);let s=`moderation-log`,{cursor:c,limit:l}=r.forwardPage(i,s),u=n.moderationLogSql({cursor:c,limit:l+1}),d=await all(a,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveModerationLogSchema.safeParse({actions:f.map(e=>({action:e.action,collection:e.collection,createdAt:e.created_at,id:e.id,operatorId:e.operator_id,targetId:e.target_id})),cursor:d.length>l&&p!==void 0?r.nextCursor({createdAt:p.created_at,id:p.id},s):null});if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}var requirePublisherRow=e=>{if(e.publisher===null)throw t.forbidden(`Register before publishing.`);return e.publisher},requireOperatorRow=e=>{if(!e.operator)throw t.forbidden(`Operator access is required.`);if(e.publisher===null)throw t.forbidden(`Register before moderating.`);return e.publisher},s=i.s.object({name:e.archiveAliasSchema.optional()}),c=i.s.object({name:e.archiveAliasSchema});async function myPublications(r,i,a){if(a.publisher===null)return t.json(200,{entries:[]},t.noStore);let o=await all(i,n.SQL.ownPublications,a.publisher.id,r.name,e.ARCHIVE_LIMITS.maxPublications);return t.json(200,{entries:o.map(e=>rowToOwnedEntry(r,e))},t.noStore)}async function myLikes(i,o,s){let c=new URL(i.url).searchParams.get(`ids`);if(c===null){let a=`likes:${s.publisher?.id??`unregistered`}`,{cursor:c,limit:l}=r.forwardPage(i,a);if(s.publisher===null)return t.json(200,{cursor:null,ids:[]},t.noStore);let u=n.likesPageSql(s.publisher.id,{cursor:c,limit:l+1}),d=await all(o,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveLikesPageSchema.safeParse({cursor:d.length>l&&p!==void 0?r.nextCursor({createdAt:p.created_at,id:p.publication_id},a):null,ids:f.map(e=>e.publication_id)});if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}t.queryParams(new URL(i.url),[`ids`]);let l=[...new Set(c.split(`,`))];if(l.length>e.ARCHIVE_LIMITS.likeStateIds||l.some(e=>!a.test(e)))throw t.badRequest();if(s.publisher===null)return t.json(200,{ids:[]},t.noStore);let u=n.likedPublicationsSql(s.publisher.id,l),d=await all(o,u.sql,...u.parameters),f=new Set(d.map(e=>e.publication_id)),p={ids:l.filter(e=>f.has(e))},m=e.archiveLikedIdsSchema.safeParse(p);if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}var l=i.s.object({clientKey:e.uuidSchema,payload:i.s.unknown()}),u=i.s.object({expectedRevision:i.s.number().int().min(1).max(2**53-1),payload:i.s.unknown()});async function resolveRetryRecord(e,n,r,i){if(r===null||r.expires_at<=Date.now())return null;if(r.payload_hash!==i)throw t.conflict(`That key was used for a different payload.`);if(r.publication_id===null)throw new t.ApiError(410,`gone`,`That publication was removed.`);let a=await entryById(e,n,r.publication_id);return a===null?`vanished`:t.json(200,a,t.noStore)}async function publishEntry(r,i,a,o,s){if(!o.publishingEnabled)throw new t.ApiError(503,`publishing-disabled`,`Publishing is currently disabled.`);let c=requirePublisherRow(s),u=await t.readJsonBody(i),d=l.safeParse(u);if(!d.success)throw t.invalid();let{clientKey:f}=d.data,p=await r.derive(d.data.payload);if(p===null)throw t.invalid();let m=p.canonicalJson,h=p.contentHash,g=c.id,_=r.name,v=await resolveRetryRecord(r,a,await first(a,n.SQL.request,g,_,f),h);if(v!==null&&v!==`vanished`)return v;let y=await first(a,n.SQL.publicationByKey,g,_,f);if(y!==null){if(y.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);if(y.content_hash!==h)throw t.conflict(`That key was used for a different payload.`);return t.json(200,rowToEntry(r,y),t.noStore)}let b=now(),x=Date.now()+e.ARCHIVE_LIMITS.idempotencyRetentionMs,S=JSON.stringify(p.tags);if(((await databaseCall(()=>a.DB.batch([a.DB.prepare(n.SQL.expireRequests).bind(Date.now()),a.DB.prepare(n.SQL.insertPublication).bind(uuid(),g,_,f,1,p.contentVersion,m,h,p.filter,p.title,S,b,b,g,_,e.ARCHIVE_LIMITS.maxPublications,g,_,f,Date.now()),a.DB.prepare(n.SQL.insertRequest).bind(g,_,f,h,b,x,g,_,f,h)])))[1]?.meta?.changes??0)===0){let e=await resolveRetryRecord(r,a,await first(a,n.SQL.request,g,_,f),h);if(e===`vanished`)throw new t.ApiError(410,`gone`,`That publication was removed.`);if(e!==null)return e;let i=await first(a,n.SQL.publicationByKey,g,_,f);if(i!==null&&i.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);if(i!==null&&i.content_hash===h)return t.json(200,rowToEntry(r,i),t.noStore);throw i===null?new t.ApiError(429,`quota`,`Publication quota reached.`):t.conflict(`That key was used for a different payload.`)}let C=await first(a,n.SQL.publicationByKey,g,_,f);if(C===null)throw t.conflict();return t.json(201,rowToEntry(r,C),t.noStore)}async function updateEntry(e,r,i,a,o,s){if(!a.publishingEnabled)throw new t.ApiError(503,`publishing-disabled`,`Publishing is currently disabled.`);let c=requirePublisherRow(o),l=await first(i,n.SQL.publicationAny,s,e.name);if(l===null||l.owner_id!==c.id)throw t.notFound();if(l.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);let d=await t.readJsonBody(r),f=u.safeParse(d);if(!f.success)throw t.invalid();let{expectedRevision:p}=f.data;if(p!==l.revision)throw t.conflict();let m=await e.derive(f.data.payload);if(m===null)throw t.invalid();let h=m.canonicalJson,g=m.contentHash;if(g===l.content_hash){let r=await first(i,n.SQL.publicationAny,s,e.name);if(r===null||r.owner_id!==c.id)throw t.notFound();if(r.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);if(r.revision!==p)throw t.conflict();return t.json(200,rowToEntry(e,r),t.noStore)}let _=JSON.stringify(m.tags);if(((await run(i,n.SQL.updatePublication,1,m.contentVersion,h,g,m.filter,m.title,_,now(),s,c.id,p,g)).meta?.changes??0)===0){let r=await first(i,n.SQL.publicationAny,s,e.name);throw r===null?t.notFound():r.hidden_at===null?t.conflict():t.forbidden(`This entry is under moderation review.`)}let v=await entryById(e,i,s);if(v===null)throw t.notFound();return t.json(200,v,t.noStore)}async function deleteEntry(e,r,i,a){if(i.operator){let t=requireOperatorRow(i);await databaseCall(()=>r.DB.batch([r.DB.prepare(n.SQL.auditRemoval).bind(uuid(),t.id,now(),a,e.name),r.DB.prepare(n.SQL.removePublicationAny).bind(a,e.name)]))}else{let o=requirePublisherRow(i),s=await first(r,n.SQL.publicationAny,a,e.name);if(s===null)return new Response(null,{headers:t.noStore,status:204});if(s.owner_id!==o.id)throw t.notFound();await run(r,n.SQL.removePublication,a,o.id)}return new Response(null,{headers:t.noStore,status:204})}async function setLike(r,i,a,o,s,c){let l=requirePublisherRow(o);if(c&&!a.likesEnabled)throw new t.ApiError(503,`likes-disabled`,`Likes are currently disabled.`);if(await first(i,n.SQL.publication,s,r.name)===null)throw t.notFound();c?await run(i,n.SQL.insertLike,l.id,now(),s):await run(i,n.SQL.removeLike,s,l.id);let u=await first(i,n.SQL.likeState,l.id,s);if(u===null)throw t.notFound();let d={id:u.id,likeCount:u.like_count,liked:u.liked===1},f=e.archiveLikeStateSchema.safeParse(d);if(!f.success)throw recordUnavailable();return t.json(200,f.data,t.noStore)}var d=i.s.object({reason:i.s.string().check(e.isWellFormedText).trim().min(1).max(e.ARCHIVE_LIMITS.reportReasonChars)});async function reportEntry(e,r,i,a,o){let s=requirePublisherRow(a),c=await t.readJsonBody(r),l=d.safeParse(c);if(!l.success)throw t.invalid();if(await first(i,n.SQL.publication,o,e.name)===null)throw t.notFound();return await run(i,n.SQL.insertReport,uuid(),s.id,l.data.reason,now(),o,e.name),new Response(null,{headers:t.noStore,status:204})}async function setHidden(e,r,i,a,o){let s=requireOperatorRow(i);if(await first(r,n.SQL.publicationAny,a,e.name)===null)throw t.notFound();let c=o?r.DB.prepare(n.SQL.hidePublication).bind(now(),a,e.name):r.DB.prepare(n.SQL.unhidePublication).bind(a,e.name);return await databaseCall(()=>r.DB.batch([r.DB.prepare(n.SQL.auditHide).bind(uuid(),s.id,o?`hide`:`unhide`,now(),a,e.name,Number(o)),c])),new Response(null,{headers:t.noStore,status:204})}async function listReports(i,a,o,s){requireOperatorRow(s);let c=new URL(a.url).searchParams.get(`status`)??`unresolved`;if(c!==`unresolved`&&c!==`resolved`)throw t.badRequest();let l=`reports:${i.name}:${c}`,{cursor:u,limit:d}=r.forwardPage(a,l,[`status`]),f=n.reportsPageSql({collection:i.name,resolved:c===`resolved`},{cursor:u,limit:d+1}),p=await all(o,f.sql,...f.parameters),m=p.slice(0,d),h=m.at(-1),g=e.archiveReportsSchema.safeParse({cursor:p.length>d&&h!==void 0?r.nextCursor({createdAt:h.created_at,id:h.id},l):null,reports:m.map(e=>({createdAt:e.created_at,entryId:e.publication_id,entryTitle:e.entry_title,id:e.id,reason:e.reason,reporter:{id:e.reporter_id,name:e.reporter_name},resolvedAt:e.resolved_at}))});if(!g.success)throw recordUnavailable();return t.json(200,g.data,t.noStore)}async function resolveReport(e,r,i,a){if(requireOperatorRow(i),await first(r,n.SQL.reportById,a,e.name)===null)throw t.notFound();return await run(r,n.SQL.resolveReport,now(),a,e.name),new Response(null,{headers:t.noStore,status:204})}async function myReports(i,a,o){let s=`own-reports:${o.publisher?.id??`unregistered`}`,{cursor:c,limit:l}=r.forwardPage(i,s);if(o.publisher===null)return t.json(200,{cursor:null,reports:[]},t.noStore);let u=n.reportsPageSql({publisherId:o.publisher.id},{cursor:c,limit:l+1}),d=await all(a,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveOwnReportsSchema.safeParse({cursor:d.length>l&&p!==void 0?r.nextCursor({createdAt:p.created_at,id:p.id},s):null,reports:f.map(e=>({collection:e.collection,createdAt:e.created_at,entryId:e.publication_id,id:e.id,reason:e.reason,resolvedAt:e.resolved_at}))});if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}exports.collectionMeta=collectionMeta,exports.deleteAccountProfile=deleteAccountProfile,exports.deleteEntry=deleteEntry,exports.deleteProfile=deleteProfile,exports.erasePublisher=erasePublisher,exports.getEntry=getEntry,exports.getMe=getMe,exports.healthJson=healthJson,exports.listEntries=listEntries,exports.listModerationLog=listModerationLog,exports.listReports=listReports,exports.myLikes=myLikes,exports.myPublications=myPublications,exports.myReports=myReports,exports.publishEntry=publishEntry,exports.registerMe=registerMe,exports.renameMe=renameMe,exports.reportEntry=reportEntry,exports.requireOperatorRow=requireOperatorRow,exports.requirePublisherRow=requirePublisherRow,exports.resolveReport=resolveReport,exports.setHidden=setHidden,exports.setLike=setLike,exports.updateEntry=updateEntry;
1
+ const e=require("../contract.cjs"),t=require("./_pagination.cjs"),n=require("./http.cjs"),r=require("./sql.cjs");let i=require("@vielzeug/spell");async function databaseCall(e){try{return await e()}catch(e){throw new n.ApiError(500,`internal`,`The service is not available.`,{cause:e,diagnostic:{kind:`database`}})}}var first=(e,t,...n)=>databaseCall(()=>e.DB.prepare(t).bind(...n).first()),all=(e,t,...n)=>databaseCall(async()=>{let r=await e.DB.prepare(t).bind(...n).all();if(!Array.isArray(r.results))throw Error(`Missing database query results`);return r.results}),run=(e,t,...n)=>databaseCall(()=>e.DB.prepare(t).bind(...n).run()),uuid=()=>crypto.randomUUID(),now=()=>new Date().toISOString(),recordUnavailable=()=>new n.ApiError(503,`record-unavailable`,`The record is not available.`,{diagnostic:{kind:`record`}}),a=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,rowToEntry=(e,t)=>{let n;try{n=JSON.parse(t.payload_json)}catch{throw recordUnavailable()}let r=e.contract.entry.safeParse({author:{id:t.owner_id,name:t.author_name},contentHash:t.content_hash,contentVersion:t.content_version,id:t.id,likeCount:t.like_count,payload:n,publishedAt:t.published_at,revision:t.revision,schemaVersion:t.schema_version,updatedAt:t.updated_at});if(!r.success)throw recordUnavailable();return r.data},rowToOwnedEntry=(t,n)=>{let r=rowToEntry(t,n);if(!e.uuidSchema.safeParse(n.client_key).success)throw recordUnavailable();return{...r,clientKey:n.client_key,hidden:n.hidden_at!==null}},entryById=async(e,t,n)=>{let i=await first(t,r.SQL.publication,n,e.name);return i===null?null:rowToEntry(e,i)},searchMatchFor=t=>{if(t===null)return;if(t.length>e.ARCHIVE_LIMITS.searchChars)throw n.badRequest();let r=(t.match(/[\p{L}\p{N}]+/gu)??[]).slice(0,e.ARCHIVE_LIMITS.searchTerms);if(r.length!==0)return r.map(e=>`"${e}"*`).join(` `)},entryCursorContext=e=>JSON.stringify([e.collection,e.sort,e.author??``,e.filters,e.tags,e.searchMatch??``]),cursorFromRow=e=>({createdAt:e.published_at,id:e.id,likeCount:e.like_count}),o=16,vocabValues=(e,t,r)=>{let i=e.getAll(t);if(i.length>o)throw n.badRequest();let a=[...new Set(i)].sort();if(a.some(e=>!r.includes(e)))throw n.badRequest();return a};async function listEntries(i,o,s){let c=new URL(o.url),l=n.queryParams(c,[`author`,`cursor`,`filter`,`limit`,`search`,`sort`,`tag`],[`filter`,`tag`]),u=l.get(`sort`)??`recent`,d=e.archiveSortSchema.safeParse(u);if(!d.success)throw n.badRequest();let f=vocabValues(l,`filter`,i.filters),p=vocabValues(l,`tag`,i.tags),m=l.get(`author`);if(m!==null&&!a.test(m))throw n.badRequest();let h=searchMatchFor(l.get(`search`)),g=entryCursorContext({author:m??void 0,collection:i.name,filters:f,searchMatch:h,sort:d.data,tags:p}),{cursor:_,limit:v}=t.forwardPageParams(l,g),y={author:m??void 0,collection:i.name,cursor:_===null?void 0:{createdAt:_.createdAt,id:_.id,likeCount:_.likeCount??0},filters:f,limit:v+1,searchMatch:h,sort:d.data,tags:p},b=r.listPublicationsSql(y),x=await all(s,b.sql,...b.parameters),S=x.slice(0,v),C=S.at(-1),w={cursor:x.length>v&&C!==void 0?t.nextCursor(cursorFromRow(C),g):null,entries:S.map(e=>rowToEntry(i,e))};return n.json(200,w,n.noStore)}function collectionMeta(t){let r=e.archiveCollectionMetaSchema.safeParse({filters:[...t.filters].sort(),name:t.name,tags:[...t.tags].sort()});if(!r.success)throw recordUnavailable();return n.json(200,r.data,n.noStore)}async function getEntry(e,t,r){let i=await entryById(e,t,r);if(i===null)throw n.notFound();return n.json(200,i,n.noStore)}async function healthJson(t,i){let a=null,o=null;try{a=(await first(i,r.SQL.schemaVersion))?.applied??0}catch{o=`database-unavailable`}o===null&&a!==1&&(o=`schema-mismatch`);let s=e.archiveHealthSchema.safeParse({dbSchemaVersion:a,features:{likes:t.likesEnabled,publishing:t.publishingEnabled},limits:e.ARCHIVE_LIMITS,reason:o,schemaVersion:1,status:o===null?`ok`:`degraded`});if(!s.success)throw recordUnavailable();return n.json(o===null?200:503,s.data,n.noStore)}var identityJson=(t,r)=>{let i=e.archiveIdentitySchema.safeParse(r);if(!i.success)throw recordUnavailable();return n.json(t,i.data,n.noStore)};function getMe(e){return identityJson(200,e?.publisher?{id:e.publisher.id,name:e.publisher.displayName,operator:e.operator}:null)}async function registerMe(e,t,i){let a=await n.readJsonBody(e),o=s.safeParse(a);if(!o.success)throw n.invalid();let c=o.data.name??``,l=await first(t,r.SQL.publisher,i.subject.issuer,i.subject.subject);if(l!==null)return identityJson(200,{id:l.id,name:l.display_name,operator:i.operator});let u=uuid();await run(t,r.SQL.insertPublisher,u,i.subject.issuer,i.subject.subject,c,now(),i.subject.issuer,i.subject.subject,Date.now());let d=await first(t,r.SQL.publisher,i.subject.issuer,i.subject.subject);if(d===null)throw await first(t,r.SQL.revokedSubject,i.subject.issuer,i.subject.subject,Date.now())===null?n.conflict():new n.ApiError(401,`unauthenticated`,`Sign-in is required.`);return identityJson(201,{id:d.id,name:d.display_name,operator:i.operator})}async function renameMe(e,t,i){let a=requirePublisherRow(i),o=await n.readJsonBody(e),s=c.safeParse(o);if(!s.success)throw n.invalid();return s.data.name!==a.displayName&&await run(t,r.SQL.renamePublisher,s.data.name,a.id,s.data.name),identityJson(200,{id:a.id,name:s.data.name,operator:i.operator})}async function deleteProfile(e,t){return t.publisher!==null&&await run(e,r.SQL.deletePublisher,t.publisher.id),new Response(null,{headers:{"cache-control":`no-store`},status:204})}async function deleteAccountProfile(t,i){let{issuer:a,subject:o}=i.subject;return await databaseCall(()=>t.DB.batch([t.DB.prepare(r.SQL.revokeSubject).bind(a,o,Date.now()+(e.SESSION_MAX_AGE_SECONDS+10)*1e3),t.DB.prepare(r.SQL.deleteSubjectPublisher).bind(a,o)])),new Response(null,{headers:n.noStore,status:204})}async function erasePublisher(e,t,i){let a=requireOperatorRow(t);return await databaseCall(()=>e.DB.batch([e.DB.prepare(r.SQL.auditErasure).bind(uuid(),a.id,now(),i),e.DB.prepare(r.SQL.deletePublisher).bind(i)])),new Response(null,{headers:n.noStore,status:204})}async function listModerationLog(i,a,o){requireOperatorRow(o);let s=`moderation-log`,{cursor:c,limit:l}=t.forwardPage(i,s),u=r.moderationLogSql({cursor:c,limit:l+1}),d=await all(a,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveModerationLogSchema.safeParse({actions:f.map(e=>({action:e.action,collection:e.collection,createdAt:e.created_at,id:e.id,operatorId:e.operator_id,targetId:e.target_id})),cursor:d.length>l&&p!==void 0?t.nextCursor({createdAt:p.created_at,id:p.id},s):null});if(!m.success)throw recordUnavailable();return n.json(200,m.data,n.noStore)}var requirePublisherRow=e=>{if(e.publisher===null)throw n.forbidden(`Register before publishing.`);return e.publisher},requireOperatorRow=e=>{if(!e.operator)throw n.forbidden(`Operator access is required.`);if(e.publisher===null)throw n.forbidden(`Register before moderating.`);return e.publisher},s=i.s.object({name:e.archiveAliasSchema.optional()}),c=i.s.object({name:e.archiveAliasSchema});async function myPublications(t,i,a){if(a.publisher===null)return n.json(200,{entries:[]},n.noStore);let o=await all(i,r.SQL.ownPublications,a.publisher.id,t.name,e.ARCHIVE_LIMITS.maxPublications);return n.json(200,{entries:o.map(e=>rowToOwnedEntry(t,e))},n.noStore)}async function myLikes(i,o,s){let c=new URL(i.url).searchParams.get(`ids`);if(c===null){let a=`likes:${s.publisher?.id??`unregistered`}`,{cursor:c,limit:l}=t.forwardPage(i,a);if(s.publisher===null)return n.json(200,{cursor:null,ids:[]},n.noStore);let u=r.likesPageSql(s.publisher.id,{cursor:c,limit:l+1}),d=await all(o,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveLikesPageSchema.safeParse({cursor:d.length>l&&p!==void 0?t.nextCursor({createdAt:p.created_at,id:p.publication_id},a):null,ids:f.map(e=>e.publication_id)});if(!m.success)throw recordUnavailable();return n.json(200,m.data,n.noStore)}n.queryParams(new URL(i.url),[`ids`]);let l=[...new Set(c.split(`,`))];if(l.length>e.ARCHIVE_LIMITS.likeStateIds||l.some(e=>!a.test(e)))throw n.badRequest();if(s.publisher===null)return n.json(200,{ids:[]},n.noStore);let u=r.likedPublicationsSql(s.publisher.id,l),d=await all(o,u.sql,...u.parameters),f=new Set(d.map(e=>e.publication_id)),p={ids:l.filter(e=>f.has(e))},m=e.archiveLikedIdsSchema.safeParse(p);if(!m.success)throw recordUnavailable();return n.json(200,m.data,n.noStore)}var l=i.s.object({clientKey:e.uuidSchema,payload:i.s.unknown()}),u=i.s.object({expectedRevision:i.s.number().int().min(1).max(2**53-1),payload:i.s.unknown()});async function resolveRetryRecord(e,t,r,i){if(r===null||r.expires_at<=Date.now())return null;if(r.payload_hash!==i)throw n.conflict(`That key was used for a different payload.`);if(r.publication_id===null)throw new n.ApiError(410,`gone`,`That publication was removed.`);let a=await entryById(e,t,r.publication_id);return a===null?`vanished`:n.json(200,a,n.noStore)}async function publishEntry(t,i,a,o,s){if(!o.publishingEnabled)throw new n.ApiError(503,`publishing-disabled`,`Publishing is currently disabled.`);let c=requirePublisherRow(s),u=await n.readJsonBody(i),d=l.safeParse(u);if(!d.success)throw n.invalid();let{clientKey:f}=d.data,p=await t.derive(d.data.payload);if(p===null)throw n.invalid();let m=p.canonicalJson,h=p.contentHash,g=c.id,_=t.name,v=await resolveRetryRecord(t,a,await first(a,r.SQL.request,g,_,f),h);if(v!==null&&v!==`vanished`)return v;let y=await first(a,r.SQL.publicationByKey,g,_,f);if(y!==null){if(y.hidden_at!==null)throw n.forbidden(`This entry is under moderation review.`);if(y.content_hash!==h)throw n.conflict(`That key was used for a different payload.`);return n.json(200,rowToEntry(t,y),n.noStore)}let b=now(),x=Date.now()+e.ARCHIVE_LIMITS.idempotencyRetentionMs,S=JSON.stringify(p.tags);if(((await databaseCall(()=>a.DB.batch([a.DB.prepare(r.SQL.expireRequests).bind(Date.now()),a.DB.prepare(r.SQL.insertPublication).bind(uuid(),g,_,f,1,p.contentVersion,m,h,p.filter,p.title,S,b,b,g,_,e.ARCHIVE_LIMITS.maxPublications,g,_,f,Date.now()),a.DB.prepare(r.SQL.insertRequest).bind(g,_,f,h,b,x,g,_,f,h)])))[1]?.meta?.changes??0)===0){let e=await resolveRetryRecord(t,a,await first(a,r.SQL.request,g,_,f),h);if(e===`vanished`)throw new n.ApiError(410,`gone`,`That publication was removed.`);if(e!==null)return e;let i=await first(a,r.SQL.publicationByKey,g,_,f);if(i!==null&&i.hidden_at!==null)throw n.forbidden(`This entry is under moderation review.`);if(i!==null&&i.content_hash===h)return n.json(200,rowToEntry(t,i),n.noStore);throw i===null?new n.ApiError(429,`quota`,`Publication quota reached.`):n.conflict(`That key was used for a different payload.`)}let C=await first(a,r.SQL.publicationByKey,g,_,f);if(C===null)throw n.conflict();return n.json(201,rowToEntry(t,C),n.noStore)}async function updateEntry(e,t,i,a,o,s){if(!a.publishingEnabled)throw new n.ApiError(503,`publishing-disabled`,`Publishing is currently disabled.`);let c=requirePublisherRow(o),l=await first(i,r.SQL.publicationAny,s,e.name);if(l===null||l.owner_id!==c.id)throw n.notFound();if(l.hidden_at!==null)throw n.forbidden(`This entry is under moderation review.`);let d=await n.readJsonBody(t),f=u.safeParse(d);if(!f.success)throw n.invalid();let{expectedRevision:p}=f.data;if(p!==l.revision)throw n.conflict();let m=await e.derive(f.data.payload);if(m===null)throw n.invalid();let h=m.canonicalJson,g=m.contentHash;if(g===l.content_hash){let t=await first(i,r.SQL.publicationAny,s,e.name);if(t===null||t.owner_id!==c.id)throw n.notFound();if(t.hidden_at!==null)throw n.forbidden(`This entry is under moderation review.`);if(t.revision!==p)throw n.conflict();return n.json(200,rowToEntry(e,t),n.noStore)}let _=JSON.stringify(m.tags);if(((await run(i,r.SQL.updatePublication,1,m.contentVersion,h,g,m.filter,m.title,_,now(),s,c.id,p,g)).meta?.changes??0)===0){let t=await first(i,r.SQL.publicationAny,s,e.name);throw t===null?n.notFound():t.hidden_at===null?n.conflict():n.forbidden(`This entry is under moderation review.`)}let v=await entryById(e,i,s);if(v===null)throw n.notFound();return n.json(200,v,n.noStore)}async function deleteEntry(e,t,i,a){if(i.operator){let n=requireOperatorRow(i);await databaseCall(()=>t.DB.batch([t.DB.prepare(r.SQL.auditRemoval).bind(uuid(),n.id,now(),a,e.name),t.DB.prepare(r.SQL.removePublicationAny).bind(a,e.name)]))}else{let o=requirePublisherRow(i),s=await first(t,r.SQL.publicationAny,a,e.name);if(s===null)return new Response(null,{headers:n.noStore,status:204});if(s.owner_id!==o.id)throw n.notFound();await run(t,r.SQL.removePublication,a,o.id)}return new Response(null,{headers:n.noStore,status:204})}async function setLike(t,i,a,o,s,c){let l=requirePublisherRow(o);if(c&&!a.likesEnabled)throw new n.ApiError(503,`likes-disabled`,`Likes are currently disabled.`);if(await first(i,r.SQL.publication,s,t.name)===null)throw n.notFound();c?await run(i,r.SQL.insertLike,l.id,now(),s):await run(i,r.SQL.removeLike,s,l.id);let u=await first(i,r.SQL.likeState,l.id,s);if(u===null)throw n.notFound();let d={id:u.id,likeCount:u.like_count,liked:u.liked===1},f=e.archiveLikeStateSchema.safeParse(d);if(!f.success)throw recordUnavailable();return n.json(200,f.data,n.noStore)}var d=i.s.object({reason:i.s.string().check(e.isWellFormedText).trim().min(1).max(e.ARCHIVE_LIMITS.reportReasonChars)});async function reportEntry(e,t,i,a,o){let s=requirePublisherRow(a),c=await n.readJsonBody(t),l=d.safeParse(c);if(!l.success)throw n.invalid();if(await first(i,r.SQL.publication,o,e.name)===null)throw n.notFound();return await run(i,r.SQL.insertReport,uuid(),s.id,l.data.reason,now(),o,e.name),new Response(null,{headers:n.noStore,status:204})}async function setHidden(e,t,i,a,o){let s=requireOperatorRow(i);if(await first(t,r.SQL.publicationAny,a,e.name)===null)throw n.notFound();let c=o?t.DB.prepare(r.SQL.hidePublication).bind(now(),a,e.name):t.DB.prepare(r.SQL.unhidePublication).bind(a,e.name);return await databaseCall(()=>t.DB.batch([t.DB.prepare(r.SQL.auditHide).bind(uuid(),s.id,o?`hide`:`unhide`,now(),a,e.name,Number(o)),c])),new Response(null,{headers:n.noStore,status:204})}async function listReports(i,a,o,s){requireOperatorRow(s);let c=new URL(a.url).searchParams.get(`status`)??`unresolved`;if(c!==`unresolved`&&c!==`resolved`)throw n.badRequest();let l=`reports:${i.name}:${c}`,{cursor:u,limit:d}=t.forwardPage(a,l,[`status`]),f=r.reportsPageSql({collection:i.name,resolved:c===`resolved`},{cursor:u,limit:d+1}),p=await all(o,f.sql,...f.parameters),m=p.slice(0,d),h=m.at(-1),g=e.archiveReportsSchema.safeParse({cursor:p.length>d&&h!==void 0?t.nextCursor({createdAt:h.created_at,id:h.id},l):null,reports:m.map(e=>({createdAt:e.created_at,entryId:e.publication_id,entryTitle:e.entry_title,id:e.id,reason:e.reason,reporter:{id:e.reporter_id,name:e.reporter_name},resolvedAt:e.resolved_at}))});if(!g.success)throw recordUnavailable();return n.json(200,g.data,n.noStore)}async function resolveReport(e,t,i,a){if(requireOperatorRow(i),await first(t,r.SQL.reportById,a,e.name)===null)throw n.notFound();return await run(t,r.SQL.resolveReport,now(),a,e.name),new Response(null,{headers:n.noStore,status:204})}async function myReports(i,a,o){let s=`own-reports:${o.publisher?.id??`unregistered`}`,{cursor:c,limit:l}=t.forwardPage(i,s);if(o.publisher===null)return n.json(200,{cursor:null,reports:[]},n.noStore);let u=r.reportsPageSql({publisherId:o.publisher.id},{cursor:c,limit:l+1}),d=await all(a,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveOwnReportsSchema.safeParse({cursor:d.length>l&&p!==void 0?t.nextCursor({createdAt:p.created_at,id:p.id},s):null,reports:f.map(e=>({collection:e.collection,createdAt:e.created_at,entryId:e.publication_id,id:e.id,reason:e.reason,resolvedAt:e.resolved_at}))});if(!m.success)throw recordUnavailable();return n.json(200,m.data,n.noStore)}exports.collectionMeta=collectionMeta,exports.deleteAccountProfile=deleteAccountProfile,exports.deleteEntry=deleteEntry,exports.deleteProfile=deleteProfile,exports.erasePublisher=erasePublisher,exports.getEntry=getEntry,exports.getMe=getMe,exports.healthJson=healthJson,exports.listEntries=listEntries,exports.listModerationLog=listModerationLog,exports.listReports=listReports,exports.myLikes=myLikes,exports.myPublications=myPublications,exports.myReports=myReports,exports.publishEntry=publishEntry,exports.registerMe=registerMe,exports.renameMe=renameMe,exports.reportEntry=reportEntry,exports.requireOperatorRow=requireOperatorRow,exports.requirePublisherRow=requirePublisherRow,exports.resolveReport=resolveReport,exports.setHidden=setHidden,exports.setLike=setLike,exports.updateEntry=updateEntry;
2
2
  //# sourceMappingURL=publications.cjs.map