@vielzeug/archive 26.10.6 → 26.10.7

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 (47) hide show
  1. package/dist/_crypto.cjs +2 -0
  2. package/dist/_crypto.cjs.map +1 -0
  3. package/dist/_crypto.d.ts +7 -0
  4. package/dist/_crypto.d.ts.map +1 -0
  5. package/dist/_crypto.js +2 -0
  6. package/dist/_crypto.js.map +1 -0
  7. package/dist/auth/worker.cjs +4 -4
  8. package/dist/auth/worker.cjs.map +1 -1
  9. package/dist/auth/worker.d.ts.map +1 -1
  10. package/dist/auth/worker.js +4 -4
  11. package/dist/auth/worker.js.map +1 -1
  12. package/dist/auth.cjs +1 -1
  13. package/dist/auth.d.ts +0 -1
  14. package/dist/auth.d.ts.map +1 -1
  15. package/dist/auth.js +1 -1
  16. package/dist/collection.cjs +1 -1
  17. package/dist/collection.cjs.map +1 -1
  18. package/dist/collection.d.ts +9 -1
  19. package/dist/collection.d.ts.map +1 -1
  20. package/dist/collection.js +1 -1
  21. package/dist/collection.js.map +1 -1
  22. package/dist/contract.cjs.map +1 -1
  23. package/dist/contract.d.ts +4 -3
  24. package/dist/contract.d.ts.map +1 -1
  25. package/dist/contract.js.map +1 -1
  26. package/dist/errors.cjs.map +1 -1
  27. package/dist/errors.d.ts +1 -1
  28. package/dist/errors.d.ts.map +1 -1
  29. package/dist/errors.js.map +1 -1
  30. package/dist/worker/_pagination.cjs +1 -1
  31. package/dist/worker/_pagination.cjs.map +1 -1
  32. package/dist/worker/_pagination.d.ts +20 -7
  33. package/dist/worker/_pagination.d.ts.map +1 -1
  34. package/dist/worker/_pagination.js +1 -1
  35. package/dist/worker/_pagination.js.map +1 -1
  36. package/dist/worker/publications.cjs +1 -1
  37. package/dist/worker/publications.cjs.map +1 -1
  38. package/dist/worker/publications.d.ts.map +1 -1
  39. package/dist/worker/publications.js +1 -1
  40. package/dist/worker/publications.js.map +1 -1
  41. package/dist/worker/sql.cjs +4 -4
  42. package/dist/worker/sql.cjs.map +1 -1
  43. package/dist/worker/sql.d.ts +3 -2
  44. package/dist/worker/sql.d.ts.map +1 -1
  45. package/dist/worker/sql.js +4 -4
  46. package/dist/worker/sql.js.map +1 -1
  47. package/package.json +2 -2
@@ -1 +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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"}
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/**\n * One page of archive entries. Every list in the package pages forward-only: the\n * cursor walks toward the oldest entry and is null after the last page.\n */\nexport interface ArchivePage<T> {\n /** Cursor for the next page; null after the last. */\n cursor: string | null;\n entries: readonly T[];\n}\n"],"mappings":"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"}
@@ -216,12 +216,13 @@ export type ArchiveModerationLog = InferOutput<typeof archiveModerationLogSchema
216
216
  /** How the online list ranks entries: explicit modes only, no vague composites. */
217
217
  export type ArchiveSort = 'liked' | 'recent';
218
218
  export declare const archiveSortSchema: Schema<ArchiveSort>;
219
- /** One page of archive entries plus forward/backward cursors around it. */
219
+ /**
220
+ * One page of archive entries. Every list in the package pages forward-only: the
221
+ * cursor walks toward the oldest entry and is null after the last page.
222
+ */
220
223
  export interface ArchivePage<T> {
221
224
  /** Cursor for the next page; null after the last. */
222
225
  cursor: string | null;
223
226
  entries: readonly T[];
224
- /** Cursor for the previous page; null when already at the top. */
225
- previous: string | null;
226
227
  }
227
228
  //# sourceMappingURL=contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
@@ -1 +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":"oCAGA,IAAa,EAAsB,kBACtB,EAAsB,UAKtB,EAA0B,OAC1B,EAAmC,IAUnC,oBAAsB,EAAe,IAChD,GAAG,EAAoB,GAAG,EAAM,oCAAoC,EAAoB,YAAY,EAAQ,gBAAgB,EAAQ,OAAS,WAAa,KAE/I,EAAmB,sBAEnB,EAAiB,oBACjB,EAAwB,oBAExB,EAA2B,EAM3B,EAAyB,EAEzB,EAAsB,EAEtB,EAAwB,mBAExB,EAAkC,oBAKlC,EAA6B,EAAE,OAAO,CAAC,CAAC,KAAK,EAC7C,EAAqC,EAAE,OAAO,CAAC,CAAC,YAAY,EAC5D,EAA8B,EAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB,EAE/D,EAAkB,kBAClB,iBAAoB,GAA2B,CAAC,EAAgB,KAAK,CAAK,EACjF,eAAmB,EAAE,OAAO,CAAC,CAAC,MAAM,gBAAgB,EAE7C,EAAqC,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAE/D,EAA4D,EAAE,OAAO,CAChF,GAAI,EACJ,KAAM,CACR,CAAC,EACY,EAAwF,EAClG,OAAO,CAAE,GAAI,EAAY,KAAM,EAAoB,SAAU,EAAE,QAAQ,CAAE,CAAC,CAAC,CAC3E,SAAS,EAGC,EAKR,EAAE,OAAO,CACZ,UAAW,EACX,MAAO,EAAE,OAAO,CAAC,CAAC,MAAM,EACxB,GAAI,EACJ,aAAc,EAAmB,SAAS,CAC5C,CAAC,EAEY,EAAmD,EAAE,OAAO,CAAE,IAAK,EAAE,MAAM,CAAU,CAAE,CAAC,EACxF,EAA2E,EAAE,OAAO,CAC/F,OAAQ,EAAE,OAAO,CAAC,CAAC,SAAS,EAC5B,IAAK,EAAE,MAAM,CAAU,CACzB,CAAC,EAEY,EAAoF,EAAE,OAAO,CACxG,GAAI,EACJ,UAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAA2B,EAC9D,MAAO,EAAE,QAAQ,CACnB,CAAC,EAGY,EAAiB,OAAO,OAAO,CAC1C,WAAY,GACZ,YAAa,KACb,uBAAwB,OACxB,aAAc,GACd,YAAa,GACb,gBAAiB,GACjB,SAAU,GACV,aAAc,KACd,mBAAoB,GAEpB,kBAAmB,IACnB,aAAc,MACd,YAAa,GACb,YAAa,EACb,aAAc,GACd,WAAY,IAEZ,WAAY,EACd,CAAC,EACY,EAiBR,EAAE,OAAO,CACZ,WAAY,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAClC,YAAa,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACnC,uBAAwB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAC9C,aAAc,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACpC,YAAa,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACnC,gBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACvC,SAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAChC,aAAc,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACpC,mBAAoB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAC1C,kBAAmB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACzC,aAAc,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACpC,YAAa,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACnC,YAAa,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACnC,aAAc,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EACpC,WAAY,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAClC,WAAY,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CACpC,CAAC,EACY,EASR,EAAE,OAAO,CACZ,gBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAClD,SAAU,EAAE,OAAO,CAAE,MAAO,EAAE,QAAQ,EAAG,WAAY,EAAE,QAAQ,CAAE,CAAC,EAClE,OAAQ,EACR,OAAQ,EAAE,KAAK,CAAC,uBAAwB,iBAAiB,CAAU,CAAC,CAAC,SAAS,EAC9E,cAAe,EAAE,QAAA,CAAgC,EACjD,OAAQ,EAAE,KAAK,CAAC,KAAM,UAAU,CAAU,CAC5C,CAAC,EAGY,EAKR,EAAE,OAAO,CACZ,gBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAClD,OAAQ,EAAE,KAAK,CAAC,4BAA6B,uBAAwB,iBAAiB,CAAU,CAAC,CAAC,SAAS,EAC3G,cAAe,EAAE,QAAA,CAA2B,EAC5C,OAAQ,EAAE,KAAK,CAAC,KAAM,UAAU,CAAU,CAC5C,CAAC,EAIY,EAAsB,CACjC,kBACA,mBACA,cACA,WACA,YACA,OACA,WACA,kBACA,iBACA,qBACA,YACA,oBACA,sBACA,QACA,eACA,qBACA,UACA,YACA,kBACA,cACA,mBACF,EAQa,EAER,EACF,OAAO,CACN,MAAO,EACJ,OAAO,CACN,KAAM,EAAE,KAAK,CAAmB,EAChC,QAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,EAClC,UAAW,CACb,CAAC,CAAC,CACD,OAAO,CACZ,CAAC,CAAC,CACD,OAAO,EAIG,EAAwC,EAAE,OAAO,CAAE,GAAI,EAAE,QAAQ,EAAI,CAAE,CAAC,CAAC,CAAC,OAAO,EAGjF,EAIR,EAAE,OAAO,CACZ,QAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAe,UAAU,CAAC,EACjE,KAAM,EAAE,OAAO,CAAC,CAAC,MAAM,CAA+B,EACtD,KAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAe,UAAU,CAAC,CAChE,CAAC,EAIY,EAWR,EAAE,OAAO,CACZ,OAAQ,EAAE,OAAO,CAAC,CAAC,SAAS,EAC5B,QAAS,EAAE,MACT,EAAE,OAAO,CACP,UAAW,EACX,QAAS,EACT,WAAY,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAe,UAAU,EAC3D,GAAI,EACJ,OAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAe,iBAAiB,EAC9D,SAAU,EACV,WAAY,EAAmB,SAAS,CAC1C,CAAC,CACH,CACF,CAAC,EAEY,EAUR,EAAE,OAAO,CACZ,OAAQ,EAAE,OAAO,CAAC,CAAC,SAAS,EAC5B,QAAS,EAAE,MACT,EAAE,OAAO,CACP,WAAY,EAAE,OAAO,CAAC,CAAC,MAAM,CAA+B,EAC5D,UAAW,EACX,QAAS,EACT,GAAI,EACJ,OAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAe,iBAAiB,EAC9D,WAAY,EAAmB,SAAS,CAC1C,CAAC,CACH,CACF,CAAC,EAIY,EAA6B,CAAC,OAAQ,SAAU,SAAU,iBAAiB,EAQ3E,EAUR,EAAE,OAAO,CACZ,QAAS,EAAE,MACT,EAAE,OAAO,CACP,OAAQ,EAAE,KAAK,CAA0B,EACzC,WAAY,EAAE,OAAO,CAAC,CAAC,MAAM,CAA+B,CAAC,CAAC,SAAS,EACvE,UAAW,EACX,GAAI,EACJ,WAAY,EACZ,SAAU,CACZ,CAAC,CACH,EACA,OAAQ,EAAE,OAAO,CAAC,CAAC,SAAS,CAC9B,CAAC,EAKY,EAAyC,EAAE,KAAK,CAAC,QAAS,QAAQ,CAAU"}
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/**\n * One page of archive entries. Every list in the package pages forward-only: the\n * cursor walks toward the oldest entry and is null after the last page.\n */\nexport interface ArchivePage<T> {\n /** Cursor for the next page; null after the last. */\n cursor: string | null;\n entries: readonly T[];\n}\n"],"mappings":"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"}
@@ -1 +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"}
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 = 'canonicalize' | 'hash' | 'index' | 'schema';\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/errors.d.ts CHANGED
@@ -3,7 +3,7 @@ export declare class ArchiveError extends Error {
3
3
  }
