better-ship 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{application-C4JOf71r.js → application-Cf9SER-i.js} +2 -2
- package/dist/{application-C4JOf71r.js.map → application-Cf9SER-i.js.map} +1 -1
- package/dist/application.d.ts +2 -2
- package/dist/application.js +1 -1
- package/dist/better-auth/apple-client-secret.d.ts +1 -1
- package/dist/better-auth/apple-client-secret.js +1 -1
- package/dist/better-auth/auth-client.js +1 -1
- package/dist/better-auth/rate-limit.d.ts +1 -1
- package/dist/better-auth/rate-limit.js +1 -1
- package/dist/better-auth.js +1 -1
- package/dist/cloudflare/auth-secondary-storage.d.ts +1 -1
- package/dist/cloudflare/auth-secondary-storage.js +1 -1
- package/dist/cloudflare/durable-object-client.js +1 -1
- package/dist/cloudflare/durable-object-errors.d.ts +1 -1
- package/dist/cloudflare/durable-object-errors.js +1 -1
- package/dist/cloudflare/kv-client.d.ts +31 -0
- package/dist/cloudflare/kv-client.d.ts.map +1 -0
- package/dist/cloudflare/kv-client.js +62 -0
- package/dist/cloudflare/kv-client.js.map +1 -0
- package/dist/cloudflare/kv-errors.d.ts +52 -0
- package/dist/cloudflare/kv-errors.d.ts.map +1 -0
- package/dist/cloudflare/kv-errors.js +144 -0
- package/dist/cloudflare/kv-errors.js.map +1 -0
- package/dist/cloudflare/queue.d.ts +2 -2
- package/dist/cloudflare/queue.js +2 -2
- package/dist/cloudflare/scheduled.d.ts +1 -1
- package/dist/cloudflare/scheduled.js +1 -1
- package/dist/cloudflare.d.ts +10 -5
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +21 -10
- package/dist/cloudflare.js.map +1 -1
- package/dist/{core-hdaNiCOU.js → core-ClSP5-V9.js} +94 -44
- package/dist/core-ClSP5-V9.js.map +1 -0
- package/dist/core.d.ts +2 -2
- package/dist/core.js +2 -2
- package/dist/http.d.ts +2 -2
- package/dist/http.js +2 -2
- package/dist/{index-B45Dr5gs.d.ts → index-Cchdg2aQ.d.ts} +2 -2
- package/dist/{index-B45Dr5gs.d.ts.map → index-Cchdg2aQ.d.ts.map} +1 -1
- package/dist/{index-G1QG0P5g.d.ts → index-DvJUcSW1.d.ts} +102 -48
- package/dist/{index-G1QG0P5g.d.ts.map → index-DvJUcSW1.d.ts.map} +1 -1
- package/dist/{outbox-repository-options-CwTTuy7B.d.ts → outbox-repository-options-AkihEaik.d.ts} +3 -3
- package/dist/{outbox-repository-options-CwTTuy7B.d.ts.map → outbox-repository-options-AkihEaik.d.ts.map} +1 -1
- package/dist/{outbox-repository-options-DOTpJH-e.js → outbox-repository-options-ICOF8g2h.js} +2 -2
- package/dist/{outbox-repository-options-DOTpJH-e.js.map → outbox-repository-options-ICOF8g2h.js.map} +1 -1
- package/dist/postgres.d.ts +10 -5
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +21 -10
- package/dist/postgres.js.map +1 -1
- package/dist/posthog/metrics.d.ts +1 -1
- package/dist/posthog/server.d.ts +2 -2
- package/dist/posthog/server.js +1 -1
- package/dist/resend.d.ts +3 -3
- package/dist/resend.js +2 -2
- package/dist/sentry/client.d.ts +1 -1
- package/dist/sentry/client.js +1 -1
- package/dist/sentry/server.d.ts +2 -2
- package/dist/tanstack.d.ts +1 -1
- package/dist/tanstack.js +1 -1
- package/dist/testing/outbox-repository.d.ts +2 -2
- package/dist/{webhook-verification-D7DIhH6C.d.ts → webhook-verification-BcQQG-vY.d.ts} +2 -2
- package/dist/{webhook-verification-D7DIhH6C.d.ts.map → webhook-verification-BcQQG-vY.d.ts.map} +1 -1
- package/dist/{webhook-verification-trEgyz7I.js → webhook-verification-D6aGQfNi.js} +2 -2
- package/dist/{webhook-verification-trEgyz7I.js.map → webhook-verification-D6aGQfNi.js.map} +1 -1
- package/package.json +3 -1
- package/src/core/define-messages.ts +209 -103
- package/src/core/index.ts +3 -1
- package/src/core/messages.ts +1 -1
- package/src/infrastructure/cloudflare/d1-database-execution.ts +27 -6
- package/src/infrastructure/cloudflare/kv-client.ts +79 -0
- package/src/infrastructure/cloudflare/kv-errors.ts +157 -0
- package/src/infrastructure/postgres/postgres-database-execution.ts +27 -6
- package/dist/core-hdaNiCOU.js.map +0 -1
package/dist/cloudflare.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as IOutboxRepository, M as OutboxClaim, N as OutboxMessage, P as IMessageStore, k as DeadLetterReason, w as ITransaction } from "./index-
|
|
3
|
-
import { n as eventCollector, r as DrizzleSchema, t as OutboxRepositoryOptions } from "./outbox-repository-options-
|
|
1
|
+
import { X as MessageId, at as DatabaseFailure, it as DatabaseError, q as DomainEvent, rt as D1ConstraintKind } from "./index-DvJUcSW1.js";
|
|
2
|
+
import { A as IOutboxRepository, M as OutboxClaim, N as OutboxMessage, P as IMessageStore, k as DeadLetterReason, w as ITransaction } from "./index-Cchdg2aQ.js";
|
|
3
|
+
import { n as eventCollector, r as DrizzleSchema, t as OutboxRepositoryOptions } from "./outbox-repository-options-AkihEaik.js";
|
|
4
4
|
import { BatchItem } from "drizzle-orm/batch";
|
|
5
5
|
import { AnyD1Database, DrizzleD1Database } from "drizzle-orm/d1";
|
|
6
6
|
//#region src/infrastructure/cloudflare/d1-database-types.d.ts
|
|
@@ -294,8 +294,13 @@ export declare class D1BatchTransaction<Schema extends DrizzleSchema> implements
|
|
|
294
294
|
}
|
|
295
295
|
//#endregion
|
|
296
296
|
//#region src/infrastructure/cloudflare/d1-database-execution.d.ts
|
|
297
|
-
/**
|
|
298
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Convert database failures and preserve other exceptions. The default executes once.
|
|
299
|
+
* Opt-in transient retries allow three attempts with full jitter and backoff caps of 250 ms and 500 ms.
|
|
300
|
+
*/
|
|
301
|
+
export declare function executeDatabase<T>(operation: string, work: () => PromiseLike<T>, options?: {
|
|
302
|
+
readonly retry?: 'never' | 'transient';
|
|
303
|
+
}): Promise<T>;
|
|
299
304
|
//#endregion
|
|
300
305
|
//#region src/infrastructure/cloudflare/d1-outbox-repository.d.ts
|
|
301
306
|
/**
|
package/dist/cloudflare.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.d.ts","names":[],"sources":["../src/infrastructure/cloudflare/d1-database-types.ts","../src/infrastructure/cloudflare/base-d1-repository.ts","../src/infrastructure/cloudflare/classify-d1-error.ts","../src/infrastructure/cloudflare/d1-message-store.ts","../src/infrastructure/cloudflare/d1-schema.ts","../src/infrastructure/cloudflare/d1-batch-transaction.ts","../src/infrastructure/cloudflare/d1-database-execution.ts","../src/infrastructure/cloudflare/d1-outbox-repository.ts"],"mappings":";;;;;;;KAKY,eAAe;;KAGf,KAAK,eAAe,iBAAiB,kBAAkB;;KAGvD,SAAS,eAAe,iBAAiB,KACnD,KAAK;;KAKK,KAAK,eAAe;WACrB,IAAI,KAAK;;;;;UCRH;EACf,iBAAiB,cAAc;EAC/B;;;;;;8BAOoB,iBAAiB,eAAe;qBACrB,IAAI,KAAK;EAAxC,YAA+B,IAAI,KAAK;;YAG9B,MAAM,WAAW;;YAKjB,KAAK,GAAG,mBAAmB,aAAa,YAAY,KAAK,QAAQ;;YAKjE,eAAe,WAAW;;;;;;;;wBCVtB,gBAAgB,gBAAgB,qBAAyB;;;;qBCb5D,eAAe,eAAe,uBACjC,iBAAiB,mBACd;EAEL,MAAM,WAAW,YAAY;EAc7B,cAAc,QAAQ,cAAc,eAAe;;;;;qBCtB9C,qDAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAAjB;;;;;;;;;;;;;;;;qBAYA,iDAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCPb,mBAAmB,eAAe,0BAA0B,aACvE,KAAK;mBAEwB;EAA7B,YAA6B,IAAI,KAAK;EAEhC,YAAY,GAAG,OAAO,IAAI,KAAK,YAAY,QAAQ,KAAK,QAAQ
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","names":[],"sources":["../src/infrastructure/cloudflare/d1-database-types.ts","../src/infrastructure/cloudflare/base-d1-repository.ts","../src/infrastructure/cloudflare/classify-d1-error.ts","../src/infrastructure/cloudflare/d1-message-store.ts","../src/infrastructure/cloudflare/d1-schema.ts","../src/infrastructure/cloudflare/d1-batch-transaction.ts","../src/infrastructure/cloudflare/d1-database-execution.ts","../src/infrastructure/cloudflare/d1-outbox-repository.ts"],"mappings":";;;;;;;KAKY,eAAe;;KAGf,KAAK,eAAe,iBAAiB,kBAAkB;;KAGvD,SAAS,eAAe,iBAAiB,KACnD,KAAK;;KAKK,KAAK,eAAe;WACrB,IAAI,KAAK;;;;;UCRH;EACf,iBAAiB,cAAc;EAC/B;;;;;;8BAOoB,iBAAiB,eAAe;qBACrB,IAAI,KAAK;EAAxC,YAA+B,IAAI,KAAK;;YAG9B,MAAM,WAAW;;YAKjB,KAAK,GAAG,mBAAmB,aAAa,YAAY,KAAK,QAAQ;;YAKjE,eAAe,WAAW;;;;;;;;wBCVtB,gBAAgB,gBAAgB,qBAAyB;;;;qBCb5D,eAAe,eAAe,uBACjC,iBAAiB,mBACd;EAEL,MAAM,WAAW,YAAY;EAc7B,cAAc,QAAQ,cAAc,eAAe;;;;;qBCtB9C,qDAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAAjB;;;;;;;;;;;;;;;;qBAYA,iDAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCPb,mBAAmB,eAAe,0BAA0B,aACvE,KAAK;mBAEwB;EAA7B,YAA6B,IAAI,KAAK;EAEhC,YAAY,GAAG,OAAO,IAAI,KAAK,YAAY,QAAQ,KAAK,QAAQ;;;;;;;;wBCNlD,gBAAgB,GACpC,mBACA,YAAY,YAAY,IACxB;WAAoB;IACnB,QAAQ;;;;;;;qBCIE,mBAAmB,eAAe,0BAA0B;mBAIpD;mBAEA;mBALF;EAEjB,YACmB,IAAI,KAAK,SAC1B,UAAS,yBACQ,YAAW;;EAMxB,aAAa,oBAAoB,QAAQ;;EAmDzC,cAAc,OAAO,aAAa,UAAU,cAAc,iBAAiB;;EAgB3E,aAAa,OAAO,aAAa,UAAU,cAAc,iBAAiB;;EA6B1E,WACJ,OAAO,aACP,UAAU,cAAc,gBACxB,QAAQ,kBACR,uBACC;;EAqBG,sBAAsB,QAAQ,MAAM,oBAAoB;;EAqBxD,mBAAmB,QAAQ,MAAM,oBAAoB;;EAqBrD,qBAAqB,QAAQ,MAAM,oBAAoB"}
|
package/dist/cloudflare.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { o as MessageAlreadyProcessedError } from "./application-
|
|
3
|
-
import { a as stageD1Statement, i as runWithUnitOfWorkContext, n as collectD1Statements, r as eventCollector, t as OUTBOX_REPOSITORY_DEFAULTS } from "./outbox-repository-options-
|
|
1
|
+
import { c as exponentialDelayCapMs, k as DatabaseError, rt as AppError } from "./core-ClSP5-V9.js";
|
|
2
|
+
import { o as MessageAlreadyProcessedError } from "./application-Cf9SER-i.js";
|
|
3
|
+
import { a as stageD1Statement, i as runWithUnitOfWorkContext, n as collectD1Statements, r as eventCollector, t as OUTBOX_REPOSITORY_DEFAULTS } from "./outbox-repository-options-ICOF8g2h.js";
|
|
4
|
+
import { Result } from "better-result";
|
|
4
5
|
import { DrizzleQueryError } from "drizzle-orm/errors";
|
|
5
6
|
import { and, asc, eq, inArray, isNull, lt, lte, or, sql } from "drizzle-orm";
|
|
6
7
|
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
@@ -68,13 +69,23 @@ function classificationOf(message) {
|
|
|
68
69
|
|
|
69
70
|
//#endregion
|
|
70
71
|
//#region src/infrastructure/cloudflare/d1-database-execution.ts
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Convert database failures and preserve other exceptions. The default executes once.
|
|
74
|
+
* Opt-in transient retries allow three attempts with full jitter and backoff caps of 250 ms and 500 ms.
|
|
75
|
+
*/
|
|
76
|
+
async function executeDatabase(operation, work, options = {}) {
|
|
77
|
+
const result = await Result.tryPromise({
|
|
78
|
+
try: async () => await work(),
|
|
79
|
+
catch: (cause) => classifyD1Error(cause, operation) ?? cause
|
|
80
|
+
}, { retry: {
|
|
81
|
+
times: options.retry === "transient" ? 2 : 0,
|
|
82
|
+
delayMs: 250,
|
|
83
|
+
backoff: "exponential",
|
|
84
|
+
jitter: true,
|
|
85
|
+
shouldRetry: (error) => error instanceof DatabaseError && error.classification === "transient"
|
|
86
|
+
} });
|
|
87
|
+
if (result.isErr()) throw result.error;
|
|
88
|
+
return result.value;
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
//#endregion
|
package/dist/cloudflare.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.js","names":[],"sources":["../src/infrastructure/cloudflare/classify-d1-error.ts","../src/infrastructure/cloudflare/d1-database-execution.ts","../src/infrastructure/cloudflare/base-d1-repository.ts","../src/infrastructure/cloudflare/d1-schema.ts","../src/infrastructure/cloudflare/d1-message-store.ts","../src/infrastructure/cloudflare/d1-batch-transaction.ts","../src/infrastructure/cloudflare/d1-outbox-repository.ts"],"sourcesContent":["import { DrizzleQueryError } from 'drizzle-orm/errors'\n\nimport { AppError, DatabaseError, type D1ConstraintKind, type FailureClassification } from '@/core'\n\nconst KIND_BY_CODE: ReadonlyMap<string, D1ConstraintKind> = new Map([\n ['SQLITE_CONSTRAINT_PRIMARYKEY', 'unique'],\n ['SQLITE_CONSTRAINT_UNIQUE', 'unique'],\n ['SQLITE_CONSTRAINT_NOTNULL', 'not_null'],\n ['SQLITE_CONSTRAINT_FOREIGNKEY', 'foreign_key'],\n ['SQLITE_CONSTRAINT_CHECK', 'check'],\n])\n\nconst KIND_BY_PROSE: ReadonlyArray<readonly [string, D1ConstraintKind]> = [\n ['UNIQUE constraint failed', 'unique'],\n ['NOT NULL constraint failed', 'not_null'],\n ['FOREIGN KEY constraint failed', 'foreign_key'],\n ['CHECK constraint failed', 'check'],\n]\n\n/**\n * Recognize D1 failures and preserve the original cause. Unrecognized callback exceptions remain unchanged.\n * D1 exposes error codes and constraint locations in text, so parsing stays at this boundary.\n */\nexport function classifyD1Error(cause: unknown, operation = 'D1 query'): DatabaseError | undefined {\n if (cause instanceof AppError || (cause instanceof Error && cause.name === 'AbortError'))\n return undefined\n const message = messagesOf(cause)\n const kind = kindOf(message)\n const classification = kind === undefined ? classificationOf(message) : 'terminal'\n const code = message.match(/\\b(?:SQLITE_[A-Z_]+|D1_[A-Z_]+)\\b/)?.[0]\n if (code === undefined && classification === 'unknown' && !(cause instanceof DrizzleQueryError))\n return undefined\n // A composite unique constraint must not be mistaken for the processed-message primary key.\n const location = message.match(/constraint failed: (\\w+)\\.(\\w+)(?=\\s*(?::\\s*SQLITE_|$|\\n))/)\n const check = kind === 'check' ? message.match(/CHECK constraint failed: ([A-Za-z_]\\w*)/) : null\n return new DatabaseError(\n operation,\n classification,\n {\n database: 'd1',\n code,\n kind,\n table: location?.[1],\n column: location?.[2],\n constraintName: check?.[1],\n },\n { cause },\n )\n}\n\nfunction kindOf(message: string): D1ConstraintKind | undefined {\n const code = message.match(/SQLITE_CONSTRAINT_\\w+/)?.[0]\n if (code !== undefined) return KIND_BY_CODE.get(code)\n return KIND_BY_PROSE.find(([prose]) => message.includes(prose))?.[1]\n}\n\n// Drizzle query messages contain SQL and values, which are not evidence about the database failure.\nfunction messagesOf(cause: unknown): string {\n const messages: string[] = []\n const seen = new Set<unknown>()\n let current = cause\n while (current instanceof Error && !seen.has(current)) {\n seen.add(current)\n if (!(current instanceof DrizzleQueryError)) messages.push(current.message)\n current = current.cause\n }\n // oxlint-disable-next-line anti-slop/no-runtime-typeof -- D1 can expose a raw string cause; this boundary accepts only that representation.\n if (typeof current === 'string') messages.push(current)\n return messages.join('\\n')\n}\n\n// Cloudflare documents these conditions separately from transient storage resets.\nconst TERMINAL_FAILURE =\n /\\bSQLITE_(?:ERROR|MISMATCH|TOOBIG|FULL|AUTH|PERM|READONLY)\\b|\\bD1_(?:TYPE_ERROR|COLUMN_NOTFOUND)\\b|no such (?:table|column|function)|syntax error|datatype mismatch|exceeded (?:timeout|its memory limit|its CPU time limit|maximum DB size)|maximum account storage limit|free tier daily row (?:read|write) limit/i\nconst TRANSIENT_FAILURE =\n /Network connection lost\\.|Replica disconnected from primary\\.|D1 DB reset because its code was updated\\.|Internal error (?:while starting up|in) D1 DB storage caused object to be reset\\.|Cannot resolve D1 DB due to transient issue on remote node\\.|Can't read from request stream because client disconnected\\./i\n\nfunction classificationOf(message: string): FailureClassification {\n if (TERMINAL_FAILURE.test(message)) return 'terminal'\n if (TRANSIENT_FAILURE.test(message)) return 'transient'\n return 'unknown'\n}\n","import { classifyD1Error } from './classify-d1-error.ts'\n\n/** Execute once without retries; convert recognized D1 failures and preserve other exceptions unchanged. */\nexport async function executeDatabase<T>(\n operation: string,\n work: () => PromiseLike<T>,\n): Promise<T> {\n try {\n return await work()\n } catch (cause) {\n throw classifyD1Error(cause, operation) ?? cause\n }\n}\n","import type { BatchItem } from 'drizzle-orm/batch'\n\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { eventCollector, stageD1Statement } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db } from './d1-database-types.ts'\n\n/** A domain aggregate that accumulates events for the unit of work to drain. */\nexport interface AggregateEvents {\n collectEvents(): ReadonlyArray<DomainEvent>\n clearEvents(): void\n}\n\n/**\n * Shared plumbing for a repository that writes inside a D1 unit of work.\n * Writes are staged, never run; the boundary commits them as one batch. Reads run on `db` now.\n */\nexport abstract class BaseD1Repository<Schema extends DrizzleSchema> {\n constructor(protected readonly db: D1Db<Schema>) {}\n\n /** Add a statement to the current D1 batch without executing it. */\n protected stage(statement: BatchItem<'sqlite'>): void {\n stageD1Statement(statement)\n }\n\n /** Execute one read with database error conversion and no automatic retries. */\n protected read<T>(operation: string, query: () => PromiseLike<T>): Promise<T> {\n return executeDatabase(operation, query)\n }\n\n /** Drain an aggregate's pending events into the active boundary. */\n protected dispatchEvents(aggregate: AggregateEvents): void {\n const events = aggregate.collectEvents()\n if (events.length === 0) return\n for (const event of events) eventCollector.emit(event)\n aggregate.clearEvents()\n }\n}\n","import { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'\n\nimport type { DomainEvent } from '@/core'\n\n/** One row per committed message. The primary key is the idempotency claim. */\nexport const processedMessages = sqliteTable(\n 'processed_messages',\n {\n messageId: text('message_id').primaryKey(),\n processedAt: integer('processed_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n },\n (table) => [index('idx_processed_messages_processed_at').on(table.processedAt)],\n)\n\n/** Events committed with their state change, waiting for the relay to deliver them. */\nexport const messageOutbox = sqliteTable(\n 'message_outbox',\n {\n id: text('id').primaryKey(),\n payload: text('payload', { mode: 'json' }).$type<DomainEvent>().notNull(),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n availableAt: integer('available_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n attempts: integer('attempts').notNull().default(0),\n claimToken: text('claim_token'),\n claimExpiresAt: integer('claim_expires_at', { mode: 'timestamp' }),\n publishedAt: integer('published_at', { mode: 'timestamp' }),\n failedAt: integer('failed_at', { mode: 'timestamp' }),\n lastError: text('last_error'),\n },\n (table) => [\n index('idx_message_outbox_due')\n .on(table.availableAt, table.createdAt)\n .where(sql`${table.publishedAt} IS NULL`),\n index('idx_message_outbox_claim_token').on(table.claimToken),\n ],\n)\n","import { eq } from 'drizzle-orm'\n\nimport { MessageAlreadyProcessedError, type IMessageStore, type MessageId } from '@/application'\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { BaseD1Repository } from './base-d1-repository.ts'\nimport { messageOutbox, processedMessages } from './d1-schema.ts'\n\n/** Processed messages and outbox events share one D1 batch; the primary key prevents concurrent duplicate commits. */\nexport class D1MessageStore<Schema extends DrizzleSchema>\n extends BaseD1Repository<Schema>\n implements IMessageStore\n{\n async claim(messageId: MessageId): Promise<void> {\n const processed = await this.read('claim message', () =>\n this.db\n .select({ messageId: processedMessages.messageId })\n .from(processedMessages)\n .where(eq(processedMessages.messageId, messageId))\n .get(),\n )\n if (processed !== undefined) throw new MessageAlreadyProcessedError(messageId)\n\n // Concurrent deliveries can pass the read; the primary key enforces the claim at commit.\n this.stage(this.db.insert(processedMessages).values({ messageId }))\n }\n\n async persistEvents(events: ReadonlyArray<DomainEvent>): Promise<void> {\n if (events.length === 0) return\n this.stage(\n this.db\n .insert(messageOutbox)\n .values(events.map((event) => ({ id: event.id, payload: event }))),\n )\n }\n}\n","import type { ITransaction } from '@/application'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { collectD1Statements, runWithUnitOfWorkContext } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db, D1Tx } from './d1-database-types.ts'\n\n/**\n * The D1 transaction boundary: every write is staged during `work` and committed as one\n * `db.batch()`. A throw inside `work` means the batch never runs.\n */\nexport class D1BatchTransaction<Schema extends DrizzleSchema> implements ITransaction<\n D1Tx<Schema>\n> {\n constructor(private readonly db: D1Db<Schema>) {}\n\n async transaction<T>(work: (tx: D1Tx<Schema>) => Promise<T>): Promise<T> {\n return runWithUnitOfWorkContext('d1', async () => {\n const value = await work({ db: this.db })\n const [first, ...rest] = collectD1Statements()\n if (first !== undefined) {\n await executeDatabase('commit unit of work', () => this.db.batch([first, ...rest]))\n }\n return value\n })\n }\n}\n","import { and, asc, eq, inArray, isNull, lt, lte, or, sql } from 'drizzle-orm'\n\nimport type { DeadLetterReason, IOutboxRepository, OutboxClaim, OutboxMessage } from '@/application'\nimport { exponentialDelayCapMs } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport {\n OUTBOX_REPOSITORY_DEFAULTS,\n type OutboxRepositoryOptions,\n} from '../outbox-repository-options.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db } from './d1-database-types.ts'\nimport { messageOutbox, processedMessages } from './d1-schema.ts'\n\n/**\n * Typist's atomic D1 claims on a plain connection, outside a unit of work.\n * Every write runs once; lease expiry and the scheduled sweep recover interrupted operations.\n */\nexport class D1OutboxRepository<Schema extends DrizzleSchema> implements IOutboxRepository {\n private readonly options: Required<OutboxRepositoryOptions>\n\n constructor(\n private readonly db: D1Db<Schema>,\n options: OutboxRepositoryOptions = {},\n private readonly now: () => Date = () => new Date(),\n ) {\n this.options = { ...OUTBOX_REPOSITORY_DEFAULTS, ...options }\n }\n\n /** Lease due rows in one statement; increment attempts even when reclaiming an expired lease. */\n async claimPending(batchSize: number): Promise<OutboxClaim> {\n const token = crypto.randomUUID()\n const now = this.now()\n const rows = await executeDatabase('claim pending outbox messages', () =>\n this.db\n .update(messageOutbox)\n .set({\n claimToken: token,\n claimExpiresAt: deadline(now.getTime() + this.options.leaseMs),\n attempts: sql`${messageOutbox.attempts} + 1`,\n })\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(\n and(\n isNull(messageOutbox.publishedAt),\n isNull(messageOutbox.failedAt),\n lte(messageOutbox.availableAt, now),\n or(isNull(messageOutbox.claimExpiresAt), lte(messageOutbox.claimExpiresAt, now)),\n ),\n )\n .orderBy(\n asc(messageOutbox.availableAt),\n asc(messageOutbox.createdAt),\n asc(messageOutbox.id),\n )\n .limit(batchSize),\n ),\n )\n .returning({\n payload: messageOutbox.payload,\n attempts: messageOutbox.attempts,\n availableAt: messageOutbox.availableAt,\n createdAt: messageOutbox.createdAt,\n }),\n )\n // UPDATE RETURNING does not preserve the selection order.\n rows.sort(\n (a, b) =>\n a.availableAt.getTime() - b.availableAt.getTime() ||\n a.createdAt.getTime() - b.createdAt.getTime() ||\n a.payload.id.localeCompare(b.payload.id),\n )\n return { token, messages: rows.map(({ payload, attempts }) => ({ payload, attempts })) }\n }\n\n /** Mark only supplied rows that still hold this token as published. */\n async markPublished(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void> {\n if (messages.length === 0) return\n await executeDatabase('mark outbox messages published', () =>\n this.db\n .update(messageOutbox)\n .set({ publishedAt: this.now(), claimToken: null, claimExpiresAt: null })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(messages)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n\n /** Release supplied rows with per-row backoff; a stale token cannot change a newer claim. */\n async releaseClaim(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void> {\n const now = this.now()\n for (const attempts of new Set(messages.map((row) => row.attempts))) {\n const rows = messages.filter((row) => row.attempts === attempts)\n const delay = exponentialDelayCapMs(\n this.options.retryBaseDelayMs,\n this.options.retryMaxDelayMs,\n attempts,\n )\n // oxlint-disable-next-line no-await-in-loop -- Stop releasing rows on the first database failure.\n await executeDatabase('release outbox claim', () =>\n this.db\n .update(messageOutbox)\n .set({\n availableAt: deadline(now.getTime() + delay),\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(rows)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n }\n\n /** Retain the full payload and record a closed reason with a readable error message. */\n async deadLetter(\n claim: OutboxClaim,\n messages: ReadonlyArray<OutboxMessage>,\n reason: DeadLetterReason,\n errorMessage: string,\n ): Promise<void> {\n if (messages.length === 0) return\n await executeDatabase('dead-letter outbox messages', () =>\n this.db\n .update(messageOutbox)\n .set({\n failedAt: this.now(),\n lastError: `${reason}: ${errorMessage}`,\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(messages)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n\n /** Delete at most batchSize published rows, strictly before the cutoff. */\n async deletePublishedBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete published outbox messages', () =>\n this.db\n .delete(messageOutbox)\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(lt(messageOutbox.publishedAt, cutoff))\n .orderBy(asc(messageOutbox.publishedAt), asc(messageOutbox.id))\n .limit(batchSize),\n ),\n )\n .returning({ id: messageOutbox.id }),\n )\n return rows.length\n }\n\n /** Delete at most batchSize failed rows; pending and published rows remain untouched. */\n async deleteFailedBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete failed outbox messages', () =>\n this.db\n .delete(messageOutbox)\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(and(isNull(messageOutbox.publishedAt), lt(messageOutbox.failedAt, cutoff)))\n .orderBy(asc(messageOutbox.failedAt), asc(messageOutbox.id))\n .limit(batchSize),\n ),\n )\n .returning({ id: messageOutbox.id }),\n )\n return rows.length\n }\n\n /** Delete at most batchSize receipts, strictly before the cutoff. */\n async deleteReceiptsBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete message receipts', () =>\n this.db\n .delete(processedMessages)\n .where(\n inArray(\n processedMessages.messageId,\n this.db\n .select({ messageId: processedMessages.messageId })\n .from(processedMessages)\n .where(lt(processedMessages.processedAt, cutoff))\n .orderBy(asc(processedMessages.processedAt), asc(processedMessages.messageId))\n .limit(batchSize),\n ),\n )\n .returning({ messageId: processedMessages.messageId }),\n )\n return rows.length\n }\n}\n\n// One JSON parameter keeps large claims below D1's 100-parameter limit.\nfunction outboxIds(messages: ReadonlyArray<OutboxMessage>) {\n return sql`(select value from json_each(${JSON.stringify(messages.map((row) => row.payload.id))}))`\n}\n\n// D1 timestamps repository seconds; round future deadlines up so a lease or delay never ends early.\nfunction deadline(ms: number): Date {\n return new Date(Math.ceil(ms / 1_000) * 1_000)\n}\n"],"mappings":";;;;;;;;AAIA,MAAM,+BAAsD,IAAI,IAAI;CAClE,CAAC,gCAAgC,QAAQ;CACzC,CAAC,4BAA4B,QAAQ;CACrC,CAAC,6BAA6B,UAAU;CACxC,CAAC,gCAAgC,aAAa;CAC9C,CAAC,2BAA2B,OAAO;AACrC,CAAC;AAED,MAAM,gBAAoE;CACxE,CAAC,4BAA4B,QAAQ;CACrC,CAAC,8BAA8B,UAAU;CACzC,CAAC,iCAAiC,aAAa;CAC/C,CAAC,2BAA2B,OAAO;AACrC;;;;;AAMA,SAAgB,gBAAgB,OAAgB,YAAY,YAAuC;CACjG,IAAI,iBAAiB,YAAa,iBAAiB,SAAS,MAAM,SAAS,cACzE,OAAO;CACT,MAAM,UAAU,WAAW,KAAK;CAChC,MAAM,OAAO,OAAO,OAAO;CAC3B,MAAM,iBAAiB,SAAS,SAAY,iBAAiB,OAAO,IAAI;CACxE,MAAM,OAAO,QAAQ,MAAM,mCAAmC,CAAC,GAAG;CAClE,IAAI,SAAS,UAAa,mBAAmB,aAAa,EAAE,iBAAiB,oBAC3E,OAAO;CAET,MAAM,WAAW,QAAQ,MAAM,4DAA4D;CAC3F,MAAM,QAAQ,SAAS,UAAU,QAAQ,MAAM,yCAAyC,IAAI;CAC5F,OAAO,IAAI,cACT,WACA,gBACA;EACE,UAAU;EACV;EACA;EACA,OAAO,WAAW;EAClB,QAAQ,WAAW;EACnB,gBAAgB,QAAQ;CAC1B,GACA,EAAE,MAAM,CACV;AACF;AAEA,SAAS,OAAO,SAA+C;CAC7D,MAAM,OAAO,QAAQ,MAAM,uBAAuB,CAAC,GAAG;CACtD,IAAI,SAAS,QAAW,OAAO,aAAa,IAAI,IAAI;CACpD,OAAO,cAAc,MAAM,CAAC,WAAW,QAAQ,SAAS,KAAK,CAAC,CAAC,GAAG;AACpE;AAGA,SAAS,WAAW,OAAwB;CAC1C,MAAM,WAAqB,CAAC;CAC5B,MAAM,uBAAO,IAAI,IAAa;CAC9B,IAAI,UAAU;CACd,OAAO,mBAAmB,SAAS,CAAC,KAAK,IAAI,OAAO,GAAG;EACrD,KAAK,IAAI,OAAO;EAChB,IAAI,EAAE,mBAAmB,oBAAoB,SAAS,KAAK,QAAQ,OAAO;EAC1E,UAAU,QAAQ;CACpB;CAEA,IAAI,OAAO,YAAY,UAAU,SAAS,KAAK,OAAO;CACtD,OAAO,SAAS,KAAK,IAAI;AAC3B;AAGA,MAAM,mBACJ;AACF,MAAM,oBACJ;AAEF,SAAS,iBAAiB,SAAwC;CAChE,IAAI,iBAAiB,KAAK,OAAO,GAAG,OAAO;CAC3C,IAAI,kBAAkB,KAAK,OAAO,GAAG,OAAO;CAC5C,OAAO;AACT;;;;;AC9EA,eAAsB,gBACpB,WACA,MACY;CACZ,IAAI;EACF,OAAO,MAAM,KAAK;CACpB,SAAS,OAAO;EACd,MAAM,gBAAgB,OAAO,SAAS,KAAK;CAC7C;AACF;;;;;;;;ACOA,IAAsB,mBAAtB,MAAqE;CACpC;CAA/B,YAAY,AAAmB,IAAkB;EAAlB;CAAmB;;CAGlD,AAAU,MAAM,WAAsC;EACpD,iBAAiB,SAAS;CAC5B;;CAGA,AAAU,KAAQ,WAAmB,OAAyC;EAC5E,OAAO,gBAAgB,WAAW,KAAK;CACzC;;CAGA,AAAU,eAAe,WAAkC;EACzD,MAAM,SAAS,UAAU,cAAc;EACvC,IAAI,OAAO,WAAW,GAAG;EACzB,KAAK,MAAM,SAAS,QAAQ,eAAe,KAAK,KAAK;EACrD,UAAU,YAAY;CACxB;AACF;;;;;ACjCA,MAAa,oBAAoB,YAC/B,sBACA;CACE,WAAW,KAAK,YAAY,CAAC,CAAC,WAAW;CACzC,aAAa,QAAQ,gBAAgB,EAAE,MAAM,YAAY,CAAC,CAAC,CACxD,QAAQ,CAAC,CACT,iCAAiB,IAAI,KAAK,CAAC;AAChC,IACC,UAAU,CAAC,MAAM,qCAAqC,CAAC,CAAC,GAAG,MAAM,WAAW,CAAC,CAChF;;AAGA,MAAa,gBAAgB,YAC3B,kBACA;CACE,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,SAAS,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,MAAmB,CAAC,CAAC,QAAQ;CACxE,WAAW,QAAQ,cAAc,EAAE,MAAM,YAAY,CAAC,CAAC,CACpD,QAAQ,CAAC,CACT,iCAAiB,IAAI,KAAK,CAAC;CAC9B,aAAa,QAAQ,gBAAgB,EAAE,MAAM,YAAY,CAAC,CAAC,CACxD,QAAQ,CAAC,CACT,iCAAiB,IAAI,KAAK,CAAC;CAC9B,UAAU,QAAQ,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;CACjD,YAAY,KAAK,aAAa;CAC9B,gBAAgB,QAAQ,oBAAoB,EAAE,MAAM,YAAY,CAAC;CACjE,aAAa,QAAQ,gBAAgB,EAAE,MAAM,YAAY,CAAC;CAC1D,UAAU,QAAQ,aAAa,EAAE,MAAM,YAAY,CAAC;CACpD,WAAW,KAAK,YAAY;AAC9B,IACC,UAAU,CACT,MAAM,wBAAwB,CAAC,CAC5B,GAAG,MAAM,aAAa,MAAM,SAAS,CAAC,CACtC,MAAM,GAAG,GAAG,MAAM,YAAY,SAAS,GAC1C,MAAM,gCAAgC,CAAC,CAAC,GAAG,MAAM,UAAU,CAC7D,CACF;;;;;AChCA,IAAa,iBAAb,cACU,iBAEV;CACE,MAAM,MAAM,WAAqC;EAQ/C,IAAI,MAPoB,KAAK,KAAK,uBAChC,KAAK,GACF,OAAO,EAAE,WAAW,kBAAkB,UAAU,CAAC,CAAC,CAClD,KAAK,iBAAiB,CAAC,CACvB,MAAM,GAAG,kBAAkB,WAAW,SAAS,CAAC,CAAC,CACjD,IAAI,CACT,MACkB,QAAW,MAAM,IAAI,6BAA6B,SAAS;EAG7E,KAAK,MAAM,KAAK,GAAG,OAAO,iBAAiB,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;CACpE;CAEA,MAAM,cAAc,QAAmD;EACrE,IAAI,OAAO,WAAW,GAAG;EACzB,KAAK,MACH,KAAK,GACF,OAAO,aAAa,CAAC,CACrB,OAAO,OAAO,KAAK,WAAW;GAAE,IAAI,MAAM;GAAI,SAAS;EAAM,EAAE,CAAC,CACrE;CACF;AACF;;;;;;;;ACzBA,IAAa,qBAAb,MAEE;CAC6B;CAA7B,YAAY,AAAiB,IAAkB;EAAlB;CAAmB;CAEhD,MAAM,YAAe,MAAoD;EACvE,OAAO,yBAAyB,MAAM,YAAY;GAChD,MAAM,QAAQ,MAAM,KAAK,EAAE,IAAI,KAAK,GAAG,CAAC;GACxC,MAAM,CAAC,OAAO,GAAG,QAAQ,oBAAoB;GAC7C,IAAI,UAAU,QACZ,MAAM,gBAAgB,6BAA6B,KAAK,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;GAEpF,OAAO;EACT,CAAC;CACH;AACF;;;;;;;;ACRA,IAAa,qBAAb,MAA2F;CAItE;CAEA;CALnB,AAAiB;CAEjB,YACE,AAAiB,IACjB,UAAmC,CAAC,GACpC,AAAiB,4BAAwB,IAAI,KAAK,GAClD;EAHiB;EAEA;EAEjB,KAAK,UAAU;GAAE,GAAG;GAA4B,GAAG;EAAQ;CAC7D;;CAGA,MAAM,aAAa,WAAyC;EAC1D,MAAM,QAAQ,OAAO,WAAW;EAChC,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,OAAO,MAAM,gBAAgB,uCACjC,KAAK,GACF,OAAO,aAAa,CAAC,CACrB,IAAI;GACH,YAAY;GACZ,gBAAgB,SAAS,IAAI,QAAQ,IAAI,KAAK,QAAQ,OAAO;GAC7D,UAAU,GAAG,GAAG,cAAc,SAAS;EACzC,CAAC,CAAC,CACD,MACC,QACE,cAAc,IACd,KAAK,GACF,OAAO,EAAE,IAAI,cAAc,GAAG,CAAC,CAAC,CAChC,KAAK,aAAa,CAAC,CACnB,MACC,IACE,OAAO,cAAc,WAAW,GAChC,OAAO,cAAc,QAAQ,GAC7B,IAAI,cAAc,aAAa,GAAG,GAClC,GAAG,OAAO,cAAc,cAAc,GAAG,IAAI,cAAc,gBAAgB,GAAG,CAAC,CACjF,CACF,CAAC,CACA,QACC,IAAI,cAAc,WAAW,GAC7B,IAAI,cAAc,SAAS,GAC3B,IAAI,cAAc,EAAE,CACtB,CAAC,CACA,MAAM,SAAS,CACpB,CACF,CAAC,CACA,UAAU;GACT,SAAS,cAAc;GACvB,UAAU,cAAc;GACxB,aAAa,cAAc;GAC3B,WAAW,cAAc;EAC3B,CAAC,CACL;EAEA,KAAK,MACF,GAAG,MACF,EAAE,YAAY,QAAQ,IAAI,EAAE,YAAY,QAAQ,KAChD,EAAE,UAAU,QAAQ,IAAI,EAAE,UAAU,QAAQ,KAC5C,EAAE,QAAQ,GAAG,cAAc,EAAE,QAAQ,EAAE,CAC3C;EACA,OAAO;GAAE;GAAO,UAAU,KAAK,KAAK,EAAE,SAAS,gBAAgB;IAAE;IAAS;GAAS,EAAE;EAAE;CACzF;;CAGA,MAAM,cAAc,OAAoB,UAAuD;EAC7F,IAAI,SAAS,WAAW,GAAG;EAC3B,MAAM,gBAAgB,wCACpB,KAAK,GACF,OAAO,aAAa,CAAC,CACrB,IAAI;GAAE,aAAa,KAAK,IAAI;GAAG,YAAY;GAAM,gBAAgB;EAAK,CAAC,CAAC,CACxE,MACC,IACE,QAAQ,cAAc,IAAI,UAAU,QAAQ,CAAC,GAC7C,GAAG,cAAc,YAAY,MAAM,KAAK,CAC1C,CACF,CACJ;CACF;;CAGA,MAAM,aAAa,OAAoB,UAAuD;EAC5F,MAAM,MAAM,KAAK,IAAI;EACrB,KAAK,MAAM,YAAY,IAAI,IAAI,SAAS,KAAK,QAAQ,IAAI,QAAQ,CAAC,GAAG;GACnE,MAAM,OAAO,SAAS,QAAQ,QAAQ,IAAI,aAAa,QAAQ;GAC/D,MAAM,QAAQ,sBACZ,KAAK,QAAQ,kBACb,KAAK,QAAQ,iBACb,QACF;GAEA,MAAM,gBAAgB,8BACpB,KAAK,GACF,OAAO,aAAa,CAAC,CACrB,IAAI;IACH,aAAa,SAAS,IAAI,QAAQ,IAAI,KAAK;IAC3C,YAAY;IACZ,gBAAgB;GAClB,CAAC,CAAC,CACD,MACC,IACE,QAAQ,cAAc,IAAI,UAAU,IAAI,CAAC,GACzC,GAAG,cAAc,YAAY,MAAM,KAAK,CAC1C,CACF,CACJ;EACF;CACF;;CAGA,MAAM,WACJ,OACA,UACA,QACA,cACe;EACf,IAAI,SAAS,WAAW,GAAG;EAC3B,MAAM,gBAAgB,qCACpB,KAAK,GACF,OAAO,aAAa,CAAC,CACrB,IAAI;GACH,UAAU,KAAK,IAAI;GACnB,WAAW,GAAG,OAAO,IAAI;GACzB,YAAY;GACZ,gBAAgB;EAClB,CAAC,CAAC,CACD,MACC,IACE,QAAQ,cAAc,IAAI,UAAU,QAAQ,CAAC,GAC7C,GAAG,cAAc,YAAY,MAAM,KAAK,CAC1C,CACF,CACJ;CACF;;CAGA,MAAM,sBAAsB,QAAc,WAAoC;EAiB5E,QAAO,MAhBY,gBAAgB,0CACjC,KAAK,GACF,OAAO,aAAa,CAAC,CACrB,MACC,QACE,cAAc,IACd,KAAK,GACF,OAAO,EAAE,IAAI,cAAc,GAAG,CAAC,CAAC,CAChC,KAAK,aAAa,CAAC,CACnB,MAAM,GAAG,cAAc,aAAa,MAAM,CAAC,CAAC,CAC5C,QAAQ,IAAI,cAAc,WAAW,GAAG,IAAI,cAAc,EAAE,CAAC,CAAC,CAC9D,MAAM,SAAS,CACpB,CACF,CAAC,CACA,UAAU,EAAE,IAAI,cAAc,GAAG,CAAC,CACvC,EACW,CAAC;CACd;;CAGA,MAAM,mBAAmB,QAAc,WAAoC;EAiBzE,QAAO,MAhBY,gBAAgB,uCACjC,KAAK,GACF,OAAO,aAAa,CAAC,CACrB,MACC,QACE,cAAc,IACd,KAAK,GACF,OAAO,EAAE,IAAI,cAAc,GAAG,CAAC,CAAC,CAChC,KAAK,aAAa,CAAC,CACnB,MAAM,IAAI,OAAO,cAAc,WAAW,GAAG,GAAG,cAAc,UAAU,MAAM,CAAC,CAAC,CAAC,CACjF,QAAQ,IAAI,cAAc,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC,CAAC,CAC3D,MAAM,SAAS,CACpB,CACF,CAAC,CACA,UAAU,EAAE,IAAI,cAAc,GAAG,CAAC,CACvC,EACW,CAAC;CACd;;CAGA,MAAM,qBAAqB,QAAc,WAAoC;EAiB3E,QAAO,MAhBY,gBAAgB,iCACjC,KAAK,GACF,OAAO,iBAAiB,CAAC,CACzB,MACC,QACE,kBAAkB,WAClB,KAAK,GACF,OAAO,EAAE,WAAW,kBAAkB,UAAU,CAAC,CAAC,CAClD,KAAK,iBAAiB,CAAC,CACvB,MAAM,GAAG,kBAAkB,aAAa,MAAM,CAAC,CAAC,CAChD,QAAQ,IAAI,kBAAkB,WAAW,GAAG,IAAI,kBAAkB,SAAS,CAAC,CAAC,CAC7E,MAAM,SAAS,CACpB,CACF,CAAC,CACA,UAAU,EAAE,WAAW,kBAAkB,UAAU,CAAC,CACzD,EACW,CAAC;CACd;AACF;AAGA,SAAS,UAAU,UAAwC;CACzD,OAAO,GAAG,gCAAgC,KAAK,UAAU,SAAS,KAAK,QAAQ,IAAI,QAAQ,EAAE,CAAC,EAAE;AAClG;AAGA,SAAS,SAAS,IAAkB;CAClC,uBAAO,IAAI,KAAK,KAAK,KAAK,KAAK,GAAK,IAAI,GAAK;AAC/C"}
|
|
1
|
+
{"version":3,"file":"cloudflare.js","names":[],"sources":["../src/infrastructure/cloudflare/classify-d1-error.ts","../src/infrastructure/cloudflare/d1-database-execution.ts","../src/infrastructure/cloudflare/base-d1-repository.ts","../src/infrastructure/cloudflare/d1-schema.ts","../src/infrastructure/cloudflare/d1-message-store.ts","../src/infrastructure/cloudflare/d1-batch-transaction.ts","../src/infrastructure/cloudflare/d1-outbox-repository.ts"],"sourcesContent":["import { DrizzleQueryError } from 'drizzle-orm/errors'\n\nimport { AppError, DatabaseError, type D1ConstraintKind, type FailureClassification } from '@/core'\n\nconst KIND_BY_CODE: ReadonlyMap<string, D1ConstraintKind> = new Map([\n ['SQLITE_CONSTRAINT_PRIMARYKEY', 'unique'],\n ['SQLITE_CONSTRAINT_UNIQUE', 'unique'],\n ['SQLITE_CONSTRAINT_NOTNULL', 'not_null'],\n ['SQLITE_CONSTRAINT_FOREIGNKEY', 'foreign_key'],\n ['SQLITE_CONSTRAINT_CHECK', 'check'],\n])\n\nconst KIND_BY_PROSE: ReadonlyArray<readonly [string, D1ConstraintKind]> = [\n ['UNIQUE constraint failed', 'unique'],\n ['NOT NULL constraint failed', 'not_null'],\n ['FOREIGN KEY constraint failed', 'foreign_key'],\n ['CHECK constraint failed', 'check'],\n]\n\n/**\n * Recognize D1 failures and preserve the original cause. Unrecognized callback exceptions remain unchanged.\n * D1 exposes error codes and constraint locations in text, so parsing stays at this boundary.\n */\nexport function classifyD1Error(cause: unknown, operation = 'D1 query'): DatabaseError | undefined {\n if (cause instanceof AppError || (cause instanceof Error && cause.name === 'AbortError'))\n return undefined\n const message = messagesOf(cause)\n const kind = kindOf(message)\n const classification = kind === undefined ? classificationOf(message) : 'terminal'\n const code = message.match(/\\b(?:SQLITE_[A-Z_]+|D1_[A-Z_]+)\\b/)?.[0]\n if (code === undefined && classification === 'unknown' && !(cause instanceof DrizzleQueryError))\n return undefined\n // A composite unique constraint must not be mistaken for the processed-message primary key.\n const location = message.match(/constraint failed: (\\w+)\\.(\\w+)(?=\\s*(?::\\s*SQLITE_|$|\\n))/)\n const check = kind === 'check' ? message.match(/CHECK constraint failed: ([A-Za-z_]\\w*)/) : null\n return new DatabaseError(\n operation,\n classification,\n {\n database: 'd1',\n code,\n kind,\n table: location?.[1],\n column: location?.[2],\n constraintName: check?.[1],\n },\n { cause },\n )\n}\n\nfunction kindOf(message: string): D1ConstraintKind | undefined {\n const code = message.match(/SQLITE_CONSTRAINT_\\w+/)?.[0]\n if (code !== undefined) return KIND_BY_CODE.get(code)\n return KIND_BY_PROSE.find(([prose]) => message.includes(prose))?.[1]\n}\n\n// Drizzle query messages contain SQL and values, which are not evidence about the database failure.\nfunction messagesOf(cause: unknown): string {\n const messages: string[] = []\n const seen = new Set<unknown>()\n let current = cause\n while (current instanceof Error && !seen.has(current)) {\n seen.add(current)\n if (!(current instanceof DrizzleQueryError)) messages.push(current.message)\n current = current.cause\n }\n // oxlint-disable-next-line anti-slop/no-runtime-typeof -- D1 can expose a raw string cause; this boundary accepts only that representation.\n if (typeof current === 'string') messages.push(current)\n return messages.join('\\n')\n}\n\n// Cloudflare documents these conditions separately from transient storage resets.\nconst TERMINAL_FAILURE =\n /\\bSQLITE_(?:ERROR|MISMATCH|TOOBIG|FULL|AUTH|PERM|READONLY)\\b|\\bD1_(?:TYPE_ERROR|COLUMN_NOTFOUND)\\b|no such (?:table|column|function)|syntax error|datatype mismatch|exceeded (?:timeout|its memory limit|its CPU time limit|maximum DB size)|maximum account storage limit|free tier daily row (?:read|write) limit/i\nconst TRANSIENT_FAILURE =\n /Network connection lost\\.|Replica disconnected from primary\\.|D1 DB reset because its code was updated\\.|Internal error (?:while starting up|in) D1 DB storage caused object to be reset\\.|Cannot resolve D1 DB due to transient issue on remote node\\.|Can't read from request stream because client disconnected\\./i\n\nfunction classificationOf(message: string): FailureClassification {\n if (TERMINAL_FAILURE.test(message)) return 'terminal'\n if (TRANSIENT_FAILURE.test(message)) return 'transient'\n return 'unknown'\n}\n","import { Result } from 'better-result'\n\nimport { DatabaseError } from '@/core'\n\nimport { classifyD1Error } from './classify-d1-error.ts'\n\n/**\n * Convert database failures and preserve other exceptions. The default executes once.\n * Opt-in transient retries allow three attempts with full jitter and backoff caps of 250 ms and 500 ms.\n */\nexport async function executeDatabase<T>(\n operation: string,\n work: () => PromiseLike<T>,\n options: { readonly retry?: 'never' | 'transient' } = {},\n): Promise<T> {\n const result = await Result.tryPromise(\n {\n try: async () => await work(),\n catch: (cause) => classifyD1Error(cause, operation) ?? cause,\n },\n {\n retry: {\n times: options.retry === 'transient' ? 2 : 0,\n delayMs: 250,\n backoff: 'exponential',\n jitter: true,\n shouldRetry: (error) =>\n error instanceof DatabaseError && error.classification === 'transient',\n },\n },\n )\n if (result.isErr()) throw result.error\n return result.value\n}\n","import type { BatchItem } from 'drizzle-orm/batch'\n\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { eventCollector, stageD1Statement } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db } from './d1-database-types.ts'\n\n/** A domain aggregate that accumulates events for the unit of work to drain. */\nexport interface AggregateEvents {\n collectEvents(): ReadonlyArray<DomainEvent>\n clearEvents(): void\n}\n\n/**\n * Shared plumbing for a repository that writes inside a D1 unit of work.\n * Writes are staged, never run; the boundary commits them as one batch. Reads run on `db` now.\n */\nexport abstract class BaseD1Repository<Schema extends DrizzleSchema> {\n constructor(protected readonly db: D1Db<Schema>) {}\n\n /** Add a statement to the current D1 batch without executing it. */\n protected stage(statement: BatchItem<'sqlite'>): void {\n stageD1Statement(statement)\n }\n\n /** Execute one read with database error conversion and no automatic retries. */\n protected read<T>(operation: string, query: () => PromiseLike<T>): Promise<T> {\n return executeDatabase(operation, query)\n }\n\n /** Drain an aggregate's pending events into the active boundary. */\n protected dispatchEvents(aggregate: AggregateEvents): void {\n const events = aggregate.collectEvents()\n if (events.length === 0) return\n for (const event of events) eventCollector.emit(event)\n aggregate.clearEvents()\n }\n}\n","import { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'\n\nimport type { DomainEvent } from '@/core'\n\n/** One row per committed message. The primary key is the idempotency claim. */\nexport const processedMessages = sqliteTable(\n 'processed_messages',\n {\n messageId: text('message_id').primaryKey(),\n processedAt: integer('processed_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n },\n (table) => [index('idx_processed_messages_processed_at').on(table.processedAt)],\n)\n\n/** Events committed with their state change, waiting for the relay to deliver them. */\nexport const messageOutbox = sqliteTable(\n 'message_outbox',\n {\n id: text('id').primaryKey(),\n payload: text('payload', { mode: 'json' }).$type<DomainEvent>().notNull(),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n availableAt: integer('available_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n attempts: integer('attempts').notNull().default(0),\n claimToken: text('claim_token'),\n claimExpiresAt: integer('claim_expires_at', { mode: 'timestamp' }),\n publishedAt: integer('published_at', { mode: 'timestamp' }),\n failedAt: integer('failed_at', { mode: 'timestamp' }),\n lastError: text('last_error'),\n },\n (table) => [\n index('idx_message_outbox_due')\n .on(table.availableAt, table.createdAt)\n .where(sql`${table.publishedAt} IS NULL`),\n index('idx_message_outbox_claim_token').on(table.claimToken),\n ],\n)\n","import { eq } from 'drizzle-orm'\n\nimport { MessageAlreadyProcessedError, type IMessageStore, type MessageId } from '@/application'\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { BaseD1Repository } from './base-d1-repository.ts'\nimport { messageOutbox, processedMessages } from './d1-schema.ts'\n\n/** Processed messages and outbox events share one D1 batch; the primary key prevents concurrent duplicate commits. */\nexport class D1MessageStore<Schema extends DrizzleSchema>\n extends BaseD1Repository<Schema>\n implements IMessageStore\n{\n async claim(messageId: MessageId): Promise<void> {\n const processed = await this.read('claim message', () =>\n this.db\n .select({ messageId: processedMessages.messageId })\n .from(processedMessages)\n .where(eq(processedMessages.messageId, messageId))\n .get(),\n )\n if (processed !== undefined) throw new MessageAlreadyProcessedError(messageId)\n\n // Concurrent deliveries can pass the read; the primary key enforces the claim at commit.\n this.stage(this.db.insert(processedMessages).values({ messageId }))\n }\n\n async persistEvents(events: ReadonlyArray<DomainEvent>): Promise<void> {\n if (events.length === 0) return\n this.stage(\n this.db\n .insert(messageOutbox)\n .values(events.map((event) => ({ id: event.id, payload: event }))),\n )\n }\n}\n","import type { ITransaction } from '@/application'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { collectD1Statements, runWithUnitOfWorkContext } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db, D1Tx } from './d1-database-types.ts'\n\n/**\n * The D1 transaction boundary: every write is staged during `work` and committed as one\n * `db.batch()`. A throw inside `work` means the batch never runs.\n */\nexport class D1BatchTransaction<Schema extends DrizzleSchema> implements ITransaction<\n D1Tx<Schema>\n> {\n constructor(private readonly db: D1Db<Schema>) {}\n\n async transaction<T>(work: (tx: D1Tx<Schema>) => Promise<T>): Promise<T> {\n return runWithUnitOfWorkContext('d1', async () => {\n const value = await work({ db: this.db })\n const [first, ...rest] = collectD1Statements()\n if (first !== undefined) {\n await executeDatabase('commit unit of work', () => this.db.batch([first, ...rest]))\n }\n return value\n })\n }\n}\n","import { and, asc, eq, inArray, isNull, lt, lte, or, sql } from 'drizzle-orm'\n\nimport type { DeadLetterReason, IOutboxRepository, OutboxClaim, OutboxMessage } from '@/application'\nimport { exponentialDelayCapMs } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport {\n OUTBOX_REPOSITORY_DEFAULTS,\n type OutboxRepositoryOptions,\n} from '../outbox-repository-options.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db } from './d1-database-types.ts'\nimport { messageOutbox, processedMessages } from './d1-schema.ts'\n\n/**\n * Typist's atomic D1 claims on a plain connection, outside a unit of work.\n * Every write runs once; lease expiry and the scheduled sweep recover interrupted operations.\n */\nexport class D1OutboxRepository<Schema extends DrizzleSchema> implements IOutboxRepository {\n private readonly options: Required<OutboxRepositoryOptions>\n\n constructor(\n private readonly db: D1Db<Schema>,\n options: OutboxRepositoryOptions = {},\n private readonly now: () => Date = () => new Date(),\n ) {\n this.options = { ...OUTBOX_REPOSITORY_DEFAULTS, ...options }\n }\n\n /** Lease due rows in one statement; increment attempts even when reclaiming an expired lease. */\n async claimPending(batchSize: number): Promise<OutboxClaim> {\n const token = crypto.randomUUID()\n const now = this.now()\n const rows = await executeDatabase('claim pending outbox messages', () =>\n this.db\n .update(messageOutbox)\n .set({\n claimToken: token,\n claimExpiresAt: deadline(now.getTime() + this.options.leaseMs),\n attempts: sql`${messageOutbox.attempts} + 1`,\n })\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(\n and(\n isNull(messageOutbox.publishedAt),\n isNull(messageOutbox.failedAt),\n lte(messageOutbox.availableAt, now),\n or(isNull(messageOutbox.claimExpiresAt), lte(messageOutbox.claimExpiresAt, now)),\n ),\n )\n .orderBy(\n asc(messageOutbox.availableAt),\n asc(messageOutbox.createdAt),\n asc(messageOutbox.id),\n )\n .limit(batchSize),\n ),\n )\n .returning({\n payload: messageOutbox.payload,\n attempts: messageOutbox.attempts,\n availableAt: messageOutbox.availableAt,\n createdAt: messageOutbox.createdAt,\n }),\n )\n // UPDATE RETURNING does not preserve the selection order.\n rows.sort(\n (a, b) =>\n a.availableAt.getTime() - b.availableAt.getTime() ||\n a.createdAt.getTime() - b.createdAt.getTime() ||\n a.payload.id.localeCompare(b.payload.id),\n )\n return { token, messages: rows.map(({ payload, attempts }) => ({ payload, attempts })) }\n }\n\n /** Mark only supplied rows that still hold this token as published. */\n async markPublished(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void> {\n if (messages.length === 0) return\n await executeDatabase('mark outbox messages published', () =>\n this.db\n .update(messageOutbox)\n .set({ publishedAt: this.now(), claimToken: null, claimExpiresAt: null })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(messages)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n\n /** Release supplied rows with per-row backoff; a stale token cannot change a newer claim. */\n async releaseClaim(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void> {\n const now = this.now()\n for (const attempts of new Set(messages.map((row) => row.attempts))) {\n const rows = messages.filter((row) => row.attempts === attempts)\n const delay = exponentialDelayCapMs(\n this.options.retryBaseDelayMs,\n this.options.retryMaxDelayMs,\n attempts,\n )\n // oxlint-disable-next-line no-await-in-loop -- Stop releasing rows on the first database failure.\n await executeDatabase('release outbox claim', () =>\n this.db\n .update(messageOutbox)\n .set({\n availableAt: deadline(now.getTime() + delay),\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(rows)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n }\n\n /** Retain the full payload and record a closed reason with a readable error message. */\n async deadLetter(\n claim: OutboxClaim,\n messages: ReadonlyArray<OutboxMessage>,\n reason: DeadLetterReason,\n errorMessage: string,\n ): Promise<void> {\n if (messages.length === 0) return\n await executeDatabase('dead-letter outbox messages', () =>\n this.db\n .update(messageOutbox)\n .set({\n failedAt: this.now(),\n lastError: `${reason}: ${errorMessage}`,\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(messages)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n\n /** Delete at most batchSize published rows, strictly before the cutoff. */\n async deletePublishedBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete published outbox messages', () =>\n this.db\n .delete(messageOutbox)\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(lt(messageOutbox.publishedAt, cutoff))\n .orderBy(asc(messageOutbox.publishedAt), asc(messageOutbox.id))\n .limit(batchSize),\n ),\n )\n .returning({ id: messageOutbox.id }),\n )\n return rows.length\n }\n\n /** Delete at most batchSize failed rows; pending and published rows remain untouched. */\n async deleteFailedBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete failed outbox messages', () =>\n this.db\n .delete(messageOutbox)\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(and(isNull(messageOutbox.publishedAt), lt(messageOutbox.failedAt, cutoff)))\n .orderBy(asc(messageOutbox.failedAt), asc(messageOutbox.id))\n .limit(batchSize),\n ),\n )\n .returning({ id: messageOutbox.id }),\n )\n return rows.length\n }\n\n /** Delete at most batchSize receipts, strictly before the cutoff. */\n async deleteReceiptsBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete message receipts', () =>\n this.db\n .delete(processedMessages)\n .where(\n inArray(\n processedMessages.messageId,\n this.db\n .select({ messageId: processedMessages.messageId })\n .from(processedMessages)\n .where(lt(processedMessages.processedAt, cutoff))\n .orderBy(asc(processedMessages.processedAt), asc(processedMessages.messageId))\n .limit(batchSize),\n ),\n )\n .returning({ messageId: processedMessages.messageId }),\n )\n return rows.length\n }\n}\n\n// One JSON parameter keeps large claims below D1's 100-parameter limit.\nfunction outboxIds(messages: ReadonlyArray<OutboxMessage>) {\n return sql`(select value from json_each(${JSON.stringify(messages.map((row) => row.payload.id))}))`\n}\n\n// D1 timestamps repository seconds; round future deadlines up so a lease or delay never ends early.\nfunction deadline(ms: number): Date {\n return new Date(Math.ceil(ms / 1_000) * 1_000)\n}\n"],"mappings":"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"}
|
|
@@ -503,54 +503,104 @@ function defineId(brand) {
|
|
|
503
503
|
|
|
504
504
|
//#endregion
|
|
505
505
|
//#region src/core/define-messages.ts
|
|
506
|
+
const completed = Symbol("MessageCollection");
|
|
506
507
|
const MessageIdSchema = z.string().min(1);
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
type: z.literal(type),
|
|
511
|
-
name: z.literal(name)
|
|
512
|
-
}) : z.object({
|
|
513
|
-
type: z.literal(type),
|
|
514
|
-
name: z.literal(name),
|
|
508
|
+
const envelopes = {
|
|
509
|
+
command: z.object({
|
|
510
|
+
type: z.literal("command"),
|
|
515
511
|
id: MessageIdSchema
|
|
516
|
-
})
|
|
512
|
+
}),
|
|
513
|
+
event: z.object({
|
|
514
|
+
type: z.literal("event"),
|
|
515
|
+
id: MessageIdSchema
|
|
516
|
+
}),
|
|
517
|
+
query: z.object({ type: z.literal("query") })
|
|
518
|
+
};
|
|
519
|
+
function messageSchema(kind, name, payload) {
|
|
520
|
+
return z.intersection(envelopes[kind], z.object({ name: z.literal(name) })).and(payload);
|
|
521
|
+
}
|
|
522
|
+
function completeSchemas(kind, schemas) {
|
|
523
|
+
const entries = Object.entries(schemas ?? {}).map(([name, payload]) => [name, messageSchema(kind, name, payload)]);
|
|
524
|
+
return Object.freeze(Object.fromEntries(entries));
|
|
525
|
+
}
|
|
526
|
+
function unionOf(schemas) {
|
|
527
|
+
return z.union(Object.values(schemas));
|
|
528
|
+
}
|
|
529
|
+
function collection(maps) {
|
|
530
|
+
return Object.freeze({
|
|
531
|
+
...maps,
|
|
532
|
+
[completed]: true,
|
|
533
|
+
commandSchema: unionOf(maps.commandSchemas),
|
|
534
|
+
eventSchema: unionOf(maps.eventSchemas),
|
|
535
|
+
querySchema: unionOf(maps.querySchemas),
|
|
536
|
+
queueSchema: z.union([...Object.values(maps.commandSchemas), ...Object.values(maps.eventSchemas)]),
|
|
537
|
+
createCommand: (name, payload, id = v7()) => {
|
|
538
|
+
return {
|
|
539
|
+
...payload,
|
|
540
|
+
type: "command",
|
|
541
|
+
name,
|
|
542
|
+
id
|
|
543
|
+
};
|
|
544
|
+
},
|
|
545
|
+
createEvent: (name, payload, id = v7()) => {
|
|
546
|
+
return {
|
|
547
|
+
...payload,
|
|
548
|
+
type: "event",
|
|
549
|
+
name,
|
|
550
|
+
id
|
|
551
|
+
};
|
|
552
|
+
},
|
|
553
|
+
createQuery: (name, payload) => {
|
|
554
|
+
return {
|
|
555
|
+
...payload,
|
|
556
|
+
type: "query",
|
|
557
|
+
name
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
});
|
|
517
561
|
}
|
|
518
|
-
/**
|
|
519
|
-
function
|
|
520
|
-
return
|
|
562
|
+
/** Build each complete schema once. Omitted kinds have empty maps and schemas that reject every input. */
|
|
563
|
+
function defineMessages(definition) {
|
|
564
|
+
return collection({
|
|
565
|
+
commandSchemas: completeSchemas("command", definition.commands),
|
|
566
|
+
eventSchemas: completeSchemas("event", definition.events),
|
|
567
|
+
querySchemas: completeSchemas("query", definition.queries)
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
/** Two collections declare the same name within one message kind. */
|
|
571
|
+
var DuplicateMessageNameError = class extends AppError {
|
|
572
|
+
kind;
|
|
573
|
+
messageName;
|
|
574
|
+
_tag = "DuplicateMessageNameError";
|
|
575
|
+
classification = "terminal";
|
|
576
|
+
/** Identify the duplicate kind and name so the application can correct its definitions. */
|
|
577
|
+
constructor(kind, messageName) {
|
|
578
|
+
super(`Duplicate ${kind} name: ${messageName}`);
|
|
579
|
+
this.kind = kind;
|
|
580
|
+
this.messageName = messageName;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
function mergeSchemas(collections, key, kind) {
|
|
584
|
+
const merged = /* @__PURE__ */ new Map();
|
|
585
|
+
for (const messages of collections) {
|
|
586
|
+
if (!messages?.[completed]) return panic("combineMessages requires completed message collections");
|
|
587
|
+
for (const [name, schema] of Object.entries(messages[key])) {
|
|
588
|
+
if (merged.has(name)) throw new DuplicateMessageNameError(kind, name);
|
|
589
|
+
merged.set(name, schema);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return Object.freeze(Object.fromEntries(merged));
|
|
521
593
|
}
|
|
522
594
|
/**
|
|
523
|
-
*
|
|
524
|
-
*
|
|
595
|
+
* Merge completed collections without rebuilding complete schemas.
|
|
596
|
+
* @throws DuplicateMessageNameError when two collections declare the same name within one kind.
|
|
525
597
|
*/
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
createCommand: (name, payload, id = v7()) => ({
|
|
533
|
-
...payload,
|
|
534
|
-
type: "command",
|
|
535
|
-
name,
|
|
536
|
-
id
|
|
537
|
-
}),
|
|
538
|
-
createEvent: (name, payload, id = v7()) => ({
|
|
539
|
-
...payload,
|
|
540
|
-
type: "event",
|
|
541
|
-
name,
|
|
542
|
-
id
|
|
543
|
-
}),
|
|
544
|
-
createQuery: (name, payload) => ({
|
|
545
|
-
...payload,
|
|
546
|
-
type: "query",
|
|
547
|
-
name
|
|
548
|
-
}),
|
|
549
|
-
commandSchema: commandSchema.pipe(z.custom()),
|
|
550
|
-
eventSchema: eventSchema.pipe(z.custom()),
|
|
551
|
-
querySchema: querySchema.pipe(z.custom()),
|
|
552
|
-
queueSchema: z.union([commandSchema, eventSchema]).pipe(z.custom())
|
|
553
|
-
};
|
|
598
|
+
function combineMessages(...collections) {
|
|
599
|
+
return collection({
|
|
600
|
+
commandSchemas: mergeSchemas(collections, "commandSchemas", "command"),
|
|
601
|
+
eventSchemas: mergeSchemas(collections, "eventSchemas", "event"),
|
|
602
|
+
querySchemas: mergeSchemas(collections, "querySchemas", "query")
|
|
603
|
+
});
|
|
554
604
|
}
|
|
555
605
|
|
|
556
606
|
//#endregion
|
|
@@ -923,5 +973,5 @@ function createLogger(module, config) {
|
|
|
923
973
|
}
|
|
924
974
|
|
|
925
975
|
//#endregion
|
|
926
|
-
export {
|
|
927
|
-
//# sourceMappingURL=core-
|
|
976
|
+
export { emailIdempotencyKey as $, defineErrors as A, InternalServerError as B, DuplicateMessageNameError as C, notImplemented as D, defineId as E, MalformedRequestError as F, AuthenticationError as G, NotAuthorizedError as H, RequestTimeoutError as I, isEmailDeliverable as J, RecordEmailEventPayloadSchema as K, ResourceNotFoundError as L, ProblemDetailsSchema as M, ValidationProblemDetailsSchema as N, unreachable as O, ValidationProblemErrorSchema as P, InvalidEmailIdempotencyKeyError as Q, UpgradeRequiredError as R, Entity as S, defineMessages as T, StaleSessionError as U, ServiceUnavailableError as V, RateLimitError as W, shouldUpdateBounce as X, parseBounceType as Y, EmailServiceError as Z, isCloudflareRetryableError as _, createLogger as a, isTransientTransportError as b, exponentialDelayCapMs as c, classifyBoundaryError as d, EmailAddressSchema as et, classifyFailure as f, isCloudflareOverloadedError as g, isCloudflareObjectResetError as h, consoleSink as i, sharedErrors as j, DatabaseError as k, exponentialEqualJitterMs as l, CLOUDFLARE_TRANSIENT_ERROR_PATTERNS as m, Logger as n, MissingConfigurationError as nt, setLogSink as o, retryAfterSecondsOf as p, BounceTypeSchema as q, configureLogger as r, AppError as rt, setLoggerErrorHook as s, LogLevel as t, AnalyticsEventIdSchema as tt, spreadJitterMs as u, isTransientCloudflareRuntimeError as v, combineMessages as w, shouldRetryFailure as x, isAbortError as y, ValidationError as z };
|
|
977
|
+
//# sourceMappingURL=core-ClSP5-V9.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-ClSP5-V9.js","names":["uuidv7","uuidv7"],"sources":["../src/core/app-error.ts","../src/core/missing-configuration.error.ts","../src/core/analytics-event.ts","../src/core/email-address.ts","../src/core/email-idempotency-key.ts","../src/core/email-service.error.ts","../src/core/email-deliverability.ts","../src/core/record-email-event.ts","../src/core/authentication.error.ts","../src/core/rate-limit.error.ts","../src/core/stale-session.error.ts","../src/core/not-authorized.error.ts","../src/core/internal.errors.ts","../src/core/request.errors.ts","../src/core/problem-details.ts","../src/core/error-table.ts","../src/core/database.error.ts","../src/core/defects.ts","../src/core/identity.ts","../src/core/define-messages.ts","../src/core/entity.ts","../src/core/failure-classification.ts","../src/core/transport-classification.ts","../src/core/cloudflare-classification.ts","../src/core/classify-failure.ts","../src/core/backoff.ts","../src/core/logger.ts"],"sourcesContent":["import type { FailureClassification } from './failure-classification.ts'\n\n/**\n * The base of every expected failure. A subclass declares its tag and its classification,\n * so no boundary has to guess what kind of failure it holds. A boundary tells our errors from\n * raw throws with `instanceof AppError`; a value that fails it was never judged.\n *\n * A `readonly` field with a literal initializer keeps the literal type, so\n * `readonly _tag = 'StoreUnavailable'` is enough for `Result` unions and `matchError`.\n */\nexport abstract class AppError extends Error {\n abstract readonly _tag: string\n abstract readonly classification: FailureClassification\n\n override get name(): string {\n return this._tag\n }\n}\n","import { AppError } from './app-error.ts'\n\n/** Required configuration is absent. Ported from Porte's configuration.errors.ts. */\nexport class MissingConfigurationError extends AppError {\n readonly _tag = 'MissingConfigurationError'\n readonly classification = 'terminal'\n\n /** Name the missing setting without including its value. */\n constructor(readonly key: string) {\n super(`${key} is required`)\n }\n}\n","import { z } from 'zod'\n\n/** An event name and its application-owned properties, ported from Typist's analytics/types.ts. */\nexport interface AnalyticsEventInput<\n TName extends string = string,\n TProperties extends object = object,\n> {\n readonly name: TName\n readonly properties: TProperties\n}\n\n/** A value supported by analytics person properties. */\nexport type AnalyticsProperty =\n | string\n | number\n | boolean\n | null\n | undefined\n | readonly AnalyticsProperty[]\n | AnalyticsProperties\n\n/** Person properties supplied by the application. */\nexport interface AnalyticsProperties {\n readonly [name: string]: AnalyticsProperty\n}\n\n/** Parse a persisted analytics event UUID; the SDK generates ordinary event UUIDs itself. */\nexport const AnalyticsEventIdSchema = z.uuid().brand<'AnalyticsEventId'>()\n\n/** The identity of one analytics event across repeated application deliveries. */\nexport type AnalyticsEventId = z.infer<typeof AnalyticsEventIdSchema>\n\n/** Supply both original values when replaying one event; never share them across different occurrences. */\nexport interface AnalyticsEventIdentity {\n readonly uuid: AnalyticsEventId\n readonly timestamp: Date\n}\n","import { z } from 'zod'\n\n/** A parsed email address for message payloads and webhook recipients. Ported from Typist's `EmailSchema`. */\nexport const EmailAddressSchema = z.email().brand<'EmailAddress'>()\n\n/** An address the schema accepted. */\nexport type EmailAddress = z.infer<typeof EmailAddressSchema>\n","import type { z } from 'zod'\n\nimport { AppError } from './app-error.ts'\n\n/** A key only `emailIdempotencyKey` mints, so every send carries one built by its rules. */\nexport type EmailIdempotencyKey = string & z.core.$brand<'EmailIdempotencyKey'>\n\n// Resend accepts 1 to 256 characters. Source: https://resend.com/docs/dashboard/emails/idempotency-keys\nconst MAX_KEY_LENGTH = 256\n\n/** A key outside 1 to 256 characters. Keys come from stable ids, so this is a defect, not a retry. */\nexport class InvalidEmailIdempotencyKeyError extends AppError {\n readonly _tag = 'InvalidEmailIdempotencyKeyError'\n readonly classification = 'terminal'\n\n constructor(readonly length: number) {\n super(`Email idempotency key must have 1 to ${MAX_KEY_LENGTH} characters`)\n }\n}\n\n/**\n * `<scope>/<id>` for one logical send. Resend deduplicates it for 24 hours.\n * Derive `id` from a stable identity: a message id, a job id, a user id with a template name.\n * The same key must map to an identical payload; a changed body is rejected as terminal.\n * A random `id` means every request is a deliberate new send, as a sign-in code resend is.\n * Source: https://resend.com/docs/dashboard/emails/idempotency-keys\n */\nexport function emailIdempotencyKey(scope: string, id: string): EmailIdempotencyKey {\n const key = `${scope}/${id}`\n if (scope.length === 0 || id.length === 0 || key.length > MAX_KEY_LENGTH)\n throw new InvalidEmailIdempotencyKeyError(key.length)\n // SAFETY: the brand is a compile-time mark; the checks above are the whole rule.\n return key as EmailIdempotencyKey\n}\n","import { AppError } from './app-error.ts'\nimport type { FailureClassification } from './failure-classification.ts'\n\n/** Why the provider rejected a send. The reason owns the classification, so the two never disagree. */\nexport type EmailSendFailureReason =\n | 'rate_limited'\n | 'idempotency_conflict'\n | 'provider_error'\n | 'quota_exceeded'\n | 'invalid_request'\n | 'configuration'\n\nconst CLASSIFICATION_BY_REASON = {\n rate_limited: 'transient',\n idempotency_conflict: 'transient',\n provider_error: 'transient',\n quota_exceeded: 'terminal',\n invalid_request: 'terminal',\n configuration: 'terminal',\n} satisfies Record<EmailSendFailureReason, FailureClassification>\n\n/**\n * The provider rejected a send. The boundary that awaited the send logs once; the adapter never does.\n * `retryAfter` is the server's delay in seconds when it sent one, so `retryAfterSecondsOf` reads it.\n */\nexport class EmailServiceError extends AppError {\n readonly _tag = 'EmailServiceError'\n readonly classification: FailureClassification\n readonly reason: EmailSendFailureReason\n readonly provider: string\n readonly providerCode: string\n readonly statusCode: number | null\n readonly retryAfter: number | null\n\n constructor(args: {\n readonly reason: EmailSendFailureReason\n readonly provider: string\n readonly providerCode: string\n readonly statusCode: number | null\n readonly retryAfter: number | null\n readonly cause: unknown\n }) {\n super(`Email send failed: ${args.provider} ${args.providerCode}`, { cause: args.cause })\n this.reason = args.reason\n this.classification = CLASSIFICATION_BY_REASON[args.reason]\n this.provider = args.provider\n this.providerCode = args.providerCode\n this.statusCode = args.statusCode\n this.retryAfter = args.retryAfter\n }\n}\n","import { z } from 'zod'\n\n/** The bounce classes Resend reports, ported from Typist's comm-eligibility.ts. */\nexport const BounceTypeSchema = z.enum(['Permanent', 'Transient', 'Undetermined'])\n\n/** One bounce class. */\nexport type BounceType = z.infer<typeof BounceTypeSchema>\n\n/** Parse a provider's bounce type. An unknown value is `Undetermined`, which suppresses. */\nexport function parseBounceType(value: string | undefined): BounceType {\n const parsed = BounceTypeSchema.safeParse(value)\n return parsed.success ? parsed.data : 'Undetermined'\n}\n\nconst BOUNCE_SEVERITY = {\n Transient: 1,\n Undetermined: 2,\n Permanent: 3,\n} satisfies Record<BounceType, number>\n\n/** Keep the higher severity. Equal severity permits replacement; callers own timestamp ordering. */\nexport function shouldUpdateBounce(incoming: BounceType, current: BounceType | null): boolean {\n if (current === null) return true\n return BOUNCE_SEVERITY[incoming] >= BOUNCE_SEVERITY[current]\n}\n\n/** Typist's send policy: no complaint, and either no bounce or a transient one. Delivery is not guaranteed. */\nexport function isEmailDeliverable(bounceType: BounceType | null, complained: boolean): boolean {\n if (complained) return false\n return bounceType === null || bounceType === 'Transient'\n}\n","import { z } from 'zod'\n\nimport { EmailAddressSchema } from './email-address.ts'\nimport { BounceTypeSchema } from './email-deliverability.ts'\n\n/**\n * The payload of the command an application registers to record a bounce or a complaint.\n * Register it under a name of the application's choice in `defineMessages`; `recordEmailEvent` handles it.\n */\nexport const RecordEmailEventPayloadSchema = z.object({\n recipients: z.array(EmailAddressSchema).min(1),\n event: z.discriminatedUnion('type', [\n z.object({\n type: z.literal('bounced'),\n bounceType: BounceTypeSchema,\n bounceSubType: z.string().nullable(),\n }),\n z.object({ type: z.literal('complained') }),\n ]),\n})\n\n/** A parsed payload. */\nexport type RecordEmailEventPayload = z.infer<typeof RecordEmailEventPayloadSchema>\n","import { AppError } from './app-error.ts'\n\n/** A protected operation requires a session that the caller did not provide. */\nexport class AuthenticationError extends AppError {\n readonly _tag = 'AuthenticationError'\n readonly classification = 'terminal'\n\n constructor() {\n super('Authentication required')\n }\n}\n","import { AppError } from './app-error.ts'\n\n/** HTTP 429 with a retry delay in seconds. A null delay means the response supplies no valid duration. */\nexport class RateLimitError extends AppError {\n readonly _tag = 'RateLimitError'\n readonly classification = 'transient'\n readonly status = 429\n\n constructor(readonly retryAfter: number | null) {\n super('Too many requests')\n }\n}\n","import { AppError } from './app-error.ts'\n\n/** A presented session cookie no longer resolves; the application owns cleanup and redirect policy. */\nexport class StaleSessionError extends AppError {\n readonly _tag = 'StaleSessionError'\n readonly classification = 'terminal'\n\n constructor() {\n super('Session is no longer valid. Sign in again.')\n }\n}\n","import { AppError } from './app-error.ts'\n\n/** The authenticated caller cannot perform this operation. Ported from Porte's auth.errors.ts. */\nexport class NotAuthorizedError extends AppError {\n readonly _tag = 'NotAuthorizedError'\n readonly classification = 'terminal'\n\n constructor() {\n super('Not allowed')\n }\n}\n","import { AppError } from './app-error.ts'\n\n/** Safe public replacement for an unknown failure, ported from Porte's internal.errors.ts. */\nexport class InternalServerError extends AppError {\n readonly _tag = 'InternalServerError'\n readonly classification = 'unknown'\n\n constructor() {\n super('Something went wrong')\n }\n}\n\n/** Safe public replacement for a classified transient failure. */\nexport class ServiceUnavailableError extends AppError {\n readonly _tag = 'ServiceUnavailableError'\n readonly classification = 'transient'\n\n constructor() {\n super('Try again shortly')\n }\n}\n","import { z } from 'zod'\n\nimport { AppError } from './app-error.ts'\nimport type { ValidationProblemError } from './problem-details.ts'\n\n/** One invalid value as a validator reports it: a message and a path of keys. */\nexport type ValidationIssue = {\n readonly message: string\n readonly path: readonly PropertyKey[]\n}\n\n/** Invalid user input, one entry per invalid value in the RFC 9457 shape. Technical failures use another error. */\nexport class ValidationError extends AppError {\n readonly _tag = 'ValidationError'\n readonly classification = 'terminal'\n\n constructor(\n readonly errors: readonly ValidationProblemError[],\n message = 'The request was not valid',\n ) {\n super(message)\n }\n\n /** Convert a Zod error. Each issue path becomes a JSON Pointer. */\n static fromZod(error: z.ZodError, message?: string): ValidationError {\n return ValidationError.fromIssues(error.issues, message)\n }\n\n /** Convert issues from any validator, such as issues TanStack serialized into an error message. */\n static fromIssues(issues: readonly ValidationIssue[], message?: string): ValidationError {\n return new ValidationError(\n issues.map((issue) => ({ detail: issue.message, pointer: jsonPointer(issue.path) })),\n message,\n )\n }\n}\n\n/** The server cannot parse the request syntax. */\nexport class MalformedRequestError extends AppError {\n readonly _tag = 'MalformedRequestError'\n readonly classification = 'terminal'\n\n constructor(args: { cause: unknown }) {\n super('The request was malformed', args)\n }\n}\n\n/** No resource matches the requested HTTP path. */\nexport class ResourceNotFoundError extends AppError {\n readonly _tag = 'ResourceNotFoundError'\n readonly classification = 'terminal'\n\n constructor() {\n super('Resource not found')\n }\n}\n\n/** A WebSocket route received a request without an upgrade. */\nexport class UpgradeRequiredError extends AppError {\n readonly _tag = 'UpgradeRequiredError'\n readonly classification = 'terminal'\n\n constructor() {\n super('WebSocket upgrade required')\n }\n}\n\n/** The requested operation did not answer before its deadline. */\nexport class RequestTimeoutError extends AppError {\n readonly _tag = 'RequestTimeoutError'\n readonly classification = 'transient'\n\n constructor() {\n super('The request timed out')\n }\n}\n\nconst readablePathSchema = z.array(z.union([z.string(), z.number()]))\n\n// A symbol step cannot be shown, so the pointer keeps only the steps a person can read.\nfunction jsonPointer(path: readonly PropertyKey[]): string {\n const parsed = readablePathSchema.safeParse(path)\n const steps = parsed.success ? parsed.data : []\n if (steps.length === 0) return '#'\n return `#/${steps.map((step) => String(step).replaceAll('~', '~0').replaceAll('/', '~1')).join('/')}`\n}\n","import { z } from 'zod'\n\n/**\n * The standard RFC 9457 body for a failed HTTP response.\n * The HTTP response status remains authoritative; extensions may add problem-specific data.\n * @see https://http.dev/problem-details\n */\nexport const ProblemDetailsSchema = z.looseObject({\n /**\n * Stable URI for the problem type; use `about:blank` when the HTTP status is sufficient.\n * @example \"about:blank\"\n */\n type: z.string(),\n /**\n * Stable human-readable name for the problem type.\n * @example \"Service Unavailable\"\n */\n title: z.string(),\n /**\n * Safe human-readable explanation for this occurrence; clients must not parse it.\n * @example \"The service is unavailable.\"\n */\n detail: z.string().optional(),\n /**\n * URI reference for this occurrence; omit it when no correlation resource exists.\n * @example \"/problems/occurrences/01K5F6Y7\"\n */\n instance: z.string().optional(),\n})\n\n/** An RFC 9457 problem body for a failed HTTP response. */\nexport type ProblemDetails = z.infer<typeof ProblemDetailsSchema>\n\n/** One invalid value in a `422` validation problem. */\nexport const ValidationProblemErrorSchema = z.object({\n /**\n * Safe explanation of why this value is invalid.\n * @example \"Must be a positive integer.\"\n */\n detail: z.string(),\n /**\n * RFC 6901 JSON Pointer fragment for the invalid value.\n * @example \"#/age\"\n */\n pointer: z.string().regex(/^#(?:\\/(?:[^~]|~[01])*)*$/u),\n})\n\n/** One invalid value in a `422` validation problem. */\nexport type ValidationProblemError = z.infer<typeof ValidationProblemErrorSchema>\n\n/** An RFC 9457 validation problem with one or more invalid content values. */\nexport const ValidationProblemDetailsSchema = ProblemDetailsSchema.extend({\n errors: z.array(ValidationProblemErrorSchema).min(1),\n})\n\n/** An RFC 9457 validation problem with one or more invalid content values. */\nexport type ValidationProblemDetails = z.infer<typeof ValidationProblemDetailsSchema>\n","import { panic } from 'better-result'\nimport { z } from 'zod'\n\nimport { AppError } from './app-error.ts'\nimport { AuthenticationError } from './authentication.error.ts'\nimport { InternalServerError, ServiceUnavailableError } from './internal.errors.ts'\nimport { NotAuthorizedError } from './not-authorized.error.ts'\nimport { ValidationProblemErrorSchema } from './problem-details.ts'\nimport { RateLimitError } from './rate-limit.error.ts'\nimport {\n MalformedRequestError,\n RequestTimeoutError,\n ResourceNotFoundError,\n UpgradeRequiredError,\n ValidationError,\n} from './request.errors.ts'\nimport { StaleSessionError } from './stale-session.error.ts'\n\ntype PublicFields = z.ZodObject\n\n/**\n * One public error: its class, its HTTP status, and the extra properties a client may read.\n * `fields` strips the thrown error on the server and parses the payload in the browser.\n */\nexport type ErrorEntry<Tag extends string, Fields extends PublicFields = PublicFields> = {\n readonly error: abstract new (...args: never[]) => AppError & { readonly _tag: Tag }\n readonly status: number\n readonly fields?: Fields\n}\n\ntype AnyErrorEntry = ErrorEntry<string>\n\n/** Entries keyed by tag. The compiler rejects a key that differs from the class's `_tag`. */\nexport type ErrorEntries<Entries> = {\n readonly [Tag in keyof Entries & string]: ErrorEntry<Tag>\n}\n\ntype EntryPayload<Tag extends string, Entry> = Entry extends {\n readonly fields: infer Fields extends z.ZodType\n}\n ? { readonly _tag: Tag; readonly message: string } & z.output<Fields>\n : { readonly _tag: Tag; readonly message: string }\n\ntype PayloadOfEntries<Entries> = {\n [Tag in keyof Entries & string]: EntryPayload<Tag, Entries[Tag]>\n}[keyof Entries & string]\n\n/** The HTTP status and public payload for one resolved failure. */\nexport type ResolvedError<Payload> = {\n readonly status: number\n readonly payload: Payload\n}\n\n/**\n * One application's public errors, shared by every transport boundary and the browser.\n * `resolve` and `fallback` serve boundaries. `parse` and `generic` serve screens.\n */\nexport type ErrorTable<Entries> = {\n readonly entries: Entries\n /** The discriminated union of every entry's `_tag`, `message`, and fields. */\n readonly payloadSchema: z.ZodType<PayloadOfEntries<Entries>>\n /** The `InternalServerError` payload, for screens that must show something. */\n readonly generic: PayloadOfEntries<Entries>\n /** Read a rejection value. Null means no known tag, so the request never reached the Worker. */\n parse(cause: unknown): PayloadOfEntries<Entries> | null\n /** Narrow a rejection value to one tag's payload, for a caller that acts on that one error. */\n is<Tag extends keyof Entries & string>(\n cause: unknown,\n tag: Tag,\n ): cause is Extract<PayloadOfEntries<Entries>, { readonly _tag: Tag }>\n /** Match a local instance or a remote error by tag. Null means the failure is not public. */\n resolve(cause: unknown): ResolvedError<PayloadOfEntries<Entries>> | null\n /** The replacement for an unmapped failure: 503 when it declared itself transient, else 500. */\n fallback(cause: unknown): ResolvedError<PayloadOfEntries<Entries>>\n}\n\n/** The union of payloads a table can produce. */\nexport type ErrorPayloadOf<Table> =\n Table extends ErrorTable<infer Entries> ? PayloadOfEntries<Entries> : never\n\n/** The union of tags a table contains. */\nexport type ErrorTagOf<Table> =\n Table extends ErrorTable<infer Entries> ? keyof Entries & string : never\n\nfunction entriesOf<const Entries extends ErrorEntries<Entries>>(entries: Entries): Entries {\n return entries\n}\n\nconst ValidationFields = z.object({ errors: z.array(ValidationProblemErrorSchema) })\nconst RateLimitFields = z.object({ retryAfter: z.number().nullable() })\n\n/** The errors every application can throw, with their statuses. `defineErrors` includes them. */\nexport const sharedErrors = entriesOf({\n MalformedRequestError: { error: MalformedRequestError, status: 400 },\n AuthenticationError: { error: AuthenticationError, status: 401 },\n StaleSessionError: { error: StaleSessionError, status: 401 },\n NotAuthorizedError: { error: NotAuthorizedError, status: 403 },\n ResourceNotFoundError: { error: ResourceNotFoundError, status: 404 },\n ValidationError: { error: ValidationError, status: 422, fields: ValidationFields },\n UpgradeRequiredError: { error: UpgradeRequiredError, status: 426 },\n RateLimitError: { error: RateLimitError, status: 429, fields: RateLimitFields },\n InternalServerError: { error: InternalServerError, status: 500 },\n ServiceUnavailableError: { error: ServiceUnavailableError, status: 503 },\n RequestTimeoutError: { error: RequestTimeoutError, status: 504 },\n})\n\ntype MergedEntries<Entries> = Omit<typeof sharedErrors, keyof Entries> & Entries\n\n// Workers RPC drops the prototype but keeps own properties and marks the error as remote.\nconst RemoteErrorSchema = z.object({\n _tag: z.string(),\n message: z.string(),\n remote: z.literal(true),\n})\n\ntype Match = { readonly tag: string; readonly entry: AnyErrorEntry; readonly error: Error }\n\nfunction match(byTag: ReadonlyMap<string, AnyErrorEntry>, cause: unknown): Match | null {\n if (cause instanceof AppError) {\n const entry = byTag.get(cause._tag)\n if (entry === undefined || !(cause instanceof entry.error)) return null\n return { tag: cause._tag, entry, error: cause }\n }\n if (!(cause instanceof Error)) return null\n const remote = RemoteErrorSchema.safeParse(cause)\n if (!remote.success) return null\n const entry = byTag.get(remote.data._tag)\n return entry === undefined ? null : { tag: remote.data._tag, entry, error: cause }\n}\n\n/**\n * Build the application's error table. The shared entries are always included;\n * an entry with the same key replaces the shared one.\n */\nexport function defineErrors<const Entries extends ErrorEntries<Entries>>(\n entries: Entries,\n): ErrorTable<MergedEntries<Entries>> {\n type Payload = PayloadOfEntries<MergedEntries<Entries>>\n const merged = { ...sharedErrors, ...entries }\n const byTag: ReadonlyMap<string, AnyErrorEntry> = new Map(Object.entries<AnyErrorEntry>(merged))\n\n const resolve = (cause: unknown): ResolvedError<Payload> | null => {\n const found = match(byTag, cause)\n if (found === null) return null\n const fields = found.entry.fields?.safeParse(found.error)\n if (fields !== undefined && !fields.success) return null\n // SAFETY: The entry's own `fields` schema produced these properties, so the payload has the shape declared for this tag.\n const payload = { _tag: found.tag, message: found.error.message, ...fields?.data } as Payload\n return { status: found.entry.status, payload }\n }\n\n const resolveShared = (error: AppError): ResolvedError<Payload> =>\n resolve(error) ?? panic(`Error table cannot resolve shared error ${error._tag}`)\n const generic = resolveShared(new InternalServerError())\n const unavailable = resolveShared(new ServiceUnavailableError())\n\n const variants = [...byTag].map(([tag, entry]) =>\n // oxlint-disable-next-line anti-slop/no-shape-in-symbol-names -- Zod names an object schema's property map `shape`.\n z.strictObject({ _tag: z.literal(tag), message: z.string(), ...entry.fields?.shape }),\n )\n const [first, ...rest] = variants\n if (first === undefined) return panic('Error table has no entries')\n // Each variant is one entry's `_tag`, `message`, and fields, which is the union `PayloadOfEntries` describes.\n const payloadSchema = z.discriminatedUnion('_tag', [first, ...rest]).pipe(z.custom<Payload>())\n\n // No return annotation: TypeScript resolves the deferred payload union differently in that position.\n const parse = (cause: unknown) => {\n const parsed = payloadSchema.safeParse(cause)\n return parsed.success ? parsed.data : null\n }\n\n return {\n entries: merged,\n payloadSchema,\n generic: generic.payload,\n parse,\n is<Tag extends keyof MergedEntries<Entries> & string>(\n cause: unknown,\n tag: Tag,\n ): cause is Extract<Payload, { readonly _tag: Tag }> {\n const parsed = parse(cause)\n return parsed !== null && parsed._tag === tag\n },\n resolve,\n fallback: (cause) =>\n cause instanceof AppError && cause.classification === 'transient' ? unavailable : generic,\n }\n}\n","import { AppError } from './app-error.ts'\nimport type { FailureClassification } from './failure-classification.ts'\n\n/** SQLite reports these constraint categories through D1. */\nexport type D1ConstraintKind = 'unique' | 'not_null' | 'foreign_key' | 'check'\n\n/** Database evidence for callers that interpret a specific code or constraint. */\nexport type DatabaseFailure =\n | {\n readonly database: 'd1'\n readonly code: string | undefined\n readonly kind: D1ConstraintKind | undefined\n readonly table: string | undefined\n readonly column: string | undefined\n readonly constraintName: string | undefined\n }\n | {\n readonly database: 'postgres'\n readonly code: string | undefined\n readonly table_name: string | undefined\n readonly column_name: string | undefined\n readonly constraint_name: string | undefined\n }\n\n/** Classification describes the failure; callers must separately establish whether repetition is safe. */\nexport class DatabaseError extends AppError {\n readonly _tag = 'DatabaseError'\n\n constructor(\n readonly operation: string,\n readonly classification: FailureClassification,\n readonly details: DatabaseFailure,\n options: { readonly cause: unknown },\n ) {\n super(`Database operation failed: ${operation}`, options)\n }\n}\n","import { panic } from 'better-result'\n\n/**\n * Mark a line the types say cannot run. A union member without a branch stops compilation here.\n *\n * @throws Panic when a value outside the union arrives at runtime.\n */\nexport function unreachable(value: never): never {\n return panic(`Unreachable: ${String(value)}`)\n}\n\n/**\n * Mark a body that is not written yet. A defect, not an expected failure.\n *\n * @param what - The behavior the body will provide, such as `invoice export`.\n * @throws Panic always.\n */\nexport function notImplemented(what: string): never {\n return panic(`Not implemented: ${what}`)\n}\n","import { v7 as uuidv7 } from 'uuid'\nimport { z } from 'zod'\n\n/** A string only `create()` or `schema.parse()` of the same kind can produce. */\nexport type Id<Brand extends string> = string & z.core.$brand<Brand>\n\n/** One id kind the application mints. `schema` parses untrusted input; `create` mints a uuid v7. */\nexport type IdDefinition<Brand extends string> = {\n readonly schema: z.core.$ZodBranded<z.ZodUUID, Brand>\n /** Mint a new time-ordered id. No parse: we produced it, so it is valid by construction. */\n create(): Id<Brand>\n}\n\n/** The id type of a definition: `type CallId = IdOf<typeof CallId>`. */\nexport type IdOf<Definition extends IdDefinition<string>> = z.output<Definition['schema']>\n\n/**\n * Define an id kind the application mints: `export const CallId = defineId('CallId')`.\n * An id another system mints, such as a provider's call id, stays a plain branded schema.\n */\nexport function defineId<const Brand extends string>(brand: Brand): IdDefinition<Brand> {\n const schema = z.uuidv7({ error: `Invalid ${brand}` })\n return {\n // SAFETY: `brand()` is type-only and returns a conditional TypeScript cannot reduce for a generic brand; for every literal it is this branded schema.\n schema: schema.brand<Brand>() as z.core.$ZodBranded<z.ZodUUID, Brand>,\n // SAFETY: uuidv7() always yields a valid v7 uuid, and the brand is a compile-time mark only.\n create: () => uuidv7() as Id<Brand>,\n }\n}\n","import { panic } from 'better-result'\nimport { v7 as uuidv7 } from 'uuid'\nimport { z } from 'zod'\n\nimport { AppError } from './app-error.ts'\nimport type { MessageId } from './messages.ts'\n\n/** Payload schemas keyed by message name. Payloads cannot declare `type`, `name`, or `id`. */\nexport type PayloadSchemas = Readonly<Record<string, z.ZodType<object>>>\n\n/** Optional payload schema maps for one message collection. */\nexport type MessageDefinition = {\n readonly commands?: PayloadSchemas\n readonly events?: PayloadSchemas\n readonly queries?: PayloadSchemas\n}\n\nconst completed = Symbol('MessageCollection')\nconst MessageIdSchema = z.string().min(1)\nconst envelopes = {\n command: z.object({ type: z.literal('command'), id: MessageIdSchema }),\n event: z.object({ type: z.literal('event'), id: MessageIdSchema }),\n query: z.object({ type: z.literal('query') }),\n}\n\ntype MessageKind = keyof typeof envelopes\ntype RoutingField = 'type' | 'name' | 'id'\ntype SchemaMap = Readonly<Record<string, z.ZodType<object>>>\ntype EmptySchemas = Record<never, never>\ntype SchemaMaps = {\n readonly commandSchemas: SchemaMap\n readonly eventSchemas: SchemaMap\n readonly querySchemas: SchemaMap\n}\ntype CompletedMessages = SchemaMaps & { readonly [completed]: true }\ntype SchemaOf<Schemas extends SchemaMap> = Schemas[keyof Schemas & string]\ntype Payload<Message> = Message extends object ? Omit<Message, RoutingField> : never\n\n/** Complete schemas, union schemas, and factories for one message collection. */\nexport type MessageCollection<Maps extends SchemaMaps> = Maps & {\n readonly [completed]: true\n readonly commandSchema: z.ZodUnion<SchemaOf<Maps['commandSchemas']>[]>\n readonly eventSchema: z.ZodUnion<SchemaOf<Maps['eventSchemas']>[]>\n readonly querySchema: z.ZodUnion<SchemaOf<Maps['querySchemas']>[]>\n /** Commands and events can cross a queue. Queries cannot. */\n readonly queueSchema: z.ZodUnion<\n (SchemaOf<Maps['commandSchemas']> | SchemaOf<Maps['eventSchemas']>)[]\n >\n /** Add the command envelope without parsing. The default ID is UUID v7; supplied IDs stay unchanged. */\n createCommand<Name extends keyof Maps['commandSchemas'] & string>(\n this: void,\n name: Name,\n payload: Payload<z.output<Maps['commandSchemas'][NoInfer<Name>]>>,\n id?: MessageId,\n ): z.output<Maps['commandSchemas'][Name]>\n /** Add the event envelope without parsing. The default ID is UUID v7; supplied IDs stay unchanged. */\n createEvent<Name extends keyof Maps['eventSchemas'] & string>(\n this: void,\n name: Name,\n payload: Payload<z.output<Maps['eventSchemas'][NoInfer<Name>]>>,\n id?: MessageId,\n ): z.output<Maps['eventSchemas'][Name]>\n /** Add the query envelope without parsing. Queries have no ID. */\n createQuery<Name extends keyof Maps['querySchemas'] & string>(\n this: void,\n name: Name,\n payload: Payload<z.output<Maps['querySchemas'][NoInfer<Name>]>>,\n ): z.output<Maps['querySchemas'][Name]>\n}\n\n/** The command union inferred from a collection's complete schemas. */\nexport type CommandOf<Messages extends CompletedMessages> = z.output<\n SchemaOf<Messages['commandSchemas']>\n>\n\n/** The event union inferred from a collection's complete schemas. */\nexport type EventOf<Messages extends CompletedMessages> = z.output<\n SchemaOf<Messages['eventSchemas']>\n>\n\n/** The query union inferred from a collection's complete schemas. */\nexport type QueryOf<Messages extends CompletedMessages> = z.output<\n SchemaOf<Messages['querySchemas']>\n>\n\n/** Every message inferred from a collection's complete schemas. */\nexport type MessageOf<Messages extends CompletedMessages> =\n | CommandOf<Messages>\n | EventOf<Messages>\n | QueryOf<Messages>\n\ntype KeysOfUnion<Value> = Value extends Value ? keyof Value : never\ntype WithoutRoutingFields<Schemas extends PayloadSchemas> = {\n readonly [Name in keyof Schemas]: KeysOfUnion<z.output<Schemas[Name]>> &\n RoutingField extends never\n ? Schemas[Name]\n : never\n}\n\nfunction messageSchema<\n Kind extends MessageKind,\n Name extends string,\n Schema extends z.ZodType<object>,\n>(kind: Kind, name: Name, payload: Schema) {\n return z.intersection(envelopes[kind], z.object({ name: z.literal(name) })).and(payload)\n}\n\ntype CompleteSchemas<Kind extends MessageKind, Schemas extends PayloadSchemas> = {\n readonly [Name in keyof Schemas & string]: ReturnType<\n typeof messageSchema<Kind, Name, Schemas[Name]>\n >\n}\n\nfunction completeSchemas<Kind extends MessageKind, Schemas extends PayloadSchemas>(\n kind: Kind,\n schemas: Schemas | undefined,\n): CompleteSchemas<Kind, Schemas> {\n const entries = Object.entries<z.ZodType<object>>(schemas ?? {}).map(([name, payload]) => [\n name,\n messageSchema(kind, name, payload),\n ])\n // SAFETY: Each entry preserves its name and wraps its payload schema with the selected envelope.\n return Object.freeze(Object.fromEntries(entries)) as CompleteSchemas<Kind, Schemas>\n}\n\nfunction unionOf<Schemas extends SchemaMap>(schemas: Schemas): z.ZodUnion<SchemaOf<Schemas>[]> {\n // SAFETY: Object.values contains exactly the complete schemas indexed by this map's keys.\n return z.union(Object.values(schemas)) as z.ZodUnion<SchemaOf<Schemas>[]>\n}\n\nfunction collection<Maps extends SchemaMaps>(maps: Maps): MessageCollection<Maps> {\n return Object.freeze<MessageCollection<Maps>>({\n ...maps,\n [completed]: true,\n commandSchema: unionOf<Maps['commandSchemas']>(maps.commandSchemas),\n eventSchema: unionOf<Maps['eventSchemas']>(maps.eventSchemas),\n querySchema: unionOf<Maps['querySchemas']>(maps.querySchemas),\n // SAFETY: The queue union contains the command and event schemas from these maps, without rebuilding them.\n queueSchema: z.union([\n ...Object.values(maps.commandSchemas),\n ...Object.values(maps.eventSchemas),\n ]) as MessageCollection<Maps>['queueSchema'],\n createCommand: (name, payload, id = uuidv7()) => {\n // SAFETY: The typed payload supplies all non-envelope fields for this name; the factory supplies its envelope.\n return { ...payload, type: 'command', name, id } as z.output<\n Maps['commandSchemas'][typeof name]\n >\n },\n createEvent: (name, payload, id = uuidv7()) => {\n // SAFETY: The typed payload supplies all non-envelope fields for this name; the factory supplies its envelope.\n return { ...payload, type: 'event', name, id } as z.output<Maps['eventSchemas'][typeof name]>\n },\n createQuery: (name, payload) => {\n // SAFETY: The typed payload supplies all non-envelope fields for this name; a query has no ID.\n return { ...payload, type: 'query', name } as z.output<Maps['querySchemas'][typeof name]>\n },\n })\n}\n\n/** Build each complete schema once. Omitted kinds have empty maps and schemas that reject every input. */\nexport function defineMessages<\n const Commands extends PayloadSchemas = EmptySchemas,\n const Events extends PayloadSchemas = EmptySchemas,\n const Queries extends PayloadSchemas = EmptySchemas,\n>(definition: {\n readonly commands?: Commands & WithoutRoutingFields<Commands>\n readonly events?: Events & WithoutRoutingFields<Events>\n readonly queries?: Queries & WithoutRoutingFields<Queries>\n}) {\n return collection({\n commandSchemas: completeSchemas<'command', Commands>('command', definition.commands),\n eventSchemas: completeSchemas<'event', Events>('event', definition.events),\n querySchemas: completeSchemas<'query', Queries>('query', definition.queries),\n })\n}\n\n/** Two collections declare the same name within one message kind. */\nexport class DuplicateMessageNameError extends AppError {\n readonly _tag = 'DuplicateMessageNameError'\n readonly classification = 'terminal'\n\n /** Identify the duplicate kind and name so the application can correct its definitions. */\n constructor(\n readonly kind: MessageKind,\n readonly messageName: string,\n ) {\n super(`Duplicate ${kind} name: ${messageName}`)\n }\n}\n\ntype SchemaAtName<Maps, Name extends PropertyKey> =\n Maps extends Record<Name, infer Schema extends z.ZodType<object>> ? Schema : never\ntype MergedMaps<Collections extends readonly CompletedMessages[]> = {\n readonly [Key in keyof SchemaMaps]: {\n readonly [Name in KeysOfUnion<Collections[number][Key]> & string]: SchemaAtName<\n Collections[number][Key],\n Name\n >\n }\n}\ntype DuplicateNames<Collections extends readonly CompletedMessages[]> =\n Collections extends readonly [\n infer First extends CompletedMessages,\n ...infer Rest extends readonly CompletedMessages[],\n ]\n ?\n | {\n [Key in keyof SchemaMaps]: keyof First[Key] & KeysOfUnion<Rest[number][Key]>\n }[keyof SchemaMaps]\n | DuplicateNames<Rest>\n : never\n\nfunction mergeSchemas<\n Collections extends readonly CompletedMessages[],\n Key extends keyof SchemaMaps,\n>(collections: Collections, key: Key, kind: MessageKind): MergedMaps<Collections>[Key] {\n const merged = new Map<string, z.ZodType<object>>()\n for (const messages of collections) {\n if (!messages?.[completed])\n return panic('combineMessages requires completed message collections')\n for (const [name, schema] of Object.entries(messages[key])) {\n if (merged.has(name)) throw new DuplicateMessageNameError(kind, name)\n merged.set(name, schema)\n }\n }\n // SAFETY: Every schema comes from the selected complete map, and duplicate names are rejected before insertion.\n return Object.freeze(Object.fromEntries(merged)) as MergedMaps<Collections>[Key]\n}\n\n/**\n * Merge completed collections without rebuilding complete schemas.\n * @throws DuplicateMessageNameError when two collections declare the same name within one kind.\n */\nexport function combineMessages<const Collections extends readonly CompletedMessages[]>(\n ...collections: Collections & (DuplicateNames<Collections> extends never ? unknown : never)\n) {\n return collection({\n commandSchemas: mergeSchemas(collections, 'commandSchemas', 'command'),\n eventSchemas: mergeSchemas(collections, 'eventSchemas', 'event'),\n querySchemas: mergeSchemas(collections, 'querySchemas', 'query'),\n })\n}\n","import type { DomainEvent } from './messages.ts'\n\n/** What an aggregate's plain data must have: an identity. A branded id keeps its brand. */\nexport type EntityData = { readonly id: string }\n\n/**\n * The base of an aggregate: its identity, the events it raised, and the plain data a repository\n * stores. The subclass keeps its constructor private and declares `static restore(data: Data)`,\n * the one door a repository rebuilds it through. A repository drains the events after `save`.\n */\nexport abstract class Entity<Data extends EntityData, TEvent extends DomainEvent = DomainEvent> {\n private events: TEvent[] = []\n\n abstract readonly id: Data['id']\n\n /** Record one event with the state change that caused it. The unit of work commits both. */\n protected addEvent(event: TEvent): void {\n this.events.push(event)\n }\n\n /** A copy of the events raised since the last `clearEvents`. */\n collectEvents(): ReadonlyArray<TEvent> {\n return [...this.events]\n }\n\n /** Forget the raised events once the boundary has taken them. */\n clearEvents(): void {\n this.events = []\n }\n\n /** Plain data for the repository. The subclass's `restore(data)` accepts exactly this. */\n abstract toPlainObject(): Data\n}\n","/**\n * What one failure is, before any policy decides what to do about it.\n *\n * `transient`: the world may differ on the next attempt. `terminal`: repeating gives the\n * same answer. `unknown`: nobody judged this error, so it came from outside unwrapped.\n */\nexport type FailureClassification = 'transient' | 'terminal' | 'unknown'\n\n/**\n * Who is asking to repeat the work.\n *\n * An immediate owner holds a caller and a socket open. A durable owner already stored the\n * work, so it can afford to try again on a failure nobody classified.\n */\nexport type RetryOwner = 'immediate' | 'durable'\n\n/** Decide whether one retry owner may repeat a failed operation. */\nexport function shouldRetryFailure(args: {\n readonly classification: FailureClassification\n readonly repeatSafe: boolean\n readonly owner: RetryOwner\n}): boolean {\n if (!args.repeatSafe) return false\n if (args.classification === 'transient') return true\n if (args.classification === 'terminal') return false\n return args.owner === 'durable'\n}\n","/**\n * Messages that the platform-neutral transports raise for a failure that may pass.\n *\n * A boundary that knows its own runtime adds its own patterns before these.\n */\nconst TRANSIENT_TRANSPORT_PATTERNS = [\n /network/,\n /fetch failed/,\n /timeout/,\n /timed?\\s*out/,\n /connection.*(lost|reset|refused|closed|aborted)/,\n /econnreset/,\n /econnrefused/,\n /etimedout/,\n /eai_again/,\n]\n\n/** Cancellation is a decision, not a failure to repeat. */\nexport function isAbortError(cause: unknown): boolean {\n return cause instanceof Error && cause.name === 'AbortError'\n}\n\n/** Match known transient transport messages while excluding explicit cancellation. */\nexport function isTransientTransportError(cause: unknown): boolean {\n if (!(cause instanceof Error) || isAbortError(cause)) return false\n const message = cause.message.toLowerCase()\n return TRANSIENT_TRANSPORT_PATTERNS.some((pattern) => pattern.test(message))\n}\n","import { z } from 'zod'\n\n/** Stable Cloudflare messages that mean the runtime failed, not our code, and a repeat may pass. */\nexport const CLOUDFLARE_TRANSIENT_ERROR_PATTERNS = {\n networkConnectionLost: /network connection lost/,\n objectReset: /caused object to be reset/,\n codeUpdateReset: /reset because its code was updated/,\n objectMoved: /object has moved to a different machine/,\n internalWorkflows: /internal workflows error/,\n} as const\n\nconst TRANSIENT_PLATFORM_PATTERNS = [\n CLOUDFLARE_TRANSIENT_ERROR_PATTERNS.objectReset,\n CLOUDFLARE_TRANSIENT_ERROR_PATTERNS.codeUpdateReset,\n CLOUDFLARE_TRANSIENT_ERROR_PATTERNS.objectMoved,\n CLOUDFLARE_TRANSIENT_ERROR_PATTERNS.internalWorkflows,\n]\n\n// workerd stamps these flags as own properties on the error it throws across a binding.\nconst RetryableFlagSchema = z.object({ retryable: z.literal(true) })\nconst OverloadedFlagSchema = z.object({ overloaded: z.literal(true) })\nconst ObjectResetFlagSchema = z.object({ durableObjectReset: z.literal(true) })\n\n/** workerd stamps `retryable` on a failure it judged safe to repeat. */\nexport function isCloudflareRetryableError(cause: unknown): boolean {\n return RetryableFlagSchema.validate(cause)\n}\n\n/** workerd stamps `overloaded` when the target could not take the call. */\nexport function isCloudflareOverloadedError(cause: unknown): boolean {\n return OverloadedFlagSchema.validate(cause)\n}\n\n/** workerd stamps `durableObjectReset` when the object restarted during the call. */\nexport function isCloudflareObjectResetError(cause: unknown): boolean {\n return ObjectResetFlagSchema.validate(cause)\n}\n\n/** Match transient Cloudflare runtime evidence without the general transport patterns. */\nexport function isTransientCloudflareRuntimeError(cause: unknown): boolean {\n if (isCloudflareObjectResetError(cause)) return true\n if (!(cause instanceof Error)) return false\n const message = cause.message.toLowerCase()\n return TRANSIENT_PLATFORM_PATTERNS.some((pattern) => pattern.test(message))\n}\n","import { z } from 'zod'\n\nimport { AppError } from './app-error.ts'\nimport {\n isCloudflareOverloadedError,\n isCloudflareRetryableError,\n isTransientCloudflareRuntimeError,\n} from './cloudflare-classification.ts'\nimport type { FailureClassification } from './failure-classification.ts'\nimport { isAbortError, isTransientTransportError } from './transport-classification.ts'\n\n/** A boundary's own rule for its raw errors. `undefined` means the boundary has no opinion. */\nexport type BoundaryErrorClassifier = (cause: unknown) => FailureClassification | undefined\n\n/**\n * Classify one raw external error: the boundary's own rule first, then Cloudflare's signals,\n * then the transport patterns. Cancellation is terminal even when Cloudflare marks it retryable.\n */\nexport function classifyBoundaryError(args: {\n readonly cause: unknown\n readonly classifyBoundary: BoundaryErrorClassifier\n readonly includeCloudflare: boolean\n}): FailureClassification {\n const boundary = args.classifyBoundary(args.cause)\n if (boundary !== undefined) return boundary\n\n if (isAbortError(args.cause)) return 'terminal'\n\n if (\n args.includeCloudflare &&\n (isCloudflareOverloadedError(args.cause) ||\n isCloudflareRetryableError(args.cause) ||\n isTransientCloudflareRuntimeError(args.cause))\n ) {\n return 'transient'\n }\n\n if (isTransientTransportError(args.cause)) return 'transient'\n\n return 'unknown'\n}\n\n/**\n * Judge a final failure for its durable retry owner. An `AppError` carries its own answer.\n * A raw throw nobody judged is unknown, so an immediate owner stops and a durable one may repeat.\n */\nexport function classifyFailure(cause: unknown): FailureClassification {\n if (cause instanceof AppError) return cause.classification\n if (isAbortError(cause)) return 'terminal'\n if (isTransientCloudflareRuntimeError(cause)) return 'transient'\n return 'unknown'\n}\n\nconst RetryAfterSchema = z.object({ retryAfter: z.number().positive() })\n\n/** Read a positive server-advertised retry delay in seconds, such as `RateLimitError.retryAfter`. */\nexport function retryAfterSecondsOf(cause: unknown): number | null {\n const result = RetryAfterSchema.safeParse(cause)\n return result.success ? result.data.retryAfter : null\n}\n","import { panic } from 'better-result'\n\n/** Uniform jitter within one fixed window. */\nexport function spreadJitterMs(spreadMs: number): number {\n if (!Number.isFinite(spreadMs) || spreadMs < 0) {\n return panic('spreadMs must be a non-negative number')\n }\n return Math.floor(Math.random() * spreadMs)\n}\n\n/**\n * Half the capped exponential delay, plus jitter over the other half. Decorrelates concurrent\n * repeats like full jitter, but keeps a floor: use it when few attempts remain before work is dropped.\n */\nexport function exponentialEqualJitterMs(\n baseDelayMs: number,\n maxDelayMs: number,\n attempt: number,\n): number {\n const halfCapMs = exponentialDelayCapMs(baseDelayMs, maxDelayMs, attempt) / 2\n return Math.floor(halfCapMs) + spreadJitterMs(halfCapMs)\n}\n\n/** Capped exponential delay for a durable retry recorded in storage. */\nexport function exponentialDelayCapMs(\n baseDelayMs: number,\n maxDelayMs: number,\n attempt: number,\n): number {\n if (!Number.isSafeInteger(attempt) || attempt < 1) {\n return panic('attempt must be a positive integer')\n }\n return Math.min(baseDelayMs * 2 ** (attempt - 1), maxDelayMs)\n}\n","/**\n * Module-scoped logging for every host.\n *\n * A logger emits one plain-data entry per log. The default sink hands it to `console` as an\n * entry object for a log service or as a colored line for a person, selected at startup.\n */\n\n/** A value a log entry may carry. Errors enter only through `Logger.error`. */\nexport type LogValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | ReadonlyArray<LogValue>\n | LogFields\n\n/** Named log values. */\nexport type LogFields = { readonly [key: string]: LogValue }\n\n/**\n * The supported log severity levels, in ascending order.\n *\n * A const object, not an enum, so a parsed environment value typed `'INFO'` is a `LogLevel`.\n */\nexport const LogLevel = {\n DEBUG: 'DEBUG',\n INFO: 'INFO',\n WARN: 'WARN',\n ERROR: 'ERROR',\n} as const\nexport type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]\n\n/** An error as plain data, including diagnostics, causes, and aggregate members. */\nexport type SerializedError = {\n readonly name: string\n readonly message: string\n readonly stack?: string\n readonly fields?: LogFields\n readonly cause?: SerializedError\n readonly errors?: ReadonlyArray<SerializedError>\n}\n\n/** One log, as the sink receives it. Plain data, safe to stringify. */\nexport type LogEntry = {\n readonly timestamp: string\n readonly level: LogLevel\n readonly module: string\n readonly message: string\n readonly details: LogFields\n readonly userId?: string\n /** Present on `Logger.error` entries that carried an error. */\n readonly error?: SerializedError\n}\n\n/** The context an error log carries. */\nexport type ErrorLogContext = {\n readonly error?: unknown\n readonly userId?: string\n readonly details?: LogFields\n}\n\n/** What a logger lets through: a level and everything above it, or nothing. */\nexport type LogThreshold = LogLevel | 'OFF'\n\n/** Overrides the application settings for one logger. */\nexport interface LoggerConfig {\n readonly level?: LogThreshold\n}\n\n/** `json` writes the entry object. `pretty` writes one readable line for a person. */\nexport type LogFormat = 'json' | 'pretty'\n\n/**\n * The application settings, set once at the composition root.\n *\n * An omitted field keeps its default. An explicit `undefined` is a compile error, so the root\n * parses an environment value before passing it. The logger reads no environment itself.\n */\nexport interface LoggerSettings extends LoggerConfig {\n readonly format?: LogFormat\n}\n\n// The production server setting, so a root that never configures still logs safely.\nconst DEFAULT_SETTINGS: Required<LoggerSettings> = {\n level: LogLevel.INFO,\n format: 'json',\n}\n\n// The application configures once at startup; loggers can exist before startup completes.\nlet settings = DEFAULT_SETTINGS\n\n/**\n * Set application settings, including for existing loggers. Per-logger overrides take priority.\n * Call at startup, never per request. An omitted field resets to INFO and JSON output.\n */\nexport function configureLogger(config: LoggerSettings): void {\n settings = {\n level: config.level ?? DEFAULT_SETTINGS.level,\n format: config.format ?? DEFAULT_SETTINGS.format,\n }\n}\n\n/** The error data sent to the configured error hook. */\nexport interface ErrorCaptureEntry {\n readonly error: unknown\n readonly distinctId: string | undefined\n readonly context: LogFields\n}\n\n/** A function that forwards one logged error to an error service. */\nexport type LoggerErrorHook = (entry: ErrorCaptureEntry) => void\n\n/** Where every log entry goes. */\nexport type LogSink = (entry: LogEntry) => void\n\nlet errorHook: LoggerErrorHook | null = null\n\n/**\n * Register the function that receives each logged error.\n *\n * Set once at the process entry point. A later call replaces the earlier hook.\n */\nexport function setLoggerErrorHook(fn: LoggerErrorHook): void {\n errorHook = fn\n}\n\n// One console method per level, so a host that filters by method can tell them apart.\nconst CONSOLE_METHOD = {\n [LogLevel.DEBUG]: 'debug',\n [LogLevel.INFO]: 'info',\n [LogLevel.WARN]: 'warn',\n [LogLevel.ERROR]: 'error',\n} as const satisfies Record<LogLevel, 'debug' | 'info' | 'warn' | 'error'>\n\n// Built from the code point so no raw control character sits in the source.\nconst ESC = String.fromCharCode(27)\nconst ANSI_RESET = `${ESC}[0m`\nconst ANSI_GRAY = `${ESC}[90m`\nconst ANSI_BLUE = `${ESC}[34m`\n\n// Severity reads at a glance: quiet levels stay dim, and an error is the only bold line.\nconst LEVEL_ANSI = {\n DEBUG: `${ESC}[2;36m`,\n INFO: ANSI_BLUE,\n WARN: `${ESC}[33m`,\n ERROR: `${ESC}[1;31m`,\n} satisfies Record<LogLevel, string>\n\nfunction prettyLine(entry: LogEntry): string {\n // Node prints debug and info exactly like log, so the level must be in the text too.\n const level = `[${entry.level}]`\n const module = `[${entry.module}]`\n // A browser console colors by method on its own; a terminal needs ANSI to do the same.\n if ('window' in globalThis) return `${entry.timestamp} ${level} ${module} ${entry.message}`\n const timestamp = `${ANSI_GRAY}${entry.timestamp}${ANSI_RESET}`\n const coloredLevel = `${LEVEL_ANSI[entry.level]}${level}${ANSI_RESET}`\n const coloredModule = `${ANSI_BLUE}${module}${ANSI_RESET}`\n return `${timestamp} ${coloredLevel} ${coloredModule} ${entry.message}`\n}\n\n/**\n * JSON format writes the entry object. Pretty format writes one line, then the details,\n * the error, and the stack as separate console arguments.\n */\nexport const consoleSink: LogSink = (entry) => {\n // Resolved per call, so a console replaced after import still receives the output.\n const write = console[CONSOLE_METHOD[entry.level]]\n if (settings.format === 'json') {\n write(entry)\n return\n }\n const line = prettyLine(entry)\n const fields =\n entry.userId === undefined ? entry.details : { ...entry.details, userId: entry.userId }\n const details = Object.keys(fields).length === 0 ? [] : [fields]\n if (entry.error === undefined) {\n write(line, ...details)\n return\n }\n // A stack inside an object prints as one quoted string; as its own argument it prints as lines.\n const { stack, ...error } = entry.error\n if (stack === undefined) write(line, ...details, error)\n else write(line, ...details, error, `\\n${stack}`)\n}\n\nlet logSink: LogSink = consoleSink\n\n/**\n * Send every log entry somewhere other than `console`.\n *\n * Set once at the process entry point, never per module. A logger is created\n * by name and nothing else, so its destination is a fact about the process.\n */\nexport function setLogSink(fn: LogSink): void {\n logSink = fn\n}\n\nconst LOG_LEVELS = Object.values(LogLevel)\nconst MAX_ERROR_DEPTH = 3\n\n/**\n * Diagnostics worth keeping off an error, named one by one.\n *\n * An allowlist rather than every own property: an error raised by a library we\n * do not control may hang a request or a user payload off itself, and a log is\n * the wrong place to discover that. `_tag` and `classification` are what every `AppError`\n * declares. `remote` and the three flags after it are the ones workerd sets itself.\n */\nconst KEPT_ERROR_FIELDS = [\n '_tag',\n 'classification',\n 'code',\n 'operation',\n 'status',\n 'statusCode',\n 'remote',\n 'retryable',\n 'overloaded',\n 'durableObjectReset',\n] as const\n\ntype ErrorDiagnostics = Error & { readonly [K in (typeof KEPT_ERROR_FIELDS)[number]]?: LogValue }\n\ntype MutableSerializedError = { -readonly [K in keyof SerializedError]: SerializedError[K] }\n\nconst serializeError = (error: Error, depth = 0): SerializedError => {\n if (depth > MAX_ERROR_DEPTH) return { name: 'Error', message: '[error nesting truncated]' }\n\n // Every field is optional, so an `Error` is already one of these.\n const carrier: ErrorDiagnostics = error\n const kept = KEPT_ERROR_FIELDS.filter((key) => carrier[key] !== undefined)\n const serialized: MutableSerializedError = { name: error.name, message: error.message }\n if (error.stack !== undefined) serialized.stack = error.stack\n if (kept.length > 0)\n serialized.fields = Object.fromEntries(kept.map((key) => [key, carrier[key]]))\n if (error.cause instanceof Error) serialized.cause = serializeError(error.cause, depth + 1)\n else if (error.cause !== undefined)\n serialized.cause = { name: 'Error', message: '[non-error cause]' }\n if (error instanceof AggregateError) {\n // oxlint-disable-next-line anti-slop/no-unknown-parameters -- AggregateError accepts arbitrary values; instanceof Error validates each member.\n serialized.errors = error.errors.map((failure: unknown) =>\n failure instanceof Error\n ? serializeError(failure, depth + 1)\n : { name: 'NonError', message: '[non-error aggregate member]' },\n )\n }\n return serialized\n}\n\n/** Write module-scoped logs with application defaults and optional overrides. */\nexport class Logger {\n private readonly config: LoggerConfig\n\n /** Create a logger for one module. */\n constructor(\n private readonly module: string,\n overrideConfig?: LoggerConfig,\n ) {\n this.config = { ...overrideConfig }\n }\n\n private log(\n level: LogLevel,\n message: string,\n details: LogFields,\n error?: SerializedError,\n userId?: string,\n ): void {\n const threshold = this.config.level ?? settings.level\n if (threshold === 'OFF') return\n if (LOG_LEVELS.indexOf(level) < LOG_LEVELS.indexOf(threshold)) return\n\n let entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n module: this.module,\n message,\n details,\n }\n if (userId !== undefined) entry = { ...entry, userId }\n logSink(error === undefined ? entry : { ...entry, error })\n }\n\n /** Write a debug log. */\n debug(message: string, details: LogFields = {}): void {\n this.log(LogLevel.DEBUG, message, details)\n }\n\n /** Write an information log. */\n info(message: string, details: LogFields = {}): void {\n this.log(LogLevel.INFO, message, details)\n }\n\n /** Write a warning log. */\n warn(message: string, details: LogFields = {}): void {\n this.log(LogLevel.WARN, message, details)\n }\n\n /**\n * Write an error log and send the error to the configured hook.\n *\n * The details say which call this was, and the error says what went wrong\n * inside it. Both the entry and the error hook receive the supplied user ID.\n */\n error(message: string, context: ErrorLogContext = {}): void {\n const details = context.details ?? {}\n const thrown = context.error\n let error: SerializedError | undefined\n if (thrown instanceof Error) {\n error = serializeError(thrown)\n } else if (thrown !== undefined) {\n // Not an `Error`, so there is no name or stack to keep, only its JSON form.\n let json: string\n try {\n json = JSON.stringify(thrown)\n } catch {\n json = '[non-serializable error]'\n }\n error = { name: 'NonError', message: json }\n }\n this.log(LogLevel.ERROR, message, details, error, context.userId)\n\n if (errorHook !== null && thrown !== undefined) {\n errorHook({ error: thrown, distinctId: context.userId, context: details })\n }\n }\n}\n\n/** Create a logger for one module. */\nexport function createLogger(module: string, config?: LoggerConfig): Logger {\n return new Logger(module, config)\n}\n"],"mappings":"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"}
|
package/dist/core.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
export { type AnalyticsEventId, AnalyticsEventIdSchema, type AnalyticsEventIdentity, type AnalyticsEventInput, type AnalyticsProperties, type AnalyticsProperty, AppError,
|
|
1
|
+
import { $ as IdOf, $t as AppError, A as isCloudflareRetryableError, At as RateLimitError, B as MessageDefinition, Bt as EmailServiceError, C as BoundaryErrorClassifier, Ct as ValidationProblemDetailsSchema, D as CLOUDFLARE_TRANSIENT_ERROR_PATTERNS, Dt as ServiceUnavailableError, E as retryAfterSecondsOf, Et as InternalServerError, F as EntityData, Ft as BounceTypeSchema, G as defineMessages, Gt as EmailAddressSchema, H as PayloadSchemas, Ht as InvalidEmailIdempotencyKeyError, I as CommandOf, It as isEmailDeliverable, J as DomainMessage, Jt as AnalyticsEventIdentity, K as DomainCommand, Kt as AnalyticsEventId, L as DuplicateMessageNameError, Lt as parseBounceType, M as isAbortError, Mt as RecordEmailEventPayload, N as isTransientTransportError, Nt as RecordEmailEventPayloadSchema, O as isCloudflareObjectResetError, Ot as NotAuthorizedError, P as Entity, Pt as BounceType, Q as IdDefinition, Qt as MissingConfigurationError, R as EventOf, Rt as shouldUpdateBounce, S as spreadJitterMs, St as ValidationProblemDetails, T as classifyFailure, Tt as ValidationProblemErrorSchema, U as QueryOf, Ut as emailIdempotencyKey, V as MessageOf, Vt as EmailIdempotencyKey, W as combineMessages, Wt as EmailAddress, X as MessageId, Xt as AnalyticsProperties, Y as DomainQuery, Yt as AnalyticsEventInput, Z as Id, Zt as AnalyticsProperty, _ as createLogger, _t as UpgradeRequiredError, a as LogFormat, at as DatabaseFailure, b as exponentialDelayCapMs, bt as ProblemDetails, c as LogThreshold, ct as ErrorPayloadOf, d as LoggerConfig, dt as ResolvedError, en as FailureClassification, et as defineId, f as LoggerErrorHook, ft as defineErrors, g as consoleSink, gt as ResourceNotFoundError, h as configureLogger, ht as RequestTimeoutError, i as LogFields, it as DatabaseError, j as isTransientCloudflareRuntimeError, jt as AuthenticationError, k as isCloudflareOverloadedError, kt as StaleSessionError, l as LogValue, lt as ErrorTable, m as SerializedError, mt as MalformedRequestError, n as ErrorLogContext, nn as shouldRetryFailure, nt as unreachable, o as LogLevel, ot as ErrorEntries, p as LoggerSettings, pt as sharedErrors, q as DomainEvent, qt as AnalyticsEventIdSchema, r as LogEntry, rt as D1ConstraintKind, s as LogSink, st as ErrorEntry, t as ErrorCaptureEntry, tn as RetryOwner, tt as notImplemented, u as Logger, ut as ErrorTagOf, v as setLogSink, vt as ValidationError, w as classifyBoundaryError, wt as ValidationProblemError, x as exponentialEqualJitterMs, xt as ProblemDetailsSchema, y as setLoggerErrorHook, yt as ValidationIssue, z as MessageCollection, zt as EmailSendFailureReason } from "./index-DvJUcSW1.js";
|
|
2
|
+
export { type AnalyticsEventId, AnalyticsEventIdSchema, type AnalyticsEventIdentity, type AnalyticsEventInput, type AnalyticsProperties, type AnalyticsProperty, AppError, AuthenticationError, type BounceType, BounceTypeSchema, type BoundaryErrorClassifier, CLOUDFLARE_TRANSIENT_ERROR_PATTERNS, type CommandOf, type D1ConstraintKind, DatabaseError, type DatabaseFailure, type DomainCommand, type DomainEvent, type DomainMessage, type DomainQuery, DuplicateMessageNameError, type EmailAddress, EmailAddressSchema, type EmailIdempotencyKey, type EmailSendFailureReason, EmailServiceError, Entity, type EntityData, type ErrorCaptureEntry, type ErrorEntries, type ErrorEntry, type ErrorLogContext, type ErrorPayloadOf, type ErrorTable, type ErrorTagOf, type EventOf, type FailureClassification, type Id, type IdDefinition, type IdOf, InternalServerError, InvalidEmailIdempotencyKeyError, type LogEntry, type LogFields, type LogFormat, LogLevel, type LogSink, type LogThreshold, type LogValue, Logger, type LoggerConfig, type LoggerErrorHook, type LoggerSettings, MalformedRequestError, type MessageCollection, type MessageDefinition, type MessageId, type MessageOf, MissingConfigurationError, NotAuthorizedError, type PayloadSchemas, type ProblemDetails, ProblemDetailsSchema, type QueryOf, RateLimitError, type RecordEmailEventPayload, RecordEmailEventPayloadSchema, RequestTimeoutError, type ResolvedError, ResourceNotFoundError, type RetryOwner, type SerializedError, ServiceUnavailableError, StaleSessionError, UpgradeRequiredError, ValidationError, type ValidationIssue, type ValidationProblemDetails, ValidationProblemDetailsSchema, type ValidationProblemError, ValidationProblemErrorSchema, classifyBoundaryError, classifyFailure, combineMessages, configureLogger, consoleSink, createLogger, defineErrors, defineId, defineMessages, emailIdempotencyKey, exponentialDelayCapMs, exponentialEqualJitterMs, isAbortError, isCloudflareObjectResetError, isCloudflareOverloadedError, isCloudflareRetryableError, isEmailDeliverable, isTransientCloudflareRuntimeError, isTransientTransportError, notImplemented, parseBounceType, retryAfterSecondsOf, setLogSink, setLoggerErrorHook, sharedErrors, shouldRetryFailure, shouldUpdateBounce, spreadJitterMs, unreachable };
|