better-ship 0.41.0 → 0.43.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-CnsIQX8h.js → application-Ckae0K1T.js} +22 -2
- package/dist/application-Ckae0K1T.js.map +1 -0
- package/dist/application.d.ts +1 -1
- package/dist/application.js +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.js +1 -1
- package/dist/better-auth.js +1 -1
- package/dist/billing/client.js +1 -1
- package/dist/browser-support/floor.js +1 -1
- package/dist/browser-support/oxlint.js +1 -1
- package/dist/cloudflare/auth-secondary-storage.js +1 -1
- package/dist/cloudflare/durable-object-client.js +2 -2
- package/dist/cloudflare/durable-object-errors.js +1 -1
- package/dist/cloudflare/kv-client.js +2 -2
- package/dist/cloudflare/kv-errors.js +1 -1
- package/dist/cloudflare/queue.d.ts +1 -1
- package/dist/cloudflare/queue.js +2 -2
- package/dist/cloudflare/r2-client.js +2 -2
- package/dist/cloudflare/r2-errors.js +1 -1
- package/dist/cloudflare/rate-limiter.js +1 -1
- package/dist/cloudflare/scheduled.js +1 -1
- package/dist/cloudflare/turnstile.js +1 -1
- package/dist/cloudflare.d.ts +2 -2
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +6 -5
- package/dist/cloudflare.js.map +1 -1
- package/dist/{core-CV8SQv01.js → core-CGj1ukXV.js} +2 -2
- package/dist/{core-CV8SQv01.js.map → core-CGj1ukXV.js.map} +1 -1
- package/dist/core.js +1 -1
- package/dist/{durable-object-errors-C7e7essH.js → durable-object-errors-BLGJplW-.js} +2 -2
- package/dist/{durable-object-errors-C7e7essH.js.map → durable-object-errors-BLGJplW-.js.map} +1 -1
- package/dist/{floor-BdRTO_LO.js → floor-DUb4oGQd.js} +2 -2
- package/dist/{floor-BdRTO_LO.js.map → floor-DUb4oGQd.js.map} +1 -1
- package/dist/http.js +2 -2
- package/dist/index-tJzJUgTh.d.ts.map +1 -1
- package/dist/{index-DaiY8xJJ.d.ts → index-wNaxYvB2.d.ts} +3 -1
- package/dist/{index-DaiY8xJJ.d.ts.map → index-wNaxYvB2.d.ts.map} +1 -1
- package/dist/{kv-errors-DXbnO7cB.js → kv-errors-dwVmCqER.js} +2 -2
- package/dist/{kv-errors-DXbnO7cB.js.map → kv-errors-dwVmCqER.js.map} +1 -1
- package/dist/{metrics-DzSCDk-u.d.ts → metrics-iWktt1QT.d.ts} +2 -2
- package/dist/{metrics-DzSCDk-u.d.ts.map → metrics-iWktt1QT.d.ts.map} +1 -1
- package/dist/{outbox-repository-options-DTsXMiR1.js → outbox-repository-options-C3JVYiF4.js} +2 -2
- package/dist/{outbox-repository-options-DTsXMiR1.js.map → outbox-repository-options-C3JVYiF4.js.map} +1 -1
- package/dist/{outbox-repository-options-BPsuMSwA.d.ts → outbox-repository-options-H-nJOB75.d.ts} +2 -2
- package/dist/{outbox-repository-options-BPsuMSwA.d.ts.map → outbox-repository-options-H-nJOB75.d.ts.map} +1 -1
- package/dist/{payment-service.error-oW2bKAfU.js → payment-service.error-o6vkwolM.js} +2 -2
- package/dist/{payment-service.error-oW2bKAfU.js.map → payment-service.error-o6vkwolM.js.map} +1 -1
- package/dist/postgres.d.ts +2 -2
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +6 -5
- 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/{r2-errors-DzpOvciV.js → r2-errors-DeIh9VWr.js} +2 -2
- package/dist/{r2-errors-DzpOvciV.js.map → r2-errors-DeIh9VWr.js.map} +1 -1
- package/dist/{request-C5k8ME_7.js → request-ChEhRhin.js} +2 -2
- package/dist/{request-C5k8ME_7.js.map → request-ChEhRhin.js.map} +1 -1
- package/dist/resend.d.ts +1 -1
- package/dist/resend.js +2 -2
- package/dist/sentry/client.js +1 -1
- package/dist/sentry/server.d.ts +1 -1
- package/dist/stripe/payment-service.error.js +1 -1
- package/dist/stripe/payment-service.js +1 -1
- package/dist/stripe/payment-webhook.js +1 -1
- package/dist/stripe/request.js +1 -1
- package/dist/tanstack.js +1 -1
- package/dist/testing/outbox-repository.d.ts +1 -1
- package/dist/testing/payment-service.js +1 -1
- package/dist/{webhook-verification-B8v3-LQ2.js → webhook-verification-BrjingvG.js} +2 -2
- package/dist/{webhook-verification-B8v3-LQ2.js.map → webhook-verification-BrjingvG.js.map} +1 -1
- package/package.json +1 -1
- package/src/application/outbox-relay.ts +17 -0
- package/src/application/outbox-repository.ts +2 -0
- package/src/core/classify-failure.ts +2 -1
- package/src/infrastructure/cloudflare/d1-outbox-repository.ts +4 -1
- package/src/infrastructure/postgres/postgres-outbox-repository.ts +4 -1
- package/dist/application-CnsIQX8h.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as DatabaseError, c as decideRedelivery, k as unreachable, r as createLogger, rt as AppError } from "./core-
|
|
1
|
+
import { A as DatabaseError, c as decideRedelivery, k as unreachable, r as createLogger, rt as AppError } from "./core-CGj1ukXV.js";
|
|
2
2
|
import { panic } from "better-result";
|
|
3
3
|
|
|
4
4
|
//#region src/application/analytics-service.ts
|
|
@@ -173,8 +173,15 @@ var OutboxRelay = class {
|
|
|
173
173
|
}
|
|
174
174
|
/** Sends the rows' events, marks the delivered rows published, and fails or retries the rest by their error. */
|
|
175
175
|
async publishDueRows(claimToken, dueRows) {
|
|
176
|
+
const publishedAt = this.now().getTime();
|
|
176
177
|
const rejectedEvents = await this.publishEvents(dueRows);
|
|
177
178
|
const rejectedEventIds = new Set(rejectedEvents.map(({ message: event }) => event.id));
|
|
179
|
+
for (const row of dueRows) logger.info("outbox_event_timing", {
|
|
180
|
+
name: row.payload.name,
|
|
181
|
+
attempt: row.attempts,
|
|
182
|
+
waitMs: publishedAt - row.createdAt.getTime(),
|
|
183
|
+
outcome: rejectedEventIds.has(row.payload.id) ? "rejected" : "delivered"
|
|
184
|
+
});
|
|
178
185
|
const deliveredRows = dueRows.filter((row) => !rejectedEventIds.has(row.payload.id));
|
|
179
186
|
if (deliveredRows.length > 0) await this.outboxRepo.markPublished(claimToken, deliveredRows);
|
|
180
187
|
for (const [error, rows] of rowsByError(rejectedEvents, dueRows)) await this.failOrRetry(claimToken, rows, error);
|
|
@@ -225,6 +232,19 @@ var OutboxRelay = class {
|
|
|
225
232
|
const decision = decideRedelivery(cause, attempts, this.delays);
|
|
226
233
|
const delayMs = decision.action === "retry" ? decision.delayMs : 0;
|
|
227
234
|
const rowsWithSameAttempts = rows.filter((row) => row.attempts === attempts);
|
|
235
|
+
const context = {
|
|
236
|
+
attempt: attempts,
|
|
237
|
+
count: rowsWithSameAttempts.length,
|
|
238
|
+
delayMs
|
|
239
|
+
};
|
|
240
|
+
if (decision.action === "retry" && decision.classification === "unknown" && attempts === 1) logger.error("outbox_unknown_failure", {
|
|
241
|
+
error: cause,
|
|
242
|
+
...context
|
|
243
|
+
});
|
|
244
|
+
else logger.debug("outbox_retry_scheduled", {
|
|
245
|
+
error: cause,
|
|
246
|
+
...context
|
|
247
|
+
});
|
|
228
248
|
await this.outboxRepo.releaseClaim(claimToken, rowsWithSameAttempts, new Date(now + delayMs));
|
|
229
249
|
}
|
|
230
250
|
}
|
|
@@ -400,4 +420,4 @@ var UnitOfWorkAlreadyActiveError = class extends AppError {
|
|
|
400
420
|
|
|
401
421
|
//#endregion
|
|
402
422
|
export { OutboxRelay as a, recordEmailEvent as c, NULL_ANALYTICS as d, QueueOrRpcPublisher as i, RecordingEmailService as l, UnitOfWork as n, MessageBus as o, OutboxRetention as r, MessageAlreadyProcessedError as s, UnitOfWorkAlreadyActiveError as t, NULL_METRICS_SERVICE as u };
|
|
403
|
-
//# sourceMappingURL=application-
|
|
423
|
+
//# sourceMappingURL=application-Ckae0K1T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"application-Ckae0K1T.js","names":["logger"],"sources":["../src/application/analytics-service.ts","../src/application/metrics-service.ts","../src/application/email-service.ts","../src/application/record-email-event.ts","../src/application/message-already-processed.error.ts","../src/application/message-bus.ts","../src/application/outbox-relay.ts","../src/application/queue-or-rpc-publisher.ts","../src/application/outbox-retention.ts","../src/application/unit-of-work.ts","../src/application/unit-of-work.errors.ts"],"sourcesContent":["import type { AnalyticsEventInput, AnalyticsProperties, AnalyticsTarget } from '@/core'\n\n/** The properties of one event name; an untyped service accepts any properties. */\nexport type AnalyticsPropertiesOf<\n TEvent extends AnalyticsEventInput,\n TName extends TEvent['name'],\n> = string extends TEvent['name']\n ? TEvent['properties']\n : Extract<TEvent, { readonly name: TName }>['properties']\n\n/** Product analytics without an SDK dependency, ported from Typist and Porte. */\nexport interface AnalyticsService<TEvent extends AnalyticsEventInput = AnalyticsEventInput> {\n /** Attach traits to one person. */\n identify(distinctId: string, properties?: AnalyticsProperties): void\n /** Merge a previous ID into this person; the application must establish that both identify the same person. */\n alias(distinctId: string, previousDistinctId: string): void\n /** Schedule an event for a person; returning does not confirm remote acceptance. */\n track<TName extends TEvent['name']>(\n name: TName,\n properties: AnalyticsPropertiesOf<TEvent, TName>,\n target: AnalyticsTarget,\n ): void\n /** Schedule an event without creating a person profile for its distinct ID. */\n trackAnonymous<TName extends TEvent['name']>(\n name: TName,\n properties: AnalyticsPropertiesOf<TEvent, TName>,\n target: AnalyticsTarget,\n ): void\n}\n\n/**\n * Reports to nobody. The composition root selects this service when analytics is disabled.\n * `satisfies` keeps its own type, so it serves an `AnalyticsService` of any event map.\n */\nexport const NULL_ANALYTICS = {\n identify: () => {},\n alias: () => {},\n track: (\n _name: string,\n _properties: AnalyticsEventInput['properties'],\n _target: AnalyticsTarget,\n ) => {},\n trackAnonymous: (\n _name: string,\n _properties: AnalyticsEventInput['properties'],\n _target: AnalyticsTarget,\n ) => {},\n} satisfies AnalyticsService\n","/** A metric dimension; use a small, bounded value set and never user or request IDs. */\nexport type MetricAttributeValue = string | number | boolean\n\n/** Dimensions shared by observations in one time series. */\nexport type MetricAttributes = Readonly<Record<string, MetricAttributeValue>>\n\n/** Unit and dimensions for an operational measurement. */\nexport interface MetricOptions {\n readonly attributes?: MetricAttributes\n readonly unit?: string\n}\n\n/** Operational measurements, ported from Typist's metrics-service.ts; applications own metric names. */\nexport interface MetricsService<TName extends string = string> {\n /** Add a nonnegative value to a total; the default increment is one. */\n count(name: TName, value?: number, options?: MetricOptions): void\n /** Record a current value. */\n gauge(name: TName, value: number, options?: MetricOptions): void\n /** Record one observation in a distribution. */\n histogram(name: TName, value: number, options?: MetricOptions): void\n}\n\n/** Records nothing when the composition root disables metrics. */\nexport const NULL_METRICS_SERVICE: MetricsService = {\n count: () => {},\n gauge: () => {},\n histogram: () => {},\n}\n","import type { EmailIdempotencyKey } from '@/core'\n\n/** A media type such as `application/pdf`. */\nexport type MimeType = `${string}/${string}`\n\n/** A file to attach. Binary content is bytes; the adapter encodes it for the provider. */\nexport type EmailAttachment = {\n readonly filename: string\n readonly content: string | Uint8Array\n /** Optional MIME metadata; Resend accepts filename and content without it. */\n readonly contentType?: MimeType\n}\n\n/**\n * One transactional email. Resend accepts 50 recipients, 75 tags with ASCII names and values,\n * and 40 MB of attachments after base64. Sources: https://resend.com/docs/api-reference/emails/send-email\n * and https://resend.com/docs/dashboard/emails/tags\n */\nexport type OutboundEmail = {\n /** Override the default for this email, for example \"Display Name <address@domain.com>\". */\n readonly from?: string\n readonly to: string | readonly string[]\n readonly subject: string\n readonly html: string\n /** Optional stable identity of this send. Resend deduplicates supplied keys for 24 hours. */\n readonly idempotencyKey?: EmailIdempotencyKey\n /** Resend generates plain text from html when omitted. An empty string disables that generation. */\n readonly text?: string\n readonly replyTo?: string\n /** Labels the provider stores with the email and returns on every delivery event. */\n readonly tags?: Readonly<Record<string, string>>\n readonly attachments?: readonly EmailAttachment[]\n}\n\n/** Received email headers, without the body or attachments. */\nexport type ReceivedEmail = { readonly headers: Readonly<Record<string, string>> }\n\n/** One attachment of a received email. `downloadUrl` is a signed link that stops working at `expiresAt`. */\nexport type ReceivedAttachment = {\n readonly filename: string | null\n readonly contentType: string\n readonly size: number\n readonly downloadUrl: string\n readonly expiresAt: string\n}\n\n/** A received email's sender, plain text body, and attachments, for an application that reads mail it receives. */\nexport type ReceivedContent = {\n /** The sender as the email states it, for example \"Display Name <address@domain.com>\". */\n readonly from: string\n readonly text: string | null\n readonly attachments: readonly ReceivedAttachment[]\n}\n\n/** The provider accepted the email and assigned its id. */\nexport type EmailAccepted = { readonly providerId: string }\n\n/**\n * The adapter supplies the default sender and maps provider errors. Each email can override the sender.\n * A provider rejection throws `EmailServiceError`; unexpected SDK rejections propagate unchanged.\n */\nexport interface IEmailService {\n /** Resolve after provider acceptance; this does not confirm delivery to the recipient. */\n sendEmail(email: OutboundEmail): Promise<EmailAccepted>\n}\n\n/** Records every send for a test. Provider ids count up as `recorded-1`, `recorded-2`. */\nexport class RecordingEmailService implements IEmailService {\n /** Sends in call order, including repeated idempotency keys. */\n readonly sent: OutboundEmail[] = []\n\n /** Record the send before returning its provider id. */\n async sendEmail(email: OutboundEmail): Promise<EmailAccepted> {\n this.sent.push(email)\n return { providerId: `recorded-${this.sent.length}` }\n }\n}\n","import type { BounceType, DomainCommand, EmailAddress, RecordEmailEventPayload } from '@/core'\n\nimport type { IUnitOfWork } from './unit-of-work.ts'\n\n/** What a bounce records for one address. */\nexport type EmailBounceRecord = {\n readonly type: BounceType\n readonly subType: string | null\n}\n\n/**\n * The application's deliverability state per address. An implementation keeps the higher bounce\n * severity with `shouldUpdateBounce`, and treats an unknown address as nothing to record.\n */\nexport interface IEmailDeliverabilityStore {\n /** Keep the higher severity atomically. Equal severity permits replacement; the store owns timestamp ordering. */\n recordBounce(address: EmailAddress, bounce: EmailBounceRecord): Promise<void>\n /** Record a complaint without clearing existing suppression state. */\n recordComplaint(address: EmailAddress): Promise<void>\n}\n\n/** The repositories the handler needs from the unit of work. */\nexport type RecordEmailEventRepos = { readonly emailDeliverability: IEmailDeliverabilityStore }\n\n/** The container slice the handler reads. */\nexport type RecordEmailEventDeps = { readonly uow: IUnitOfWork<RecordEmailEventRepos> }\n\n/** The command, under whatever name the application registered the payload. */\nexport type RecordEmailEventCommand = DomainCommand & RecordEmailEventPayload\n\n/**\n * Record a bounce or a complaint for every recipient inside the command's unit of work.\n * The command id is the provider's event id, so a redelivered webhook is `MessageAlreadyProcessedError`.\n */\nexport async function recordEmailEvent(\n command: RecordEmailEventCommand,\n deps: RecordEmailEventDeps,\n): Promise<void> {\n await deps.uow.run(command.id, async ({ emailDeliverability }) => {\n for (const address of command.recipients) {\n if (command.event.type === 'bounced') {\n // oxlint-disable-next-line no-await-in-loop -- Finish each transaction write before starting another; a failure must leave no pending writes.\n await emailDeliverability.recordBounce(address, {\n type: command.event.bounceType,\n subType: command.event.bounceSubType,\n })\n } else {\n // oxlint-disable-next-line no-await-in-loop -- A rejected write must stop the transaction before another write starts.\n await emailDeliverability.recordComplaint(address)\n }\n }\n })\n}\n","import { AppError, DatabaseError, type MessageId } from '@/core'\n\n/** The message's effects already committed. A caller can skip duplicate delivery without repeating those effects. */\nexport class MessageAlreadyProcessedError extends AppError {\n readonly _tag = 'MessageAlreadyProcessedError'\n readonly classification = 'terminal'\n\n constructor(\n readonly messageId: MessageId,\n options?: { readonly cause: unknown },\n ) {\n super(`message ${messageId} already processed`, options)\n }\n\n /** Recognize only the processed-message primary key; preserve other failures unchanged at the call site. */\n static from(cause: unknown, messageId: MessageId): MessageAlreadyProcessedError | undefined {\n if (cause instanceof MessageAlreadyProcessedError) return cause\n if (!(cause instanceof DatabaseError)) return undefined\n const details = cause.details\n const duplicate =\n details.database === 'd1'\n ? details.kind === 'unique' &&\n details.table === 'processed_messages' &&\n details.column === 'message_id'\n : details.code === '23505' &&\n details.table_name === 'processed_messages' &&\n details.constraint_name === 'processed_messages_pkey'\n if (!duplicate) return undefined\n return new MessageAlreadyProcessedError(messageId, { cause })\n }\n}\n","import { panic } from 'better-result'\n\nimport {\n createLogger,\n unreachable,\n type DomainCommand,\n type DomainEvent,\n type DomainMessage,\n type DomainQuery,\n} from '@/core'\n\nimport { MessageAlreadyProcessedError } from './message-already-processed.error.ts'\nimport type { Handlers, MessageResult, RegistryMessage } from './registry.ts'\n\nconst logger = createLogger('message-bus')\n\n/** The port an entrypoint dispatches through. `AppDeps` names this as `IMessageBus<typeof registry>`. */\nexport interface IMessageBus<TRegistry extends Handlers<never>> {\n handle<TMessage extends DomainMessage & RegistryMessage<TRegistry>>(\n message: TMessage,\n ): Promise<MessageResult<TMessage, TRegistry>>\n}\n\n/**\n * Routes one message to its slot. The bus opens no transaction and keeps no outbox:\n * a handler that writes opens the unit of work itself.\n */\nexport class MessageBus<Deps, TRegistry extends Handlers<Deps>> implements IMessageBus<TRegistry> {\n constructor(\n private readonly deps: Deps,\n private readonly registry: TRegistry,\n ) {}\n\n /** Resolves to the handler's own value for a command or query, and to void for an event. */\n async handle<TMessage extends DomainMessage & RegistryMessage<TRegistry>>(\n message: TMessage,\n ): Promise<MessageResult<TMessage, TRegistry>> {\n // SAFETY: the slot for this name is what `MessageResult` names, and dispatch returns its value.\n return (await this.dispatch(message)) as MessageResult<TMessage, TRegistry>\n }\n\n // oxlint-disable-next-line anti-slop/no-unknown-returns -- one table holds every slot's value; `handle` names it.\n private dispatch(message: DomainMessage): Promise<unknown> {\n if (message.type === 'command') return this.handleCommand(message)\n if (message.type === 'query') return this.handleQuery(message)\n if (message.type === 'event') return this.handleEvent(message)\n // A message parsed at a boundary cannot reach here; one that does must not resolve silently.\n return unreachable(message)\n }\n\n // oxlint-disable-next-line anti-slop/no-unknown-returns -- see dispatch.\n private handleCommand(command: DomainCommand): Promise<unknown> {\n // `satisfies` proved the registry complete, so a missing slot is a broken registry object.\n const handler = this.registry.commands[command.name]\n if (handler === undefined) return panic(`No handler for command ${command.name}`)\n logger.debug('handling_command', { name: command.name, id: command.id })\n return handler(command, this.deps)\n }\n\n // oxlint-disable-next-line anti-slop/no-unknown-returns -- see dispatch.\n private handleQuery(query: DomainQuery): Promise<unknown> {\n const handler = this.registry.queries[query.name]\n if (handler === undefined) return panic(`No handler for query ${query.name}`)\n return handler(query, this.deps)\n }\n\n /**\n * Subscribers run one after another, so one unit of work finishes before the next starts.\n * Every subscriber still runs. One failure is rethrown as is. Several are thrown as one `AggregateError`.\n * A `MessageAlreadyProcessedError` means that subscriber already committed on an earlier delivery, so it counts as delivered.\n */\n private async handleEvent(event: DomainEvent): Promise<void> {\n const subscriptions = this.registry.events[event.name] ?? []\n const failures: unknown[] = []\n for (const [subscriber, subscription] of subscriptions.entries()) {\n try {\n await subscription(event, this.deps)\n } catch (reason) {\n if (reason instanceof MessageAlreadyProcessedError) {\n // The bus swallows this error, so this line is the only record that the skip happened.\n logger.info('duplicate_subscriber_ignored', {\n event: event.name,\n id: event.id,\n subscriber,\n })\n continue\n }\n failures.push(reason)\n }\n }\n if (failures.length === 1) throw failures[0]\n if (failures.length > 1) {\n throw new AggregateError(failures, `${event.name}: ${failures.length} subscribers failed`)\n }\n }\n}\n","import { createLogger, decideRedelivery, type DomainEvent, type RedeliveryDelays } from '@/core'\n\nimport type { IOutboxPublisher, RejectedMessage } from './outbox-publisher.ts'\nimport type { IOutboxRepository, OutboxFailureReason, OutboxRow } from './outbox-repository.ts'\n\n/** Optional limits for one relay; durable attempts exclude the publisher's immediate retries. */\nexport type OutboxRelayOptions = {\n readonly batchSize?: number\n readonly maxBatches?: number\n readonly maxAttempts?: number\n /** The longest wait before the first retry; each later retry doubles it. Jitter picks between half and all of it. */\n readonly retryBaseDelayMs?: number\n /** The longest wait before a retry. */\n readonly retryMaxDelayMs?: number\n}\n\nconst logger = createLogger('outbox-relay')\n\n/**\n * Moves committed events out of the outbox: claims due rows, sends their events through the publisher,\n * and records each result on its row. A repository failure propagates to the caller.\n */\nexport class OutboxRelay {\n private readonly options: Required<OutboxRelayOptions>\n\n constructor(\n private readonly publisher: IOutboxPublisher,\n private readonly outboxRepo: IOutboxRepository,\n options: OutboxRelayOptions = {},\n private readonly now: () => Date = () => new Date(),\n ) {\n this.options = {\n batchSize: options.batchSize ?? 50,\n maxBatches: options.maxBatches ?? 10,\n maxAttempts: options.maxAttempts ?? 10,\n retryBaseDelayMs: options.retryBaseDelayMs ?? 1_000,\n retryMaxDelayMs: options.retryMaxDelayMs ?? 60_000,\n }\n }\n\n /**\n * Runs one batch: claims due rows, marks rows with no attempt left failed, and publishes the rest.\n * Called by `drain()`. Returns the claimed count; zero means no row was due.\n */\n async flush(): Promise<number> {\n const batch = await this.outboxRepo.claimDueRows(this.options.batchSize)\n const { exhaustedRows, dueRows } = this.splitByAttemptsLeft(batch.rows)\n await this.markFailed(batch.token, exhaustedRows, 'exhausted', 'Claim attempt limit exceeded')\n if (dueRows.length > 0) await this.publishDueRows(batch.token, dueRows)\n // drain() keeps going while a claim finds rows, even if every row was exhausted.\n return batch.rows.length\n }\n\n /** Sends the rows' events, marks the delivered rows published, and fails or retries the rest by their error. */\n private async publishDueRows(\n claimToken: string,\n dueRows: ReadonlyArray<OutboxRow>,\n ): Promise<void> {\n const publishedAt = this.now().getTime()\n const rejectedEvents = await this.publishEvents(dueRows)\n const rejectedEventIds = new Set(rejectedEvents.map(({ message: event }) => event.id))\n for (const row of dueRows) {\n // The wait from the commit to the delivery start: the latency a priority event exists to avoid.\n logger.info('outbox_event_timing', {\n name: row.payload.name,\n attempt: row.attempts,\n waitMs: publishedAt - row.createdAt.getTime(),\n outcome: rejectedEventIds.has(row.payload.id) ? 'rejected' : 'delivered',\n })\n }\n const deliveredRows = dueRows.filter((row) => !rejectedEventIds.has(row.payload.id))\n if (deliveredRows.length > 0) await this.outboxRepo.markPublished(claimToken, deliveredRows)\n for (const [error, rows] of rowsByError(rejectedEvents, dueRows)) {\n // oxlint-disable-next-line no-await-in-loop -- Each error decides fail or retry for its own rows.\n await this.failOrRetry(claimToken, rows, error)\n }\n }\n\n /** Publishes the rows' events. A thrown publish counts as every event rejected with that error. */\n private async publishEvents(\n rows: ReadonlyArray<OutboxRow>,\n ): Promise<readonly RejectedMessage<DomainEvent>[]> {\n const events = rows.map((row) => row.payload)\n try {\n return await this.publisher.publish(events)\n } catch (error) {\n return events.map((event) => ({ message: event, error }))\n }\n }\n\n /**\n * Runs batches until no row is due: at most `maxBatches` × `batchSize` rows, 500 by default. Called\n * after a commit through `waitUntilWake`, and by the app's cron sweep. Returns the total claimed count.\n */\n async drain(): Promise<number> {\n let total = 0\n for (let batchCount = 0; batchCount < this.options.maxBatches; batchCount += 1) {\n // oxlint-disable-next-line no-await-in-loop -- An empty claim must stop the drain before another claim starts.\n const claimed = await this.flush()\n total += claimed\n if (claimed === 0) break\n }\n return total\n }\n\n /**\n * Decides fail or retry for rows whose events were not delivered: the whole send failed, or one event\n * was rejected. Marks them failed on a terminal error or a last attempt; releases the rest for retry.\n */\n private async failOrRetry(\n claimToken: string,\n rows: ReadonlyArray<OutboxRow>,\n cause: unknown,\n ): Promise<void> {\n const errorMessage =\n cause instanceof Error ? cause.message : 'Publisher rejected with a non-error value'\n if (decideRedelivery(cause, 1, this.delays).action === 'ack') {\n await this.markFailed(claimToken, rows, 'terminal', errorMessage, cause)\n return\n }\n const { exhaustedRows, retryRows } = this.splitByLastAttempt(rows)\n await this.markFailed(claimToken, exhaustedRows, 'exhausted', errorMessage, cause)\n await this.releaseForRetry(claimToken, retryRows, cause)\n }\n\n /** Releases rows for another attempt. Each row waits longer the more attempts it has used. */\n private async releaseForRetry(\n claimToken: string,\n rows: ReadonlyArray<OutboxRow>,\n cause: unknown,\n ): Promise<void> {\n const now = this.now().getTime()\n for (const attempts of new Set(rows.map((row) => row.attempts))) {\n const decision = decideRedelivery(cause, attempts, this.delays)\n // An ack here means a member error changed between calls; the row then retries at once.\n const delayMs = decision.action === 'retry' ? decision.delayMs : 0\n const rowsWithSameAttempts = rows.filter((row) => row.attempts === attempts)\n // As the queue consumer does: an unknown failure is an error once, on its first attempt.\n const context = { attempt: attempts, count: rowsWithSameAttempts.length, delayMs }\n if (decision.action === 'retry' && decision.classification === 'unknown' && attempts === 1) {\n logger.error('outbox_unknown_failure', { error: cause, ...context })\n } else {\n logger.debug('outbox_retry_scheduled', { error: cause, ...context })\n }\n // oxlint-disable-next-line no-await-in-loop -- Stop releasing rows on the first database failure.\n await this.outboxRepo.releaseClaim(claimToken, rowsWithSameAttempts, new Date(now + delayMs))\n }\n }\n\n private get delays(): RedeliveryDelays {\n return { baseDelayMs: this.options.retryBaseDelayMs, maxDelayMs: this.options.retryMaxDelayMs }\n }\n\n /** Splits claimed rows: rows whose last run crashed with no attempt left, and rows that may run now. */\n private splitByAttemptsLeft(rows: ReadonlyArray<OutboxRow>) {\n return {\n exhaustedRows: rows.filter((row) => this.hasNoAttemptLeft(row)),\n dueRows: rows.filter((row) => !this.hasNoAttemptLeft(row)),\n }\n }\n\n /** Splits failed rows: rows that just used their last attempt, and rows that may retry. */\n private splitByLastAttempt(rows: ReadonlyArray<OutboxRow>) {\n return {\n exhaustedRows: rows.filter((row) => this.isLastAttempt(row)),\n retryRows: rows.filter((row) => !this.isLastAttempt(row)),\n }\n }\n\n /** True when the row used every attempt before this claim: its last run crashed, so it must not run again. */\n private hasNoAttemptLeft(row: OutboxRow): boolean {\n // The claim already counted this run, so a row on its last allowed run equals the limit.\n return row.attempts > this.options.maxAttempts\n }\n\n /** True when the run that just failed was the row's last allowed attempt, so it must not be retried. */\n private isLastAttempt(row: OutboxRow): boolean {\n return row.attempts >= this.options.maxAttempts\n }\n\n /** Calls `outboxRepo.markFailed`, then logs one error. Does nothing for an empty list. */\n private async markFailed(\n claimToken: string,\n rows: ReadonlyArray<OutboxRow>,\n reason: OutboxFailureReason,\n errorMessage: string,\n cause?: unknown,\n ): Promise<void> {\n if (rows.length === 0) return\n await this.outboxRepo.markFailed(claimToken, rows, reason, errorMessage)\n logger.error('outbox_rows_failed', {\n error: cause,\n claimToken,\n count: rows.length,\n reason,\n })\n }\n}\n\n/** Groups the rejected events' rows by error object, so one error decides fail or retry for all its rows at once. */\nfunction rowsByError(\n rejectedEvents: readonly RejectedMessage<DomainEvent>[],\n rows: ReadonlyArray<OutboxRow>,\n): Map<unknown, OutboxRow[]> {\n const rowByEventId = new Map(rows.map((row) => [row.payload.id, row]))\n const groups = new Map<unknown, OutboxRow[]>()\n for (const { message: event, error } of rejectedEvents) {\n const row = rowByEventId.get(event.id)\n if (row === undefined) continue\n const group = groups.get(error) ?? []\n group.push(row)\n groups.set(error, group)\n }\n return groups\n}\n","import type { DomainEvent } from '@/core'\n\nimport type { IOutboxPublisher, RejectedMessage } from './outbox-publisher.ts'\n\n/** A client for a consumer that runs one event when called over RPC; it rejects when the event fails. */\nexport interface IEventConsumerClient {\n consume(event: DomainEvent): Promise<void>\n}\n\n/** The two clients, the app's rule for choosing between them, and the RPC budget. */\nexport type QueueOrRpcPublisherOptions = {\n readonly queue: IOutboxPublisher\n readonly consumer: IEventConsumerClient\n /** The app's rule: which events go over RPC. */\n readonly usesRpc: (event: DomainEvent) => boolean\n /** RPC calls in one request. Cloudflare allows 32 Worker invocations per request. */\n readonly callsPerRequest?: number\n}\n\nconst DEFAULT_CALLS_PER_REQUEST = 25\n\n/**\n * Publishes each event over RPC or to the queue. Events that the rule picks are consumed over RPC,\n * in parallel, until the request's call budget is used; every other event goes to the queue.\n * Build one per request, so the budget counts that request's calls. A failed call or queue send\n * rejects only its own events.\n */\nexport class QueueOrRpcPublisher implements IOutboxPublisher {\n private callsLeft: number\n\n constructor(private readonly options: QueueOrRpcPublisherOptions) {\n this.callsLeft = options.callsPerRequest ?? DEFAULT_CALLS_PER_REQUEST\n }\n\n async publish(\n events: ReadonlyArray<DomainEvent>,\n ): Promise<readonly RejectedMessage<DomainEvent>[]> {\n const rpcEvents: DomainEvent[] = []\n const queuedEvents: DomainEvent[] = []\n for (const event of events) {\n if (this.options.usesRpc(event) && this.callsLeft > 0) {\n rpcEvents.push(event)\n this.callsLeft -= 1\n } else {\n queuedEvents.push(event)\n }\n }\n\n const [queueResult, ...callResults] = await Promise.allSettled([\n queuedEvents.length > 0 ? this.options.queue.publish(queuedEvents) : Promise.resolve([]),\n ...rpcEvents.map((event) => this.options.consumer.consume(event)),\n ])\n\n const queueRejected =\n queueResult?.status === 'fulfilled'\n ? queueResult.value\n : queuedEvents.map((event) => ({ message: event, error: queueResult?.reason }))\n const callRejected = rpcEvents.flatMap((event, index) => {\n const result = callResults[index]\n return result?.status === 'rejected' ? [{ message: event, error: result.reason }] : []\n })\n return [...queueRejected, ...callRejected]\n }\n}\n","import type { IOutboxRetentionRepository } from './outbox-repository.ts'\n\n/** Typist's retention defaults; receipt retention must cover the application's supported replay period. */\nexport type OutboxRetentionOptions = {\n readonly publishedRetentionMs?: number\n readonly failedRetentionMs?: number\n readonly receiptRetentionMs?: number\n readonly batchSize?: number\n readonly maxBatches?: number\n}\n\n/** Deleted records per category during one bounded sweep. */\nexport type OutboxRetentionResult = {\n readonly published: number\n readonly failed: number\n readonly receipts: number\n}\n\n/** Bounded cleanup ported from Typist's message retention handler. The application schedules each sweep. */\nexport class OutboxRetention {\n private readonly options: Required<OutboxRetentionOptions>\n\n constructor(\n private readonly repository: IOutboxRetentionRepository,\n options: OutboxRetentionOptions = {},\n ) {\n this.options = {\n publishedRetentionMs: options.publishedRetentionMs ?? 86_400_000,\n failedRetentionMs: options.failedRetentionMs ?? 30 * 86_400_000,\n receiptRetentionMs: options.receiptRetentionMs ?? 7 * 86_400_000,\n batchSize: options.batchSize ?? 1_000,\n maxBatches: options.maxBatches ?? 10,\n }\n }\n\n /** Calculate all cutoffs from one supplied time; propagate repository failures to the calling boundary. */\n async sweep(at: Date): Promise<OutboxRetentionResult> {\n const { publishedRetentionMs, failedRetentionMs, receiptRetentionMs } = this.options\n const published = await this.deleteBatches((size) =>\n this.repository.deletePublishedBefore(new Date(at.getTime() - publishedRetentionMs), size),\n )\n const failed = await this.deleteBatches((size) =>\n this.repository.deleteFailedBefore(new Date(at.getTime() - failedRetentionMs), size),\n )\n const receipts = await this.deleteBatches((size) =>\n this.repository.deleteReceiptsBefore(new Date(at.getTime() - receiptRetentionMs), size),\n )\n return { published, failed, receipts }\n }\n\n private async deleteBatches(deleteBatch: (size: number) => Promise<number>): Promise<number> {\n let total = 0\n for (let batch = 0; batch < this.options.maxBatches; batch += 1) {\n // oxlint-disable-next-line no-await-in-loop -- The delete count decides whether another batch is needed.\n const deleted = await deleteBatch(this.options.batchSize)\n total += deleted\n if (deleted < this.options.batchSize) break\n }\n return total\n }\n}\n","import type { MessageId } from '@/core'\n\nimport type { IEventCollector } from './event-collector.ts'\nimport { MessageAlreadyProcessedError } from './message-already-processed.error.ts'\nimport type { IMessageStore } from './message-store.ts'\nimport type { IOutboxWake } from './outbox-wake.ts'\nimport type { ITransaction } from './transaction.ts'\n\n/**\n * Commits the processed-message record, repository writes, and emitted events together before resolving.\n * An existing record rejects with `MessageAlreadyProcessedError`; external effects must run through the outbox after commit.\n * Nested calls reject with `UnitOfWorkAlreadyActiveError`.\n */\nexport interface IUnitOfWork<Repos> {\n run<T>(messageId: MessageId, work: (repos: Repos) => Promise<T>): Promise<T>\n}\n\nexport type UnitOfWorkDeps<Tx, Repos> = {\n readonly transaction: ITransaction<Tx>\n /** The processed-messages and outbox repository for this transaction. */\n readonly messageStore: (tx: Tx) => IMessageStore\n /** Repositories participate in the current transaction. */\n readonly createRepositories: (tx: Tx) => Repos\n readonly eventCollector: IEventCollector\n readonly relay: IOutboxWake\n}\n\n/** The unit of work: the boundary, the repositories, the events. Every database call is a port. */\nexport class UnitOfWork<Tx, Repos> implements IUnitOfWork<Repos> {\n constructor(private readonly deps: UnitOfWorkDeps<Tx, Repos>) {}\n\n async run<T>(messageId: MessageId, work: (repos: Repos) => Promise<T>): Promise<T> {\n const { transaction, messageStore, createRepositories, eventCollector, relay } = this.deps\n let written = 0\n\n let value: T\n try {\n value = await transaction.transaction(async (tx) => {\n const store = messageStore(tx)\n await store.claim(messageId)\n const repos = createRepositories(tx)\n const result = await work(repos)\n const pending = eventCollector.collect()\n await store.persistEvents(pending)\n written = pending.length\n return result\n })\n } catch (error) {\n throw MessageAlreadyProcessedError.from(error, messageId) ?? error\n }\n\n if (written > 0) relay.wake()\n return value\n }\n}\n","import { AppError } from '@/core'\n\n/** A boundary was opened inside another. One message, one atomic scope. */\nexport class UnitOfWorkAlreadyActiveError extends AppError {\n readonly _tag = 'UnitOfWorkAlreadyActiveError'\n readonly classification = 'terminal'\n\n constructor() {\n super('a unit of work is already active')\n }\n}\n"],"mappings":"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"}
|
package/dist/application.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { J as DomainEvent, X as DomainQuery, Y as DomainMessage, Z as MessageId, q as DomainCommand } from "./index-tJzJUgTh.js";
|
|
2
|
-
import { $ as ObservabilityUser, A as RecordEmailEventDeps, B as EmailAccepted, C as MessageRegistry, D as EmailBounceRecord, E as RegistryMessage, F as UnitOfWorkDeps, G as ReceivedAttachment, H as IEmailService, I as ITransaction, J as RecordingEmailService, K as ReceivedContent, L as IOutboxWake, M as recordEmailEvent, N as IUnitOfWork, O as IEmailDeliverabilityStore, P as UnitOfWork, Q as ObservabilityService, R as IMessageStore, S as Handlers, T as QueryRegistry, U as MimeType, V as EmailAttachment, W as OutboundEmail, X as setObservabilityUserHook, Y as applyObservabilityUser, Z as ObservabilityScope, _ as RejectedMessage, a as OutboxRetentionResult, at as AnalyticsPropertiesOf, b as CommandRegistry, c as QueueOrRpcPublisherOptions, ct as CommandHandler, d as IOutboxRepository, et as MetricAttributeValue, f as IOutboxRetentionRepository, g as IOutboxPublisher, h as OutboxRow, i as OutboxRetentionOptions, it as NULL_METRICS_SERVICE, j as RecordEmailEventRepos, k as RecordEmailEventCommand, l as OutboxRelay, lt as EventHandler, m as OutboxFailureReason, n as MessageAlreadyProcessedError, nt as MetricOptions, o as IEventConsumerClient, ot as AnalyticsService, p as OutboxBatch, q as ReceivedEmail, r as OutboxRetention, rt as MetricsService, s as QueueOrRpcPublisher, st as NULL_ANALYTICS, t as UnitOfWorkAlreadyActiveError, tt as MetricAttributes, u as OutboxRelayOptions, ut as QueryHandler, v as IMessageBus, w as MessageResult, x as EventRegistry, y as MessageBus, z as IEventCollector } from "./index-
|
|
2
|
+
import { $ as ObservabilityUser, A as RecordEmailEventDeps, B as EmailAccepted, C as MessageRegistry, D as EmailBounceRecord, E as RegistryMessage, F as UnitOfWorkDeps, G as ReceivedAttachment, H as IEmailService, I as ITransaction, J as RecordingEmailService, K as ReceivedContent, L as IOutboxWake, M as recordEmailEvent, N as IUnitOfWork, O as IEmailDeliverabilityStore, P as UnitOfWork, Q as ObservabilityService, R as IMessageStore, S as Handlers, T as QueryRegistry, U as MimeType, V as EmailAttachment, W as OutboundEmail, X as setObservabilityUserHook, Y as applyObservabilityUser, Z as ObservabilityScope, _ as RejectedMessage, a as OutboxRetentionResult, at as AnalyticsPropertiesOf, b as CommandRegistry, c as QueueOrRpcPublisherOptions, ct as CommandHandler, d as IOutboxRepository, et as MetricAttributeValue, f as IOutboxRetentionRepository, g as IOutboxPublisher, h as OutboxRow, i as OutboxRetentionOptions, it as NULL_METRICS_SERVICE, j as RecordEmailEventRepos, k as RecordEmailEventCommand, l as OutboxRelay, lt as EventHandler, m as OutboxFailureReason, n as MessageAlreadyProcessedError, nt as MetricOptions, o as IEventConsumerClient, ot as AnalyticsService, p as OutboxBatch, q as ReceivedEmail, r as OutboxRetention, rt as MetricsService, s as QueueOrRpcPublisher, st as NULL_ANALYTICS, t as UnitOfWorkAlreadyActiveError, tt as MetricAttributes, u as OutboxRelayOptions, ut as QueryHandler, v as IMessageBus, w as MessageResult, x as EventRegistry, y as MessageBus, z as IEventCollector } from "./index-wNaxYvB2.js";
|
|
3
3
|
export { type AnalyticsPropertiesOf, type AnalyticsService, type CommandHandler, type CommandRegistry, type DomainCommand, type DomainEvent, type DomainMessage, type DomainQuery, type EmailAccepted, type EmailAttachment, type EmailBounceRecord, type EventHandler, type EventRegistry, type Handlers, type IEmailDeliverabilityStore, type IEmailService, type IEventCollector, type IEventConsumerClient, type IMessageBus, type IMessageStore, type IOutboxPublisher, type IOutboxRepository, type IOutboxRetentionRepository, type IOutboxWake, type ITransaction, type IUnitOfWork, MessageAlreadyProcessedError, MessageBus, type MessageId, type MessageRegistry, type MessageResult, type MetricAttributeValue, type MetricAttributes, type MetricOptions, type MetricsService, type MimeType, NULL_ANALYTICS, NULL_METRICS_SERVICE, type ObservabilityScope, type ObservabilityService, type ObservabilityUser, type OutboundEmail, type OutboxBatch, type OutboxFailureReason, OutboxRelay, type OutboxRelayOptions, OutboxRetention, type OutboxRetentionOptions, type OutboxRetentionResult, type OutboxRow, type QueryHandler, type QueryRegistry, QueueOrRpcPublisher, type QueueOrRpcPublisherOptions, type ReceivedAttachment, type ReceivedContent, type ReceivedEmail, type RecordEmailEventCommand, type RecordEmailEventDeps, type RecordEmailEventRepos, RecordingEmailService, type RegistryMessage, type RejectedMessage, UnitOfWork, UnitOfWorkAlreadyActiveError, type UnitOfWorkDeps, applyObservabilityUser, recordEmailEvent, setObservabilityUserHook };
|
package/dist/application.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as OutboxRelay, c as recordEmailEvent, d as NULL_ANALYTICS, i as QueueOrRpcPublisher, l as RecordingEmailService, n as UnitOfWork, o as MessageBus, r as OutboxRetention, s as MessageAlreadyProcessedError, t as UnitOfWorkAlreadyActiveError, u as NULL_METRICS_SERVICE } from "./application-
|
|
1
|
+
import { a as OutboxRelay, c as recordEmailEvent, d as NULL_ANALYTICS, i as QueueOrRpcPublisher, l as RecordingEmailService, n as UnitOfWork, o as MessageBus, r as OutboxRetention, s as MessageAlreadyProcessedError, t as UnitOfWorkAlreadyActiveError, u as NULL_METRICS_SERVICE } from "./application-Ckae0K1T.js";
|
|
2
2
|
import { n as setObservabilityUserHook, t as applyObservabilityUser } from "./request-user-B4rh1c2-.js";
|
|
3
3
|
|
|
4
4
|
export { MessageAlreadyProcessedError, MessageBus, NULL_ANALYTICS, NULL_METRICS_SERVICE, OutboxRelay, OutboxRetention, QueueOrRpcPublisher, RecordingEmailService, UnitOfWork, UnitOfWorkAlreadyActiveError, applyObservabilityUser, recordEmailEvent, setObservabilityUserHook };
|
package/dist/better-auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as createLogger } from "./core-
|
|
1
|
+
import { r as createLogger } from "./core-CGj1ukXV.js";
|
|
2
2
|
import { v7 } from "uuid";
|
|
3
3
|
import { isAPIError } from "better-auth/api";
|
|
4
4
|
import { anonymous, bearer, captcha, deviceAuthorization, emailOTP, lastLoginMethod, magicLink, openAPI } from "better-auth/plugins";
|
package/dist/billing/client.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as BrowserFloorError, n as buildTargets, r as updateScreenCopy, t as browserFloor } from "../floor-
|
|
1
|
+
import { i as BrowserFloorError, n as buildTargets, r as updateScreenCopy, t as browserFloor } from "../floor-DUb4oGQd.js";
|
|
2
2
|
|
|
3
3
|
export { BrowserFloorError, browserFloor, buildTargets, updateScreenCopy };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as BrowserFloorError, t as browserFloor } from "../floor-
|
|
1
|
+
import { i as BrowserFloorError, t as browserFloor } from "../floor-DUb4oGQd.js";
|
|
2
2
|
import { t as POLYFILLED_APIS } from "../polyfills-DEQFkqRI.js";
|
|
3
3
|
import { defineRule, eslintCompatPlugin } from "@oxlint/plugins";
|
|
4
4
|
import bcd from "@mdn/browser-compat-data/forLegacyNode";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as shouldRetryFailure, b as isAbortError } from "../core-
|
|
2
|
-
import { i as isClassifiedDurableObjectError, n as DurableObjectCallError, r as classifyDurableObjectError } from "../durable-object-errors-
|
|
1
|
+
import { S as shouldRetryFailure, b as isAbortError } from "../core-CGj1ukXV.js";
|
|
2
|
+
import { i as isClassifiedDurableObjectError, n as DurableObjectCallError, r as classifyDurableObjectError } from "../durable-object-errors-BLGJplW-.js";
|
|
3
3
|
import { Result } from "better-result";
|
|
4
4
|
|
|
5
5
|
//#region src/infrastructure/cloudflare/durable-object-client.ts
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as isDurableObjectCallError, i as isClassifiedDurableObjectError, n as DurableObjectCallError, r as classifyDurableObjectError, t as DURABLE_OBJECT_CALL_ERROR } from "../durable-object-errors-
|
|
1
|
+
import { a as isDurableObjectCallError, i as isClassifiedDurableObjectError, n as DurableObjectCallError, r as classifyDurableObjectError, t as DURABLE_OBJECT_CALL_ERROR } from "../durable-object-errors-BLGJplW-.js";
|
|
2
2
|
|
|
3
3
|
export { DURABLE_OBJECT_CALL_ERROR, DurableObjectCallError, classifyDurableObjectError, isClassifiedDurableObjectError, isDurableObjectCallError };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { d as spreadJitterMs, l as exponentialDelayCapMs } from "../core-
|
|
2
|
-
import { i as isKVPutRateLimitError, n as decideKVRetry, t as KVError } from "../kv-errors-
|
|
1
|
+
import { d as spreadJitterMs, l as exponentialDelayCapMs } from "../core-CGj1ukXV.js";
|
|
2
|
+
import { i as isKVPutRateLimitError, n as decideKVRetry, t as KVError } from "../kv-errors-dwVmCqER.js";
|
|
3
3
|
import { Result } from "better-result";
|
|
4
4
|
|
|
5
5
|
//#region src/infrastructure/cloudflare/kv-client.ts
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as isRetryableKVError, i as isKVPutRateLimitError, n as decideKVRetry, o as isTerminalKVError, r as extractKVMeta, s as isTransientKVError, t as KVError } from "../kv-errors-
|
|
1
|
+
import { a as isRetryableKVError, i as isKVPutRateLimitError, n as decideKVRetry, o as isTerminalKVError, r as extractKVMeta, s as isTransientKVError, t as KVError } from "../kv-errors-dwVmCqER.js";
|
|
2
2
|
|
|
3
3
|
export { KVError, decideKVRetry, extractKVMeta, isKVPutRateLimitError, isRetryableKVError, isTerminalKVError, isTransientKVError };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $t as FailureClassification, J as DomainEvent, Qt as AppError, q as DomainCommand } from "../index-tJzJUgTh.js";
|
|
2
|
-
import { L as IOutboxWake, _ as RejectedMessage, l as OutboxRelay } from "../index-
|
|
2
|
+
import { L as IOutboxWake, _ as RejectedMessage, l as OutboxRelay } from "../index-wNaxYvB2.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/infrastructure/cloudflare/queue/queue-message.d.ts
|
|
5
5
|
/** A message a queue carries: a command from outside the request, or an event from the outbox. */
|
package/dist/cloudflare/queue.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as shouldRetryFailure, c as decideRedelivery, f as classifyBoundaryError, r as createLogger, rt as AppError } from "../core-
|
|
2
|
-
import { s as MessageAlreadyProcessedError } from "../application-
|
|
1
|
+
import { S as shouldRetryFailure, c as decideRedelivery, f as classifyBoundaryError, r as createLogger, rt as AppError } from "../core-CGj1ukXV.js";
|
|
2
|
+
import { s as MessageAlreadyProcessedError } from "../application-Ckae0K1T.js";
|
|
3
3
|
import { Result } from "better-result";
|
|
4
4
|
|
|
5
5
|
//#region src/infrastructure/cloudflare/queue/queue-limits.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as isAbortError, rt as AppError } from "../core-
|
|
2
|
-
import { n as R2JsonError, t as R2Error } from "../r2-errors-
|
|
1
|
+
import { b as isAbortError, rt as AppError } from "../core-CGj1ukXV.js";
|
|
2
|
+
import { n as R2JsonError, t as R2Error } from "../r2-errors-DeIh9VWr.js";
|
|
3
3
|
|
|
4
4
|
//#region src/infrastructure/cloudflare/r2-client.ts
|
|
5
5
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as shouldRetryFailure, f as classifyBoundaryError, rt as AppError, u as exponentialEqualJitterMs } from "../core-
|
|
1
|
+
import { S as shouldRetryFailure, f as classifyBoundaryError, rt as AppError, u as exponentialEqualJitterMs } from "../core-CGj1ukXV.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { Result } from "better-result";
|
|
4
4
|
|
package/dist/cloudflare.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { J as DomainEvent, Z as MessageId, at as DatabaseError, it as D1ConstraintKind, ot as DatabaseFailure } from "./index-tJzJUgTh.js";
|
|
2
|
-
import { I as ITransaction, R as IMessageStore, d as IOutboxRepository, h as OutboxRow, m as OutboxFailureReason, p as OutboxBatch } from "./index-
|
|
3
|
-
import { n as eventCollector, r as DrizzleSchema, t as OutboxRepositoryOptions } from "./outbox-repository-options-
|
|
2
|
+
import { I as ITransaction, R as IMessageStore, d as IOutboxRepository, h as OutboxRow, m as OutboxFailureReason, p as OutboxBatch } from "./index-wNaxYvB2.js";
|
|
3
|
+
import { n as eventCollector, r as DrizzleSchema, t as OutboxRepositoryOptions } from "./outbox-repository-options-H-nJOB75.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
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAYjB,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;;;;;;;qBCGE,mBAAmB,eAAe,0BAA0B;mBAIpD;mBAEA;mBALF;EAEjB,YACmB,IAAI,KAAK,SAC1B,UAAS,yBACQ,YAAW;;EAMxB,aAAa,oBAAoB,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAYjB,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;;;;;;;qBCGE,mBAAmB,eAAe,0BAA0B;mBAIpD;mBAEA;mBALF;EAEjB,YACmB,IAAI,KAAK,SAC1B,UAAS,yBACQ,YAAW;;EAMxB,aAAa,oBAAoB,QAAQ;;EAsDzC,cAAc,oBAAoB,MAAM,cAAc,aAAa;;EAanE,aACJ,oBACA,MAAM,cAAc,YACpB,aAAa,OACZ;;EAgBG,WACJ,oBACA,MAAM,cAAc,YACpB,QAAQ,qBACR,uBACC;;EAkBG,sBAAsB,QAAQ,MAAM,oBAAoB;;EAqBxD,mBAAmB,QAAQ,MAAM,oBAAoB;;EAqBrD,qBAAqB,QAAQ,MAAM,oBAAoB"}
|
package/dist/cloudflare.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as DatabaseError, rt as AppError } from "./core-
|
|
2
|
-
import { s 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 { A as DatabaseError, rt as AppError } from "./core-CGj1ukXV.js";
|
|
2
|
+
import { s as MessageAlreadyProcessedError } from "./application-Ckae0K1T.js";
|
|
3
|
+
import { a as stageD1Statement, i as runWithUnitOfWorkContext, n as collectD1Statements, r as eventCollector, t as OUTBOX_REPOSITORY_DEFAULTS } from "./outbox-repository-options-C3JVYiF4.js";
|
|
4
4
|
import { Result } from "better-result";
|
|
5
5
|
import { DrizzleQueryError } from "drizzle-orm/errors";
|
|
6
6
|
import { and, asc, eq, inArray, isNull, lt, lte, or, sql } from "drizzle-orm";
|
|
@@ -210,9 +210,10 @@ var D1OutboxRepository = class {
|
|
|
210
210
|
rows.sort((a, b) => a.availableAt.getTime() - b.availableAt.getTime() || a.createdAt.getTime() - b.createdAt.getTime() || a.payload.id.localeCompare(b.payload.id));
|
|
211
211
|
return {
|
|
212
212
|
token,
|
|
213
|
-
rows: rows.map(({ payload, attempts }) => ({
|
|
213
|
+
rows: rows.map(({ payload, attempts, createdAt }) => ({
|
|
214
214
|
payload,
|
|
215
|
-
attempts
|
|
215
|
+
attempts,
|
|
216
|
+
createdAt
|
|
216
217
|
}))
|
|
217
218
|
};
|
|
218
219
|
}
|
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 { 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 { IOutboxRepository, OutboxBatch, OutboxFailureReason, OutboxRow } from '@/application'\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 claimDueRows(batchSize: number): Promise<OutboxBatch> {\n const token = crypto.randomUUID()\n const now = this.now()\n const rows = await executeDatabase('claim due outbox rows', () =>\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, rows: rows.map(({ payload, attempts }) => ({ payload, attempts })) }\n }\n\n /** Mark only supplied rows that still hold this token as published. */\n async markPublished(claimToken: string, rows: ReadonlyArray<OutboxRow>): Promise<void> {\n if (rows.length === 0) return\n await executeDatabase('mark outbox rows published', () =>\n this.db\n .update(messageOutbox)\n .set({ publishedAt: this.now(), claimToken: null, claimExpiresAt: null })\n .where(\n and(inArray(messageOutbox.id, outboxIds(rows)), eq(messageOutbox.claimToken, claimToken)),\n ),\n )\n }\n\n /** Release supplied rows until `availableAt`; a stale token cannot change a newer claim. */\n async releaseClaim(\n claimToken: string,\n rows: ReadonlyArray<OutboxRow>,\n availableAt: Date,\n ): Promise<void> {\n await executeDatabase('release outbox rows', () =>\n this.db\n .update(messageOutbox)\n .set({\n availableAt: deadline(availableAt.getTime()),\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(inArray(messageOutbox.id, outboxIds(rows)), eq(messageOutbox.claimToken, claimToken)),\n ),\n )\n }\n\n /** Set `failed_at`, keep the full payload, and record the reason with a readable error message. */\n async markFailed(\n claimToken: string,\n rows: ReadonlyArray<OutboxRow>,\n reason: OutboxFailureReason,\n errorMessage: string,\n ): Promise<void> {\n if (rows.length === 0) return\n await executeDatabase('mark outbox rows failed', () =>\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(inArray(messageOutbox.id, outboxIds(rows)), eq(messageOutbox.claimToken, claimToken)),\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(rows: ReadonlyArray<OutboxRow>) {\n return sql`(select value from json_each(${JSON.stringify(rows.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;;;;;;;;ACvEA,eAAsB,gBACpB,WACA,MACA,UAAsD,CAAC,GAC3C;CACZ,MAAM,SAAS,MAAM,OAAO,WAC1B;EACE,KAAK,YAAY,MAAM,KAAK;EAC5B,QAAQ,UAAU,gBAAgB,OAAO,SAAS,KAAK;CACzD,GACA,EACE,OAAO;EACL,OAAO,QAAQ,UAAU,cAAc,IAAI;EAC3C,SAAS;EACT,SAAS;EACT,QAAQ;EACR,cAAc,UACZ,iBAAiB,iBAAiB,MAAM,mBAAmB;CAC/D,EACF,CACF;CACA,IAAI,OAAO,MAAM,GAAG,MAAM,OAAO;CACjC,OAAO,OAAO;AAChB;;;;;;;;ACdA,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;;;;;;;;ACTA,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,+BACjC,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,MAAM,KAAK,KAAK,EAAE,SAAS,gBAAgB;IAAE;IAAS;GAAS,EAAE;EAAE;CACrF;;CAGA,MAAM,cAAc,YAAoB,MAA+C;EACrF,IAAI,KAAK,WAAW,GAAG;EACvB,MAAM,gBAAgB,oCACpB,KAAK,GACF,OAAO,aAAa,CAAC,CACrB,IAAI;GAAE,aAAa,KAAK,IAAI;GAAG,YAAY;GAAM,gBAAgB;EAAK,CAAC,CAAC,CACxE,MACC,IAAI,QAAQ,cAAc,IAAI,UAAU,IAAI,CAAC,GAAG,GAAG,cAAc,YAAY,UAAU,CAAC,CAC1F,CACJ;CACF;;CAGA,MAAM,aACJ,YACA,MACA,aACe;EACf,MAAM,gBAAgB,6BACpB,KAAK,GACF,OAAO,aAAa,CAAC,CACrB,IAAI;GACH,aAAa,SAAS,YAAY,QAAQ,CAAC;GAC3C,YAAY;GACZ,gBAAgB;EAClB,CAAC,CAAC,CACD,MACC,IAAI,QAAQ,cAAc,IAAI,UAAU,IAAI,CAAC,GAAG,GAAG,cAAc,YAAY,UAAU,CAAC,CAC1F,CACJ;CACF;;CAGA,MAAM,WACJ,YACA,MACA,QACA,cACe;EACf,IAAI,KAAK,WAAW,GAAG;EACvB,MAAM,gBAAgB,iCACpB,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,IAAI,QAAQ,cAAc,IAAI,UAAU,IAAI,CAAC,GAAG,GAAG,cAAc,YAAY,UAAU,CAAC,CAC1F,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,MAAgC;CACjD,OAAO,GAAG,gCAAgC,KAAK,UAAU,KAAK,KAAK,QAAQ,IAAI,QAAQ,EAAE,CAAC,EAAE;AAC9F;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 { IOutboxRepository, OutboxBatch, OutboxFailureReason, OutboxRow } from '@/application'\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 claimDueRows(batchSize: number): Promise<OutboxBatch> {\n const token = crypto.randomUUID()\n const now = this.now()\n const rows = await executeDatabase('claim due outbox rows', () =>\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 {\n token,\n rows: rows.map(({ payload, attempts, createdAt }) => ({ payload, attempts, createdAt })),\n }\n }\n\n /** Mark only supplied rows that still hold this token as published. */\n async markPublished(claimToken: string, rows: ReadonlyArray<OutboxRow>): Promise<void> {\n if (rows.length === 0) return\n await executeDatabase('mark outbox rows published', () =>\n this.db\n .update(messageOutbox)\n .set({ publishedAt: this.now(), claimToken: null, claimExpiresAt: null })\n .where(\n and(inArray(messageOutbox.id, outboxIds(rows)), eq(messageOutbox.claimToken, claimToken)),\n ),\n )\n }\n\n /** Release supplied rows until `availableAt`; a stale token cannot change a newer claim. */\n async releaseClaim(\n claimToken: string,\n rows: ReadonlyArray<OutboxRow>,\n availableAt: Date,\n ): Promise<void> {\n await executeDatabase('release outbox rows', () =>\n this.db\n .update(messageOutbox)\n .set({\n availableAt: deadline(availableAt.getTime()),\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(inArray(messageOutbox.id, outboxIds(rows)), eq(messageOutbox.claimToken, claimToken)),\n ),\n )\n }\n\n /** Set `failed_at`, keep the full payload, and record the reason with a readable error message. */\n async markFailed(\n claimToken: string,\n rows: ReadonlyArray<OutboxRow>,\n reason: OutboxFailureReason,\n errorMessage: string,\n ): Promise<void> {\n if (rows.length === 0) return\n await executeDatabase('mark outbox rows failed', () =>\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(inArray(messageOutbox.id, outboxIds(rows)), eq(messageOutbox.claimToken, claimToken)),\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(rows: ReadonlyArray<OutboxRow>) {\n return sql`(select value from json_each(${JSON.stringify(rows.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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"}
|
|
@@ -764,7 +764,7 @@ function classifyFailure(cause) {
|
|
|
764
764
|
const remote = RemoteAppErrorSchema.safeParse(cause);
|
|
765
765
|
if (cause instanceof Error && remote.success) return remote.data.classification;
|
|
766
766
|
if (isAbortError(cause)) return "terminal";
|
|
767
|
-
if (isTransientCloudflareRuntimeError(cause)) return "transient";
|
|
767
|
+
if (isCloudflareRetryableError(cause) || isTransientCloudflareRuntimeError(cause)) return "transient";
|
|
768
768
|
return "unknown";
|
|
769
769
|
}
|
|
770
770
|
const RetryAfterSchema = z.object({ retryAfter: z.number().positive() });
|
|
@@ -972,4 +972,4 @@ function createLogger(module) {
|
|
|
972
972
|
|
|
973
973
|
//#endregion
|
|
974
974
|
export { InvalidEmailIdempotencyKeyError as $, DatabaseError as A, ValidationError as B, Entity as C, defineId as D, defineMessages as E, ValidationProblemErrorSchema as F, RateLimitError as G, ServiceUnavailableError as H, MalformedRequestError as I, BounceTypeSchema as J, AuthenticationError as K, RequestTimeoutError as L, sharedErrors as M, ProblemDetailsSchema as N, notImplemented as O, ValidationProblemDetailsSchema as P, EmailServiceError as Q, ResourceNotFoundError as R, shouldRetryFailure as S, combineMessages as T, NotAuthorizedError as U, InternalServerError as V, StaleSessionError as W, parseBounceType as X, isEmailDeliverable as Y, shouldUpdateBounce as Z, isCloudflareOverloadedError as _, setLoggerErrorHook as a, isAbortError as b, decideRedelivery as c, spreadJitterMs as d, emailIdempotencyKey as et, classifyBoundaryError as f, isCloudflareObjectResetError as g, CLOUDFLARE_TRANSIENT_ERROR_PATTERNS as h, setLogSink as i, defineErrors as j, unreachable as k, exponentialDelayCapMs as l, retryAfterSecondsOf as m, configureLogger as n, MissingConfigurationError as nt, consoleSink as o, classifyFailure as p, RecordEmailEventPayloadSchema as q, createLogger as r, AppError as rt, DEFAULT_REDELIVERY_DELAYS as s, LogLevel as t, EmailAddressSchema as tt, exponentialEqualJitterMs as u, isCloudflareRetryableError as v, DuplicateMessageNameError as w, isTransientTransportError as x, isTransientCloudflareRuntimeError as y, UpgradeRequiredError as z };
|
|
975
|
-
//# sourceMappingURL=core-
|
|
975
|
+
//# sourceMappingURL=core-CGj1ukXV.js.map
|