4
4
  export declare class ArchiveDefinitionError extends ArchiveError {
5
5
  }
6
- export type ArchiveDerivationStage = 'schema' | 'canonicalize' | 'index' | 'hash' | 'response';
6
+ export type ArchiveDerivationStage = 'canonicalize' | 'hash' | 'index' | 'schema';
7
7
  /** A collection callback or core runtime fault, not invalid submitted content. */
8
8
  export declare class ArchiveDerivationError extends ArchiveError {
9
9
  readonly stage: ArchiveDerivationStage;
@@ -1 +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"}
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,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElF,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"}
@@ -1 +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"}
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 = 'canonicalize' | 'hash' | 'index' | 'schema';\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"}
@@ -1,2 +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;
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,likeCount:n.s.number().int().min(0).max(2**53-1).optional()});function forwardPageParams(n,i){let a=n.get(`limit`),o=a===null?e.ARCHIVE_LIMITS.pageSize:Number(a);if(!Number.isInteger(o)||o<1||o>e.ARCHIVE_LIMITS.maxPageSize)throw t.badRequest();let s=n.get(`cursor`),c=s===null?null:decodeCursorValue(s,r);if(c!==null&&c.context!==i)throw t.badRequest();return{cursor:c,limit:o}}function forwardPage(e,n,r=[]){let i=t.queryParams(new URL(e.url),[`cursor`,`limit`,...r]);return{...forwardPageParams(i,n),params:i}}var nextCursor=(e,t)=>encodeCursorValue({...e,context:t});exports.decodeCursorValue=decodeCursorValue,exports.encodeCursorValue=encodeCursorValue,exports.forwardPage=forwardPage,exports.forwardPageParams=forwardPageParams,exports.nextCursor=nextCursor;
2
2
  //# sourceMappingURL=_pagination.cjs.map
