@vielzeug/archive 26.11.12 → 26.11.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/_server/d1.d.ts +23 -0
  2. package/dist/_server/d1.d.ts.map +1 -0
  3. package/dist/_server/execution.d.ts +5 -0
  4. package/dist/_server/execution.d.ts.map +1 -0
  5. package/dist/_server/http.cjs +2 -0
  6. package/dist/_server/http.cjs.map +1 -0
  7. package/dist/{worker → _server}/http.d.ts +6 -11
  8. package/dist/_server/http.d.ts.map +1 -0
  9. package/dist/{worker → _server}/http.js +18 -44
  10. package/dist/_server/http.js.map +1 -0
  11. package/dist/{worker/_service.cjs → _server/service.cjs} +1 -1
  12. package/dist/{worker/_service.js.map → _server/service.cjs.map} +1 -1
  13. package/dist/{worker/_service.d.ts → _server/service.d.ts} +1 -1
  14. package/dist/_server/service.d.ts.map +1 -0
  15. package/dist/{worker/_service.js → _server/service.js} +2 -2
  16. package/dist/_server/service.js.map +1 -0
  17. package/dist/_server/uuid.cjs +2 -0
  18. package/dist/_server/uuid.cjs.map +1 -0
  19. package/dist/_server/uuid.d.ts +3 -0
  20. package/dist/_server/uuid.d.ts.map +1 -0
  21. package/dist/_server/uuid.js +5 -0
  22. package/dist/_server/uuid.js.map +1 -0
  23. package/dist/auth/_confirmation.cjs +3 -3
  24. package/dist/auth/_confirmation.cjs.map +1 -1
  25. package/dist/auth/_confirmation.js +4 -4
  26. package/dist/auth/_confirmation.js.map +1 -1
  27. package/dist/auth/config.cjs.map +1 -1
  28. package/dist/auth/config.d.ts +1 -1
  29. package/dist/auth/config.js.map +1 -1
  30. package/dist/auth/errors.cjs +1 -1
  31. package/dist/auth/errors.cjs.map +1 -1
  32. package/dist/auth/errors.d.ts +1 -1
  33. package/dist/auth/errors.d.ts.map +1 -1
  34. package/dist/auth/errors.js +1 -1
  35. package/dist/auth/errors.js.map +1 -1
  36. package/dist/auth/store.cjs.map +1 -1
  37. package/dist/auth/store.d.ts +1 -1
  38. package/dist/auth/store.js.map +1 -1
  39. package/dist/auth/worker.cjs +1 -1
  40. package/dist/auth/worker.cjs.map +1 -1
  41. package/dist/auth/worker.d.ts +1 -1
  42. package/dist/auth/worker.d.ts.map +1 -1
  43. package/dist/auth/worker.js +153 -152
  44. package/dist/auth/worker.js.map +1 -1
  45. package/dist/errors.cjs +1 -1
  46. package/dist/errors.cjs.map +1 -1
  47. package/dist/errors.d.ts.map +1 -1
  48. package/dist/errors.js +1 -1
  49. package/dist/errors.js.map +1 -1
  50. package/dist/worker/_cors.cjs +2 -0
  51. package/dist/worker/_cors.cjs.map +1 -0
  52. package/dist/worker/_cors.d.ts +13 -0
  53. package/dist/worker/_cors.d.ts.map +1 -0
  54. package/dist/worker/_cors.js +32 -0
  55. package/dist/worker/_cors.js.map +1 -0
  56. package/dist/worker/_pagination.cjs +1 -1
  57. package/dist/worker/_pagination.cjs.map +1 -1
  58. package/dist/worker/_pagination.js +18 -18
  59. package/dist/worker/_pagination.js.map +1 -1
  60. package/dist/worker/auth.cjs +1 -1
  61. package/dist/worker/auth.cjs.map +1 -1
  62. package/dist/worker/auth.d.ts.map +1 -1
  63. package/dist/worker/auth.js +18 -18
  64. package/dist/worker/auth.js.map +1 -1
  65. package/dist/worker/env.cjs +1 -1
  66. package/dist/worker/env.cjs.map +1 -1
  67. package/dist/worker/env.d.ts +1 -22
  68. package/dist/worker/env.d.ts.map +1 -1
  69. package/dist/worker/env.js +1 -1
  70. package/dist/worker/env.js.map +1 -1
  71. package/dist/worker/publications.cjs +1 -1
  72. package/dist/worker/publications.cjs.map +1 -1
  73. package/dist/worker/publications.d.ts.map +1 -1
  74. package/dist/worker/publications.js +263 -262
  75. package/dist/worker/publications.js.map +1 -1
  76. package/dist/worker/worker.cjs +1 -1
  77. package/dist/worker/worker.cjs.map +1 -1
  78. package/dist/worker/worker.d.ts +2 -4
  79. package/dist/worker/worker.d.ts.map +1 -1
  80. package/dist/worker/worker.js +124 -122
  81. package/dist/worker/worker.js.map +1 -1
  82. package/dist/worker.d.ts +5 -3
  83. package/dist/worker.d.ts.map +1 -1
  84. package/package.json +2 -2
  85. package/dist/worker/_service.cjs.map +0 -1
  86. package/dist/worker/_service.d.ts.map +0 -1
  87. package/dist/worker/http.cjs +0 -2
  88. package/dist/worker/http.cjs.map +0 -1
  89. package/dist/worker/http.d.ts.map +0 -1
  90. package/dist/worker/http.js.map +0 -1
@@ -1,12 +1,14 @@
1
- import { ARCHIVE_LIMITS as e, PUBLISHER_HEADER as t, archiveHealthSchema as n } from "../contract.js";
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";
1
+ import { ApiError as e, badRequest as t, expiredSessionCookie as n, json as r, queryParams as i, toErrorResponse as a } from "../_server/http.js";
2
+ import { UUID_RE as o } from "../_server/uuid.js";
3
+ import { ARCHIVE_LIMITS as s, PUBLISHER_HEADER as c, archiveHealthSchema as l } from "../contract.js";
4
+ import { ArchiveDefinitionError as u, ArchiveDerivationError as d } from "../errors.js";
5
+ import { corsHeaders as f, preflight as p, requireMutationGuard as m } from "./_cors.js";
6
+ import { requireSession as h, verifySession as g } from "./auth.js";
7
+ import { apiConfig as _ } from "./env.js";
8
+ 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";
9
+ import { SQL as B } from "./sql.js";
8
10
  // src/worker/worker.ts
