better-ship 0.4.1 → 0.5.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.
Files changed (81) hide show
  1. package/dist/{application-rpZoTfzU.js → application-DEerezCl.js} +2 -2
  2. package/dist/{application-rpZoTfzU.js.map → application-DEerezCl.js.map} +1 -1
  3. package/dist/application.d.ts +1 -1
  4. package/dist/application.js +1 -1
  5. package/dist/better-auth/apple-client-secret.d.ts +21 -0
  6. package/dist/better-auth/apple-client-secret.d.ts.map +1 -0
  7. package/dist/better-auth/apple-client-secret.js +44 -0
  8. package/dist/better-auth/apple-client-secret.js.map +1 -0
  9. package/dist/better-auth/auth-client.d.ts +16 -0
  10. package/dist/better-auth/auth-client.d.ts.map +1 -0
  11. package/dist/better-auth/auth-client.js +24 -0
  12. package/dist/better-auth/auth-client.js.map +1 -0
  13. package/dist/better-auth/rate-limit.d.ts +34 -0
  14. package/dist/better-auth/rate-limit.d.ts.map +1 -0
  15. package/dist/better-auth/rate-limit.js +59 -0
  16. package/dist/better-auth/rate-limit.js.map +1 -0
  17. package/dist/better-auth/secondary-storage.d.ts +11 -0
  18. package/dist/better-auth/secondary-storage.d.ts.map +1 -0
  19. package/dist/better-auth/secondary-storage.js +18 -0
  20. package/dist/better-auth/secondary-storage.js.map +1 -0
  21. package/dist/better-auth/stripe.d.ts +9 -0
  22. package/dist/better-auth/stripe.d.ts.map +1 -0
  23. package/dist/better-auth/stripe.js +17 -0
  24. package/dist/better-auth/stripe.js.map +1 -0
  25. package/dist/better-auth.d.ts +100 -0
  26. package/dist/better-auth.d.ts.map +1 -0
  27. package/dist/better-auth.js +112 -0
  28. package/dist/better-auth.js.map +1 -0
  29. package/dist/cloudflare/auth-secondary-storage.d.ts +36 -0
  30. package/dist/cloudflare/auth-secondary-storage.d.ts.map +1 -0
  31. package/dist/cloudflare/auth-secondary-storage.js +98 -0
  32. package/dist/cloudflare/auth-secondary-storage.js.map +1 -0
  33. package/dist/cloudflare.d.ts +3 -3
  34. package/dist/cloudflare.js +3 -3
  35. package/dist/core-CnwWj4MY.js +620 -0
  36. package/dist/core-CnwWj4MY.js.map +1 -0
  37. package/dist/core.d.ts +2 -2
  38. package/dist/core.js +2 -2
  39. package/dist/index-B9Y1hVh6.d.ts +460 -0
  40. package/dist/index-B9Y1hVh6.d.ts.map +1 -0
  41. package/dist/{index-BN_tjxME.d.ts → index-Bdyh3_xC.d.ts} +2 -2
  42. package/dist/{index-BN_tjxME.d.ts.map → index-Bdyh3_xC.d.ts.map} +1 -1
  43. package/dist/postgres.d.ts +3 -3
  44. package/dist/postgres.js +3 -3
  45. package/dist/tanstack.d.ts +81 -1
  46. package/dist/tanstack.d.ts.map +1 -0
  47. package/dist/tanstack.js +214 -0
  48. package/dist/tanstack.js.map +1 -0
  49. package/dist/{unit-of-work-context-BLNj72v0.js → unit-of-work-context-C7vz5OYS.js} +2 -2
  50. package/dist/{unit-of-work-context-BLNj72v0.js.map → unit-of-work-context-C7vz5OYS.js.map} +1 -1
  51. package/dist/{unit-of-work-context-CbUJGHlL.d.ts → unit-of-work-context-ml9L9cyY.d.ts} +2 -2
  52. package/dist/{unit-of-work-context-CbUJGHlL.d.ts.map → unit-of-work-context-ml9L9cyY.d.ts.map} +1 -1
  53. package/package.json +57 -12
  54. package/src/core/authentication.error.ts +11 -0
  55. package/src/core/error-table.ts +188 -0
  56. package/src/core/index.ts +31 -0
  57. package/src/core/internal.errors.ts +21 -0
  58. package/src/core/logger.ts +27 -15
  59. package/src/core/not-authorized.error.ts +11 -0
  60. package/src/core/problem-details.ts +57 -0
  61. package/src/core/rate-limit.error.ts +12 -0
  62. package/src/core/request.errors.ts +86 -0
  63. package/src/core/stale-session.error.ts +11 -0
  64. package/src/infrastructure/better-auth/apple-client-secret.ts +54 -0
  65. package/src/infrastructure/better-auth/auth-client.ts +26 -0
  66. package/src/infrastructure/better-auth/options.ts +232 -0
  67. package/src/infrastructure/better-auth/rate-limit.ts +69 -0
  68. package/src/infrastructure/better-auth/secondary-storage.ts +21 -0
  69. package/src/infrastructure/better-auth/stripe.ts +11 -0
  70. package/src/infrastructure/cloudflare/auth-secondary-storage.ts +113 -0
  71. package/src/infrastructure/tanstack/auth.middleware.ts +89 -0
  72. package/src/infrastructure/tanstack/error.middleware.ts +81 -0
  73. package/src/infrastructure/tanstack/http-error-response.ts +30 -0
  74. package/src/infrastructure/tanstack/index.ts +12 -0
  75. package/src/infrastructure/tanstack/resolve-failure.ts +24 -0
  76. package/src/infrastructure/tanstack/session-cookies.ts +36 -0
  77. package/src/infrastructure/tanstack/to-error-payload.ts +32 -0
  78. package/dist/core-MjwyZJ4i.js +0 -302
  79. package/dist/core-MjwyZJ4i.js.map +0 -1
  80. package/dist/index-ToddX0m3.d.ts +0 -213
  81. package/dist/index-ToddX0m3.d.ts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"better-auth.js","names":["uuidv7"],"sources":["../src/infrastructure/better-auth/options.ts"],"sourcesContent":["import type {\n BetterAuthOptions,\n BetterAuthPlugin,\n BetterAuthRateLimitStorage,\n DBAdapterInstance,\n} from 'better-auth'\nimport { isAPIError } from 'better-auth/api'\nimport {\n anonymous,\n bearer,\n captcha,\n deviceAuthorization,\n lastLoginMethod,\n magicLink,\n openAPI,\n type CloudflareTurnstileOptions,\n type LastLoginMethodOptions,\n} from 'better-auth/plugins'\nimport { v7 as uuidv7 } from 'uuid'\n\nimport { createLogger } from '@/core'\n\nconst logger = createLogger('auth')\n\n// Better Auth does not export its default captcha paths; keep them when adding magic-link requests.\nconst MAGIC_LINK_CAPTCHA_ENDPOINTS = [\n '/sign-up/email',\n '/sign-in/email',\n '/request-password-reset',\n '/sign-in/magic-link',\n]\n\n/**\n * Shared policy from Porte and Typist. The factory merges application overrides into these defaults.\n */\nexport const authDefaults = {\n onAPIError: {\n throw: false,\n onError(error) {\n if (isAPIError(error) && error.statusCode < 500) return\n logger.error('auth_operation_failed', { error })\n },\n },\n rateLimit: { window: 60, max: 100 },\n session: {\n expiresIn: 60 * 60 * 24 * 7,\n updateAge: 60 * 60 * 24,\n freshAge: 60 * 60 * 24,\n cookieCache: { enabled: true, maxAge: 300 },\n },\n advanced: {\n database: { generateId: () => uuidv7(), joins: true },\n ipAddress: { ipAddressHeaders: ['cf-connecting-ip', 'x-forwarded-for'] },\n },\n} satisfies BetterAuthOptions\n\n/** Configure built-in plugins with native options. Runtime auth requires captcha; lastLoginMethod is enabled unless explicitly disabled. */\nexport type AuthPluginOptions = {\n readonly magicLink?: Parameters<typeof magicLink>[0]\n readonly anonymous?: NonNullable<Parameters<typeof anonymous>[0]>\n readonly captcha?: Omit<CloudflareTurnstileOptions, 'provider'>\n readonly lastLoginMethod?: false | NonNullable<Parameters<typeof lastLoginMethod>[0]>\n readonly openAPI?: NonNullable<Parameters<typeof openAPI>[0]>\n readonly bearer?: NonNullable<Parameters<typeof bearer>[0]>\n readonly deviceAuthorization?: NonNullable<Parameters<typeof deviceAuthorization>[0]>\n}\n\n/**\n * Runtime auth requires rate limiting and Turnstile credentials. Schema generation omits both.\n * Both modes select plugins through `integrations`, so schema generation must repeat the runtime selection.\n */\nexport type AuthConfig = Omit<BetterAuthOptions, 'database' | 'plugins' | 'rateLimit'> & {\n readonly rateLimit?: Omit<\n NonNullable<BetterAuthOptions['rateLimit']>,\n 'enabled' | 'storage' | 'customStorage'\n >\n} & (\n | {\n readonly mode?: 'runtime'\n readonly rateLimitStorage: BetterAuthRateLimitStorage\n readonly integrations: AuthPluginOptions & {\n readonly captcha: Omit<CloudflareTurnstileOptions, 'provider'>\n }\n }\n | {\n readonly mode: 'schema'\n readonly rateLimitStorage?: never\n readonly integrations: AuthPluginOptions\n }\n )\n\ntype PluginTypes = {\n magicLink: ReturnType<typeof magicLink>\n anonymous: ReturnType<typeof anonymous>\n captcha: ReturnType<typeof captcha>\n lastLoginMethod: ReturnType<typeof lastLoginMethod<{}>>\n openAPI: ReturnType<typeof openAPI>\n bearer: ReturnType<typeof bearer>\n deviceAuthorization: ReturnType<typeof deviceAuthorization>\n}\n\ntype LastLoginPlugin<Options extends AuthPluginOptions> = Options extends {\n readonly lastLoginMethod: infer Settings extends LastLoginMethodOptions\n}\n ? ReturnType<typeof lastLoginMethod<Settings>>\n : PluginTypes['lastLoginMethod']\n\ntype SelectedPlugin<Options extends AuthPluginOptions, Key extends keyof PluginTypes> =\n Options extends Record<Key, false | undefined>\n ? []\n : Options extends Record<Key, NonNullable<AuthPluginOptions[Key]>>\n ? [Key extends 'lastLoginMethod' ? LastLoginPlugin<Options> : PluginTypes[Key]]\n : Key extends 'lastLoginMethod'\n ? [PluginTypes[Key]]\n : []\n\ntype SelectedPlugins<Options extends AuthPluginOptions> = [\n ...SelectedPlugin<Options, 'captcha'>,\n ...SelectedPlugin<Options, 'lastLoginMethod'>,\n ...SelectedPlugin<Options, 'openAPI'>,\n ...SelectedPlugin<Options, 'magicLink'>,\n ...SelectedPlugin<Options, 'anonymous'>,\n ...SelectedPlugin<Options, 'deviceAuthorization'>,\n ...SelectedPlugin<Options, 'bearer'>,\n]\n\ntype ConfiguredPlugins<Config extends AuthConfig> = SelectedPlugins<Config['integrations']>\n\ntype MergeOptions<Defaults, Overrides> = Omit<Defaults, keyof Overrides> & Overrides\n\ntype SessionOptions<Config extends AuthConfig> = MergeOptions<\n typeof authDefaults.session,\n NonNullable<Config['session']>\n>\n\ntype ConfiguredOptions<Config extends AuthConfig, Plugins extends BetterAuthPlugin[]> = Omit<\n Config,\n 'mode' | 'integrations' | 'rateLimitStorage' | 'rateLimit' | 'session' | 'advanced' | 'onAPIError'\n> & {\n database: DBAdapterInstance\n onAPIError: MergeOptions<typeof authDefaults.onAPIError, NonNullable<Config['onAPIError']>>\n rateLimit: NonNullable<BetterAuthOptions['rateLimit']>\n session: Omit<SessionOptions<Config>, 'cookieCache'> & {\n cookieCache: MergeOptions<\n typeof authDefaults.session.cookieCache,\n NonNullable<NonNullable<Config['session']>['cookieCache']>\n >\n }\n advanced: MergeOptions<typeof authDefaults.advanced, NonNullable<Config['advanced']>> & {\n database: MergeOptions<\n typeof authDefaults.advanced.database,\n NonNullable<NonNullable<Config['advanced']>['database']>\n >\n ipAddress: MergeOptions<\n typeof authDefaults.advanced.ipAddress,\n NonNullable<NonNullable<Config['advanced']>['ipAddress']>\n >\n }\n plugins: [...ConfiguredPlugins<Config>, ...Plugins]\n}\n\n/** A required database adapter and application config produce options with inferred plugin and session types. */\nexport type BetterAuthOptionsFactory = <\n const Config extends AuthConfig,\n const Plugins extends BetterAuthPlugin[] = [],\n>(\n database: DBAdapterInstance,\n config: Config,\n additionalPlugins?: Plugins,\n) => ConfiguredOptions<Config, Plugins>\n\nfunction buildPlugins<const Options extends AuthPluginOptions>(\n options: Options,\n): SelectedPlugins<Options> {\n const plugins: BetterAuthPlugin[] = []\n if (options.captcha !== undefined) {\n const captchaOptions = { ...options.captcha }\n if (options.magicLink !== undefined && captchaOptions.endpoints === undefined)\n captchaOptions.endpoints = [...MAGIC_LINK_CAPTCHA_ENDPOINTS]\n plugins.push(captcha({ ...captchaOptions, provider: 'cloudflare-turnstile' }))\n }\n if (options.lastLoginMethod !== false) plugins.push(lastLoginMethod(options.lastLoginMethod))\n if (options.openAPI !== undefined) plugins.push(openAPI(options.openAPI))\n if (options.magicLink !== undefined)\n plugins.push(magicLink({ expiresIn: 300, ...options.magicLink }))\n if (options.anonymous !== undefined) plugins.push(anonymous(options.anonymous))\n if (options.deviceAuthorization !== undefined)\n plugins.push(deviceAuthorization(options.deviceAuthorization))\n if (options.bearer !== undefined) plugins.push(bearer(options.bearer))\n // SAFETY: Each branch adds exactly the plugin selected by the corresponding configuration key.\n return plugins as SelectedPlugins<Options>\n}\n\n/**\n * Build native Better Auth options with shared defaults and configured plugins; both arguments are required.\n * Pass Stripe and other plugins in the third argument. Put tanstackStartCookies() last.\n */\nexport const createBetterAuthOptions: BetterAuthOptionsFactory = (\n database,\n config,\n additionalPlugins,\n) => {\n const { mode, integrations, rateLimitStorage, ...options } = config\n const result: BetterAuthOptions = {\n ...options,\n database,\n onAPIError: { ...authDefaults.onAPIError, ...options.onAPIError },\n rateLimit:\n mode === 'schema'\n ? { enabled: false }\n : {\n ...authDefaults.rateLimit,\n ...options.rateLimit,\n enabled: true,\n customStorage: rateLimitStorage,\n },\n session: {\n ...authDefaults.session,\n ...options.session,\n cookieCache: { ...authDefaults.session.cookieCache, ...options.session?.cookieCache },\n },\n advanced: {\n ...authDefaults.advanced,\n ...options.advanced,\n database: { ...authDefaults.advanced.database, ...options.advanced?.database },\n ipAddress: { ...authDefaults.advanced.ipAddress, ...options.advanced?.ipAddress },\n },\n plugins: [...buildPlugins(integrations), ...(additionalPlugins ?? [])],\n }\n // SAFETY: The factory preserves configured field schemas and plugins while merging only the documented default fields.\n return result as ConfiguredOptions<typeof config, Exclude<typeof additionalPlugins, undefined>>\n}\n"],"mappings":";;;;;;AAsBA,MAAM,SAAS,aAAa,MAAM;AAGlC,MAAM,+BAA+B;CACnC;CACA;CACA;CACA;AACF;;;;AAKA,MAAa,eAAe;CAC1B,YAAY;EACV,OAAO;EACP,QAAQ,OAAO;GACb,IAAI,WAAW,KAAK,KAAK,MAAM,aAAa,KAAK;GACjD,OAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC;EACjD;CACF;CACA,WAAW;EAAE,QAAQ;EAAI,KAAK;CAAI;CAClC,SAAS;EACP,WAAW;EACX,WAAW;EACX,UAAU;EACV,aAAa;GAAE,SAAS;GAAM,QAAQ;EAAI;CAC5C;CACA,UAAU;EACR,UAAU;GAAE,kBAAkBA,GAAO;GAAG,OAAO;EAAK;EACpD,WAAW,EAAE,kBAAkB,CAAC,oBAAoB,iBAAiB,EAAE;CACzE;AACF;AAqHA,SAAS,aACP,SAC0B;CAC1B,MAAM,UAA8B,CAAC;CACrC,IAAI,QAAQ,YAAY,QAAW;EACjC,MAAM,iBAAiB,EAAE,GAAG,QAAQ,QAAQ;EAC5C,IAAI,QAAQ,cAAc,UAAa,eAAe,cAAc,QAClE,eAAe,YAAY,CAAC,GAAG,4BAA4B;EAC7D,QAAQ,KAAK,QAAQ;GAAE,GAAG;GAAgB,UAAU;EAAuB,CAAC,CAAC;CAC/E;CACA,IAAI,QAAQ,oBAAoB,OAAO,QAAQ,KAAK,gBAAgB,QAAQ,eAAe,CAAC;CAC5F,IAAI,QAAQ,YAAY,QAAW,QAAQ,KAAK,QAAQ,QAAQ,OAAO,CAAC;CACxE,IAAI,QAAQ,cAAc,QACxB,QAAQ,KAAK,UAAU;EAAE,WAAW;EAAK,GAAG,QAAQ;CAAU,CAAC,CAAC;CAClE,IAAI,QAAQ,cAAc,QAAW,QAAQ,KAAK,UAAU,QAAQ,SAAS,CAAC;CAC9E,IAAI,QAAQ,wBAAwB,QAClC,QAAQ,KAAK,oBAAoB,QAAQ,mBAAmB,CAAC;CAC/D,IAAI,QAAQ,WAAW,QAAW,QAAQ,KAAK,OAAO,QAAQ,MAAM,CAAC;CAErE,OAAO;AACT;;;;;AAMA,MAAa,2BACX,UACA,QACA,sBACG;CACH,MAAM,EAAE,MAAM,cAAc,kBAAkB,GAAG,YAAY;CA4B7D,OAAO;EA1BL,GAAG;EACH;EACA,YAAY;GAAE,GAAG,aAAa;GAAY,GAAG,QAAQ;EAAW;EAChE,WACE,SAAS,WACL,EAAE,SAAS,MAAM,IACjB;GACE,GAAG,aAAa;GAChB,GAAG,QAAQ;GACX,SAAS;GACT,eAAe;EACjB;EACN,SAAS;GACP,GAAG,aAAa;GAChB,GAAG,QAAQ;GACX,aAAa;IAAE,GAAG,aAAa,QAAQ;IAAa,GAAG,QAAQ,SAAS;GAAY;EACtF;EACA,UAAU;GACR,GAAG,aAAa;GAChB,GAAG,QAAQ;GACX,UAAU;IAAE,GAAG,aAAa,SAAS;IAAU,GAAG,QAAQ,UAAU;GAAS;GAC7E,WAAW;IAAE,GAAG,aAAa,SAAS;IAAW,GAAG,QAAQ,UAAU;GAAU;EAClF;EACA,SAAS,CAAC,GAAG,aAAa,YAAY,GAAG,GAAI,qBAAqB,CAAC,CAAE;CAG3D;AACd"}