@@ -1 +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"}
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\n/**\n * The package's one pagination idiom: forward-only keyset pages. A cursor is an\n * opaque envelope of `{ context, createdAt, id, likeCount }` — `createdAt`/`id`\n * are the sort boundary (a timestamp and the row id), `likeCount` is the optional\n * extra sort field (the boundary count of the liked-sorted entry list), and\n * `context` names the query the cursor was minted for, so a cursor replayed under\n * a different query is a 400, never a silently wrong page.\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\n/** One keyset boundary: timestamp, row id, and the liked sort's boundary count. */\nexport interface ForwardCursor {\n context: string;\n createdAt: string;\n id: string;\n likeCount?: number;\n}\n\nconst forwardCursorSchema: Schema<ForwardCursor> = s.object({\n context: s.string(),\n createdAt: isoTimestampSchema,\n id: uuidSchema,\n // Only the liked-sorted entry list carries a boundary count; every other list\n // omits the key entirely.\n likeCount: s.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional(),\n});\n\n/** Parses `limit` and the forward cursor from an already allow-listed parameter set. */\nexport function forwardPageParams(params: URLSearchParams, context: string) {\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 };\n}\n\n/** Allow-lists the query, then parses `limit` and the forward cursor. */\nexport function forwardPage(request: Request, context: string, extra: readonly string[] = []) {\n const params = queryParams(new URL(request.url), ['cursor', 'limit', ...extra]);\n return { ...forwardPageParams(params, context), params };\n}\n\nexport const nextCursor = (cursor: Omit<ForwardCursor, 'context'>, context: string): string =>\n encodeCursorValue({ ...cursor, context });\n"],"mappings":"4FAYA,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,CAUA,IAAM,EAA6C,EAAA,EAAE,OAAO,CAC1D,QAAS,EAAA,EAAE,OAAO,EAClB,UAAW,EAAA,mBACX,GAAI,EAAA,WAGJ,UAAW,EAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAA2B,CAAC,CAAC,SAAS,CAC3E,CAAC,EAGD,SAAgB,kBAAkB,EAAyB,EAAiB,CAC1E,IAAM,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,OAAM,CACzB,CAGA,SAAgB,YAAY,EAAkB,EAAiB,EAA2B,CAAC,EAAG,CAC5F,IAAM,EAAS,EAAA,YAAY,IAAI,IAAI,EAAQ,GAAG,EAAG,CAAC,SAAU,QAAS,GAAG,CAAK,CAAC,EAC9E,MAAO,CAAE,GAAG,kBAAkB,EAAQ,CAAO,EAAG,QAAO,CACzD,CAEA,IAAa,YAAc,EAAwC,IACjE,kBAAkB,CAAE,GAAG,EAAQ,SAAQ,CAAC"}
@@ -1,18 +1,31 @@
1
1
  import { type Schema } from '@vielzeug/spell';
2
+ /**
3
+ * The package's one pagination idiom: forward-only keyset pages. A cursor is an
4
+ * opaque envelope of `{ context, createdAt, id, likeCount }` — `createdAt`/`id`
5
+ * are the sort boundary (a timestamp and the row id), `likeCount` is the optional
6
+ * extra sort field (the boundary count of the liked-sorted entry list), and
7
+ * `context` names the query the cursor was minted for, so a cursor replayed under
8
+ * a different query is a 400, never a silently wrong page.
9
+ */
2
10
  export declare const encodeCursorValue: (value: unknown) => string;
3
11
  export declare function decodeCursorValue<T>(raw: string, schema: Schema<T>): T;
12
+ /** One keyset boundary: timestamp, row id, and the liked sort's boundary count. */
4
13
  export interface ForwardCursor {
14
+ context: string;
5
15
  createdAt: string;
6
16
  id: string;
17
+ likeCount?: number;
7
18
  }
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;
19
+ /** Parses `limit` and the forward cursor from an already allow-listed parameter set. */
20
+ export declare function forwardPageParams(params: URLSearchParams, context: string): {
21
+ cursor: ForwardCursor | null;
14
22
  limit: number;
23
+ };
24
+ /** Allow-lists the query, then parses `limit` and the forward cursor. */
25
+ export declare function forwardPage(request: Request, context: string, extra?: readonly string[]): {
15
26
  params: URLSearchParams;
27
+ cursor: ForwardCursor | null;
28
+ limit: number;
16
29
  };
17
- export declare const nextCursor: (row: ForwardCursor, context: string) => string;
30
+ export declare const nextCursor: (cursor: Omit<ForwardCursor, "context">, context: string) => string;
18
31
  //# sourceMappingURL=_pagination.d.ts.map
@@ -1 +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"}
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;;;;;;;GAOG;AACH,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;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM;;;EAQzE;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,SAAS,MAAM,EAAO;;;;EAG3F;AAED,eAAO,MAAM,UAAU,GAAI,QAAQ,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,EAAE,SAAS,MAAM,KAAG,MAC1C,CAAC"}
@@ -1,2 +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};
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,likeCount:a.number().int().min(0).max(2**53-1).optional()});function forwardPageParams(t,n){let i=t.get(`limit`),a=i===null?e.pageSize:Number(i);if(!Number.isInteger(a)||a<1||a>e.maxPageSize)throw r();let s=t.get(`cursor`),c=s===null?null:decodeCursorValue(s,o);if(c!==null&&c.context!==n)throw r();return{cursor:c,limit:a}}function forwardPage(e,t,n=[]){let r=i(new URL(e.url),[`cursor`,`limit`,...n]);return{...forwardPageParams(r,t),params:r}}var nextCursor=(e,t)=>encodeCursorValue({...e,context:t});export{decodeCursorValue,encodeCursorValue,forwardPage,forwardPageParams,nextCursor};
2
2
  //# sourceMappingURL=_pagination.js.map