9
- var B = [
11
+ var V = [
10
12
  {
11
13
  id: "accountErase",
12
14
  label: "/me/account",
@@ -119,7 +121,7 @@ var B = [
119
121
  pattern: /^\/api\/v1\/me\/reports$/
120
122
  }
121
123
  ], matchRoute = (e) => {
122
- for (let t of B) {
124
+ for (let t of V) {
123
125
  let n = e.match(t.pattern);
124
126
  if (n) return {
125
127
  id: t.id,
@@ -129,13 +131,13 @@ var B = [
129
131
  };
130
132
  }
131
133
  return null;
132
- }, routeLabel = (e) => matchRoute(e)?.label ?? "unknown", V = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, H = /* @__PURE__ */ new Set([
134
+ }, routeLabel = (e) => matchRoute(e)?.label ?? "unknown", H = /* @__PURE__ */ new Set([
133
135
  "entry",
134
136
  "like",
135
137
  "report",
136
138
  "hidden",
137
139
  "resolveReport"
138
- ]), notAllowed = () => new c(405, "method-not-allowed", "That method is not supported here."), U = /* @__PURE__ */ new WeakMap(), rateLimitHash = async (e, t) => {
140
+ ]), notAllowed = () => new e(405, "method-not-allowed", "That method is not supported here."), U = /* @__PURE__ */ new WeakMap(), rateLimitHash = async (e, t) => {
139
141
  let n = U.get(e);
140
142
  n === void 0 && (n = crypto.subtle.importKey("raw", new TextEncoder().encode(e.rateLimitPepper), {
141
143
  hash: "SHA-256",
@@ -144,186 +146,186 @@ var B = [
144
146
  let r = await crypto.subtle.sign("HMAC", await n, new TextEncoder().encode(t));
145
147
  return [...new Uint8Array(r)].map((e) => e.toString(16).padStart(2, "0")).join("");
146
148
  }, W = 60;
147
- async function readLimit(e, t, n) {
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 });
149
+ async function readLimit(t, n, r) {
150
+ let i = r.headers.get("cf-connecting-ip") ?? (t.appEnv === "local" ? "127.0.0.1" : null);
151
+ if (i === null) throw new e(503, "unavailable", "The service is temporarily unavailable.");
152
+ let { success: a } = await n.READ_LIMIT.limit({ key: await rateLimitHash(t, i) });
153
+ if (!a) throw new e(429, "rate-limited", "Too many requests. Try again later.", { retryAfterSeconds: W });
152
154
  }
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 });
155
+ async function writeLimit(t, n, r, i) {
156
+ let { success: a } = await n.WRITE_LIMIT.limit({ key: await rateLimitHash(t, JSON.stringify([r, i])) });
157
+ if (!a) throw new e(429, "rate-limited", "Too many requests. Try again later.", { retryAfterSeconds: W });
156
158
  }
157
- async function cachedPublicGet(t, n, r, i) {
158
- if (t.headers.get("cookie") !== null || t.headers.get("authorization") !== null) return r();
159
- let a = new URL(t.url);
160
- a.search = new URLSearchParams([...a.searchParams.entries()].sort(([e], [t]) => e < t ? -1 : +(e > t))).toString();
161
- let o = caches.default, s = new Request(a.toString(), { method: "GET" }), c = await o.match(s).catch(() => {
162
- i();
159
+ async function cachedPublicGet(e, t, n, r) {
160
+ if (e.headers.get("cookie") !== null || e.headers.get("authorization") !== null) return n();
161
+ let i = new URL(e.url);
162
+ i.search = new URLSearchParams([...i.searchParams.entries()].sort(([e], [t]) => e < t ? -1 : +(e > t))).toString();
163
+ let a = caches.default, o = new Request(i.toString(), { method: "GET" }), c = await a.match(o).catch(() => {
164
+ r();
163
165
  });
164
166
  if (c) return c;
165
- let l = await r();
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
+ let l = await n();
168
+ return l.headers.set("cache-control", `public, max-age=${s.publicCacheSeconds}, s-maxage=${s.publicCacheSeconds}`), t.waitUntil(a.put(o, l.clone()).catch(r)), l;
167
169
  }
168
170
  var withCors = (e, t, n) => {
169
- let r = u(t, n);
171
+ let r = f(t, n);
170
172
  if (Object.keys(r).length === 0) return e;
171
173
  let i = new Response(e.body, e);
172
174
  for (let [e, t] of Object.entries(r)) i.headers.set(e, t);
173
175
  return i;
174
176
  };
175
- function requireExpectedPublisher(e, n, r) {
177
+ function requireExpectedPublisher(t, n, r) {
176
178
  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.");
179
+ let i = t.headers.get(c);
180
+ if (i === null || !o.test(i)) throw new e(400, "bad-request", "The request is not valid.");
181
+ if (i !== n.publisher.id) throw new e(409, "publisher-changed", "The signed-in publisher changed since this request was prepared.");
180
182
  }
181
- function createArchiveWorker(e) {
182
- let t = /* @__PURE__ */ new Map();
183
- for (let n of e) {
184
- if (t.has(n.name)) throw new r(`Duplicate archive collection: ${n.name}`);
185
- t.set(n.name, n);
183
+ function createArchiveWorker(s) {
184
+ let c = /* @__PURE__ */ new Map();
185
+ for (let e of s) {
186
+ if (c.has(e.name)) throw new u(`Duplicate archive collection: ${e.name}`);
187
+ c.set(e.name, e);
186
188
  }
187
- let collectionFor = (e) => {
188
- let n = t.get(e);
189
- if (n === void 0) throw new c(404, "not-found", "No such route.");
189
+ let collectionFor = (t) => {
190
+ let n = c.get(t);
191
+ if (n === void 0) throw new e(404, "not-found", "No such route.");
190
192
  return n;
191
193
  };
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, [
194
+ async function handle(a, s, l) {
195
+ let u = _(s), d = new URL(a.url), { pathname: f } = d, B = a.method;
196
+ if (!f.startsWith("/api/v1/")) throw new e(404, "not-found", "No such route.");
197
+ if (B === "OPTIONS") return p(a, u);
198
+ let V = matchRoute(f);
199
+ if (V === null) throw new e(404, "not-found", "No such route.");
200
+ if (!V.methods.includes(B)) throw notAllowed();
201
+ if (H.has(V.id) && !o.test(V.segments[1] ?? "")) throw t();
202
+ if (V.id === "health") return withCors(await T(u, s), a, u);
203
+ V.id === "reports" && i(d, [
202
204
  "cursor",
203
205
  "limit",
204
206
  "status"
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");
207
+ ]), await readLimit(u, s, a);
208
+ let U = B !== "GET" && B !== "HEAD" && B !== "OPTIONS";
209
+ U && m(a, u);
210
+ let W = B === "GET" && (V.id === "meta" || V.id === "entries" || V.id === "entry"), G = V.id === "signOut" && B === "POST", K = W || G ? null : await g(a, u, s, V.id === "accountErase");
209
211
  if (U) {
210
- if (G) return withCors(f(200, { ok: !0 }, {
212
+ if (G) return withCors(r(200, { ok: !0 }, {
211
213
  "cache-control": "no-store",
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);
214
+ "set-cookie": n(u.appEnv === "production")
215
+ }), a, u);
216
+ let e = h(K);
217
+ requireExpectedPublisher(a, e, V.id === "me" && B === "POST"), await writeLimit(u, s, e.subject.issuer, e.subject.subject);
216
218
  }
217
- let jsonWithCors = (t) => withCors(t, e, i);
218
- switch (B.id) {
219
- case "meta": return m(u, []), jsonWithCors(_(collectionFor(B.segments[0] ?? "")));
219
+ let jsonWithCors = (e) => withCors(e, a, u);
220
+ switch (V.id) {
221
+ case "meta": return i(d, []), jsonWithCors(v(collectionFor(V.segments[0] ?? "")));
220
222
  case "entries": {
221
- let t = collectionFor(B.segments[0] ?? "");
222
- if (z === "GET") return jsonWithCors(await cachedPublicGet(e, r, () => T(t, e, n), () => emit({
223
+ let e = collectionFor(V.segments[0] ?? "");
224
+ if (B === "GET") return jsonWithCors(await cachedPublicGet(a, l, () => E(e, a, s), () => emit({
223
225
  diagnostic: { kind: "cache" },
224
226
  type: "cache-error"
225
227
  })));
226
- if (z === "POST") return jsonWithCors(await j(t, e, n, i, a(K)));
228
+ if (B === "POST") return jsonWithCors(await M(e, a, s, u, h(K)));
227
229
  throw notAllowed();
228
230
  }
229
231
  case "entry": {
230
- let t = collectionFor(B.segments[0] ?? ""), r = B.segments[1] ?? "";
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));
232
+ let e = collectionFor(V.segments[0] ?? ""), t = V.segments[1] ?? "";
233
+ if (B === "GET") return i(d, []), jsonWithCors(await C(e, s, t));
234
+ if (B === "PUT") return jsonWithCors(await z(e, a, s, u, h(K), t));
235
+ if (B === "DELETE") return jsonWithCors(await b(e, s, h(K), t));
234
236
  throw notAllowed();
235
237
  }
236
238
  case "like": {
237
- let e = collectionFor(B.segments[0] ?? ""), t = B.segments[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));
239
+ let e = collectionFor(V.segments[0] ?? ""), t = V.segments[1] ?? "";
240
+ if (B === "PUT") return jsonWithCors(await R(e, s, u, h(K), t, !0));
241
+ if (B === "DELETE") return jsonWithCors(await R(e, s, u, h(K), t, !1));
240
242
  throw notAllowed();
241
243
  }
242
244
  case "report": {
243
- if (z !== "POST") throw notAllowed();
244
- let t = collectionFor(B.segments[0] ?? "");
245
- return jsonWithCors(await P(t, e, n, a(K), B.segments[1] ?? ""));
245
+ if (B !== "POST") throw notAllowed();
246
+ let e = collectionFor(V.segments[0] ?? "");
247
+ return jsonWithCors(await F(e, a, s, h(K), V.segments[1] ?? ""));
246
248
  }
247
249
  case "hidden": {
248
- if (z !== "PUT" && z !== "DELETE") throw notAllowed();
249
- let e = collectionFor(B.segments[0] ?? "");
250
- return jsonWithCors(await I(e, n, a(K), B.segments[1] ?? "", z === "PUT"));
250
+ if (B !== "PUT" && B !== "DELETE") throw notAllowed();
251
+ let e = collectionFor(V.segments[0] ?? "");
252
+ return jsonWithCors(await L(e, s, h(K), V.segments[1] ?? "", B === "PUT"));
251
253
  }
252
254
  case "reports": {
253
- let t = a(K);
254
- return requireExpectedPublisher(e, t, !1), jsonWithCors(await D(collectionFor(B.segments[0] ?? ""), e, n, t));
255
+ let e = h(K);
256
+ return requireExpectedPublisher(a, e, !1), jsonWithCors(await O(collectionFor(V.segments[0] ?? ""), a, s, e));
255
257
  }
256
- case "resolveReport": return m(u, []), jsonWithCors(await F(collectionFor(B.segments[0] ?? ""), n, a(K), B.segments[1] ?? ""));
258
+ case "resolveReport": return i(d, []), jsonWithCors(await I(collectionFor(V.segments[0] ?? ""), s, h(K), V.segments[1] ?? ""));
257
259
  case "modLog": {
258
- let t = a(K);
259
- return requireExpectedPublisher(e, t, !1), jsonWithCors(await E(e, n, t));
260
+ let e = h(K);
261
+ return requireExpectedPublisher(a, e, !1), jsonWithCors(await D(a, s, e));
260
262
  }
261
263
  case "publisherErase":
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)));
264
+ if (B !== "DELETE") throw notAllowed();
265
+ return jsonWithCors(await S(s, h(K), V.segments[0] ?? ""));
266
+ case "accountErase": return i(d, []), jsonWithCors(await y(s, h(K)));
265
267
  case "me":
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)));
268
+ if (B === "GET") return jsonWithCors(w(K));
269
+ if (B === "POST") return jsonWithCors(await N(a, s, h(K)));
270
+ if (B === "PUT") return jsonWithCors(await P(a, s, h(K)));
271
+ if (B === "DELETE") return jsonWithCors(await x(s, h(K)));
270
272
  throw notAllowed();
271
273
  case "mePublications":
272
274
  case "meLikes":
273
275
  case "meReports": {
274
- let r = a(K);
275
- if (requireExpectedPublisher(e, r, !1), B.id === "mePublications") {
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));
276
+ let e = h(K);
277
+ if (requireExpectedPublisher(a, e, !1), V.id === "mePublications") {
278
+ let n = i(d, ["collection"]).get("collection");
279
+ if (n === null) throw t();
280
+ let r = c.get(n);
281
+ if (r === void 0) throw t();
282
+ return jsonWithCors(await A(r, s, e));
281
283
  }
282
- return jsonWithCors(await (B.id === "meReports" ? A(e, n, r) : O(e, n, r)));
284
+ return jsonWithCors(await (V.id === "meReports" ? j(a, s, e) : k(a, s, e)));
283
285
  }
284
286
  default: throw notAllowed();
285
287
  }
286
288
  }
287
- let u = /* @__PURE__ */ new Set(), emit = (e) => {
288
- if (u.size !== 0) for (let t of u) try {
289
+ let f = /* @__PURE__ */ new Set(), emit = (e) => {
290
+ if (f.size !== 0) for (let t of f) try {
289
291
  t(e);
290
292
  } catch {}
291
293
  };
292
294
  return {
293
- async fetch(e, t, r) {
294
- let a = crypto.randomUUID(), o = routeLabel(new URL(e.url).pathname), l = Date.now();
295
+ async fetch(t, n, r) {
296
+ let i = crypto.randomUUID(), o = routeLabel(new URL(t.url).pathname), s = Date.now();
295
297
  try {
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
- requestId: a,
298
+ let e = await handle(t, n, r), a;
299
+ return e.status === 503 && o === "/health" && (a = { kind: l.parse(await e.clone().json()).reason === "database-unavailable" ? "database" : "schema" }), emit({
300
+ code: e.ok ? "ok" : "unavailable",
301
+ ms: Date.now() - s,
302
+ requestId: i,
301
303
  route: o,
302
- status: i.status,
304
+ status: e.status,
303
305
  type: "request",
304
- ...s === void 0 ? {} : { diagnostic: s }
305
- }), i;
306
- } catch (n) {
307
- let r = g(n, a);
306
+ ...a === void 0 ? {} : { diagnostic: a }
307
+ }), e;
308
+ } catch (r) {
309
+ let c = a(r, i);
308
310
  emit({
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
- requestId: a,
311
+ code: r instanceof e ? r.code : "internal",
312
+ diagnostic: r instanceof e ? r.diagnostic : { kind: r instanceof d ? "collection" : "internal" },
313
+ ms: Date.now() - s,
314
+ requestId: i,
313
315
  route: o,
314
- status: r.status,
316
+ status: c.status,
315
317
  type: "request"
316
318
  });
317
319
  try {
318
- return withCors(r, e, s(t));
320
+ return withCors(c, t, _(n));
319
321
  } catch {
320
- return r;
322
+ return c;
321
323
  }
322
324
  }
323
325
  },
324
326
  async scheduled(e, t, n) {
325
327
  try {
326
- let e = Date.now(), n = await t.DB.batch([t.DB.prepare(z.expireRequests).bind(e), t.DB.prepare(z.expireRevocations).bind(e)]);
328
+ let e = Date.now(), n = await t.DB.batch([t.DB.prepare(B.expireRequests).bind(e), t.DB.prepare(B.expireRevocations).bind(e)]);
327
329
  emit({
328
330
  removed: n.reduce((e, t) => e + (t.meta?.changes ?? 0), 0),
329
331
  status: "ok",
@@ -341,9 +343,9 @@ function createArchiveWorker(e) {
341
343
  let n = t?.signal;
342
344
  if (n?.aborted) return () => {};
343
345
  let off = () => {
344
- u.delete(e), n?.removeEventListener("abort", off);
346
+ f.delete(e), n?.removeEventListener("abort", off);
345
347
  };
346
- return u.add(e), n?.addEventListener("abort", off, { once: !0 }), off;
348
+ return f.add(e), n?.addEventListener("abort", off, { once: !0 }), off;
347
349
  }
348
350
  };
349
351
  }
@@ -1 +1 @@
1
- {"version":3,"file":"worker.js","names":["ROUTES","route","found","pathname","UUID_PATH","ID_SEGMENT_ROUTES","ApiError","hmacKeys","key","config","mac","text","b","RATE_LIMIT_RETRY_AFTER_SECONDS","ip","request","env","success","issuer","subject","produce","url","a","edgeCache","cached","response","ARCHIVE_LIMITS","ctx","onFailure","headers","corsHeaders","next","value","exempt","session","expected","PUBLISHER_HEADER","registered","collection","collections","ArchiveDefinitionError","segment","apiConfig","method","preflight","match","badRequest","healthJson","queryParams","isMutation","requireMutationGuard","isPublicGet","isSignOut","verifySession","json","expiredSessionCookie","verified","requireSession","collectionMeta","listEntries","publishEntry","id","getEntry","updateEntry","deleteEntry","setLike","reportEntry","setHidden","listReports","resolveReport","listModerationLog","erasePublisher","deleteAccountProfile","getMe","registerMe","renameMe","deleteProfile","requested","myPublications","myReports","myLikes","tappers","tapper","event","requestId","startedAt","diagnostic","archiveHealthSchema","error","toErrorResponse","ArchiveDerivationError","timestamp","results","SQL","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.js","names":["ROUTES","route","found","pathname","ID_SEGMENT_ROUTES","ApiError","hmacKeys","key","config","mac","text","b","RATE_LIMIT_RETRY_AFTER_SECONDS","ip","request","env","success","issuer","subject","produce","url","a","edgeCache","cached","response","ARCHIVE_LIMITS","ctx","onFailure","headers","corsHeaders","next","value","exempt","session","expected","PUBLISHER_HEADER","UUID_RE","registered","collection","collections","ArchiveDefinitionError","segment","apiConfig","method","preflight","match","badRequest","healthJson","queryParams","isMutation","requireMutationGuard","isPublicGet","isSignOut","verifySession","json","expiredSessionCookie","verified","requireSession","collectionMeta","listEntries","publishEntry","id","getEntry","updateEntry","deleteEntry","setLike","reportEntry","setHidden","listReports","resolveReport","listModerationLog","erasePublisher","deleteAccountProfile","getMe","registerMe","renameMe","deleteProfile","requested","myPublications","myReports","myLikes","tappers","tapper","event","requestId","startedAt","diagnostic","archiveHealthSchema","error","toErrorResponse","ArchiveDerivationError","timestamp","results","SQL","sum","result","signal","options","handler"],"sources":["../../src/worker/worker.ts"],"sourcesContent":["import type { ArchiveExecutionContext } from '../_server/execution';\nimport {\n ApiError,\n type ArchiveDiagnostic,\n badRequest,\n expiredSessionCookie,\n json,\n queryParams,\n toErrorResponse,\n} from '../_server/http';\nimport { UUID_RE } from '../_server/uuid';\nimport type { ArchiveCollection } from '../collection';\nimport { ARCHIVE_LIMITS, type ArchiveErrorCode, archiveHealthSchema, PUBLISHER_HEADER } from '../contract';\nimport { ArchiveDefinitionError, ArchiveDerivationError } from '../errors';\nimport { corsHeaders, preflight, requireMutationGuard } from './_cors';\nimport { requireSession, type Session, verifySession } from './auth';\nimport { type ApiConfig, type ArchiveWorkerEnv, apiConfig } from './env';\nimport {\n collectionMeta,\n deleteAccountProfile,\n deleteEntry,\n deleteProfile,\n erasePublisher,\n getEntry,\n getMe,\n healthJson,\n listEntries,\n listModerationLog,\n listReports,\n myLikes,\n myPublications,\n myReports,\n publishEntry,\n registerMe,\n renameMe,\n reportEntry,\n resolveReport,\n setHidden,\n setLike,\n updateEntry,\n} from './publications';\nimport { SQL } from './sql';\n\n/**\n * The community-archive Worker factory: anonymous public reads, cookie-authenticated\n * mutations behind strict Origin/request-marker guards, IP and subject rate limits,\n * operator moderation over the registered collections, and a daily expiry sweep for\n * retired publish retry keys.\n *\n * Trust model: configuration and signing keys come only from validated Env bindings;\n * the session cookie is the single credential; every response is either no-store\n * (private/error) or a 30s anonymous public cache entry. Runtime observability\n * flows exclusively through `tap()` — the core itself never logs.\n */\n\n/** The minimal execution-context surface the core uses; the runtime's satisfies it. */\n/** One sanitized request-completion event: ids, codes, route classes and duration only. */\nexport interface ArchiveRequestEvent {\n /** 'ok' on success; otherwise the route's `ARCHIVE_ERROR_CODES` value. */\n code: ArchiveErrorCode | 'ok';\n diagnostic?: ArchiveDiagnostic;\n /** Whole-request wall-clock duration in milliseconds. */\n ms: number;\n requestId: string;\n /** A finite route class like `/collections/:collection/entries`, never the raw path. */\n route: string;\n status: number;\n type: 'request';\n}\n\n/** The daily retry-key sweep, observable like every other runtime behavior. */\nexport type ArchiveScheduledEvent =\n | {\n /** Retired retry-key rows this run removed. */\n removed: number;\n type: 'scheduled';\n status: 'ok';\n }\n | { type: 'scheduled'; status: 'failed'; diagnostic: ArchiveDiagnostic };\n\nexport type ArchiveEvent =\n | ArchiveRequestEvent\n | ArchiveScheduledEvent\n | { readonly type: 'cache-error'; readonly diagnostic: ArchiveDiagnostic };\n\n/**\n * The Worker's exported-handler shape plus its observability side channel. Declared\n * structurally so the package needs no `@cloudflare/workers-types`: a deployment's\n * generated `ExportedHandler<Env>` accepts the factory's result as-is.\n */\nexport interface ArchiveWorkerHandler<E extends ArchiveWorkerEnv = ArchiveWorkerEnv> {\n fetch(request: Request, env: E, ctx: ArchiveExecutionContext): Promise<Response>;\n scheduled?(controller: unknown, env: E, ctx: ArchiveExecutionContext): void | Promise<void>;\n /**\n * Observes every request's outcome and every scheduled sweep. The handler owns\n * logging policy (console, a structured logger, or nothing); handler errors are\n * swallowed so observability can never affect the Worker, and with no handlers\n * registered there is zero work.\n */\n tap(handler: (event: ArchiveEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;\n}\n\n/** One dispatchable route: the id both the switch and the event labels derive from. */\ntype RouteId =\n | 'health'\n | 'signOut'\n | 'meta'\n | 'reports'\n | 'resolveReport'\n | 'entries'\n | 'entry'\n | 'like'\n | 'report'\n | 'hidden'\n | 'me'\n | 'accountErase'\n | 'mePublications'\n | 'meLikes'\n | 'meReports'\n | 'modLog'\n | 'publisherErase';\n\n/** One matched route: its id (for dispatch) and label (for events), plus captures. */\ninterface RouteMatch {\n id: RouteId;\n /** The finite event label, never the raw path. */\n label: string;\n /** The methods the route serves; anything else is 405 before any guard runs. */\n methods: readonly string[];\n /** The path's captured segments, in pattern order: collection first, then id. */\n segments: readonly string[];\n}\n\n/**\n * The one route table: dispatch, methods, and event labels all read from here, so a\n * new route can never miss its observability class or its method set. Methods are\n * enforced before any guard runs — a wrong-method request never spends rate-limit\n * budget, verifies a session, or answers an auth-shaped error.\n */\nconst ROUTES: readonly { id: RouteId; label: string; methods: readonly string[]; pattern: RegExp }[] = [\n { id: 'accountErase', label: '/me/account', methods: ['DELETE'], pattern: /^\\/api\\/v1\\/me\\/account$/ },\n { id: 'health', label: '/health', methods: ['GET'], pattern: /^\\/api\\/v1\\/health$/ },\n { id: 'signOut', label: '/sign-out', methods: ['POST'], pattern: /^\\/api\\/v1\\/sign-out$/ },\n {\n id: 'resolveReport',\n label: '/collections/:collection/reports/:id/resolved',\n methods: ['PUT'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/reports\\/([0-9a-f-]{36})\\/resolved$/i,\n },\n {\n id: 'meta',\n label: '/collections/:collection',\n methods: ['GET'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)$/,\n },\n {\n id: 'reports',\n label: '/collections/:collection/reports',\n methods: ['GET'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/reports$/,\n },\n {\n id: 'entries',\n label: '/collections/:collection/entries',\n methods: ['GET', 'POST'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries$/,\n },\n {\n id: 'entry',\n label: '/collections/:collection/entries/:id',\n methods: ['GET', 'PUT', 'DELETE'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})$/i,\n },\n {\n id: 'like',\n label: '/collections/:collection/entries/:id/like',\n methods: ['PUT', 'DELETE'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})\\/like$/i,\n },\n {\n id: 'report',\n label: '/collections/:collection/entries/:id/report',\n methods: ['POST'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})\\/report$/i,\n },\n {\n id: 'hidden',\n label: '/collections/:collection/entries/:id/hidden',\n methods: ['PUT', 'DELETE'],\n pattern: /^\\/api\\/v1\\/collections\\/([a-z0-9-]+)\\/entries\\/([0-9a-f-]{36})\\/hidden$/i,\n },\n {\n id: 'modLog',\n label: '/moderation-log',\n methods: ['GET'],\n pattern: /^\\/api\\/v1\\/moderation-log$/,\n },\n {\n id: 'publisherErase',\n label: '/publishers/:id',\n methods: ['DELETE'],\n pattern: /^\\/api\\/v1\\/publishers\\/([0-9a-f-]{36})$/i,\n },\n { id: 'me', label: '/me', methods: ['GET', 'POST', 'PUT', 'DELETE'], pattern: /^\\/api\\/v1\\/me$/ },\n { id: 'mePublications', label: '/me/publications', methods: ['GET'], pattern: /^\\/api\\/v1\\/me\\/publications$/ },\n { id: 'meLikes', label: '/me/likes', methods: ['GET'], pattern: /^\\/api\\/v1\\/me\\/likes$/ },\n { id: 'meReports', label: '/me/reports', methods: ['GET'], pattern: /^\\/api\\/v1\\/me\\/reports$/ },\n];\n\nconst matchRoute = (pathname: string): RouteMatch | null => {\n for (const route of ROUTES) {\n const found = pathname.match(route.pattern);\n if (found) return { id: route.id, label: route.label, methods: route.methods, segments: found.slice(1) };\n }\n return null;\n};\n\n/** The matched route's event label; unmatched paths stay 'unknown', never the raw path. */\nconst routeLabel = (pathname: string): string => matchRoute(pathname)?.label ?? 'unknown';\n\n/** The routes whose second path segment is an entry id: they share the one id rule. */\nconst ID_SEGMENT_ROUTES: ReadonlySet<RouteId> = new Set(['entry', 'like', 'report', 'hidden', 'resolveReport']);\n\nconst notAllowed = (): ApiError => new ApiError(405, 'method-not-allowed', 'That method is not supported here.');\n\nconst hmacKeys = new WeakMap<ApiConfig, Promise<CryptoKey>>();\n/** Peppered HMAC: rate-limit keys cannot be reversed to IPs without the deployment secret. */\nconst rateLimitHash = async (config: ApiConfig, text: string): Promise<string> => {\n let key = hmacKeys.get(config);\n if (key === undefined) {\n key = crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(config.rateLimitPepper),\n { hash: 'SHA-256', name: 'HMAC' },\n false,\n ['sign'],\n );\n hmacKeys.set(config, key);\n }\n const mac = await crypto.subtle.sign('HMAC', await key, new TextEncoder().encode(text));\n return [...new Uint8Array(mac)].map((b) => b.toString(16).padStart(2, '0')).join('');\n};\n\n/**\n * The Retry-After the 429s carry. The platform limiter does not expose its period at\n * runtime, so deployments must configure 60-second windows — a longer window would\n * make the header lie.\n */\nconst RATE_LIMIT_RETRY_AFTER_SECONDS = 60;\n\n/** The connecting IP, peppered-HMAC'd, keys the read limit; loopback only when APP_ENV=local. */\nasync function readLimit(config: ApiConfig, env: ArchiveWorkerEnv, request: Request): Promise<void> {\n const ip = request.headers.get('cf-connecting-ip') ?? (config.appEnv === 'local' ? '127.0.0.1' : null);\n if (ip === null) throw new ApiError(503, 'unavailable', 'The service is temporarily unavailable.');\n const { success } = await env.READ_LIMIT.limit({ key: await rateLimitHash(config, ip) });\n if (!success) {\n throw new ApiError(429, 'rate-limited', 'Too many requests. Try again later.', {\n retryAfterSeconds: RATE_LIMIT_RETRY_AFTER_SECONDS,\n });\n }\n}\n\n/** The verified issuer+subject, peppered-HMAC'd, keys the write limit ahead of every mutation. */\nasync function writeLimit(config: ApiConfig, env: ArchiveWorkerEnv, issuer: string, subject: string): Promise<void> {\n const { success } = await env.WRITE_LIMIT.limit({\n key: await rateLimitHash(config, JSON.stringify([issuer, subject])),\n });\n if (!success) {\n throw new ApiError(429, 'rate-limited', 'Too many requests. Try again later.', {\n retryAfterSeconds: RATE_LIMIT_RETRY_AFTER_SECONDS,\n });\n }\n}\n\n/** Only anonymous list reads may serve from the edge cache. */\nasync function cachedPublicGet(\n request: Request,\n ctx: ArchiveExecutionContext,\n produce: () => Promise<Response>,\n onFailure: () => void,\n): Promise<Response> {\n const anonymous = request.headers.get('cookie') === null && request.headers.get('authorization') === null;\n if (!anonymous) return produce();\n const url = new URL(request.url);\n url.search = new URLSearchParams(\n [...url.searchParams.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n ).toString();\n const edgeCache = caches.default;\n const key = new Request(url.toString(), { method: 'GET' });\n const cached = await edgeCache.match(key).catch(() => {\n onFailure();\n return undefined;\n });\n if (cached) return cached;\n const response = await produce();\n response.headers.set(\n 'cache-control',\n `public, max-age=${ARCHIVE_LIMITS.publicCacheSeconds}, s-maxage=${ARCHIVE_LIMITS.publicCacheSeconds}`,\n );\n ctx.waitUntil(edgeCache.put(key, response.clone()).catch(onFailure));\n return response;\n}\n\nconst withCors = (response: Response, request: Request, config: ApiConfig): Response => {\n const headers = corsHeaders(request, config);\n if (Object.keys(headers).length === 0) return response;\n const next = new Response(response.body, response);\n for (const [key, value] of Object.entries(headers)) next.headers.set(key, value);\n return next;\n};\n\n/**\n * Fail-closed precondition for registered publishers: the UI pins the opaque\n * publisher UUID it believes the session belongs to, so a queued write from one\n * tab can never land under another account that took over the shared cookie.\n * The header is never an identity source — every check still resolves the\n * publisher from the verified JWT — it only vetoes mismatched writes.\n */\nfunction requireExpectedPublisher(request: Request, session: Session, exempt: boolean): void {\n if (exempt || session.publisher === null) return;\n const expected = request.headers.get(PUBLISHER_HEADER);\n if (expected === null || !UUID_RE.test(expected)) {\n throw new ApiError(400, 'bad-request', 'The request is not valid.');\n }\n if (expected !== session.publisher.id) {\n throw new ApiError(409, 'publisher-changed', 'The signed-in publisher changed since this request was prepared.');\n }\n}\n\n/**\n * Builds the Worker for one deployment's registered collections. Each collection owns\n * one URL + storage dimension; a duplicate registration is a deployment bug and\n * fails fast.\n */\nexport function createArchiveWorker<E extends ArchiveWorkerEnv>(\n collections: readonly ArchiveCollection[],\n): ArchiveWorkerHandler<E> {\n const registered = new Map<string, ArchiveCollection>();\n for (const collection of collections) {\n if (registered.has(collection.name))\n throw new ArchiveDefinitionError(`Duplicate archive collection: ${collection.name}`);\n registered.set(collection.name, collection);\n }\n\n /** Resolves the route's collection segment; an unregistered collection is a 404, not a 500. */\n const collectionFor = (segment: string): ArchiveCollection => {\n const collection = registered.get(segment);\n if (collection === undefined) throw new ApiError(404, 'not-found', 'No such route.');\n return collection;\n };\n\n async function handle(request: Request, env: E, ctx: ArchiveExecutionContext): Promise<Response> {\n const config = apiConfig(env);\n const url = new URL(request.url);\n const { pathname } = url;\n const method = request.method;\n\n if (!pathname.startsWith('/api/v1/')) throw new ApiError(404, 'not-found', 'No such route.');\n if (method === 'OPTIONS') return preflight(request, config);\n\n const match = matchRoute(pathname);\n // An unknown path inside the mount answers 404 like the paths outside it, and a\n // known route under an unsupported method answers 405 — both with no side\n // effects: no rate-limit spend, no session verification, no auth state leaked,\n // no guard-shaped error for a method the route does not serve.\n if (match === null) throw new ApiError(404, 'not-found', 'No such route.');\n if (!match.methods.includes(method)) throw notAllowed();\n\n // A malformed entry id is the same class of client bug: it answers 400 before\n // any rate-limit spend, session verification or auth state.\n if (ID_SEGMENT_ROUTES.has(match.id) && !UUID_RE.test(match.segments[1] ?? '')) throw badRequest();\n\n // Health sits deliberately outside the rate-limit and session pipelines: uptime\n // probes must never burn the anonymous read budget or couple to authentication,\n // and it touches the database exactly once (the schema-version read).\n if (match.id === 'health') {\n return withCors(await healthJson(config, env), request, config);\n }\n if (match.id === 'reports') queryParams(url, ['cursor', 'limit', 'status']);\n\n await readLimit(config, env, request);\n\n // The same-origin mutation contract is proven BEFORE any identity work: a\n // foreign-origin write or missing marker must fail with 403 even while carrying a\n // garbage token — JWKS/JWT cost and 401 semantics stay behind the origin fence.\n const isMutation = method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS';\n if (isMutation) requireMutationGuard(request, config);\n\n // Session verification runs only where identity matters: private routes, the\n // moderation surfaces and every mutation. Public reads (list, detail, meta) never\n // invoke JWKS or the publisher lookup — browsing must not couple to authentication\n // availability. Sign-out is a cookie-clearing reply, not a session operation.\n const isPublicGet = method === 'GET' && (match.id === 'meta' || match.id === 'entries' || match.id === 'entry');\n const isSignOut = match.id === 'signOut' && method === 'POST';\n const session =\n isPublicGet || isSignOut ? null : await verifySession(request, config, env, match.id === 'accountErase');\n\n if (isMutation) {\n if (isSignOut) {\n // The shared `archiveOkSchema` body; every session cookie attribute is the\n // contract's, never ad hoc.\n const response = json(\n 200,\n { ok: true },\n {\n 'cache-control': 'no-store',\n 'set-cookie': expiredSessionCookie(config.appEnv === 'production'),\n },\n );\n return withCors(response, request, config);\n }\n const verified = requireSession(session);\n // The free fail-closed header precondition runs before the quota token is spent:\n // a stale tab's rejected writes must not consume the current account's budget.\n requireExpectedPublisher(request, verified, match.id === 'me' && method === 'POST');\n await writeLimit(config, env, verified.subject.issuer, verified.subject.subject);\n }\n\n const jsonWithCors = (response: Response) => withCors(response, request, config);\n\n switch (match.id) {\n case 'meta': {\n queryParams(url, []);\n return jsonWithCors(collectionMeta(collectionFor(match.segments[0] ?? '')));\n }\n case 'entries': {\n const collection = collectionFor(match.segments[0] ?? '');\n if (method === 'GET') {\n const response = await cachedPublicGet(\n request,\n ctx,\n () => listEntries(collection, request, env),\n () => emit({ diagnostic: { kind: 'cache' }, type: 'cache-error' }),\n );\n return jsonWithCors(response);\n }\n if (method === 'POST') {\n return jsonWithCors(await publishEntry(collection, request, env, config, requireSession(session)));\n }\n throw notAllowed();\n }\n case 'entry': {\n const collection = collectionFor(match.segments[0] ?? '');\n const id = match.segments[1] ?? '';\n if (method === 'GET') {\n queryParams(url, []);\n return jsonWithCors(await getEntry(collection, env, id));\n }\n if (method === 'PUT') {\n return jsonWithCors(await updateEntry(collection, request, env, config, requireSession(session), id));\n }\n if (method === 'DELETE') {\n return jsonWithCors(await deleteEntry(collection, env, requireSession(session), id));\n }\n throw notAllowed();\n }\n case 'like': {\n const collection = collectionFor(match.segments[0] ?? '');\n const id = match.segments[1] ?? '';\n if (method === 'PUT') {\n return jsonWithCors(await setLike(collection, env, config, requireSession(session), id, true));\n }\n if (method === 'DELETE') {\n return jsonWithCors(await setLike(collection, env, config, requireSession(session), id, false));\n }\n throw notAllowed();\n }\n case 'report': {\n if (method !== 'POST') throw notAllowed();\n const collection = collectionFor(match.segments[0] ?? '');\n return jsonWithCors(\n await reportEntry(collection, request, env, requireSession(session), match.segments[1] ?? ''),\n );\n }\n case 'hidden': {\n if (method !== 'PUT' && method !== 'DELETE') throw notAllowed();\n const collection = collectionFor(match.segments[0] ?? '');\n return jsonWithCors(\n await setHidden(collection, env, requireSession(session), match.segments[1] ?? '', method === 'PUT'),\n );\n }\n case 'reports': {\n // Private reads carry the same fail-closed precondition as writes: a stale tab\n // must not read another publisher's moderation queue after an account switch.\n const verified = requireSession(session);\n requireExpectedPublisher(request, verified, false);\n return jsonWithCors(await listReports(collectionFor(match.segments[0] ?? ''), request, env, verified));\n }\n case 'resolveReport': {\n queryParams(url, []);\n return jsonWithCors(\n await resolveReport(\n collectionFor(match.segments[0] ?? ''),\n env,\n requireSession(session),\n match.segments[1] ?? '',\n ),\n );\n }\n case 'modLog': {\n // The accountability read carries the same fail-closed precondition as every\n // private surface: a stale tab must not read the moderation history after an\n // account switch.\n const verified = requireSession(session);\n requireExpectedPublisher(request, verified, false);\n return jsonWithCors(await listModerationLog(request, env, verified));\n }\n case 'publisherErase': {\n // The terminal moderation action: erasing a publisher removes their row, and\n // FK cascades take the publications, likes and retry keys with it — the same\n // cascade a self-erase walks, applied to any publisher the operator names.\n if (method !== 'DELETE') throw notAllowed();\n return jsonWithCors(await erasePublisher(env, requireSession(session), match.segments[0] ?? ''));\n }\n case 'accountErase': {\n queryParams(url, []);\n return jsonWithCors(await deleteAccountProfile(env, requireSession(session)));\n }\n case 'me': {\n if (method === 'GET') return jsonWithCors(getMe(session));\n if (method === 'POST') return jsonWithCors(await registerMe(request, env, requireSession(session)));\n if (method === 'PUT') return jsonWithCors(await renameMe(request, env, requireSession(session)));\n if (method === 'DELETE') return jsonWithCors(await deleteProfile(env, requireSession(session)));\n throw notAllowed();\n }\n case 'mePublications':\n case 'meLikes':\n case 'meReports': {\n // Private reads carry the same fail-closed precondition as writes: a stale tab\n // must not consume another publisher's data after a shared-cookie account switch.\n const verified = requireSession(session);\n requireExpectedPublisher(request, verified, false);\n if (match.id === 'mePublications') {\n // The owner's entries are per-collection: the collection param names the\n // registered collection, and an absent or unregistered value is a malformed\n // request, not an empty list.\n const params = queryParams(url, ['collection']);\n const requested = params.get('collection');\n if (requested === null) throw badRequest();\n const collection = registered.get(requested);\n if (collection === undefined) throw badRequest();\n return jsonWithCors(await myPublications(collection, env, verified));\n }\n return jsonWithCors(\n await (match.id === 'meReports' ? myReports(request, env, verified) : myLikes(request, env, verified)),\n );\n }\n default:\n throw notAllowed();\n }\n }\n\n const tappers = new Set<(event: ArchiveEvent) => void>();\n const emit = (event: ArchiveEvent): void => {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must never affect the Worker.\n }\n }\n };\n\n return {\n async fetch(request: Request, env: E, ctx: ArchiveExecutionContext): Promise<Response> {\n const requestId = crypto.randomUUID();\n const route = routeLabel(new URL(request.url).pathname);\n const startedAt = Date.now();\n try {\n const response = await handle(request, env, ctx);\n let diagnostic: ArchiveDiagnostic | undefined;\n if (response.status === 503 && route === '/health') {\n const health = archiveHealthSchema.parse(await response.clone().json());\n diagnostic = { kind: health.reason === 'database-unavailable' ? 'database' : 'schema' };\n }\n emit({\n code: response.ok ? 'ok' : 'unavailable',\n ms: Date.now() - startedAt,\n requestId,\n route,\n status: response.status,\n type: 'request',\n ...(diagnostic === undefined ? {} : { diagnostic }),\n });\n return response;\n } catch (error) {\n const response = toErrorResponse(error, requestId);\n emit({\n code: error instanceof ApiError ? error.code : 'internal',\n diagnostic:\n error instanceof ApiError\n ? error.diagnostic\n : { kind: error instanceof ArchiveDerivationError ? 'collection' : 'internal' },\n ms: Date.now() - startedAt,\n requestId,\n route,\n status: response.status,\n type: 'request',\n });\n // Allowed origins see the same CORS contract on errors as on successes.\n try {\n return withCors(response, request, apiConfig(env));\n } catch {\n return response; // the config error itself is the response\n }\n }\n },\n\n async scheduled(_controller: unknown, env: E, _ctx: ArchiveExecutionContext): Promise<void> {\n try {\n const timestamp = Date.now();\n const results = await env.DB.batch([\n env.DB.prepare(SQL.expireRequests).bind(timestamp),\n env.DB.prepare(SQL.expireRevocations).bind(timestamp),\n ]);\n emit({\n removed: results.reduce((sum, result) => sum + (result.meta?.changes ?? 0), 0),\n status: 'ok',\n type: 'scheduled',\n });\n } catch (error) {\n emit({ diagnostic: { kind: 'database' }, status: 'failed', type: 'scheduled' });\n throw error;\n }\n },\n\n tap(handler, options) {\n const signal = options?.signal;\n if (signal?.aborted) return () => {};\n const off = () => {\n tappers.delete(handler);\n signal?.removeEventListener('abort', off);\n };\n tappers.add(handler);\n signal?.addEventListener('abort', off, { once: true });\n return off;\n },\n };\n}\n"],"mappings":"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"}
package/dist/worker.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export type { ArchiveWorkerEnv, AuthIssuerServiceBinding, D1Like, D1Statement, RateLimiterBinding } from './worker/env';
2
- export type { ArchiveDiagnostic } from './worker/http';
3
- export { type ArchiveEvent, type ArchiveExecutionContext, type ArchiveRequestEvent, type ArchiveScheduledEvent, type ArchiveWorkerHandler, createArchiveWorker, } from './worker/worker';
1
+ export type { D1Like, D1Statement } from './_server/d1';
2
+ export type { ArchiveExecutionContext } from './_server/execution';
3
+ export type { ArchiveDiagnostic } from './_server/http';
4
+ export type { ArchiveWorkerEnv, AuthIssuerServiceBinding, RateLimiterBinding } from './worker/env';
5
+ export { type ArchiveEvent, type ArchiveRequestEvent, type ArchiveScheduledEvent, type ArchiveWorkerHandler, createArchiveWorker, } from './worker/worker';
4
6
  //# sourceMappingURL=worker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACxH,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAIA,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACnG,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/archive",
3
- "version": "26.11.12",
3
+ "version": "26.11.13",
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",
@@ -62,7 +62,7 @@
62
62
  "test:types": "tsc -p tsconfig.json --noEmit"
63
63
  },
64
64
  "dependencies": {
65
- "@vielzeug/spell": "26.11.12",
65
+ "@vielzeug/spell": "26.11.13",
66
66
  "jose": "6.2.15"
67
67
  },
68
68
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"_service.cjs","names":["controller","timer","binding","request","consume","reject"],"sources":["../../src/worker/_service.ts"],"sourcesContent":["/** Bound the complete service exchange, including response-body decoding. */\nexport async function serviceRequest<T>(\n binding: { fetch(request: Request): Promise<Response> },\n request: Request,\n consume: (response: Response) => Promise<T>,\n): Promise<T> {\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n binding.fetch(new Request(request, { signal: controller.signal })).then(consume),\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(new Error('Service request timed out.'));\n }, 5000);\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n"],"mappings":"AACA,eAAsB,eACpB,EACA,EACA,EACY,CACZ,IAAMA,EAAa,IAAI,gBACnBC,EACJ,GAAI,CACF,OAAO,MAAM,QAAQ,KAAK,CACxBC,EAAQ,MAAM,IAAI,QAAQC,EAAS,CAAE,OAAQH,EAAW,MAAO,CAAC,CAAC,CAAC,CAAC,KAAKI,CAAO,EAC/E,IAAI,SAAgB,EAAU,IAAW,CACvC,EAAQ,eAAiB,CACvBJ,EAAW,MAAM,EACjBK,EAAW,MAAM,4BAA4B,CAAC,CAChD,EAAG,GAAI,CACT,CAAC,CACH,CAAC,CACH,QAAU,CACJJ,IAAU,IAAA,IAAW,aAAaA,CAAK,CAC7C,CACF,C"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"_service.d.ts","sourceRoot":"","sources":["../../src/worker/_service.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wBAAsB,cAAc,CAAC,CAAC,EACpC,OAAO,EAAE;IAAE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAE,EACvD,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAC1C,OAAO,CAAC,CAAC,CAAC,CAgBZ"}