@vielzeug/archive 26.11.5 → 26.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"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_http","require_errors","require_sql","require_auth","require_env","require_publications","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","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,11 +1,11 @@
1
1
  import { ARCHIVE_LIMITS as e, PUBLISHER_HEADER as t, archiveHealthSchema as n } from "../contract.js";
2
- import { ApiError as r, badRequest as i, corsHeaders as a, expiredSessionCookie as o, json as s, preflight as c, queryParams as l, requireMutationGuard as u, toErrorResponse as d } from "./http.js";
3
- import { ArchiveDefinitionError as f, ArchiveDerivationError as p } from "../errors.js";
4
- import { SQL as m } from "./sql.js";
5
- import { requireSession as h, verifySession as g } from "./auth.js";
6
- import { apiConfig as _ } from "./env.js";
7
- import { collectionMeta as v, deleteAccountProfile as y, deleteEntry as b, deleteProfile as x, erasePublisher as S, getEntry as C, getMe as w, healthJson as T, listEntries as E, listModerationLog as D, listReports as O, myLikes as k, myPublications as A, myReports as j, publishEntry as M, registerMe as N, renameMe as P, reportEntry as F, resolveReport as I, setHidden as L, setLike as R, updateEntry as z } from "./publications.js";
8
- //#region src/worker/worker.ts
2
+ import { ArchiveDefinitionError as r, ArchiveDerivationError as i } from "../errors.js";
3
+ import { requireSession as a, verifySession as o } from "./auth.js";
4
+ import { apiConfig as s } from "./env.js";
5
+ import { ApiError as c, badRequest as l, corsHeaders as u, expiredSessionCookie as d, json as f, preflight as p, queryParams as m, requireMutationGuard as h, toErrorResponse as g } from "./http.js";
6
+ import { collectionMeta as _, deleteAccountProfile as v, deleteEntry as y, deleteProfile as b, erasePublisher as x, getEntry as S, getMe as C, healthJson as w, listEntries as T, listModerationLog as E, listReports as D, myLikes as O, myPublications as k, myReports as A, publishEntry as j, registerMe as M, renameMe as N, reportEntry as P, resolveReport as F, setHidden as I, setLike as L, updateEntry as R } from "./publications.js";
7
+ import { SQL as z } from "./sql.js";
8
+ // src/worker/worker.ts
9
9
  var B = [
10
10
  {
11
11
  id: "accountErase",
@@ -135,7 +135,7 @@ var B = [
135
135
  "report",
136
136
  "hidden",
137
137
  "resolveReport"
138
- ]), notAllowed = () => new r(405, "method-not-allowed", "That method is not supported here."), U = /* @__PURE__ */ new WeakMap(), rateLimitHash = async (e, t) => {
138
+ ]), notAllowed = () => new c(405, "method-not-allowed", "That method is not supported here."), U = /* @__PURE__ */ new WeakMap(), rateLimitHash = async (e, t) => {
139
139
  let n = U.get(e);
140
140
  n === void 0 && (n = crypto.subtle.importKey("raw", new TextEncoder().encode(e.rateLimitPepper), {
141
141
  hash: "SHA-256",
@@ -145,14 +145,14 @@ var B = [
145
145
  return [...new Uint8Array(r)].map((e) => e.toString(16).padStart(2, "0")).join("");
146
146
  }, W = 60;
147
147
  async function readLimit(e, t, n) {
148
- let i = n.headers.get("cf-connecting-ip") ?? (e.appEnv === "local" ? "127.0.0.1" : null);
149
- if (i === null) throw new r(503, "unavailable", "The service is temporarily unavailable.");
150
- let { success: a } = await t.READ_LIMIT.limit({ key: await rateLimitHash(e, i) });
151
- if (!a) throw new r(429, "rate-limited", "Too many requests. Try again later.", { retryAfterSeconds: W });
148
+ let r = n.headers.get("cf-connecting-ip") ?? (e.appEnv === "local" ? "127.0.0.1" : null);
149
+ if (r === null) throw new c(503, "unavailable", "The service is temporarily unavailable.");
150
+ let { success: i } = await t.READ_LIMIT.limit({ key: await rateLimitHash(e, r) });
151
+ if (!i) throw new c(429, "rate-limited", "Too many requests. Try again later.", { retryAfterSeconds: W });
152
152
  }
153
- async function writeLimit(e, t, n, i) {
154
- let { success: a } = await t.WRITE_LIMIT.limit({ key: await rateLimitHash(e, JSON.stringify([n, i])) });
155
- if (!a) throw new r(429, "rate-limited", "Too many requests. Try again later.", { retryAfterSeconds: W });
153
+ async function writeLimit(e, t, n, r) {
154
+ let { success: i } = await t.WRITE_LIMIT.limit({ key: await rateLimitHash(e, JSON.stringify([n, r])) });
155
+ if (!i) throw new c(429, "rate-limited", "Too many requests. Try again later.", { retryAfterSeconds: W });
156
156
  }
157
157
  async function cachedPublicGet(t, n, r, i) {
158
158
  if (t.headers.get("cookie") !== null || t.headers.get("authorization") !== null) return r();
@@ -166,164 +166,164 @@ async function cachedPublicGet(t, n, r, i) {
166
166
  return l.headers.set("cache-control", `public, max-age=${e.publicCacheSeconds}, s-maxage=${e.publicCacheSeconds}`), n.waitUntil(o.put(s, l.clone()).catch(i)), l;
167
167
  }
168
168
  var withCors = (e, t, n) => {
169
- let r = a(t, n);
169
+ let r = u(t, n);
170
170
  if (Object.keys(r).length === 0) return e;
171
171
  let i = new Response(e.body, e);
172
172
  for (let [e, t] of Object.entries(r)) i.headers.set(e, t);
173
173
  return i;
174
174
  };
175
- function requireExpectedPublisher(e, n, i) {
176
- if (i || n.publisher === null) return;
177
- let a = e.headers.get(t);
178
- if (a === null || !V.test(a)) throw new r(400, "bad-request", "The request is not valid.");
179
- if (a !== n.publisher.id) throw new r(409, "publisher-changed", "The signed-in publisher changed since this request was prepared.");
175
+ function requireExpectedPublisher(e, n, r) {
176
+ if (r || n.publisher === null) return;
177
+ let i = e.headers.get(t);
178
+ if (i === null || !V.test(i)) throw new c(400, "bad-request", "The request is not valid.");
179
+ if (i !== n.publisher.id) throw new c(409, "publisher-changed", "The signed-in publisher changed since this request was prepared.");
180
180
  }
181
181
  function createArchiveWorker(e) {
182
182
  let t = /* @__PURE__ */ new Map();
183
183
  for (let n of e) {
184
- if (t.has(n.name)) throw new f(`Duplicate archive collection: ${n.name}`);
184
+ if (t.has(n.name)) throw new r(`Duplicate archive collection: ${n.name}`);
185
185
  t.set(n.name, n);
186
186
  }
187
187
  let collectionFor = (e) => {
188
188
  let n = t.get(e);
189
- if (n === void 0) throw new r(404, "not-found", "No such route.");
189
+ if (n === void 0) throw new c(404, "not-found", "No such route.");
190
190
  return n;
191
191
  };
192
- async function handle(e, n, a) {
193
- let d = _(n), f = new URL(e.url), { pathname: p } = f, m = e.method;
194
- if (!p.startsWith("/api/v1/")) throw new r(404, "not-found", "No such route.");
195
- if (m === "OPTIONS") return c(e, d);
196
- let B = matchRoute(p);
197
- if (B === null) throw new r(404, "not-found", "No such route.");
198
- if (!B.methods.includes(m)) throw notAllowed();
199
- if (H.has(B.id) && !V.test(B.segments[1] ?? "")) throw i();
200
- if (B.id === "health") return withCors(await T(d, n), e, d);
201
- B.id === "reports" && l(f, [
192
+ async function handle(e, n, r) {
193
+ let i = s(n), u = new URL(e.url), { pathname: g } = u, z = e.method;
194
+ if (!g.startsWith("/api/v1/")) throw new c(404, "not-found", "No such route.");
195
+ if (z === "OPTIONS") return p(e, i);
196
+ let B = matchRoute(g);
197
+ if (B === null) throw new c(404, "not-found", "No such route.");
198
+ if (!B.methods.includes(z)) throw notAllowed();
199
+ if (H.has(B.id) && !V.test(B.segments[1] ?? "")) throw l();
200
+ if (B.id === "health") return withCors(await w(i, n), e, i);
201
+ B.id === "reports" && m(u, [
202
202
  "cursor",
203
203
  "limit",
204
204
  "status"
205
- ]), await readLimit(d, n, e);
206
- let U = m !== "GET" && m !== "HEAD" && m !== "OPTIONS";
207
- U && u(e, d);
208
- let W = m === "GET" && (B.id === "meta" || B.id === "entries" || B.id === "entry"), G = B.id === "signOut" && m === "POST", K = W || G ? null : await g(e, d, n, B.id === "accountErase");
205
+ ]), await readLimit(i, n, e);
206
+ let U = z !== "GET" && z !== "HEAD" && z !== "OPTIONS";
207
+ U && h(e, i);
208
+ let W = z === "GET" && (B.id === "meta" || B.id === "entries" || B.id === "entry"), G = B.id === "signOut" && z === "POST", K = W || G ? null : await o(e, i, n, B.id === "accountErase");
209
209
  if (U) {
210
- if (G) return withCors(s(200, { ok: !0 }, {
210
+ if (G) return withCors(f(200, { ok: !0 }, {
211
211
  "cache-control": "no-store",
212
- "set-cookie": o(d.appEnv === "production")
213
- }), e, d);
214
- let t = h(K);
215
- requireExpectedPublisher(e, t, B.id === "me" && m === "POST"), await writeLimit(d, n, t.subject.issuer, t.subject.subject);
212
+ "set-cookie": d(i.appEnv === "production")
213
+ }), e, i);
214
+ let t = a(K);
215
+ requireExpectedPublisher(e, t, B.id === "me" && z === "POST"), await writeLimit(i, n, t.subject.issuer, t.subject.subject);
216
216
  }
217
- let jsonWithCors = (t) => withCors(t, e, d);
217
+ let jsonWithCors = (t) => withCors(t, e, i);
218
218
  switch (B.id) {
219
- case "meta": return l(f, []), jsonWithCors(v(collectionFor(B.segments[0] ?? "")));
219
+ case "meta": return m(u, []), jsonWithCors(_(collectionFor(B.segments[0] ?? "")));
220
220
  case "entries": {
221
221
  let t = collectionFor(B.segments[0] ?? "");
222
- if (m === "GET") return jsonWithCors(await cachedPublicGet(e, a, () => E(t, e, n), () => emit({
222
+ if (z === "GET") return jsonWithCors(await cachedPublicGet(e, r, () => T(t, e, n), () => emit({
223
223
  diagnostic: { kind: "cache" },
224
224
  type: "cache-error"
225
225
  })));
226
- if (m === "POST") return jsonWithCors(await M(t, e, n, d, h(K)));
226
+ if (z === "POST") return jsonWithCors(await j(t, e, n, i, a(K)));
227
227
  throw notAllowed();
228
228
  }
229
229
  case "entry": {
230
230
  let t = collectionFor(B.segments[0] ?? ""), r = B.segments[1] ?? "";
231
- if (m === "GET") return l(f, []), jsonWithCors(await C(t, n, r));
232
- if (m === "PUT") return jsonWithCors(await z(t, e, n, d, h(K), r));
233
- if (m === "DELETE") return jsonWithCors(await b(t, n, h(K), r));
231
+ if (z === "GET") return m(u, []), jsonWithCors(await S(t, n, r));
232
+ if (z === "PUT") return jsonWithCors(await R(t, e, n, i, a(K), r));
233
+ if (z === "DELETE") return jsonWithCors(await y(t, n, a(K), r));
234
234
  throw notAllowed();
235
235
  }
236
236
  case "like": {
237
237
  let e = collectionFor(B.segments[0] ?? ""), t = B.segments[1] ?? "";
238
- if (m === "PUT") return jsonWithCors(await R(e, n, d, h(K), t, !0));
239
- if (m === "DELETE") return jsonWithCors(await R(e, n, d, h(K), t, !1));
238
+ if (z === "PUT") return jsonWithCors(await L(e, n, i, a(K), t, !0));
239
+ if (z === "DELETE") return jsonWithCors(await L(e, n, i, a(K), t, !1));
240
240
  throw notAllowed();
241
241
  }
242
242
  case "report": {
243
- if (m !== "POST") throw notAllowed();
243
+ if (z !== "POST") throw notAllowed();
244
244
  let t = collectionFor(B.segments[0] ?? "");
245
- return jsonWithCors(await F(t, e, n, h(K), B.segments[1] ?? ""));
245
+ return jsonWithCors(await P(t, e, n, a(K), B.segments[1] ?? ""));
246
246
  }
247
247
  case "hidden": {
248
- if (m !== "PUT" && m !== "DELETE") throw notAllowed();
248
+ if (z !== "PUT" && z !== "DELETE") throw notAllowed();
249
249
  let e = collectionFor(B.segments[0] ?? "");
250
- return jsonWithCors(await L(e, n, h(K), B.segments[1] ?? "", m === "PUT"));
250
+ return jsonWithCors(await I(e, n, a(K), B.segments[1] ?? "", z === "PUT"));
251
251
  }
252
252
  case "reports": {
253
- let t = h(K);
254
- return requireExpectedPublisher(e, t, !1), jsonWithCors(await O(collectionFor(B.segments[0] ?? ""), e, n, t));
253
+ let t = a(K);
254
+ return requireExpectedPublisher(e, t, !1), jsonWithCors(await D(collectionFor(B.segments[0] ?? ""), e, n, t));
255
255
  }
256
- case "resolveReport": return l(f, []), jsonWithCors(await I(collectionFor(B.segments[0] ?? ""), n, h(K), B.segments[1] ?? ""));
256
+ case "resolveReport": return m(u, []), jsonWithCors(await F(collectionFor(B.segments[0] ?? ""), n, a(K), B.segments[1] ?? ""));
257
257
  case "modLog": {
258
- let t = h(K);
259
- return requireExpectedPublisher(e, t, !1), jsonWithCors(await D(e, n, t));
258
+ let t = a(K);
259
+ return requireExpectedPublisher(e, t, !1), jsonWithCors(await E(e, n, t));
260
260
  }
261
261
  case "publisherErase":
262
- if (m !== "DELETE") throw notAllowed();
263
- return jsonWithCors(await S(n, h(K), B.segments[0] ?? ""));
264
- case "accountErase": return l(f, []), jsonWithCors(await y(n, h(K)));
262
+ if (z !== "DELETE") throw notAllowed();
263
+ return jsonWithCors(await x(n, a(K), B.segments[0] ?? ""));
264
+ case "accountErase": return m(u, []), jsonWithCors(await v(n, a(K)));
265
265
  case "me":
266
- if (m === "GET") return jsonWithCors(w(K));
267
- if (m === "POST") return jsonWithCors(await N(e, n, h(K)));
268
- if (m === "PUT") return jsonWithCors(await P(e, n, h(K)));
269
- if (m === "DELETE") return jsonWithCors(await x(n, h(K)));
266
+ if (z === "GET") return jsonWithCors(C(K));
267
+ if (z === "POST") return jsonWithCors(await M(e, n, a(K)));
268
+ if (z === "PUT") return jsonWithCors(await N(e, n, a(K)));
269
+ if (z === "DELETE") return jsonWithCors(await b(n, a(K)));
270
270
  throw notAllowed();
271
271
  case "mePublications":
272
272
  case "meLikes":
273
273
  case "meReports": {
274
- let r = h(K);
274
+ let r = a(K);
275
275
  if (requireExpectedPublisher(e, r, !1), B.id === "mePublications") {
276
- let e = l(f, ["collection"]).get("collection");
277
- if (e === null) throw i();
278
- let a = t.get(e);
279
- if (a === void 0) throw i();
280
- return jsonWithCors(await A(a, n, r));
276
+ let e = m(u, ["collection"]).get("collection");
277
+ if (e === null) throw l();
278
+ let i = t.get(e);
279
+ if (i === void 0) throw l();
280
+ return jsonWithCors(await k(i, n, r));
281
281
  }
282
- return jsonWithCors(await (B.id === "meReports" ? j(e, n, r) : k(e, n, r)));
282
+ return jsonWithCors(await (B.id === "meReports" ? A(e, n, r) : O(e, n, r)));
283
283
  }
284
284
  default: throw notAllowed();
285
285
  }
286
286
  }
287
- let a = /* @__PURE__ */ new Set(), emit = (e) => {
288
- if (a.size !== 0) for (let t of a) try {
287
+ let u = /* @__PURE__ */ new Set(), emit = (e) => {
288
+ if (u.size !== 0) for (let t of u) try {
289
289
  t(e);
290
290
  } catch {}
291
291
  };
292
292
  return {
293
- async fetch(e, t, i) {
294
- let a = crypto.randomUUID(), o = routeLabel(new URL(e.url).pathname), s = Date.now();
293
+ async fetch(e, t, r) {
294
+ let a = crypto.randomUUID(), o = routeLabel(new URL(e.url).pathname), l = Date.now();
295
295
  try {
296
- let r = await handle(e, t, i), c;
297
- return r.status === 503 && o === "/health" && (c = { kind: n.parse(await r.clone().json()).reason === "database-unavailable" ? "database" : "schema" }), emit({
298
- code: r.ok ? "ok" : "unavailable",
299
- ms: Date.now() - s,
296
+ let i = await handle(e, t, r), s;
297
+ return i.status === 503 && o === "/health" && (s = { kind: n.parse(await i.clone().json()).reason === "database-unavailable" ? "database" : "schema" }), emit({
298
+ code: i.ok ? "ok" : "unavailable",
299
+ ms: Date.now() - l,
300
300
  requestId: a,
301
301
  route: o,
302
- status: r.status,
302
+ status: i.status,
303
303
  type: "request",
304
- ...c === void 0 ? {} : { diagnostic: c }
305
- }), r;
304
+ ...s === void 0 ? {} : { diagnostic: s }
305
+ }), i;
306
306
  } catch (n) {
307
- let i = d(n, a);
307
+ let r = g(n, a);
308
308
  emit({
309
- code: n instanceof r ? n.code : "internal",
310
- diagnostic: n instanceof r ? n.diagnostic : { kind: n instanceof p ? "collection" : "internal" },
311
- ms: Date.now() - s,
309
+ code: n instanceof c ? n.code : "internal",
310
+ diagnostic: n instanceof c ? n.diagnostic : { kind: n instanceof i ? "collection" : "internal" },
311
+ ms: Date.now() - l,
312
312
  requestId: a,
313
313
  route: o,
314
- status: i.status,
314
+ status: r.status,
315
315
  type: "request"
316
316
  });
317
317
  try {
318
- return withCors(i, e, _(t));
318
+ return withCors(r, e, s(t));
319
319
  } catch {
320
- return i;
320
+ return r;
321
321
  }
322
322
  }
323
323
  },
324
324
  async scheduled(e, t, n) {
325
325
  try {
326
- let e = Date.now(), n = await t.DB.batch([t.DB.prepare(m.expireRequests).bind(e), t.DB.prepare(m.expireRevocations).bind(e)]);
326
+ let e = Date.now(), n = await t.DB.batch([t.DB.prepare(z.expireRequests).bind(e), t.DB.prepare(z.expireRevocations).bind(e)]);
327
327
  emit({
328
328
  removed: n.reduce((e, t) => e + (t.meta?.changes ?? 0), 0),
329
329
  status: "ok",
@@ -341,13 +341,12 @@ function createArchiveWorker(e) {
341
341
  let n = t?.signal;
342
342
  if (n?.aborted) return () => {};
343
343
  let off = () => {
344
- a.delete(e), n?.removeEventListener("abort", off);
344
+ u.delete(e), n?.removeEventListener("abort", off);
345
345
  };
346
- return a.add(e), n?.addEventListener("abort", off, { once: !0 }), off;
346
+ return u.add(e), n?.addEventListener("abort", off, { once: !0 }), off;
347
347
  }
348
348
  };
349
349
  }
350
- //#endregion
351
350
  export { createArchiveWorker };
352
351
 
353
352
  //# sourceMappingURL=worker.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/archive",
3
- "version": "26.11.5",
3
+ "version": "26.11.6",
4
4
  "description": "Community content publishing for Cloudflare Workers + D1: a hardened Worker core over per-collection payload schemas",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,24 +52,24 @@
52
52
  "registry": "https://registry.npmjs.org/"
53
53
  },
54
54
  "scripts": {
55
- "build": "vite build && pnpm --silent run build:types",
55
+ "build": "vite build && bun run --silent build:types",
56
56
  "build:types": "tsc -p tsconfig.declarations.json",
57
57
  "fix": "biome check --write src test",
58
58
  "lint": "biome ci src test",
59
- "prepublishOnly": "pnpm --silent run build",
59
+ "prepublishOnly": "bun run --silent build",
60
60
  "test": "vitest",
61
61
  "test:integration": "vitest run --config vitest.config.integration.ts",
62
62
  "test:types": "tsc -p tsconfig.json --noEmit"
63
63
  },
64
64
  "dependencies": {
65
- "@vielzeug/spell": "26.11.5",
66
- "jose": "6.2.12"
65
+ "@vielzeug/spell": "26.11.6",
66
+ "jose": "6.2.15"
67
67
  },
68
68
  "devDependencies": {
69
- "@types/node": "^26.6.5",
69
+ "@types/node": "^26.6.6",
70
70
  "typescript": "^6.0.3",
71
- "vite": "^8.3.4",
72
- "vitest": "^5.0.5",
73
- "wrangler": "4.151.2"
71
+ "vite": "^8.3.6",
72
+ "vitest": "^5.1.0",
73
+ "wrangler": "4.158.0"
74
74
  }
75
75
  }