@@ -0,0 +1,36 @@
1
+ import { $ as AppError } from "../index-B9Y1hVh6.js";
2
+ import { DurableObject } from "cloudflare:workers";
3
+ //#region src/infrastructure/cloudflare/auth-secondary-storage.d.ts
4
+ /** The supplied TTL cannot produce a valid expiry timestamp. Retrying the same input cannot fix it. */
5
+ export declare class InvalidTtlError extends AppError {
6
+ readonly _tag = "InvalidTtlError";
7
+ readonly classification = "terminal";
8
+ constructor();
9
+ }
10
+ /** The stored value cannot be incremented safely. The failed operation leaves it unchanged. */
11
+ export declare class InvalidCounterError extends AppError {
12
+ readonly _tag = "InvalidCounterError";
13
+ readonly classification = "terminal";
14
+ constructor();
15
+ }
16
+ /**
17
+ * Store one Better Auth secondary storage key per object. Export this class from the application's Worker entrypoint.
18
+ * Schedule cleanup at the caller's expiry; reads enforce expiry even when the alarm runs late.
19
+ * Requires compatibility date 2026-02-24 or later so deleteAll also removes the alarm atomically.
20
+ */
21
+ export declare class AuthSecondaryStorage extends DurableObject {
22
+ /** Return null for absent or expired values, even when the cleanup alarm has not run. */
23
+ get(): Promise<string | null>;
24
+ /** Replace the value and its expiry. Omit ttl to keep the value until deletion. */
25
+ set(value: string, ttl?: number): Promise<void>;
26
+ /** Remove the value and its cleanup alarm before answering. */
27
+ delete(): Promise<void>;
28
+ /** Consume the value once. Concurrent callers receive null after the first successful consumption. */
29
+ getAndDelete(): Promise<string | null>;
30
+ /** Increment atomically. Apply ttl only when creating a counter or replacing an expired counter. */
31
+ increment(ttl: number): Promise<number>;
32
+ /** Delete expired data. An old alarm must not delete a value whose expiry was extended. */
33
+ alarm(): Promise<void>;
34
+ }
35
+ //#endregion
36
+ //# sourceMappingURL=auth-secondary-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-secondary-storage.d.ts","names":[],"sources":["../../src/infrastructure/cloudflare/auth-secondary-storage.ts"],"mappings":";;;;qBAWa,wBAAwB;WAC1B;WACA;EAET;;;qBAMW,4BAA4B;WAC9B;WACA;EAET;;;;;;;qBAUW,6BAA6B;;EAElC,OAAO;;EAMP,IAAI,eAAe,eAAe;;EAUlC,UAAU;;EAKV,gBAAgB;;EAQhB,UAAU,cAAc;;EAef,SAAS"}
@@ -0,0 +1,98 @@
1
+ import { A as AppError } from "../core-CnwWj4MY.js";
2
+ import { DurableObject } from "cloudflare:workers";
3
+
4
+ //#region src/infrastructure/cloudflare/auth-secondary-storage.ts
5
+ const MIN_TTL_SECONDS = 1;
6
+ const MILLISECONDS_PER_SECOND = 1e3;
7
+ /** The supplied TTL cannot produce a valid expiry timestamp. Retrying the same input cannot fix it. */
8
+ var InvalidTtlError = class extends AppError {
9
+ _tag = "InvalidTtlError";
10
+ classification = "terminal";
11
+ constructor() {
12
+ super("TTL must be positive integer seconds with a safe expiry timestamp");
13
+ }
14
+ };
15
+ /** The stored value cannot be incremented safely. The failed operation leaves it unchanged. */
16
+ var InvalidCounterError = class extends AppError {
17
+ _tag = "InvalidCounterError";
18
+ classification = "terminal";
19
+ constructor() {
20
+ super("Counter must contain a safe integer that can be incremented");
21
+ }
22
+ };
23
+ /**
24
+ * Store one Better Auth secondary storage key per object. Export this class from the application's Worker entrypoint.
25
+ * Schedule cleanup at the caller's expiry; reads enforce expiry even when the alarm runs late.
26
+ * Requires compatibility date 2026-02-24 or later so deleteAll also removes the alarm atomically.
27
+ */
28
+ var AuthSecondaryStorage = class extends DurableObject {
29
+ /** Return null for absent or expired values, even when the cleanup alarm has not run. */
30
+ async get() {
31
+ return unexpiredRecord(await this.ctx.storage.get("value"))?.value ?? null;
32
+ }
33
+ /** Replace the value and its expiry. Omit ttl to keep the value until deletion. */
34
+ async set(value, ttl) {
35
+ const expiresAt = ttl === void 0 ? null : expiry(ttl);
36
+ await this.ctx.storage.transaction(async (transaction) => {
37
+ await transaction.put("value", {
38
+ value,
39
+ expiresAt
40
+ });
41
+ if (expiresAt === null) await transaction.deleteAlarm();
42
+ else await transaction.setAlarm(expiresAt);
43
+ });
44
+ }
45
+ /** Remove the value and its cleanup alarm before answering. */
46
+ async delete() {
47
+ await this.ctx.storage.deleteAll();
48
+ }
49
+ /** Consume the value once. Concurrent callers receive null after the first successful consumption. */
50
+ async getAndDelete() {
51
+ const record = await this.ctx.storage.get("value");
52
+ await this.ctx.storage.deleteAll();
53
+ return unexpiredRecord(record)?.value ?? null;
54
+ }
55
+ /** Increment atomically. Apply ttl only when creating a counter or replacing an expired counter. */
56
+ async increment(ttl) {
57
+ const expiresAt = expiry(ttl);
58
+ return this.ctx.storage.transaction(async (transaction) => {
59
+ const record = unexpiredRecord(await transaction.get("value"));
60
+ const count = record === void 0 ? 1 : nextCount(record.value);
61
+ await transaction.put("value", {
62
+ value: String(count),
63
+ expiresAt: record === void 0 ? expiresAt : record.expiresAt
64
+ });
65
+ if (record === void 0) await transaction.setAlarm(expiresAt);
66
+ return count;
67
+ });
68
+ }
69
+ /** Delete expired data. An old alarm must not delete a value whose expiry was extended. */
70
+ async alarm() {
71
+ const record = await this.ctx.storage.get("value");
72
+ if (record?.expiresAt === null) return;
73
+ if (record !== void 0 && record.expiresAt > Date.now()) {
74
+ await this.ctx.storage.setAlarm(record.expiresAt);
75
+ return;
76
+ }
77
+ await this.ctx.storage.deleteAll();
78
+ }
79
+ };
80
+ function unexpiredRecord(record) {
81
+ if (record === void 0) return void 0;
82
+ if (record.expiresAt !== null && record.expiresAt <= Date.now()) return void 0;
83
+ return record;
84
+ }
85
+ function expiry(ttl) {
86
+ const expiresAt = Date.now() + ttl * MILLISECONDS_PER_SECOND;
87
+ if (!Number.isSafeInteger(ttl) || ttl < MIN_TTL_SECONDS || !Number.isSafeInteger(expiresAt)) throw new InvalidTtlError();
88
+ return expiresAt;
89
+ }
90
+ function nextCount(value) {
91
+ const count = Number(value);
92
+ if (String(count) !== value || !Number.isSafeInteger(count) || !Number.isSafeInteger(count + 1)) throw new InvalidCounterError();
93
+ return count + 1;
94
+ }
95
+
96
+ //#endregion
97
+ export { AuthSecondaryStorage, InvalidCounterError, InvalidTtlError };
98
+ //# sourceMappingURL=auth-secondary-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-secondary-storage.js","names":[],"sources":["../../src/infrastructure/cloudflare/auth-secondary-storage.ts"],"sourcesContent":["import { DurableObject } from 'cloudflare:workers'\n\nimport { AppError } from '@/core'\n\n// Better Auth supplies whole seconds; an omitted TTL retains the value until explicit deletion.\nconst MIN_TTL_SECONDS = 1\nconst MILLISECONDS_PER_SECOND = 1000\n\ntype StoredValue = { value: string; expiresAt: number | null }\n\n/** The supplied TTL cannot produce a valid expiry timestamp. Retrying the same input cannot fix it. */\nexport class InvalidTtlError extends AppError {\n readonly _tag = 'InvalidTtlError'\n readonly classification = 'terminal'\n\n constructor() {\n super('TTL must be positive integer seconds with a safe expiry timestamp')\n }\n}\n\n/** The stored value cannot be incremented safely. The failed operation leaves it unchanged. */\nexport class InvalidCounterError extends AppError {\n readonly _tag = 'InvalidCounterError'\n readonly classification = 'terminal'\n\n constructor() {\n super('Counter must contain a safe integer that can be incremented')\n }\n}\n\n/**\n * Store one Better Auth secondary storage key per object. Export this class from the application's Worker entrypoint.\n * Schedule cleanup at the caller's expiry; reads enforce expiry even when the alarm runs late.\n * Requires compatibility date 2026-02-24 or later so deleteAll also removes the alarm atomically.\n */\nexport class AuthSecondaryStorage extends DurableObject {\n /** Return null for absent or expired values, even when the cleanup alarm has not run. */\n async get(): Promise<string | null> {\n const record = await this.ctx.storage.get<StoredValue>('value')\n return unexpiredRecord(record)?.value ?? null\n }\n\n /** Replace the value and its expiry. Omit ttl to keep the value until deletion. */\n async set(value: string, ttl?: number): Promise<void> {\n const expiresAt = ttl === undefined ? null : expiry(ttl)\n await this.ctx.storage.transaction(async (transaction) => {\n await transaction.put('value', { value, expiresAt })\n if (expiresAt === null) await transaction.deleteAlarm()\n else await transaction.setAlarm(expiresAt)\n })\n }\n\n /** Remove the value and its cleanup alarm before answering. */\n async delete(): Promise<void> {\n await this.ctx.storage.deleteAll()\n }\n\n /** Consume the value once. Concurrent callers receive null after the first successful consumption. */\n async getAndDelete(): Promise<string | null> {\n // Storage input gates prevent another request from replacing the value between these operations.\n const record = await this.ctx.storage.get<StoredValue>('value')\n await this.ctx.storage.deleteAll()\n return unexpiredRecord(record)?.value ?? null\n }\n\n /** Increment atomically. Apply ttl only when creating a counter or replacing an expired counter. */\n async increment(ttl: number): Promise<number> {\n const expiresAt = expiry(ttl)\n return this.ctx.storage.transaction(async (transaction) => {\n const record = unexpiredRecord(await transaction.get<StoredValue>('value'))\n const count = record === undefined ? 1 : nextCount(record.value)\n await transaction.put('value', {\n value: String(count),\n expiresAt: record === undefined ? expiresAt : record.expiresAt,\n })\n if (record === undefined) await transaction.setAlarm(expiresAt)\n return count\n })\n }\n\n /** Delete expired data. An old alarm must not delete a value whose expiry was extended. */\n override async alarm(): Promise<void> {\n const record = await this.ctx.storage.get<StoredValue>('value')\n if (record?.expiresAt === null) return\n if (record !== undefined && record.expiresAt > Date.now()) {\n await this.ctx.storage.setAlarm(record.expiresAt)\n return\n }\n await this.ctx.storage.deleteAll()\n }\n}\n\nfunction unexpiredRecord(record: StoredValue | undefined): StoredValue | undefined {\n if (record === undefined) return undefined\n if (record.expiresAt !== null && record.expiresAt <= Date.now()) return undefined\n return record\n}\n\nfunction expiry(ttl: number): number {\n const expiresAt = Date.now() + ttl * MILLISECONDS_PER_SECOND\n if (!Number.isSafeInteger(ttl) || ttl < MIN_TTL_SECONDS || !Number.isSafeInteger(expiresAt)) {\n throw new InvalidTtlError()\n }\n return expiresAt\n}\n\nfunction nextCount(value: string): number {\n const count = Number(value)\n if (String(count) !== value || !Number.isSafeInteger(count) || !Number.isSafeInteger(count + 1)) {\n throw new InvalidCounterError()\n }\n return count + 1\n}\n"],"mappings":";;;;AAKA,MAAM,kBAAkB;AACxB,MAAM,0BAA0B;;AAKhC,IAAa,kBAAb,cAAqC,SAAS;CAC5C,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,cAAc;EACZ,MAAM,mEAAmE;CAC3E;AACF;;AAGA,IAAa,sBAAb,cAAyC,SAAS;CAChD,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAE1B,cAAc;EACZ,MAAM,6DAA6D;CACrE;AACF;;;;;;AAOA,IAAa,uBAAb,cAA0C,cAAc;;CAEtD,MAAM,MAA8B;EAElC,OAAO,gBAAgB,MADF,KAAK,IAAI,QAAQ,IAAiB,OAAO,CACjC,CAAC,EAAE,SAAS;CAC3C;;CAGA,MAAM,IAAI,OAAe,KAA6B;EACpD,MAAM,YAAY,QAAQ,SAAY,OAAO,OAAO,GAAG;EACvD,MAAM,KAAK,IAAI,QAAQ,YAAY,OAAO,gBAAgB;GACxD,MAAM,YAAY,IAAI,SAAS;IAAE;IAAO;GAAU,CAAC;GACnD,IAAI,cAAc,MAAM,MAAM,YAAY,YAAY;QACjD,MAAM,YAAY,SAAS,SAAS;EAC3C,CAAC;CACH;;CAGA,MAAM,SAAwB;EAC5B,MAAM,KAAK,IAAI,QAAQ,UAAU;CACnC;;CAGA,MAAM,eAAuC;EAE3C,MAAM,SAAS,MAAM,KAAK,IAAI,QAAQ,IAAiB,OAAO;EAC9D,MAAM,KAAK,IAAI,QAAQ,UAAU;EACjC,OAAO,gBAAgB,MAAM,CAAC,EAAE,SAAS;CAC3C;;CAGA,MAAM,UAAU,KAA8B;EAC5C,MAAM,YAAY,OAAO,GAAG;EAC5B,OAAO,KAAK,IAAI,QAAQ,YAAY,OAAO,gBAAgB;GACzD,MAAM,SAAS,gBAAgB,MAAM,YAAY,IAAiB,OAAO,CAAC;GAC1E,MAAM,QAAQ,WAAW,SAAY,IAAI,UAAU,OAAO,KAAK;GAC/D,MAAM,YAAY,IAAI,SAAS;IAC7B,OAAO,OAAO,KAAK;IACnB,WAAW,WAAW,SAAY,YAAY,OAAO;GACvD,CAAC;GACD,IAAI,WAAW,QAAW,MAAM,YAAY,SAAS,SAAS;GAC9D,OAAO;EACT,CAAC;CACH;;CAGA,MAAe,QAAuB;EACpC,MAAM,SAAS,MAAM,KAAK,IAAI,QAAQ,IAAiB,OAAO;EAC9D,IAAI,QAAQ,cAAc,MAAM;EAChC,IAAI,WAAW,UAAa,OAAO,YAAY,KAAK,IAAI,GAAG;GACzD,MAAM,KAAK,IAAI,QAAQ,SAAS,OAAO,SAAS;GAChD;EACF;EACA,MAAM,KAAK,IAAI,QAAQ,UAAU;CACnC;AACF;AAEA,SAAS,gBAAgB,QAA0D;CACjF,IAAI,WAAW,QAAW,OAAO;CACjC,IAAI,OAAO,cAAc,QAAQ,OAAO,aAAa,KAAK,IAAI,GAAG,OAAO;CACxE,OAAO;AACT;AAEA,SAAS,OAAO,KAAqB;CACnC,MAAM,YAAY,KAAK,IAAI,IAAI,MAAM;CACrC,IAAI,CAAC,OAAO,cAAc,GAAG,KAAK,MAAM,mBAAmB,CAAC,OAAO,cAAc,SAAS,GACxF,MAAM,IAAI,gBAAgB;CAE5B,OAAO;AACT;AAEA,SAAS,UAAU,OAAuB;CACxC,MAAM,QAAQ,OAAO,KAAK;CAC1B,IAAI,OAAO,KAAK,MAAM,SAAS,CAAC,OAAO,cAAc,KAAK,KAAK,CAAC,OAAO,cAAc,QAAQ,CAAC,GAC5F,MAAM,IAAI,oBAAoB;CAEhC,OAAO,QAAQ;AACjB"}
@@ -1,6 +1,6 @@
1
- import { C as Event, T as MessageId, s as ITransaction, v as IMessageStore } from "./index-BN_tjxME.js";
2
- import { E as DatabaseFailure, T as DatabaseError, w as D1ConstraintKind } from "./index-ToddX0m3.js";
3
- import { n as DrizzleSchema, t as eventCollector } from "./unit-of-work-context-CbUJGHlL.js";
1
+ import { C as Event, T as MessageId, s as ITransaction, v as IMessageStore } from "./index-Bdyh3_xC.js";
2
+ import { E as DatabaseFailure, T as DatabaseError, w as D1ConstraintKind } from "./index-B9Y1hVh6.js";
3
+ import { n as DrizzleSchema, t as eventCollector } from "./unit-of-work-context-ml9L9cyY.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
@@ -1,6 +1,6 @@
1
- import { m as AppError, p as DatabaseError } from "./core-MjwyZJ4i.js";
2
- import { i as MessageAlreadyProcessedError } from "./application-rpZoTfzU.js";
3
- import { i as stageD1Statement, n as eventCollector, r as runWithUnitOfWorkContext, t as collectD1Statements } from "./unit-of-work-context-BLNj72v0.js";
1
+ import { A as AppError, p as DatabaseError } from "./core-CnwWj4MY.js";
2
+ import { i as MessageAlreadyProcessedError } from "./application-DEerezCl.js";
3
+ import { i as stageD1Statement, n as eventCollector, r as runWithUnitOfWorkContext, t as collectD1Statements } from "./unit-of-work-context-C7vz5OYS.js";
4
4
  import { DrizzleQueryError } from "drizzle-orm/errors";
5
5
  import { eq, sql } from "drizzle-orm";
6
6
  import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";