@vielzeug/archive 0.0.0-stage → 26.10.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.
Files changed (112) hide show
  1. package/README.md +81 -2
  2. package/dist/_json.cjs +2 -0
  3. package/dist/_json.cjs.map +1 -0
  4. package/dist/_json.d.ts +4 -0
  5. package/dist/_json.d.ts.map +1 -0
  6. package/dist/_json.js +2 -0
  7. package/dist/_json.js.map +1 -0
  8. package/dist/auth/config.cjs +2 -0
  9. package/dist/auth/config.cjs.map +1 -0
  10. package/dist/auth/config.d.ts +88 -0
  11. package/dist/auth/config.d.ts.map +1 -0
  12. package/dist/auth/config.js +2 -0
  13. package/dist/auth/config.js.map +1 -0
  14. package/dist/auth/errors.cjs +2 -0
  15. package/dist/auth/errors.cjs.map +1 -0
  16. package/dist/auth/errors.d.ts +10 -0
  17. package/dist/auth/errors.d.ts.map +1 -0
  18. package/dist/auth/errors.js +2 -0
  19. package/dist/auth/errors.js.map +1 -0
  20. package/dist/auth/store.cjs +19 -0
  21. package/dist/auth/store.cjs.map +1 -0
  22. package/dist/auth/store.d.ts +45 -0
  23. package/dist/auth/store.d.ts.map +1 -0
  24. package/dist/auth/store.js +19 -0
  25. package/dist/auth/store.js.map +1 -0
  26. package/dist/auth/worker.cjs +16 -0
  27. package/dist/auth/worker.cjs.map +1 -0
  28. package/dist/auth/worker.d.ts +47 -0
  29. package/dist/auth/worker.d.ts.map +1 -0
  30. package/dist/auth/worker.js +16 -0
  31. package/dist/auth/worker.js.map +1 -0
  32. package/dist/auth.cjs +1 -0
  33. package/dist/auth.d.ts +6 -0
  34. package/dist/auth.d.ts.map +1 -0
  35. package/dist/auth.js +1 -0
  36. package/dist/collection.cjs +2 -0
  37. package/dist/collection.cjs.map +1 -0
  38. package/dist/collection.d.ts +131 -0
  39. package/dist/collection.d.ts.map +1 -0
  40. package/dist/collection.js +2 -0
  41. package/dist/collection.js.map +1 -0
  42. package/dist/contract.cjs +2 -0
  43. package/dist/contract.cjs.map +1 -0
  44. package/dist/contract.d.ts +227 -0
  45. package/dist/contract.d.ts.map +1 -0
  46. package/dist/contract.js +2 -0
  47. package/dist/contract.js.map +1 -0
  48. package/dist/errors.cjs +2 -0
  49. package/dist/errors.cjs.map +1 -0
  50. package/dist/errors.d.ts +12 -0
  51. package/dist/errors.d.ts.map +1 -0
  52. package/dist/errors.js +2 -0
  53. package/dist/errors.js.map +1 -0
  54. package/dist/index.cjs +1 -0
  55. package/dist/index.d.ts +4 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +1 -0
  58. package/dist/worker/_pagination.cjs +2 -0
  59. package/dist/worker/_pagination.cjs.map +1 -0
  60. package/dist/worker/_pagination.d.ts +18 -0
  61. package/dist/worker/_pagination.d.ts.map +1 -0
  62. package/dist/worker/_pagination.js +2 -0
  63. package/dist/worker/_pagination.js.map +1 -0
  64. package/dist/worker/_service.cjs +2 -0
  65. package/dist/worker/_service.cjs.map +1 -0
  66. package/dist/worker/_service.d.ts +5 -0
  67. package/dist/worker/_service.d.ts.map +1 -0
  68. package/dist/worker/_service.js +2 -0
  69. package/dist/worker/_service.js.map +1 -0
  70. package/dist/worker/auth.cjs +2 -0
  71. package/dist/worker/auth.cjs.map +1 -0
  72. package/dist/worker/auth.d.ts +34 -0
  73. package/dist/worker/auth.d.ts.map +1 -0
  74. package/dist/worker/auth.js +2 -0
  75. package/dist/worker/auth.js.map +1 -0
  76. package/dist/worker/env.cjs +2 -0
  77. package/dist/worker/env.cjs.map +1 -0
  78. package/dist/worker/env.d.ts +73 -0
  79. package/dist/worker/env.d.ts.map +1 -0
  80. package/dist/worker/env.js +2 -0
  81. package/dist/worker/env.js.map +1 -0
  82. package/dist/worker/http.cjs +2 -0
  83. package/dist/worker/http.cjs.map +1 -0
  84. package/dist/worker/http.d.ts +66 -0
  85. package/dist/worker/http.d.ts.map +1 -0
  86. package/dist/worker/http.js +2 -0
  87. package/dist/worker/http.js.map +1 -0
  88. package/dist/worker/publications.cjs +2 -0
  89. package/dist/worker/publications.cjs.map +1 -0
  90. package/dist/worker/publications.d.ts +60 -0
  91. package/dist/worker/publications.d.ts.map +1 -0
  92. package/dist/worker/publications.js +2 -0
  93. package/dist/worker/publications.js.map +1 -0
  94. package/dist/worker/sql.cjs +60 -0
  95. package/dist/worker/sql.cjs.map +1 -0
  96. package/dist/worker/sql.d.ts +86 -0
  97. package/dist/worker/sql.d.ts.map +1 -0
  98. package/dist/worker/sql.js +60 -0
  99. package/dist/worker/sql.js.map +1 -0
  100. package/dist/worker/worker.cjs +2 -0
  101. package/dist/worker/worker.cjs.map +1 -0
  102. package/dist/worker/worker.d.ts +72 -0
  103. package/dist/worker/worker.d.ts.map +1 -0
  104. package/dist/worker/worker.js +2 -0
  105. package/dist/worker/worker.js.map +1 -0
  106. package/dist/worker.cjs +1 -0
  107. package/dist/worker.d.ts +4 -0
  108. package/dist/worker.d.ts.map +1 -0
  109. package/dist/worker.js +1 -0
  110. package/migrations/0001_community_archive.sql +173 -0
  111. package/migrations/auth/0001_auth.sql +65 -0
  112. package/package.json +72 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.cjs","names":[],"sources":["../src/contract.ts"],"sourcesContent":["import { type InferOutput, type Schema, s } from '@vielzeug/spell';\n\n/** Shared naming: one place so the client, Worker and issuer cannot drift. */\nexport const SESSION_COOKIE_NAME = 'archive_session';\nexport const SESSION_COOKIE_PATH = '/api/v1';\n/**\n * The maximum age a session JWT may carry, and the cookie Max-Age an issuer sets:\n * a long-lived signed-in state. The Worker rejects older tokens at this bound.\n */\nexport const SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;\nexport const OPERATOR_SESSION_MAX_AGE_SECONDS = 15 * 60;\n\n/**\n * The full Set-Cookie value for the session cookie: name, token and the security\n * attributes every issuer and the Worker must agree on — HttpOnly, SameSite=Strict,\n * the contract Path, Max-Age, and Secure in production. Issuers mint with the\n * session's max age; the Worker and issuers clear with `maxAgeSeconds: 0`. Never\n * hand-compose the attributes: the cookie's CSRF and theft posture is contract,\n * not per-issuer choice.\n */\nexport const sessionCookieValue = (token: string, options: { maxAgeSeconds: number; secure: boolean }): string =>\n `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Path=${SESSION_COOKIE_PATH}; Max-Age=${options.maxAgeSeconds}${options.secure ? '; Secure' : ''}`;\n\nexport const PUBLISHER_HEADER = 'x-archive-publisher';\n/** Pins account erasure to the issuer account shown when confirmation began. */\nexport const ACCOUNT_HEADER = 'X-Archive-Account';\nexport const REQUEST_MARKER_HEADER = 'x-archive-request';\n/** The wire-contract version advertised by health and stamped on every entry envelope. */\nexport const ARCHIVE_CONTRACT_VERSION = 1;\n/**\n * The storage schema version: the `archive_schema_meta` stamp the package's migration\n * writes and the Worker reads back through health. A mismatch answers `degraded`\n * instead of `ok` — a stale database is the most likely deployment fault.\n */\nexport const ARCHIVE_SCHEMA_VERSION = 1;\n/** Canonical issuer database initialization stamp. */\nexport const AUTH_SCHEMA_VERSION = 1;\n/** The `roles` claim value a session JWT carries for operator authority (moderation). */\nexport const ARCHIVE_OPERATOR_ROLE = 'archive-operator';\n/** The routable collection-name rule: lowercase `[a-z0-9-]`, at most 32 chars. */\nexport const ARCHIVE_COLLECTION_NAME_PATTERN = /^[a-z0-9-]{1,32}$/;\n\n// Exported schemas carry explicit public `Schema<Output>` annotations: the concrete\n// spell schema classes are not part of spell's public export surface, so inferred\n// types would not be portable across package declaration files.\nexport const uuidSchema: Schema<string> = s.string().uuid();\nexport const isoTimestampSchema: Schema<string> = s.string().isoDateTime();\nexport const hex64Schema: Schema<string> = s.string().regex(/^[0-9a-f]{64}$/);\n/** Well-formed user text: no lone UTF-16 surrogates, no C0/C1/DEL control characters. */\nexport const ILL_FORMED_TEXT = /[\\p{Cs}\\p{Cc}]/u;\nexport const isWellFormedText = (value: string): boolean => !ILL_FORMED_TEXT.test(value);\nconst textString = () => s.string().check(isWellFormedText);\n/** The one alias rule, shared by identity mutations and every emitted DTO. */\nexport const archiveAliasSchema: Schema<string> = textString().trim().max(24);\n\nexport const archiveAuthorSchema: Schema<{ id: string; name: string }> = s.object({\n id: uuidSchema,\n name: archiveAliasSchema,\n});\nexport const archiveIdentitySchema: Schema<{ id: string; name: string; operator: boolean } | null> = s\n .object({ id: uuidSchema, name: archiveAliasSchema, operator: s.boolean() })\n .nullable();\nexport type ArchiveIdentity = InferOutput<typeof archiveIdentitySchema>;\n/** The issuer account metadata export; its id is the session subject, not a publisher id. */\nexport const archiveAccountSchema: Schema<{\n createdAt: string;\n email: string;\n id: string;\n lastSignInAt: string | null;\n}> = s.object({\n createdAt: isoTimestampSchema,\n email: s.string().email(),\n id: uuidSchema,\n lastSignInAt: isoTimestampSchema.nullable(),\n});\nexport type ArchiveAccount = InferOutput<typeof archiveAccountSchema>;\nexport const archiveLikedIdsSchema: Schema<{ ids: string[] }> = s.object({ ids: s.array(uuidSchema) });\nexport const archiveLikesPageSchema: Schema<{ cursor: string | null; ids: string[] }> = s.object({\n cursor: s.string().nullable(),\n ids: s.array(uuidSchema),\n});\nexport type ArchiveLikesPage = InferOutput<typeof archiveLikesPageSchema>;\nexport const archiveLikeStateSchema: Schema<{ id: string; likeCount: number; liked: boolean }> = s.object({\n id: uuidSchema,\n likeCount: s.number().int().min(0).max(Number.MAX_SAFE_INTEGER),\n liked: s.boolean(),\n});\n\n/** Application bounds shared by handlers, the health route, and clients. */\nexport const ARCHIVE_LIMITS = Object.freeze({\n aliasChars: 24,\n cursorChars: 1024,\n idempotencyRetentionMs: 48 * 60 * 60 * 1000,\n likeStateIds: 50,\n maxPageSize: 50,\n maxPublications: 50,\n pageSize: 10,\n payloadBytes: 8192,\n publicCacheSeconds: 30,\n /** One free-text moderation reason: trimmed, non-empty, at most this many chars. */\n reportReasonChars: 256,\n requestBytes: 12 * 1024,\n searchChars: 64,\n searchTerms: 6,\n tagsPerEntry: 16,\n titleChars: 128,\n /** One declared filter or tag value: well-formed, non-empty, at most this many chars. */\n vocabChars: 64,\n});\nexport const archiveLimitsSchema: Schema<{\n aliasChars: number;\n cursorChars: number;\n idempotencyRetentionMs: number;\n likeStateIds: number;\n maxPageSize: number;\n maxPublications: number;\n pageSize: number;\n payloadBytes: number;\n publicCacheSeconds: number;\n reportReasonChars: number;\n requestBytes: number;\n searchChars: number;\n searchTerms: number;\n tagsPerEntry: number;\n titleChars: number;\n vocabChars: number;\n}> = s.object({\n aliasChars: s.number().int().min(1),\n cursorChars: s.number().int().min(1),\n idempotencyRetentionMs: s.number().int().min(1),\n likeStateIds: s.number().int().min(1),\n maxPageSize: s.number().int().min(1),\n maxPublications: s.number().int().min(1),\n pageSize: s.number().int().min(1),\n payloadBytes: s.number().int().min(1),\n publicCacheSeconds: s.number().int().min(0),\n reportReasonChars: s.number().int().min(1),\n requestBytes: s.number().int().min(1),\n searchChars: s.number().int().min(1),\n searchTerms: s.number().int().min(1),\n tagsPerEntry: s.number().int().min(1),\n titleChars: s.number().int().min(1),\n vocabChars: s.number().int().min(1),\n});\nexport const archiveHealthSchema: Schema<{\n /** The storage schema stamp (`archive_schema_meta`) the database actually carries. */\n dbSchemaVersion: number | null;\n features: { likes: boolean; publishing: boolean };\n limits: InferOutput<typeof archiveLimitsSchema>;\n schemaVersion: 1;\n /** 'degraded' when the database's storage schema does not match the Worker's. */\n status: 'ok' | 'degraded';\n reason: 'database-unavailable' | 'schema-mismatch' | null;\n}> = s.object({\n dbSchemaVersion: s.number().int().min(0).nullable(),\n features: s.object({ likes: s.boolean(), publishing: s.boolean() }),\n limits: archiveLimitsSchema,\n reason: s.enum(['database-unavailable', 'schema-mismatch'] as const).nullable(),\n schemaVersion: s.literal(ARCHIVE_CONTRACT_VERSION),\n status: s.enum(['ok', 'degraded'] as const),\n});\nexport type ArchiveHealth = InferOutput<typeof archiveHealthSchema>;\n\nexport const archiveAuthHealthSchema: Schema<{\n dbSchemaVersion: number | null;\n schemaVersion: 1;\n status: 'ok' | 'degraded';\n reason: 'configuration-unavailable' | 'database-unavailable' | 'schema-mismatch' | null;\n}> = s.object({\n dbSchemaVersion: s.number().int().min(0).nullable(),\n reason: s.enum(['configuration-unavailable', 'database-unavailable', 'schema-mismatch'] as const).nullable(),\n schemaVersion: s.literal(AUTH_SCHEMA_VERSION),\n status: s.enum(['ok', 'degraded'] as const),\n});\nexport type AuthHealth = InferOutput<typeof archiveAuthHealthSchema>;\n\n/** The Worker's full error-code vocabulary — the contract both sides test against. */\nexport const ARCHIVE_ERROR_CODES = [\n 'account-changed',\n 'auth-unavailable',\n 'bad-request',\n 'conflict',\n 'forbidden',\n 'gone',\n 'internal',\n 'invalid-payload',\n 'likes-disabled',\n 'method-not-allowed',\n 'not-found',\n 'publisher-changed',\n 'publishing-disabled',\n 'quota',\n 'rate-limited',\n 'record-unavailable',\n 'timeout',\n 'too-large',\n 'unauthenticated',\n 'unavailable',\n 'unsupported-media',\n] as const;\nexport type ArchiveErrorCode = (typeof ARCHIVE_ERROR_CODES)[number];\n\n/**\n * The error envelope every route answers failures with: `{ error: { code, message,\n * requestId } }`. Clients validate against this one schema — an envelope carrying an\n * unknown code or malformed fields is not an archive error at all.\n */\nexport const archiveErrorResponseSchema: Schema<{\n error: { code: ArchiveErrorCode; message: string; requestId: string };\n}> = s\n .object({\n error: s\n .object({\n code: s.enum(ARCHIVE_ERROR_CODES),\n message: s.string().min(1).max(256),\n requestId: uuidSchema,\n })\n .strict(),\n })\n .strict();\nexport type ArchiveErrorResponse = InferOutput<typeof archiveErrorResponseSchema>;\n\n/** The sign-out acknowledgement: the only non-collection response body, shared by both sides. */\nexport const archiveOkSchema: Schema<{ ok: true }> = s.object({ ok: s.literal(true) }).strict();\n\n/** One collection's public self-description: its name and declared query vocabularies. */\nexport const archiveCollectionMetaSchema: Schema<{\n filters: string[];\n name: string;\n tags: string[];\n}> = s.object({\n filters: s.array(s.string().min(1).max(ARCHIVE_LIMITS.vocabChars)),\n name: s.string().regex(ARCHIVE_COLLECTION_NAME_PATTERN),\n tags: s.array(s.string().min(1).max(ARCHIVE_LIMITS.vocabChars)),\n});\nexport type ArchiveCollectionMeta = InferOutput<typeof archiveCollectionMetaSchema>;\n\n/** One reader report an operator reviews: what was reported, why, and by whom. */\nexport const archiveReportsSchema: Schema<{\n cursor: string | null;\n reports: {\n id: string;\n createdAt: string;\n entryId: string;\n entryTitle: string;\n reason: string;\n reporter: { id: string; name: string };\n resolvedAt: string | null;\n }[];\n}> = s.object({\n cursor: s.string().nullable(),\n reports: s.array(\n s.object({\n createdAt: isoTimestampSchema,\n entryId: uuidSchema,\n entryTitle: s.string().min(1).max(ARCHIVE_LIMITS.titleChars),\n id: uuidSchema,\n reason: s.string().min(1).max(ARCHIVE_LIMITS.reportReasonChars),\n reporter: archiveAuthorSchema,\n resolvedAt: isoTimestampSchema.nullable(),\n }),\n ),\n});\nexport type ArchiveReports = InferOutput<typeof archiveReportsSchema>;\nexport const archiveOwnReportsSchema: Schema<{\n cursor: string | null;\n reports: {\n collection: string;\n createdAt: string;\n entryId: string;\n id: string;\n reason: string;\n resolvedAt: string | null;\n }[];\n}> = s.object({\n cursor: s.string().nullable(),\n reports: s.array(\n s.object({\n collection: s.string().regex(ARCHIVE_COLLECTION_NAME_PATTERN),\n createdAt: isoTimestampSchema,\n entryId: uuidSchema,\n id: uuidSchema,\n reason: s.string().min(1).max(ARCHIVE_LIMITS.reportReasonChars),\n resolvedAt: isoTimestampSchema.nullable(),\n }),\n ),\n});\nexport type ArchiveOwnReports = InferOutput<typeof archiveOwnReportsSchema>;\n\n/** The moderation actions an operator can take; the audit log's whole vocabulary. */\nexport const ARCHIVE_MODERATION_ACTIONS = ['hide', 'unhide', 'remove', 'erase-publisher'] as const;\nexport type ArchiveModerationAction = (typeof ARCHIVE_MODERATION_ACTIONS)[number];\n\n/**\n * The operator accountability log: one row per effective moderation action, newest\n * first. Operator and target travel as opaque ids only — no names (they change), no\n * content — so the log stays honest after renames and survives the erasures it records.\n */\nexport const archiveModerationLogSchema: Schema<{\n cursor: string | null;\n actions: {\n id: string;\n action: ArchiveModerationAction;\n collection: string | null;\n createdAt: string;\n operatorId: string;\n targetId: string;\n }[];\n}> = s.object({\n actions: s.array(\n s.object({\n action: s.enum(ARCHIVE_MODERATION_ACTIONS),\n collection: s.string().regex(ARCHIVE_COLLECTION_NAME_PATTERN).nullable(),\n createdAt: isoTimestampSchema,\n id: uuidSchema,\n operatorId: uuidSchema,\n targetId: uuidSchema,\n }),\n ),\n cursor: s.string().nullable(),\n});\nexport type ArchiveModerationLog = InferOutput<typeof archiveModerationLogSchema>;\n\n/** How the online list ranks entries: explicit modes only, no vague composites. */\nexport type ArchiveSort = 'liked' | 'recent';\nexport const archiveSortSchema: Schema<ArchiveSort> = s.enum(['liked', 'recent'] as const);\n\n/** One page of archive entries plus forward/backward cursors around it. */\nexport interface ArchivePage<T> {\n /** Cursor for the next page; null after the last. */\n cursor: string | null;\n entries: readonly T[];\n /** Cursor for the previous page; null when already at the top. */\n previous: string | null;\n}\n"],"mappings":"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"}
