@vielzeug/archive 26.11.4 → 26.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_crypto.js +1 -2
- package/dist/_json.js +1 -2
- package/dist/auth/_confirmation.js +1 -2
- package/dist/auth/config.js +1 -2
- package/dist/auth/errors.js +1 -2
- package/dist/auth/store.js +1 -2
- package/dist/auth/worker.cjs +1 -1
- package/dist/auth/worker.cjs.map +1 -1
- package/dist/auth/worker.js +28 -29
- package/dist/collection.cjs +1 -1
- package/dist/collection.cjs.map +1 -1
- package/dist/collection.js +50 -51
- package/dist/contract.js +1 -2
- package/dist/errors.js +1 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +4 -4
- package/dist/worker/_pagination.js +1 -2
- package/dist/worker/_service.js +1 -2
- package/dist/worker/auth.js +1 -2
- package/dist/worker/env.js +1 -2
- package/dist/worker/http.js +1 -2
- package/dist/worker/publications.cjs +1 -1
- package/dist/worker/publications.cjs.map +1 -1
- package/dist/worker/publications.js +138 -139
- package/dist/worker/sql.js +1 -2
- package/dist/worker/worker.cjs +1 -1
- package/dist/worker/worker.cjs.map +1 -1
- package/dist/worker/worker.js +95 -96
- package/package.json +9 -9
|
@@ -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":"A,4LA+IA,IAAM8C,EAAiG,CACrG,CAAE,GAAI,eAAgB,MAAO,cAAe,QAAS,CAAC,QAAQ,EAAG,QAAS,0BAA2B,EACrG,CAAE,GAAI,SAAU,MAAO,UAAW,QAAS,CAAC,KAAK,EAAG,QAAS,qBAAsB,EACnF,CAAE,GAAI,UAAW,MAAO,YAAa,QAAS,CAAC,MAAM,EAAG,QAAS,uBAAwB,EACzF,CACE,GAAI,gBACJ,MAAO,gDACP,QAAS,CAAC,KAAK,EACf,QAAS,6EACX,EACA,CACE,GAAI,OACJ,MAAO,2BACP,QAAS,CAAC,KAAK,EACf,QAAS,wCACX,EACA,CACE,GAAI,UACJ,MAAO,mCACP,QAAS,CAAC,KAAK,EACf,QAAS,iDACX,EACA,CACE,GAAI,UACJ,MAAO,mCACP,QAAS,CAAC,MAAO,MAAM,EACvB,QAAS,iDACX,EACA,CACE,GAAI,QACJ,MAAO,uCACP,QAAS,CAAC,MAAO,MAAO,QAAQ,EAChC,QAAS,mEACX,EACA,CACE,GAAI,OACJ,MAAO,4CACP,QAAS,CAAC,MAAO,QAAQ,EACzB,QAAS,yEACX,EACA,CACE,GAAI,SACJ,MAAO,8CACP,QAAS,CAAC,MAAM,EAChB,QAAS,2EACX,EACA,CACE,GAAI,SACJ,MAAO,8CACP,QAAS,CAAC,MAAO,QAAQ,EACzB,QAAS,2EACX,EACA,CACE,GAAI,SACJ,MAAO,kBACP,QAAS,CAAC,KAAK,EACf,QAAS,6BACX,EACA,CACE,GAAI,iBACJ,MAAO,kBACP,QAAS,CAAC,QAAQ,EAClB,QAAS,2CACX,EACA,CAAE,GAAI,KAAM,MAAO,MAAO,QAAS,CAAC,MAAO,OAAQ,MAAO,QAAQ,EAAG,QAAS,iBAAkB,EAChG,CAAE,GAAI,iBAAkB,MAAO,mBAAoB,QAAS,CAAC,KAAK,EAAG,QAAS,+BAAgC,EAC9G,CAAE,GAAI,UAAW,MAAO,YAAa,QAAS,CAAC,KAAK,EAAG,QAAS,wBAAyB,EACzF,CAAE,GAAI,YAAa,MAAO,cAAe,QAAS,CAAC,KAAK,EAAG,QAAS,0BAA2B,CACjG,EAEM,WAAc,GAAwC,CAC1D,IAAK,IAAMC,KAASD,EAAQ,CAC1B,IAAME,EAAQC,EAAS,MAAMF,EAAM,OAAO,EAC1C,GAAIC,EAAO,MAAO,CAAE,GAAID,EAAM,GAAI,MAAOA,EAAM,MAAO,QAASA,EAAM,QAAS,SAAUC,EAAM,MAAM,CAAC,CAAE,CACzG,CACA,OAAO,IACT,EAGM,WAAc,GAA6B,WAAWC,CAAQ,CAAC,EAAE,OAAS,UAE1EC,EAAY,kEAGZC,EAA0C,IAAI,IAAI,CAAC,QAAS,OAAQ,SAAU,SAAU,eAAe,CAAC,EAExG,eAA6B,IAAInD,EAAAA,SAAS,IAAK,qBAAsB,oCAAoC,EAEzGoD,EAAW,IAAI,QAEf,cAAgB,MAAO,EAAmB,IAAkC,CAChF,IAAIC,EAAMD,EAAS,IAAIE,CAAM,EACzBD,IAAQ,IAAA,KACV,EAAM,OAAO,OAAO,UAClB,MACA,IAAI,YAAY,CAAC,CAAC,OAAOC,EAAO,eAAe,EAC/C,CAAE,KAAM,UAAW,KAAM,MAAO,EAChC,GACA,CAAC,MAAM,CACT,EACAF,EAAS,IAAIE,EAAQD,CAAG,GAE1B,IAAME,EAAM,MAAM,OAAO,OAAO,KAAK,OAAQ,MAAMF,EAAK,IAAI,YAAY,CAAC,CAAC,OAAOG,CAAI,CAAC,EACtF,MAAO,CAAC,GAAG,IAAI,WAAWD,CAAG,CAAC,CAAC,CAAC,IAAK,GAAME,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CACrF,EAOMC,EAAiC,GAGvC,eAAe,UAAU,EAAmB,EAAuB,EAAiC,CAClG,IAAMC,EAAKC,EAAQ,QAAQ,IAAI,kBAAkB,IAAMN,EAAO,SAAW,QAAU,YAAc,MACjG,GAAIK,IAAO,KAAM,MAAM,IAAI3D,EAAAA,SAAS,IAAK,cAAe,yCAAyC,EACjG,GAAM,CAAE,WAAY,MAAM6D,EAAI,WAAW,MAAM,CAAE,IAAK,MAAM,cAAcP,EAAQK,CAAE,CAAE,CAAC,EACvF,GAAI,CAACG,EACH,MAAM,IAAI9D,EAAAA,SAAS,IAAK,eAAgB,sCAAuC,CAC7E,kBAAmB0D,CACrB,CAAC,CAEL,CAGA,eAAe,WAAW,EAAmB,EAAuB,EAAgB,EAAgC,CAClH,GAAM,CAAE,WAAY,MAAMG,EAAI,YAAY,MAAM,CAC9C,IAAK,MAAM,cAAcP,EAAQ,KAAK,UAAU,CAACS,EAAQC,CAAO,CAAC,CAAC,CACpE,CAAC,EACD,GAAI,CAACF,EACH,MAAM,IAAI9D,EAAAA,SAAS,IAAK,eAAgB,sCAAuC,CAC7E,kBAAmB0D,CACrB,CAAC,CAEL,CAGA,eAAe,gBACb,EACA,EACA,EACA,EACmB,CAEnB,GADkBE,EAAQ,QAAQ,IAAI,QAAQ,IAAM,MAAQA,EAAQ,QAAQ,IAAI,eAAe,IAAM,KACrF,OAAOK,EAAQ,EAC/B,IAAMC,EAAM,IAAI,IAAIN,EAAQ,GAAG,EAC/B,EAAI,OAAS,IAAI,gBACf,CAAC,GAAGM,EAAI,aAAa,QAAQ,CAAC,CAAC,CAAC,MAAM,CAACC,GAAI,CAACV,KAAQU,EAAIV,EAAI,GAAK,IAAIA,EAAU,CACjF,CAAC,CAAC,SAAS,EACX,IAAMW,EAAY,OAAO,QACnBf,EAAM,IAAI,QAAQa,EAAI,SAAS,EAAG,CAAE,OAAQ,KAAM,CAAC,EACnDG,EAAS,MAAMD,EAAU,MAAMf,CAAG,CAAC,CAAC,UAAY,CACpDiB,EAAU,CAEZ,CAAC,EACD,GAAID,EAAQ,OAAOA,EACnB,IAAME,EAAW,MAAMN,EAAQ,EAM/B,OALAM,EAAS,QAAQ,IACf,gBACA,mBAAmBtE,EAAAA,eAAe,mBAAmB,aAAaA,EAAAA,eAAe,oBACnF,EACAuE,EAAI,UAAUJ,EAAU,IAAIf,EAAKkB,EAAS,MAAM,CAAC,CAAC,CAAC,MAAMD,CAAS,CAAC,EAC5DC,CACT,CAEA,IAAM,UAAY,EAAoB,EAAkB,IAAgC,CACtF,IAAME,EAAUvE,EAAAA,YAAY0D,EAASN,CAAM,EAC3C,GAAI,OAAO,KAAKmB,CAAO,CAAC,CAAC,SAAW,EAAG,OAAOF,EAC9C,IAAMG,EAAO,IAAI,SAASH,EAAS,KAAMA,CAAQ,EACjD,IAAK,GAAM,CAAClB,EAAKsB,KAAU,OAAO,QAAQF,CAAO,EAAGC,EAAK,QAAQ,IAAIrB,EAAKsB,CAAK,EAC/E,OAAOD,CACT,EASA,SAAS,yBAAyB,EAAkB,EAAkB,EAAuB,CAC3F,GAAIE,GAAUC,EAAQ,YAAc,KAAM,OAC1C,IAAMC,EAAWlB,EAAQ,QAAQ,IAAIzD,EAAAA,gBAAgB,EACrD,GAAI2E,IAAa,MAAQ,CAAC5B,EAAU,KAAK4B,CAAQ,EAC/C,MAAM,IAAI9E,EAAAA,SAAS,IAAK,cAAe,2BAA2B,EAEpE,GAAI8E,IAAaD,EAAQ,UAAU,GACjC,MAAM,IAAI7E,EAAAA,SAAS,IAAK,oBAAqB,kEAAkE,CAEnH,CAOA,SAAgB,oBACd,EACyB,CACzB,IAAM+E,EAAa,IAAI,IACvB,IAAK,IAAMC,KAAcC,EAAa,CACpC,GAAIF,EAAW,IAAIC,EAAW,IAAI,EAChC,MAAM,IAAI5E,EAAAA,uBAAuB,iCAAiC4E,EAAW,MAAM,EACrFD,EAAW,IAAIC,EAAW,KAAMA,CAAU,CAC5C,CAGA,IAAM,cAAiB,GAAuC,CAC5D,IAAMA,EAAaD,EAAW,IAAIG,CAAO,EACzC,GAAIF,IAAe,IAAA,GAAW,MAAM,IAAIhF,EAAAA,SAAS,IAAK,YAAa,gBAAgB,EACnF,OAAOgF,CACT,EAEA,eAAe,OAAO,EAAkB,EAAQ,EAAiD,CAC/F,IAAM1B,EAASjD,EAAAA,UAAUwD,CAAG,EACtBK,EAAM,IAAI,IAAIN,EAAQ,GAAG,EACzB,CAAE,YAAaM,EACfiB,EAASvB,EAAQ,OAEvB,GAAI,CAACX,EAAS,WAAW,UAAU,EAAG,MAAM,IAAIjD,EAAAA,SAAS,IAAK,YAAa,gBAAgB,EAC3F,GAAImF,IAAW,UAAW,OAAO7E,EAAAA,UAAUsD,EAASN,CAAM,EAE1D,IAAM8B,EAAQ,WAAWnC,CAAQ,EAKjC,GAAImC,IAAU,KAAM,MAAM,IAAIpF,EAAAA,SAAS,IAAK,YAAa,gBAAgB,EACzE,GAAI,CAACoF,EAAM,QAAQ,SAASD,CAAM,EAAG,MAAM,WAAW,EAItD,GAAIhC,EAAkB,IAAIiC,EAAM,EAAE,GAAK,CAAClC,EAAU,KAAKkC,EAAM,SAAS,IAAM,EAAE,EAAG,MAAM7E,EAAAA,WAAW,EAKlG,GAAI6E,EAAM,KAAO,SACf,OAAO,SAAS,MAAM5E,EAAAA,WAAW8C,EAAQO,CAAG,EAAGD,EAASN,CAAM,EAE5D8B,EAAM,KAAO,WAAWzC,EAAA,YAAYuB,EAAK,CAAC,SAAU,QAAS,QAAQ,CAAC,EAE1E,MAAM,UAAUZ,EAAQO,EAAKD,CAAO,EAKpC,IAAMyB,EAAaF,IAAW,OAASA,IAAW,QAAUA,IAAW,UACnEE,GAAY1C,EAAA,qBAAqBiB,EAASN,CAAM,EAMpD,IAAMgC,EAAcH,IAAW,QAAUC,EAAM,KAAO,QAAUA,EAAM,KAAO,WAAaA,EAAM,KAAO,SACjGG,EAAYH,EAAM,KAAO,WAAaD,IAAW,OACjDN,EACJS,GAAeC,EAAY,KAAO,MAAM9E,EAAAA,cAAcmD,EAASN,EAAQO,EAAKuB,EAAM,KAAO,cAAc,EAEzG,GAAIC,EAAY,CACd,GAAIE,EAWF,OAAO,SARU7E,EAAAA,KACf,IACA,CAAE,GAAI,EAAK,EACX,CACE,gBAAiB,WACjB,aAAcC,EAAAA,qBAAqB2C,EAAO,SAAW,YAAY,CACnE,CAEc,EAAUM,EAASN,CAAM,EAE3C,IAAMkC,EAAW5E,EAAAA,eAAeiE,CAAO,EAGvC,yBAAyBjB,EAAS4B,EAAUJ,EAAM,KAAO,MAAQD,IAAW,MAAM,EAClF,MAAM,WAAW7B,EAAQO,EAAK2B,EAAS,QAAQ,OAAQA,EAAS,QAAQ,OAAO,CACjF,CAEA,IAAM,aAAgB,GAAuB,SAASjB,EAAUX,EAASN,CAAM,EAE/E,OAAQ8B,EAAM,GAAd,CACE,IAAK,OAEH,OADAzC,EAAA,YAAYuB,EAAK,CAAC,CAAC,EACZ,aAAarD,EAAAA,eAAe,cAAcuE,EAAM,SAAS,IAAM,EAAE,CAAC,CAAC,EAE5E,IAAK,UAAW,CACd,IAAMJ,EAAa,cAAcI,EAAM,SAAS,IAAM,EAAE,EACxD,GAAID,IAAW,MAOb,OAAO,aAAa,MANG,gBACrBvB,EACAY,MACM1D,EAAAA,YAAYkE,EAAYpB,EAASC,CAAG,MACpC,KAAK,CAAE,WAAY,CAAE,KAAM,OAAQ,EAAG,KAAM,aAAc,CAAC,CACnE,CAC4B,EAE9B,GAAIsB,IAAW,OACb,OAAO,aAAa,MAAMpE,EAAAA,aAAaiE,EAAYpB,EAASC,EAAKP,EAAQ1C,EAAAA,eAAeiE,CAAO,CAAC,CAAC,EAEnG,MAAM,WAAW,CACnB,CACA,IAAK,QAAS,CACZ,IAAMG,EAAa,cAAcI,EAAM,SAAS,IAAM,EAAE,EAClDK,EAAKL,EAAM,SAAS,IAAM,GAChC,GAAID,IAAW,MAEb,OADAxC,EAAA,YAAYuB,EAAK,CAAC,CAAC,EACZ,aAAa,MAAMlD,EAAAA,SAASgE,EAAYnB,EAAK4B,CAAE,CAAC,EAEzD,GAAIN,IAAW,MACb,OAAO,aAAa,MAAMlE,EAAAA,YAAY+D,EAAYpB,EAASC,EAAKP,EAAQ1C,EAAAA,eAAeiE,CAAO,EAAGY,CAAE,CAAC,EAEtG,GAAIN,IAAW,SACb,OAAO,aAAa,MAAMjE,EAAAA,YAAY8D,EAAYnB,EAAKjD,EAAAA,eAAeiE,CAAO,EAAGY,CAAE,CAAC,EAErF,MAAM,WAAW,CACnB,CACA,IAAK,OAAQ,CACX,IAAMT,EAAa,cAAcI,EAAM,SAAS,IAAM,EAAE,EAClDK,EAAKL,EAAM,SAAS,IAAM,GAChC,GAAID,IAAW,MACb,OAAO,aAAa,MAAMhE,EAAAA,QAAQ6D,EAAYnB,EAAKP,EAAQ1C,EAAAA,eAAeiE,CAAO,EAAGY,EAAI,EAAI,CAAC,EAE/F,GAAIN,IAAW,SACb,OAAO,aAAa,MAAMhE,EAAAA,QAAQ6D,EAAYnB,EAAKP,EAAQ1C,EAAAA,eAAeiE,CAAO,EAAGY,EAAI,EAAK,CAAC,EAEhG,MAAM,WAAW,CACnB,CACA,IAAK,SAAU,CACb,GAAIN,IAAW,OAAQ,MAAM,WAAW,EACxC,IAAMH,EAAa,cAAcI,EAAM,SAAS,IAAM,EAAE,EACxD,OAAO,aACL,MAAMhE,EAAAA,YAAY4D,EAAYpB,EAASC,EAAKjD,EAAAA,eAAeiE,CAAO,EAAGO,EAAM,SAAS,IAAM,EAAE,CAC9F,CACF,CACA,IAAK,SAAU,CACb,GAAID,IAAW,OAASA,IAAW,SAAU,MAAM,WAAW,EAC9D,IAAMH,EAAa,cAAcI,EAAM,SAAS,IAAM,EAAE,EACxD,OAAO,aACL,MAAM/D,EAAAA,UAAU2D,EAAYnB,EAAKjD,EAAAA,eAAeiE,CAAO,EAAGO,EAAM,SAAS,IAAM,GAAID,IAAW,KAAK,CACrG,CACF,CACA,IAAK,UAAW,CAGd,IAAMK,EAAW5E,EAAAA,eAAeiE,CAAO,EAEvC,OADA,yBAAyBjB,EAAS4B,EAAU,EAAK,EAC1C,aAAa,MAAMlE,EAAAA,YAAY,cAAc8D,EAAM,SAAS,IAAM,EAAE,EAAGxB,EAASC,EAAK2B,CAAQ,CAAC,CACvG,CACA,IAAK,gBAEH,OADA7C,EAAA,YAAYuB,EAAK,CAAC,CAAC,EACZ,aACL,MAAM3C,EAAAA,cACJ,cAAc6D,EAAM,SAAS,IAAM,EAAE,EACrCvB,EACAjD,EAAAA,eAAeiE,CAAO,EACtBO,EAAM,SAAS,IAAM,EACvB,CACF,EAEF,IAAK,SAAU,CAIb,IAAMI,EAAW5E,EAAAA,eAAeiE,CAAO,EAEvC,OADA,yBAAyBjB,EAAS4B,EAAU,EAAK,EAC1C,aAAa,MAAMhE,EAAAA,kBAAkBoC,EAASC,EAAK2B,CAAQ,CAAC,CACrE,CACA,IAAK,iBAIH,GAAIL,IAAW,SAAU,MAAM,WAAW,EAC1C,OAAO,aAAa,MAAM1D,EAAAA,eAAeoC,EAAKjD,EAAAA,eAAeiE,CAAO,EAAGO,EAAM,SAAS,IAAM,EAAE,CAAC,EAEjG,IAAK,eAEH,OADAzC,EAAA,YAAYuB,EAAK,CAAC,CAAC,EACZ,aAAa,MAAMxC,EAAAA,qBAAqBmC,EAAKjD,EAAAA,eAAeiE,CAAO,CAAC,CAAC,EAE9E,IAAK,KACH,GAAIM,IAAW,MAAO,OAAO,aAAaxD,EAAAA,MAAMkD,CAAO,CAAC,EACxD,GAAIM,IAAW,OAAQ,OAAO,aAAa,MAAMvD,EAAAA,WAAWgC,EAASC,EAAKjD,EAAAA,eAAeiE,CAAO,CAAC,CAAC,EAClG,GAAIM,IAAW,MAAO,OAAO,aAAa,MAAMtD,EAAAA,SAAS+B,EAASC,EAAKjD,EAAAA,eAAeiE,CAAO,CAAC,CAAC,EAC/F,GAAIM,IAAW,SAAU,OAAO,aAAa,MAAMrD,EAAAA,cAAc+B,EAAKjD,EAAAA,eAAeiE,CAAO,CAAC,CAAC,EAC9F,MAAM,WAAW,EAEnB,IAAK,iBACL,IAAK,UACL,IAAK,YAAa,CAGhB,IAAMW,EAAW5E,EAAAA,eAAeiE,CAAO,EAEvC,GADA,yBAAyBjB,EAAS4B,EAAU,EAAK,EAC7CJ,EAAM,KAAO,iBAAkB,CAKjC,IAAMM,EADS3D,EAAAA,YAAYmC,EAAK,CAAC,YAAY,CAC3B,CAAA,CAAO,IAAI,YAAY,EACzC,GAAIwB,IAAc,KAAM,MAAMnF,EAAAA,WAAW,EACzC,IAAMyE,EAAaD,EAAW,IAAIW,CAAS,EAC3C,GAAIV,IAAe,IAAA,GAAW,MAAMzE,EAAAA,WAAW,EAC/C,OAAO,aAAa,MAAMyB,EAAAA,eAAegD,EAAYnB,EAAK2B,CAAQ,CAAC,CACrE,CACA,OAAO,aACL,MAAOJ,EAAM,KAAO,YAAcnD,EAAAA,UAAU2B,EAASC,EAAK2B,CAAQ,EAAItD,EAAAA,QAAQ0B,EAASC,EAAK2B,CAAQ,EACtG,CACF,CACA,QACE,MAAM,WAAW,CACrB,CACF,CAEA,IAAMG,EAAU,IAAI,IACd,KAAQ,GAA8B,CAC1C,GAAIA,EAAQ,OAAS,EACrB,IAAK,IAAMC,KAAUD,EACnB,GAAI,CACFC,EAAOC,CAAK,CACd,MAAQ,CAER,CAEJ,EAEA,MAAO,CACL,MAAM,MAAM,EAAkB,EAAQ,EAAiD,CACrF,IAAMC,EAAY,OAAO,WAAW,EAC9B/C,EAAQ,WAAW,IAAI,IAAIa,EAAQ,GAAG,CAAC,CAAC,QAAQ,EAChDmC,EAAY,KAAK,IAAI,EAC3B,GAAI,CACF,IAAMxB,EAAW,MAAM,OAAOX,EAASC,EAAKW,CAAG,EAC3CwB,EAcJ,OAbIzB,EAAS,SAAW,KAAOxB,IAAU,YAEvC,EAAa,CAAE,KADAZ,EAAAA,oBAAoB,MAAM,MAAMoC,EAAS,MAAM,CAAC,CAAC,KAAK,CAChD,CAAA,CAAO,SAAW,uBAAyB,WAAa,QAAS,GAExF,KAAK,CACH,KAAMA,EAAS,GAAK,KAAO,cAC3B,GAAI,KAAK,IAAI,EAAIwB,EACjB,YACA,QACA,OAAQxB,EAAS,OACjB,KAAM,UACN,GAAIyB,IAAe,IAAA,GAAY,CAAC,EAAI,CAAE,YAAW,CACnD,CAAC,EACMzB,CACT,OAAS0B,EAAO,CACd,IAAM1B,EAAWnC,EAAAA,gBAAgB6D,EAAOH,CAAS,EACjD,KAAK,CACH,KAAMG,aAAiBjG,EAAAA,SAAWiG,EAAM,KAAO,WAC/C,WACEA,aAAiBjG,EAAAA,SACbiG,EAAM,WACN,CAAE,KAAMA,aAAiB5D,EAAAA,uBAAyB,aAAe,UAAW,EAClF,GAAI,KAAK,IAAI,EAAI0D,EACjB,YACA,QACA,OAAQxB,EAAS,OACjB,KAAM,SACR,CAAC,EAED,GAAI,CACF,OAAO,SAASA,EAAUX,EAASvD,EAAAA,UAAUwD,CAAG,CAAC,CACnD,MAAQ,CACN,OAAOU,CACT,CACF,CACF,EAEA,MAAM,UAAU,EAAsB,EAAQ,EAA8C,CAC1F,GAAI,CACF,IAAM2B,EAAY,KAAK,IAAI,EACrBC,EAAU,MAAMtC,EAAI,GAAG,MAAM,CACjCA,EAAI,GAAG,QAAQvB,EAAAA,IAAI,cAAc,CAAC,CAAC,KAAK4D,CAAS,EACjDrC,EAAI,GAAG,QAAQvB,EAAAA,IAAI,iBAAiB,CAAC,CAAC,KAAK4D,CAAS,CACtD,CAAC,EACD,KAAK,CACH,QAASC,EAAQ,QAAQ,EAAK,IAAWC,GAAOC,EAAO,MAAM,SAAW,GAAI,CAAC,EAC7E,OAAQ,KACR,KAAM,WACR,CAAC,CACH,OAASJ,EAAO,CAEd,MADA,KAAK,CAAE,WAAY,CAAE,KAAM,UAAW,EAAG,OAAQ,SAAU,KAAM,WAAY,CAAC,EACxEA,CACR,CACF,EAEA,IAAI,EAAS,EAAS,CACpB,IAAMK,EAASC,GAAS,OACxB,GAAID,GAAQ,QAAS,UAAa,CAAC,EACnC,IAAM,QAAY,CAChBX,EAAQ,OAAOa,CAAO,EACtBF,GAAQ,oBAAoB,QAAS,GAAG,CAC1C,EAGA,OAFAX,EAAQ,IAAIa,CAAO,EACnBF,GAAQ,iBAAiB,QAAS,IAAK,CAAE,KAAM,EAAK,CAAC,EAC9C,GACT,CACF,CACF,C"}
|
package/dist/worker/worker.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ARCHIVE_LIMITS as e, PUBLISHER_HEADER as t, archiveHealthSchema as n } from "../contract.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
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
|
|
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
|
|
149
|
-
if (
|
|
150
|
-
let { success:
|
|
151
|
-
if (!
|
|
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,
|
|
154
|
-
let { success:
|
|
155
|
-
if (!
|
|
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 =
|
|
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,
|
|
176
|
-
if (
|
|
177
|
-
let
|
|
178
|
-
if (
|
|
179
|
-
if (
|
|
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
|
|
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
|
|
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,
|
|
193
|
-
let
|
|
194
|
-
if (!
|
|
195
|
-
if (
|
|
196
|
-
let B = matchRoute(
|
|
197
|
-
if (B === null) throw new
|
|
198
|
-
if (!B.methods.includes(
|
|
199
|
-
if (H.has(B.id) && !V.test(B.segments[1] ?? "")) throw
|
|
200
|
-
if (B.id === "health") return withCors(await
|
|
201
|
-
B.id === "reports" &&
|
|
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(
|
|
206
|
-
let U =
|
|
207
|
-
U &&
|
|
208
|
-
let W =
|
|
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(
|
|
210
|
+
if (G) return withCors(f(200, { ok: !0 }, {
|
|
211
211
|
"cache-control": "no-store",
|
|
212
|
-
"set-cookie":
|
|
213
|
-
}), e,
|
|
214
|
-
let t =
|
|
215
|
-
requireExpectedPublisher(e, t, B.id === "me" &&
|
|
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,
|
|
217
|
+
let jsonWithCors = (t) => withCors(t, e, i);
|
|
218
218
|
switch (B.id) {
|
|
219
|
-
case "meta": return
|
|
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 (
|
|
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 (
|
|
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 (
|
|
232
|
-
if (
|
|
233
|
-
if (
|
|
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 (
|
|
239
|
-
if (
|
|
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 (
|
|
243
|
+
if (z !== "POST") throw notAllowed();
|
|
244
244
|
let t = collectionFor(B.segments[0] ?? "");
|
|
245
|
-
return jsonWithCors(await
|
|
245
|
+
return jsonWithCors(await P(t, e, n, a(K), B.segments[1] ?? ""));
|
|
246
246
|
}
|
|
247
247
|
case "hidden": {
|
|
248
|
-
if (
|
|
248
|
+
if (z !== "PUT" && z !== "DELETE") throw notAllowed();
|
|
249
249
|
let e = collectionFor(B.segments[0] ?? "");
|
|
250
|
-
return jsonWithCors(await
|
|
250
|
+
return jsonWithCors(await I(e, n, a(K), B.segments[1] ?? "", z === "PUT"));
|
|
251
251
|
}
|
|
252
252
|
case "reports": {
|
|
253
|
-
let t =
|
|
254
|
-
return requireExpectedPublisher(e, t, !1), jsonWithCors(await
|
|
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
|
|
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 =
|
|
259
|
-
return requireExpectedPublisher(e, t, !1), jsonWithCors(await
|
|
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 (
|
|
263
|
-
return jsonWithCors(await
|
|
264
|
-
case "accountErase": return
|
|
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 (
|
|
267
|
-
if (
|
|
268
|
-
if (
|
|
269
|
-
if (
|
|
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 =
|
|
274
|
+
let r = a(K);
|
|
275
275
|
if (requireExpectedPublisher(e, r, !1), B.id === "mePublications") {
|
|
276
|
-
let e =
|
|
277
|
-
if (e === null) throw
|
|
278
|
-
let
|
|
279
|
-
if (
|
|
280
|
-
return jsonWithCors(await
|
|
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" ?
|
|
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
|
|
288
|
-
if (
|
|
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,
|
|
294
|
-
let a = crypto.randomUUID(), o = routeLabel(new URL(e.url).pathname),
|
|
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
|
|
297
|
-
return
|
|
298
|
-
code:
|
|
299
|
-
ms: Date.now() -
|
|
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:
|
|
302
|
+
status: i.status,
|
|
303
303
|
type: "request",
|
|
304
|
-
...
|
|
305
|
-
}),
|
|
304
|
+
...s === void 0 ? {} : { diagnostic: s }
|
|
305
|
+
}), i;
|
|
306
306
|
} catch (n) {
|
|
307
|
-
let
|
|
307
|
+
let r = g(n, a);
|
|
308
308
|
emit({
|
|
309
|
-
code: n instanceof
|
|
310
|
-
diagnostic: n instanceof
|
|
311
|
-
ms: Date.now() -
|
|
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:
|
|
314
|
+
status: r.status,
|
|
315
315
|
type: "request"
|
|
316
316
|
});
|
|
317
317
|
try {
|
|
318
|
-
return withCors(
|
|
318
|
+
return withCors(r, e, s(t));
|
|
319
319
|
} catch {
|
|
320
|
-
return
|
|
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(
|
|
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
|
-
|
|
344
|
+
u.delete(e), n?.removeEventListener("abort", off);
|
|
345
345
|
};
|
|
346
|
-
return
|
|
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.
|
|
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 &&
|
|
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": "
|
|
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.
|
|
66
|
-
"jose": "6.2.
|
|
65
|
+
"@vielzeug/spell": "26.11.6",
|
|
66
|
+
"jose": "6.2.15"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@types/node": "^26.6.
|
|
69
|
+
"@types/node": "^26.6.6",
|
|
70
70
|
"typescript": "^6.0.3",
|
|
71
|
-
"vite": "^8.3.
|
|
72
|
-
"vitest": "^5.0
|
|
73
|
-
"wrangler": "4.
|
|
71
|
+
"vite": "^8.3.6",
|
|
72
|
+
"vitest": "^5.1.0",
|
|
73
|
+
"wrangler": "4.158.0"
|
|
74
74
|
}
|
|
75
75
|
}
|