better-ship 0.7.0 → 0.10.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-DjsQtpgw.js → application-DYfcAy-Q.js} +2 -2
- package/dist/{application-DjsQtpgw.js.map → application-DYfcAy-Q.js.map} +1 -1
- package/dist/application.d.ts +2 -2
- package/dist/application.js +1 -1
- package/dist/better-auth/apple-client-secret.d.ts +11 -4
- package/dist/better-auth/apple-client-secret.d.ts.map +1 -1
- package/dist/better-auth/apple-client-secret.js +39 -11
- package/dist/better-auth/apple-client-secret.js.map +1 -1
- package/dist/better-auth/auth-client.js +1 -1
- package/dist/better-auth/rate-limit.d.ts +21 -2
- package/dist/better-auth/rate-limit.d.ts.map +1 -1
- package/dist/better-auth/rate-limit.js +38 -2
- package/dist/better-auth/rate-limit.js.map +1 -1
- package/dist/better-auth.js +1 -1
- package/dist/cloudflare/auth-secondary-storage.d.ts +1 -1
- package/dist/cloudflare/auth-secondary-storage.js +1 -1
- package/dist/cloudflare/queue.d.ts +175 -0
- package/dist/cloudflare/queue.d.ts.map +1 -0
- package/dist/cloudflare/queue.js +419 -0
- package/dist/cloudflare/queue.js.map +1 -0
- package/dist/cloudflare/scheduled.d.ts +36 -0
- package/dist/cloudflare/scheduled.d.ts.map +1 -0
- package/dist/cloudflare/scheduled.js +37 -0
- package/dist/cloudflare/scheduled.js.map +1 -0
- package/dist/cloudflare.d.ts +3 -3
- package/dist/cloudflare.js +3 -3
- package/dist/{core-Cx5SeyKV.js → core-DC_4pOrI.js} +92 -2
- package/dist/core-DC_4pOrI.js.map +1 -0
- package/dist/core.d.ts +2 -2
- package/dist/core.js +2 -2
- package/dist/{index-BStnggQO.d.ts → index-6XMPJu7u.d.ts} +49 -2
- package/dist/index-6XMPJu7u.d.ts.map +1 -0
- package/dist/{index-DWIRrJMP.d.ts → index-_Cihfq1J.d.ts} +2 -2
- package/dist/{index-DWIRrJMP.d.ts.map → index-_Cihfq1J.d.ts.map} +1 -1
- package/dist/postgres.d.ts +3 -3
- package/dist/postgres.js +3 -3
- package/dist/tanstack.d.ts +18 -7
- package/dist/tanstack.d.ts.map +1 -1
- package/dist/tanstack.js +3 -3
- package/dist/tanstack.js.map +1 -1
- package/dist/{unit-of-work-context-DToDpAkv.js → unit-of-work-context-BFCmU92Z.js} +2 -2
- package/dist/{unit-of-work-context-DToDpAkv.js.map → unit-of-work-context-BFCmU92Z.js.map} +1 -1
- package/dist/{unit-of-work-context-CPz9rdSS.d.ts → unit-of-work-context-BWTJfYPW.d.ts} +3 -3
- package/dist/{unit-of-work-context-CPz9rdSS.d.ts.map → unit-of-work-context-BWTJfYPW.d.ts.map} +1 -1
- package/package.json +3 -6
- package/src/core/backoff.ts +29 -0
- package/src/core/classify-failure.ts +60 -0
- package/src/core/cloudflare-classification.ts +45 -0
- package/src/core/index.ts +14 -0
- package/src/infrastructure/better-auth/apple-client-secret.ts +41 -14
- package/src/infrastructure/better-auth/rate-limit.ts +44 -1
- package/src/infrastructure/cloudflare/queue/index.ts +25 -0
- package/src/infrastructure/cloudflare/queue/queue-client.ts +177 -0
- package/src/infrastructure/cloudflare/queue/queue-consumer.ts +146 -0
- package/src/infrastructure/cloudflare/queue/queue-limits.ts +54 -0
- package/src/infrastructure/cloudflare/queue/queue-message.ts +4 -0
- package/src/infrastructure/cloudflare/queue/queue.errors.ts +105 -0
- package/src/infrastructure/cloudflare/queue/redelivery-policy.ts +64 -0
- package/src/infrastructure/cloudflare/scheduled/index.ts +8 -0
- package/src/infrastructure/cloudflare/scheduled/scheduled-handler.ts +62 -0
- package/src/infrastructure/tanstack/auth.middleware.ts +5 -3
- package/src/infrastructure/tanstack/error.middleware.ts +6 -7
- package/src/infrastructure/tanstack/request-context.ts +11 -0
- package/dist/core-Cx5SeyKV.js.map +0 -1
- package/dist/index-BStnggQO.d.ts.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { E as DatabaseError, T as unreachable, U as AppError, a as createLogger } from "./core-DC_4pOrI.js";
|
|
2
2
|
import { panic } from "better-result";
|
|
3
3
|
|
|
4
4
|
//#region src/application/message-already-processed.error.ts
|
|
@@ -128,4 +128,4 @@ var UnitOfWorkAlreadyActiveError = class extends AppError {
|
|
|
128
128
|
|
|
129
129
|
//#endregion
|
|
130
130
|
export { MessageAlreadyProcessedError as i, UnitOfWork as n, MessageBus as r, UnitOfWorkAlreadyActiveError as t };
|
|
131
|
-
//# sourceMappingURL=application-
|
|
131
|
+
//# sourceMappingURL=application-DYfcAy-Q.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application-DjsQtpgw.js","names":[],"sources":["../src/application/message-already-processed.error.ts","../src/application/message-bus.ts","../src/application/unit-of-work.ts","../src/application/unit-of-work.errors.ts"],"sourcesContent":["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 * Every subscriber runs to completion. One failure is rethrown as is. Several are thrown as\n * one `AggregateError`. A `MessageAlreadyProcessedError` means that subscriber already committed on\n * 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 outcomes = await Promise.allSettled(\n subscriptions.map((subscription) => subscription(event, this.deps)),\n )\n const failures: unknown[] = []\n for (const [subscriber, outcome] of outcomes.entries()) {\n if (outcome.status === 'fulfilled') continue\n if (outcome.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', { event: event.name, id: event.id, subscriber })\n continue\n }\n failures.push(outcome.reason)\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 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 { IOutboxRelay } from './outbox-relay.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`; independent concurrent calls are allowed.\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: IOutboxRelay\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":";;;;;AAGA,IAAa,+BAAb,MAAa,qCAAqC,SAAS;CAK9C;CAJX,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,YACE,AAAS,WACT,SACA;EACA,MAAM,WAAW,UAAU,qBAAqB,OAAO;EAH9C;CAIX;;CAGA,OAAO,KAAK,OAAgB,WAAgE;EAC1F,IAAI,iBAAiB,8BAA8B,OAAO;EAC1D,IAAI,EAAE,iBAAiB,gBAAgB,OAAO;EAC9C,MAAM,UAAU,MAAM;EAStB,IAAI,EAPF,QAAQ,aAAa,OACjB,QAAQ,SAAS,YACjB,QAAQ,UAAU,wBAClB,QAAQ,WAAW,eACnB,QAAQ,SAAS,WACjB,QAAQ,eAAe,wBACvB,QAAQ,oBAAoB,4BAClB,OAAO;EACvB,OAAO,IAAI,6BAA6B,WAAW,EAAE,MAAM,CAAC;CAC9D;AACF;;;;AChBA,MAAM,SAAS,aAAa,aAAa;;;;;AAazC,IAAa,aAAb,MAAkG;CAE7E;CACA;CAFnB,YACE,AAAiB,MACjB,AAAiB,UACjB;EAFiB;EACA;CAChB;;CAGH,MAAM,OACJ,SAC6C;EAE7C,OAAQ,MAAM,KAAK,SAAS,OAAO;CACrC;CAGA,AAAQ,SAAS,SAA0C;EACzD,IAAI,QAAQ,SAAS,WAAW,OAAO,KAAK,cAAc,OAAO;EACjE,IAAI,QAAQ,SAAS,SAAS,OAAO,KAAK,YAAY,OAAO;EAC7D,IAAI,QAAQ,SAAS,SAAS,OAAO,KAAK,YAAY,OAAO;EAE7D,OAAO,YAAY,OAAO;CAC5B;CAGA,AAAQ,cAAc,SAA0C;EAE9D,MAAM,UAAU,KAAK,SAAS,SAAS,QAAQ;EAC/C,IAAI,YAAY,QAAW,OAAO,MAAM,0BAA0B,QAAQ,MAAM;EAChF,OAAO,MAAM,oBAAoB;GAAE,MAAM,QAAQ;GAAM,IAAI,QAAQ;EAAG,CAAC;EACvE,OAAO,QAAQ,SAAS,KAAK,IAAI;CACnC;CAGA,AAAQ,YAAY,OAAsC;EACxD,MAAM,UAAU,KAAK,SAAS,QAAQ,MAAM;EAC5C,IAAI,YAAY,QAAW,OAAO,MAAM,wBAAwB,MAAM,MAAM;EAC5E,OAAO,QAAQ,OAAO,KAAK,IAAI;CACjC;;;;;;CAOA,MAAc,YAAY,OAAmC;EAC3D,MAAM,gBAAgB,KAAK,SAAS,OAAO,MAAM,SAAS,CAAC;EAC3D,MAAM,WAAW,MAAM,QAAQ,WAC7B,cAAc,KAAK,iBAAiB,aAAa,OAAO,KAAK,IAAI,CAAC,CACpE;EACA,MAAM,WAAsB,CAAC;EAC7B,KAAK,MAAM,CAAC,YAAY,YAAY,SAAS,QAAQ,GAAG;GACtD,IAAI,QAAQ,WAAW,aAAa;GACpC,IAAI,QAAQ,kBAAkB,8BAA8B;IAE1D,OAAO,KAAK,gCAAgC;KAAE,OAAO,MAAM;KAAM,IAAI,MAAM;KAAI;IAAW,CAAC;IAC3F;GACF;GACA,SAAS,KAAK,QAAQ,MAAM;EAC9B;EACA,IAAI,SAAS,WAAW,GAAG,MAAM,SAAS;EAC1C,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,eAAe,UAAU,GAAG,MAAM,KAAK,IAAI,SAAS,OAAO,oBAAoB;CAE7F;AACF;;;;;AC/DA,IAAa,aAAb,MAAiE;CAClC;CAA7B,YAAY,AAAiB,MAAiC;EAAjC;CAAkC;CAE/D,MAAM,IAAO,WAAsB,MAAgD;EACjF,MAAM,EAAE,aAAa,cAAc,oBAAoB,gBAAgB,UAAU,KAAK;EACtF,IAAI,UAAU;EAEd,IAAI;EACJ,IAAI;GACF,QAAQ,MAAM,YAAY,YAAY,OAAO,OAAO;IAClD,MAAM,QAAQ,aAAa,EAAE;IAC7B,MAAM,MAAM,MAAM,SAAS;IAE3B,MAAM,SAAS,MAAM,KADP,mBAAmB,EACH,CAAC;IAC/B,MAAM,UAAU,eAAe,QAAQ;IACvC,MAAM,MAAM,cAAc,OAAO;IACjC,UAAU,QAAQ;IAClB,OAAO;GACT,CAAC;EACH,SAAS,OAAO;GACd,MAAM,6BAA6B,KAAK,OAAO,SAAS,KAAK;EAC/D;EAEA,IAAI,UAAU,GAAG,MAAM,KAAK;EAC5B,OAAO;CACT;AACF;;;;;ACnDA,IAAa,+BAAb,cAAkD,SAAS;CACzD,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,cAAc;EACZ,MAAM,kCAAkC;CAC1C;AACF"}
|
|
1
|
+
{"version":3,"file":"application-DYfcAy-Q.js","names":[],"sources":["../src/application/message-already-processed.error.ts","../src/application/message-bus.ts","../src/application/unit-of-work.ts","../src/application/unit-of-work.errors.ts"],"sourcesContent":["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 * Every subscriber runs to completion. One failure is rethrown as is. Several are thrown as\n * one `AggregateError`. A `MessageAlreadyProcessedError` means that subscriber already committed on\n * 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 outcomes = await Promise.allSettled(\n subscriptions.map((subscription) => subscription(event, this.deps)),\n )\n const failures: unknown[] = []\n for (const [subscriber, outcome] of outcomes.entries()) {\n if (outcome.status === 'fulfilled') continue\n if (outcome.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', { event: event.name, id: event.id, subscriber })\n continue\n }\n failures.push(outcome.reason)\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 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 { IOutboxRelay } from './outbox-relay.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`; independent concurrent calls are allowed.\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: IOutboxRelay\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":";;;;;AAGA,IAAa,+BAAb,MAAa,qCAAqC,SAAS;CAK9C;CAJX,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,YACE,AAAS,WACT,SACA;EACA,MAAM,WAAW,UAAU,qBAAqB,OAAO;EAH9C;CAIX;;CAGA,OAAO,KAAK,OAAgB,WAAgE;EAC1F,IAAI,iBAAiB,8BAA8B,OAAO;EAC1D,IAAI,EAAE,iBAAiB,gBAAgB,OAAO;EAC9C,MAAM,UAAU,MAAM;EAStB,IAAI,EAPF,QAAQ,aAAa,OACjB,QAAQ,SAAS,YACjB,QAAQ,UAAU,wBAClB,QAAQ,WAAW,eACnB,QAAQ,SAAS,WACjB,QAAQ,eAAe,wBACvB,QAAQ,oBAAoB,4BAClB,OAAO;EACvB,OAAO,IAAI,6BAA6B,WAAW,EAAE,MAAM,CAAC;CAC9D;AACF;;;;AChBA,MAAM,SAAS,aAAa,aAAa;;;;;AAazC,IAAa,aAAb,MAAkG;CAE7E;CACA;CAFnB,YACE,AAAiB,MACjB,AAAiB,UACjB;EAFiB;EACA;CAChB;;CAGH,MAAM,OACJ,SAC6C;EAE7C,OAAQ,MAAM,KAAK,SAAS,OAAO;CACrC;CAGA,AAAQ,SAAS,SAA0C;EACzD,IAAI,QAAQ,SAAS,WAAW,OAAO,KAAK,cAAc,OAAO;EACjE,IAAI,QAAQ,SAAS,SAAS,OAAO,KAAK,YAAY,OAAO;EAC7D,IAAI,QAAQ,SAAS,SAAS,OAAO,KAAK,YAAY,OAAO;EAE7D,OAAO,YAAY,OAAO;CAC5B;CAGA,AAAQ,cAAc,SAA0C;EAE9D,MAAM,UAAU,KAAK,SAAS,SAAS,QAAQ;EAC/C,IAAI,YAAY,QAAW,OAAO,MAAM,0BAA0B,QAAQ,MAAM;EAChF,OAAO,MAAM,oBAAoB;GAAE,MAAM,QAAQ;GAAM,IAAI,QAAQ;EAAG,CAAC;EACvE,OAAO,QAAQ,SAAS,KAAK,IAAI;CACnC;CAGA,AAAQ,YAAY,OAAsC;EACxD,MAAM,UAAU,KAAK,SAAS,QAAQ,MAAM;EAC5C,IAAI,YAAY,QAAW,OAAO,MAAM,wBAAwB,MAAM,MAAM;EAC5E,OAAO,QAAQ,OAAO,KAAK,IAAI;CACjC;;;;;;CAOA,MAAc,YAAY,OAAmC;EAC3D,MAAM,gBAAgB,KAAK,SAAS,OAAO,MAAM,SAAS,CAAC;EAC3D,MAAM,WAAW,MAAM,QAAQ,WAC7B,cAAc,KAAK,iBAAiB,aAAa,OAAO,KAAK,IAAI,CAAC,CACpE;EACA,MAAM,WAAsB,CAAC;EAC7B,KAAK,MAAM,CAAC,YAAY,YAAY,SAAS,QAAQ,GAAG;GACtD,IAAI,QAAQ,WAAW,aAAa;GACpC,IAAI,QAAQ,kBAAkB,8BAA8B;IAE1D,OAAO,KAAK,gCAAgC;KAAE,OAAO,MAAM;KAAM,IAAI,MAAM;KAAI;IAAW,CAAC;IAC3F;GACF;GACA,SAAS,KAAK,QAAQ,MAAM;EAC9B;EACA,IAAI,SAAS,WAAW,GAAG,MAAM,SAAS;EAC1C,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,eAAe,UAAU,GAAG,MAAM,KAAK,IAAI,SAAS,OAAO,oBAAoB;CAE7F;AACF;;;;;AC/DA,IAAa,aAAb,MAAiE;CAClC;CAA7B,YAAY,AAAiB,MAAiC;EAAjC;CAAkC;CAE/D,MAAM,IAAO,WAAsB,MAAgD;EACjF,MAAM,EAAE,aAAa,cAAc,oBAAoB,gBAAgB,UAAU,KAAK;EACtF,IAAI,UAAU;EAEd,IAAI;EACJ,IAAI;GACF,QAAQ,MAAM,YAAY,YAAY,OAAO,OAAO;IAClD,MAAM,QAAQ,aAAa,EAAE;IAC7B,MAAM,MAAM,MAAM,SAAS;IAE3B,MAAM,SAAS,MAAM,KADP,mBAAmB,EACH,CAAC;IAC/B,MAAM,UAAU,eAAe,QAAQ;IACvC,MAAM,MAAM,cAAc,OAAO;IACjC,UAAU,QAAQ;IAClB,OAAO;GACT,CAAC;EACH,SAAS,OAAO;GACd,MAAM,6BAA6B,KAAK,OAAO,SAAS,KAAK;EAC/D;EAEA,IAAI,UAAU,GAAG,MAAM,KAAK;EAC5B,OAAO;CACT;AACF;;;;;ACnDA,IAAa,+BAAb,cAAkD,SAAS;CACzD,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,cAAc;EACZ,MAAM,kCAAkC;CAC1C;AACF"}
|
package/dist/application.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as RegistryMessage, a as UnitOfWorkDeps, b as EventHandler, c as IOutboxRelay, d as CommandRegistry, f as EventRegistry, g as QueryRegistry, h as MessageResult, i as UnitOfWork, l as IMessageBus, m as MessageRegistry, n as MessageAlreadyProcessedError, o as IEventCollector, p as Handlers, r as IUnitOfWork, s as ITransaction, t as UnitOfWorkAlreadyActiveError, u as MessageBus, v as IMessageStore, x as QueryHandler, y as CommandHandler } from "./index-
|
|
1
|
+
import { G as DomainMessage, K as DomainQuery, U as DomainCommand, W as DomainEvent, q as MessageId } from "./index-6XMPJu7u.js";
|
|
2
|
+
import { _ as RegistryMessage, a as UnitOfWorkDeps, b as EventHandler, c as IOutboxRelay, d as CommandRegistry, f as EventRegistry, g as QueryRegistry, h as MessageResult, i as UnitOfWork, l as IMessageBus, m as MessageRegistry, n as MessageAlreadyProcessedError, o as IEventCollector, p as Handlers, r as IUnitOfWork, s as ITransaction, t as UnitOfWorkAlreadyActiveError, u as MessageBus, v as IMessageStore, x as QueryHandler, y as CommandHandler } from "./index-_Cihfq1J.js";
|
|
3
3
|
export { type CommandHandler, type CommandRegistry, type DomainCommand, type DomainEvent, type DomainMessage, type DomainQuery, type EventHandler, type EventRegistry, type Handlers, type IEventCollector, type IMessageBus, type IMessageStore, type IOutboxRelay, type ITransaction, type IUnitOfWork, MessageAlreadyProcessedError, MessageBus, type MessageId, type MessageRegistry, type MessageResult, type QueryHandler, type QueryRegistry, type RegistryMessage, UnitOfWork, UnitOfWorkAlreadyActiveError, type UnitOfWorkDeps };
|
package/dist/application.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as MessageAlreadyProcessedError, n as UnitOfWork, r as MessageBus, t as UnitOfWorkAlreadyActiveError } from "./application-
|
|
1
|
+
import { i as MessageAlreadyProcessedError, n as UnitOfWork, r as MessageBus, t as UnitOfWorkAlreadyActiveError } from "./application-DYfcAy-Q.js";
|
|
2
2
|
|
|
3
3
|
export { MessageAlreadyProcessedError, MessageBus, UnitOfWork, UnitOfWorkAlreadyActiveError };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { kt as AppError } from "../index-6XMPJu7u.js";
|
|
2
2
|
//#region src/infrastructure/better-auth/apple-client-secret.d.ts
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Apple's issuer and OAuth callback origin, and the `aud` claim of the client secret. Include it in
|
|
5
|
+
* trustedOrigins when enabling Apple. Source: the same Apple page as `SECRET_LIFETIME_SECONDS`.
|
|
6
|
+
*/
|
|
4
7
|
export declare const APPLE_ORIGIN = "https://appleid.apple.com";
|
|
5
8
|
/** Apple credentials used to sign a client secret. Ported from Porte's apple-client-secret.ts. */
|
|
6
9
|
export type AppleKeyConfig = {
|
|
@@ -15,7 +18,11 @@ export declare class AppleClientSecretError extends AppError {
|
|
|
15
18
|
readonly classification = "terminal";
|
|
16
19
|
constructor(cause: unknown);
|
|
17
20
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Sign Apple's six-month client secret synchronously, so Better Auth options can be built without awaiting it.
|
|
23
|
+
* Signing uses `node:crypto` as Typist does, so the Worker needs the `nodejs_compat` flag.
|
|
24
|
+
* Invalid private keys return an empty secret and log once per isolate, as in Typist.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generateAppleClientSecret(config: AppleKeyConfig): string;
|
|
20
27
|
//#endregion
|
|
21
28
|
//# sourceMappingURL=apple-client-secret.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apple-client-secret.d.ts","names":[],"sources":["../../src/infrastructure/better-auth/apple-client-secret.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"apple-client-secret.d.ts","names":[],"sources":["../../src/infrastructure/better-auth/apple-client-secret.ts"],"mappings":";;;;;;qBAkBa;;YAGD;WACD;WACA;WACA;WACA;;;qBAIE,+BAA+B;WACjC;WACA;EAET,YAAY;;;;;;;wBAiBE,0BAA0B,QAAQ"}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { U as AppError, a as createLogger } from "../core-DC_4pOrI.js";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { createPrivateKey, sign } from "node:crypto";
|
|
3
4
|
|
|
4
5
|
//#region src/infrastructure/better-auth/apple-client-secret.ts
|
|
5
6
|
const logger = createLogger("auth");
|
|
6
7
|
let appleKeyFailureLogged = false;
|
|
7
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Apple caps `exp` at 15,777,000 seconds after `iat`; 180 days stays under it. Source:
|
|
10
|
+
* https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret
|
|
11
|
+
*/
|
|
12
|
+
const SECRET_LIFETIME_SECONDS = 15552e3;
|
|
13
|
+
/**
|
|
14
|
+
* Apple's issuer and OAuth callback origin, and the `aud` claim of the client secret. Include it in
|
|
15
|
+
* trustedOrigins when enabling Apple. Source: the same Apple page as `SECRET_LIFETIME_SECONDS`.
|
|
16
|
+
*/
|
|
8
17
|
const APPLE_ORIGIN = "https://appleid.apple.com";
|
|
9
18
|
/** Apple signing failed. The application's error boundary owns reporting. */
|
|
10
19
|
var AppleClientSecretError = class extends AppError {
|
|
@@ -14,11 +23,18 @@ var AppleClientSecretError = class extends AppError {
|
|
|
14
23
|
super("Apple client secret signing failed", { cause });
|
|
15
24
|
}
|
|
16
25
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
function base64UrlJson(segment) {
|
|
27
|
+
return Buffer.from(JSON.stringify(segment)).toString("base64url");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Sign Apple's six-month client secret synchronously, so Better Auth options can be built without awaiting it.
|
|
31
|
+
* Signing uses `node:crypto` as Typist does, so the Worker needs the `nodejs_compat` flag.
|
|
32
|
+
* Invalid private keys return an empty secret and log once per isolate, as in Typist.
|
|
33
|
+
*/
|
|
34
|
+
function generateAppleClientSecret(config) {
|
|
35
|
+
const key = config.applePrivateKey.replace(/\\n/g, "\n");
|
|
20
36
|
try {
|
|
21
|
-
key
|
|
37
|
+
createPrivateKey(key);
|
|
22
38
|
} catch (error) {
|
|
23
39
|
if (!appleKeyFailureLogged) {
|
|
24
40
|
appleKeyFailureLogged = true;
|
|
@@ -29,11 +45,23 @@ async function generateAppleClientSecret(config) {
|
|
|
29
45
|
}
|
|
30
46
|
return "";
|
|
31
47
|
}
|
|
48
|
+
const issuedAt = Math.floor(Date.now() / 1e3);
|
|
49
|
+
const input = `${base64UrlJson({
|
|
50
|
+
alg: "ES256",
|
|
51
|
+
kid: config.appleKeyId
|
|
52
|
+
})}.${base64UrlJson({
|
|
53
|
+
iss: config.appleTeamId,
|
|
54
|
+
sub: config.appleClientId,
|
|
55
|
+
aud: APPLE_ORIGIN,
|
|
56
|
+
iat: issuedAt,
|
|
57
|
+
exp: issuedAt + SECRET_LIFETIME_SECONDS
|
|
58
|
+
})}`;
|
|
32
59
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
60
|
+
const signature = sign("sha256", Buffer.from(input), {
|
|
61
|
+
key,
|
|
62
|
+
dsaEncoding: "ieee-p1363"
|
|
63
|
+
});
|
|
64
|
+
return `${input}.${Buffer.from(signature).toString("base64url")}`;
|
|
37
65
|
} catch (cause) {
|
|
38
66
|
throw new AppleClientSecretError(cause);
|
|
39
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apple-client-secret.js","names":[],"sources":["../../src/infrastructure/better-auth/apple-client-secret.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"apple-client-secret.js","names":[],"sources":["../../src/infrastructure/better-auth/apple-client-secret.ts"],"sourcesContent":["import { Buffer } from 'node:buffer'\nimport { createPrivateKey, sign } from 'node:crypto'\n\nimport { AppError, createLogger } from '@/core'\n\nconst logger = createLogger('auth')\n// Typist reports an invalid key once per isolate because session checks repeatedly construct auth options.\nlet appleKeyFailureLogged = false\n/**\n * Apple caps `exp` at 15,777,000 seconds after `iat`; 180 days stays under it. Source:\n * https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret\n */\nconst SECRET_LIFETIME_SECONDS = 180 * 24 * 60 * 60\n\n/**\n * Apple's issuer and OAuth callback origin, and the `aud` claim of the client secret. Include it in\n * trustedOrigins when enabling Apple. Source: the same Apple page as `SECRET_LIFETIME_SECONDS`.\n */\nexport const APPLE_ORIGIN = 'https://appleid.apple.com'\n\n/** Apple credentials used to sign a client secret. Ported from Porte's apple-client-secret.ts. */\nexport type AppleKeyConfig = {\n readonly appleClientId: string\n readonly appleTeamId: string\n readonly appleKeyId: string\n readonly applePrivateKey: string\n}\n\n/** Apple signing failed. The application's error boundary owns reporting. */\nexport class AppleClientSecretError extends AppError {\n readonly _tag = 'AppleClientSecretError'\n readonly classification = 'terminal'\n\n constructor(cause: unknown) {\n super('Apple client secret signing failed', { cause })\n }\n}\n\n/** One JWS segment before encoding: the protected header or the claims. */\ntype JwsSegment = Readonly<Record<string, string | number>>\n\nfunction base64UrlJson(segment: JwsSegment): string {\n return Buffer.from(JSON.stringify(segment)).toString('base64url')\n}\n\n/**\n * Sign Apple's six-month client secret synchronously, so Better Auth options can be built without awaiting it.\n * Signing uses `node:crypto` as Typist does, so the Worker needs the `nodejs_compat` flag.\n * Invalid private keys return an empty secret and log once per isolate, as in Typist.\n */\nexport function generateAppleClientSecret(config: AppleKeyConfig): string {\n const key = config.applePrivateKey.replace(/\\\\n/g, '\\n')\n // Validation only: workerd's sign() takes the PEM string, not the KeyObject.\n try {\n createPrivateKey(key)\n } catch (error) {\n if (!appleKeyFailureLogged) {\n appleKeyFailureLogged = true\n logger.error('apple_private_key_invalid', { error, details: { keyId: config.appleKeyId } })\n }\n return ''\n }\n const issuedAt = Math.floor(Date.now() / 1000)\n const header = base64UrlJson({ alg: 'ES256', kid: config.appleKeyId })\n const claims = base64UrlJson({\n iss: config.appleTeamId,\n sub: config.appleClientId,\n aud: APPLE_ORIGIN,\n iat: issuedAt,\n exp: issuedAt + SECRET_LIFETIME_SECONDS,\n })\n const input = `${header}.${claims}`\n try {\n // A JWS carries the raw r||s signature; OpenSSL emits DER unless told otherwise.\n const signature = sign('sha256', Buffer.from(input), { key, dsaEncoding: 'ieee-p1363' })\n // TypeScript 7 types the returned buffer without an encoding parameter on toString.\n return `${input}.${Buffer.from(signature).toString('base64url')}`\n } catch (cause) {\n throw new AppleClientSecretError(cause)\n }\n}\n"],"mappings":";;;;;AAKA,MAAM,SAAS,aAAa,MAAM;AAElC,IAAI,wBAAwB;;;;;AAK5B,MAAM,0BAA0B;;;;;AAMhC,MAAa,eAAe;;AAW5B,IAAa,yBAAb,cAA4C,SAAS;CACnD,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,YAAY,OAAgB;EAC1B,MAAM,sCAAsC,EAAE,MAAM,CAAC;CACvD;AACF;AAKA,SAAS,cAAc,SAA6B;CAClD,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC,CAAC,CAAC,SAAS,WAAW;AAClE;;;;;;AAOA,SAAgB,0BAA0B,QAAgC;CACxE,MAAM,MAAM,OAAO,gBAAgB,QAAQ,QAAQ,IAAI;CAEvD,IAAI;EACF,iBAAiB,GAAG;CACtB,SAAS,OAAO;EACd,IAAI,CAAC,uBAAuB;GAC1B,wBAAwB;GACxB,OAAO,MAAM,6BAA6B;IAAE;IAAO,SAAS,EAAE,OAAO,OAAO,WAAW;GAAE,CAAC;EAC5F;EACA,OAAO;CACT;CACA,MAAM,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;CAS7C,MAAM,QAAQ,GARC,cAAc;EAAE,KAAK;EAAS,KAAK,OAAO;CAAW,CAQ9C,EAAE,GAPT,cAAc;EAC3B,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK;EACL,KAAK;EACL,KAAK,WAAW;CAClB,CACgC;CAChC,IAAI;EAEF,MAAM,YAAY,KAAK,UAAU,OAAO,KAAK,KAAK,GAAG;GAAE;GAAK,aAAa;EAAa,CAAC;EAEvF,OAAO,GAAG,MAAM,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,SAAS,WAAW;CAChE,SAAS,OAAO;EACd,MAAM,IAAI,uBAAuB,KAAK;CACxC;AACF"}
|
|
@@ -1,12 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BetterAuthRateLimitStorage } from "better-auth";
|
|
1
|
+
import { kt as AppError } from "../index-6XMPJu7u.js";
|
|
2
|
+
import { BetterAuthOptions, BetterAuthRateLimitStorage } from "better-auth";
|
|
3
3
|
//#region src/infrastructure/better-auth/rate-limit.d.ts
|
|
4
|
+
/** A rule Better Auth passes to storage: a window in seconds and the requests it allows. */
|
|
5
|
+
export type AuthRateLimitRule = {
|
|
6
|
+
readonly window: number;
|
|
7
|
+
readonly max: number;
|
|
8
|
+
};
|
|
4
9
|
/** Each rule must match its binding's deployed limit and period. Cloudflare accepts only 10 or 60 seconds. */
|
|
5
10
|
export type AuthRateLimitBinding = {
|
|
6
11
|
readonly binding: Pick<RateLimit, 'limit'>;
|
|
7
12
|
readonly window: 10 | 60;
|
|
8
13
|
readonly max: number;
|
|
9
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Better Auth's built-in rules, which it does not export. Sign-in, sign-up, change-password, and
|
|
17
|
+
* change-email paths get 3 requests per 10 seconds. Password reset, verification email, forget-password,
|
|
18
|
+
* and email OTP send paths get 3 per 60 seconds. A package test pins both against the installed version.
|
|
19
|
+
* Source: `getDefaultSpecialRules` in
|
|
20
|
+
* https://github.com/better-auth/better-auth/blob/v1.7.4/packages/better-auth/src/api/rate-limiter/index.ts
|
|
21
|
+
*/
|
|
22
|
+
export declare const BUILT_IN_AUTH_RATE_LIMIT_RULES: readonly AuthRateLimitRule[];
|
|
23
|
+
/**
|
|
24
|
+
* Every rule the configured instance can pass to storage, deduplicated: the general rule, the built-in
|
|
25
|
+
* rules, each plugin's rules, and static custom rules. Function-valued custom rules decide per request
|
|
26
|
+
* and are not listed. Disabled rate limiting returns no rules.
|
|
27
|
+
*/
|
|
28
|
+
export declare function authRateLimitRules(options: Pick<BetterAuthOptions, 'rateLimit' | 'plugins'>): readonly AuthRateLimitRule[];
|
|
10
29
|
/** A binding failure with no known retry classification. The cause preserves the original failure. */
|
|
11
30
|
export declare class AuthRateLimitError extends AppError {
|
|
12
31
|
readonly _tag = "AuthRateLimitError";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","names":[],"sources":["../../src/infrastructure/better-auth/rate-limit.ts"],"mappings":";;;;YAKY;WACD,SAAS,KAAK;WACd;WACA
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","names":[],"sources":["../../src/infrastructure/better-auth/rate-limit.ts"],"mappings":";;;;YAKY;WACD;WACA;;;YAIC;WACD,SAAS,KAAK;WACd;WACA;;;;;;;;;qBAUE,yCAAyC;;;;;;wBAUtC,mBACd,SAAS,KAAK,uDACJ;;qBAmBC,2BAA2B;WAC7B;WACA;EAET,YAAY;;;qBAMD,0CAA0C;WAK1C;WACA;WALF;WACA;EAET,YACW,gBACA;;;YAOD;WACD;;;;;;wBAOK,2BACd,mBAAmB,0BACjB,gBAAyB,uBAC1B"}
|
|
@@ -1,6 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { U as AppError, a as createLogger } from "../core-DC_4pOrI.js";
|
|
2
2
|
|
|
3
3
|
//#region src/infrastructure/better-auth/rate-limit.ts
|
|
4
|
+
/**
|
|
5
|
+
* Better Auth's built-in rules, which it does not export. Sign-in, sign-up, change-password, and
|
|
6
|
+
* change-email paths get 3 requests per 10 seconds. Password reset, verification email, forget-password,
|
|
7
|
+
* and email OTP send paths get 3 per 60 seconds. A package test pins both against the installed version.
|
|
8
|
+
* Source: `getDefaultSpecialRules` in
|
|
9
|
+
* https://github.com/better-auth/better-auth/blob/v1.7.4/packages/better-auth/src/api/rate-limiter/index.ts
|
|
10
|
+
*/
|
|
11
|
+
const BUILT_IN_AUTH_RATE_LIMIT_RULES = [{
|
|
12
|
+
window: 10,
|
|
13
|
+
max: 3
|
|
14
|
+
}, {
|
|
15
|
+
window: 60,
|
|
16
|
+
max: 3
|
|
17
|
+
}];
|
|
18
|
+
/**
|
|
19
|
+
* Every rule the configured instance can pass to storage, deduplicated: the general rule, the built-in
|
|
20
|
+
* rules, each plugin's rules, and static custom rules. Function-valued custom rules decide per request
|
|
21
|
+
* and are not listed. Disabled rate limiting returns no rules.
|
|
22
|
+
*/
|
|
23
|
+
function authRateLimitRules(options) {
|
|
24
|
+
const rateLimit = options.rateLimit;
|
|
25
|
+
if (rateLimit?.enabled === false) return [];
|
|
26
|
+
const rules = [{
|
|
27
|
+
window: rateLimit?.window ?? 10,
|
|
28
|
+
max: rateLimit?.max ?? 100
|
|
29
|
+
}, ...BUILT_IN_AUTH_RATE_LIMIT_RULES];
|
|
30
|
+
for (const plugin of options.plugins ?? []) for (const rule of plugin.rateLimit ?? []) rules.push({
|
|
31
|
+
window: rule.window,
|
|
32
|
+
max: rule.max
|
|
33
|
+
});
|
|
34
|
+
for (const rule of Object.values(rateLimit?.customRules ?? {})) if (rule !== false && !(rule instanceof Function)) rules.push({
|
|
35
|
+
window: rule.window,
|
|
36
|
+
max: rule.max
|
|
37
|
+
});
|
|
38
|
+
return [...new Map(rules.map((rule) => [`${rule.window}:${rule.max}`, rule])).values()];
|
|
39
|
+
}
|
|
4
40
|
/** A binding failure with no known retry classification. The cause preserves the original failure. */
|
|
5
41
|
var AuthRateLimitError = class extends AppError {
|
|
6
42
|
_tag = "AuthRateLimitError";
|
|
@@ -55,5 +91,5 @@ function createAuthRateLimitStorage(bindings, { failureMode = "allow" } = {}) {
|
|
|
55
91
|
}
|
|
56
92
|
|
|
57
93
|
//#endregion
|
|
58
|
-
export { AuthRateLimitError, UnsupportedAuthRateLimitRuleError, createAuthRateLimitStorage };
|
|
94
|
+
export { AuthRateLimitError, BUILT_IN_AUTH_RATE_LIMIT_RULES, UnsupportedAuthRateLimitRuleError, authRateLimitRules, createAuthRateLimitStorage };
|
|
59
95
|
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.js","names":[],"sources":["../../src/infrastructure/better-auth/rate-limit.ts"],"sourcesContent":["import type { BetterAuthRateLimitStorage } from 'better-auth'\n\nimport { AppError, createLogger } from '@/core'\n\n/** Each rule must match its binding's deployed limit and period. Cloudflare accepts only 10 or 60 seconds. */\nexport type AuthRateLimitBinding = {\n readonly binding: Pick<RateLimit, 'limit'>\n readonly window: 10 | 60\n readonly max: number\n}\n\n/** A binding failure with no known retry classification. The cause preserves the original failure. */\nexport class AuthRateLimitError extends AppError {\n readonly _tag = 'AuthRateLimitError'\n readonly classification = 'unknown'\n\n constructor(cause: unknown) {\n super('Authentication rate limiter is unavailable', { cause })\n }\n}\n\n/** No configured binding matches this Better Auth rule. Outage policy cannot bypass a configuration error. */\nexport class UnsupportedAuthRateLimitRuleError extends AppError {\n readonly _tag = 'UnsupportedAuthRateLimitRuleError'\n readonly classification = 'terminal'\n\n constructor(\n readonly window: number,\n readonly max: number,\n ) {\n super('No Cloudflare binding supports the authentication rate limit rule')\n }\n}\n\n/** Typist allows requests during binding outages. Select block to propagate an error instead. */\nexport type AuthRateLimitOptions = {\n readonly failureMode?: 'allow' | 'block'\n}\n\n/**\n * Port Typist and Porte's binding adapters with explicit rule matching and configurable outage behavior.\n * Bindings enforce approximate limits per Cloudflare location; retryAfter is a conservative full window.\n */\nexport function createAuthRateLimitStorage(\n bindings: readonly AuthRateLimitBinding[],\n { failureMode = 'allow' }: AuthRateLimitOptions = {},\n): BetterAuthRateLimitStorage {\n const logger = createLogger('auth-rate-limit')\n return {\n async consume(key, rule) {\n const configured = bindings.find(\n (entry) => entry.window === rule.window && entry.max === rule.max,\n )\n if (configured === undefined)\n throw new UnsupportedAuthRateLimitRuleError(rule.window, rule.max)\n try {\n const { success } = await configured.binding.limit({ key })\n return { allowed: success, retryAfter: success ? null : rule.window }\n } catch (cause) {\n if (failureMode === 'block') throw new AuthRateLimitError(cause)\n logger.error('rate_limit_binding_failed', {\n error: cause,\n details: { window: rule.window, max: rule.max, failureMode },\n })\n return { allowed: true, retryAfter: null }\n }\n },\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"rate-limit.js","names":[],"sources":["../../src/infrastructure/better-auth/rate-limit.ts"],"sourcesContent":["import type { BetterAuthOptions, BetterAuthRateLimitStorage } from 'better-auth'\n\nimport { AppError, createLogger } from '@/core'\n\n/** A rule Better Auth passes to storage: a window in seconds and the requests it allows. */\nexport type AuthRateLimitRule = {\n readonly window: number\n readonly max: number\n}\n\n/** Each rule must match its binding's deployed limit and period. Cloudflare accepts only 10 or 60 seconds. */\nexport type AuthRateLimitBinding = {\n readonly binding: Pick<RateLimit, 'limit'>\n readonly window: 10 | 60\n readonly max: number\n}\n\n/**\n * Better Auth's built-in rules, which it does not export. Sign-in, sign-up, change-password, and\n * change-email paths get 3 requests per 10 seconds. Password reset, verification email, forget-password,\n * and email OTP send paths get 3 per 60 seconds. A package test pins both against the installed version.\n * Source: `getDefaultSpecialRules` in\n * https://github.com/better-auth/better-auth/blob/v1.7.4/packages/better-auth/src/api/rate-limiter/index.ts\n */\nexport const BUILT_IN_AUTH_RATE_LIMIT_RULES: readonly AuthRateLimitRule[] = [\n { window: 10, max: 3 },\n { window: 60, max: 3 },\n]\n\n/**\n * Every rule the configured instance can pass to storage, deduplicated: the general rule, the built-in\n * rules, each plugin's rules, and static custom rules. Function-valued custom rules decide per request\n * and are not listed. Disabled rate limiting returns no rules.\n */\nexport function authRateLimitRules(\n options: Pick<BetterAuthOptions, 'rateLimit' | 'plugins'>,\n): readonly AuthRateLimitRule[] {\n const rateLimit = options.rateLimit\n if (rateLimit?.enabled === false) return []\n // Better Auth's own defaults when the general rule is not set: window 10, max 100. Source:\n // https://github.com/better-auth/better-auth/blob/v1.7.4/packages/better-auth/src/context/create-context.ts\n const rules: AuthRateLimitRule[] = [\n { window: rateLimit?.window ?? 10, max: rateLimit?.max ?? 100 },\n ...BUILT_IN_AUTH_RATE_LIMIT_RULES,\n ]\n for (const plugin of options.plugins ?? [])\n for (const rule of plugin.rateLimit ?? []) rules.push({ window: rule.window, max: rule.max })\n for (const rule of Object.values(rateLimit?.customRules ?? {}))\n if (rule !== false && !(rule instanceof Function))\n rules.push({ window: rule.window, max: rule.max })\n const unique = new Map(rules.map((rule) => [`${rule.window}:${rule.max}`, rule]))\n return [...unique.values()]\n}\n\n/** A binding failure with no known retry classification. The cause preserves the original failure. */\nexport class AuthRateLimitError extends AppError {\n readonly _tag = 'AuthRateLimitError'\n readonly classification = 'unknown'\n\n constructor(cause: unknown) {\n super('Authentication rate limiter is unavailable', { cause })\n }\n}\n\n/** No configured binding matches this Better Auth rule. Outage policy cannot bypass a configuration error. */\nexport class UnsupportedAuthRateLimitRuleError extends AppError {\n readonly _tag = 'UnsupportedAuthRateLimitRuleError'\n readonly classification = 'terminal'\n\n constructor(\n readonly window: number,\n readonly max: number,\n ) {\n super('No Cloudflare binding supports the authentication rate limit rule')\n }\n}\n\n/** Typist allows requests during binding outages. Select block to propagate an error instead. */\nexport type AuthRateLimitOptions = {\n readonly failureMode?: 'allow' | 'block'\n}\n\n/**\n * Port Typist and Porte's binding adapters with explicit rule matching and configurable outage behavior.\n * Bindings enforce approximate limits per Cloudflare location; retryAfter is a conservative full window.\n */\nexport function createAuthRateLimitStorage(\n bindings: readonly AuthRateLimitBinding[],\n { failureMode = 'allow' }: AuthRateLimitOptions = {},\n): BetterAuthRateLimitStorage {\n const logger = createLogger('auth-rate-limit')\n return {\n async consume(key, rule) {\n const configured = bindings.find(\n (entry) => entry.window === rule.window && entry.max === rule.max,\n )\n if (configured === undefined)\n throw new UnsupportedAuthRateLimitRuleError(rule.window, rule.max)\n try {\n const { success } = await configured.binding.limit({ key })\n return { allowed: success, retryAfter: success ? null : rule.window }\n } catch (cause) {\n if (failureMode === 'block') throw new AuthRateLimitError(cause)\n logger.error('rate_limit_binding_failed', {\n error: cause,\n details: { window: rule.window, max: rule.max, failureMode },\n })\n return { allowed: true, retryAfter: null }\n }\n },\n }\n}\n"],"mappings":";;;;;;;;;;AAwBA,MAAa,iCAA+D,CAC1E;CAAE,QAAQ;CAAI,KAAK;AAAE,GACrB;CAAE,QAAQ;CAAI,KAAK;AAAE,CACvB;;;;;;AAOA,SAAgB,mBACd,SAC8B;CAC9B,MAAM,YAAY,QAAQ;CAC1B,IAAI,WAAW,YAAY,OAAO,OAAO,CAAC;CAG1C,MAAM,QAA6B,CACjC;EAAE,QAAQ,WAAW,UAAU;EAAI,KAAK,WAAW,OAAO;CAAI,GAC9D,GAAG,8BACL;CACA,KAAK,MAAM,UAAU,QAAQ,WAAW,CAAC,GACvC,KAAK,MAAM,QAAQ,OAAO,aAAa,CAAC,GAAG,MAAM,KAAK;EAAE,QAAQ,KAAK;EAAQ,KAAK,KAAK;CAAI,CAAC;CAC9F,KAAK,MAAM,QAAQ,OAAO,OAAO,WAAW,eAAe,CAAC,CAAC,GAC3D,IAAI,SAAS,SAAS,EAAE,gBAAgB,WACtC,MAAM,KAAK;EAAE,QAAQ,KAAK;EAAQ,KAAK,KAAK;CAAI,CAAC;CAErD,OAAO,CAAC,GAAG,IADQ,IAAI,MAAM,KAAK,SAAS,CAAC,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO,IAAI,CAAC,CAC/D,CAAC,CAAC,OAAO,CAAC;AAC5B;;AAGA,IAAa,qBAAb,cAAwC,SAAS;CAC/C,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,YAAY,OAAgB;EAC1B,MAAM,8CAA8C,EAAE,MAAM,CAAC;CAC/D;AACF;;AAGA,IAAa,oCAAb,cAAuD,SAAS;CAKnD;CACA;CALX,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,YACE,AAAS,QACT,AAAS,KACT;EACA,MAAM,mEAAmE;EAHhE;EACA;CAGX;AACF;;;;;AAWA,SAAgB,2BACd,UACA,EAAE,cAAc,YAAkC,CAAC,GACvB;CAC5B,MAAM,SAAS,aAAa,iBAAiB;CAC7C,OAAO,EACL,MAAM,QAAQ,KAAK,MAAM;EACvB,MAAM,aAAa,SAAS,MACzB,UAAU,MAAM,WAAW,KAAK,UAAU,MAAM,QAAQ,KAAK,GAChE;EACA,IAAI,eAAe,QACjB,MAAM,IAAI,kCAAkC,KAAK,QAAQ,KAAK,GAAG;EACnE,IAAI;GACF,MAAM,EAAE,YAAY,MAAM,WAAW,QAAQ,MAAM,EAAE,IAAI,CAAC;GAC1D,OAAO;IAAE,SAAS;IAAS,YAAY,UAAU,OAAO,KAAK;GAAO;EACtE,SAAS,OAAO;GACd,IAAI,gBAAgB,SAAS,MAAM,IAAI,mBAAmB,KAAK;GAC/D,OAAO,MAAM,6BAA6B;IACxC,OAAO;IACP,SAAS;KAAE,QAAQ,KAAK;KAAQ,KAAK,KAAK;KAAK;IAAY;GAC7D,CAAC;GACD,OAAO;IAAE,SAAS;IAAM,YAAY;GAAK;EAC3C;CACF,EACF;AACF"}
|
package/dist/better-auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as createLogger } from "./core-
|
|
1
|
+
import { a as createLogger } from "./core-DC_4pOrI.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";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { kt as AppError } from "../index-6XMPJu7u.js";
|
|
2
2
|
import { DurableObject } from "cloudflare:workers";
|
|
3
3
|
//#region src/infrastructure/cloudflare/auth-secondary-storage.d.ts
|
|
4
4
|
/** The supplied TTL cannot produce a valid expiry timestamp. Retrying the same input cannot fix it. */
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { At as FailureClassification, U as DomainCommand, W as DomainEvent, kt as AppError } from "../index-6XMPJu7u.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/infrastructure/cloudflare/queue/queue-message.d.ts
|
|
4
|
+
/** A message a queue carries: a command from outside the request, or an event from the outbox. */
|
|
5
|
+
type QueueMessage = DomainCommand | DomainEvent;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/infrastructure/cloudflare/queue/queue-client.d.ts
|
|
8
|
+
/** Cloudflare's delivery delay, in seconds. */
|
|
9
|
+
type QueueSendOptions = {
|
|
10
|
+
readonly delaySeconds: number;
|
|
11
|
+
};
|
|
12
|
+
/** The two calls the client makes on a queue binding. */
|
|
13
|
+
type QueueBinding<Body> = Pick<Queue<Body>, 'send' | 'sendBatch'>;
|
|
14
|
+
/**
|
|
15
|
+
* The lanes and the dead letter of one application. A lane is a queue with its own consumer
|
|
16
|
+
* settings; `laneOf` is the product's routing rule, written once at the composition root.
|
|
17
|
+
*/
|
|
18
|
+
type QueueClientOptions<TMessage extends QueueMessage, Lane extends string> = {
|
|
19
|
+
readonly queues: Readonly<Record<Lane, QueueBinding<TMessage>>>;
|
|
20
|
+
/** Where the consumer parks poison bodies. Cloudflare also routes exhausted retries here. */
|
|
21
|
+
readonly deadLetter: Pick<Queue, 'send'>;
|
|
22
|
+
readonly laneOf: (message: TMessage) => Lane;
|
|
23
|
+
};
|
|
24
|
+
/** The port an entrypoint, a relay, or a consumer sends through. */
|
|
25
|
+
interface IQueueClient<TMessage extends QueueMessage> {
|
|
26
|
+
send(message: TMessage, options?: QueueSendOptions): Promise<void>;
|
|
27
|
+
sendBatch(messages: ReadonlyArray<TMessage>): Promise<void>;
|
|
28
|
+
/** Sends every message within the limits and returns the oversized ones. */
|
|
29
|
+
sendBatchPartial(messages: ReadonlyArray<TMessage>): Promise<ReadonlyArray<TMessage>>;
|
|
30
|
+
/** Parks a delivered message's body as it arrived. */
|
|
31
|
+
sendToDlq(message: Message): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Sends messages to the application's queues. Checks the size limits before a send, retries a
|
|
35
|
+
* transient binding failure a bounded number of times, and converts the final failure to `QueueSendError`.
|
|
36
|
+
*/
|
|
37
|
+
export declare class QueueClient<TMessage extends QueueMessage, Lane extends string> implements IQueueClient<TMessage> {
|
|
38
|
+
private readonly options;
|
|
39
|
+
constructor(options: QueueClientOptions<TMessage, Lane>);
|
|
40
|
+
/** @throws OversizedMessageError before the send. @throws QueueSendError after the retries. */
|
|
41
|
+
send(message: TMessage, options?: QueueSendOptions): Promise<void>;
|
|
42
|
+
/** Rejects the whole batch before any send when one message is oversized. */
|
|
43
|
+
sendBatch(messages: ReadonlyArray<TMessage>): Promise<void>;
|
|
44
|
+
sendBatchPartial(messages: ReadonlyArray<TMessage>): Promise<ReadonlyArray<TMessage>>;
|
|
45
|
+
sendToDlq(message: Message): Promise<void>;
|
|
46
|
+
/** Each lane's chunks retry on their own, so one failed chunk does not resend the others. */
|
|
47
|
+
private sendSizedBatch;
|
|
48
|
+
private queueFor;
|
|
49
|
+
private callQueue;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/infrastructure/cloudflare/queue/queue-consumer.d.ts
|
|
53
|
+
/** Wraps one dispatch: an error-tracking scope, an analytics context. The default runs it as is. */
|
|
54
|
+
type DispatchWrapper<TMessage extends QueueMessage> = (message: TMessage, attempt: number, run: () => Promise<void>) => Promise<void>;
|
|
55
|
+
/** The one call the consumer makes on the bus. */
|
|
56
|
+
type DispatchBus<TMessage extends QueueMessage> = {
|
|
57
|
+
handle(message: TMessage): Promise<unknown>;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Parses and dispatches each queue message, then selects acknowledgement or retry.
|
|
61
|
+
* Cloudflare owns the retry limit and sends exhausted messages to the configured dead letter.
|
|
62
|
+
* The consumer logs each failure once; handlers do not.
|
|
63
|
+
*/
|
|
64
|
+
export declare class QueueConsumer<TMessage extends QueueMessage> {
|
|
65
|
+
private readonly bus;
|
|
66
|
+
private readonly queueClient;
|
|
67
|
+
private readonly schema;
|
|
68
|
+
private readonly wrapDispatch;
|
|
69
|
+
/**
|
|
70
|
+
* @param bus - Where a parsed message goes.
|
|
71
|
+
* @param queueClient - Parks an unparseable body in the dead letter.
|
|
72
|
+
* @param schema - What a body may be: the application's `messages.queueSchema`.
|
|
73
|
+
* @param wrapDispatch - Wraps one dispatch in the application's context. Default: run it as is.
|
|
74
|
+
*/
|
|
75
|
+
constructor(bus: DispatchBus<TMessage>, queueClient: Pick<IQueueClient<TMessage>, 'sendToDlq'>, schema: z.ZodType<TMessage>, wrapDispatch?: DispatchWrapper<TMessage>);
|
|
76
|
+
/** Every message settles on its own. A settlement that throws rejects the batch after its siblings settle. */
|
|
77
|
+
processBatch(batch: MessageBatch): Promise<void>;
|
|
78
|
+
private processMessage;
|
|
79
|
+
/** Returns null on failure; the caller owns the single log point. */
|
|
80
|
+
private parseMessage;
|
|
81
|
+
/** Park an unparseable message in the dead letter, then acknowledge its source delivery. */
|
|
82
|
+
private parkPoison;
|
|
83
|
+
/**
|
|
84
|
+
* Retry declared transient and unknown failures so Cloudflare can enforce the attempt ceiling.
|
|
85
|
+
* Acknowledge only declared terminal failures.
|
|
86
|
+
*/
|
|
87
|
+
private handleFailure;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/infrastructure/cloudflare/queue/queue-limits.d.ts
|
|
91
|
+
/**
|
|
92
|
+
* The Cloudflare Queues producer limits with metadata headroom, checked before each send so a
|
|
93
|
+
* violation never reaches the binding. The dead letter has the same per-message limit.
|
|
94
|
+
* https://developers.cloudflare.com/queues/platform/limits/
|
|
95
|
+
*/
|
|
96
|
+
export declare const QUEUE_LIMITS: {
|
|
97
|
+
readonly messageBytes: 127000;
|
|
98
|
+
readonly batchBytes: 250000;
|
|
99
|
+
readonly batchCount: 100;
|
|
100
|
+
};
|
|
101
|
+
/** The size the binding measures: the JSON encoding of the body, in UTF-8 bytes. */
|
|
102
|
+
export declare function messageByteLength(message: QueueMessage): number;
|
|
103
|
+
/** A message with its measured size. */
|
|
104
|
+
type SizedMessage<TMessage> = {
|
|
105
|
+
readonly message: TMessage;
|
|
106
|
+
readonly bytes: number;
|
|
107
|
+
};
|
|
108
|
+
/** Measure every message once. */
|
|
109
|
+
export declare function sizeMessages<TMessage extends QueueMessage>(messages: ReadonlyArray<TMessage>): SizedMessage<TMessage>[];
|
|
110
|
+
/** Whether one message is over the per-message limit. */
|
|
111
|
+
export declare function isOversized(sized: SizedMessage<QueueMessage>): boolean;
|
|
112
|
+
/** Split a batch at the message-count and byte limits, keeping the order. */
|
|
113
|
+
export declare function chunkByLimits<TMessage>(sized: ReadonlyArray<SizedMessage<TMessage>>): TMessage[][];
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/infrastructure/cloudflare/queue/queue.errors.d.ts
|
|
116
|
+
/** The Queue binding calls the client makes. Each is safe to repeat. */
|
|
117
|
+
type QueueOperation = 'send' | 'send_batch' | 'send_dlq';
|
|
118
|
+
/** Classify a raw Queue binding failure: the code table first, then Cloudflare's signals, then transport. */
|
|
119
|
+
export declare function classifyQueueError(cause: unknown): FailureClassification;
|
|
120
|
+
/** A send through a Queue binding failed. The classification says whether an immediate retry may help. */
|
|
121
|
+
export declare class QueueSendError extends AppError {
|
|
122
|
+
readonly _tag = "QueueSendError";
|
|
123
|
+
readonly classification: FailureClassification;
|
|
124
|
+
readonly operation: QueueOperation;
|
|
125
|
+
constructor(args: {
|
|
126
|
+
readonly operation: QueueOperation;
|
|
127
|
+
readonly cause: unknown;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* A message over the per-message cap can never be sent on any queue; the dead letter has the
|
|
132
|
+
* same cap. The client detects it before the send, so it never comes from a platform error.
|
|
133
|
+
*/
|
|
134
|
+
export declare class OversizedMessageError extends AppError {
|
|
135
|
+
readonly _tag = "OversizedMessageError";
|
|
136
|
+
readonly classification = "terminal";
|
|
137
|
+
readonly messageName: string;
|
|
138
|
+
readonly bytes: number;
|
|
139
|
+
readonly limit: number;
|
|
140
|
+
constructor(args: {
|
|
141
|
+
readonly messageName: string;
|
|
142
|
+
readonly bytes: number;
|
|
143
|
+
readonly limit: number;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The dead letter did not accept an unparseable message. Always transient, whatever the send
|
|
148
|
+
* failure was, so the source delivery retries instead of acknowledging and losing the body.
|
|
149
|
+
*/
|
|
150
|
+
export declare class DeadLetterUnavailableError extends AppError {
|
|
151
|
+
readonly _tag = "DeadLetterUnavailableError";
|
|
152
|
+
readonly classification = "transient";
|
|
153
|
+
constructor(cause: unknown);
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/infrastructure/cloudflare/queue/redelivery-policy.d.ts
|
|
157
|
+
/** How the consumer settles one failed message: acknowledge it, or have Cloudflare redeliver it after a delay. */
|
|
158
|
+
type RedeliveryDecision = {
|
|
159
|
+
readonly action: 'ack';
|
|
160
|
+
readonly classification: 'terminal';
|
|
161
|
+
} | {
|
|
162
|
+
readonly action: 'retry';
|
|
163
|
+
readonly classification: Exclude<FailureClassification, 'terminal'>;
|
|
164
|
+
readonly delaySeconds: number;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Decide whether the consumer acknowledges a failed message or asks Cloudflare to redeliver it.
|
|
168
|
+
* A terminal failure acknowledges. Transient and unknown failures redeliver: after the advertised
|
|
169
|
+
* wait plus a spread when the error carries one, else with exponential equal-jitter backoff.
|
|
170
|
+
* An `AggregateError` from event delivery redelivers when any member does, after the longest wait.
|
|
171
|
+
*/
|
|
172
|
+
export declare function decideRedelivery(cause: unknown, attempt: number): RedeliveryDecision;
|
|
173
|
+
//#endregion
|
|
174
|
+
export type { DispatchBus, DispatchWrapper, IQueueClient, QueueBinding, QueueClientOptions, QueueMessage, QueueOperation, QueueSendOptions, RedeliveryDecision, SizedMessage };
|
|
175
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","names":[],"sources":["../../src/infrastructure/cloudflare/queue/queue-message.ts","../../src/infrastructure/cloudflare/queue/queue-client.ts","../../src/infrastructure/cloudflare/queue/queue-consumer.ts","../../src/infrastructure/cloudflare/queue/queue-limits.ts","../../src/infrastructure/cloudflare/queue/queue.errors.ts","../../src/infrastructure/cloudflare/queue/redelivery-policy.ts"],"mappings":";;;;KAGY,eAAe,gBAAgB;;;;KCa/B;WAA8B;;;KAG9B,aAAa,QAAQ,KAAK,MAAM;;;;;KAMhC,mBAAmB,iBAAiB,cAAc;WACnD,QAAQ,SAAS,OAAO,MAAM,aAAa;;WAE3C,YAAY,KAAK;WACjB,SAAS,SAAS,aAAa;;;UAIzB,aAAa,iBAAiB;EAC7C,KAAK,SAAS,UAAU,UAAU,mBAAmB;EACrD,UAAU,UAAU,cAAc,YAAY;;EAE9C,iBAAiB,UAAU,cAAc,YAAY,QAAQ,cAAc;;EAE3E,UAAU,SAAS,UAAU;;;;;;qBA8BlB,YACX,iBAAiB,cACjB,gCACW,aAAa;mBACK;EAA7B,YAA6B,SAAS,mBAAmB,UAAU;;EAG7D,KAAK,SAAS,UAAU,UAAU,mBAAmB;;EA4BrD,UAAU,UAAU,cAAc,YAAY;EAO9C,iBAAiB,UAAU,cAAc,YAAY,QAAQ,cAAc;EAM3E,UAAU,SAAS,UAAU;;UAQrB;UA4BN;UAIM;;;;;KChJJ,gBAAgB,iBAAiB,iBAC3C,SAAS,UACT,iBACA,WAAW,kBACR;;KAGO,YAAY,iBAAiB;EAEvC,OAAO,SAAS,WAAW;;;;;;;qBAQhB,cAAc,iBAAiB;mBAQvB;mBACA;mBACA;mBACA;;;;;;;EAJnB,YACmB,KAAK,YAAY,WACjB,aAAa,KAAK,aAAa,yBAC/B,QAAQ,EAAE,QAAQ,WAClB,eAAc,gBAAgB;;EAI3C,aAAa,OAAO,eAAe;UAc3B;;UA0BA;;UAMA;;;;;UAaN;;;;;;;;;qBCjGG;WACX;WACA;WACA;;;wBAIc,kBAAkB,SAAS;;KAK/B,aAAa;WAAuB,SAAS;WAAmB;;;wBAG5D,aAAa,iBAAiB,cAC5C,UAAU,cAAc,YACvB,aAAa;;wBAKA,YAAY,OAAO,aAAa;;wBAKhC,cAAc,UAC5B,OAAO,cAAc,aAAa,aACjC;;;;KCjCS;;wBA4CI,mBAAmB,iBAAiB;;qBASvC,uBAAuB;WACzB;WACA,gBAAgB;WAChB,WAAW;EAEpB,YAAY;aAAiB,WAAW;aAAyB;;;;;;;qBAWtD,8BAA8B;WAChC;WACA;WACA;WACA;WACA;EAET,YAAY;aACD;aACA;aACA;;;;;;;qBAeA,mCAAmC;WACrC;WACA;EAET,YAAY;;;;;KCrFF;WACG;WAAwB;;WAExB;WACA,gBAAgB,QAAQ;WACxB;;;;;;;;wBASC,iBAAiB,gBAAgB,kBAAkB"}
|