@@ -1 +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"}
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\n/**\n * The package's one pagination idiom: forward-only keyset pages. A cursor is an\n * opaque envelope of `{ context, createdAt, id, likeCount }` — `createdAt`/`id`\n * are the sort boundary (a timestamp and the row id), `likeCount` is the optional\n * extra sort field (the boundary count of the liked-sorted entry list), and\n * `context` names the query the cursor was minted for, so a cursor replayed under\n * a different query is a 400, never a silently wrong page.\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\n/** One keyset boundary: timestamp, row id, and the liked sort's boundary count. */\nexport interface ForwardCursor {\n context: string;\n createdAt: string;\n id: string;\n likeCount?: number;\n}\n\nconst forwardCursorSchema: Schema<ForwardCursor> = s.object({\n context: s.string(),\n createdAt: isoTimestampSchema,\n id: uuidSchema,\n // Only the liked-sorted entry list carries a boundary count; every other list\n // omits the key entirely.\n likeCount: s.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional(),\n});\n\n/** Parses `limit` and the forward cursor from an already allow-listed parameter set. */\nexport function forwardPageParams(params: URLSearchParams, context: string) {\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 };\n}\n\n/** Allow-lists the query, then parses `limit` and the forward cursor. */\nexport function forwardPage(request: Request, context: string, extra: readonly string[] = []) {\n const params = queryParams(new URL(request.url), ['cursor', 'limit', ...extra]);\n return { ...forwardPageParams(params, context), params };\n}\n\nexport const nextCursor = (cursor: Omit<ForwardCursor, 'context'>, context: string): string =>\n encodeCursorValue({ ...cursor, context });\n"],"mappings":"oLAYA,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,CAUA,IAAM,EAA6C,EAAE,OAAO,CAC1D,QAAS,EAAE,OAAO,EAClB,UAAW,EACX,GAAI,EAGJ,UAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAA2B,CAAC,CAAC,SAAS,CAC3E,CAAC,EAGD,SAAgB,kBAAkB,EAAyB,EAAiB,CAC1E,IAAM,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,OAAM,CACzB,CAGA,SAAgB,YAAY,EAAkB,EAAiB,EAA2B,CAAC,EAAG,CAC5F,IAAM,EAAS,EAAY,IAAI,IAAI,EAAQ,GAAG,EAAG,CAAC,SAAU,QAAS,GAAG,CAAK,CAAC,EAC9E,MAAO,CAAE,GAAG,kBAAkB,EAAQ,CAAO,EAAG,QAAO,CACzD,CAEA,IAAa,YAAc,EAAwC,IACjE,kBAAkB,CAAE,GAAG,EAAQ,SAAQ,CAAC"}
@@ -1,2 +1,2 @@
1
- const e=require("../contract.cjs"),t=require("./http.cjs"),n=require("./sql.cjs"),r=require("./_pagination.cjs");let i=require("@vielzeug/spell");async function databaseCall(e){try{return await e()}catch(e){throw new t.ApiError(500,`internal`,`The service is not available.`,{cause:e,diagnostic:{kind:`database`}})}}var first=(e,t,...n)=>databaseCall(()=>e.DB.prepare(t).bind(...n).first()),all=(e,t,...n)=>databaseCall(async()=>{let r=await e.DB.prepare(t).bind(...n).all();if(!Array.isArray(r.results))throw Error(`Missing database query results`);return r.results}),run=(e,t,...n)=>databaseCall(()=>e.DB.prepare(t).bind(...n).run()),uuid=()=>crypto.randomUUID(),now=()=>new Date().toISOString(),recordUnavailable=()=>new t.ApiError(503,`record-unavailable`,`The record is not available.`,{diagnostic:{kind:`record`}}),a=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,rowToEntry=(e,t)=>{let n;try{n=JSON.parse(t.payload_json)}catch{throw recordUnavailable()}let r=e.contract.entry.safeParse({author:{id:t.owner_id,name:t.author_name},contentHash:t.content_hash,contentVersion:t.content_version,id:t.id,likeCount:t.like_count,payload:n,publishedAt:t.published_at,revision:t.revision,schemaVersion:t.schema_version,updatedAt:t.updated_at});if(!r.success)throw recordUnavailable();return r.data},rowToOwnedEntry=(t,n)=>{let r=rowToEntry(t,n);if(!e.uuidSchema.safeParse(n.client_key).success)throw recordUnavailable();return{...r,clientKey:n.client_key,hidden:n.hidden_at!==null}},entryById=async(e,t,r)=>{let i=await first(t,n.SQL.publication,r,e.name);return i===null?null:rowToEntry(e,i)},searchMatchFor=n=>{if(n===null)return;if(n.length>e.ARCHIVE_LIMITS.searchChars)throw t.badRequest();let r=(n.match(/[\p{L}\p{N}]+/gu)??[]).slice(0,e.ARCHIVE_LIMITS.searchTerms);if(r.length!==0)return r.map(e=>`"${e}"*`).join(` `)},o=i.s.object({c:i.s.object({id:i.s.string().uuid(),likeCount:i.s.number().int().min(0).max(2**53-1),publishedAt:i.s.string().isoDateTime()}),d:i.s.enum([`next`,`previous`]),q:i.s.object({a:i.s.string().uuid().nullable(),e:i.s.string().max(e.ARCHIVE_LIMITS.vocabChars).nullable(),g:i.s.string().max(e.ARCHIVE_LIMITS.vocabChars),m:i.s.string().max(e.ARCHIVE_LIMITS.searchChars*e.ARCHIVE_LIMITS.searchTerms).nullable(),s:i.s.enum([`liked`,`recent`]),t:i.s.string().max(e.ARCHIVE_LIMITS.vocabChars).nullable()})}),cursorFromRow=e=>({id:e.id,likeCount:e.like_count,publishedAt:e.published_at}),encodeCursor=(e,t,n)=>r.encodeCursorValue({c:cursorFromRow(e),d:t,q:{a:n.author??null,e:n.filter??null,g:n.collection,m:n.searchMatch??null,s:n.sort,t:n.tag??null}}),decodeCursor=(e,n)=>{let i=r.decodeCursorValue(e,o),{q:a}=i;if((a.a??void 0)!==n.author||a.s!==n.sort||a.g!==n.collection||(a.e??void 0)!==n.filter||(a.m??void 0)!==n.searchMatch||(a.t??void 0)!==n.tag)throw t.badRequest();return i};async function listEntries(r,i,o){let s=new URL(i.url),c=t.queryParams(s,[`author`,`cursor`,`filter`,`limit`,`search`,`sort`,`tag`]),l=c.get(`sort`)??`recent`,u=e.archiveSortSchema.safeParse(l);if(!u.success)throw t.badRequest();let d=c.get(`limit`),f=d===null?e.ARCHIVE_LIMITS.pageSize:Number(d);if(!Number.isInteger(f)||f<1||f>e.ARCHIVE_LIMITS.maxPageSize)throw t.badRequest();let p=c.get(`filter`);if(p!==null&&!r.filters.includes(p))throw t.badRequest();let m=c.get(`tag`);if(m!==null&&!r.tags.includes(m))throw t.badRequest();let h=c.get(`author`);if(h!==null&&!a.test(h))throw t.badRequest();let g=searchMatchFor(c.get(`search`)),_={author:h??void 0,collection:r.name,filter:p??void 0,limit:f+1,searchMatch:g,sort:u.data,tag:m??void 0},v=c.get(`cursor`),y=v===null?null:decodeCursor(v,_),b=y?.d??`next`;y!==null&&(_.cursor=y.c),_.direction=b;let fetchRows=async e=>{let t=n.listPublicationsSql(e);return all(o,t.sql,...t.parameters)},x=await fetchRows(_),S=x.length>f,C=x.slice(0,f);b===`previous`&&C.reverse();let w=!1;if(y!==null&&C.length>0){let e={..._,limit:1};b===`next`?(e.cursor=cursorFromRow(C[0]),e.direction=`previous`):(e.cursor=cursorFromRow(C[C.length-1]),e.direction=`next`),w=(await fetchRows(e)).length>0}let T=C.length>0&&(b===`next`&&S||b===`previous`&&w)?encodeCursor(C[C.length-1],`next`,_):null,E=C.length>0&&(b===`previous`&&S||b===`next`&&w)?encodeCursor(C[0],`previous`,_):null,D={cursor:T,entries:C.map(e=>rowToEntry(r,e)),previous:E};return t.json(200,D,t.noStore)}function collectionMeta(n){let r=e.archiveCollectionMetaSchema.safeParse({filters:[...n.filters].sort(),name:n.name,tags:[...n.tags].sort()});if(!r.success)throw recordUnavailable();return t.json(200,r.data,t.noStore)}async function getEntry(e,n,r){let i=await entryById(e,n,r);if(i===null)throw t.notFound();return t.json(200,i,t.noStore)}async function healthJson(r,i){let a=null,o=null;try{a=(await first(i,n.SQL.schemaVersion))?.applied??0}catch{o=`database-unavailable`}o===null&&a!==1&&(o=`schema-mismatch`);let s=e.archiveHealthSchema.safeParse({dbSchemaVersion:a,features:{likes:r.likesEnabled,publishing:r.publishingEnabled},limits:e.ARCHIVE_LIMITS,reason:o,schemaVersion:1,status:o===null?`ok`:`degraded`});if(!s.success)throw recordUnavailable();return t.json(o===null?200:503,s.data,t.noStore)}var identityJson=(n,r)=>{let i=e.archiveIdentitySchema.safeParse(r);if(!i.success)throw recordUnavailable();return t.json(n,i.data,t.noStore)};function getMe(e){return identityJson(200,e?.publisher?{id:e.publisher.id,name:e.publisher.displayName,operator:e.operator}:null)}async function registerMe(e,r,i){let a=await t.readJsonBody(e),o=s.safeParse(a);if(!o.success)throw t.invalid();let c=o.data.name??``,l=await first(r,n.SQL.publisher,i.subject.issuer,i.subject.subject);if(l!==null)return identityJson(200,{id:l.id,name:l.display_name,operator:i.operator});let u=uuid();await run(r,n.SQL.insertPublisher,u,i.subject.issuer,i.subject.subject,c,now(),i.subject.issuer,i.subject.subject,Date.now());let d=await first(r,n.SQL.publisher,i.subject.issuer,i.subject.subject);if(d===null)throw await first(r,n.SQL.revokedSubject,i.subject.issuer,i.subject.subject,Date.now())===null?t.conflict():new t.ApiError(401,`unauthenticated`,`Sign-in is required.`);return identityJson(201,{id:d.id,name:d.display_name,operator:i.operator})}async function renameMe(e,r,i){let a=requirePublisherRow(i),o=await t.readJsonBody(e),s=c.safeParse(o);if(!s.success)throw t.invalid();return s.data.name!==a.displayName&&await run(r,n.SQL.renamePublisher,s.data.name,a.id,s.data.name),identityJson(200,{id:a.id,name:s.data.name,operator:i.operator})}async function deleteProfile(e,t){return t.publisher!==null&&await run(e,n.SQL.deletePublisher,t.publisher.id),new Response(null,{headers:{"cache-control":`no-store`},status:204})}async function deleteAccountProfile(r,i){let{issuer:a,subject:o}=i.subject;return await databaseCall(()=>r.DB.batch([r.DB.prepare(n.SQL.revokeSubject).bind(a,o,Date.now()+(e.SESSION_MAX_AGE_SECONDS+10)*1e3),r.DB.prepare(n.SQL.deleteSubjectPublisher).bind(a,o)])),new Response(null,{headers:t.noStore,status:204})}async function erasePublisher(e,r,i){let a=requireOperatorRow(r);return await databaseCall(()=>e.DB.batch([e.DB.prepare(n.SQL.auditErasure).bind(uuid(),a.id,now(),i),e.DB.prepare(n.SQL.deletePublisher).bind(i)])),new Response(null,{headers:t.noStore,status:204})}async function listModerationLog(i,a,o){requireOperatorRow(o);let s=`moderation-log`,{cursor:c,limit:l}=r.forwardPage(i,s),u=n.moderationLogSql({cursor:c,limit:l+1}),d=await all(a,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveModerationLogSchema.safeParse({actions:f.map(e=>({action:e.action,collection:e.collection,createdAt:e.created_at,id:e.id,operatorId:e.operator_id,targetId:e.target_id})),cursor:d.length>l&&p!==void 0?r.nextCursor({createdAt:p.created_at,id:p.id},s):null});if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}var requirePublisherRow=e=>{if(e.publisher===null)throw t.forbidden(`Register before publishing.`);return e.publisher},requireOperatorRow=e=>{if(!e.operator)throw t.forbidden(`Operator access is required.`);if(e.publisher===null)throw t.forbidden(`Register before moderating.`);return e.publisher},s=i.s.object({name:e.archiveAliasSchema.optional()}),c=i.s.object({name:e.archiveAliasSchema});async function myPublications(r,i,a){if(a.publisher===null)return t.json(200,{entries:[]},t.noStore);let o=await all(i,n.SQL.ownPublications,a.publisher.id,r.name,e.ARCHIVE_LIMITS.maxPublications);return t.json(200,{entries:o.map(e=>rowToOwnedEntry(r,e))},t.noStore)}async function myLikes(i,o,s){let c=new URL(i.url).searchParams.get(`ids`);if(c===null){let a=`likes:${s.publisher?.id??`unregistered`}`,{cursor:c,limit:l}=r.forwardPage(i,a);if(s.publisher===null)return t.json(200,{cursor:null,ids:[]},t.noStore);let u=n.likesPageSql(s.publisher.id,{cursor:c,limit:l+1}),d=await all(o,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveLikesPageSchema.safeParse({cursor:d.length>l&&p!==void 0?r.nextCursor({createdAt:p.created_at,id:p.publication_id},a):null,ids:f.map(e=>e.publication_id)});if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}t.queryParams(new URL(i.url),[`ids`]);let l=[...new Set(c.split(`,`))];if(l.length>e.ARCHIVE_LIMITS.likeStateIds||l.some(e=>!a.test(e)))throw t.badRequest();if(s.publisher===null)return t.json(200,{ids:[]},t.noStore);let u=n.likedPublicationsSql(s.publisher.id,l),d=await all(o,u.sql,...u.parameters),f=new Set(d.map(e=>e.publication_id)),p={ids:l.filter(e=>f.has(e))},m=e.archiveLikedIdsSchema.safeParse(p);if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}var l=i.s.object({clientKey:e.uuidSchema,payload:i.s.unknown()}),u=i.s.object({expectedRevision:i.s.number().int().min(1).max(2**53-1),payload:i.s.unknown()});async function publishEntry(r,i,a,o,s){if(!o.publishingEnabled)throw new t.ApiError(503,`publishing-disabled`,`Publishing is currently disabled.`);let c=requirePublisherRow(s),u=await t.readJsonBody(i),d=l.safeParse(u);if(!d.success)throw t.invalid();let{clientKey:f}=d.data,p=await r.derive(d.data.payload);if(p===null)throw t.invalid();let m=p.canonicalJson,h=p.contentHash,g=c.id,_=r.name,v=Date.now(),y=await first(a,n.SQL.request,g,_,f);if(y!==null&&y.expires_at>v){if(y.payload_hash!==h)throw t.conflict(`That key was used for a different payload.`);if(y.publication_id===null)throw new t.ApiError(410,`gone`,`That publication was removed.`);let e=await entryById(r,a,y.publication_id);if(e!==null)return t.json(200,e,t.noStore)}let b=await first(a,n.SQL.publicationByKey,g,_,f);if(b!==null){if(b.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);if(b.content_hash!==h)throw t.conflict(`That key was used for a different payload.`);return t.json(200,rowToEntry(r,b),t.noStore)}let x=now(),S=Date.now()+e.ARCHIVE_LIMITS.idempotencyRetentionMs,C=JSON.stringify(p.tags);if(((await databaseCall(()=>a.DB.batch([a.DB.prepare(n.SQL.expireRequests).bind(Date.now()),a.DB.prepare(n.SQL.insertPublication).bind(uuid(),g,_,f,1,p.contentVersion,m,h,p.filter,p.title,C,x,x,g,_,e.ARCHIVE_LIMITS.maxPublications,g,_,f,Date.now()),a.DB.prepare(n.SQL.insertRequest).bind(g,_,f,h,x,S,g,_,f,h)])))[1]?.meta?.changes??0)===0){let e=await first(a,n.SQL.request,g,_,f);if(e!==null&&e.expires_at>Date.now()){if(e.publication_id===null)throw new t.ApiError(410,`gone`,`That publication was removed.`);if(e.payload_hash===h){let n=await entryById(r,a,e.publication_id);if(n!==null)return t.json(200,n,t.noStore);throw new t.ApiError(410,`gone`,`That publication was removed.`)}throw t.conflict(`That key was used for a different payload.`)}let i=await first(a,n.SQL.publicationByKey,g,_,f);if(i!==null&&i.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);if(i!==null&&i.content_hash===h)return t.json(200,rowToEntry(r,i),t.noStore);throw i===null?new t.ApiError(429,`quota`,`Publication quota reached.`):t.conflict(`That key was used for a different payload.`)}let w=await first(a,n.SQL.publicationByKey,g,_,f);if(w===null)throw t.conflict();return t.json(201,rowToEntry(r,w),t.noStore)}async function updateEntry(e,r,i,a,o,s){if(!a.publishingEnabled)throw new t.ApiError(503,`publishing-disabled`,`Publishing is currently disabled.`);let c=requirePublisherRow(o),l=await first(i,n.SQL.publicationAny,s,e.name);if(l===null||l.owner_id!==c.id)throw t.notFound();if(l.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);let d=await t.readJsonBody(r),f=u.safeParse(d);if(!f.success)throw t.invalid();let{expectedRevision:p}=f.data;if(p!==l.revision)throw t.conflict();let m=await e.derive(f.data.payload);if(m===null)throw t.invalid();let h=m.canonicalJson,g=m.contentHash;if(g===l.content_hash){let r=await first(i,n.SQL.publicationAny,s,e.name);if(r===null||r.owner_id!==c.id)throw t.notFound();if(r.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);if(r.revision!==p)throw t.conflict();return t.json(200,rowToEntry(e,r),t.noStore)}let _=JSON.stringify(m.tags);if(((await run(i,n.SQL.updatePublication,1,m.contentVersion,h,g,m.filter,m.title,_,now(),s,c.id,p,g)).meta?.changes??0)===0){let r=await first(i,n.SQL.publicationAny,s,e.name);throw r===null?t.notFound():r.hidden_at===null?t.conflict():t.forbidden(`This entry is under moderation review.`)}let v=await entryById(e,i,s);if(v===null)throw t.notFound();return t.json(200,v,t.noStore)}async function deleteEntry(e,r,i,a){if(i.operator){let t=requireOperatorRow(i);await databaseCall(()=>r.DB.batch([r.DB.prepare(n.SQL.auditRemoval).bind(uuid(),t.id,now(),a,e.name),r.DB.prepare(n.SQL.removePublicationAny).bind(a,e.name)]))}else{let o=requirePublisherRow(i),s=await first(r,n.SQL.publicationAny,a,e.name);if(s===null)return new Response(null,{headers:t.noStore,status:204});if(s.owner_id!==o.id)throw t.notFound();await run(r,n.SQL.removePublication,a,o.id)}return new Response(null,{headers:t.noStore,status:204})}async function setLike(r,i,a,o,s,c){let l=requirePublisherRow(o);if(c&&!a.likesEnabled)throw new t.ApiError(503,`likes-disabled`,`Likes are currently disabled.`);if(await first(i,n.SQL.publication,s,r.name)===null)throw t.notFound();c?await run(i,n.SQL.insertLike,l.id,now(),s):await run(i,n.SQL.removeLike,s,l.id);let u=await first(i,n.SQL.likeState,l.id,s);if(u===null)throw t.notFound();let d={id:u.id,likeCount:u.like_count,liked:u.liked===1},f=e.archiveLikeStateSchema.safeParse(d);if(!f.success)throw recordUnavailable();return t.json(200,f.data,t.noStore)}var d=i.s.object({reason:i.s.string().check(e.isWellFormedText).trim().min(1).max(e.ARCHIVE_LIMITS.reportReasonChars)});async function reportEntry(e,r,i,a,o){let s=requirePublisherRow(a),c=await t.readJsonBody(r),l=d.safeParse(c);if(!l.success)throw t.invalid();if(await first(i,n.SQL.publication,o,e.name)===null)throw t.notFound();return await run(i,n.SQL.insertReport,uuid(),s.id,l.data.reason,now(),o,e.name),new Response(null,{headers:t.noStore,status:204})}async function setHidden(e,r,i,a,o){let s=requireOperatorRow(i);if(await first(r,n.SQL.publicationAny,a,e.name)===null)throw t.notFound();let c=o?r.DB.prepare(n.SQL.hidePublication).bind(now(),a,e.name):r.DB.prepare(n.SQL.unhidePublication).bind(a,e.name);return await databaseCall(()=>r.DB.batch([r.DB.prepare(n.SQL.auditHide).bind(uuid(),s.id,o?`hide`:`unhide`,now(),a,e.name,Number(o)),c])),new Response(null,{headers:t.noStore,status:204})}async function listReports(i,a,o,s){requireOperatorRow(s);let c=new URL(a.url).searchParams.get(`status`)??`unresolved`;if(c!==`unresolved`&&c!==`resolved`)throw t.badRequest();let l=`reports:${i.name}:${c}`,{cursor:u,limit:d}=r.forwardPage(a,l,[`status`]),f=n.reportsPageSql({collection:i.name,resolved:c===`resolved`},{cursor:u,limit:d+1}),p=await all(o,f.sql,...f.parameters),m=p.slice(0,d),h=m.at(-1),g=e.archiveReportsSchema.safeParse({cursor:p.length>d&&h!==void 0?r.nextCursor({createdAt:h.created_at,id:h.id},l):null,reports:m.map(e=>({createdAt:e.created_at,entryId:e.publication_id,entryTitle:e.entry_title,id:e.id,reason:e.reason,reporter:{id:e.reporter_id,name:e.reporter_name},resolvedAt:e.resolved_at}))});if(!g.success)throw recordUnavailable();return t.json(200,g.data,t.noStore)}async function resolveReport(e,r,i,a){if(requireOperatorRow(i),await first(r,n.SQL.reportById,a,e.name)===null)throw t.notFound();return await run(r,n.SQL.resolveReport,now(),a,e.name),new Response(null,{headers:t.noStore,status:204})}async function myReports(i,a,o){let s=`own-reports:${o.publisher?.id??`unregistered`}`,{cursor:c,limit:l}=r.forwardPage(i,s);if(o.publisher===null)return t.json(200,{cursor:null,reports:[]},t.noStore);let u=n.reportsPageSql({publisherId:o.publisher.id},{cursor:c,limit:l+1}),d=await all(a,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveOwnReportsSchema.safeParse({cursor:d.length>l&&p!==void 0?r.nextCursor({createdAt:p.created_at,id:p.id},s):null,reports:f.map(e=>({collection:e.collection,createdAt:e.created_at,entryId:e.publication_id,id:e.id,reason:e.reason,resolvedAt:e.resolved_at}))});if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}exports.collectionMeta=collectionMeta,exports.deleteAccountProfile=deleteAccountProfile,exports.deleteEntry=deleteEntry,exports.deleteProfile=deleteProfile,exports.erasePublisher=erasePublisher,exports.getEntry=getEntry,exports.getMe=getMe,exports.healthJson=healthJson,exports.listEntries=listEntries,exports.listModerationLog=listModerationLog,exports.listReports=listReports,exports.myLikes=myLikes,exports.myPublications=myPublications,exports.myReports=myReports,exports.publishEntry=publishEntry,exports.registerMe=registerMe,exports.renameMe=renameMe,exports.reportEntry=reportEntry,exports.requireOperatorRow=requireOperatorRow,exports.requirePublisherRow=requirePublisherRow,exports.resolveReport=resolveReport,exports.setHidden=setHidden,exports.setLike=setLike,exports.updateEntry=updateEntry;
1
+ const e=require("../contract.cjs"),t=require("./http.cjs"),n=require("./sql.cjs"),r=require("./_pagination.cjs");let i=require("@vielzeug/spell");async function databaseCall(e){try{return await e()}catch(e){throw new t.ApiError(500,`internal`,`The service is not available.`,{cause:e,diagnostic:{kind:`database`}})}}var first=(e,t,...n)=>databaseCall(()=>e.DB.prepare(t).bind(...n).first()),all=(e,t,...n)=>databaseCall(async()=>{let r=await e.DB.prepare(t).bind(...n).all();if(!Array.isArray(r.results))throw Error(`Missing database query results`);return r.results}),run=(e,t,...n)=>databaseCall(()=>e.DB.prepare(t).bind(...n).run()),uuid=()=>crypto.randomUUID(),now=()=>new Date().toISOString(),recordUnavailable=()=>new t.ApiError(503,`record-unavailable`,`The record is not available.`,{diagnostic:{kind:`record`}}),a=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,rowToEntry=(e,t)=>{let n;try{n=JSON.parse(t.payload_json)}catch{throw recordUnavailable()}let r=e.contract.entry.safeParse({author:{id:t.owner_id,name:t.author_name},contentHash:t.content_hash,contentVersion:t.content_version,id:t.id,likeCount:t.like_count,payload:n,publishedAt:t.published_at,revision:t.revision,schemaVersion:t.schema_version,updatedAt:t.updated_at});if(!r.success)throw recordUnavailable();return r.data},rowToOwnedEntry=(t,n)=>{let r=rowToEntry(t,n);if(!e.uuidSchema.safeParse(n.client_key).success)throw recordUnavailable();return{...r,clientKey:n.client_key,hidden:n.hidden_at!==null}},entryById=async(e,t,r)=>{let i=await first(t,n.SQL.publication,r,e.name);return i===null?null:rowToEntry(e,i)},searchMatchFor=n=>{if(n===null)return;if(n.length>e.ARCHIVE_LIMITS.searchChars)throw t.badRequest();let r=(n.match(/[\p{L}\p{N}]+/gu)??[]).slice(0,e.ARCHIVE_LIMITS.searchTerms);if(r.length!==0)return r.map(e=>`"${e}"*`).join(` `)},entryCursorContext=e=>JSON.stringify([e.collection,e.sort,e.author??``,e.filter??``,e.tag??``,e.searchMatch??``]),cursorFromRow=e=>({createdAt:e.published_at,id:e.id,likeCount:e.like_count});async function listEntries(i,o,s){let c=new URL(o.url),l=t.queryParams(c,[`author`,`cursor`,`filter`,`limit`,`search`,`sort`,`tag`]),u=l.get(`sort`)??`recent`,d=e.archiveSortSchema.safeParse(u);if(!d.success)throw t.badRequest();let f=l.get(`filter`);if(f!==null&&!i.filters.includes(f))throw t.badRequest();let p=l.get(`tag`);if(p!==null&&!i.tags.includes(p))throw t.badRequest();let m=l.get(`author`);if(m!==null&&!a.test(m))throw t.badRequest();let h=searchMatchFor(l.get(`search`)),g=entryCursorContext({author:m??void 0,collection:i.name,filter:f??void 0,searchMatch:h,sort:d.data,tag:p??void 0}),{cursor:_,limit:v}=r.forwardPageParams(l,g),y={author:m??void 0,collection:i.name,cursor:_===null?void 0:{createdAt:_.createdAt,id:_.id,likeCount:_.likeCount??0},filter:f??void 0,limit:v+1,searchMatch:h,sort:d.data,tag:p??void 0},b=n.listPublicationsSql(y),x=await all(s,b.sql,...b.parameters),S=x.slice(0,v),C=S.at(-1),w={cursor:x.length>v&&C!==void 0?r.nextCursor(cursorFromRow(C),g):null,entries:S.map(e=>rowToEntry(i,e))};return t.json(200,w,t.noStore)}function collectionMeta(n){let r=e.archiveCollectionMetaSchema.safeParse({filters:[...n.filters].sort(),name:n.name,tags:[...n.tags].sort()});if(!r.success)throw recordUnavailable();return t.json(200,r.data,t.noStore)}async function getEntry(e,n,r){let i=await entryById(e,n,r);if(i===null)throw t.notFound();return t.json(200,i,t.noStore)}async function healthJson(r,i){let a=null,o=null;try{a=(await first(i,n.SQL.schemaVersion))?.applied??0}catch{o=`database-unavailable`}o===null&&a!==1&&(o=`schema-mismatch`);let s=e.archiveHealthSchema.safeParse({dbSchemaVersion:a,features:{likes:r.likesEnabled,publishing:r.publishingEnabled},limits:e.ARCHIVE_LIMITS,reason:o,schemaVersion:1,status:o===null?`ok`:`degraded`});if(!s.success)throw recordUnavailable();return t.json(o===null?200:503,s.data,t.noStore)}var identityJson=(n,r)=>{let i=e.archiveIdentitySchema.safeParse(r);if(!i.success)throw recordUnavailable();return t.json(n,i.data,t.noStore)};function getMe(e){return identityJson(200,e?.publisher?{id:e.publisher.id,name:e.publisher.displayName,operator:e.operator}:null)}async function registerMe(e,r,i){let a=await t.readJsonBody(e),s=o.safeParse(a);if(!s.success)throw t.invalid();let c=s.data.name??``,l=await first(r,n.SQL.publisher,i.subject.issuer,i.subject.subject);if(l!==null)return identityJson(200,{id:l.id,name:l.display_name,operator:i.operator});let u=uuid();await run(r,n.SQL.insertPublisher,u,i.subject.issuer,i.subject.subject,c,now(),i.subject.issuer,i.subject.subject,Date.now());let d=await first(r,n.SQL.publisher,i.subject.issuer,i.subject.subject);if(d===null)throw await first(r,n.SQL.revokedSubject,i.subject.issuer,i.subject.subject,Date.now())===null?t.conflict():new t.ApiError(401,`unauthenticated`,`Sign-in is required.`);return identityJson(201,{id:d.id,name:d.display_name,operator:i.operator})}async function renameMe(e,r,i){let a=requirePublisherRow(i),o=await t.readJsonBody(e),c=s.safeParse(o);if(!c.success)throw t.invalid();return c.data.name!==a.displayName&&await run(r,n.SQL.renamePublisher,c.data.name,a.id,c.data.name),identityJson(200,{id:a.id,name:c.data.name,operator:i.operator})}async function deleteProfile(e,t){return t.publisher!==null&&await run(e,n.SQL.deletePublisher,t.publisher.id),new Response(null,{headers:{"cache-control":`no-store`},status:204})}async function deleteAccountProfile(r,i){let{issuer:a,subject:o}=i.subject;return await databaseCall(()=>r.DB.batch([r.DB.prepare(n.SQL.revokeSubject).bind(a,o,Date.now()+(e.SESSION_MAX_AGE_SECONDS+10)*1e3),r.DB.prepare(n.SQL.deleteSubjectPublisher).bind(a,o)])),new Response(null,{headers:t.noStore,status:204})}async function erasePublisher(e,r,i){let a=requireOperatorRow(r);return await databaseCall(()=>e.DB.batch([e.DB.prepare(n.SQL.auditErasure).bind(uuid(),a.id,now(),i),e.DB.prepare(n.SQL.deletePublisher).bind(i)])),new Response(null,{headers:t.noStore,status:204})}async function listModerationLog(i,a,o){requireOperatorRow(o);let s=`moderation-log`,{cursor:c,limit:l}=r.forwardPage(i,s),u=n.moderationLogSql({cursor:c,limit:l+1}),d=await all(a,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveModerationLogSchema.safeParse({actions:f.map(e=>({action:e.action,collection:e.collection,createdAt:e.created_at,id:e.id,operatorId:e.operator_id,targetId:e.target_id})),cursor:d.length>l&&p!==void 0?r.nextCursor({createdAt:p.created_at,id:p.id},s):null});if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}var requirePublisherRow=e=>{if(e.publisher===null)throw t.forbidden(`Register before publishing.`);return e.publisher},requireOperatorRow=e=>{if(!e.operator)throw t.forbidden(`Operator access is required.`);if(e.publisher===null)throw t.forbidden(`Register before moderating.`);return e.publisher},o=i.s.object({name:e.archiveAliasSchema.optional()}),s=i.s.object({name:e.archiveAliasSchema});async function myPublications(r,i,a){if(a.publisher===null)return t.json(200,{entries:[]},t.noStore);let o=await all(i,n.SQL.ownPublications,a.publisher.id,r.name,e.ARCHIVE_LIMITS.maxPublications);return t.json(200,{entries:o.map(e=>rowToOwnedEntry(r,e))},t.noStore)}async function myLikes(i,o,s){let c=new URL(i.url).searchParams.get(`ids`);if(c===null){let a=`likes:${s.publisher?.id??`unregistered`}`,{cursor:c,limit:l}=r.forwardPage(i,a);if(s.publisher===null)return t.json(200,{cursor:null,ids:[]},t.noStore);let u=n.likesPageSql(s.publisher.id,{cursor:c,limit:l+1}),d=await all(o,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveLikesPageSchema.safeParse({cursor:d.length>l&&p!==void 0?r.nextCursor({createdAt:p.created_at,id:p.publication_id},a):null,ids:f.map(e=>e.publication_id)});if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}t.queryParams(new URL(i.url),[`ids`]);let l=[...new Set(c.split(`,`))];if(l.length>e.ARCHIVE_LIMITS.likeStateIds||l.some(e=>!a.test(e)))throw t.badRequest();if(s.publisher===null)return t.json(200,{ids:[]},t.noStore);let u=n.likedPublicationsSql(s.publisher.id,l),d=await all(o,u.sql,...u.parameters),f=new Set(d.map(e=>e.publication_id)),p={ids:l.filter(e=>f.has(e))},m=e.archiveLikedIdsSchema.safeParse(p);if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}var c=i.s.object({clientKey:e.uuidSchema,payload:i.s.unknown()}),l=i.s.object({expectedRevision:i.s.number().int().min(1).max(2**53-1),payload:i.s.unknown()});async function resolveRetryRecord(e,n,r,i){if(r===null||r.expires_at<=Date.now())return null;if(r.payload_hash!==i)throw t.conflict(`That key was used for a different payload.`);if(r.publication_id===null)throw new t.ApiError(410,`gone`,`That publication was removed.`);let a=await entryById(e,n,r.publication_id);return a===null?`vanished`:t.json(200,a,t.noStore)}async function publishEntry(r,i,a,o,s){if(!o.publishingEnabled)throw new t.ApiError(503,`publishing-disabled`,`Publishing is currently disabled.`);let l=requirePublisherRow(s),u=await t.readJsonBody(i),d=c.safeParse(u);if(!d.success)throw t.invalid();let{clientKey:f}=d.data,p=await r.derive(d.data.payload);if(p===null)throw t.invalid();let m=p.canonicalJson,h=p.contentHash,g=l.id,_=r.name,v=await resolveRetryRecord(r,a,await first(a,n.SQL.request,g,_,f),h);if(v!==null&&v!==`vanished`)return v;let y=await first(a,n.SQL.publicationByKey,g,_,f);if(y!==null){if(y.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);if(y.content_hash!==h)throw t.conflict(`That key was used for a different payload.`);return t.json(200,rowToEntry(r,y),t.noStore)}let b=now(),x=Date.now()+e.ARCHIVE_LIMITS.idempotencyRetentionMs,S=JSON.stringify(p.tags);if(((await databaseCall(()=>a.DB.batch([a.DB.prepare(n.SQL.expireRequests).bind(Date.now()),a.DB.prepare(n.SQL.insertPublication).bind(uuid(),g,_,f,1,p.contentVersion,m,h,p.filter,p.title,S,b,b,g,_,e.ARCHIVE_LIMITS.maxPublications,g,_,f,Date.now()),a.DB.prepare(n.SQL.insertRequest).bind(g,_,f,h,b,x,g,_,f,h)])))[1]?.meta?.changes??0)===0){let e=await resolveRetryRecord(r,a,await first(a,n.SQL.request,g,_,f),h);if(e===`vanished`)throw new t.ApiError(410,`gone`,`That publication was removed.`);if(e!==null)return e;let i=await first(a,n.SQL.publicationByKey,g,_,f);if(i!==null&&i.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);if(i!==null&&i.content_hash===h)return t.json(200,rowToEntry(r,i),t.noStore);throw i===null?new t.ApiError(429,`quota`,`Publication quota reached.`):t.conflict(`That key was used for a different payload.`)}let C=await first(a,n.SQL.publicationByKey,g,_,f);if(C===null)throw t.conflict();return t.json(201,rowToEntry(r,C),t.noStore)}async function updateEntry(e,r,i,a,o,s){if(!a.publishingEnabled)throw new t.ApiError(503,`publishing-disabled`,`Publishing is currently disabled.`);let c=requirePublisherRow(o),u=await first(i,n.SQL.publicationAny,s,e.name);if(u===null||u.owner_id!==c.id)throw t.notFound();if(u.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);let d=await t.readJsonBody(r),f=l.safeParse(d);if(!f.success)throw t.invalid();let{expectedRevision:p}=f.data;if(p!==u.revision)throw t.conflict();let m=await e.derive(f.data.payload);if(m===null)throw t.invalid();let h=m.canonicalJson,g=m.contentHash;if(g===u.content_hash){let r=await first(i,n.SQL.publicationAny,s,e.name);if(r===null||r.owner_id!==c.id)throw t.notFound();if(r.hidden_at!==null)throw t.forbidden(`This entry is under moderation review.`);if(r.revision!==p)throw t.conflict();return t.json(200,rowToEntry(e,r),t.noStore)}let _=JSON.stringify(m.tags);if(((await run(i,n.SQL.updatePublication,1,m.contentVersion,h,g,m.filter,m.title,_,now(),s,c.id,p,g)).meta?.changes??0)===0){let r=await first(i,n.SQL.publicationAny,s,e.name);throw r===null?t.notFound():r.hidden_at===null?t.conflict():t.forbidden(`This entry is under moderation review.`)}let v=await entryById(e,i,s);if(v===null)throw t.notFound();return t.json(200,v,t.noStore)}async function deleteEntry(e,r,i,a){if(i.operator){let t=requireOperatorRow(i);await databaseCall(()=>r.DB.batch([r.DB.prepare(n.SQL.auditRemoval).bind(uuid(),t.id,now(),a,e.name),r.DB.prepare(n.SQL.removePublicationAny).bind(a,e.name)]))}else{let o=requirePublisherRow(i),s=await first(r,n.SQL.publicationAny,a,e.name);if(s===null)return new Response(null,{headers:t.noStore,status:204});if(s.owner_id!==o.id)throw t.notFound();await run(r,n.SQL.removePublication,a,o.id)}return new Response(null,{headers:t.noStore,status:204})}async function setLike(r,i,a,o,s,c){let l=requirePublisherRow(o);if(c&&!a.likesEnabled)throw new t.ApiError(503,`likes-disabled`,`Likes are currently disabled.`);if(await first(i,n.SQL.publication,s,r.name)===null)throw t.notFound();c?await run(i,n.SQL.insertLike,l.id,now(),s):await run(i,n.SQL.removeLike,s,l.id);let u=await first(i,n.SQL.likeState,l.id,s);if(u===null)throw t.notFound();let d={id:u.id,likeCount:u.like_count,liked:u.liked===1},f=e.archiveLikeStateSchema.safeParse(d);if(!f.success)throw recordUnavailable();return t.json(200,f.data,t.noStore)}var u=i.s.object({reason:i.s.string().check(e.isWellFormedText).trim().min(1).max(e.ARCHIVE_LIMITS.reportReasonChars)});async function reportEntry(e,r,i,a,o){let s=requirePublisherRow(a),c=await t.readJsonBody(r),l=u.safeParse(c);if(!l.success)throw t.invalid();if(await first(i,n.SQL.publication,o,e.name)===null)throw t.notFound();return await run(i,n.SQL.insertReport,uuid(),s.id,l.data.reason,now(),o,e.name),new Response(null,{headers:t.noStore,status:204})}async function setHidden(e,r,i,a,o){let s=requireOperatorRow(i);if(await first(r,n.SQL.publicationAny,a,e.name)===null)throw t.notFound();let c=o?r.DB.prepare(n.SQL.hidePublication).bind(now(),a,e.name):r.DB.prepare(n.SQL.unhidePublication).bind(a,e.name);return await databaseCall(()=>r.DB.batch([r.DB.prepare(n.SQL.auditHide).bind(uuid(),s.id,o?`hide`:`unhide`,now(),a,e.name,Number(o)),c])),new Response(null,{headers:t.noStore,status:204})}async function listReports(i,a,o,s){requireOperatorRow(s);let c=new URL(a.url).searchParams.get(`status`)??`unresolved`;if(c!==`unresolved`&&c!==`resolved`)throw t.badRequest();let l=`reports:${i.name}:${c}`,{cursor:u,limit:d}=r.forwardPage(a,l,[`status`]),f=n.reportsPageSql({collection:i.name,resolved:c===`resolved`},{cursor:u,limit:d+1}),p=await all(o,f.sql,...f.parameters),m=p.slice(0,d),h=m.at(-1),g=e.archiveReportsSchema.safeParse({cursor:p.length>d&&h!==void 0?r.nextCursor({createdAt:h.created_at,id:h.id},l):null,reports:m.map(e=>({createdAt:e.created_at,entryId:e.publication_id,entryTitle:e.entry_title,id:e.id,reason:e.reason,reporter:{id:e.reporter_id,name:e.reporter_name},resolvedAt:e.resolved_at}))});if(!g.success)throw recordUnavailable();return t.json(200,g.data,t.noStore)}async function resolveReport(e,r,i,a){if(requireOperatorRow(i),await first(r,n.SQL.reportById,a,e.name)===null)throw t.notFound();return await run(r,n.SQL.resolveReport,now(),a,e.name),new Response(null,{headers:t.noStore,status:204})}async function myReports(i,a,o){let s=`own-reports:${o.publisher?.id??`unregistered`}`,{cursor:c,limit:l}=r.forwardPage(i,s);if(o.publisher===null)return t.json(200,{cursor:null,reports:[]},t.noStore);let u=n.reportsPageSql({publisherId:o.publisher.id},{cursor:c,limit:l+1}),d=await all(a,u.sql,...u.parameters),f=d.slice(0,l),p=f.at(-1),m=e.archiveOwnReportsSchema.safeParse({cursor:d.length>l&&p!==void 0?r.nextCursor({createdAt:p.created_at,id:p.id},s):null,reports:f.map(e=>({collection:e.collection,createdAt:e.created_at,entryId:e.publication_id,id:e.id,reason:e.reason,resolvedAt:e.resolved_at}))});if(!m.success)throw recordUnavailable();return t.json(200,m.data,t.noStore)}exports.collectionMeta=collectionMeta,exports.deleteAccountProfile=deleteAccountProfile,exports.deleteEntry=deleteEntry,exports.deleteProfile=deleteProfile,exports.erasePublisher=erasePublisher,exports.getEntry=getEntry,exports.getMe=getMe,exports.healthJson=healthJson,exports.listEntries=listEntries,exports.listModerationLog=listModerationLog,exports.listReports=listReports,exports.myLikes=myLikes,exports.myPublications=myPublications,exports.myReports=myReports,exports.publishEntry=publishEntry,exports.registerMe=registerMe,exports.renameMe=renameMe,exports.reportEntry=reportEntry,exports.requireOperatorRow=requireOperatorRow,exports.requirePublisherRow=requirePublisherRow,exports.resolveReport=resolveReport,exports.setHidden=setHidden,exports.setLike=setLike,exports.updateEntry=updateEntry;
2
2
  //# sourceMappingURL=publications.cjs.map