@vielzeug/archive 26.11.11 → 26.11.13
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/dist/_server/d1.d.ts +23 -0
- package/dist/_server/d1.d.ts.map +1 -0
- package/dist/_server/execution.d.ts +5 -0
- package/dist/_server/execution.d.ts.map +1 -0
- package/dist/_server/http.cjs +2 -0
- package/dist/_server/http.cjs.map +1 -0
- package/dist/{worker → _server}/http.d.ts +6 -11
- package/dist/_server/http.d.ts.map +1 -0
- package/dist/{worker → _server}/http.js +18 -44
- package/dist/_server/http.js.map +1 -0
- package/dist/{worker/_service.cjs → _server/service.cjs} +1 -1
- package/dist/{worker/_service.js.map → _server/service.cjs.map} +1 -1
- package/dist/{worker/_service.d.ts → _server/service.d.ts} +1 -1
- package/dist/_server/service.d.ts.map +1 -0
- package/dist/{worker/_service.js → _server/service.js} +2 -2
- package/dist/_server/service.js.map +1 -0
- package/dist/_server/uuid.cjs +2 -0
- package/dist/_server/uuid.cjs.map +1 -0
- package/dist/_server/uuid.d.ts +3 -0
- package/dist/_server/uuid.d.ts.map +1 -0
- package/dist/_server/uuid.js +5 -0
- package/dist/_server/uuid.js.map +1 -0
- package/dist/auth/_confirmation.cjs +3 -3
- package/dist/auth/_confirmation.cjs.map +1 -1
- package/dist/auth/_confirmation.js +4 -4
- package/dist/auth/_confirmation.js.map +1 -1
- package/dist/auth/config.cjs.map +1 -1
- package/dist/auth/config.d.ts +1 -1
- package/dist/auth/config.js.map +1 -1
- package/dist/auth/errors.cjs +1 -1
- package/dist/auth/errors.cjs.map +1 -1
- package/dist/auth/errors.d.ts +1 -1
- package/dist/auth/errors.d.ts.map +1 -1
- package/dist/auth/errors.js +1 -1
- package/dist/auth/errors.js.map +1 -1
- package/dist/auth/store.cjs.map +1 -1
- package/dist/auth/store.d.ts +1 -1
- package/dist/auth/store.js.map +1 -1
- package/dist/auth/worker.cjs +1 -1
- package/dist/auth/worker.cjs.map +1 -1
- package/dist/auth/worker.d.ts +1 -1
- package/dist/auth/worker.d.ts.map +1 -1
- package/dist/auth/worker.js +153 -152
- package/dist/auth/worker.js.map +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/worker/_cors.cjs +2 -0
- package/dist/worker/_cors.cjs.map +1 -0
- package/dist/worker/_cors.d.ts +13 -0
- package/dist/worker/_cors.d.ts.map +1 -0
- package/dist/worker/_cors.js +32 -0
- package/dist/worker/_cors.js.map +1 -0
- package/dist/worker/_pagination.cjs +1 -1
- package/dist/worker/_pagination.cjs.map +1 -1
- package/dist/worker/_pagination.js +18 -18
- package/dist/worker/_pagination.js.map +1 -1
- package/dist/worker/auth.cjs +1 -1
- package/dist/worker/auth.cjs.map +1 -1
- package/dist/worker/auth.d.ts.map +1 -1
- package/dist/worker/auth.js +18 -18
- package/dist/worker/auth.js.map +1 -1
- package/dist/worker/env.cjs +1 -1
- package/dist/worker/env.cjs.map +1 -1
- package/dist/worker/env.d.ts +1 -22
- package/dist/worker/env.d.ts.map +1 -1
- package/dist/worker/env.js +1 -1
- package/dist/worker/env.js.map +1 -1
- package/dist/worker/publications.cjs +1 -1
- package/dist/worker/publications.cjs.map +1 -1
- package/dist/worker/publications.d.ts.map +1 -1
- package/dist/worker/publications.js +263 -262
- package/dist/worker/publications.js.map +1 -1
- package/dist/worker/worker.cjs +1 -1
- package/dist/worker/worker.cjs.map +1 -1
- package/dist/worker/worker.d.ts +2 -4
- package/dist/worker/worker.d.ts.map +1 -1
- package/dist/worker/worker.js +124 -122
- package/dist/worker/worker.js.map +1 -1
- package/dist/worker.d.ts +5 -3
- package/dist/worker.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/worker/_service.cjs.map +0 -1
- package/dist/worker/_service.d.ts.map +0 -1
- package/dist/worker/http.cjs +0 -2
- package/dist/worker/http.cjs.map +0 -1
- package/dist/worker/http.d.ts.map +0 -1
- package/dist/worker/http.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publications.js","names":["operation","cause","ApiError","params","env","sql","result","UUID_RE","payloadJson","row","parsed","collection","entry","uuidSchema","SQL","id","search","ARCHIVE_LIMITS","badRequest","terms","t","query","MAX_VOCAB_VALUES","raw","key","values","declared","value","url","request","queryParams","sortRaw","sortParsed","archiveSortSchema","filters","tags","author","searchMatch","context","forwardPageParams","cursor","limit","bound","listPublicationsSql","fetched","page","last","pageDto","nextCursor","json","noStore","archiveCollectionMetaSchema","notFound","dbSchemaVersion","reason","archiveHealthSchema","config","archiveIdentitySchema","identity","status","session","body","readJsonBody","registerBodySchema","invalid","name","existing","conflict","publisher","renameBodySchema","issuer","subject","SESSION_MAX_AGE_SECONDS","operator","publisherId","forwardPage","moderationLogSql","rows","archiveModerationLogSchema","forbidden","s","archiveAliasSchema","likesPageSql","archiveLikesPageSchema","ids","likedPublicationsSql","liked","r","dto","validated","archiveLikedIdsSchema","publishBodySchema","updateBodySchema","record","hash","derivation","owner","replay","clientKey","timestamp","expiresAt","tagsJson","racedReplay","raced","created","expectedRevision","current","updated","state","archiveLikeStateSchema","reportBodySchema","isWellFormedText","reporter","statement","hidden","reportsPageSql","archiveReportsSchema","archiveOwnReportsSchema"],"sources":["../../src/worker/publications.ts"],"sourcesContent":["import { s } from '@vielzeug/spell';\nimport type { ArchiveCollection } from '../collection';\nimport {\n ARCHIVE_CONTRACT_VERSION,\n ARCHIVE_LIMITS,\n ARCHIVE_SCHEMA_VERSION,\n type ArchiveModerationAction,\n archiveAliasSchema,\n archiveCollectionMetaSchema,\n archiveHealthSchema,\n archiveIdentitySchema,\n archiveLikedIdsSchema,\n archiveLikeStateSchema,\n archiveLikesPageSchema,\n archiveModerationLogSchema,\n archiveOwnReportsSchema,\n archiveReportsSchema,\n archiveSortSchema,\n isWellFormedText,\n SESSION_MAX_AGE_SECONDS,\n uuidSchema,\n} from '../contract';\nimport { forwardPage, forwardPageParams, nextCursor } from './_pagination';\nimport { JWT_CLOCK_TOLERANCE_SECONDS, type Publisher, type Session } from './auth';\nimport type { ApiConfig, ArchiveWorkerEnv } from './env';\nimport {\n ApiError,\n badRequest,\n conflict,\n forbidden,\n invalid,\n json,\n noStore,\n notFound,\n queryParams,\n readJsonBody,\n} from './http';\nimport {\n likedPublicationsSql,\n likesPageSql,\n listPublicationsSql,\n moderationLogSql,\n type PublicationCursor,\n type PublicationFilter,\n reportsPageSql,\n SQL,\n} from './sql';\n\n/**\n * Route handlers for /api/v1, parameterized by the registered collection. All SQL lives\n * in sql.ts and is bound positionally here in the authored order; the DTO boundary\n * re-validates every stored row before it leaves, content validation lives in the\n * collection's derive step, and moderation state (hidden entries) is visible only\n * through the owner and operator surfaces.\n */\n\ninterface PublicationRow {\n author_name: string;\n client_key: string;\n collection: string;\n content_hash: string;\n content_version: number;\n filter_value: string | null;\n /** Null while visible; set while an operator has removed the entry from public view. */\n hidden_at: string | null;\n id: string;\n like_count: number;\n owner_id: string;\n payload_json: string;\n published_at: string;\n revision: number;\n schema_version: number;\n tags_json: string;\n title: string;\n updated_at: string;\n}\n\nasync function databaseCall<T>(operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (cause) {\n throw new ApiError(500, 'internal', 'The service is not available.', { cause, diagnostic: { kind: 'database' } });\n }\n}\nconst first = <T>(env: ArchiveWorkerEnv, sql: string, ...params: (string | number | null)[]) =>\n databaseCall(() =>\n env.DB.prepare(sql)\n .bind(...params)\n .first<T>(),\n );\nconst all = <T>(env: ArchiveWorkerEnv, sql: string, ...params: (string | number | null)[]) =>\n databaseCall(async () => {\n const result = await env.DB.prepare(sql)\n .bind(...params)\n .all<T>();\n if (!Array.isArray(result.results)) throw new Error('Missing database query results');\n return result.results;\n });\nconst run = (env: ArchiveWorkerEnv, sql: string, ...params: (string | number | null)[]) =>\n databaseCall(() =>\n env.DB.prepare(sql)\n .bind(...params)\n .run(),\n );\n\nconst uuid = (): string => crypto.randomUUID();\nconst now = (): string => new Date().toISOString();\n\n/** A stored row that fails the emitted-DTO contract is unavailable, not malformed data. */\nconst recordUnavailable = (): ApiError =>\n new ApiError(503, 'record-unavailable', 'The record is not available.', { diagnostic: { kind: 'record' } });\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Maps a joined row to the public DTO through the collection's strict entry schema:\n * stored JSON is parsed and re-validated, stored versions are emitted as-is, and any\n * drift — unknown fields, corrupt payload, mismatched versions — surfaces as 503\n * `record-unavailable` rather than leaking or fabricating data.\n */\nconst rowToEntry = (collection: ArchiveCollection, row: PublicationRow) => {\n let payloadJson: unknown;\n try {\n payloadJson = JSON.parse(row.payload_json);\n } catch {\n throw recordUnavailable();\n }\n const parsed = collection.contract.entry.safeParse({\n author: { id: row.owner_id, name: row.author_name },\n contentHash: row.content_hash,\n contentVersion: row.content_version,\n id: row.id,\n likeCount: row.like_count,\n payload: payloadJson,\n publishedAt: row.published_at,\n revision: row.revision,\n schemaVersion: row.schema_version,\n updatedAt: row.updated_at,\n });\n if (!parsed.success) throw recordUnavailable();\n return parsed.data;\n};\n\nconst rowToOwnedEntry = (collection: ArchiveCollection, row: PublicationRow) => {\n const entry = rowToEntry(collection, row);\n // The private owner DTO adds the create retry key and the moderation state;\n // public routes never emit either.\n const key = uuidSchema.safeParse(row.client_key);\n if (!key.success) throw recordUnavailable();\n return { ...entry, clientKey: row.client_key, hidden: row.hidden_at !== null };\n};\n\nconst entryById = async (collection: ArchiveCollection, env: ArchiveWorkerEnv, id: string) => {\n const row = await first<PublicationRow>(env, SQL.publication, id, collection.name);\n return row === null ? null : rowToEntry(collection, row);\n};\n\n/* ----------------------------------- list --------------------------------- */\n\n/** Normalizes free text into the bounded FTS prefix-match expression the SQL expects. */\nconst searchMatchFor = (search: string | null): string | undefined => {\n if (search === null) return undefined;\n if (search.length > ARCHIVE_LIMITS.searchChars) throw badRequest();\n // Unicode word extraction: punctuation splits terms rather than poisoning them,\n // so \"Hunter's\" and \"fire-ice\" match the words their indexed titles carry.\n const terms = (search.match(/[\\p{L}\\p{N}]+/gu) ?? []).slice(0, ARCHIVE_LIMITS.searchTerms);\n if (terms.length === 0) return undefined;\n return terms.map((t) => `\"${t}\"*`).join(' ');\n};\n\n/**\n * The entry-list cursor context: every query dimension the page's boundary depends\n * on, so a cursor minted under one filter/sort/search and replayed under another is\n * a 400 — the same forward-cursor guard every other list in the package uses.\n */\nconst entryCursorContext = (query: {\n author?: string;\n collection: string;\n filters: readonly string[];\n searchMatch?: string;\n sort: 'liked' | 'recent';\n tags: readonly string[];\n}): string =>\n JSON.stringify([\n query.collection,\n query.sort,\n query.author ?? '',\n query.filters,\n query.tags,\n query.searchMatch ?? '',\n ]);\n\nconst cursorFromRow = (row: PublicationRow): PublicationCursor => ({\n createdAt: row.published_at,\n id: row.id,\n likeCount: row.like_count,\n});\n\n/** Repeated `filter`/`tag` values one entry-list request may carry (OR within each). */\nconst MAX_VOCAB_VALUES = 16;\n\n/** Reads one repeatable vocabulary param: every value must be declared, no repeats\n * past the bound, and the list is deduplicated and sorted so `?tag=a&tag=a` matches\n * `?tag=a` and value order never changes the cursor context. */\nconst vocabValues = (params: URLSearchParams, key: string, declared: readonly string[]): string[] => {\n const raw = params.getAll(key);\n if (raw.length > MAX_VOCAB_VALUES) throw badRequest();\n const values = [...new Set(raw)].sort();\n if (values.some((value) => !declared.includes(value))) throw badRequest();\n return values;\n};\n\nexport async function listEntries(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n): Promise<Response> {\n const url = new URL(request.url);\n const params = queryParams(url, ['author', 'cursor', 'filter', 'limit', 'search', 'sort', 'tag'], ['filter', 'tag']);\n\n const sortRaw = params.get('sort') ?? 'recent';\n const sortParsed = archiveSortSchema.safeParse(sortRaw);\n if (!sortParsed.success) throw badRequest();\n\n const filters = vocabValues(params, 'filter', collection.filters);\n const tags = vocabValues(params, 'tag', collection.tags);\n const author = params.get('author');\n if (author !== null && !UUID_RE.test(author)) throw badRequest();\n\n const searchMatch = searchMatchFor(params.get('search'));\n const context = entryCursorContext({\n author: author ?? undefined,\n collection: collection.name,\n filters,\n searchMatch,\n sort: sortParsed.data,\n tags,\n });\n const { cursor, limit } = forwardPageParams(params, context);\n\n const query: PublicationFilter = {\n author: author ?? undefined,\n collection: collection.name,\n cursor:\n cursor === null ? undefined : { createdAt: cursor.createdAt, id: cursor.id, likeCount: cursor.likeCount ?? 0 },\n filters,\n limit: limit + 1,\n searchMatch,\n sort: sortParsed.data,\n tags,\n };\n\n // One bounded query — never a count: the over-read row is the next page's proof.\n const bound = listPublicationsSql(query);\n const fetched = await all<PublicationRow>(env, bound.sql, ...bound.parameters);\n const page = fetched.slice(0, limit);\n const last = page.at(-1);\n\n // Entries are validated per row through the collection's entry schema; the cursor\n // is internally generated, so the page composes validated parts directly.\n const pageDto = {\n cursor: fetched.length > limit && last !== undefined ? nextCursor(cursorFromRow(last), context) : null,\n entries: page.map((row) => rowToEntry(collection, row)),\n };\n return json(200, pageDto, noStore);\n}\n\n/** The collection's public self-description: name plus the declared query vocabularies. */\nexport function collectionMeta(collection: ArchiveCollection): Response {\n const parsed = archiveCollectionMetaSchema.safeParse({\n filters: [...collection.filters].sort(),\n name: collection.name,\n tags: [...collection.tags].sort(),\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n}\n\nexport async function getEntry(collection: ArchiveCollection, env: ArchiveWorkerEnv, id: string): Promise<Response> {\n const entry = await entryById(collection, env, id);\n if (entry === null) throw notFound();\n return json(200, entry, noStore);\n}\n\n/* ---------------------------------- health --------------------------------- */\n\n/**\n * Service health: the feature flags, the wire limits, and the storage schema stamp\n * actually present in the database. A database whose migrations lag the package\n * answers `degraded` instead of pretending to be fine — the most likely deployment\n * fault becomes a one-glance diagnosis.\n */\nexport async function healthJson(config: ApiConfig, env: ArchiveWorkerEnv): Promise<Response> {\n let dbSchemaVersion: number | null = null;\n let reason: 'database-unavailable' | 'schema-mismatch' | null = null;\n try {\n const row = await first<{ applied: number }>(env, SQL.schemaVersion);\n dbSchemaVersion = row?.applied ?? 0;\n } catch {\n reason = 'database-unavailable';\n }\n if (reason === null && dbSchemaVersion !== ARCHIVE_SCHEMA_VERSION) reason = 'schema-mismatch';\n const parsed = archiveHealthSchema.safeParse({\n dbSchemaVersion,\n features: { likes: config.likesEnabled, publishing: config.publishingEnabled },\n limits: ARCHIVE_LIMITS,\n reason,\n schemaVersion: ARCHIVE_CONTRACT_VERSION,\n status: reason === null ? 'ok' : 'degraded',\n });\n if (!parsed.success) throw recordUnavailable();\n return json(reason === null ? 200 : 503, parsed.data, noStore);\n}\n\n/* --------------------------------- identity -------------------------------- */\n\n/** Emits the private identity DTO through the strict shared schema. */\nconst identityJson = (status: number, identity: unknown): Response => {\n const parsed = archiveIdentitySchema.safeParse(identity);\n if (!parsed.success) throw recordUnavailable();\n return json(status, parsed.data, noStore);\n};\n\nexport function getMe(session: Session | null): Response {\n return identityJson(\n 200,\n session?.publisher\n ? { id: session.publisher.id, name: session.publisher.displayName, operator: session.operator }\n : null,\n );\n}\n\nexport async function registerMe(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const body = await readJsonBody(request);\n const parsed = registerBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n const name = parsed.data.name ?? '';\n\n const existing = await first<{ display_name: string; id: string }>(\n env,\n SQL.publisher,\n session.subject.issuer,\n session.subject.subject,\n );\n if (existing !== null) {\n return identityJson(200, { id: existing.id, name: existing.display_name, operator: session.operator });\n }\n const id = uuid();\n await run(\n env,\n SQL.insertPublisher,\n id,\n session.subject.issuer,\n session.subject.subject,\n name,\n now(),\n session.subject.issuer,\n session.subject.subject,\n Date.now(),\n );\n const row = await first<{ display_name: string; id: string }>(\n env,\n SQL.publisher,\n session.subject.issuer,\n session.subject.subject,\n );\n if (row === null) {\n if ((await first(env, SQL.revokedSubject, session.subject.issuer, session.subject.subject, Date.now())) !== null) {\n throw new ApiError(401, 'unauthenticated', 'Sign-in is required.');\n }\n throw conflict();\n }\n return identityJson(201, { id: row.id, name: row.display_name, operator: session.operator });\n}\n\nexport async function renameMe(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const publisher = requirePublisherRow(session);\n const body = await readJsonBody(request);\n const parsed = renameBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n if (parsed.data.name !== publisher.displayName) {\n await run(env, SQL.renamePublisher, parsed.data.name, publisher.id, parsed.data.name);\n }\n return identityJson(200, { id: publisher.id, name: parsed.data.name, operator: session.operator });\n}\n\n/**\n * Archive-profile erasure, independent of the identity provider's account.\n * The credential survives so an issuer can retry coordinated account erasure.\n */\nexport async function deleteProfile(env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n if (session.publisher !== null) await run(env, SQL.deletePublisher, session.publisher.id);\n return new Response(null, {\n headers: {\n 'cache-control': 'no-store',\n },\n status: 204,\n });\n}\n\n/** Revocation and erasure share a transaction, preventing a stale session from recreating the profile. */\nexport async function deleteAccountProfile(env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const { issuer, subject } = session.subject;\n // Issuance may be one tolerance ahead; expiry verification permits another.\n await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.revokeSubject).bind(\n issuer,\n subject,\n Date.now() + (SESSION_MAX_AGE_SECONDS + 2 * JWT_CLOCK_TOLERANCE_SECONDS) * 1000,\n ),\n env.DB.prepare(SQL.deleteSubjectPublisher).bind(issuer, subject),\n ]),\n );\n return new Response(null, { headers: noStore, status: 204 });\n}\n\n/**\n * Operator-side GDPR erasure: removes any publisher's row; FK cascades take its\n * publications, likes and retry keys. Idempotent for the same reason the self-erase\n * is: an already-erased publisher is a 204 with nothing left to remove.\n */\nexport async function erasePublisher(env: ArchiveWorkerEnv, session: Session, publisherId: string): Promise<Response> {\n const operator = requireOperatorRow(session);\n // The terminal moderation action: erasing a publisher removes their row, and FK\n // cascades take the publications, likes and retry keys with it — the same cascade a\n // self-erase walks, applied to any publisher the operator names. The guarded audit\n // and deletion share a transaction, so retries never record fictitious actions.\n await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.auditErasure).bind(uuid(), operator.id, now(), publisherId),\n env.DB.prepare(SQL.deletePublisher).bind(publisherId),\n ]),\n );\n return new Response(null, { headers: noStore, status: 204 });\n}\n\n/** The operator's accountability log: newest actions first, bounded, ids only. */\nexport async function listModerationLog(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n requireOperatorRow(session);\n const context = 'moderation-log';\n const { cursor, limit } = forwardPage(request, context);\n const bound = moderationLogSql({ cursor, limit: limit + 1 });\n const rows = await all<{\n id: string;\n action: ArchiveModerationAction;\n collection: string | null;\n created_at: string;\n operator_id: string;\n target_id: string;\n }>(env, bound.sql, ...bound.parameters);\n const page = rows.slice(0, limit);\n const last = page.at(-1);\n const parsed = archiveModerationLogSchema.safeParse({\n actions: page.map((row) => ({\n action: row.action,\n collection: row.collection,\n createdAt: row.created_at,\n id: row.id,\n operatorId: row.operator_id,\n targetId: row.target_id,\n })),\n cursor:\n rows.length > limit && last !== undefined\n ? nextCursor({ createdAt: last.created_at, id: last.id }, context)\n : null,\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n}\n\nexport const requirePublisherRow = (session: Session): Publisher => {\n if (session.publisher === null) throw forbidden('Register before publishing.');\n return session.publisher;\n};\n\n/** Operator authority plus a registered publisher row: moderation routes need both,\n * the role claim for authority and the row for attribution. */\nexport const requireOperatorRow = (session: Session): Publisher => {\n if (!session.operator) throw forbidden('Operator access is required.');\n if (session.publisher === null) throw forbidden('Register before moderating.');\n return session.publisher;\n};\n\n// Private identity payloads share the one alias rule with the emitted DTOs.\nconst registerBodySchema = s.object({ name: archiveAliasSchema.optional() });\nconst renameBodySchema = s.object({ name: archiveAliasSchema });\n\nexport async function myPublications(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n session: Session,\n): Promise<Response> {\n if (session.publisher === null) return json(200, { entries: [] }, noStore);\n const rows = await all<PublicationRow>(\n env,\n SQL.ownPublications,\n session.publisher.id,\n collection.name,\n ARCHIVE_LIMITS.maxPublications,\n );\n // The private owner DTO adds the create retry key; public routes never emit it.\n return json(200, { entries: rows.map((row) => rowToOwnedEntry(collection, row)) }, noStore);\n}\n\nexport async function myLikes(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const params = new URL(request.url).searchParams;\n const raw = params.get('ids');\n if (raw === null) {\n const context = `likes:${session.publisher?.id ?? 'unregistered'}`;\n const { cursor, limit } = forwardPage(request, context);\n if (session.publisher === null) return json(200, { cursor: null, ids: [] }, noStore);\n const bound = likesPageSql(session.publisher.id, { cursor, limit: limit + 1 });\n const rows = await all<{ created_at: string; publication_id: string }>(env, bound.sql, ...bound.parameters);\n const page = rows.slice(0, limit);\n const last = page.at(-1);\n const parsed = archiveLikesPageSchema.safeParse({\n cursor:\n rows.length > limit && last !== undefined\n ? nextCursor({ createdAt: last.created_at, id: last.publication_id }, context)\n : null,\n ids: page.map((row) => row.publication_id),\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n }\n queryParams(new URL(request.url), ['ids']);\n const ids = [...new Set(raw.split(','))];\n if (ids.length > ARCHIVE_LIMITS.likeStateIds || ids.some((id) => !UUID_RE.test(id))) throw badRequest();\n if (session.publisher === null) return json(200, { ids: [] }, noStore);\n const bound = likedPublicationsSql(session.publisher.id, ids);\n const rows = await all<{ publication_id: string }>(env, bound.sql, ...bound.parameters);\n const liked = new Set(rows.map((r) => r.publication_id));\n const dto = { ids: ids.filter((id) => liked.has(id)) };\n const validated = archiveLikedIdsSchema.safeParse(dto);\n if (!validated.success) throw recordUnavailable();\n return json(200, validated.data, noStore);\n}\n\n/* --------------------------------- mutations ------------------------------- */\n\n// The core validates the request envelope; the collection's derive step validates the payload.\nconst publishBodySchema = s.object({ clientKey: uuidSchema, payload: s.unknown() });\nconst updateBodySchema = s.object({\n expectedRevision: s.number().int().min(1).max(Number.MAX_SAFE_INTEGER),\n payload: s.unknown(),\n});\n\ninterface RetryRecord {\n expires_at: number;\n payload_hash: string;\n publication_id: string | null;\n}\n\n/**\n * The one replay/conflict/gone decision for a create retry key, shared by the\n * pre-read and the post-insert race re-read: a live record for a different payload\n * is a 409, a tombstone (null publication id) is a 410, and a live record for the\n * same payload replays the entry. `'vanished'` means the recorded publication is\n * no longer readable — hidden or raced-deleted — and only the caller knows which:\n * the pre-read falls through to the publication-by-key lookup (a hidden entry is\n * 403 there), the post-insert re-read answers 410.\n */\nasync function resolveRetryRecord(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n record: RetryRecord | null,\n hash: string,\n): Promise<Response | 'vanished' | null> {\n if (record === null || record.expires_at <= Date.now()) return null;\n if (record.payload_hash !== hash) throw conflict('That key was used for a different payload.');\n if (record.publication_id === null) throw new ApiError(410, 'gone', 'That publication was removed.');\n const entry = await entryById(collection, env, record.publication_id);\n if (entry === null) return 'vanished';\n return json(200, entry, noStore);\n}\n\nexport async function publishEntry(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n config: ApiConfig,\n session: Session,\n): Promise<Response> {\n if (!config.publishingEnabled) throw new ApiError(503, 'publishing-disabled', 'Publishing is currently disabled.');\n const publisher = requirePublisherRow(session);\n const body = await readJsonBody(request);\n const parsed = publishBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n const { clientKey } = parsed.data;\n // The derive step owns every content rejection: schema, vocabulary, title, byte bound.\n const derivation = await collection.derive(parsed.data.payload);\n if (derivation === null) throw invalid();\n\n const payloadJson = derivation.canonicalJson;\n const hash = derivation.contentHash;\n const owner = publisher.id;\n const name = collection.name;\n\n // Retry-key inspection first: the request record decides replay vs conflict vs gone.\n // A logically expired record is ignored entirely — retention is enforced by time,\n // not just by the periodic sweep.\n const retry = await first<RetryRecord>(env, SQL.request, owner, name, clientKey);\n const replay = await resolveRetryRecord(collection, env, retry, hash);\n if (replay !== null && replay !== 'vanished') return replay;\n\n const existing = await first<PublicationRow>(env, SQL.publicationByKey, owner, name, clientKey);\n if (existing !== null) {\n // A hidden entry is frozen for its owner too: replaying the create key must not\n // read as a live publication.\n if (existing.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n if (existing.content_hash !== hash) throw conflict('That key was used for a different payload.');\n return json(200, rowToEntry(collection, existing), noStore);\n }\n\n const timestamp = now();\n const expiresAt = Date.now() + ARCHIVE_LIMITS.idempotencyRetentionMs;\n const tagsJson = JSON.stringify(derivation.tags);\n const results = await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.expireRequests).bind(Date.now()),\n env.DB.prepare(SQL.insertPublication).bind(\n uuid(),\n owner,\n name,\n clientKey,\n ARCHIVE_CONTRACT_VERSION,\n derivation.contentVersion,\n payloadJson,\n hash,\n derivation.filter,\n derivation.title,\n tagsJson,\n timestamp,\n timestamp,\n owner,\n name,\n ARCHIVE_LIMITS.maxPublications,\n owner,\n name,\n clientKey,\n Date.now(),\n ),\n env.DB.prepare(SQL.insertRequest).bind(\n owner,\n name,\n clientKey,\n hash,\n timestamp,\n expiresAt,\n owner,\n name,\n clientKey,\n hash,\n ),\n ]),\n );\n if ((results[1]?.meta?.changes ?? 0) === 0) {\n // The guarded insert was skipped: a race already wrote the key, the key's\n // publication was removed, or the owner's live-publication quota is full.\n // Re-reading the request record first distinguishes a tombstone from a race.\n const racedRecord = await first<RetryRecord>(env, SQL.request, owner, name, clientKey);\n const racedReplay = await resolveRetryRecord(collection, env, racedRecord, hash);\n if (racedReplay === 'vanished') throw new ApiError(410, 'gone', 'That publication was removed.');\n if (racedReplay !== null) return racedReplay;\n const raced = await first<PublicationRow>(env, SQL.publicationByKey, owner, name, clientKey);\n if (raced !== null && raced.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n if (raced !== null && raced.content_hash === hash) return json(200, rowToEntry(collection, raced), noStore);\n if (raced !== null) throw conflict('That key was used for a different payload.');\n throw new ApiError(429, 'quota', 'Publication quota reached.');\n }\n\n const created = await first<PublicationRow>(env, SQL.publicationByKey, owner, name, clientKey);\n if (created === null) throw conflict();\n return json(201, rowToEntry(collection, created), noStore);\n}\n\nexport async function updateEntry(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n config: ApiConfig,\n session: Session,\n id: string,\n): Promise<Response> {\n if (!config.publishingEnabled) throw new ApiError(503, 'publishing-disabled', 'Publishing is currently disabled.');\n const publisher = requirePublisherRow(session);\n // The owner's row regardless of moderation state: the author must be told their\n // entry is frozen, not that it vanished.\n const row = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n // Replacing an entry is the author's alone; anyone else sees the entry as absent.\n if (row === null || row.owner_id !== publisher.id) throw notFound();\n // Hidden entries are frozen: an edit must not bypass the operator's decision.\n if (row.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n\n const body = await readJsonBody(request);\n const parsed = updateBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n const { expectedRevision } = parsed.data;\n\n if (expectedRevision !== row.revision) throw conflict();\n\n const derivation = await collection.derive(parsed.data.payload);\n if (derivation === null) throw invalid();\n const payloadJson = derivation.canonicalJson;\n const hash = derivation.contentHash;\n if (hash === row.content_hash) {\n const current = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n if (current === null || current.owner_id !== publisher.id) throw notFound();\n if (current.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n if (current.revision !== expectedRevision) throw conflict();\n return json(200, rowToEntry(collection, current), noStore);\n }\n\n const tagsJson = JSON.stringify(derivation.tags);\n const result = await run(\n env,\n SQL.updatePublication,\n ARCHIVE_CONTRACT_VERSION,\n derivation.contentVersion,\n payloadJson,\n hash,\n derivation.filter,\n derivation.title,\n tagsJson,\n now(),\n id,\n publisher.id,\n expectedRevision,\n hash,\n );\n if ((result.meta?.changes ?? 0) === 0) {\n const current = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n if (current === null) throw notFound();\n if (current.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n throw conflict();\n }\n const updated = await entryById(collection, env, id);\n if (updated === null) throw notFound();\n return json(200, updated, noStore);\n}\n\nexport async function deleteEntry(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n session: Session,\n id: string,\n): Promise<Response> {\n // The owner's row regardless of moderation state: the author may still remove a\n // hidden entry (their content, their call) — hiding freezes edits, not removal.\n // Removing an entry is the author's alone; an operator may remove ANY entry — the\n // terminal moderation action for abusive content. The operator path carries the\n // same registered-publisher-row requirement every moderation route shares: one\n // accountable identity per action. Everyone else sees the entry as absent.\n if (session.operator) {\n const operator = requireOperatorRow(session);\n await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.auditRemoval).bind(uuid(), operator.id, now(), id, collection.name),\n env.DB.prepare(SQL.removePublicationAny).bind(id, collection.name),\n ]),\n );\n } else {\n const publisher = requirePublisherRow(session);\n const row = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n if (row === null) return new Response(null, { headers: noStore, status: 204 });\n if (row.owner_id !== publisher.id) throw notFound();\n await run(env, SQL.removePublication, id, publisher.id);\n }\n return new Response(null, { headers: noStore, status: 204 });\n}\n\nexport async function setLike(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n config: ApiConfig,\n session: Session,\n id: string,\n liked: boolean,\n): Promise<Response> {\n const publisher = requirePublisherRow(session);\n if (liked && !config.likesEnabled) throw new ApiError(503, 'likes-disabled', 'Likes are currently disabled.');\n const row = await first<PublicationRow>(env, SQL.publication, id, collection.name);\n if (row === null) throw notFound();\n if (liked) {\n await run(env, SQL.insertLike, publisher.id, now(), id);\n } else {\n await run(env, SQL.removeLike, id, publisher.id);\n }\n // One consistent snapshot: the count and the viewer's flag read together.\n const state = await first<{ id: string; like_count: number; liked: number }>(env, SQL.likeState, publisher.id, id);\n if (state === null) throw notFound();\n const dto = { id: state.id, likeCount: state.like_count, liked: state.liked === 1 };\n const validated = archiveLikeStateSchema.safeParse(dto);\n if (!validated.success) throw recordUnavailable();\n return json(200, validated.data, noStore);\n}\n\n/* -------------------------------- moderation ------------------------------- */\n\n// One free-text report reason: trimmed, non-empty, well-formed, bounded.\nconst reportBodySchema = s.object({\n reason: s.string().check(isWellFormedText).trim().min(1).max(ARCHIVE_LIMITS.reportReasonChars),\n});\n\n/**\n * A reader's abuse report against one visible entry. Idempotent per (entry, reporter):\n * reporting again is the same 204, never a duplicate row. Hidden or missing entries\n * are 404 — they cannot be reported, they are already actioned or gone.\n */\nexport async function reportEntry(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n session: Session,\n id: string,\n): Promise<Response> {\n const reporter = requirePublisherRow(session);\n const body = await readJsonBody(request);\n const parsed = reportBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n const row = await first<PublicationRow>(env, SQL.publication, id, collection.name);\n if (row === null) throw notFound();\n // Bound in the SQL's authored placeholder order: reporter, reason, created_at,\n // then the WHERE's entry id and collection.\n await run(env, SQL.insertReport, uuid(), reporter.id, parsed.data.reason, now(), id, collection.name);\n return new Response(null, { headers: noStore, status: 204 });\n}\n\n/**\n * The operator soft-hide: removes an entry from every public surface while keeping\n * the row, its likes and its retry key intact. Idempotent — already in the requested\n * state answers the same 204. Operator authority is the session's verified role\n * claim; a registered publisher row is still required (one accountable identity).\n */\nexport async function setHidden(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n session: Session,\n id: string,\n hidden: boolean,\n): Promise<Response> {\n const operator = requireOperatorRow(session);\n // Any collection row, visible or hidden: the operator acts on the storage row. The\n // pre-read establishes existence only. The transactional audit guard decides\n // whether the requested transition is an effective action.\n const row = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n if (row === null) throw notFound();\n const statement = hidden\n ? env.DB.prepare(SQL.hidePublication).bind(now(), id, collection.name)\n : env.DB.prepare(SQL.unhidePublication).bind(id, collection.name);\n await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.auditHide).bind(\n uuid(),\n operator.id,\n hidden ? 'hide' : 'unhide',\n now(),\n id,\n collection.name,\n Number(hidden),\n ),\n statement,\n ]),\n );\n return new Response(null, { headers: noStore, status: 204 });\n}\n\n/** The operator's report queue for one collection: newest first, bounded. */\nexport async function listReports(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n session: Session,\n): Promise<Response> {\n requireOperatorRow(session);\n const status = new URL(request.url).searchParams.get('status') ?? 'unresolved';\n if (status !== 'unresolved' && status !== 'resolved') throw badRequest();\n const context = `reports:${collection.name}:${status}`;\n const { cursor, limit } = forwardPage(request, context, ['status']);\n const bound = reportsPageSql(\n { collection: collection.name, resolved: status === 'resolved' },\n { cursor, limit: limit + 1 },\n );\n const rows = await all<ReportRow>(env, bound.sql, ...bound.parameters);\n const page = rows.slice(0, limit);\n const last = page.at(-1);\n const parsed = archiveReportsSchema.safeParse({\n cursor:\n rows.length > limit && last !== undefined\n ? nextCursor({ createdAt: last.created_at, id: last.id }, context)\n : null,\n reports: page.map((row) => ({\n createdAt: row.created_at,\n entryId: row.publication_id,\n entryTitle: row.entry_title,\n id: row.id,\n reason: row.reason,\n reporter: { id: row.reporter_id, name: row.reporter_name },\n resolvedAt: row.resolved_at,\n })),\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n}\n\ninterface ReportRow {\n collection: string;\n created_at: string;\n entry_title: string;\n id: string;\n publication_id: string;\n reason: string;\n reporter_id: string;\n reporter_name: string;\n resolved_at: string | null;\n}\n\nexport async function resolveReport(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n session: Session,\n id: string,\n): Promise<Response> {\n requireOperatorRow(session);\n const row = await first<{ id: string }>(env, SQL.reportById, id, collection.name);\n if (row === null) throw notFound();\n await run(env, SQL.resolveReport, now(), id, collection.name);\n return new Response(null, { headers: noStore, status: 204 });\n}\n\nexport async function myReports(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const context = `own-reports:${session.publisher?.id ?? 'unregistered'}`;\n const { cursor, limit } = forwardPage(request, context);\n if (session.publisher === null) return json(200, { cursor: null, reports: [] }, noStore);\n const bound = reportsPageSql({ publisherId: session.publisher.id }, { cursor, limit: limit + 1 });\n const rows = await all<ReportRow>(env, bound.sql, ...bound.parameters);\n const page = rows.slice(0, limit);\n const last = page.at(-1);\n const parsed = archiveOwnReportsSchema.safeParse({\n cursor:\n rows.length > limit && last !== undefined\n ? nextCursor({ createdAt: last.created_at, id: last.id }, context)\n : null,\n reports: page.map((row) => ({\n collection: row.collection,\n createdAt: row.created_at,\n entryId: row.publication_id,\n id: row.id,\n reason: row.reason,\n resolvedAt: row.resolved_at,\n })),\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"publications.js","names":["operation","cause","ApiError","params","env","sql","result","payloadJson","row","parsed","collection","entry","uuidSchema","SQL","id","search","ARCHIVE_LIMITS","badRequest","terms","t","query","MAX_VOCAB_VALUES","raw","key","values","declared","value","url","request","queryParams","sortRaw","sortParsed","archiveSortSchema","filters","tags","author","UUID_RE","searchMatch","context","forwardPageParams","cursor","limit","bound","listPublicationsSql","fetched","page","last","pageDto","nextCursor","json","noStore","archiveCollectionMetaSchema","notFound","dbSchemaVersion","reason","archiveHealthSchema","config","archiveIdentitySchema","identity","status","session","body","readJsonBody","registerBodySchema","invalid","name","existing","conflict","publisher","renameBodySchema","issuer","subject","SESSION_MAX_AGE_SECONDS","operator","publisherId","forwardPage","moderationLogSql","rows","archiveModerationLogSchema","forbidden","s","archiveAliasSchema","likesPageSql","archiveLikesPageSchema","ids","likedPublicationsSql","liked","r","dto","validated","archiveLikedIdsSchema","publishBodySchema","updateBodySchema","record","hash","derivation","owner","replay","clientKey","timestamp","expiresAt","tagsJson","racedReplay","raced","created","expectedRevision","current","updated","state","archiveLikeStateSchema","reportBodySchema","isWellFormedText","reporter","statement","hidden","reportsPageSql","archiveReportsSchema","archiveOwnReportsSchema"],"sources":["../../src/worker/publications.ts"],"sourcesContent":["import { s } from '@vielzeug/spell';\nimport {\n ApiError,\n badRequest,\n conflict,\n forbidden,\n invalid,\n json,\n noStore,\n notFound,\n queryParams,\n readJsonBody,\n} from '../_server/http';\nimport { UUID_RE } from '../_server/uuid';\nimport type { ArchiveCollection } from '../collection';\nimport {\n ARCHIVE_CONTRACT_VERSION,\n ARCHIVE_LIMITS,\n ARCHIVE_SCHEMA_VERSION,\n type ArchiveModerationAction,\n archiveAliasSchema,\n archiveCollectionMetaSchema,\n archiveHealthSchema,\n archiveIdentitySchema,\n archiveLikedIdsSchema,\n archiveLikeStateSchema,\n archiveLikesPageSchema,\n archiveModerationLogSchema,\n archiveOwnReportsSchema,\n archiveReportsSchema,\n archiveSortSchema,\n isWellFormedText,\n SESSION_MAX_AGE_SECONDS,\n uuidSchema,\n} from '../contract';\nimport { forwardPage, forwardPageParams, nextCursor } from './_pagination';\nimport { JWT_CLOCK_TOLERANCE_SECONDS, type Publisher, type Session } from './auth';\nimport type { ApiConfig, ArchiveWorkerEnv } from './env';\nimport {\n likedPublicationsSql,\n likesPageSql,\n listPublicationsSql,\n moderationLogSql,\n type PublicationCursor,\n type PublicationFilter,\n reportsPageSql,\n SQL,\n} from './sql';\n\n/**\n * Route handlers for /api/v1, parameterized by the registered collection. All SQL lives\n * in sql.ts and is bound positionally here in the authored order; the DTO boundary\n * re-validates every stored row before it leaves, content validation lives in the\n * collection's derive step, and moderation state (hidden entries) is visible only\n * through the owner and operator surfaces.\n */\n\ninterface PublicationRow {\n author_name: string;\n client_key: string;\n collection: string;\n content_hash: string;\n content_version: number;\n filter_value: string | null;\n /** Null while visible; set while an operator has removed the entry from public view. */\n hidden_at: string | null;\n id: string;\n like_count: number;\n owner_id: string;\n payload_json: string;\n published_at: string;\n revision: number;\n schema_version: number;\n tags_json: string;\n title: string;\n updated_at: string;\n}\n\nasync function databaseCall<T>(operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (cause) {\n throw new ApiError(500, 'internal', 'The service is not available.', { cause, diagnostic: { kind: 'database' } });\n }\n}\nconst first = <T>(env: ArchiveWorkerEnv, sql: string, ...params: (string | number | null)[]) =>\n databaseCall(() =>\n env.DB.prepare(sql)\n .bind(...params)\n .first<T>(),\n );\nconst all = <T>(env: ArchiveWorkerEnv, sql: string, ...params: (string | number | null)[]) =>\n databaseCall(async () => {\n const result = await env.DB.prepare(sql)\n .bind(...params)\n .all<T>();\n if (!Array.isArray(result.results)) throw new Error('Missing database query results');\n return result.results;\n });\nconst run = (env: ArchiveWorkerEnv, sql: string, ...params: (string | number | null)[]) =>\n databaseCall(() =>\n env.DB.prepare(sql)\n .bind(...params)\n .run(),\n );\n\nconst uuid = (): string => crypto.randomUUID();\nconst now = (): string => new Date().toISOString();\n\n/** A stored row that fails the emitted-DTO contract is unavailable, not malformed data. */\nconst recordUnavailable = (): ApiError =>\n new ApiError(503, 'record-unavailable', 'The record is not available.', { diagnostic: { kind: 'record' } });\n\n/**\n * Maps a joined row to the public DTO through the collection's strict entry schema:\n * stored JSON is parsed and re-validated, stored versions are emitted as-is, and any\n * drift — unknown fields, corrupt payload, mismatched versions — surfaces as 503\n * `record-unavailable` rather than leaking or fabricating data.\n */\nconst rowToEntry = (collection: ArchiveCollection, row: PublicationRow) => {\n let payloadJson: unknown;\n try {\n payloadJson = JSON.parse(row.payload_json);\n } catch {\n throw recordUnavailable();\n }\n const parsed = collection.contract.entry.safeParse({\n author: { id: row.owner_id, name: row.author_name },\n contentHash: row.content_hash,\n contentVersion: row.content_version,\n id: row.id,\n likeCount: row.like_count,\n payload: payloadJson,\n publishedAt: row.published_at,\n revision: row.revision,\n schemaVersion: row.schema_version,\n updatedAt: row.updated_at,\n });\n if (!parsed.success) throw recordUnavailable();\n return parsed.data;\n};\n\nconst rowToOwnedEntry = (collection: ArchiveCollection, row: PublicationRow) => {\n const entry = rowToEntry(collection, row);\n // The private owner DTO adds the create retry key and the moderation state;\n // public routes never emit either.\n const key = uuidSchema.safeParse(row.client_key);\n if (!key.success) throw recordUnavailable();\n return { ...entry, clientKey: row.client_key, hidden: row.hidden_at !== null };\n};\n\nconst entryById = async (collection: ArchiveCollection, env: ArchiveWorkerEnv, id: string) => {\n const row = await first<PublicationRow>(env, SQL.publication, id, collection.name);\n return row === null ? null : rowToEntry(collection, row);\n};\n\n/* ----------------------------------- list --------------------------------- */\n\n/** Normalizes free text into the bounded FTS prefix-match expression the SQL expects. */\nconst searchMatchFor = (search: string | null): string | undefined => {\n if (search === null) return undefined;\n if (search.length > ARCHIVE_LIMITS.searchChars) throw badRequest();\n // Unicode word extraction: punctuation splits terms rather than poisoning them,\n // so \"Hunter's\" and \"fire-ice\" match the words their indexed titles carry.\n const terms = (search.match(/[\\p{L}\\p{N}]+/gu) ?? []).slice(0, ARCHIVE_LIMITS.searchTerms);\n if (terms.length === 0) return undefined;\n return terms.map((t) => `\"${t}\"*`).join(' ');\n};\n\n/**\n * The entry-list cursor context: every query dimension the page's boundary depends\n * on, so a cursor minted under one filter/sort/search and replayed under another is\n * a 400 — the same forward-cursor guard every other list in the package uses.\n */\nconst entryCursorContext = (query: {\n author?: string;\n collection: string;\n filters: readonly string[];\n searchMatch?: string;\n sort: 'liked' | 'recent';\n tags: readonly string[];\n}): string =>\n JSON.stringify([\n query.collection,\n query.sort,\n query.author ?? '',\n query.filters,\n query.tags,\n query.searchMatch ?? '',\n ]);\n\nconst cursorFromRow = (row: PublicationRow): PublicationCursor => ({\n createdAt: row.published_at,\n id: row.id,\n likeCount: row.like_count,\n});\n\n/** Repeated `filter`/`tag` values one entry-list request may carry (OR within each). */\nconst MAX_VOCAB_VALUES = 16;\n\n/** Reads one repeatable vocabulary param: every value must be declared, no repeats\n * past the bound, and the list is deduplicated and sorted so `?tag=a&tag=a` matches\n * `?tag=a` and value order never changes the cursor context. */\nconst vocabValues = (params: URLSearchParams, key: string, declared: readonly string[]): string[] => {\n const raw = params.getAll(key);\n if (raw.length > MAX_VOCAB_VALUES) throw badRequest();\n const values = [...new Set(raw)].sort();\n if (values.some((value) => !declared.includes(value))) throw badRequest();\n return values;\n};\n\nexport async function listEntries(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n): Promise<Response> {\n const url = new URL(request.url);\n const params = queryParams(url, ['author', 'cursor', 'filter', 'limit', 'search', 'sort', 'tag'], ['filter', 'tag']);\n\n const sortRaw = params.get('sort') ?? 'recent';\n const sortParsed = archiveSortSchema.safeParse(sortRaw);\n if (!sortParsed.success) throw badRequest();\n\n const filters = vocabValues(params, 'filter', collection.filters);\n const tags = vocabValues(params, 'tag', collection.tags);\n const author = params.get('author');\n if (author !== null && !UUID_RE.test(author)) throw badRequest();\n\n const searchMatch = searchMatchFor(params.get('search'));\n const context = entryCursorContext({\n author: author ?? undefined,\n collection: collection.name,\n filters,\n searchMatch,\n sort: sortParsed.data,\n tags,\n });\n const { cursor, limit } = forwardPageParams(params, context);\n\n const query: PublicationFilter = {\n author: author ?? undefined,\n collection: collection.name,\n cursor:\n cursor === null ? undefined : { createdAt: cursor.createdAt, id: cursor.id, likeCount: cursor.likeCount ?? 0 },\n filters,\n limit: limit + 1,\n searchMatch,\n sort: sortParsed.data,\n tags,\n };\n\n // One bounded query — never a count: the over-read row is the next page's proof.\n const bound = listPublicationsSql(query);\n const fetched = await all<PublicationRow>(env, bound.sql, ...bound.parameters);\n const page = fetched.slice(0, limit);\n const last = page.at(-1);\n\n // Entries are validated per row through the collection's entry schema; the cursor\n // is internally generated, so the page composes validated parts directly.\n const pageDto = {\n cursor: fetched.length > limit && last !== undefined ? nextCursor(cursorFromRow(last), context) : null,\n entries: page.map((row) => rowToEntry(collection, row)),\n };\n return json(200, pageDto, noStore);\n}\n\n/** The collection's public self-description: name plus the declared query vocabularies. */\nexport function collectionMeta(collection: ArchiveCollection): Response {\n const parsed = archiveCollectionMetaSchema.safeParse({\n filters: [...collection.filters].sort(),\n name: collection.name,\n tags: [...collection.tags].sort(),\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n}\n\nexport async function getEntry(collection: ArchiveCollection, env: ArchiveWorkerEnv, id: string): Promise<Response> {\n const entry = await entryById(collection, env, id);\n if (entry === null) throw notFound();\n return json(200, entry, noStore);\n}\n\n/* ---------------------------------- health --------------------------------- */\n\n/**\n * Service health: the feature flags, the wire limits, and the storage schema stamp\n * actually present in the database. A database whose migrations lag the package\n * answers `degraded` instead of pretending to be fine — the most likely deployment\n * fault becomes a one-glance diagnosis.\n */\nexport async function healthJson(config: ApiConfig, env: ArchiveWorkerEnv): Promise<Response> {\n let dbSchemaVersion: number | null = null;\n let reason: 'database-unavailable' | 'schema-mismatch' | null = null;\n try {\n const row = await first<{ applied: number }>(env, SQL.schemaVersion);\n dbSchemaVersion = row?.applied ?? 0;\n } catch {\n reason = 'database-unavailable';\n }\n if (reason === null && dbSchemaVersion !== ARCHIVE_SCHEMA_VERSION) reason = 'schema-mismatch';\n const parsed = archiveHealthSchema.safeParse({\n dbSchemaVersion,\n features: { likes: config.likesEnabled, publishing: config.publishingEnabled },\n limits: ARCHIVE_LIMITS,\n reason,\n schemaVersion: ARCHIVE_CONTRACT_VERSION,\n status: reason === null ? 'ok' : 'degraded',\n });\n if (!parsed.success) throw recordUnavailable();\n return json(reason === null ? 200 : 503, parsed.data, noStore);\n}\n\n/* --------------------------------- identity -------------------------------- */\n\n/** Emits the private identity DTO through the strict shared schema. */\nconst identityJson = (status: number, identity: unknown): Response => {\n const parsed = archiveIdentitySchema.safeParse(identity);\n if (!parsed.success) throw recordUnavailable();\n return json(status, parsed.data, noStore);\n};\n\nexport function getMe(session: Session | null): Response {\n return identityJson(\n 200,\n session?.publisher\n ? { id: session.publisher.id, name: session.publisher.displayName, operator: session.operator }\n : null,\n );\n}\n\nexport async function registerMe(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const body = await readJsonBody(request);\n const parsed = registerBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n const name = parsed.data.name ?? '';\n\n const existing = await first<{ display_name: string; id: string }>(\n env,\n SQL.publisher,\n session.subject.issuer,\n session.subject.subject,\n );\n if (existing !== null) {\n return identityJson(200, { id: existing.id, name: existing.display_name, operator: session.operator });\n }\n const id = uuid();\n await run(\n env,\n SQL.insertPublisher,\n id,\n session.subject.issuer,\n session.subject.subject,\n name,\n now(),\n session.subject.issuer,\n session.subject.subject,\n Date.now(),\n );\n const row = await first<{ display_name: string; id: string }>(\n env,\n SQL.publisher,\n session.subject.issuer,\n session.subject.subject,\n );\n if (row === null) {\n if ((await first(env, SQL.revokedSubject, session.subject.issuer, session.subject.subject, Date.now())) !== null) {\n throw new ApiError(401, 'unauthenticated', 'Sign-in is required.');\n }\n throw conflict();\n }\n return identityJson(201, { id: row.id, name: row.display_name, operator: session.operator });\n}\n\nexport async function renameMe(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const publisher = requirePublisherRow(session);\n const body = await readJsonBody(request);\n const parsed = renameBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n if (parsed.data.name !== publisher.displayName) {\n await run(env, SQL.renamePublisher, parsed.data.name, publisher.id, parsed.data.name);\n }\n return identityJson(200, { id: publisher.id, name: parsed.data.name, operator: session.operator });\n}\n\n/**\n * Archive-profile erasure, independent of the identity provider's account.\n * The credential survives so an issuer can retry coordinated account erasure.\n */\nexport async function deleteProfile(env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n if (session.publisher !== null) await run(env, SQL.deletePublisher, session.publisher.id);\n return new Response(null, {\n headers: {\n 'cache-control': 'no-store',\n },\n status: 204,\n });\n}\n\n/** Revocation and erasure share a transaction, preventing a stale session from recreating the profile. */\nexport async function deleteAccountProfile(env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const { issuer, subject } = session.subject;\n // Issuance may be one tolerance ahead; expiry verification permits another.\n await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.revokeSubject).bind(\n issuer,\n subject,\n Date.now() + (SESSION_MAX_AGE_SECONDS + 2 * JWT_CLOCK_TOLERANCE_SECONDS) * 1000,\n ),\n env.DB.prepare(SQL.deleteSubjectPublisher).bind(issuer, subject),\n ]),\n );\n return new Response(null, { headers: noStore, status: 204 });\n}\n\n/**\n * Operator-side GDPR erasure: removes any publisher's row; FK cascades take its\n * publications, likes and retry keys. Idempotent for the same reason the self-erase\n * is: an already-erased publisher is a 204 with nothing left to remove.\n */\nexport async function erasePublisher(env: ArchiveWorkerEnv, session: Session, publisherId: string): Promise<Response> {\n const operator = requireOperatorRow(session);\n // The terminal moderation action: erasing a publisher removes their row, and FK\n // cascades take the publications, likes and retry keys with it — the same cascade a\n // self-erase walks, applied to any publisher the operator names. The guarded audit\n // and deletion share a transaction, so retries never record fictitious actions.\n await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.auditErasure).bind(uuid(), operator.id, now(), publisherId),\n env.DB.prepare(SQL.deletePublisher).bind(publisherId),\n ]),\n );\n return new Response(null, { headers: noStore, status: 204 });\n}\n\n/** The operator's accountability log: newest actions first, bounded, ids only. */\nexport async function listModerationLog(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n requireOperatorRow(session);\n const context = 'moderation-log';\n const { cursor, limit } = forwardPage(request, context);\n const bound = moderationLogSql({ cursor, limit: limit + 1 });\n const rows = await all<{\n id: string;\n action: ArchiveModerationAction;\n collection: string | null;\n created_at: string;\n operator_id: string;\n target_id: string;\n }>(env, bound.sql, ...bound.parameters);\n const page = rows.slice(0, limit);\n const last = page.at(-1);\n const parsed = archiveModerationLogSchema.safeParse({\n actions: page.map((row) => ({\n action: row.action,\n collection: row.collection,\n createdAt: row.created_at,\n id: row.id,\n operatorId: row.operator_id,\n targetId: row.target_id,\n })),\n cursor:\n rows.length > limit && last !== undefined\n ? nextCursor({ createdAt: last.created_at, id: last.id }, context)\n : null,\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n}\n\nexport const requirePublisherRow = (session: Session): Publisher => {\n if (session.publisher === null) throw forbidden('Register before publishing.');\n return session.publisher;\n};\n\n/** Operator authority plus a registered publisher row: moderation routes need both,\n * the role claim for authority and the row for attribution. */\nexport const requireOperatorRow = (session: Session): Publisher => {\n if (!session.operator) throw forbidden('Operator access is required.');\n if (session.publisher === null) throw forbidden('Register before moderating.');\n return session.publisher;\n};\n\n// Private identity payloads share the one alias rule with the emitted DTOs.\nconst registerBodySchema = s.object({ name: archiveAliasSchema.optional() });\nconst renameBodySchema = s.object({ name: archiveAliasSchema });\n\nexport async function myPublications(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n session: Session,\n): Promise<Response> {\n if (session.publisher === null) return json(200, { entries: [] }, noStore);\n const rows = await all<PublicationRow>(\n env,\n SQL.ownPublications,\n session.publisher.id,\n collection.name,\n ARCHIVE_LIMITS.maxPublications,\n );\n // The private owner DTO adds the create retry key; public routes never emit it.\n return json(200, { entries: rows.map((row) => rowToOwnedEntry(collection, row)) }, noStore);\n}\n\nexport async function myLikes(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const params = new URL(request.url).searchParams;\n const raw = params.get('ids');\n if (raw === null) {\n const context = `likes:${session.publisher?.id ?? 'unregistered'}`;\n const { cursor, limit } = forwardPage(request, context);\n if (session.publisher === null) return json(200, { cursor: null, ids: [] }, noStore);\n const bound = likesPageSql(session.publisher.id, { cursor, limit: limit + 1 });\n const rows = await all<{ created_at: string; publication_id: string }>(env, bound.sql, ...bound.parameters);\n const page = rows.slice(0, limit);\n const last = page.at(-1);\n const parsed = archiveLikesPageSchema.safeParse({\n cursor:\n rows.length > limit && last !== undefined\n ? nextCursor({ createdAt: last.created_at, id: last.publication_id }, context)\n : null,\n ids: page.map((row) => row.publication_id),\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n }\n queryParams(new URL(request.url), ['ids']);\n const ids = [...new Set(raw.split(','))];\n if (ids.length > ARCHIVE_LIMITS.likeStateIds || ids.some((id) => !UUID_RE.test(id))) throw badRequest();\n if (session.publisher === null) return json(200, { ids: [] }, noStore);\n const bound = likedPublicationsSql(session.publisher.id, ids);\n const rows = await all<{ publication_id: string }>(env, bound.sql, ...bound.parameters);\n const liked = new Set(rows.map((r) => r.publication_id));\n const dto = { ids: ids.filter((id) => liked.has(id)) };\n const validated = archiveLikedIdsSchema.safeParse(dto);\n if (!validated.success) throw recordUnavailable();\n return json(200, validated.data, noStore);\n}\n\n/* --------------------------------- mutations ------------------------------- */\n\n// The core validates the request envelope; the collection's derive step validates the payload.\nconst publishBodySchema = s.object({ clientKey: uuidSchema, payload: s.unknown() });\nconst updateBodySchema = s.object({\n expectedRevision: s.number().int().min(1).max(Number.MAX_SAFE_INTEGER),\n payload: s.unknown(),\n});\n\ninterface RetryRecord {\n expires_at: number;\n payload_hash: string;\n publication_id: string | null;\n}\n\n/**\n * The one replay/conflict/gone decision for a create retry key, shared by the\n * pre-read and the post-insert race re-read: a live record for a different payload\n * is a 409, a tombstone (null publication id) is a 410, and a live record for the\n * same payload replays the entry. `'vanished'` means the recorded publication is\n * no longer readable — hidden or raced-deleted — and only the caller knows which:\n * the pre-read falls through to the publication-by-key lookup (a hidden entry is\n * 403 there), the post-insert re-read answers 410.\n */\nasync function resolveRetryRecord(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n record: RetryRecord | null,\n hash: string,\n): Promise<Response | 'vanished' | null> {\n if (record === null || record.expires_at <= Date.now()) return null;\n if (record.payload_hash !== hash) throw conflict('That key was used for a different payload.');\n if (record.publication_id === null) throw new ApiError(410, 'gone', 'That publication was removed.');\n const entry = await entryById(collection, env, record.publication_id);\n if (entry === null) return 'vanished';\n return json(200, entry, noStore);\n}\n\nexport async function publishEntry(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n config: ApiConfig,\n session: Session,\n): Promise<Response> {\n if (!config.publishingEnabled) throw new ApiError(503, 'publishing-disabled', 'Publishing is currently disabled.');\n const publisher = requirePublisherRow(session);\n const body = await readJsonBody(request);\n const parsed = publishBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n const { clientKey } = parsed.data;\n // The derive step owns every content rejection: schema, vocabulary, title, byte bound.\n const derivation = await collection.derive(parsed.data.payload);\n if (derivation === null) throw invalid();\n\n const payloadJson = derivation.canonicalJson;\n const hash = derivation.contentHash;\n const owner = publisher.id;\n const name = collection.name;\n\n // Retry-key inspection first: the request record decides replay vs conflict vs gone.\n // A logically expired record is ignored entirely — retention is enforced by time,\n // not just by the periodic sweep.\n const retry = await first<RetryRecord>(env, SQL.request, owner, name, clientKey);\n const replay = await resolveRetryRecord(collection, env, retry, hash);\n if (replay !== null && replay !== 'vanished') return replay;\n\n const existing = await first<PublicationRow>(env, SQL.publicationByKey, owner, name, clientKey);\n if (existing !== null) {\n // A hidden entry is frozen for its owner too: replaying the create key must not\n // read as a live publication.\n if (existing.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n if (existing.content_hash !== hash) throw conflict('That key was used for a different payload.');\n return json(200, rowToEntry(collection, existing), noStore);\n }\n\n const timestamp = now();\n const expiresAt = Date.now() + ARCHIVE_LIMITS.idempotencyRetentionMs;\n const tagsJson = JSON.stringify(derivation.tags);\n const results = await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.expireRequests).bind(Date.now()),\n env.DB.prepare(SQL.insertPublication).bind(\n uuid(),\n owner,\n name,\n clientKey,\n ARCHIVE_CONTRACT_VERSION,\n derivation.contentVersion,\n payloadJson,\n hash,\n derivation.filter,\n derivation.title,\n tagsJson,\n timestamp,\n timestamp,\n owner,\n name,\n ARCHIVE_LIMITS.maxPublications,\n owner,\n name,\n clientKey,\n Date.now(),\n ),\n env.DB.prepare(SQL.insertRequest).bind(\n owner,\n name,\n clientKey,\n hash,\n timestamp,\n expiresAt,\n owner,\n name,\n clientKey,\n hash,\n ),\n ]),\n );\n if ((results[1]?.meta?.changes ?? 0) === 0) {\n // The guarded insert was skipped: a race already wrote the key, the key's\n // publication was removed, or the owner's live-publication quota is full.\n // Re-reading the request record first distinguishes a tombstone from a race.\n const racedRecord = await first<RetryRecord>(env, SQL.request, owner, name, clientKey);\n const racedReplay = await resolveRetryRecord(collection, env, racedRecord, hash);\n if (racedReplay === 'vanished') throw new ApiError(410, 'gone', 'That publication was removed.');\n if (racedReplay !== null) return racedReplay;\n const raced = await first<PublicationRow>(env, SQL.publicationByKey, owner, name, clientKey);\n if (raced !== null && raced.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n if (raced !== null && raced.content_hash === hash) return json(200, rowToEntry(collection, raced), noStore);\n if (raced !== null) throw conflict('That key was used for a different payload.');\n throw new ApiError(429, 'quota', 'Publication quota reached.');\n }\n\n const created = await first<PublicationRow>(env, SQL.publicationByKey, owner, name, clientKey);\n if (created === null) throw conflict();\n return json(201, rowToEntry(collection, created), noStore);\n}\n\nexport async function updateEntry(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n config: ApiConfig,\n session: Session,\n id: string,\n): Promise<Response> {\n if (!config.publishingEnabled) throw new ApiError(503, 'publishing-disabled', 'Publishing is currently disabled.');\n const publisher = requirePublisherRow(session);\n // The owner's row regardless of moderation state: the author must be told their\n // entry is frozen, not that it vanished.\n const row = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n // Replacing an entry is the author's alone; anyone else sees the entry as absent.\n if (row === null || row.owner_id !== publisher.id) throw notFound();\n // Hidden entries are frozen: an edit must not bypass the operator's decision.\n if (row.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n\n const body = await readJsonBody(request);\n const parsed = updateBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n const { expectedRevision } = parsed.data;\n\n if (expectedRevision !== row.revision) throw conflict();\n\n const derivation = await collection.derive(parsed.data.payload);\n if (derivation === null) throw invalid();\n const payloadJson = derivation.canonicalJson;\n const hash = derivation.contentHash;\n if (hash === row.content_hash) {\n const current = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n if (current === null || current.owner_id !== publisher.id) throw notFound();\n if (current.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n if (current.revision !== expectedRevision) throw conflict();\n return json(200, rowToEntry(collection, current), noStore);\n }\n\n const tagsJson = JSON.stringify(derivation.tags);\n const result = await run(\n env,\n SQL.updatePublication,\n ARCHIVE_CONTRACT_VERSION,\n derivation.contentVersion,\n payloadJson,\n hash,\n derivation.filter,\n derivation.title,\n tagsJson,\n now(),\n id,\n publisher.id,\n expectedRevision,\n hash,\n );\n if ((result.meta?.changes ?? 0) === 0) {\n const current = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n if (current === null) throw notFound();\n if (current.hidden_at !== null) throw forbidden('This entry is under moderation review.');\n throw conflict();\n }\n const updated = await entryById(collection, env, id);\n if (updated === null) throw notFound();\n return json(200, updated, noStore);\n}\n\nexport async function deleteEntry(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n session: Session,\n id: string,\n): Promise<Response> {\n // The owner's row regardless of moderation state: the author may still remove a\n // hidden entry (their content, their call) — hiding freezes edits, not removal.\n // Removing an entry is the author's alone; an operator may remove ANY entry — the\n // terminal moderation action for abusive content. The operator path carries the\n // same registered-publisher-row requirement every moderation route shares: one\n // accountable identity per action. Everyone else sees the entry as absent.\n if (session.operator) {\n const operator = requireOperatorRow(session);\n await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.auditRemoval).bind(uuid(), operator.id, now(), id, collection.name),\n env.DB.prepare(SQL.removePublicationAny).bind(id, collection.name),\n ]),\n );\n } else {\n const publisher = requirePublisherRow(session);\n const row = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n if (row === null) return new Response(null, { headers: noStore, status: 204 });\n if (row.owner_id !== publisher.id) throw notFound();\n await run(env, SQL.removePublication, id, publisher.id);\n }\n return new Response(null, { headers: noStore, status: 204 });\n}\n\nexport async function setLike(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n config: ApiConfig,\n session: Session,\n id: string,\n liked: boolean,\n): Promise<Response> {\n const publisher = requirePublisherRow(session);\n if (liked && !config.likesEnabled) throw new ApiError(503, 'likes-disabled', 'Likes are currently disabled.');\n const row = await first<PublicationRow>(env, SQL.publication, id, collection.name);\n if (row === null) throw notFound();\n if (liked) {\n await run(env, SQL.insertLike, publisher.id, now(), id);\n } else {\n await run(env, SQL.removeLike, id, publisher.id);\n }\n // One consistent snapshot: the count and the viewer's flag read together.\n const state = await first<{ id: string; like_count: number; liked: number }>(env, SQL.likeState, publisher.id, id);\n if (state === null) throw notFound();\n const dto = { id: state.id, likeCount: state.like_count, liked: state.liked === 1 };\n const validated = archiveLikeStateSchema.safeParse(dto);\n if (!validated.success) throw recordUnavailable();\n return json(200, validated.data, noStore);\n}\n\n/* -------------------------------- moderation ------------------------------- */\n\n// One free-text report reason: trimmed, non-empty, well-formed, bounded.\nconst reportBodySchema = s.object({\n reason: s.string().check(isWellFormedText).trim().min(1).max(ARCHIVE_LIMITS.reportReasonChars),\n});\n\n/**\n * A reader's abuse report against one visible entry. Idempotent per (entry, reporter):\n * reporting again is the same 204, never a duplicate row. Hidden or missing entries\n * are 404 — they cannot be reported, they are already actioned or gone.\n */\nexport async function reportEntry(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n session: Session,\n id: string,\n): Promise<Response> {\n const reporter = requirePublisherRow(session);\n const body = await readJsonBody(request);\n const parsed = reportBodySchema.safeParse(body);\n if (!parsed.success) throw invalid();\n const row = await first<PublicationRow>(env, SQL.publication, id, collection.name);\n if (row === null) throw notFound();\n // Bound in the SQL's authored placeholder order: reporter, reason, created_at,\n // then the WHERE's entry id and collection.\n await run(env, SQL.insertReport, uuid(), reporter.id, parsed.data.reason, now(), id, collection.name);\n return new Response(null, { headers: noStore, status: 204 });\n}\n\n/**\n * The operator soft-hide: removes an entry from every public surface while keeping\n * the row, its likes and its retry key intact. Idempotent — already in the requested\n * state answers the same 204. Operator authority is the session's verified role\n * claim; a registered publisher row is still required (one accountable identity).\n */\nexport async function setHidden(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n session: Session,\n id: string,\n hidden: boolean,\n): Promise<Response> {\n const operator = requireOperatorRow(session);\n // Any collection row, visible or hidden: the operator acts on the storage row. The\n // pre-read establishes existence only. The transactional audit guard decides\n // whether the requested transition is an effective action.\n const row = await first<PublicationRow>(env, SQL.publicationAny, id, collection.name);\n if (row === null) throw notFound();\n const statement = hidden\n ? env.DB.prepare(SQL.hidePublication).bind(now(), id, collection.name)\n : env.DB.prepare(SQL.unhidePublication).bind(id, collection.name);\n await databaseCall(() =>\n env.DB.batch([\n env.DB.prepare(SQL.auditHide).bind(\n uuid(),\n operator.id,\n hidden ? 'hide' : 'unhide',\n now(),\n id,\n collection.name,\n Number(hidden),\n ),\n statement,\n ]),\n );\n return new Response(null, { headers: noStore, status: 204 });\n}\n\n/** The operator's report queue for one collection: newest first, bounded. */\nexport async function listReports(\n collection: ArchiveCollection,\n request: Request,\n env: ArchiveWorkerEnv,\n session: Session,\n): Promise<Response> {\n requireOperatorRow(session);\n const status = new URL(request.url).searchParams.get('status') ?? 'unresolved';\n if (status !== 'unresolved' && status !== 'resolved') throw badRequest();\n const context = `reports:${collection.name}:${status}`;\n const { cursor, limit } = forwardPage(request, context, ['status']);\n const bound = reportsPageSql(\n { collection: collection.name, resolved: status === 'resolved' },\n { cursor, limit: limit + 1 },\n );\n const rows = await all<ReportRow>(env, bound.sql, ...bound.parameters);\n const page = rows.slice(0, limit);\n const last = page.at(-1);\n const parsed = archiveReportsSchema.safeParse({\n cursor:\n rows.length > limit && last !== undefined\n ? nextCursor({ createdAt: last.created_at, id: last.id }, context)\n : null,\n reports: page.map((row) => ({\n createdAt: row.created_at,\n entryId: row.publication_id,\n entryTitle: row.entry_title,\n id: row.id,\n reason: row.reason,\n reporter: { id: row.reporter_id, name: row.reporter_name },\n resolvedAt: row.resolved_at,\n })),\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n}\n\ninterface ReportRow {\n collection: string;\n created_at: string;\n entry_title: string;\n id: string;\n publication_id: string;\n reason: string;\n reporter_id: string;\n reporter_name: string;\n resolved_at: string | null;\n}\n\nexport async function resolveReport(\n collection: ArchiveCollection,\n env: ArchiveWorkerEnv,\n session: Session,\n id: string,\n): Promise<Response> {\n requireOperatorRow(session);\n const row = await first<{ id: string }>(env, SQL.reportById, id, collection.name);\n if (row === null) throw notFound();\n await run(env, SQL.resolveReport, now(), id, collection.name);\n return new Response(null, { headers: noStore, status: 204 });\n}\n\nexport async function myReports(request: Request, env: ArchiveWorkerEnv, session: Session): Promise<Response> {\n const context = `own-reports:${session.publisher?.id ?? 'unregistered'}`;\n const { cursor, limit } = forwardPage(request, context);\n if (session.publisher === null) return json(200, { cursor: null, reports: [] }, noStore);\n const bound = reportsPageSql({ publisherId: session.publisher.id }, { cursor, limit: limit + 1 });\n const rows = await all<ReportRow>(env, bound.sql, ...bound.parameters);\n const page = rows.slice(0, limit);\n const last = page.at(-1);\n const parsed = archiveOwnReportsSchema.safeParse({\n cursor:\n rows.length > limit && last !== undefined\n ? nextCursor({ createdAt: last.created_at, id: last.id }, context)\n : null,\n reports: page.map((row) => ({\n collection: row.collection,\n createdAt: row.created_at,\n entryId: row.publication_id,\n id: row.id,\n reason: row.reason,\n resolvedAt: row.resolved_at,\n })),\n });\n if (!parsed.success) throw recordUnavailable();\n return json(200, parsed.data, noStore);\n}\n"],"mappings":"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"}
|
package/dist/worker/worker.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../contract.cjs"),t=require("../errors.cjs"),n=require("./auth.cjs"),r=require("./env.cjs"),i=require("./http.cjs"),a=require("./publications.cjs"),o=require("./sql.cjs");var s=[{id:`accountErase`,label:`/me/account`,methods:[`DELETE`],pattern:/^\/api\/v1\/me\/account$/},{id:`health`,label:`/health`,methods:[`GET`],pattern:/^\/api\/v1\/health$/},{id:`signOut`,label:`/sign-out`,methods:[`POST`],pattern:/^\/api\/v1\/sign-out$/},{id:`resolveReport`,label:`/collections/:collection/reports/:id/resolved`,methods:[`PUT`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/reports\/([0-9a-f-]{36})\/resolved$/i},{id:`meta`,label:`/collections/:collection`,methods:[`GET`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)$/},{id:`reports`,label:`/collections/:collection/reports`,methods:[`GET`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/reports$/},{id:`entries`,label:`/collections/:collection/entries`,methods:[`GET`,`POST`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries$/},{id:`entry`,label:`/collections/:collection/entries/:id`,methods:[`GET`,`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})$/i},{id:`like`,label:`/collections/:collection/entries/:id/like`,methods:[`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/like$/i},{id:`report`,label:`/collections/:collection/entries/:id/report`,methods:[`POST`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/report$/i},{id:`hidden`,label:`/collections/:collection/entries/:id/hidden`,methods:[`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/hidden$/i},{id:`modLog`,label:`/moderation-log`,methods:[`GET`],pattern:/^\/api\/v1\/moderation-log$/},{id:`publisherErase`,label:`/publishers/:id`,methods:[`DELETE`],pattern:/^\/api\/v1\/publishers\/([0-9a-f-]{36})$/i},{id:`me`,label:`/me`,methods:[`GET`,`POST`,`PUT`,`DELETE`],pattern:/^\/api\/v1\/me$/},{id:`mePublications`,label:`/me/publications`,methods:[`GET`],pattern:/^\/api\/v1\/me\/publications$/},{id:`meLikes`,label:`/me/likes`,methods:[`GET`],pattern:/^\/api\/v1\/me\/likes$/},{id:`meReports`,label:`/me/reports`,methods:[`GET`],pattern:/^\/api\/v1\/me\/reports$/}],matchRoute=e=>{for(let t of s){let n=e.match(t.pattern);if(n)return{id:t.id,label:t.label,methods:t.methods,segments:n.slice(1)}}return null},routeLabel=e=>matchRoute(e)?.label??`unknown`,c=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,l=new Set([`entry`,`like`,`report`,`hidden`,`resolveReport`]),notAllowed=()=>new i.ApiError(405,`method-not-allowed`,`That method is not supported here.`),u=new WeakMap,rateLimitHash=async(e,t)=>{let n=u.get(e);n===void 0&&(n=crypto.subtle.importKey(`raw`,new TextEncoder().encode(e.rateLimitPepper),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),u.set(e,n));let r=await crypto.subtle.sign(`HMAC`,await n,new TextEncoder().encode(t));return[...new Uint8Array(r)].map(e=>e.toString(16).padStart(2,`0`)).join(``)},d=60;async function readLimit(e,t,n){let r=n.headers.get(`cf-connecting-ip`)??(e.appEnv===`local`?`127.0.0.1`:null);if(r===null)throw new i.ApiError(503,`unavailable`,`The service is temporarily unavailable.`);let{success:a}=await t.READ_LIMIT.limit({key:await rateLimitHash(e,r)});if(!a)throw new i.ApiError(429,`rate-limited`,`Too many requests. Try again later.`,{retryAfterSeconds:d})}async function writeLimit(e,t,n,r){let{success:a}=await t.WRITE_LIMIT.limit({key:await rateLimitHash(e,JSON.stringify([n,r]))});if(!a)throw new i.ApiError(429,`rate-limited`,`Too many requests. Try again later.`,{retryAfterSeconds:d})}async function cachedPublicGet(t,n,r,i){if(t.headers.get(`cookie`)!==null||t.headers.get(`authorization`)!==null)return r();let a=new URL(t.url);a.search=new URLSearchParams([...a.searchParams.entries()].sort(([e],[t])=>e<t?-1:+(e>t))).toString();let o=caches.default,s=new Request(a.toString(),{method:`GET`}),c=await o.match(s).catch(()=>{i()});if(c)return c;let l=await r();return l.headers.set(`cache-control`,`public, max-age=${e.ARCHIVE_LIMITS.publicCacheSeconds}, s-maxage=${e.ARCHIVE_LIMITS.publicCacheSeconds}`),n.waitUntil(o.put(s,l.clone()).catch(i)),l}var withCors=(e,t,n)=>{let r=i.corsHeaders(t,n);if(Object.keys(r).length===0)return e;let a=new Response(e.body,e);for(let[e,t]of Object.entries(r))a.headers.set(e,t);return a};function requireExpectedPublisher(t,n,r){if(r||n.publisher===null)return;let a=t.headers.get(e.PUBLISHER_HEADER);if(a===null||!c.test(a))throw new i.ApiError(400,`bad-request`,`The request is not valid.`);if(a!==n.publisher.id)throw new i.ApiError(409,`publisher-changed`,`The signed-in publisher changed since this request was prepared.`)}function createArchiveWorker(s){let u=new Map;for(let e of s){if(u.has(e.name))throw new t.ArchiveDefinitionError(`Duplicate archive collection: ${e.name}`);u.set(e.name,e)}let collectionFor=e=>{let t=u.get(e);if(t===void 0)throw new i.ApiError(404,`not-found`,`No such route.`);return t};async function handle(e,t,o){let s=r.apiConfig(t),d=new URL(e.url),{pathname:f}=d,p=e.method;if(!f.startsWith(`/api/v1/`))throw new i.ApiError(404,`not-found`,`No such route.`);if(p===`OPTIONS`)return i.preflight(e,s);let m=matchRoute(f);if(m===null)throw new i.ApiError(404,`not-found`,`No such route.`);if(!m.methods.includes(p))throw notAllowed();if(l.has(m.id)&&!c.test(m.segments[1]??``))throw i.badRequest();if(m.id===`health`)return withCors(await a.healthJson(s,t),e,s);m.id===`reports`&&i.queryParams(d,[`cursor`,`limit`,`status`]),await readLimit(s,t,e);let h=p!==`GET`&&p!==`HEAD`&&p!==`OPTIONS`;h&&i.requireMutationGuard(e,s);let g=p===`GET`&&(m.id===`meta`||m.id===`entries`||m.id===`entry`),_=m.id===`signOut`&&p===`POST`,v=g||_?null:await n.verifySession(e,s,t,m.id===`accountErase`);if(h){if(_)return withCors(i.json(200,{ok:!0},{"cache-control":`no-store`,"set-cookie":i.expiredSessionCookie(s.appEnv===`production`)}),e,s);let r=n.requireSession(v);requireExpectedPublisher(e,r,m.id===`me`&&p===`POST`),await writeLimit(s,t,r.subject.issuer,r.subject.subject)}let jsonWithCors=t=>withCors(t,e,s);switch(m.id){case`meta`:return i.queryParams(d,[]),jsonWithCors(a.collectionMeta(collectionFor(m.segments[0]??``)));case`entries`:{let r=collectionFor(m.segments[0]??``);if(p===`GET`)return jsonWithCors(await cachedPublicGet(e,o,()=>a.listEntries(r,e,t),()=>emit({diagnostic:{kind:`cache`},type:`cache-error`})));if(p===`POST`)return jsonWithCors(await a.publishEntry(r,e,t,s,n.requireSession(v)));throw notAllowed()}case`entry`:{let r=collectionFor(m.segments[0]??``),o=m.segments[1]??``;if(p===`GET`)return i.queryParams(d,[]),jsonWithCors(await a.getEntry(r,t,o));if(p===`PUT`)return jsonWithCors(await a.updateEntry(r,e,t,s,n.requireSession(v),o));if(p===`DELETE`)return jsonWithCors(await a.deleteEntry(r,t,n.requireSession(v),o));throw notAllowed()}case`like`:{let e=collectionFor(m.segments[0]??``),r=m.segments[1]??``;if(p===`PUT`)return jsonWithCors(await a.setLike(e,t,s,n.requireSession(v),r,!0));if(p===`DELETE`)return jsonWithCors(await a.setLike(e,t,s,n.requireSession(v),r,!1));throw notAllowed()}case`report`:{if(p!==`POST`)throw notAllowed();let r=collectionFor(m.segments[0]??``);return jsonWithCors(await a.reportEntry(r,e,t,n.requireSession(v),m.segments[1]??``))}case`hidden`:{if(p!==`PUT`&&p!==`DELETE`)throw notAllowed();let e=collectionFor(m.segments[0]??``);return jsonWithCors(await a.setHidden(e,t,n.requireSession(v),m.segments[1]??``,p===`PUT`))}case`reports`:{let r=n.requireSession(v);return requireExpectedPublisher(e,r,!1),jsonWithCors(await a.listReports(collectionFor(m.segments[0]??``),e,t,r))}case`resolveReport`:return i.queryParams(d,[]),jsonWithCors(await a.resolveReport(collectionFor(m.segments[0]??``),t,n.requireSession(v),m.segments[1]??``));case`modLog`:{let r=n.requireSession(v);return requireExpectedPublisher(e,r,!1),jsonWithCors(await a.listModerationLog(e,t,r))}case`publisherErase`:if(p!==`DELETE`)throw notAllowed();return jsonWithCors(await a.erasePublisher(t,n.requireSession(v),m.segments[0]??``));case`accountErase`:return i.queryParams(d,[]),jsonWithCors(await a.deleteAccountProfile(t,n.requireSession(v)));case`me`:if(p===`GET`)return jsonWithCors(a.getMe(v));if(p===`POST`)return jsonWithCors(await a.registerMe(e,t,n.requireSession(v)));if(p===`PUT`)return jsonWithCors(await a.renameMe(e,t,n.requireSession(v)));if(p===`DELETE`)return jsonWithCors(await a.deleteProfile(t,n.requireSession(v)));throw notAllowed();case`mePublications`:case`meLikes`:case`meReports`:{let r=n.requireSession(v);if(requireExpectedPublisher(e,r,!1),m.id===`mePublications`){let e=i.queryParams(d,[`collection`]).get(`collection`);if(e===null)throw i.badRequest();let n=u.get(e);if(n===void 0)throw i.badRequest();return jsonWithCors(await a.myPublications(n,t,r))}return jsonWithCors(await(m.id===`meReports`?a.myReports(e,t,r):a.myLikes(e,t,r)))}default:throw notAllowed()}}let d=new Set,emit=e=>{if(d.size!==0)for(let t of d)try{t(e)}catch{}};return{async fetch(n,a,o){let s=crypto.randomUUID(),c=routeLabel(new URL(n.url).pathname),l=Date.now();try{let t=await handle(n,a,o),r;return t.status===503&&c===`/health`&&(r={kind:e.archiveHealthSchema.parse(await t.clone().json()).reason===`database-unavailable`?`database`:`schema`}),emit({code:t.ok?`ok`:`unavailable`,ms:Date.now()-l,requestId:s,route:c,status:t.status,type:`request`,...r===void 0?{}:{diagnostic:r}}),t}catch(e){let o=i.toErrorResponse(e,s);emit({code:e instanceof i.ApiError?e.code:`internal`,diagnostic:e instanceof i.ApiError?e.diagnostic:{kind:e instanceof t.ArchiveDerivationError?`collection`:`internal`},ms:Date.now()-l,requestId:s,route:c,status:o.status,type:`request`});try{return withCors(o,n,r.apiConfig(a))}catch{return o}}},async scheduled(e,t,n){try{let e=Date.now(),n=await t.DB.batch([t.DB.prepare(o.SQL.expireRequests).bind(e),t.DB.prepare(o.SQL.expireRevocations).bind(e)]);emit({removed:n.reduce((e,t)=>e+(t.meta?.changes??0),0),status:`ok`,type:`scheduled`})}catch(e){throw emit({diagnostic:{kind:`database`},status:`failed`,type:`scheduled`}),e}},tap(e,t){let n=t?.signal;if(n?.aborted)return()=>{};let off=()=>{d.delete(e),n?.removeEventListener(`abort`,off)};return d.add(e),n?.addEventListener(`abort`,off,{once:!0}),off}}}exports.createArchiveWorker=createArchiveWorker;
|
|
1
|
+
const e=require("../_server/http.cjs"),t=require("../_server/uuid.cjs"),n=require("../contract.cjs"),r=require("../errors.cjs"),i=require("./_cors.cjs"),a=require("./auth.cjs"),o=require("./env.cjs"),s=require("./publications.cjs"),c=require("./sql.cjs");var l=[{id:`accountErase`,label:`/me/account`,methods:[`DELETE`],pattern:/^\/api\/v1\/me\/account$/},{id:`health`,label:`/health`,methods:[`GET`],pattern:/^\/api\/v1\/health$/},{id:`signOut`,label:`/sign-out`,methods:[`POST`],pattern:/^\/api\/v1\/sign-out$/},{id:`resolveReport`,label:`/collections/:collection/reports/:id/resolved`,methods:[`PUT`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/reports\/([0-9a-f-]{36})\/resolved$/i},{id:`meta`,label:`/collections/:collection`,methods:[`GET`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)$/},{id:`reports`,label:`/collections/:collection/reports`,methods:[`GET`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/reports$/},{id:`entries`,label:`/collections/:collection/entries`,methods:[`GET`,`POST`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries$/},{id:`entry`,label:`/collections/:collection/entries/:id`,methods:[`GET`,`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})$/i},{id:`like`,label:`/collections/:collection/entries/:id/like`,methods:[`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/like$/i},{id:`report`,label:`/collections/:collection/entries/:id/report`,methods:[`POST`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/report$/i},{id:`hidden`,label:`/collections/:collection/entries/:id/hidden`,methods:[`PUT`,`DELETE`],pattern:/^\/api\/v1\/collections\/([a-z0-9-]+)\/entries\/([0-9a-f-]{36})\/hidden$/i},{id:`modLog`,label:`/moderation-log`,methods:[`GET`],pattern:/^\/api\/v1\/moderation-log$/},{id:`publisherErase`,label:`/publishers/:id`,methods:[`DELETE`],pattern:/^\/api\/v1\/publishers\/([0-9a-f-]{36})$/i},{id:`me`,label:`/me`,methods:[`GET`,`POST`,`PUT`,`DELETE`],pattern:/^\/api\/v1\/me$/},{id:`mePublications`,label:`/me/publications`,methods:[`GET`],pattern:/^\/api\/v1\/me\/publications$/},{id:`meLikes`,label:`/me/likes`,methods:[`GET`],pattern:/^\/api\/v1\/me\/likes$/},{id:`meReports`,label:`/me/reports`,methods:[`GET`],pattern:/^\/api\/v1\/me\/reports$/}],matchRoute=e=>{for(let t of l){let n=e.match(t.pattern);if(n)return{id:t.id,label:t.label,methods:t.methods,segments:n.slice(1)}}return null},routeLabel=e=>matchRoute(e)?.label??`unknown`,u=new Set([`entry`,`like`,`report`,`hidden`,`resolveReport`]),notAllowed=()=>new e.ApiError(405,`method-not-allowed`,`That method is not supported here.`),d=new WeakMap,rateLimitHash=async(e,t)=>{let n=d.get(e);n===void 0&&(n=crypto.subtle.importKey(`raw`,new TextEncoder().encode(e.rateLimitPepper),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),d.set(e,n));let r=await crypto.subtle.sign(`HMAC`,await n,new TextEncoder().encode(t));return[...new Uint8Array(r)].map(e=>e.toString(16).padStart(2,`0`)).join(``)},f=60;async function readLimit(t,n,r){let i=r.headers.get(`cf-connecting-ip`)??(t.appEnv===`local`?`127.0.0.1`:null);if(i===null)throw new e.ApiError(503,`unavailable`,`The service is temporarily unavailable.`);let{success:a}=await n.READ_LIMIT.limit({key:await rateLimitHash(t,i)});if(!a)throw new e.ApiError(429,`rate-limited`,`Too many requests. Try again later.`,{retryAfterSeconds:f})}async function writeLimit(t,n,r,i){let{success:a}=await n.WRITE_LIMIT.limit({key:await rateLimitHash(t,JSON.stringify([r,i]))});if(!a)throw new e.ApiError(429,`rate-limited`,`Too many requests. Try again later.`,{retryAfterSeconds:f})}async function cachedPublicGet(e,t,r,i){if(e.headers.get(`cookie`)!==null||e.headers.get(`authorization`)!==null)return r();let a=new URL(e.url);a.search=new URLSearchParams([...a.searchParams.entries()].sort(([e],[t])=>e<t?-1:+(e>t))).toString();let o=caches.default,s=new Request(a.toString(),{method:`GET`}),c=await o.match(s).catch(()=>{i()});if(c)return c;let l=await r();return l.headers.set(`cache-control`,`public, max-age=${n.ARCHIVE_LIMITS.publicCacheSeconds}, s-maxage=${n.ARCHIVE_LIMITS.publicCacheSeconds}`),t.waitUntil(o.put(s,l.clone()).catch(i)),l}var withCors=(e,t,n)=>{let r=i.corsHeaders(t,n);if(Object.keys(r).length===0)return e;let a=new Response(e.body,e);for(let[e,t]of Object.entries(r))a.headers.set(e,t);return a};function requireExpectedPublisher(r,i,a){if(a||i.publisher===null)return;let o=r.headers.get(n.PUBLISHER_HEADER);if(o===null||!t.UUID_RE.test(o))throw new e.ApiError(400,`bad-request`,`The request is not valid.`);if(o!==i.publisher.id)throw new e.ApiError(409,`publisher-changed`,`The signed-in publisher changed since this request was prepared.`)}function createArchiveWorker(l){let d=new Map;for(let e of l){if(d.has(e.name))throw new r.ArchiveDefinitionError(`Duplicate archive collection: ${e.name}`);d.set(e.name,e)}let collectionFor=t=>{let n=d.get(t);if(n===void 0)throw new e.ApiError(404,`not-found`,`No such route.`);return n};async function handle(n,r,c){let l=o.apiConfig(r),f=new URL(n.url),{pathname:p}=f,m=n.method;if(!p.startsWith(`/api/v1/`))throw new e.ApiError(404,`not-found`,`No such route.`);if(m===`OPTIONS`)return i.preflight(n,l);let h=matchRoute(p);if(h===null)throw new e.ApiError(404,`not-found`,`No such route.`);if(!h.methods.includes(m))throw notAllowed();if(u.has(h.id)&&!t.UUID_RE.test(h.segments[1]??``))throw e.badRequest();if(h.id===`health`)return withCors(await s.healthJson(l,r),n,l);h.id===`reports`&&e.queryParams(f,[`cursor`,`limit`,`status`]),await readLimit(l,r,n);let g=m!==`GET`&&m!==`HEAD`&&m!==`OPTIONS`;g&&i.requireMutationGuard(n,l);let _=m===`GET`&&(h.id===`meta`||h.id===`entries`||h.id===`entry`),v=h.id===`signOut`&&m===`POST`,y=_||v?null:await a.verifySession(n,l,r,h.id===`accountErase`);if(g){if(v)return withCors(e.json(200,{ok:!0},{"cache-control":`no-store`,"set-cookie":e.expiredSessionCookie(l.appEnv===`production`)}),n,l);let t=a.requireSession(y);requireExpectedPublisher(n,t,h.id===`me`&&m===`POST`),await writeLimit(l,r,t.subject.issuer,t.subject.subject)}let jsonWithCors=e=>withCors(e,n,l);switch(h.id){case`meta`:return e.queryParams(f,[]),jsonWithCors(s.collectionMeta(collectionFor(h.segments[0]??``)));case`entries`:{let e=collectionFor(h.segments[0]??``);if(m===`GET`)return jsonWithCors(await cachedPublicGet(n,c,()=>s.listEntries(e,n,r),()=>emit({diagnostic:{kind:`cache`},type:`cache-error`})));if(m===`POST`)return jsonWithCors(await s.publishEntry(e,n,r,l,a.requireSession(y)));throw notAllowed()}case`entry`:{let t=collectionFor(h.segments[0]??``),i=h.segments[1]??``;if(m===`GET`)return e.queryParams(f,[]),jsonWithCors(await s.getEntry(t,r,i));if(m===`PUT`)return jsonWithCors(await s.updateEntry(t,n,r,l,a.requireSession(y),i));if(m===`DELETE`)return jsonWithCors(await s.deleteEntry(t,r,a.requireSession(y),i));throw notAllowed()}case`like`:{let e=collectionFor(h.segments[0]??``),t=h.segments[1]??``;if(m===`PUT`)return jsonWithCors(await s.setLike(e,r,l,a.requireSession(y),t,!0));if(m===`DELETE`)return jsonWithCors(await s.setLike(e,r,l,a.requireSession(y),t,!1));throw notAllowed()}case`report`:{if(m!==`POST`)throw notAllowed();let e=collectionFor(h.segments[0]??``);return jsonWithCors(await s.reportEntry(e,n,r,a.requireSession(y),h.segments[1]??``))}case`hidden`:{if(m!==`PUT`&&m!==`DELETE`)throw notAllowed();let e=collectionFor(h.segments[0]??``);return jsonWithCors(await s.setHidden(e,r,a.requireSession(y),h.segments[1]??``,m===`PUT`))}case`reports`:{let e=a.requireSession(y);return requireExpectedPublisher(n,e,!1),jsonWithCors(await s.listReports(collectionFor(h.segments[0]??``),n,r,e))}case`resolveReport`:return e.queryParams(f,[]),jsonWithCors(await s.resolveReport(collectionFor(h.segments[0]??``),r,a.requireSession(y),h.segments[1]??``));case`modLog`:{let e=a.requireSession(y);return requireExpectedPublisher(n,e,!1),jsonWithCors(await s.listModerationLog(n,r,e))}case`publisherErase`:if(m!==`DELETE`)throw notAllowed();return jsonWithCors(await s.erasePublisher(r,a.requireSession(y),h.segments[0]??``));case`accountErase`:return e.queryParams(f,[]),jsonWithCors(await s.deleteAccountProfile(r,a.requireSession(y)));case`me`:if(m===`GET`)return jsonWithCors(s.getMe(y));if(m===`POST`)return jsonWithCors(await s.registerMe(n,r,a.requireSession(y)));if(m===`PUT`)return jsonWithCors(await s.renameMe(n,r,a.requireSession(y)));if(m===`DELETE`)return jsonWithCors(await s.deleteProfile(r,a.requireSession(y)));throw notAllowed();case`mePublications`:case`meLikes`:case`meReports`:{let t=a.requireSession(y);if(requireExpectedPublisher(n,t,!1),h.id===`mePublications`){let n=e.queryParams(f,[`collection`]).get(`collection`);if(n===null)throw e.badRequest();let i=d.get(n);if(i===void 0)throw e.badRequest();return jsonWithCors(await s.myPublications(i,r,t))}return jsonWithCors(await(h.id===`meReports`?s.myReports(n,r,t):s.myLikes(n,r,t)))}default:throw notAllowed()}}let f=new Set,emit=e=>{if(f.size!==0)for(let t of f)try{t(e)}catch{}};return{async fetch(t,i,a){let s=crypto.randomUUID(),c=routeLabel(new URL(t.url).pathname),l=Date.now();try{let e=await handle(t,i,a),r;return e.status===503&&c===`/health`&&(r={kind:n.archiveHealthSchema.parse(await e.clone().json()).reason===`database-unavailable`?`database`:`schema`}),emit({code:e.ok?`ok`:`unavailable`,ms:Date.now()-l,requestId:s,route:c,status:e.status,type:`request`,...r===void 0?{}:{diagnostic:r}}),e}catch(n){let a=e.toErrorResponse(n,s);emit({code:n instanceof e.ApiError?n.code:`internal`,diagnostic:n instanceof e.ApiError?n.diagnostic:{kind:n instanceof r.ArchiveDerivationError?`collection`:`internal`},ms:Date.now()-l,requestId:s,route:c,status:a.status,type:`request`});try{return withCors(a,t,o.apiConfig(i))}catch{return a}}},async scheduled(e,t,n){try{let e=Date.now(),n=await t.DB.batch([t.DB.prepare(c.SQL.expireRequests).bind(e),t.DB.prepare(c.SQL.expireRevocations).bind(e)]);emit({removed:n.reduce((e,t)=>e+(t.meta?.changes??0),0),status:`ok`,type:`scheduled`})}catch(e){throw emit({diagnostic:{kind:`database`},status:`failed`,type:`scheduled`}),e}},tap(e,t){let n=t?.signal;if(n?.aborted)return()=>{};let off=()=>{f.delete(e),n?.removeEventListener(`abort`,off)};return f.add(e),n?.addEventListener(`abort`,off,{once:!0}),off}}}exports.createArchiveWorker=createArchiveWorker;
|
|
2
2
|
//# sourceMappingURL=worker.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.cjs","names":["ApiError","ARCHIVE_LIMITS","corsHeaders","PUBLISHER_HEADER","ArchiveDefinitionError","apiConfig","preflight","badRequest","healthJson","verifySession","json","expiredSessionCookie","requireSession","collectionMeta","listEntries","publishEntry","getEntry","updateEntry","deleteEntry","setLike","reportEntry","setHidden","listReports","resolveReport","listModerationLog","erasePublisher","deleteAccountProfile","getMe","registerMe","renameMe","deleteProfile","queryParams","myPublications","myReports","myLikes","archiveHealthSchema","toErrorResponse","ArchiveDerivationError","SQL","require_contract","require_errors","require_auth","require_env","require_http","require_publications","require_sql","ROUTES","route","found","pathname","UUID_PATH","ID_SEGMENT_ROUTES","hmacKeys","key","config","mac","text","b","RATE_LIMIT_RETRY_AFTER_SECONDS","ip","request","env","success","issuer","subject","produce","url","a","edgeCache","cached","onFailure","response","ctx","headers","next","value","exempt","session","expected","registered","collection","collections","segment","method","match","isMutation","isPublicGet","isSignOut","verified","id","requested","tappers","tapper","event","requestId","startedAt","diagnostic","error","timestamp","results","sum","result","signal","options","handler"],"sources":["../../src/worker/worker.ts"],"sourcesContent":["import type { ArchiveCollection } from '../collection';\nimport { ARCHIVE_LIMITS, type ArchiveErrorCode, archiveHealthSchema, PUBLISHER_HEADER } from '../contract';\nimport { ArchiveDefinitionError, ArchiveDerivationError } from '../errors';\nimport { requireSession, type Session, verifySession } from './auth';\nimport { type ApiConfig, type ArchiveWorkerEnv, apiConfig } from './env';\nimport {\n ApiError,\n type ArchiveDiagnostic,\n badRequest,\n corsHeaders,\n expiredSessionCookie,\n json,\n preflight,\n queryParams,\n requireMutationGuard,\n toErrorResponse,\n} from './http';\nimport {\n collectionMeta,\n deleteAccountProfile,\n deleteEntry,\n deleteProfile,\n erasePublisher,\n getEntry,\n getMe,\n healthJson,\n listEntries,\n listModerationLog,\n listReports,\n myLikes,\n myPublications,\n myReports,\n publishEntry,\n registerMe,\n renameMe,\n reportEntry,\n resolveReport,\n setHidden,\n setLike,\n updateEntry,\n} from './publications';\nimport { SQL } from './sql';\n\n/**\n * The community-archive Worker factory: anonymous public reads, cookie-authenticated\n * mutations behind strict Origin/request-marker guards, IP and subject rate limits,\n * operator moderation over the registered collections, and a daily expiry sweep for\n * retired publish retry keys.\n *\n * Trust model: configuration and signing keys come only from validated Env bindings;\n * the session cookie is the single credential; every response is either no-store\n * (private/error) or a 30s anonymous public cache entry. Runtime observability\n * flows exclusively through `tap()` — the core itself never logs.\n */\n\n/** The minimal execution-context surface the core uses; the runtime's satisfies it. */\nexport interface ArchiveExecutionContext {\n waitUntil(promise: Promise<unknown>): void;\n}\n\n/** One sanitized request-completion event: ids, codes, route classes and duration only. */\nexport interface ArchiveRequestEvent {\n /** 'ok' on success; otherwise the route's `ARCHIVE_ERROR_CODES` value. */\n code: ArchiveErrorCode | 'ok';\n diagnostic?: ArchiveDiagnostic;\n /** Whole-request wall-clock duration in milliseconds. */\n ms: number;\n requestId: string;\n /** A finite route class like `/collections/:collection/entries`, never the raw path. */\n route: string;\n status: number;\n type: 'request';\n}\n\n/** The daily retry-key sweep, observable like every other runtime behavior. */\nexport type ArchiveScheduledEvent =\n | {\n /** Retired retry-key rows this run removed. */\n removed: number;\n type: 'scheduled';\n status: 'ok';\n }\n | { type: 'scheduled'; status: 'failed'; diagnostic: ArchiveDiagnostic };\n\nexport type ArchiveEvent =\n | ArchiveRequestEvent\n | ArchiveScheduledEvent\n | { readonly type: 'cache-error'; readonly diagnostic: ArchiveDiagnostic };\n\n/**\n * The Worker's exported-handler shape plus its observability side channel. Declared\n * structurally so the package needs no `@cloudflare/workers-types`: a deployment's\n * generated `ExportedHandler<Env>` accepts the factory's result as-is.\n */\nexport interface ArchiveWorkerHandler<E extends ArchiveWorkerEnv = ArchiveWorkerEnv> {\n fetch(request: Request, env: E, ctx: ArchiveExecutionContext): Promise<Response>;\n scheduled?(controller: unknown, env: E, ctx: ArchiveExecutionContext): void | Promise<void>;\n /**\n * Observes every request's outcome and every scheduled sweep. The handler owns\n * logging policy (console, a structured logger, or nothing); handler errors are\n * swallowed so observability can never affect the Worker, and with no handlers\n * registered there is zero work.\n */\n tap(handler: (event: ArchiveEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;\n}\n\n/** One dispatchable route: the id both the switch and the event labels derive from. */\ntype RouteId =\n | 'health'\n | 'signOut'\n | 'meta'\n | 'reports'\n | 'resolveReport'\n | 'entries'\n | 'entry'\n | 'like'\n | 'report'\n | 'hidden'\n | 'me'\n | 'accountErase'\n | 'mePublications'\n | 'meLikes'\n | 'meReports'\n | 'modLog'\n | 'publisherErase';\n\n/** One matched route: its id (for dispatch) and label (for events), plus captures. */\ninterface RouteMatch {\n id: RouteId;\n /** The finite event label, never the raw path. */\n label: string;\n /** The methods the route serves; anything else is 405 before any guard runs. */\n methods: readonly string[];\n /** The path's captured segments, in pattern order: collection first, then id. */\n segments: readonly string[];\n}\n\n/**\n * The one route table: dispatch, methods, and event labels all read from here, so a\n * new route can never miss its observability class or its method set. Methods are\n * enforced before any guard runs — a wrong-method request never spends rate-limit\n * budget, verifies a session, or answers an auth-shaped error.\n */\nconst ROUTES: readonly { id: RouteId; label: string; methods: readonly string[]; pattern: RegExp }[] = [\n { id: 'accountErase', label: '/me/account', methods: ['DELETE'], pattern: /^\\/api\\/v1\\/me\\/account$/ },\n { id: 'health', label: '/health', methods: ['GET'], pattern: /^\\/api\\/v1\\/health$/ },\n { id: 'signOut', label: '/sign-out', methods: ['POST'], pattern: /^\\/api\\/v1\\/sign-out$/ },\n {\n id: 'resolveReport',\n label: '/collections/:collection/reports/:id/resolved',\n methods: ['PUT'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/reports\\/([0-9a-f-]{36})\\/resolved$/i,\n },\n {\n id: 'meta',\n label: '/collections/:collection',\n methods: ['GET'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)$/,\n },\n {\n id: 'reports',\n label: '/collections/:collection/reports',\n methods: ['GET'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/reports$/,\n },\n {\n id: 'entries',\n label: '/collections/:collection/entries',\n methods: ['GET', 'POST'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries$/,\n },\n {\n id: 'entry',\n label: '/collections/:collection/entries/:id',\n methods: ['GET', 'PUT', 'DELETE'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})$/i,\n },\n {\n id: 'like',\n label: '/collections/:collection/entries/:id/like',\n methods: ['PUT', 'DELETE'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})\\/like$/i,\n },\n {\n id: 'report',\n label: '/collections/:collection/entries/:id/report',\n methods: ['POST'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})\\/report$/i,\n },\n {\n id: 'hidden',\n label: '/collections/:collection/entries/:id/hidden',\n methods: ['PUT', 'DELETE'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})\\/hidden$/i,\n },\n {\n id: 'modLog',\n label: '/moderation-log',\n methods: ['GET'],\n pattern: /^\\/api\\/v1\\/moderation-log$/,\n },\n {\n id: 'publisherErase',\n label: '/publishers/:id',\n methods: ['DELETE'],\n pattern: /^\\/api\\/v1\\/publishers\\/([0-9a-f-]{36})$/i,\n },\n { id: 'me', label: '/me', methods: ['GET', 'POST', 'PUT', 'DELETE'], pattern: /^\\/api\\/v1\\/me$/ },\n { id: 'mePublications', label: '/me/publications', methods: ['GET'], pattern: /^\\/api\\/v1\\/me\\/publications$/ },\n { id: 'meLikes', label: '/me/likes', methods: ['GET'], pattern: /^\\/api\\/v1\\/me\\/likes$/ },\n { id: 'meReports', label: '/me/reports', methods: ['GET'], pattern: /^\\/api\\/v1\\/me\\/reports$/ },\n];\n\nconst matchRoute = (pathname: string): RouteMatch | null => {\n for (const route of ROUTES) {\n const found = pathname.match(route.pattern);\n if (found) return { id: route.id, label: route.label, methods: route.methods, segments: found.slice(1) };\n }\n return null;\n};\n\n/** The matched route's event label; unmatched paths stay 'unknown', never the raw path. */\nconst routeLabel = (pathname: string): string => matchRoute(pathname)?.label ?? 'unknown';\n\nconst UUID_PATH = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** The routes whose second path segment is an entry id: they share the one id rule. */\nconst ID_SEGMENT_ROUTES: ReadonlySet<RouteId> = new Set(['entry', 'like', 'report', 'hidden', 'resolveReport']);\n\nconst notAllowed = (): ApiError => new ApiError(405, 'method-not-allowed', 'That method is not supported here.');\n\nconst hmacKeys = new WeakMap<ApiConfig, Promise<CryptoKey>>();\n/** Peppered HMAC: rate-limit keys cannot be reversed to IPs without the deployment secret. */\nconst rateLimitHash = async (config: ApiConfig, text: string): Promise<string> => {\n let key = hmacKeys.get(config);\n if (key === undefined) {\n key = crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(config.rateLimitPepper),\n { hash: 'SHA-256', name: 'HMAC' },\n false,\n ['sign'],\n );\n hmacKeys.set(config, key);\n }\n const mac = await crypto.subtle.sign('HMAC', await key, new TextEncoder().encode(text));\n return [...new Uint8Array(mac)].map((b) => b.toString(16).padStart(2, '0')).join('');\n};\n\n/**\n * The Retry-After the 429s carry. The platform limiter does not expose its period at\n * runtime, so deployments must configure 60-second windows — a longer window would\n * make the header lie.\n */\nconst RATE_LIMIT_RETRY_AFTER_SECONDS = 60;\n\n/** The connecting IP, peppered-HMAC'd, keys the read limit; loopback only when APP_ENV=local. */\nasync function readLimit(config: ApiConfig, env: ArchiveWorkerEnv, request: Request): Promise<void> {\n const ip = request.headers.get('cf-connecting-ip') ?? (config.appEnv === 'local' ? '127.0.0.1' : null);\n if (ip === null) throw new ApiError(503, 'unavailable', 'The service is temporarily unavailable.');\n const { success } = await env.READ_LIMIT.limit({ key: await rateLimitHash(config, ip) });\n if (!success) {\n throw new ApiError(429, 'rate-limited', 'Too many requests. Try again later.', {\n retryAfterSeconds: RATE_LIMIT_RETRY_AFTER_SECONDS,\n });\n }\n}\n\n/** The verified issuer+subject, peppered-HMAC'd, keys the write limit ahead of every mutation. */\nasync function writeLimit(config: ApiConfig, env: ArchiveWorkerEnv, issuer: string, subject: string): Promise<void> {\n const { success } = await env.WRITE_LIMIT.limit({\n key: await rateLimitHash(config, JSON.stringify([issuer, subject])),\n });\n if (!success) {\n throw new ApiError(429, 'rate-limited', 'Too many requests. Try again later.', {\n retryAfterSeconds: RATE_LIMIT_RETRY_AFTER_SECONDS,\n });\n }\n}\n\n/** Only anonymous list reads may serve from the edge cache. */\nasync function cachedPublicGet(\n request: Request,\n ctx: ArchiveExecutionContext,\n produce: () => Promise<Response>,\n onFailure: () => void,\n): Promise<Response> {\n const anonymous = request.headers.get('cookie') === null && request.headers.get('authorization') === null;\n if (!anonymous) return produce();\n const url = new URL(request.url);\n url.search = new URLSearchParams(\n [...url.searchParams.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n ).toString();\n const edgeCache = caches.default;\n const key = new Request(url.toString(), { method: 'GET' });\n const cached = await edgeCache.match(key).catch(() => {\n onFailure();\n return undefined;\n });\n if (cached) return cached;\n const response = await produce();\n response.headers.set(\n 'cache-control',\n `public, max-age=${ARCHIVE_LIMITS.publicCacheSeconds}, s-maxage=${ARCHIVE_LIMITS.publicCacheSeconds}`,\n );\n ctx.waitUntil(edgeCache.put(key, response.clone()).catch(onFailure));\n return response;\n}\n\nconst withCors = (response: Response, request: Request, config: ApiConfig): Response => {\n const headers = corsHeaders(request, config);\n if (Object.keys(headers).length === 0) return response;\n const next = new Response(response.body, response);\n for (const [key, value] of Object.entries(headers)) next.headers.set(key, value);\n return next;\n};\n\n/**\n * Fail-closed precondition for registered publishers: the UI pins the opaque\n * publisher UUID it believes the session belongs to, so a queued write from one\n * tab can never land under another account that took over the shared cookie.\n * The header is never an identity source — every check still resolves the\n * publisher from the verified JWT — it only vetoes mismatched writes.\n */\nfunction requireExpectedPublisher(request: Request, session: Session, exempt: boolean): void {\n if (exempt || session.publisher === null) return;\n const expected = request.headers.get(PUBLISHER_HEADER);\n if (expected === null || !UUID_PATH.test(expected)) {\n throw new ApiError(400, 'bad-request', 'The request is not valid.');\n }\n if (expected !== session.publisher.id) {\n throw new ApiError(409, 'publisher-changed', 'The signed-in publisher changed since this request was prepared.');\n }\n}\n\n/**\n * Builds the Worker for one deployment's registered collections. Each collection owns\n * one URL + storage dimension; a duplicate registration is a deployment bug and\n * fails fast.\n */\nexport function createArchiveWorker<E extends ArchiveWorkerEnv>(\n collections: readonly ArchiveCollection[],\n): ArchiveWorkerHandler<E> {\n const registered = new Map<string, ArchiveCollection>();\n for (const collection of collections) {\n if (registered.has(collection.name))\n throw new ArchiveDefinitionError(`Duplicate archive collection: ${collection.name}`);\n registered.set(collection.name, collection);\n }\n\n /** Resolves the route's collection segment; an unregistered collection is a 404, not a 500. */\n const collectionFor = (segment: string): ArchiveCollection => {\n const collection = registered.get(segment);\n if (collection === undefined) throw new ApiError(404, 'not-found', 'No such route.');\n return collection;\n };\n\n async function handle(request: Request, env: E, ctx: ArchiveExecutionContext): Promise<Response> {\n const config = apiConfig(env);\n const url = new URL(request.url);\n const { pathname } = url;\n const method = request.method;\n\n if (!pathname.startsWith('/api/v1/')) throw new ApiError(404, 'not-found', 'No such route.');\n if (method === 'OPTIONS') return preflight(request, config);\n\n const match = matchRoute(pathname);\n // An unknown path inside the mount answers 404 like the paths outside it, and a\n // known route under an unsupported method answers 405 — both with no side\n // effects: no rate-limit spend, no session verification, no auth state leaked,\n // no guard-shaped error for a method the route does not serve.\n if (match === null) throw new ApiError(404, 'not-found', 'No such route.');\n if (!match.methods.includes(method)) throw notAllowed();\n\n // A malformed entry id is the same class of client bug: it answers 400 before\n // any rate-limit spend, session verification or auth state.\n if (ID_SEGMENT_ROUTES.has(match.id) && !UUID_PATH.test(match.segments[1] ?? '')) throw badRequest();\n\n // Health sits deliberately outside the rate-limit and session pipelines: uptime\n // probes must never burn the anonymous read budget or couple to authentication,\n // and it touches the database exactly once (the schema-version read).\n if (match.id === 'health') {\n return withCors(await healthJson(config, env), request, config);\n }\n if (match.id === 'reports') queryParams(url, ['cursor', 'limit', 'status']);\n\n await readLimit(config, env, request);\n\n // The same-origin mutation contract is proven BEFORE any identity work: a\n // foreign-origin write or missing marker must fail with 403 even while carrying a\n // garbage token — JWKS/JWT cost and 401 semantics stay behind the origin fence.\n const isMutation = method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS';\n if (isMutation) requireMutationGuard(request, config);\n\n // Session verification runs only where identity matters: private routes, the\n // moderation surfaces and every mutation. Public reads (list, detail, meta) never\n // invoke JWKS or the publisher lookup — browsing must not couple to authentication\n // availability. Sign-out is a cookie-clearing reply, not a session operation.\n const isPublicGet = method === 'GET' && (match.id === 'meta' || match.id === 'entries' || match.id === 'entry');\n const isSignOut = match.id === 'signOut' && method === 'POST';\n const session =\n isPublicGet || isSignOut ? null : await verifySession(request, config, env, match.id === 'accountErase');\n\n if (isMutation) {\n if (isSignOut) {\n // The shared `archiveOkSchema` body; every session cookie attribute is the\n // contract's, never ad hoc.\n const response = json(\n 200,\n { ok: true },\n {\n 'cache-control': 'no-store',\n 'set-cookie': expiredSessionCookie(config.appEnv === 'production'),\n },\n );\n return withCors(response, request, config);\n }\n const verified = requireSession(session);\n // The free fail-closed header precondition runs before the quota token is spent:\n // a stale tab's rejected writes must not consume the current account's budget.\n requireExpectedPublisher(request, verified, match.id === 'me' && method === 'POST');\n await writeLimit(config, env, verified.subject.issuer, verified.subject.subject);\n }\n\n const jsonWithCors = (response: Response) => withCors(response, request, config);\n\n switch (match.id) {\n case 'meta': {\n queryParams(url, []);\n return jsonWithCors(collectionMeta(collectionFor(match.segments[0] ?? '')));\n }\n case 'entries': {\n const collection = collectionFor(match.segments[0] ?? '');\n if (method === 'GET') {\n const response = await cachedPublicGet(\n request,\n ctx,\n () => listEntries(collection, request, env),\n () => emit({ diagnostic: { kind: 'cache' }, type: 'cache-error' }),\n );\n return jsonWithCors(response);\n }\n if (method === 'POST') {\n return jsonWithCors(await publishEntry(collection, request, env, config, requireSession(session)));\n }\n throw notAllowed();\n }\n case 'entry': {\n const collection = collectionFor(match.segments[0] ?? '');\n const id = match.segments[1] ?? '';\n if (method === 'GET') {\n queryParams(url, []);\n return jsonWithCors(await getEntry(collection, env, id));\n }\n if (method === 'PUT') {\n return jsonWithCors(await updateEntry(collection, request, env, config, requireSession(session), id));\n }\n if (method === 'DELETE') {\n return jsonWithCors(await deleteEntry(collection, env, requireSession(session), id));\n }\n throw notAllowed();\n }\n case 'like': {\n const collection = collectionFor(match.segments[0] ?? '');\n const id = match.segments[1] ?? '';\n if (method === 'PUT') {\n return jsonWithCors(await setLike(collection, env, config, requireSession(session), id, true));\n }\n if (method === 'DELETE') {\n return jsonWithCors(await setLike(collection, env, config, requireSession(session), id, false));\n }\n throw notAllowed();\n }\n case 'report': {\n if (method !== 'POST') throw notAllowed();\n const collection = collectionFor(match.segments[0] ?? '');\n return jsonWithCors(\n await reportEntry(collection, request, env, requireSession(session), match.segments[1] ?? ''),\n );\n }\n case 'hidden': {\n if (method !== 'PUT' && method !== 'DELETE') throw notAllowed();\n const collection = collectionFor(match.segments[0] ?? '');\n return jsonWithCors(\n await setHidden(collection, env, requireSession(session), match.segments[1] ?? '', method === 'PUT'),\n );\n }\n case 'reports': {\n // Private reads carry the same fail-closed precondition as writes: a stale tab\n // must not read another publisher's moderation queue after an account switch.\n const verified = requireSession(session);\n requireExpectedPublisher(request, verified, false);\n return jsonWithCors(await listReports(collectionFor(match.segments[0] ?? ''), request, env, verified));\n }\n case 'resolveReport': {\n queryParams(url, []);\n return jsonWithCors(\n await resolveReport(\n collectionFor(match.segments[0] ?? ''),\n env,\n requireSession(session),\n match.segments[1] ?? '',\n ),\n );\n }\n case 'modLog': {\n // The accountability read carries the same fail-closed precondition as every\n // private surface: a stale tab must not read the moderation history after an\n // account switch.\n const verified = requireSession(session);\n requireExpectedPublisher(request, verified, false);\n return jsonWithCors(await listModerationLog(request, env, verified));\n }\n case 'publisherErase': {\n // The terminal moderation action: erasing a publisher removes their row, and\n // FK cascades take the publications, likes and retry keys with it — the same\n // cascade a self-erase walks, applied to any publisher the operator names.\n if (method !== 'DELETE') throw notAllowed();\n return jsonWithCors(await erasePublisher(env, requireSession(session), match.segments[0] ?? ''));\n }\n case 'accountErase': {\n queryParams(url, []);\n return jsonWithCors(await deleteAccountProfile(env, requireSession(session)));\n }\n case 'me': {\n if (method === 'GET') return jsonWithCors(getMe(session));\n if (method === 'POST') return jsonWithCors(await registerMe(request, env, requireSession(session)));\n if (method === 'PUT') return jsonWithCors(await renameMe(request, env, requireSession(session)));\n if (method === 'DELETE') return jsonWithCors(await deleteProfile(env, requireSession(session)));\n throw notAllowed();\n }\n case 'mePublications':\n case 'meLikes':\n case 'meReports': {\n // Private reads carry the same fail-closed precondition as writes: a stale tab\n // must not consume another publisher's data after a shared-cookie account switch.\n const verified = requireSession(session);\n requireExpectedPublisher(request, verified, false);\n if (match.id === 'mePublications') {\n // The owner's entries are per-collection: the collection param names the\n // registered collection, and an absent or unregistered value is a malformed\n // request, not an empty list.\n const params = queryParams(url, ['collection']);\n const requested = params.get('collection');\n if (requested === null) throw badRequest();\n const collection = registered.get(requested);\n if (collection === undefined) throw badRequest();\n return jsonWithCors(await myPublications(collection, env, verified));\n }\n return jsonWithCors(\n await (match.id === 'meReports' ? myReports(request, env, verified) : myLikes(request, env, verified)),\n );\n }\n default:\n throw notAllowed();\n }\n }\n\n const tappers = new Set<(event: ArchiveEvent) => void>();\n const emit = (event: ArchiveEvent): void => {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must never affect the Worker.\n }\n }\n };\n\n return {\n async fetch(request: Request, env: E, ctx: ArchiveExecutionContext): Promise<Response> {\n const requestId = crypto.randomUUID();\n const route = routeLabel(new URL(request.url).pathname);\n const startedAt = Date.now();\n try {\n const response = await handle(request, env, ctx);\n let diagnostic: ArchiveDiagnostic | undefined;\n if (response.status === 503 && route === '/health') {\n const health = archiveHealthSchema.parse(await response.clone().json());\n diagnostic = { kind: health.reason === 'database-unavailable' ? 'database' : 'schema' };\n }\n emit({\n code: response.ok ? 'ok' : 'unavailable',\n ms: Date.now() - startedAt,\n requestId,\n route,\n status: response.status,\n type: 'request',\n ...(diagnostic === undefined ? {} : { diagnostic }),\n });\n return response;\n } catch (error) {\n const response = toErrorResponse(error, requestId);\n emit({\n code: error instanceof ApiError ? error.code : 'internal',\n diagnostic:\n error instanceof ApiError\n ? error.diagnostic\n : { kind: error instanceof ArchiveDerivationError ? 'collection' : 'internal' },\n ms: Date.now() - startedAt,\n requestId,\n route,\n status: response.status,\n type: 'request',\n });\n // Allowed origins see the same CORS contract on errors as on successes.\n try {\n return withCors(response, request, apiConfig(env));\n } catch {\n return response; // the config error itself is the response\n }\n }\n },\n\n async scheduled(_controller: unknown, env: E, _ctx: ArchiveExecutionContext): Promise<void> {\n try {\n const timestamp = Date.now();\n const results = await env.DB.batch([\n env.DB.prepare(SQL.expireRequests).bind(timestamp),\n env.DB.prepare(SQL.expireRevocations).bind(timestamp),\n ]);\n emit({\n removed: results.reduce((sum, result) => sum + (result.meta?.changes ?? 0), 0),\n status: 'ok',\n type: 'scheduled',\n });\n } catch (error) {\n emit({ diagnostic: { kind: 'database' }, status: 'failed', type: 'scheduled' });\n throw error;\n }\n },\n\n tap(handler, options) {\n const signal = options?.signal;\n if (signal?.aborted) return () => {};\n const off = () => {\n tappers.delete(handler);\n signal?.removeEventListener('abort', off);\n };\n tappers.add(handler);\n signal?.addEventListener('abort', off, { once: true });\n return off;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worker.cjs","names":["ApiError","ARCHIVE_LIMITS","corsHeaders","PUBLISHER_HEADER","UUID_RE","ArchiveDefinitionError","apiConfig","preflight","badRequest","healthJson","verifySession","json","expiredSessionCookie","requireSession","collectionMeta","listEntries","publishEntry","getEntry","updateEntry","deleteEntry","setLike","reportEntry","setHidden","listReports","resolveReport","listModerationLog","erasePublisher","deleteAccountProfile","getMe","registerMe","renameMe","deleteProfile","queryParams","myPublications","myReports","myLikes","archiveHealthSchema","toErrorResponse","ArchiveDerivationError","SQL","require_http","require_uuid","require_contract","require_errors","require__cors","require_auth","require_env","require_publications","require_sql","ROUTES","route","found","pathname","ID_SEGMENT_ROUTES","hmacKeys","key","config","mac","text","b","RATE_LIMIT_RETRY_AFTER_SECONDS","ip","request","env","success","issuer","subject","produce","url","a","edgeCache","cached","onFailure","response","ctx","headers","next","value","exempt","session","expected","registered","collection","collections","segment","method","match","isMutation","isPublicGet","isSignOut","verified","id","requested","tappers","tapper","event","requestId","startedAt","diagnostic","error","timestamp","results","sum","result","signal","options","handler"],"sources":["../../src/worker/worker.ts"],"sourcesContent":["import type { ArchiveExecutionContext } from '../_server/execution';\nimport {\n ApiError,\n type ArchiveDiagnostic,\n badRequest,\n expiredSessionCookie,\n json,\n queryParams,\n toErrorResponse,\n} from '../_server/http';\nimport { UUID_RE } from '../_server/uuid';\nimport type { ArchiveCollection } from '../collection';\nimport { ARCHIVE_LIMITS, type ArchiveErrorCode, archiveHealthSchema, PUBLISHER_HEADER } from '../contract';\nimport { ArchiveDefinitionError, ArchiveDerivationError } from '../errors';\nimport { corsHeaders, preflight, requireMutationGuard } from './_cors';\nimport { requireSession, type Session, verifySession } from './auth';\nimport { type ApiConfig, type ArchiveWorkerEnv, apiConfig } from './env';\nimport {\n collectionMeta,\n deleteAccountProfile,\n deleteEntry,\n deleteProfile,\n erasePublisher,\n getEntry,\n getMe,\n healthJson,\n listEntries,\n listModerationLog,\n listReports,\n myLikes,\n myPublications,\n myReports,\n publishEntry,\n registerMe,\n renameMe,\n reportEntry,\n resolveReport,\n setHidden,\n setLike,\n updateEntry,\n} from './publications';\nimport { SQL } from './sql';\n\n/**\n * The community-archive Worker factory: anonymous public reads, cookie-authenticated\n * mutations behind strict Origin/request-marker guards, IP and subject rate limits,\n * operator moderation over the registered collections, and a daily expiry sweep for\n * retired publish retry keys.\n *\n * Trust model: configuration and signing keys come only from validated Env bindings;\n * the session cookie is the single credential; every response is either no-store\n * (private/error) or a 30s anonymous public cache entry. Runtime observability\n * flows exclusively through `tap()` — the core itself never logs.\n */\n\n/** The minimal execution-context surface the core uses; the runtime's satisfies it. */\n/** One sanitized request-completion event: ids, codes, route classes and duration only. */\nexport interface ArchiveRequestEvent {\n /** 'ok' on success; otherwise the route's `ARCHIVE_ERROR_CODES` value. */\n code: ArchiveErrorCode | 'ok';\n diagnostic?: ArchiveDiagnostic;\n /** Whole-request wall-clock duration in milliseconds. */\n ms: number;\n requestId: string;\n /** A finite route class like `/collections/:collection/entries`, never the raw path. */\n route: string;\n status: number;\n type: 'request';\n}\n\n/** The daily retry-key sweep, observable like every other runtime behavior. */\nexport type ArchiveScheduledEvent =\n | {\n /** Retired retry-key rows this run removed. */\n removed: number;\n type: 'scheduled';\n status: 'ok';\n }\n | { type: 'scheduled'; status: 'failed'; diagnostic: ArchiveDiagnostic };\n\nexport type ArchiveEvent =\n | ArchiveRequestEvent\n | ArchiveScheduledEvent\n | { readonly type: 'cache-error'; readonly diagnostic: ArchiveDiagnostic };\n\n/**\n * The Worker's exported-handler shape plus its observability side channel. Declared\n * structurally so the package needs no `@cloudflare/workers-types`: a deployment's\n * generated `ExportedHandler<Env>` accepts the factory's result as-is.\n */\nexport interface ArchiveWorkerHandler<E extends ArchiveWorkerEnv = ArchiveWorkerEnv> {\n fetch(request: Request, env: E, ctx: ArchiveExecutionContext): Promise<Response>;\n scheduled?(controller: unknown, env: E, ctx: ArchiveExecutionContext): void | Promise<void>;\n /**\n * Observes every request's outcome and every scheduled sweep. The handler owns\n * logging policy (console, a structured logger, or nothing); handler errors are\n * swallowed so observability can never affect the Worker, and with no handlers\n * registered there is zero work.\n */\n tap(handler: (event: ArchiveEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;\n}\n\n/** One dispatchable route: the id both the switch and the event labels derive from. */\ntype RouteId =\n | 'health'\n | 'signOut'\n | 'meta'\n | 'reports'\n | 'resolveReport'\n | 'entries'\n | 'entry'\n | 'like'\n | 'report'\n | 'hidden'\n | 'me'\n | 'accountErase'\n | 'mePublications'\n | 'meLikes'\n | 'meReports'\n | 'modLog'\n | 'publisherErase';\n\n/** One matched route: its id (for dispatch) and label (for events), plus captures. */\ninterface RouteMatch {\n id: RouteId;\n /** The finite event label, never the raw path. */\n label: string;\n /** The methods the route serves; anything else is 405 before any guard runs. */\n methods: readonly string[];\n /** The path's captured segments, in pattern order: collection first, then id. */\n segments: readonly string[];\n}\n\n/**\n * The one route table: dispatch, methods, and event labels all read from here, so a\n * new route can never miss its observability class or its method set. Methods are\n * enforced before any guard runs — a wrong-method request never spends rate-limit\n * budget, verifies a session, or answers an auth-shaped error.\n */\nconst ROUTES: readonly { id: RouteId; label: string; methods: readonly string[]; pattern: RegExp }[] = [\n { id: 'accountErase', label: '/me/account', methods: ['DELETE'], pattern: /^\\/api\\/v1\\/me\\/account$/ },\n { id: 'health', label: '/health', methods: ['GET'], pattern: /^\\/api\\/v1\\/health$/ },\n { id: 'signOut', label: '/sign-out', methods: ['POST'], pattern: /^\\/api\\/v1\\/sign-out$/ },\n {\n id: 'resolveReport',\n label: '/collections/:collection/reports/:id/resolved',\n methods: ['PUT'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/reports\\/([0-9a-f-]{36})\\/resolved$/i,\n },\n {\n id: 'meta',\n label: '/collections/:collection',\n methods: ['GET'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)$/,\n },\n {\n id: 'reports',\n label: '/collections/:collection/reports',\n methods: ['GET'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/reports$/,\n },\n {\n id: 'entries',\n label: '/collections/:collection/entries',\n methods: ['GET', 'POST'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries$/,\n },\n {\n id: 'entry',\n label: '/collections/:collection/entries/:id',\n methods: ['GET', 'PUT', 'DELETE'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})$/i,\n },\n {\n id: 'like',\n label: '/collections/:collection/entries/:id/like',\n methods: ['PUT', 'DELETE'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})\\/like$/i,\n },\n {\n id: 'report',\n label: '/collections/:collection/entries/:id/report',\n methods: ['POST'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})\\/report$/i,\n },\n {\n id: 'hidden',\n label: '/collections/:collection/entries/:id/hidden',\n methods: ['PUT', 'DELETE'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})\\/hidden$/i,\n },\n {\n id: 'modLog',\n label: '/moderation-log',\n methods: ['GET'],\n pattern: /^\\/api\\/v1\\/moderation-log$/,\n },\n {\n id: 'publisherErase',\n label: '/publishers/:id',\n methods: ['DELETE'],\n pattern: /^\\/api\\/v1\\/publishers\\/([0-9a-f-]{36})$/i,\n },\n { id: 'me', label: '/me', methods: ['GET', 'POST', 'PUT', 'DELETE'], pattern: /^\\/api\\/v1\\/me$/ },\n { id: 'mePublications', label: '/me/publications', methods: ['GET'], pattern: /^\\/api\\/v1\\/me\\/publications$/ },\n { id: 'meLikes', label: '/me/likes', methods: ['GET'], pattern: /^\\/api\\/v1\\/me\\/likes$/ },\n { id: 'meReports', label: '/me/reports', methods: ['GET'], pattern: /^\\/api\\/v1\\/me\\/reports$/ },\n];\n\nconst matchRoute = (pathname: string): RouteMatch | null => {\n for (const route of ROUTES) {\n const found = pathname.match(route.pattern);\n if (found) return { id: route.id, label: route.label, methods: route.methods, segments: found.slice(1) };\n }\n return null;\n};\n\n/** The matched route's event label; unmatched paths stay 'unknown', never the raw path. */\nconst routeLabel = (pathname: string): string => matchRoute(pathname)?.label ?? 'unknown';\n\n/** The routes whose second path segment is an entry id: they share the one id rule. */\nconst ID_SEGMENT_ROUTES: ReadonlySet<RouteId> = new Set(['entry', 'like', 'report', 'hidden', 'resolveReport']);\n\nconst notAllowed = (): ApiError => new ApiError(405, 'method-not-allowed', 'That method is not supported here.');\n\nconst hmacKeys = new WeakMap<ApiConfig, Promise<CryptoKey>>();\n/** Peppered HMAC: rate-limit keys cannot be reversed to IPs without the deployment secret. */\nconst rateLimitHash = async (config: ApiConfig, text: string): Promise<string> => {\n let key = hmacKeys.get(config);\n if (key === undefined) {\n key = crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(config.rateLimitPepper),\n { hash: 'SHA-256', name: 'HMAC' },\n false,\n ['sign'],\n );\n hmacKeys.set(config, key);\n }\n const mac = await crypto.subtle.sign('HMAC', await key, new TextEncoder().encode(text));\n return [...new Uint8Array(mac)].map((b) => b.toString(16).padStart(2, '0')).join('');\n};\n\n/**\n * The Retry-After the 429s carry. The platform limiter does not expose its period at\n * runtime, so deployments must configure 60-second windows — a longer window would\n * make the header lie.\n */\nconst RATE_LIMIT_RETRY_AFTER_SECONDS = 60;\n\n/** The connecting IP, peppered-HMAC'd, keys the read limit; loopback only when APP_ENV=local. */\nasync function readLimit(config: ApiConfig, env: ArchiveWorkerEnv, request: Request): Promise<void> {\n const ip = request.headers.get('cf-connecting-ip') ?? (config.appEnv === 'local' ? '127.0.0.1' : null);\n if (ip === null) throw new ApiError(503, 'unavailable', 'The service is temporarily unavailable.');\n const { success } = await env.READ_LIMIT.limit({ key: await rateLimitHash(config, ip) });\n if (!success) {\n throw new ApiError(429, 'rate-limited', 'Too many requests. Try again later.', {\n retryAfterSeconds: RATE_LIMIT_RETRY_AFTER_SECONDS,\n });\n }\n}\n\n/** The verified issuer+subject, peppered-HMAC'd, keys the write limit ahead of every mutation. */\nasync function writeLimit(config: ApiConfig, env: ArchiveWorkerEnv, issuer: string, subject: string): Promise<void> {\n const { success } = await env.WRITE_LIMIT.limit({\n key: await rateLimitHash(config, JSON.stringify([issuer, subject])),\n });\n if (!success) {\n throw new ApiError(429, 'rate-limited', 'Too many requests. Try again later.', {\n retryAfterSeconds: RATE_LIMIT_RETRY_AFTER_SECONDS,\n });\n }\n}\n\n/** Only anonymous list reads may serve from the edge cache. */\nasync function cachedPublicGet(\n request: Request,\n ctx: ArchiveExecutionContext,\n produce: () => Promise<Response>,\n onFailure: () => void,\n): Promise<Response> {\n const anonymous = request.headers.get('cookie') === null && request.headers.get('authorization') === null;\n if (!anonymous) return produce();\n const url = new URL(request.url);\n url.search = new URLSearchParams(\n [...url.searchParams.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n ).toString();\n const edgeCache = caches.default;\n const key = new Request(url.toString(), { method: 'GET' });\n const cached = await edgeCache.match(key).catch(() => {\n onFailure();\n return undefined;\n });\n if (cached) return cached;\n const response = await produce();\n response.headers.set(\n 'cache-control',\n `public, max-age=${ARCHIVE_LIMITS.publicCacheSeconds}, s-maxage=${ARCHIVE_LIMITS.publicCacheSeconds}`,\n );\n ctx.waitUntil(edgeCache.put(key, response.clone()).catch(onFailure));\n return response;\n}\n\nconst withCors = (response: Response, request: Request, config: ApiConfig): Response => {\n const headers = corsHeaders(request, config);\n if (Object.keys(headers).length === 0) return response;\n const next = new Response(response.body, response);\n for (const [key, value] of Object.entries(headers)) next.headers.set(key, value);\n return next;\n};\n\n/**\n * Fail-closed precondition for registered publishers: the UI pins the opaque\n * publisher UUID it believes the session belongs to, so a queued write from one\n * tab can never land under another account that took over the shared cookie.\n * The header is never an identity source — every check still resolves the\n * publisher from the verified JWT — it only vetoes mismatched writes.\n */\nfunction requireExpectedPublisher(request: Request, session: Session, exempt: boolean): void {\n if (exempt || session.publisher === null) return;\n const expected = request.headers.get(PUBLISHER_HEADER);\n if (expected === null || !UUID_RE.test(expected)) {\n throw new ApiError(400, 'bad-request', 'The request is not valid.');\n }\n if (expected !== session.publisher.id) {\n throw new ApiError(409, 'publisher-changed', 'The signed-in publisher changed since this request was prepared.');\n }\n}\n\n/**\n * Builds the Worker for one deployment's registered collections. Each collection owns\n * one URL + storage dimension; a duplicate registration is a deployment bug and\n * fails fast.\n */\nexport function createArchiveWorker<E extends ArchiveWorkerEnv>(\n collections: readonly ArchiveCollection[],\n): ArchiveWorkerHandler<E> {\n const registered = new Map<string, ArchiveCollection>();\n for (const collection of collections) {\n if (registered.has(collection.name))\n throw new ArchiveDefinitionError(`Duplicate archive collection: ${collection.name}`);\n registered.set(collection.name, collection);\n }\n\n /** Resolves the route's collection segment; an unregistered collection is a 404, not a 500. */\n const collectionFor = (segment: string): ArchiveCollection => {\n const collection = registered.get(segment);\n if (collection === undefined) throw new ApiError(404, 'not-found', 'No such route.');\n return collection;\n };\n\n async function handle(request: Request, env: E, ctx: ArchiveExecutionContext): Promise<Response> {\n const config = apiConfig(env);\n const url = new URL(request.url);\n const { pathname } = url;\n const method = request.method;\n\n if (!pathname.startsWith('/api/v1/')) throw new ApiError(404, 'not-found', 'No such route.');\n if (method === 'OPTIONS') return preflight(request, config);\n\n const match = matchRoute(pathname);\n // An unknown path inside the mount answers 404 like the paths outside it, and a\n // known route under an unsupported method answers 405 — both with no side\n // effects: no rate-limit spend, no session verification, no auth state leaked,\n // no guard-shaped error for a method the route does not serve.\n if (match === null) throw new ApiError(404, 'not-found', 'No such route.');\n if (!match.methods.includes(method)) throw notAllowed();\n\n // A malformed entry id is the same class of client bug: it answers 400 before\n // any rate-limit spend, session verification or auth state.\n if (ID_SEGMENT_ROUTES.has(match.id) && !UUID_RE.test(match.segments[1] ?? '')) throw badRequest();\n\n // Health sits deliberately outside the rate-limit and session pipelines: uptime\n // probes must never burn the anonymous read budget or couple to authentication,\n // and it touches the database exactly once (the schema-version read).\n if (match.id === 'health') {\n return withCors(await healthJson(config, env), request, config);\n }\n if (match.id === 'reports') queryParams(url, ['cursor', 'limit', 'status']);\n\n await readLimit(config, env, request);\n\n // The same-origin mutation contract is proven BEFORE any identity work: a\n // foreign-origin write or missing marker must fail with 403 even while carrying a\n // garbage token — JWKS/JWT cost and 401 semantics stay behind the origin fence.\n const isMutation = method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS';\n if (isMutation) requireMutationGuard(request, config);\n\n // Session verification runs only where identity matters: private routes, the\n // moderation surfaces and every mutation. Public reads (list, detail, meta) never\n // invoke JWKS or the publisher lookup — browsing must not couple to authentication\n // availability. Sign-out is a cookie-clearing reply, not a session operation.\n const isPublicGet = method === 'GET' && (match.id === 'meta' || match.id === 'entries' || match.id === 'entry');\n const isSignOut = match.id === 'signOut' && method === 'POST';\n const session =\n isPublicGet || isSignOut ? null : await verifySession(request, config, env, match.id === 'accountErase');\n\n if (isMutation) {\n if (isSignOut) {\n // The shared `archiveOkSchema` body; every session cookie attribute is the\n // contract's, never ad hoc.\n const response = json(\n 200,\n { ok: true },\n {\n 'cache-control': 'no-store',\n 'set-cookie': expiredSessionCookie(config.appEnv === 'production'),\n },\n );\n return withCors(response, request, config);\n }\n const verified = requireSession(session);\n // The free fail-closed header precondition runs before the quota token is spent:\n // a stale tab's rejected writes must not consume the current account's budget.\n requireExpectedPublisher(request, verified, match.id === 'me' && method === 'POST');\n await writeLimit(config, env, verified.subject.issuer, verified.subject.subject);\n }\n\n const jsonWithCors = (response: Response) => withCors(response, request, config);\n\n switch (match.id) {\n case 'meta': {\n queryParams(url, []);\n return jsonWithCors(collectionMeta(collectionFor(match.segments[0] ?? '')));\n }\n case 'entries': {\n const collection = collectionFor(match.segments[0] ?? '');\n if (method === 'GET') {\n const response = await cachedPublicGet(\n request,\n ctx,\n () => listEntries(collection, request, env),\n () => emit({ diagnostic: { kind: 'cache' }, type: 'cache-error' }),\n );\n return jsonWithCors(response);\n }\n if (method === 'POST') {\n return jsonWithCors(await publishEntry(collection, request, env, config, requireSession(session)));\n }\n throw notAllowed();\n }\n case 'entry': {\n const collection = collectionFor(match.segments[0] ?? '');\n const id = match.segments[1] ?? '';\n if (method === 'GET') {\n queryParams(url, []);\n return jsonWithCors(await getEntry(collection, env, id));\n }\n if (method === 'PUT') {\n return jsonWithCors(await updateEntry(collection, request, env, config, requireSession(session), id));\n }\n if (method === 'DELETE') {\n return jsonWithCors(await deleteEntry(collection, env, requireSession(session), id));\n }\n throw notAllowed();\n }\n case 'like': {\n const collection = collectionFor(match.segments[0] ?? '');\n const id = match.segments[1] ?? '';\n if (method === 'PUT') {\n return jsonWithCors(await setLike(collection, env, config, requireSession(session), id, true));\n }\n if (method === 'DELETE') {\n return jsonWithCors(await setLike(collection, env, config, requireSession(session), id, false));\n }\n throw notAllowed();\n }\n case 'report': {\n if (method !== 'POST') throw notAllowed();\n const collection = collectionFor(match.segments[0] ?? '');\n return jsonWithCors(\n await reportEntry(collection, request, env, requireSession(session), match.segments[1] ?? ''),\n );\n }\n case 'hidden': {\n if (method !== 'PUT' && method !== 'DELETE') throw notAllowed();\n const collection = collectionFor(match.segments[0] ?? '');\n return jsonWithCors(\n await setHidden(collection, env, requireSession(session), match.segments[1] ?? '', method === 'PUT'),\n );\n }\n case 'reports': {\n // Private reads carry the same fail-closed precondition as writes: a stale tab\n // must not read another publisher's moderation queue after an account switch.\n const verified = requireSession(session);\n requireExpectedPublisher(request, verified, false);\n return jsonWithCors(await listReports(collectionFor(match.segments[0] ?? ''), request, env, verified));\n }\n case 'resolveReport': {\n queryParams(url, []);\n return jsonWithCors(\n await resolveReport(\n collectionFor(match.segments[0] ?? ''),\n env,\n requireSession(session),\n match.segments[1] ?? '',\n ),\n );\n }\n case 'modLog': {\n // The accountability read carries the same fail-closed precondition as every\n // private surface: a stale tab must not read the moderation history after an\n // account switch.\n const verified = requireSession(session);\n requireExpectedPublisher(request, verified, false);\n return jsonWithCors(await listModerationLog(request, env, verified));\n }\n case 'publisherErase': {\n // The terminal moderation action: erasing a publisher removes their row, and\n // FK cascades take the publications, likes and retry keys with it — the same\n // cascade a self-erase walks, applied to any publisher the operator names.\n if (method !== 'DELETE') throw notAllowed();\n return jsonWithCors(await erasePublisher(env, requireSession(session), match.segments[0] ?? ''));\n }\n case 'accountErase': {\n queryParams(url, []);\n return jsonWithCors(await deleteAccountProfile(env, requireSession(session)));\n }\n case 'me': {\n if (method === 'GET') return jsonWithCors(getMe(session));\n if (method === 'POST') return jsonWithCors(await registerMe(request, env, requireSession(session)));\n if (method === 'PUT') return jsonWithCors(await renameMe(request, env, requireSession(session)));\n if (method === 'DELETE') return jsonWithCors(await deleteProfile(env, requireSession(session)));\n throw notAllowed();\n }\n case 'mePublications':\n case 'meLikes':\n case 'meReports': {\n // Private reads carry the same fail-closed precondition as writes: a stale tab\n // must not consume another publisher's data after a shared-cookie account switch.\n const verified = requireSession(session);\n requireExpectedPublisher(request, verified, false);\n if (match.id === 'mePublications') {\n // The owner's entries are per-collection: the collection param names the\n // registered collection, and an absent or unregistered value is a malformed\n // request, not an empty list.\n const params = queryParams(url, ['collection']);\n const requested = params.get('collection');\n if (requested === null) throw badRequest();\n const collection = registered.get(requested);\n if (collection === undefined) throw badRequest();\n return jsonWithCors(await myPublications(collection, env, verified));\n }\n return jsonWithCors(\n await (match.id === 'meReports' ? myReports(request, env, verified) : myLikes(request, env, verified)),\n );\n }\n default:\n throw notAllowed();\n }\n }\n\n const tappers = new Set<(event: ArchiveEvent) => void>();\n const emit = (event: ArchiveEvent): void => {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must never affect the Worker.\n }\n }\n };\n\n return {\n async fetch(request: Request, env: E, ctx: ArchiveExecutionContext): Promise<Response> {\n const requestId = crypto.randomUUID();\n const route = routeLabel(new URL(request.url).pathname);\n const startedAt = Date.now();\n try {\n const response = await handle(request, env, ctx);\n let diagnostic: ArchiveDiagnostic | undefined;\n if (response.status === 503 && route === '/health') {\n const health = archiveHealthSchema.parse(await response.clone().json());\n diagnostic = { kind: health.reason === 'database-unavailable' ? 'database' : 'schema' };\n }\n emit({\n code: response.ok ? 'ok' : 'unavailable',\n ms: Date.now() - startedAt,\n requestId,\n route,\n status: response.status,\n type: 'request',\n ...(diagnostic === undefined ? {} : { diagnostic }),\n });\n return response;\n } catch (error) {\n const response = toErrorResponse(error, requestId);\n emit({\n code: error instanceof ApiError ? error.code : 'internal',\n diagnostic:\n error instanceof ApiError\n ? error.diagnostic\n : { kind: error instanceof ArchiveDerivationError ? 'collection' : 'internal' },\n ms: Date.now() - startedAt,\n requestId,\n route,\n status: response.status,\n type: 'request',\n });\n // Allowed origins see the same CORS contract on errors as on successes.\n try {\n return withCors(response, request, apiConfig(env));\n } catch {\n return response; // the config error itself is the response\n }\n }\n },\n\n async scheduled(_controller: unknown, env: E, _ctx: ArchiveExecutionContext): Promise<void> {\n try {\n const timestamp = Date.now();\n const results = await env.DB.batch([\n env.DB.prepare(SQL.expireRequests).bind(timestamp),\n env.DB.prepare(SQL.expireRevocations).bind(timestamp),\n ]);\n emit({\n removed: results.reduce((sum, result) => sum + (result.meta?.changes ?? 0), 0),\n status: 'ok',\n type: 'scheduled',\n });\n } catch (error) {\n emit({ diagnostic: { kind: 'database' }, status: 'failed', type: 'scheduled' });\n throw error;\n }\n },\n\n tap(handler, options) {\n const signal = options?.signal;\n if (signal?.aborted) return () => {};\n const off = () => {\n tappers.delete(handler);\n signal?.removeEventListener('abort', off);\n };\n tappers.add(handler);\n signal?.addEventListener('abort', off, { once: true });\n return off;\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/worker/worker.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { ArchiveExecutionContext } from '../_server/execution';
|
|
2
|
+
import { type ArchiveDiagnostic } from '../_server/http';
|
|
1
3
|
import type { ArchiveCollection } from '../collection';
|
|
2
4
|
import { type ArchiveErrorCode } from '../contract';
|
|
3
5
|
import { type ArchiveWorkerEnv } from './env';
|
|
4
|
-
import { type ArchiveDiagnostic } from './http';
|
|
5
6
|
/**
|
|
6
7
|
* The community-archive Worker factory: anonymous public reads, cookie-authenticated
|
|
7
8
|
* mutations behind strict Origin/request-marker guards, IP and subject rate limits,
|
|
@@ -14,9 +15,6 @@ import { type ArchiveDiagnostic } from './http';
|
|
|
14
15
|
* flows exclusively through `tap()` — the core itself never logs.
|
|
15
16
|
*/
|
|
16
17
|
/** The minimal execution-context surface the core uses; the runtime's satisfies it. */
|
|
17
|
-
export interface ArchiveExecutionContext {
|
|
18
|
-
waitUntil(promise: Promise<unknown>): void;
|
|
19
|
-
}
|
|
20
18
|
/** One sanitized request-completion event: ids, codes, route classes and duration only. */
|
|
21
19
|
export interface ArchiveRequestEvent {
|
|
22
20
|
/** 'ok' on success; otherwise the route's `ARCHIVE_ERROR_CODES` value. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/worker/worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/worker/worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAEL,KAAK,iBAAiB,EAMvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAkB,KAAK,gBAAgB,EAAyC,MAAM,aAAa,CAAC;AAI3G,OAAO,EAAkB,KAAK,gBAAgB,EAAa,MAAM,OAAO,CAAC;AA2BzE;;;;;;;;;;GAUG;AAEH,uFAAuF;AACvF,2FAA2F;AAC3F,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC9B,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,+EAA+E;AAC/E,MAAM,MAAM,qBAAqB,GAC7B;IACE,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,IAAI,CAAC;CACd,GACD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE3E,MAAM,MAAM,YAAY,GACpB,mBAAmB,GACnB,qBAAqB,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE7E;;;;GAIG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,gBAAgB,GAAG,gBAAgB;IACjF,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,uBAAuB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjF,SAAS,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,uBAAuB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F;;;;;OAKG;IACH,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,MAAM,IAAI,CAAC;CACtG;AAqOD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,gBAAgB,EAC5D,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,oBAAoB,CAAC,CAAC,CAAC,CA+SzB"}
|