@@ -0,0 +1,227 @@
1
+ import { type InferOutput, type Schema } from '@vielzeug/spell';
2
+ /** Shared naming: one place so the client, Worker and issuer cannot drift. */
3
+ export declare const SESSION_COOKIE_NAME = "archive_session";
4
+ export declare const SESSION_COOKIE_PATH = "/api/v1";
5
+ /**
6
+ * The maximum age a session JWT may carry, and the cookie Max-Age an issuer sets:
7
+ * a long-lived signed-in state. The Worker rejects older tokens at this bound.
8
+ */
9
+ export declare const SESSION_MAX_AGE_SECONDS: number;
10
+ export declare const OPERATOR_SESSION_MAX_AGE_SECONDS: number;
11
+ /**
12
+ * The full Set-Cookie value for the session cookie: name, token and the security
13
+ * attributes every issuer and the Worker must agree on — HttpOnly, SameSite=Strict,
14
+ * the contract Path, Max-Age, and Secure in production. Issuers mint with the
15
+ * session's max age; the Worker and issuers clear with `maxAgeSeconds: 0`. Never
16
+ * hand-compose the attributes: the cookie's CSRF and theft posture is contract,
17
+ * not per-issuer choice.
18
+ */
19
+ export declare const sessionCookieValue: (token: string, options: {
20
+ maxAgeSeconds: number;
21
+ secure: boolean;
22
+ }) => string;
23
+ export declare const PUBLISHER_HEADER = "x-archive-publisher";
24
+ /** Pins account erasure to the issuer account shown when confirmation began. */
25
+ export declare const ACCOUNT_HEADER = "X-Archive-Account";
26
+ export declare const REQUEST_MARKER_HEADER = "x-archive-request";
27
+ /** The wire-contract version advertised by health and stamped on every entry envelope. */
28
+ export declare const ARCHIVE_CONTRACT_VERSION = 1;
29
+ /**
30
+ * The storage schema version: the `archive_schema_meta` stamp the package's migration
31
+ * writes and the Worker reads back through health. A mismatch answers `degraded`
32
+ * instead of `ok` — a stale database is the most likely deployment fault.
33
+ */
34
+ export declare const ARCHIVE_SCHEMA_VERSION = 1;
35
+ /** Canonical issuer database initialization stamp. */
36
+ export declare const AUTH_SCHEMA_VERSION = 1;
37
+ /** The `roles` claim value a session JWT carries for operator authority (moderation). */
38
+ export declare const ARCHIVE_OPERATOR_ROLE = "archive-operator";
39
+ /** The routable collection-name rule: lowercase `[a-z0-9-]`, at most 32 chars. */
40
+ export declare const ARCHIVE_COLLECTION_NAME_PATTERN: RegExp;
41
+ export declare const uuidSchema: Schema<string>;
42
+ export declare const isoTimestampSchema: Schema<string>;
43
+ export declare const hex64Schema: Schema<string>;
44
+ /** Well-formed user text: no lone UTF-16 surrogates, no C0/C1/DEL control characters. */
45
+ export declare const ILL_FORMED_TEXT: RegExp;
46
+ export declare const isWellFormedText: (value: string) => boolean;
47
+ /** The one alias rule, shared by identity mutations and every emitted DTO. */
48
+ export declare const archiveAliasSchema: Schema<string>;
49
+ export declare const archiveAuthorSchema: Schema<{
50
+ id: string;
51
+ name: string;
52
+ }>;
53
+ export declare const archiveIdentitySchema: Schema<{
54
+ id: string;
55
+ name: string;
56
+ operator: boolean;
57
+ } | null>;
58
+ export type ArchiveIdentity = InferOutput<typeof archiveIdentitySchema>;
59
+ /** The issuer account metadata export; its id is the session subject, not a publisher id. */
60
+ export declare const archiveAccountSchema: Schema<{
61
+ createdAt: string;
62
+ email: string;
63
+ id: string;
64
+ lastSignInAt: string | null;
65
+ }>;
66
+ export type ArchiveAccount = InferOutput<typeof archiveAccountSchema>;
67
+ export declare const archiveLikedIdsSchema: Schema<{
68
+ ids: string[];
69
+ }>;
70
+ export declare const archiveLikesPageSchema: Schema<{
71
+ cursor: string | null;
72
+ ids: string[];
73
+ }>;
74
+ export type ArchiveLikesPage = InferOutput<typeof archiveLikesPageSchema>;
75
+ export declare const archiveLikeStateSchema: Schema<{
76
+ id: string;
77
+ likeCount: number;
78
+ liked: boolean;
79
+ }>;
80
+ /** Application bounds shared by handlers, the health route, and clients. */
81
+ export declare const ARCHIVE_LIMITS: Readonly<{
82
+ aliasChars: 24;
83
+ cursorChars: 1024;
84
+ idempotencyRetentionMs: number;
85
+ likeStateIds: 50;
86
+ maxPageSize: 50;
87
+ maxPublications: 50;
88
+ pageSize: 10;
89
+ payloadBytes: 8192;
90
+ publicCacheSeconds: 30;
91
+ /** One free-text moderation reason: trimmed, non-empty, at most this many chars. */
92
+ reportReasonChars: 256;
93
+ requestBytes: number;
94
+ searchChars: 64;
95
+ searchTerms: 6;
96
+ tagsPerEntry: 16;
97
+ titleChars: 128;
98
+ /** One declared filter or tag value: well-formed, non-empty, at most this many chars. */
99
+ vocabChars: 64;
100
+ }>;
101
+ export declare const archiveLimitsSchema: Schema<{
102
+ aliasChars: number;
103
+ cursorChars: number;
104
+ idempotencyRetentionMs: number;
105
+ likeStateIds: number;
106
+ maxPageSize: number;
107
+ maxPublications: number;
108
+ pageSize: number;
109
+ payloadBytes: number;
110
+ publicCacheSeconds: number;
111
+ reportReasonChars: number;
112
+ requestBytes: number;
113
+ searchChars: number;
114
+ searchTerms: number;
115
+ tagsPerEntry: number;
116
+ titleChars: number;
117
+ vocabChars: number;
118
+ }>;
119
+ export declare const archiveHealthSchema: Schema<{
120
+ /** The storage schema stamp (`archive_schema_meta`) the database actually carries. */
121
+ dbSchemaVersion: number | null;
122
+ features: {
123
+ likes: boolean;
124
+ publishing: boolean;
125
+ };
126
+ limits: InferOutput<typeof archiveLimitsSchema>;
127
+ schemaVersion: 1;
128
+ /** 'degraded' when the database's storage schema does not match the Worker's. */
129
+ status: 'ok' | 'degraded';
130
+ reason: 'database-unavailable' | 'schema-mismatch' | null;
131
+ }>;
132
+ export type ArchiveHealth = InferOutput<typeof archiveHealthSchema>;
133
+ export declare const archiveAuthHealthSchema: Schema<{
134
+ dbSchemaVersion: number | null;
135
+ schemaVersion: 1;
136
+ status: 'ok' | 'degraded';
137
+ reason: 'configuration-unavailable' | 'database-unavailable' | 'schema-mismatch' | null;
138
+ }>;
139
+ export type AuthHealth = InferOutput<typeof archiveAuthHealthSchema>;
140
+ /** The Worker's full error-code vocabulary — the contract both sides test against. */
141
+ export declare const ARCHIVE_ERROR_CODES: readonly ["account-changed", "auth-unavailable", "bad-request", "conflict", "forbidden", "gone", "internal", "invalid-payload", "likes-disabled", "method-not-allowed", "not-found", "publisher-changed", "publishing-disabled", "quota", "rate-limited", "record-unavailable", "timeout", "too-large", "unauthenticated", "unavailable", "unsupported-media"];
142
+ export type ArchiveErrorCode = (typeof ARCHIVE_ERROR_CODES)[number];
143
+ /**
144
+ * The error envelope every route answers failures with: `{ error: { code, message,
145
+ * requestId } }`. Clients validate against this one schema — an envelope carrying an
146
+ * unknown code or malformed fields is not an archive error at all.
147
+ */
148
+ export declare const archiveErrorResponseSchema: Schema<{
149
+ error: {
150
+ code: ArchiveErrorCode;
151
+ message: string;
152
+ requestId: string;
153
+ };
154
+ }>;
155
+ export type ArchiveErrorResponse = InferOutput<typeof archiveErrorResponseSchema>;
156
+ /** The sign-out acknowledgement: the only non-collection response body, shared by both sides. */
157
+ export declare const archiveOkSchema: Schema<{
158
+ ok: true;
159
+ }>;
160
+ /** One collection's public self-description: its name and declared query vocabularies. */
161
+ export declare const archiveCollectionMetaSchema: Schema<{
162
+ filters: string[];
163
+ name: string;
164
+ tags: string[];
165
+ }>;
166
+ export type ArchiveCollectionMeta = InferOutput<typeof archiveCollectionMetaSchema>;
167
+ /** One reader report an operator reviews: what was reported, why, and by whom. */
168
+ export declare const archiveReportsSchema: Schema<{
169
+ cursor: string | null;
170
+ reports: {
171
+ id: string;
172
+ createdAt: string;
173
+ entryId: string;
174
+ entryTitle: string;
175
+ reason: string;
176
+ reporter: {
177
+ id: string;
178
+ name: string;
179
+ };
180
+ resolvedAt: string | null;
181
+ }[];
182
+ }>;
183
+ export type ArchiveReports = InferOutput<typeof archiveReportsSchema>;
184
+ export declare const archiveOwnReportsSchema: Schema<{
185
+ cursor: string | null;
186
+ reports: {
187
+ collection: string;
188
+ createdAt: string;
189
+ entryId: string;
190
+ id: string;
191
+ reason: string;
192
+ resolvedAt: string | null;
193
+ }[];
194
+ }>;
195
+ export type ArchiveOwnReports = InferOutput<typeof archiveOwnReportsSchema>;
196
+ /** The moderation actions an operator can take; the audit log's whole vocabulary. */
197
+ export declare const ARCHIVE_MODERATION_ACTIONS: readonly ["hide", "unhide", "remove", "erase-publisher"];
198
+ export type ArchiveModerationAction = (typeof ARCHIVE_MODERATION_ACTIONS)[number];
199
+ /**
200
+ * The operator accountability log: one row per effective moderation action, newest
201
+ * first. Operator and target travel as opaque ids only — no names (they change), no
202
+ * content — so the log stays honest after renames and survives the erasures it records.
203
+ */
204
+ export declare const archiveModerationLogSchema: Schema<{
205
+ cursor: string | null;
206
+ actions: {
207
+ id: string;
208
+ action: ArchiveModerationAction;
209
+ collection: string | null;
210
+ createdAt: string;
211
+ operatorId: string;
212
+ targetId: string;
213
+ }[];
214
+ }>;
215
+ export type ArchiveModerationLog = InferOutput<typeof archiveModerationLogSchema>;
216
+ /** How the online list ranks entries: explicit modes only, no vague composites. */
217
+ export type ArchiveSort = 'liked' | 'recent';
218
+ export declare const archiveSortSchema: Schema<ArchiveSort>;
219
+ /** One page of archive entries plus forward/backward cursors around it. */
220
+ export interface ArchivePage<T> {
221
+ /** Cursor for the next page; null after the last. */
222
+ cursor: string | null;
223
+ entries: readonly T[];
224
+ /** Cursor for the previous page; null when already at the top. */
225
+ previous: string | null;
226
+ }
227
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,MAAM,EAAK,MAAM,iBAAiB,CAAC;AAEnE,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AACrD,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAC7C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAAoB,CAAC;AACzD,eAAO,MAAM,gCAAgC,QAAU,CAAC;AAExD;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,SAAS;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,KAAG,MACwD,CAAC;AAEjK,eAAO,MAAM,gBAAgB,wBAAwB,CAAC;AACtD,gFAAgF;AAChF,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAClD,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AACzD,0FAA0F;AAC1F,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,yFAAyF;AACzF,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD,kFAAkF;AAClF,eAAO,MAAM,+BAA+B,QAAsB,CAAC;AAKnE,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,CAAqB,CAAC;AAC5D,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,CAA4B,CAAC;AAC3E,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,CAAsC,CAAC;AAC9E,yFAAyF;AACzF,eAAO,MAAM,eAAe,QAAoB,CAAC;AACjD,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,OAAuC,CAAC;AAEzF,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,CAA+B,CAAC;AAE9E,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAGnE,CAAC;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAEpF,CAAC;AACd,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACxE,6FAA6F;AAC7F,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAKC,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACtE,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC;IAAE,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAA0C,CAAC;AACvG,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAGlF,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC1E,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAI3F,CAAC;AAEH,4EAA4E;AAC5E,eAAO,MAAM,cAAc;;;;;;;;;;IAUzB,oFAAoF;;;;;;;IAOpF,yFAAyF;;EAEzF,CAAC;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAiBC,CAAC;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC;IACvC,sFAAsF;IACtF,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC;IAClD,MAAM,EAAE,WAAW,CAAC,OAAO,mBAAmB,CAAC,CAAC;IAChD,aAAa,EAAE,CAAC,CAAC;IACjB,iFAAiF;IACjF,MAAM,EAAE,IAAI,GAAG,UAAU,CAAC;IAC1B,MAAM,EAAE,sBAAsB,GAAG,iBAAiB,GAAG,IAAI,CAAC;CAC3D,CAOC,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEpE,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC;IAC3C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,CAAC,CAAC;IACjB,MAAM,EAAE,IAAI,GAAG,UAAU,CAAC;IAC1B,MAAM,EAAE,2BAA2B,GAAG,sBAAsB,GAAG,iBAAiB,GAAG,IAAI,CAAC;CACzF,CAKC,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAErE,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,gWAsBtB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC;IAC9C,KAAK,EAAE;QAAE,IAAI,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACvE,CAUU,CAAC;AACZ,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAElF,iGAAiG;AACjG,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAA8C,CAAC;AAEhG,0FAA0F;AAC1F,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC;IAC/C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAIC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,WAAW,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEpF,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC;IACxC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QACvC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,EAAE,CAAC;CACL,CAaC,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACtE,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC;IAC3C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,EAAE,CAAC;CACL,CAYC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE5E,qFAAqF;AACrF,eAAO,MAAM,0BAA0B,0DAA2D,CAAC;AACnG,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAElF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC;IAC9C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,uBAAuB,CAAC;QAChC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,EAAE,CAAC;CACL,CAYC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAElF,mFAAmF;AACnF,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC7C,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,WAAW,CAAwC,CAAC;AAE3F,2EAA2E;AAC3E,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,qDAAqD;IACrD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB"}
@@ -0,0 +1,2 @@
1
+ import{s as e}from"@vielzeug/spell";var t=`archive_session`,n=`/api/v1`,r=2592e3,i=900,sessionCookieValue=(e,r)=>`${t}=${e}; HttpOnly; SameSite=Strict; Path=${n}; Max-Age=${r.maxAgeSeconds}${r.secure?`; Secure`:``}`,a=`x-archive-publisher`,o=`X-Archive-Account`,s=`x-archive-request`,c=1,l=1,u=1,d=`archive-operator`,f=/^[a-z0-9-]{1,32}$/,p=e.string().uuid(),m=e.string().isoDateTime(),h=e.string().regex(/^[0-9a-f]{64}$/),g=/[\p{Cs}\p{Cc}]/u,isWellFormedText=e=>!g.test(e),textString=()=>e.string().check(isWellFormedText),_=textString().trim().max(24),v=e.object({id:p,name:_}),y=e.object({id:p,name:_,operator:e.boolean()}).nullable(),b=e.object({createdAt:m,email:e.string().email(),id:p,lastSignInAt:m.nullable()}),x=e.object({ids:e.array(p)}),S=e.object({cursor:e.string().nullable(),ids:e.array(p)}),C=e.object({id:p,likeCount:e.number().int().min(0).max(2**53-1),liked:e.boolean()}),w=Object.freeze({aliasChars:24,cursorChars:1024,idempotencyRetentionMs:1728e5,likeStateIds:50,maxPageSize:50,maxPublications:50,pageSize:10,payloadBytes:8192,publicCacheSeconds:30,reportReasonChars:256,requestBytes:12288,searchChars:64,searchTerms:6,tagsPerEntry:16,titleChars:128,vocabChars:64}),T=e.object({aliasChars:e.number().int().min(1),cursorChars:e.number().int().min(1),idempotencyRetentionMs:e.number().int().min(1),likeStateIds:e.number().int().min(1),maxPageSize:e.number().int().min(1),maxPublications:e.number().int().min(1),pageSize:e.number().int().min(1),payloadBytes:e.number().int().min(1),publicCacheSeconds:e.number().int().min(0),reportReasonChars:e.number().int().min(1),requestBytes:e.number().int().min(1),searchChars:e.number().int().min(1),searchTerms:e.number().int().min(1),tagsPerEntry:e.number().int().min(1),titleChars:e.number().int().min(1),vocabChars:e.number().int().min(1)}),E=e.object({dbSchemaVersion:e.number().int().min(0).nullable(),features:e.object({likes:e.boolean(),publishing:e.boolean()}),limits:T,reason:e.enum([`database-unavailable`,`schema-mismatch`]).nullable(),schemaVersion:e.literal(1),status:e.enum([`ok`,`degraded`])}),D=e.object({dbSchemaVersion:e.number().int().min(0).nullable(),reason:e.enum([`configuration-unavailable`,`database-unavailable`,`schema-mismatch`]).nullable(),schemaVersion:e.literal(1),status:e.enum([`ok`,`degraded`])}),O=[`account-changed`,`auth-unavailable`,`bad-request`,`conflict`,`forbidden`,`gone`,`internal`,`invalid-payload`,`likes-disabled`,`method-not-allowed`,`not-found`,`publisher-changed`,`publishing-disabled`,`quota`,`rate-limited`,`record-unavailable`,`timeout`,`too-large`,`unauthenticated`,`unavailable`,`unsupported-media`],k=e.object({error:e.object({code:e.enum(O),message:e.string().min(1).max(256),requestId:p}).strict()}).strict(),A=e.object({ok:e.literal(!0)}).strict(),j=e.object({filters:e.array(e.string().min(1).max(w.vocabChars)),name:e.string().regex(f),tags:e.array(e.string().min(1).max(w.vocabChars))}),M=e.object({cursor:e.string().nullable(),reports:e.array(e.object({createdAt:m,entryId:p,entryTitle:e.string().min(1).max(w.titleChars),id:p,reason:e.string().min(1).max(w.reportReasonChars),reporter:v,resolvedAt:m.nullable()}))}),N=e.object({cursor:e.string().nullable(),reports:e.array(e.object({collection:e.string().regex(f),createdAt:m,entryId:p,id:p,reason:e.string().min(1).max(w.reportReasonChars),resolvedAt:m.nullable()}))}),P=[`hide`,`unhide`,`remove`,`erase-publisher`],F=e.object({actions:e.array(e.object({action:e.enum(P),collection:e.string().regex(f).nullable(),createdAt:m,id:p,operatorId:p,targetId:p})),cursor:e.string().nullable()}),I=e.enum([`liked`,`recent`]);export{o as ACCOUNT_HEADER,f as ARCHIVE_COLLECTION_NAME_PATTERN,c as ARCHIVE_CONTRACT_VERSION,O as ARCHIVE_ERROR_CODES,w as ARCHIVE_LIMITS,P as ARCHIVE_MODERATION_ACTIONS,d as ARCHIVE_OPERATOR_ROLE,l as ARCHIVE_SCHEMA_VERSION,u as AUTH_SCHEMA_VERSION,g as ILL_FORMED_TEXT,i as OPERATOR_SESSION_MAX_AGE_SECONDS,a as PUBLISHER_HEADER,s as REQUEST_MARKER_HEADER,t as SESSION_COOKIE_NAME,n as SESSION_COOKIE_PATH,r as SESSION_MAX_AGE_SECONDS,b as archiveAccountSchema,_ as archiveAliasSchema,D as archiveAuthHealthSchema,v as archiveAuthorSchema,j as archiveCollectionMetaSchema,k as archiveErrorResponseSchema,E as archiveHealthSchema,y as archiveIdentitySchema,C as archiveLikeStateSchema,x as archiveLikedIdsSchema,S as archiveLikesPageSchema,T as archiveLimitsSchema,F as archiveModerationLogSchema,A as archiveOkSchema,N as archiveOwnReportsSchema,M as archiveReportsSchema,I as archiveSortSchema,h as hex64Schema,isWellFormedText,m as isoTimestampSchema,sessionCookieValue,p as uuidSchema};
2
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","names":[],"sources":["../src/contract.ts"],"sourcesContent":["import { type InferOutput, type Schema, s } from '@vielzeug/spell';\n\n/** Shared naming: one place so the client, Worker and issuer cannot drift. */\nexport const SESSION_COOKIE_NAME = 'archive_session';\nexport const SESSION_COOKIE_PATH = '/api/v1';\n/**\n * The maximum age a session JWT may carry, and the cookie Max-Age an issuer sets:\n * a long-lived signed-in state. The Worker rejects older tokens at this bound.\n */\nexport const SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;\nexport const OPERATOR_SESSION_MAX_AGE_SECONDS = 15 * 60;\n\n/**\n * The full Set-Cookie value for the session cookie: name, token and the security\n * attributes every issuer and the Worker must agree on — HttpOnly, SameSite=Strict,\n * the contract Path, Max-Age, and Secure in production. Issuers mint with the\n * session's max age; the Worker and issuers clear with `maxAgeSeconds: 0`. Never\n * hand-compose the attributes: the cookie's CSRF and theft posture is contract,\n * not per-issuer choice.\n */\nexport const sessionCookieValue = (token: string, options: { maxAgeSeconds: number; secure: boolean }): string =>\n `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Path=${SESSION_COOKIE_PATH}; Max-Age=${options.maxAgeSeconds}${options.secure ? '; Secure' : ''}`;\n\nexport const PUBLISHER_HEADER = 'x-archive-publisher';\n/** Pins account erasure to the issuer account shown when confirmation began. */\nexport const ACCOUNT_HEADER = 'X-Archive-Account';\nexport const REQUEST_MARKER_HEADER = 'x-archive-request';\n/** The wire-contract version advertised by health and stamped on every entry envelope. */\nexport const ARCHIVE_CONTRACT_VERSION = 1;\n/**\n * The storage schema version: the `archive_schema_meta` stamp the package's migration\n * writes and the Worker reads back through health. A mismatch answers `degraded`\n * instead of `ok` — a stale database is the most likely deployment fault.\n */\nexport const ARCHIVE_SCHEMA_VERSION = 1;\n/** Canonical issuer database initialization stamp. */\nexport const AUTH_SCHEMA_VERSION = 1;\n/** The `roles` claim value a session JWT carries for operator authority (moderation). */\nexport const ARCHIVE_OPERATOR_ROLE = 'archive-operator';\n/** The routable collection-name rule: lowercase `[a-z0-9-]`, at most 32 chars. */\nexport const ARCHIVE_COLLECTION_NAME_PATTERN = /^[a-z0-9-]{1,32}$/;\n\n// Exported schemas carry explicit public `Schema<Output>` annotations: the concrete\n// spell schema classes are not part of spell's public export surface, so inferred\n// types would not be portable across package declaration files.\nexport const uuidSchema: Schema<string> = s.string().uuid();\nexport const isoTimestampSchema: Schema<string> = s.string().isoDateTime();\nexport const hex64Schema: Schema<string> = s.string().regex(/^[0-9a-f]{64}$/);\n/** Well-formed user text: no lone UTF-16 surrogates, no C0/C1/DEL control characters. */\nexport const ILL_FORMED_TEXT = /[\\p{Cs}\\p{Cc}]/u;\nexport const isWellFormedText = (value: string): boolean => !ILL_FORMED_TEXT.test(value);\nconst textString = () => s.string().check(isWellFormedText);\n/** The one alias rule, shared by identity mutations and every emitted DTO. */\nexport const archiveAliasSchema: Schema<string> = textString().trim().max(24);\n\nexport const archiveAuthorSchema: Schema<{ id: string; name: string }> = s.object({\n id: uuidSchema,\n name: archiveAliasSchema,\n});\nexport const archiveIdentitySchema: Schema<{ id: string; name: string; operator: boolean } | null> = s\n .object({ id: uuidSchema, name: archiveAliasSchema, operator: s.boolean() })\n .nullable();\nexport type ArchiveIdentity = InferOutput<typeof archiveIdentitySchema>;\n/** The issuer account metadata export; its id is the session subject, not a publisher id. */\nexport const archiveAccountSchema: Schema<{\n createdAt: string;\n email: string;\n id: string;\n lastSignInAt: string | null;\n}> = s.object({\n createdAt: isoTimestampSchema,\n email: s.string().email(),\n id: uuidSchema,\n lastSignInAt: isoTimestampSchema.nullable(),\n});\nexport type ArchiveAccount = InferOutput<typeof archiveAccountSchema>;\nexport const archiveLikedIdsSchema: Schema<{ ids: string[] }> = s.object({ ids: s.array(uuidSchema) });\nexport const archiveLikesPageSchema: Schema<{ cursor: string | null; ids: string[] }> = s.object({\n cursor: s.string().nullable(),\n ids: s.array(uuidSchema),\n});\nexport type ArchiveLikesPage = InferOutput<typeof archiveLikesPageSchema>;\nexport const archiveLikeStateSchema: Schema<{ id: string; likeCount: number; liked: boolean }> = s.object({\n id: uuidSchema,\n likeCount: s.number().int().min(0).max(Number.MAX_SAFE_INTEGER),\n liked: s.boolean(),\n});\n\n/** Application bounds shared by handlers, the health route, and clients. */\nexport const ARCHIVE_LIMITS = Object.freeze({\n aliasChars: 24,\n cursorChars: 1024,\n idempotencyRetentionMs: 48 * 60 * 60 * 1000,\n likeStateIds: 50,\n maxPageSize: 50,\n maxPublications: 50,\n pageSize: 10,\n payloadBytes: 8192,\n publicCacheSeconds: 30,\n /** One free-text moderation reason: trimmed, non-empty, at most this many chars. */\n reportReasonChars: 256,\n requestBytes: 12 * 1024,\n searchChars: 64,\n searchTerms: 6,\n tagsPerEntry: 16,\n titleChars: 128,\n /** One declared filter or tag value: well-formed, non-empty, at most this many chars. */\n vocabChars: 64,\n});\nexport const archiveLimitsSchema: Schema<{\n aliasChars: number;\n cursorChars: number;\n idempotencyRetentionMs: number;\n likeStateIds: number;\n maxPageSize: number;\n maxPublications: number;\n pageSize: number;\n payloadBytes: number;\n publicCacheSeconds: number;\n reportReasonChars: number;\n requestBytes: number;\n searchChars: number;\n searchTerms: number;\n tagsPerEntry: number;\n titleChars: number;\n vocabChars: number;\n}> = s.object({\n aliasChars: s.number().int().min(1),\n cursorChars: s.number().int().min(1),\n idempotencyRetentionMs: s.number().int().min(1),\n likeStateIds: s.number().int().min(1),\n maxPageSize: s.number().int().min(1),\n maxPublications: s.number().int().min(1),\n pageSize: s.number().int().min(1),\n payloadBytes: s.number().int().min(1),\n publicCacheSeconds: s.number().int().min(0),\n reportReasonChars: s.number().int().min(1),\n requestBytes: s.number().int().min(1),\n searchChars: s.number().int().min(1),\n searchTerms: s.number().int().min(1),\n tagsPerEntry: s.number().int().min(1),\n titleChars: s.number().int().min(1),\n vocabChars: s.number().int().min(1),\n});\nexport const archiveHealthSchema: Schema<{\n /** The storage schema stamp (`archive_schema_meta`) the database actually carries. */\n dbSchemaVersion: number | null;\n features: { likes: boolean; publishing: boolean };\n limits: InferOutput<typeof archiveLimitsSchema>;\n schemaVersion: 1;\n /** 'degraded' when the database's storage schema does not match the Worker's. */\n status: 'ok' | 'degraded';\n reason: 'database-unavailable' | 'schema-mismatch' | null;\n}> = s.object({\n dbSchemaVersion: s.number().int().min(0).nullable(),\n features: s.object({ likes: s.boolean(), publishing: s.boolean() }),\n limits: archiveLimitsSchema,\n reason: s.enum(['database-unavailable', 'schema-mismatch'] as const).nullable(),\n schemaVersion: s.literal(ARCHIVE_CONTRACT_VERSION),\n status: s.enum(['ok', 'degraded'] as const),\n});\nexport type ArchiveHealth = InferOutput<typeof archiveHealthSchema>;\n\nexport const archiveAuthHealthSchema: Schema<{\n dbSchemaVersion: number | null;\n schemaVersion: 1;\n status: 'ok' | 'degraded';\n reason: 'configuration-unavailable' | 'database-unavailable' | 'schema-mismatch' | null;\n}> = s.object({\n dbSchemaVersion: s.number().int().min(0).nullable(),\n reason: s.enum(['configuration-unavailable', 'database-unavailable', 'schema-mismatch'] as const).nullable(),\n schemaVersion: s.literal(AUTH_SCHEMA_VERSION),\n status: s.enum(['ok', 'degraded'] as const),\n});\nexport type AuthHealth = InferOutput<typeof archiveAuthHealthSchema>;\n\n/** The Worker's full error-code vocabulary — the contract both sides test against. */\nexport const ARCHIVE_ERROR_CODES = [\n 'account-changed',\n 'auth-unavailable',\n 'bad-request',\n 'conflict',\n 'forbidden',\n 'gone',\n 'internal',\n 'invalid-payload',\n 'likes-disabled',\n 'method-not-allowed',\n 'not-found',\n 'publisher-changed',\n 'publishing-disabled',\n 'quota',\n 'rate-limited',\n 'record-unavailable',\n 'timeout',\n 'too-large',\n 'unauthenticated',\n 'unavailable',\n 'unsupported-media',\n] as const;\nexport type ArchiveErrorCode = (typeof ARCHIVE_ERROR_CODES)[number];\n\n/**\n * The error envelope every route answers failures with: `{ error: { code, message,\n * requestId } }`. Clients validate against this one schema — an envelope carrying an\n * unknown code or malformed fields is not an archive error at all.\n */\nexport const archiveErrorResponseSchema: Schema<{\n error: { code: ArchiveErrorCode; message: string; requestId: string };\n}> = s\n .object({\n error: s\n .object({\n code: s.enum(ARCHIVE_ERROR_CODES),\n message: s.string().min(1).max(256),\n requestId: uuidSchema,\n })\n .strict(),\n })\n .strict();\nexport type ArchiveErrorResponse = InferOutput<typeof archiveErrorResponseSchema>;\n\n/** The sign-out acknowledgement: the only non-collection response body, shared by both sides. */\nexport const archiveOkSchema: Schema<{ ok: true }> = s.object({ ok: s.literal(true) }).strict();\n\n/** One collection's public self-description: its name and declared query vocabularies. */\nexport const archiveCollectionMetaSchema: Schema<{\n filters: string[];\n name: string;\n tags: string[];\n}> = s.object({\n filters: s.array(s.string().min(1).max(ARCHIVE_LIMITS.vocabChars)),\n name: s.string().regex(ARCHIVE_COLLECTION_NAME_PATTERN),\n tags: s.array(s.string().min(1).max(ARCHIVE_LIMITS.vocabChars)),\n});\nexport type ArchiveCollectionMeta = InferOutput<typeof archiveCollectionMetaSchema>;\n\n/** One reader report an operator reviews: what was reported, why, and by whom. */\nexport const archiveReportsSchema: Schema<{\n cursor: string | null;\n reports: {\n id: string;\n createdAt: string;\n entryId: string;\n entryTitle: string;\n reason: string;\n reporter: { id: string; name: string };\n resolvedAt: string | null;\n }[];\n}> = s.object({\n cursor: s.string().nullable(),\n reports: s.array(\n s.object({\n createdAt: isoTimestampSchema,\n entryId: uuidSchema,\n entryTitle: s.string().min(1).max(ARCHIVE_LIMITS.titleChars),\n id: uuidSchema,\n reason: s.string().min(1).max(ARCHIVE_LIMITS.reportReasonChars),\n reporter: archiveAuthorSchema,\n resolvedAt: isoTimestampSchema.nullable(),\n }),\n ),\n});\nexport type ArchiveReports = InferOutput<typeof archiveReportsSchema>;\nexport const archiveOwnReportsSchema: Schema<{\n cursor: string | null;\n reports: {\n collection: string;\n createdAt: string;\n entryId: string;\n id: string;\n reason: string;\n resolvedAt: string | null;\n }[];\n}> = s.object({\n cursor: s.string().nullable(),\n reports: s.array(\n s.object({\n collection: s.string().regex(ARCHIVE_COLLECTION_NAME_PATTERN),\n createdAt: isoTimestampSchema,\n entryId: uuidSchema,\n id: uuidSchema,\n reason: s.string().min(1).max(ARCHIVE_LIMITS.reportReasonChars),\n resolvedAt: isoTimestampSchema.nullable(),\n }),\n ),\n});\nexport type ArchiveOwnReports = InferOutput<typeof archiveOwnReportsSchema>;\n\n/** The moderation actions an operator can take; the audit log's whole vocabulary. */\nexport const ARCHIVE_MODERATION_ACTIONS = ['hide', 'unhide', 'remove', 'erase-publisher'] as const;\nexport type ArchiveModerationAction = (typeof ARCHIVE_MODERATION_ACTIONS)[number];\n\n/**\n * The operator accountability log: one row per effective moderation action, newest\n * first. Operator and target travel as opaque ids only — no names (they change), no\n * content — so the log stays honest after renames and survives the erasures it records.\n */\nexport const archiveModerationLogSchema: Schema<{\n cursor: string | null;\n actions: {\n id: string;\n action: ArchiveModerationAction;\n collection: string | null;\n createdAt: string;\n operatorId: string;\n targetId: string;\n }[];\n}> = s.object({\n actions: s.array(\n s.object({\n action: s.enum(ARCHIVE_MODERATION_ACTIONS),\n collection: s.string().regex(ARCHIVE_COLLECTION_NAME_PATTERN).nullable(),\n createdAt: isoTimestampSchema,\n id: uuidSchema,\n operatorId: uuidSchema,\n targetId: uuidSchema,\n }),\n ),\n cursor: s.string().nullable(),\n});\nexport type ArchiveModerationLog = InferOutput<typeof archiveModerationLogSchema>;\n\n/** How the online list ranks entries: explicit modes only, no vague composites. */\nexport type ArchiveSort = 'liked' | 'recent';\nexport const archiveSortSchema: Schema<ArchiveSort> = s.enum(['liked', 'recent'] as const);\n\n/** One page of archive entries plus forward/backward cursors around it. */\nexport interface ArchivePage<T> {\n /** Cursor for the next page; null after the last. */\n cursor: string | null;\n entries: readonly T[];\n /** Cursor for the previous page; null when already at the top. */\n previous: string | null;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var ArchiveError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},ArchiveDefinitionError=class extends ArchiveError{},ArchiveDerivationError=class extends ArchiveError{stage;constructor(e,t,n){super(t,n),this.stage=e}};exports.ArchiveDefinitionError=ArchiveDefinitionError,exports.ArchiveDerivationError=ArchiveDerivationError,exports.ArchiveError=ArchiveError;
2
+ //# sourceMappingURL=errors.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["export class ArchiveError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class ArchiveDefinitionError extends ArchiveError {}\n\nexport type ArchiveDerivationStage = 'schema' | 'canonicalize' | 'index' | 'hash' | 'response';\n\n/** A collection callback or core runtime fault, not invalid submitted content. */\nexport class ArchiveDerivationError extends ArchiveError {\n readonly stage: ArchiveDerivationStage;\n\n constructor(stage: ArchiveDerivationStage, message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.stage = stage;\n }\n}\n"],"mappings":"AAAA,IAAa,aAAb,cAAkC,KAAM,CACtC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAEa,uBAAb,cAA4C,YAAa,CAAC,EAK7C,uBAAb,cAA4C,YAAa,CACvD,MAEA,YAAY,EAA+B,EAAiB,EAAqB,CAC/E,MAAM,EAAS,CAAI,EACnB,KAAK,MAAQ,CACf,CACF"}
@@ -0,0 +1,12 @@
1
+ export declare class ArchiveError extends Error {
2
+ constructor(message: string, opts?: ErrorOptions);
3
+ }
4
+ export declare class ArchiveDefinitionError extends ArchiveError {
5
+ }
6
+ export type ArchiveDerivationStage = 'schema' | 'canonicalize' | 'index' | 'hash' | 'response';
7
+ /** A collection callback or core runtime fault, not invalid submitted content. */
8
+ export declare class ArchiveDerivationError extends ArchiveError {
9
+ readonly stage: ArchiveDerivationStage;
10
+ constructor(stage: ArchiveDerivationStage, message: string, opts?: ErrorOptions);
11
+ }
12
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAKjD;AAED,qBAAa,sBAAuB,SAAQ,YAAY;CAAG;AAE3D,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,cAAc,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/F,kFAAkF;AAClF,qBAAa,sBAAuB,SAAQ,YAAY;IACtD,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;gBAE3B,KAAK,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAIhF"}
package/dist/errors.js ADDED
@@ -0,0 +1,2 @@
1
+ var ArchiveError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},ArchiveDefinitionError=class extends ArchiveError{},ArchiveDerivationError=class extends ArchiveError{stage;constructor(e,t,n){super(t,n),this.stage=e}};export{ArchiveDefinitionError,ArchiveDerivationError,ArchiveError};
2
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["export class ArchiveError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class ArchiveDefinitionError extends ArchiveError {}\n\nexport type ArchiveDerivationStage = 'schema' | 'canonicalize' | 'index' | 'hash' | 'response';\n\n/** A collection callback or core runtime fault, not invalid submitted content. */\nexport class ArchiveDerivationError extends ArchiveError {\n readonly stage: ArchiveDerivationStage;\n\n constructor(stage: ArchiveDerivationStage, message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.stage = stage;\n }\n}\n"],"mappings":"AAAA,IAAa,aAAb,cAAkC,KAAM,CACtC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAEa,uBAAb,cAA4C,YAAa,CAAC,EAK7C,uBAAb,cAA4C,YAAa,CACvD,MAEA,YAAY,EAA+B,EAAiB,EAAqB,CAC/E,MAAM,EAAS,CAAI,EACnB,KAAK,MAAQ,CACf,CACF"}
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./contract.cjs"),t=require("./errors.cjs"),n=require("./collection.cjs");exports.ACCOUNT_HEADER=e.ACCOUNT_HEADER,exports.ARCHIVE_COLLECTION_NAME_PATTERN=e.ARCHIVE_COLLECTION_NAME_PATTERN,exports.ARCHIVE_CONTRACT_VERSION=e.ARCHIVE_CONTRACT_VERSION,exports.ARCHIVE_ERROR_CODES=e.ARCHIVE_ERROR_CODES,exports.ARCHIVE_LIMITS=e.ARCHIVE_LIMITS,exports.ARCHIVE_MODERATION_ACTIONS=e.ARCHIVE_MODERATION_ACTIONS,exports.ARCHIVE_OPERATOR_ROLE=e.ARCHIVE_OPERATOR_ROLE,exports.ARCHIVE_SCHEMA_VERSION=e.ARCHIVE_SCHEMA_VERSION,exports.AUTH_SCHEMA_VERSION=e.AUTH_SCHEMA_VERSION,exports.ArchiveDefinitionError=t.ArchiveDefinitionError,exports.ArchiveDerivationError=t.ArchiveDerivationError,exports.ArchiveError=t.ArchiveError,exports.ILL_FORMED_TEXT=e.ILL_FORMED_TEXT,exports.OPERATOR_SESSION_MAX_AGE_SECONDS=e.OPERATOR_SESSION_MAX_AGE_SECONDS,exports.PUBLISHER_HEADER=e.PUBLISHER_HEADER,exports.REQUEST_MARKER_HEADER=e.REQUEST_MARKER_HEADER,exports.SESSION_COOKIE_NAME=e.SESSION_COOKIE_NAME,exports.SESSION_COOKIE_PATH=e.SESSION_COOKIE_PATH,exports.SESSION_MAX_AGE_SECONDS=e.SESSION_MAX_AGE_SECONDS,exports.archiveAccountSchema=e.archiveAccountSchema,exports.archiveAliasSchema=e.archiveAliasSchema,exports.archiveAuthHealthSchema=e.archiveAuthHealthSchema,exports.archiveAuthorSchema=e.archiveAuthorSchema,exports.archiveCollectionMetaSchema=e.archiveCollectionMetaSchema,exports.archiveErrorResponseSchema=e.archiveErrorResponseSchema,exports.archiveHealthSchema=e.archiveHealthSchema,exports.archiveIdentitySchema=e.archiveIdentitySchema,exports.archiveLikeStateSchema=e.archiveLikeStateSchema,exports.archiveLikedIdsSchema=e.archiveLikedIdsSchema,exports.archiveLikesPageSchema=e.archiveLikesPageSchema,exports.archiveLimitsSchema=e.archiveLimitsSchema,exports.archiveModerationLogSchema=e.archiveModerationLogSchema,exports.archiveOkSchema=e.archiveOkSchema,exports.archiveOwnReportsSchema=e.archiveOwnReportsSchema,exports.archiveReportsSchema=e.archiveReportsSchema,exports.archiveSortSchema=e.archiveSortSchema,exports.defineArchiveCollection=n.defineArchiveCollection,exports.hex64Schema=e.hex64Schema,exports.isWellFormedText=e.isWellFormedText,exports.isoTimestampSchema=e.isoTimestampSchema,exports.sessionCookieValue=e.sessionCookieValue,exports.uuidSchema=e.uuidSchema;
@@ -0,0 +1,4 @@
1
+ export * from './collection';
2
+ export * from './contract';
3
+ export * from './errors';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{ACCOUNT_HEADER as e,ARCHIVE_COLLECTION_NAME_PATTERN as t,ARCHIVE_CONTRACT_VERSION as n,ARCHIVE_ERROR_CODES as r,ARCHIVE_LIMITS as i,ARCHIVE_MODERATION_ACTIONS as a,ARCHIVE_OPERATOR_ROLE as o,ARCHIVE_SCHEMA_VERSION as s,AUTH_SCHEMA_VERSION as c,ILL_FORMED_TEXT as l,OPERATOR_SESSION_MAX_AGE_SECONDS as u,PUBLISHER_HEADER as d,REQUEST_MARKER_HEADER as f,SESSION_COOKIE_NAME as p,SESSION_COOKIE_PATH as m,SESSION_MAX_AGE_SECONDS as h,archiveAccountSchema as g,archiveAliasSchema as _,archiveAuthHealthSchema as v,archiveAuthorSchema as y,archiveCollectionMetaSchema as b,archiveErrorResponseSchema as x,archiveHealthSchema as S,archiveIdentitySchema as C,archiveLikeStateSchema as w,archiveLikedIdsSchema as T,archiveLikesPageSchema as E,archiveLimitsSchema as D,archiveModerationLogSchema as O,archiveOkSchema as k,archiveOwnReportsSchema as A,archiveReportsSchema as j,archiveSortSchema as M,hex64Schema as N,isWellFormedText as P,isoTimestampSchema as F,sessionCookieValue as I,uuidSchema as L}from"./contract.js";import{ArchiveDefinitionError as R,ArchiveDerivationError as z,ArchiveError as B}from"./errors.js";import{defineArchiveCollection as V}from"./collection.js";export{e as ACCOUNT_HEADER,t as ARCHIVE_COLLECTION_NAME_PATTERN,n as ARCHIVE_CONTRACT_VERSION,r as ARCHIVE_ERROR_CODES,i as ARCHIVE_LIMITS,a as ARCHIVE_MODERATION_ACTIONS,o as ARCHIVE_OPERATOR_ROLE,s as ARCHIVE_SCHEMA_VERSION,c as AUTH_SCHEMA_VERSION,R as ArchiveDefinitionError,z as ArchiveDerivationError,B as ArchiveError,l as ILL_FORMED_TEXT,u as OPERATOR_SESSION_MAX_AGE_SECONDS,d as PUBLISHER_HEADER,f as REQUEST_MARKER_HEADER,p as SESSION_COOKIE_NAME,m as SESSION_COOKIE_PATH,h as SESSION_MAX_AGE_SECONDS,g as archiveAccountSchema,_ as archiveAliasSchema,v as archiveAuthHealthSchema,y as archiveAuthorSchema,b as archiveCollectionMetaSchema,x as archiveErrorResponseSchema,S as archiveHealthSchema,C as archiveIdentitySchema,w as archiveLikeStateSchema,T as archiveLikedIdsSchema,E as archiveLikesPageSchema,D as archiveLimitsSchema,O as archiveModerationLogSchema,k as archiveOkSchema,A as archiveOwnReportsSchema,j as archiveReportsSchema,M as archiveSortSchema,V as defineArchiveCollection,N as hex64Schema,P as isWellFormedText,F as isoTimestampSchema,I as sessionCookieValue,L as uuidSchema};
@@ -0,0 +1,2 @@
1
+ const e=require("../contract.cjs"),t=require("./http.cjs");let n=require("@vielzeug/spell");var encodeCursorValue=e=>{let t=new TextEncoder().encode(JSON.stringify(e));return btoa(Array.from(t,e=>String.fromCharCode(e)).join(``)).replaceAll(`+`,`-`).replaceAll(`/`,`_`).replaceAll(`=`,``)};function decodeCursorValue(n,r){if(n.length>e.ARCHIVE_LIMITS.cursorChars||!/^[A-Za-z0-9_-]+$/.test(n))throw t.badRequest();let i;try{let e=n.replaceAll(`-`,`+`).replaceAll(`_`,`/`),t=Uint8Array.from(atob(e+`=`.repeat((4-e.length%4)%4)),e=>e.charCodeAt(0));i=JSON.parse(new TextDecoder(`utf-8`,{fatal:!0}).decode(t))}catch{throw t.badRequest()}let a=r.safeParse(i);if(!a.success||encodeCursorValue(i)!==n)throw t.badRequest();return a.data}var r=n.s.object({context:n.s.string(),createdAt:e.isoTimestampSchema,id:e.uuidSchema});function forwardPage(n,i,a=[]){let o=t.queryParams(new URL(n.url),[`cursor`,`limit`,...a]),s=o.get(`limit`),c=s===null?e.ARCHIVE_LIMITS.pageSize:Number(s);if(!Number.isInteger(c)||c<1||c>e.ARCHIVE_LIMITS.maxPageSize)throw t.badRequest();let l=o.get(`cursor`),u=l===null?null:decodeCursorValue(l,r);if(u!==null&&u.context!==i)throw t.badRequest();return{cursor:u,limit:c,params:o}}var nextCursor=(e,t)=>encodeCursorValue({context:t,createdAt:e.createdAt,id:e.id});exports.decodeCursorValue=decodeCursorValue,exports.encodeCursorValue=encodeCursorValue,exports.forwardPage=forwardPage,exports.nextCursor=nextCursor;
2
+ //# sourceMappingURL=_pagination.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_pagination.cjs","names":[],"sources":["../../src/worker/_pagination.ts"],"sourcesContent":["import { type Schema, s } from '@vielzeug/spell';\nimport { ARCHIVE_LIMITS, isoTimestampSchema, uuidSchema } from '../contract';\nimport { badRequest, queryParams } from './http';\n\nexport const encodeCursorValue = (value: unknown): string => {\n const bytes = new TextEncoder().encode(JSON.stringify(value));\n return btoa(Array.from(bytes, (byte) => String.fromCharCode(byte)).join(''))\n .replaceAll('+', '-')\n .replaceAll('/', '_')\n .replaceAll('=', '');\n};\n\nexport function decodeCursorValue<T>(raw: string, schema: Schema<T>): T {\n if (raw.length > ARCHIVE_LIMITS.cursorChars || !/^[A-Za-z0-9_-]+$/.test(raw)) throw badRequest();\n let value: unknown;\n try {\n const base64 = raw.replaceAll('-', '+').replaceAll('_', '/');\n const bytes = Uint8Array.from(atob(base64 + '='.repeat((4 - (base64.length % 4)) % 4)), (char) =>\n char.charCodeAt(0),\n );\n value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));\n } catch {\n throw badRequest();\n }\n const parsed = schema.safeParse(value);\n if (!parsed.success || encodeCursorValue(value) !== raw) throw badRequest();\n return parsed.data;\n}\n\nconst forwardCursorSchema = s.object({\n context: s.string(),\n createdAt: isoTimestampSchema,\n id: uuidSchema,\n});\n\nexport interface ForwardCursor {\n createdAt: string;\n id: string;\n}\n\nexport function forwardPage(request: Request, context: string, extra: readonly string[] = []) {\n const params = queryParams(new URL(request.url), ['cursor', 'limit', ...extra]);\n const rawLimit = params.get('limit');\n const limit = rawLimit === null ? ARCHIVE_LIMITS.pageSize : Number(rawLimit);\n if (!Number.isInteger(limit) || limit < 1 || limit > ARCHIVE_LIMITS.maxPageSize) throw badRequest();\n const raw = params.get('cursor');\n const cursor = raw === null ? null : decodeCursorValue(raw, forwardCursorSchema);\n if (cursor !== null && cursor.context !== context) throw badRequest();\n return { cursor, limit, params };\n}\n\nexport const nextCursor = (row: ForwardCursor, context: string): string =>\n encodeCursorValue({ context, createdAt: row.createdAt, id: row.id });\n"],"mappings":"4FAIA,IAAa,kBAAqB,GAA2B,CAC3D,IAAM,EAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,UAAU,CAAK,CAAC,EAC5D,OAAO,KAAK,MAAM,KAAK,EAAQ,GAAS,OAAO,aAAa,CAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CACzE,WAAW,IAAK,GAAG,CAAC,CACpB,WAAW,IAAK,GAAG,CAAC,CACpB,WAAW,IAAK,EAAE,CACvB,EAEA,SAAgB,kBAAqB,EAAa,EAAsB,CACtE,GAAI,EAAI,OAAS,EAAA,eAAe,aAAe,CAAC,mBAAmB,KAAK,CAAG,EAAG,MAAM,EAAA,WAAW,EAC/F,IAAI,EACJ,GAAI,CACF,IAAM,EAAS,EAAI,WAAW,IAAK,GAAG,CAAC,CAAC,WAAW,IAAK,GAAG,EACrD,EAAQ,WAAW,KAAK,KAAK,EAAS,IAAI,QAAQ,EAAK,EAAO,OAAS,GAAM,CAAC,CAAC,EAAI,GACvF,EAAK,WAAW,CAAC,CACnB,EACA,EAAQ,KAAK,MAAM,IAAI,YAAY,QAAS,CAAE,MAAO,EAAK,CAAC,CAAC,CAAC,OAAO,CAAK,CAAC,CAC5E,MAAQ,CACN,MAAM,EAAA,WAAW,CACnB,CACA,IAAM,EAAS,EAAO,UAAU,CAAK,EACrC,GAAI,CAAC,EAAO,SAAW,kBAAkB,CAAK,IAAM,EAAK,MAAM,EAAA,WAAW,EAC1E,OAAO,EAAO,IAChB,CAEA,IAAM,EAAsB,EAAA,EAAE,OAAO,CACnC,QAAS,EAAA,EAAE,OAAO,EAClB,UAAW,EAAA,mBACX,GAAI,EAAA,UACN,CAAC,EAOD,SAAgB,YAAY,EAAkB,EAAiB,EAA2B,CAAC,EAAG,CAC5F,IAAM,EAAS,EAAA,YAAY,IAAI,IAAI,EAAQ,GAAG,EAAG,CAAC,SAAU,QAAS,GAAG,CAAK,CAAC,EACxE,EAAW,EAAO,IAAI,OAAO,EAC7B,EAAQ,IAAa,KAAO,EAAA,eAAe,SAAW,OAAO,CAAQ,EAC3E,GAAI,CAAC,OAAO,UAAU,CAAK,GAAK,EAAQ,GAAK,EAAQ,EAAA,eAAe,YAAa,MAAM,EAAA,WAAW,EAClG,IAAM,EAAM,EAAO,IAAI,QAAQ,EACzB,EAAS,IAAQ,KAAO,KAAO,kBAAkB,EAAK,CAAmB,EAC/E,GAAI,IAAW,MAAQ,EAAO,UAAY,EAAS,MAAM,EAAA,WAAW,EACpE,MAAO,CAAE,SAAQ,QAAO,QAAO,CACjC,CAEA,IAAa,YAAc,EAAoB,IAC7C,kBAAkB,CAAE,UAAS,UAAW,EAAI,UAAW,GAAI,EAAI,EAAG,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type Schema } from '@vielzeug/spell';
2
+ export declare const encodeCursorValue: (value: unknown) => string;
3
+ export declare function decodeCursorValue<T>(raw: string, schema: Schema<T>): T;
4
+ export interface ForwardCursor {
5
+ createdAt: string;
6
+ id: string;
7
+ }
8
+ export declare function forwardPage(request: Request, context: string, extra?: readonly string[]): {
9
+ cursor: {
10
+ id: string;
11
+ createdAt: string;
12
+ context: string;
13
+ } | null;
14
+ limit: number;
15
+ params: URLSearchParams;
16
+ };
17
+ export declare const nextCursor: (row: ForwardCursor, context: string) => string;
18
+ //# sourceMappingURL=_pagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_pagination.d.ts","sourceRoot":"","sources":["../../src/worker/_pagination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAK,MAAM,iBAAiB,CAAC;AAIjD,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,MAMlD,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAetE;AAQD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,SAAS,MAAM,EAAO;;;;;;;;EAS3F;AAED,eAAO,MAAM,UAAU,GAAI,KAAK,aAAa,EAAE,SAAS,MAAM,KAAG,MACK,CAAC"}
@@ -0,0 +1,2 @@
1
+ import{ARCHIVE_LIMITS as e,isoTimestampSchema as t,uuidSchema as n}from"../contract.js";import{badRequest as r,queryParams as i}from"./http.js";import{s as a}from"@vielzeug/spell";var encodeCursorValue=e=>{let t=new TextEncoder().encode(JSON.stringify(e));return btoa(Array.from(t,e=>String.fromCharCode(e)).join(``)).replaceAll(`+`,`-`).replaceAll(`/`,`_`).replaceAll(`=`,``)};function decodeCursorValue(t,n){if(t.length>e.cursorChars||!/^[A-Za-z0-9_-]+$/.test(t))throw r();let i;try{let e=t.replaceAll(`-`,`+`).replaceAll(`_`,`/`),n=Uint8Array.from(atob(e+`=`.repeat((4-e.length%4)%4)),e=>e.charCodeAt(0));i=JSON.parse(new TextDecoder(`utf-8`,{fatal:!0}).decode(n))}catch{throw r()}let a=n.safeParse(i);if(!a.success||encodeCursorValue(i)!==t)throw r();return a.data}var o=a.object({context:a.string(),createdAt:t,id:n});function forwardPage(t,n,a=[]){let s=i(new URL(t.url),[`cursor`,`limit`,...a]),c=s.get(`limit`),l=c===null?e.pageSize:Number(c);if(!Number.isInteger(l)||l<1||l>e.maxPageSize)throw r();let u=s.get(`cursor`),d=u===null?null:decodeCursorValue(u,o);if(d!==null&&d.context!==n)throw r();return{cursor:d,limit:l,params:s}}var nextCursor=(e,t)=>encodeCursorValue({context:t,createdAt:e.createdAt,id:e.id});export{decodeCursorValue,encodeCursorValue,forwardPage,nextCursor};
2
+ //# sourceMappingURL=_pagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_pagination.js","names":[],"sources":["../../src/worker/_pagination.ts"],"sourcesContent":["import { type Schema, s } from '@vielzeug/spell';\nimport { ARCHIVE_LIMITS, isoTimestampSchema, uuidSchema } from '../contract';\nimport { badRequest, queryParams } from './http';\n\nexport const encodeCursorValue = (value: unknown): string => {\n const bytes = new TextEncoder().encode(JSON.stringify(value));\n return btoa(Array.from(bytes, (byte) => String.fromCharCode(byte)).join(''))\n .replaceAll('+', '-')\n .replaceAll('/', '_')\n .replaceAll('=', '');\n};\n\nexport function decodeCursorValue<T>(raw: string, schema: Schema<T>): T {\n if (raw.length > ARCHIVE_LIMITS.cursorChars || !/^[A-Za-z0-9_-]+$/.test(raw)) throw badRequest();\n let value: unknown;\n try {\n const base64 = raw.replaceAll('-', '+').replaceAll('_', '/');\n const bytes = Uint8Array.from(atob(base64 + '='.repeat((4 - (base64.length % 4)) % 4)), (char) =>\n char.charCodeAt(0),\n );\n value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));\n } catch {\n throw badRequest();\n }\n const parsed = schema.safeParse(value);\n if (!parsed.success || encodeCursorValue(value) !== raw) throw badRequest();\n return parsed.data;\n}\n\nconst forwardCursorSchema = s.object({\n context: s.string(),\n createdAt: isoTimestampSchema,\n id: uuidSchema,\n});\n\nexport interface ForwardCursor {\n createdAt: string;\n id: string;\n}\n\nexport function forwardPage(request: Request, context: string, extra: readonly string[] = []) {\n const params = queryParams(new URL(request.url), ['cursor', 'limit', ...extra]);\n const rawLimit = params.get('limit');\n const limit = rawLimit === null ? ARCHIVE_LIMITS.pageSize : Number(rawLimit);\n if (!Number.isInteger(limit) || limit < 1 || limit > ARCHIVE_LIMITS.maxPageSize) throw badRequest();\n const raw = params.get('cursor');\n const cursor = raw === null ? null : decodeCursorValue(raw, forwardCursorSchema);\n if (cursor !== null && cursor.context !== context) throw badRequest();\n return { cursor, limit, params };\n}\n\nexport const nextCursor = (row: ForwardCursor, context: string): string =>\n encodeCursorValue({ context, createdAt: row.createdAt, id: row.id });\n"],"mappings":"oLAIA,IAAa,kBAAqB,GAA2B,CAC3D,IAAM,EAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,UAAU,CAAK,CAAC,EAC5D,OAAO,KAAK,MAAM,KAAK,EAAQ,GAAS,OAAO,aAAa,CAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CACzE,WAAW,IAAK,GAAG,CAAC,CACpB,WAAW,IAAK,GAAG,CAAC,CACpB,WAAW,IAAK,EAAE,CACvB,EAEA,SAAgB,kBAAqB,EAAa,EAAsB,CACtE,GAAI,EAAI,OAAS,EAAe,aAAe,CAAC,mBAAmB,KAAK,CAAG,EAAG,MAAM,EAAW,EAC/F,IAAI,EACJ,GAAI,CACF,IAAM,EAAS,EAAI,WAAW,IAAK,GAAG,CAAC,CAAC,WAAW,IAAK,GAAG,EACrD,EAAQ,WAAW,KAAK,KAAK,EAAS,IAAI,QAAQ,EAAK,EAAO,OAAS,GAAM,CAAC,CAAC,EAAI,GACvF,EAAK,WAAW,CAAC,CACnB,EACA,EAAQ,KAAK,MAAM,IAAI,YAAY,QAAS,CAAE,MAAO,EAAK,CAAC,CAAC,CAAC,OAAO,CAAK,CAAC,CAC5E,MAAQ,CACN,MAAM,EAAW,CACnB,CACA,IAAM,EAAS,EAAO,UAAU,CAAK,EACrC,GAAI,CAAC,EAAO,SAAW,kBAAkB,CAAK,IAAM,EAAK,MAAM,EAAW,EAC1E,OAAO,EAAO,IAChB,CAEA,IAAM,EAAsB,EAAE,OAAO,CACnC,QAAS,EAAE,OAAO,EAClB,UAAW,EACX,GAAI,CACN,CAAC,EAOD,SAAgB,YAAY,EAAkB,EAAiB,EAA2B,CAAC,EAAG,CAC5F,IAAM,EAAS,EAAY,IAAI,IAAI,EAAQ,GAAG,EAAG,CAAC,SAAU,QAAS,GAAG,CAAK,CAAC,EACxE,EAAW,EAAO,IAAI,OAAO,EAC7B,EAAQ,IAAa,KAAO,EAAe,SAAW,OAAO,CAAQ,EAC3E,GAAI,CAAC,OAAO,UAAU,CAAK,GAAK,EAAQ,GAAK,EAAQ,EAAe,YAAa,MAAM,EAAW,EAClG,IAAM,EAAM,EAAO,IAAI,QAAQ,EACzB,EAAS,IAAQ,KAAO,KAAO,kBAAkB,EAAK,CAAmB,EAC/E,GAAI,IAAW,MAAQ,EAAO,UAAY,EAAS,MAAM,EAAW,EACpE,MAAO,CAAE,SAAQ,QAAO,QAAO,CACjC,CAEA,IAAa,YAAc,EAAoB,IAC7C,kBAAkB,CAAE,UAAS,UAAW,EAAI,UAAW,GAAI,EAAI,EAAG,CAAC"}
@@ -0,0 +1,2 @@
1
+ async function serviceRequest(e,t,n){let r=new AbortController,i;try{return await Promise.race([e.fetch(new Request(t,{signal:r.signal})).then(n),new Promise((e,t)=>{i=setTimeout(()=>{r.abort(),t(Error(`Service request timed out.`))},5e3)})])}finally{i!==void 0&&clearTimeout(i)}}exports.serviceRequest=serviceRequest;
2
+ //# sourceMappingURL=_service.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_service.cjs","names":[],"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,IAAM,EAAa,IAAI,gBACnB,EACJ,GAAI,CACF,OAAO,MAAM,QAAQ,KAAK,CACxB,EAAQ,MAAM,IAAI,QAAQ,EAAS,CAAE,OAAQ,EAAW,MAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAO,EAC/E,IAAI,SAAgB,EAAU,IAAW,CACvC,EAAQ,eAAiB,CACvB,EAAW,MAAM,EACjB,EAAW,MAAM,4BAA4B,CAAC,CAChD,EAAG,GAAI,CACT,CAAC,CACH,CAAC,CACH,QAAU,CACJ,IAAU,IAAA,IAAW,aAAa,CAAK,CAC7C,CACF"}
@@ -0,0 +1,5 @@
1
+ /** Bound the complete service exchange, including response-body decoding. */
2
+ export declare function serviceRequest<T>(binding: {
3
+ fetch(request: Request): Promise<Response>;
4
+ }, request: Request, consume: (response: Response) => Promise<T>): Promise<T>;
5
+ //# sourceMappingURL=_service.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,2 @@
1
+ async function serviceRequest(e,t,n){let r=new AbortController,i;try{return await Promise.race([e.fetch(new Request(t,{signal:r.signal})).then(n),new Promise((e,t)=>{i=setTimeout(()=>{r.abort(),t(Error(`Service request timed out.`))},5e3)})])}finally{i!==void 0&&clearTimeout(i)}}export{serviceRequest};
2
+ //# sourceMappingURL=_service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_service.js","names":[],"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,IAAM,EAAa,IAAI,gBACnB,EACJ,GAAI,CACF,OAAO,MAAM,QAAQ,KAAK,CACxB,EAAQ,MAAM,IAAI,QAAQ,EAAS,CAAE,OAAQ,EAAW,MAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAO,EAC/E,IAAI,SAAgB,EAAU,IAAW,CACvC,EAAQ,eAAiB,CACvB,EAAW,MAAM,EACjB,EAAW,MAAM,4BAA4B,CAAC,CAChD,EAAG,GAAI,CACT,CAAC,CACH,CAAC,CACH,QAAU,CACJ,IAAU,IAAA,IAAW,aAAa,CAAK,CAC7C,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../contract.cjs"),t=require("./_service.cjs"),n=require("./http.cjs"),r=require("./sql.cjs");let i=require("jose");var a=e.SESSION_MAX_AGE_SECONDS,o=256,authUnavailable=()=>new n.ApiError(503,`auth-unavailable`,`Sign-in is currently unavailable.`);async function verifySession(s,c,l,u=!1){let d=n.sessionToken(s);if(d===null)return null;let f;try{f=(await(0,i.jwtVerify)(d,c.jwks,{algorithms:[`RS256`],audience:c.audience,clockTolerance:5,issuer:c.issuer,maxTokenAge:a,requiredClaims:[`sub`,`iss`,`aud`,`iat`,`exp`,`sessionVersion`]})).payload}catch(e){throw e instanceof i.errors.JWKSTimeout||e instanceof i.errors.JWKSInvalid||e instanceof i.errors.JWKSMultipleMatchingKeys?authUnavailable():e instanceof i.errors.JOSEError?n.unauthenticated():authUnavailable()}let p=f.sub,m=f.iss,h=f.iat,g=f.roles;if(!Number.isSafeInteger(f.sessionVersion)||f.sessionVersion<0)throw n.unauthenticated();let _=Array.isArray(g)&&g.includes(`archive-operator`);if(typeof p!=`string`||p.length===0||p.length>o||!e.isWellFormedText(p)||typeof m!=`string`||typeof h==`number`&&h*1e3>Date.now()+5e3||_&&(typeof h!=`number`||typeof f.exp!=`number`||f.exp-h>900||Date.now()/1e3-h>905))throw n.unauthenticated();if(!(u&&await l.DB.prepare(r.SQL.revokedSubject).bind(m,p,Date.now()).first()!==null))try{await t.serviceRequest(l.AUTH_ISSUER_SERVICE,new Request(new URL(`/api/v1/auth/session`,c.issuer),{headers:{cookie:`archive_session=${d}`},redirect:`manual`}),async t=>{let r=await t.json();if(t.status===200&&e.archiveOkSchema.safeParse(r).success)return;let i=e.archiveErrorResponseSchema.safeParse(r);throw t.status===401&&i.success&&i.data.error.code===`unauthenticated`?n.unauthenticated():authUnavailable()})}catch(e){throw e instanceof n.ApiError&&e.status===401?e:authUnavailable()}if(!u&&await l.DB.prepare(r.SQL.revokedSubject).bind(m,p,Date.now()).first()!==null)throw n.unauthenticated();let v=await l.DB.prepare(r.SQL.publisher).bind(m,p).first();return{operator:_,publisher:v===null?null:{displayName:v.display_name,id:v.id},subject:{issuer:m,subject:p}}}function requireSession(e){if(e===null)throw n.unauthenticated();return e}exports.requireSession=requireSession,exports.verifySession=verifySession;
2
+ //# sourceMappingURL=auth.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.cjs","names":[],"sources":["../../src/worker/auth.ts"],"sourcesContent":["import { errors, type JWTPayload, jwtVerify } from 'jose';\nimport {\n ARCHIVE_OPERATOR_ROLE,\n archiveErrorResponseSchema,\n archiveOkSchema,\n isWellFormedText,\n OPERATOR_SESSION_MAX_AGE_SECONDS,\n SESSION_MAX_AGE_SECONDS,\n} from '../contract';\nimport { serviceRequest } from './_service';\nimport type { ApiConfig, ArchiveWorkerEnv } from './env';\nimport { ApiError, sessionToken, unauthenticated } from './http';\nimport { SQL } from './sql';\n\n/**\n * Session authentication. The Worker trusts exactly one source: the archive_session\n * HttpOnly cookie holding an RS256 JWT from the configured issuer. Tokens are verified\n * before any database read and are never echoed into DTOs; the publisher row maps the\n * opaque (issuer, subject) pair to the public publisher id. An optional `roles` claim\n * from the same trusted issuer grants operator authority (moderation routes).\n */\nexport interface VerifiedSubject {\n issuer: string;\n subject: string;\n}\n\nexport interface Publisher {\n displayName: string;\n id: string;\n}\n\nexport interface Session {\n /** True only when the verified token's `roles` claim carries the operator role. */\n operator: boolean;\n /** The registered publisher row; null while the verified user has not registered. */\n publisher: Publisher | null;\n subject: VerifiedSubject;\n}\n\nconst JWT_MAX_AGE_SECONDS = SESSION_MAX_AGE_SECONDS;\nexport const JWT_CLOCK_TOLERANCE_SECONDS = 5;\nconst SUBJECT_MAX_CHARS = 256;\n\nconst authUnavailable = (): ApiError => new ApiError(503, 'auth-unavailable', 'Sign-in is currently unavailable.');\n\n/**\n * Verifies the session cookie; returns null for anonymous requests.\n * Token/signature/claim problems are 401; JWKS provider faults (fetch failure,\n * timeout, undecodable document) are 503 `auth-unavailable` — an outage is not a\n * reason to treat the caller as signed out.\n */\nexport async function verifySession(\n request: Request,\n config: ApiConfig,\n env: ArchiveWorkerEnv,\n allowErasureRetry = false,\n): Promise<Session | null> {\n const token = sessionToken(request);\n if (token === null) return null;\n\n let payload: JWTPayload;\n try {\n const result = await jwtVerify(token, config.jwks, {\n algorithms: ['RS256'],\n audience: config.audience,\n clockTolerance: JWT_CLOCK_TOLERANCE_SECONDS,\n issuer: config.issuer,\n maxTokenAge: JWT_MAX_AGE_SECONDS,\n requiredClaims: ['sub', 'iss', 'aud', 'iat', 'exp', 'sessionVersion'],\n });\n payload = result.payload;\n } catch (error) {\n // Provider faults are 503: a JWKS outage must never read as a signed-out user.\n if (\n error instanceof errors.JWKSTimeout ||\n error instanceof errors.JWKSInvalid ||\n error instanceof errors.JWKSMultipleMatchingKeys\n ) {\n throw authUnavailable();\n }\n // Everything else in the JOSE family — malformed, expired, wrong alg/aud/iss,\n // bad signature, no matching key — is a token problem.\n if (error instanceof errors.JOSEError) throw unauthenticated();\n // Network fetch failures surface outside the JOSE error family.\n throw authUnavailable();\n }\n const subject = payload.sub;\n const issuer = payload.iss;\n const iat = payload.iat;\n const roles = payload.roles;\n if (!Number.isSafeInteger(payload.sessionVersion) || (payload.sessionVersion as number) < 0) throw unauthenticated();\n // Least authority: a malformed roles claim grants nothing, and operator authority\n // only ever comes from this verified, audience- and issuer-checked token.\n const operator = Array.isArray(roles) && roles.includes(ARCHIVE_OPERATOR_ROLE);\n // Malformed subjects must reject outright: lone surrogates/control characters\n // could otherwise collapse distinct claims into the same stored identity.\n if (\n typeof subject !== 'string' ||\n subject.length === 0 ||\n subject.length > SUBJECT_MAX_CHARS ||\n !isWellFormedText(subject)\n ) {\n throw unauthenticated();\n }\n if (typeof issuer !== 'string') throw unauthenticated();\n // A token claiming to be issued in the future beyond clock tolerance is a claims failure.\n if (typeof iat === 'number' && iat * 1000 > Date.now() + JWT_CLOCK_TOLERANCE_SECONDS * 1000) {\n throw unauthenticated();\n }\n if (\n operator &&\n (typeof iat !== 'number' ||\n typeof payload.exp !== 'number' ||\n payload.exp - iat > OPERATOR_SESSION_MAX_AGE_SECONDS ||\n Date.now() / 1000 - iat > OPERATOR_SESSION_MAX_AGE_SECONDS + JWT_CLOCK_TOLERANCE_SECONDS)\n )\n throw unauthenticated();\n\n // Only a previously tombstoned principal can bypass live validation to finish\n // partial erasure. Its profile cannot be re-created while the tombstone lives.\n const erasureRetry =\n allowErasureRetry && (await env.DB.prepare(SQL.revokedSubject).bind(issuer, subject, Date.now()).first()) !== null;\n if (!erasureRetry) {\n try {\n await serviceRequest(\n env.AUTH_ISSUER_SERVICE,\n new Request(new URL('/api/v1/auth/session', config.issuer), {\n headers: { cookie: `archive_session=${token}` },\n redirect: 'manual',\n }),\n async (response) => {\n const body: unknown = await response.json();\n if (response.status === 200 && archiveOkSchema.safeParse(body).success) return;\n const failure = archiveErrorResponseSchema.safeParse(body);\n if (response.status === 401 && failure.success && failure.data.error.code === 'unauthenticated')\n throw unauthenticated();\n throw authUnavailable();\n },\n );\n } catch (error) {\n if (error instanceof ApiError && error.status === 401) throw error;\n throw authUnavailable();\n }\n }\n if (!allowErasureRetry) {\n const revoked = await env.DB.prepare(SQL.revokedSubject).bind(issuer, subject, Date.now()).first();\n if (revoked !== null) throw unauthenticated();\n }\n const row = await env.DB.prepare(SQL.publisher).bind(issuer, subject).first<{\n display_name: string;\n id: string;\n }>();\n return {\n operator,\n publisher: row === null ? null : { displayName: row.display_name, id: row.id },\n subject: { issuer, subject },\n };\n}\n\n/** Session required for private routes; anonymous gets 401. */\nexport function requireSession(session: Session | null): Session {\n if (session === null) throw unauthenticated();\n return session;\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { ApiConfig, ArchiveWorkerEnv } from './env';
2
+ /**
3
+ * Session authentication. The Worker trusts exactly one source: the archive_session
4
+ * HttpOnly cookie holding an RS256 JWT from the configured issuer. Tokens are verified
5
+ * before any database read and are never echoed into DTOs; the publisher row maps the
6
+ * opaque (issuer, subject) pair to the public publisher id. An optional `roles` claim
7
+ * from the same trusted issuer grants operator authority (moderation routes).
8
+ */
9
+ export interface VerifiedSubject {
10
+ issuer: string;
11
+ subject: string;
12
+ }
13
+ export interface Publisher {
14
+ displayName: string;
15
+ id: string;
16
+ }
17
+ export interface Session {
18
+ /** True only when the verified token's `roles` claim carries the operator role. */
19
+ operator: boolean;
20
+ /** The registered publisher row; null while the verified user has not registered. */
21
+ publisher: Publisher | null;
22
+ subject: VerifiedSubject;
23
+ }
24
+ export declare const JWT_CLOCK_TOLERANCE_SECONDS = 5;
25
+ /**
26
+ * Verifies the session cookie; returns null for anonymous requests.
27
+ * Token/signature/claim problems are 401; JWKS provider faults (fetch failure,
28
+ * timeout, undecodable document) are 503 `auth-unavailable` — an outage is not a
29
+ * reason to treat the caller as signed out.
30
+ */
31
+ export declare function verifySession(request: Request, config: ApiConfig, env: ArchiveWorkerEnv, allowErasureRetry?: boolean): Promise<Session | null>;
32
+ /** Session required for private routes; anonymous gets 401. */
33
+ export declare function requireSession(session: Session | null): Session;
34
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/worker/auth.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAIzD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,OAAO;IACtB,mFAAmF;IACnF,QAAQ,EAAE,OAAO,CAAC;IAClB,qFAAqF;IACrF,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;CAC1B;AAGD,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAK7C;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,SAAS,EACjB,GAAG,EAAE,gBAAgB,EACrB,iBAAiB,UAAQ,GACxB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAqGzB;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAG/D"}
@@ -0,0 +1,2 @@
1
+ import{SESSION_MAX_AGE_SECONDS as e,archiveErrorResponseSchema as t,archiveOkSchema as n,isWellFormedText as r}from"../contract.js";import{serviceRequest as i}from"./_service.js";import{ApiError as a,sessionToken as o,unauthenticated as s}from"./http.js";import{SQL as c}from"./sql.js";import{errors as l,jwtVerify as u}from"jose";var d=e,f=256,authUnavailable=()=>new a(503,`auth-unavailable`,`Sign-in is currently unavailable.`);async function verifySession(e,p,m,h=!1){let g=o(e);if(g===null)return null;let _;try{_=(await u(g,p.jwks,{algorithms:[`RS256`],audience:p.audience,clockTolerance:5,issuer:p.issuer,maxTokenAge:d,requiredClaims:[`sub`,`iss`,`aud`,`iat`,`exp`,`sessionVersion`]})).payload}catch(e){throw e instanceof l.JWKSTimeout||e instanceof l.JWKSInvalid||e instanceof l.JWKSMultipleMatchingKeys?authUnavailable():e instanceof l.JOSEError?s():authUnavailable()}let v=_.sub,y=_.iss,b=_.iat,x=_.roles;if(!Number.isSafeInteger(_.sessionVersion)||_.sessionVersion<0)throw s();let S=Array.isArray(x)&&x.includes(`archive-operator`);if(typeof v!=`string`||v.length===0||v.length>f||!r(v)||typeof y!=`string`||typeof b==`number`&&b*1e3>Date.now()+5e3||S&&(typeof b!=`number`||typeof _.exp!=`number`||_.exp-b>900||Date.now()/1e3-b>905))throw s();if(!(h&&await m.DB.prepare(c.revokedSubject).bind(y,v,Date.now()).first()!==null))try{await i(m.AUTH_ISSUER_SERVICE,new Request(new URL(`/api/v1/auth/session`,p.issuer),{headers:{cookie:`archive_session=${g}`},redirect:`manual`}),async e=>{let r=await e.json();if(e.status===200&&n.safeParse(r).success)return;let i=t.safeParse(r);throw e.status===401&&i.success&&i.data.error.code===`unauthenticated`?s():authUnavailable()})}catch(e){throw e instanceof a&&e.status===401?e:authUnavailable()}if(!h&&await m.DB.prepare(c.revokedSubject).bind(y,v,Date.now()).first()!==null)throw s();let C=await m.DB.prepare(c.publisher).bind(y,v).first();return{operator:S,publisher:C===null?null:{displayName:C.display_name,id:C.id},subject:{issuer:y,subject:v}}}function requireSession(e){if(e===null)throw s();return e}export{requireSession,verifySession};
2
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","names":[],"sources":["../../src/worker/auth.ts"],"sourcesContent":["import { errors, type JWTPayload, jwtVerify } from 'jose';\nimport {\n ARCHIVE_OPERATOR_ROLE,\n archiveErrorResponseSchema,\n archiveOkSchema,\n isWellFormedText,\n OPERATOR_SESSION_MAX_AGE_SECONDS,\n SESSION_MAX_AGE_SECONDS,\n} from '../contract';\nimport { serviceRequest } from './_service';\nimport type { ApiConfig, ArchiveWorkerEnv } from './env';\nimport { ApiError, sessionToken, unauthenticated } from './http';\nimport { SQL } from './sql';\n\n/**\n * Session authentication. The Worker trusts exactly one source: the archive_session\n * HttpOnly cookie holding an RS256 JWT from the configured issuer. Tokens are verified\n * before any database read and are never echoed into DTOs; the publisher row maps the\n * opaque (issuer, subject) pair to the public publisher id. An optional `roles` claim\n * from the same trusted issuer grants operator authority (moderation routes).\n */\nexport interface VerifiedSubject {\n issuer: string;\n subject: string;\n}\n\nexport interface Publisher {\n displayName: string;\n id: string;\n}\n\nexport interface Session {\n /** True only when the verified token's `roles` claim carries the operator role. */\n operator: boolean;\n /** The registered publisher row; null while the verified user has not registered. */\n publisher: Publisher | null;\n subject: VerifiedSubject;\n}\n\nconst JWT_MAX_AGE_SECONDS = SESSION_MAX_AGE_SECONDS;\nexport const JWT_CLOCK_TOLERANCE_SECONDS = 5;\nconst SUBJECT_MAX_CHARS = 256;\n\nconst authUnavailable = (): ApiError => new ApiError(503, 'auth-unavailable', 'Sign-in is currently unavailable.');\n\n/**\n * Verifies the session cookie; returns null for anonymous requests.\n * Token/signature/claim problems are 401; JWKS provider faults (fetch failure,\n * timeout, undecodable document) are 503 `auth-unavailable` — an outage is not a\n * reason to treat the caller as signed out.\n */\nexport async function verifySession(\n request: Request,\n config: ApiConfig,\n env: ArchiveWorkerEnv,\n allowErasureRetry = false,\n): Promise<Session | null> {\n const token = sessionToken(request);\n if (token === null) return null;\n\n let payload: JWTPayload;\n try {\n const result = await jwtVerify(token, config.jwks, {\n algorithms: ['RS256'],\n audience: config.audience,\n clockTolerance: JWT_CLOCK_TOLERANCE_SECONDS,\n issuer: config.issuer,\n maxTokenAge: JWT_MAX_AGE_SECONDS,\n requiredClaims: ['sub', 'iss', 'aud', 'iat', 'exp', 'sessionVersion'],\n });\n payload = result.payload;\n } catch (error) {\n // Provider faults are 503: a JWKS outage must never read as a signed-out user.\n if (\n error instanceof errors.JWKSTimeout ||\n error instanceof errors.JWKSInvalid ||\n error instanceof errors.JWKSMultipleMatchingKeys\n ) {\n throw authUnavailable();\n }\n // Everything else in the JOSE family — malformed, expired, wrong alg/aud/iss,\n // bad signature, no matching key — is a token problem.\n if (error instanceof errors.JOSEError) throw unauthenticated();\n // Network fetch failures surface outside the JOSE error family.\n throw authUnavailable();\n }\n const subject = payload.sub;\n const issuer = payload.iss;\n const iat = payload.iat;\n const roles = payload.roles;\n if (!Number.isSafeInteger(payload.sessionVersion) || (payload.sessionVersion as number) < 0) throw unauthenticated();\n // Least authority: a malformed roles claim grants nothing, and operator authority\n // only ever comes from this verified, audience- and issuer-checked token.\n const operator = Array.isArray(roles) && roles.includes(ARCHIVE_OPERATOR_ROLE);\n // Malformed subjects must reject outright: lone surrogates/control characters\n // could otherwise collapse distinct claims into the same stored identity.\n if (\n typeof subject !== 'string' ||\n subject.length === 0 ||\n subject.length > SUBJECT_MAX_CHARS ||\n !isWellFormedText(subject)\n ) {\n throw unauthenticated();\n }\n if (typeof issuer !== 'string') throw unauthenticated();\n // A token claiming to be issued in the future beyond clock tolerance is a claims failure.\n if (typeof iat === 'number' && iat * 1000 > Date.now() + JWT_CLOCK_TOLERANCE_SECONDS * 1000) {\n throw unauthenticated();\n }\n if (\n operator &&\n (typeof iat !== 'number' ||\n typeof payload.exp !== 'number' ||\n payload.exp - iat > OPERATOR_SESSION_MAX_AGE_SECONDS ||\n Date.now() / 1000 - iat > OPERATOR_SESSION_MAX_AGE_SECONDS + JWT_CLOCK_TOLERANCE_SECONDS)\n )\n throw unauthenticated();\n\n // Only a previously tombstoned principal can bypass live validation to finish\n // partial erasure. Its profile cannot be re-created while the tombstone lives.\n const erasureRetry =\n allowErasureRetry && (await env.DB.prepare(SQL.revokedSubject).bind(issuer, subject, Date.now()).first()) !== null;\n if (!erasureRetry) {\n try {\n await serviceRequest(\n env.AUTH_ISSUER_SERVICE,\n new Request(new URL('/api/v1/auth/session', config.issuer), {\n headers: { cookie: `archive_session=${token}` },\n redirect: 'manual',\n }),\n async (response) => {\n const body: unknown = await response.json();\n if (response.status === 200 && archiveOkSchema.safeParse(body).success) return;\n const failure = archiveErrorResponseSchema.safeParse(body);\n if (response.status === 401 && failure.success && failure.data.error.code === 'unauthenticated')\n throw unauthenticated();\n throw authUnavailable();\n },\n );\n } catch (error) {\n if (error instanceof ApiError && error.status === 401) throw error;\n throw authUnavailable();\n }\n }\n if (!allowErasureRetry) {\n const revoked = await env.DB.prepare(SQL.revokedSubject).bind(issuer, subject, Date.now()).first();\n if (revoked !== null) throw unauthenticated();\n }\n const row = await env.DB.prepare(SQL.publisher).bind(issuer, subject).first<{\n display_name: string;\n id: string;\n }>();\n return {\n operator,\n publisher: row === null ? null : { displayName: row.display_name, id: row.id },\n subject: { issuer, subject },\n };\n}\n\n/** Session required for private routes; anonymous gets 401. */\nexport function requireSession(session: Session | null): Session {\n if (session === null) throw unauthenticated();\n return session;\n}\n"],"mappings":"2UAuCA,IAAM,EAAsB,EAEtB,EAAoB,IAEpB,oBAAkC,IAAI,EAAS,IAAK,mBAAoB,mCAAmC,EAQjH,eAAsB,cACpB,EACA,EACA,EACA,EAAoB,GACK,CACzB,IAAM,EAAQ,EAAa,CAAO,EAClC,GAAI,IAAU,KAAM,OAAO,KAE3B,IAAI,EACJ,GAAI,CASF,GAAU,MARW,EAAU,EAAO,EAAO,KAAM,CACjD,WAAY,CAAC,OAAO,EACpB,SAAU,EAAO,SACjB,eAAA,EACA,OAAQ,EAAO,OACf,YAAa,EACb,eAAgB,CAAC,MAAO,MAAO,MAAO,MAAO,MAAO,gBAAgB,CACtE,CAAC,EAAA,CACgB,OACnB,OAAS,EAAO,CAad,MAVE,aAAiB,EAAO,aACxB,aAAiB,EAAO,aACxB,aAAiB,EAAO,yBAElB,gBAAgB,EAIpB,aAAiB,EAAO,UAAiB,EAAgB,EAEvD,gBAAgB,CACxB,CACA,IAAM,EAAU,EAAQ,IAClB,EAAS,EAAQ,IACjB,EAAM,EAAQ,IACd,EAAQ,EAAQ,MACtB,GAAI,CAAC,OAAO,cAAc,EAAQ,cAAc,GAAM,EAAQ,eAA4B,EAAG,MAAM,EAAgB,EAGnH,IAAM,EAAW,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAA,kBAA8B,EAgB7E,GAZE,OAAO,GAAY,UACnB,EAAQ,SAAW,GACnB,EAAQ,OAAS,GACjB,CAAC,EAAiB,CAAO,GAIvB,OAAO,GAAW,UAElB,OAAO,GAAQ,UAAY,EAAM,IAAO,KAAK,IAAI,EAAA,KAInD,IACC,OAAO,GAAQ,UACd,OAAO,EAAQ,KAAQ,UACvB,EAAQ,IAAM,EAAA,KACd,KAAK,IAAI,EAAI,IAAO,EAAA,KAEtB,MAAM,EAAgB,EAMxB,GAAI,EADF,GAAsB,MAAM,EAAI,GAAG,QAAQ,EAAI,cAAc,CAAC,CAAC,KAAK,EAAQ,EAAS,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,IAAO,MAE9G,GAAI,CACF,MAAM,EACJ,EAAI,oBACJ,IAAI,QAAQ,IAAI,IAAI,uBAAwB,EAAO,MAAM,EAAG,CAC1D,QAAS,CAAE,OAAQ,mBAAmB,GAAQ,EAC9C,SAAU,QACZ,CAAC,EACD,KAAO,IAAa,CAClB,IAAM,EAAgB,MAAM,EAAS,KAAK,EAC1C,GAAI,EAAS,SAAW,KAAO,EAAgB,UAAU,CAAI,CAAC,CAAC,QAAS,OACxE,IAAM,EAAU,EAA2B,UAAU,CAAI,EAGzD,MAFI,EAAS,SAAW,KAAO,EAAQ,SAAW,EAAQ,KAAK,MAAM,OAAS,kBACtE,EAAgB,EAClB,gBAAgB,CACxB,CACF,CACF,OAAS,EAAO,CAEd,MADI,aAAiB,GAAY,EAAM,SAAW,IAAW,EACvD,gBAAgB,CACxB,CAEF,GAAI,CAAC,GAEC,MADkB,EAAI,GAAG,QAAQ,EAAI,cAAc,CAAC,CAAC,KAAK,EAAQ,EAAS,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,IACjF,KAAM,MAAM,EAAgB,EAE9C,IAAM,EAAM,MAAM,EAAI,GAAG,QAAQ,EAAI,SAAS,CAAC,CAAC,KAAK,EAAQ,CAAO,CAAC,CAAC,MAGnE,EACH,MAAO,CACL,WACA,UAAW,IAAQ,KAAO,KAAO,CAAE,YAAa,EAAI,aAAc,GAAI,EAAI,EAAG,EAC7E,QAAS,CAAE,SAAQ,SAAQ,CAC7B,CACF,CAGA,SAAgB,eAAe,EAAkC,CAC/D,GAAI,IAAY,KAAM,MAAM,EAAgB,EAC5C,OAAO,CACT"}