better-ship 0.16.0 → 0.18.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 (129) hide show
  1. package/dist/{application-Cf9SER-i.js → application-CNdzHIXk.js} +5 -7
  2. package/dist/{application-Cf9SER-i.js.map → application-CNdzHIXk.js.map} +1 -1
  3. package/dist/application.d.ts +2 -2
  4. package/dist/application.js +1 -1
  5. package/dist/better-auth/apple-client-secret.d.ts +1 -1
  6. package/dist/better-auth/apple-client-secret.js +2 -2
  7. package/dist/better-auth/apple-client-secret.js.map +1 -1
  8. package/dist/better-auth/auth-client.js +1 -1
  9. package/dist/better-auth/rate-limit.d.ts +1 -1
  10. package/dist/better-auth/rate-limit.js +4 -6
  11. package/dist/better-auth/rate-limit.js.map +1 -1
  12. package/dist/better-auth.js +1 -1
  13. package/dist/billing/client.d.ts +86 -0
  14. package/dist/billing/client.d.ts.map +1 -0
  15. package/dist/billing/client.js +77 -0
  16. package/dist/billing/client.js.map +1 -0
  17. package/dist/cloudflare/auth-secondary-storage.d.ts +1 -1
  18. package/dist/cloudflare/auth-secondary-storage.js +1 -1
  19. package/dist/cloudflare/durable-object-client.js +1 -1
  20. package/dist/cloudflare/durable-object-errors.d.ts +1 -1
  21. package/dist/cloudflare/durable-object-errors.js +1 -1
  22. package/dist/cloudflare/kv-client.d.ts +31 -0
  23. package/dist/cloudflare/kv-client.d.ts.map +1 -0
  24. package/dist/cloudflare/kv-client.js +62 -0
  25. package/dist/cloudflare/kv-client.js.map +1 -0
  26. package/dist/cloudflare/kv-errors.d.ts +52 -0
  27. package/dist/cloudflare/kv-errors.d.ts.map +1 -0
  28. package/dist/cloudflare/kv-errors.js +144 -0
  29. package/dist/cloudflare/kv-errors.js.map +1 -0
  30. package/dist/cloudflare/queue.d.ts +2 -2
  31. package/dist/cloudflare/queue.d.ts.map +1 -1
  32. package/dist/cloudflare/queue.js +10 -12
  33. package/dist/cloudflare/queue.js.map +1 -1
  34. package/dist/cloudflare/r2-client.d.ts +70 -0
  35. package/dist/cloudflare/r2-client.d.ts.map +1 -0
  36. package/dist/cloudflare/r2-client.js +153 -0
  37. package/dist/cloudflare/r2-client.js.map +1 -0
  38. package/dist/cloudflare/r2-errors.d.ts +34 -0
  39. package/dist/cloudflare/r2-errors.d.ts.map +1 -0
  40. package/dist/cloudflare/r2-errors.js +90 -0
  41. package/dist/cloudflare/r2-errors.js.map +1 -0
  42. package/dist/cloudflare/scheduled.d.ts +1 -1
  43. package/dist/cloudflare/scheduled.js +2 -2
  44. package/dist/cloudflare/scheduled.js.map +1 -1
  45. package/dist/cloudflare.d.ts +10 -5
  46. package/dist/cloudflare.d.ts.map +1 -1
  47. package/dist/cloudflare.js +21 -10
  48. package/dist/cloudflare.js.map +1 -1
  49. package/dist/{core-ClSP5-V9.js → core-UUW2unJd.js} +106 -195
  50. package/dist/core-UUW2unJd.js.map +1 -0
  51. package/dist/core.d.ts +2 -2
  52. package/dist/core.js +2 -2
  53. package/dist/http.d.ts +2 -2
  54. package/dist/http.js +3 -3
  55. package/dist/http.js.map +1 -1
  56. package/dist/{index-Cchdg2aQ.d.ts → index-CYHXOH08.d.ts} +2 -2
  57. package/dist/{index-Cchdg2aQ.d.ts.map → index-CYHXOH08.d.ts.map} +1 -1
  58. package/dist/{index-DvJUcSW1.d.ts → index-DUFS0SJ_.d.ts} +41 -102
  59. package/dist/index-DUFS0SJ_.d.ts.map +1 -0
  60. package/dist/{outbox-repository-options-ICOF8g2h.js → outbox-repository-options-CgdqU20T.js} +2 -2
  61. package/dist/{outbox-repository-options-ICOF8g2h.js.map → outbox-repository-options-CgdqU20T.js.map} +1 -1
  62. package/dist/{outbox-repository-options-AkihEaik.d.ts → outbox-repository-options-DjBgFhMi.d.ts} +3 -3
  63. package/dist/{outbox-repository-options-AkihEaik.d.ts.map → outbox-repository-options-DjBgFhMi.d.ts.map} +1 -1
  64. package/dist/postgres.d.ts +10 -5
  65. package/dist/postgres.d.ts.map +1 -1
  66. package/dist/postgres.js +21 -10
  67. package/dist/postgres.js.map +1 -1
  68. package/dist/posthog/metrics.d.ts +1 -1
  69. package/dist/posthog/server.d.ts +2 -2
  70. package/dist/posthog/server.js +1 -1
  71. package/dist/resend.d.ts +3 -3
  72. package/dist/resend.js +2 -2
  73. package/dist/sentry/client.d.ts +1 -1
  74. package/dist/sentry/client.d.ts.map +1 -1
  75. package/dist/sentry/client.js +10 -4
  76. package/dist/sentry/client.js.map +1 -1
  77. package/dist/sentry/server.d.ts +2 -2
  78. package/dist/sentry/server.js +5 -5
  79. package/dist/sentry/server.js.map +1 -1
  80. package/dist/stripe/payment-service.d.ts +52 -0
  81. package/dist/stripe/payment-service.d.ts.map +1 -0
  82. package/dist/stripe/payment-service.error.d.ts +13 -0
  83. package/dist/stripe/payment-service.error.d.ts.map +1 -0
  84. package/dist/stripe/payment-service.error.js +30 -0
  85. package/dist/stripe/payment-service.error.js.map +1 -0
  86. package/dist/stripe/payment-service.js +72 -0
  87. package/dist/stripe/payment-service.js.map +1 -0
  88. package/dist/stripe/payment-webhook.d.ts +18 -0
  89. package/dist/stripe/payment-webhook.d.ts.map +1 -0
  90. package/dist/stripe/payment-webhook.js +47 -0
  91. package/dist/stripe/payment-webhook.js.map +1 -0
  92. package/dist/tanstack.d.ts +1 -1
  93. package/dist/tanstack.js +4 -4
  94. package/dist/tanstack.js.map +1 -1
  95. package/dist/testing/outbox-repository.d.ts +2 -2
  96. package/dist/testing/payment-service.d.ts +10 -0
  97. package/dist/testing/payment-service.d.ts.map +1 -0
  98. package/dist/testing/payment-service.js +76 -0
  99. package/dist/testing/payment-service.js.map +1 -0
  100. package/dist/{webhook-verification-D6aGQfNi.js → webhook-verification-BBmjkHgI.js} +2 -2
  101. package/dist/{webhook-verification-D6aGQfNi.js.map → webhook-verification-BBmjkHgI.js.map} +1 -1
  102. package/dist/{webhook-verification-BcQQG-vY.d.ts → webhook-verification-BuxPzdr4.d.ts} +2 -2
  103. package/dist/{webhook-verification-BcQQG-vY.d.ts.map → webhook-verification-BuxPzdr4.d.ts.map} +1 -1
  104. package/package.json +15 -6
  105. package/src/application/outbox-relay.ts +3 -1
  106. package/src/core/console-log-sink.ts +65 -0
  107. package/src/core/index.ts +1 -4
  108. package/src/core/logger.ts +80 -281
  109. package/src/infrastructure/better-auth/apple-client-secret.ts +1 -1
  110. package/src/infrastructure/better-auth/rate-limit.ts +3 -1
  111. package/src/infrastructure/billing/client.ts +173 -0
  112. package/src/infrastructure/cloudflare/d1-database-execution.ts +27 -6
  113. package/src/infrastructure/cloudflare/kv-client.ts +79 -0
  114. package/src/infrastructure/cloudflare/kv-errors.ts +157 -0
  115. package/src/infrastructure/cloudflare/queue/queue-consumer.ts +8 -9
  116. package/src/infrastructure/cloudflare/r2-client.ts +162 -0
  117. package/src/infrastructure/cloudflare/r2-errors.ts +111 -0
  118. package/src/infrastructure/cloudflare/scheduled/scheduled-handler.ts +1 -1
  119. package/src/infrastructure/http/webhook-handler.ts +1 -1
  120. package/src/infrastructure/postgres/postgres-database-execution.ts +27 -6
  121. package/src/infrastructure/sentry/client.ts +6 -3
  122. package/src/infrastructure/sentry/server.ts +4 -4
  123. package/src/infrastructure/stripe/payment-service.error.ts +42 -0
  124. package/src/infrastructure/stripe/payment-service.ts +129 -0
  125. package/src/infrastructure/stripe/payment-webhook.ts +49 -0
  126. package/src/infrastructure/tanstack/resolve-failure.ts +3 -2
  127. package/src/infrastructure/testing/payment-service-contract.ts +91 -0
  128. package/dist/core-ClSP5-V9.js.map +0 -1
  129. package/dist/index-DvJUcSW1.d.ts.map +0 -1
@@ -0,0 +1,153 @@
1
+ import { nt as AppError, v as isAbortError } from "../core-UUW2unJd.js";
2
+ import { R2Error, R2JsonError } from "./r2-errors.js";
3
+
4
+ //#region src/infrastructure/cloudflare/r2-client.ts
5
+ /**
6
+ * R2 access with one attempt per operation and typed failures. Inject the bucket at the composition root.
7
+ * Callers own retries, stream replay, and recovery. The client does not log or buffer streamed uploads.
8
+ */
9
+ var R2Client = class {
10
+ bucket;
11
+ constructor(bucket) {
12
+ this.bucket = bucket;
13
+ }
14
+ /** Read native metadata, or return null when the object does not exist. */
15
+ head(key) {
16
+ return callR2("head", () => this.bucket.head(key));
17
+ }
18
+ /** Return a native body without consuming it. The consumer owns later body-read failures. */
19
+ async get(key, options) {
20
+ const object = await callR2("get", () => this.bucket.get(key, options));
21
+ if (object === null) return { status: "missing" };
22
+ if (!("body" in object)) return {
23
+ status: "precondition-failed",
24
+ object
25
+ };
26
+ return {
27
+ status: "found",
28
+ object
29
+ };
30
+ }
31
+ /** Buffer one stored JSON body. Read failures throw R2Error; invalid JSON throws R2JsonError. */
32
+ async getJson(key) {
33
+ const text = await callR2("getJson", async () => {
34
+ const object = await this.bucket.get(key);
35
+ return object === null ? null : await object.text();
36
+ });
37
+ if (text === null) return { status: "missing" };
38
+ try {
39
+ return {
40
+ status: "found",
41
+ value: JSON.parse(text)
42
+ };
43
+ } catch (cause) {
44
+ throw new R2JsonError({
45
+ operation: "decode",
46
+ cause
47
+ });
48
+ }
49
+ }
50
+ /** Write once, including conditional writes. Pass streams directly to the binding. */
51
+ async put(key, body, options) {
52
+ const object = await callR2("put", () => this.bucket.put(key, body, options));
53
+ return object === null ? { status: "precondition-failed" } : {
54
+ status: "stored",
55
+ object
56
+ };
57
+ }
58
+ /** Serialize once and default the content type to application/json without changing caller options. */
59
+ async putJson(key, value, options) {
60
+ let text;
61
+ try {
62
+ text = JSON.stringify(value);
63
+ } catch (cause) {
64
+ throw new R2JsonError({
65
+ operation: "encode",
66
+ cause
67
+ });
68
+ }
69
+ if (text === void 0) throw new R2JsonError({
70
+ operation: "encode",
71
+ cause: void 0
72
+ });
73
+ return this.put(key, text, {
74
+ ...options,
75
+ httpMetadata: jsonMetadata(options?.httpMetadata)
76
+ });
77
+ }
78
+ /** Delete keys in one binding call. Missing keys succeed; R2 enforces its batch limit. */
79
+ delete(keys) {
80
+ return callR2("delete", () => this.bucket.delete(keys));
81
+ }
82
+ /** Return one native page with the requested metadata, cursor, and delimiter results. */
83
+ list(options) {
84
+ return callR2("list", () => this.bucket.list(options));
85
+ }
86
+ /** Create once. A failed response can leave an upload whose ID the caller never received. */
87
+ async createMultipartUpload(key, options) {
88
+ const upload = await callR2("createMultipartUpload", () => this.bucket.createMultipartUpload(key, options));
89
+ return new R2MultipartClient(upload);
90
+ }
91
+ /** Construct a handle without I/O. Each subsequent operation checks whether the upload exists. */
92
+ resumeMultipartUpload(key, uploadId) {
93
+ return new R2MultipartClient(this.bucket.resumeMultipartUpload(key, uploadId));
94
+ }
95
+ };
96
+ /** An upload handle that binds its key and ID once. Every operation makes one attempt. */
97
+ var R2MultipartClient = class {
98
+ upload;
99
+ constructor(upload) {
100
+ this.upload = upload;
101
+ }
102
+ /** The destination object key. */
103
+ get key() {
104
+ return this.upload.key;
105
+ }
106
+ /** The provider ID to persist when the application needs to resume this upload. */
107
+ get uploadId() {
108
+ return this.upload.uploadId;
109
+ }
110
+ /** Upload one part without buffering or replaying its body. */
111
+ uploadPart(partNumber, body, options) {
112
+ return callR2("uploadPart", () => this.upload.uploadPart(partNumber, body, options));
113
+ }
114
+ /** Sort a copy of the parts and return native metadata after completion. Callers own uncertain outcomes. */
115
+ complete(parts) {
116
+ return callR2("complete", () => this.upload.complete([...parts].sort((a, b) => a.partNumber - b.partNumber)));
117
+ }
118
+ /** Ensure no active upload remains. NoSuchUpload succeeds; a completed object remains intact. */
119
+ async abort() {
120
+ try {
121
+ await callR2("abort", () => this.upload.abort());
122
+ } catch (cause) {
123
+ if (cause instanceof R2Error && cause.operation === "abort" && cause.r2?.code === 10024) return;
124
+ throw cause;
125
+ }
126
+ }
127
+ };
128
+ async function callR2(operation, work) {
129
+ try {
130
+ return await work();
131
+ } catch (cause) {
132
+ if (isAbortError(cause) || cause instanceof AppError) throw cause;
133
+ throw new R2Error({
134
+ operation,
135
+ cause
136
+ });
137
+ }
138
+ }
139
+ function jsonMetadata(metadata) {
140
+ if (metadata instanceof Headers) {
141
+ const headers = new Headers(metadata);
142
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
143
+ return headers;
144
+ }
145
+ return {
146
+ ...metadata,
147
+ contentType: metadata?.contentType ?? "application/json"
148
+ };
149
+ }
150
+
151
+ //#endregion
152
+ export { R2Client, R2MultipartClient };
153
+ //# sourceMappingURL=r2-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2-client.js","names":[],"sources":["../../src/infrastructure/cloudflare/r2-client.ts"],"sourcesContent":["import { AppError, isAbortError } from '@/core'\n\nimport { R2Error, R2JsonError, type R2Operation } from './r2-errors.ts'\n\n/** Values accepted by the R2 binding. */\nexport type R2Value = Parameters<R2Bucket['put']>[1]\n\n/** Missing objects, failed conditions, and returned bodies are distinct outcomes. */\nexport type R2GetResult =\n | { status: 'missing' }\n | { status: 'precondition-failed'; object: R2Object }\n | { status: 'found'; object: R2ObjectBody }\n\n/** A failed condition does not establish whether an earlier attempt committed. */\nexport type R2PutResult = { status: 'precondition-failed' } | { status: 'stored'; object: R2Object }\n\n/** Untrusted JSON data, with absence separate from a stored null. The application supplies its schema. */\nexport type R2JsonResult = { status: 'missing' } | { status: 'found'; value: unknown }\n\n/**\n * R2 access with one attempt per operation and typed failures. Inject the bucket at the composition root.\n * Callers own retries, stream replay, and recovery. The client does not log or buffer streamed uploads.\n */\nexport class R2Client {\n constructor(private readonly bucket: R2Bucket) {}\n\n /** Read native metadata, or return null when the object does not exist. */\n head(key: string): Promise<R2Object | null> {\n return callR2('head', () => this.bucket.head(key))\n }\n\n /** Return a native body without consuming it. The consumer owns later body-read failures. */\n async get(key: string, options?: R2GetOptions): Promise<R2GetResult> {\n const object = await callR2('get', () => this.bucket.get(key, options))\n if (object === null) return { status: 'missing' }\n if (!('body' in object)) return { status: 'precondition-failed', object }\n return { status: 'found', object }\n }\n\n /** Buffer one stored JSON body. Read failures throw R2Error; invalid JSON throws R2JsonError. */\n async getJson(key: string): Promise<R2JsonResult> {\n const text = await callR2('getJson', async () => {\n const object = await this.bucket.get(key)\n return object === null ? null : await object.text()\n })\n if (text === null) return { status: 'missing' }\n try {\n return { status: 'found', value: JSON.parse(text) }\n } catch (cause) {\n throw new R2JsonError({ operation: 'decode', cause })\n }\n }\n\n /** Write once, including conditional writes. Pass streams directly to the binding. */\n async put(key: string, body: R2Value, options?: R2PutOptions): Promise<R2PutResult> {\n const object = await callR2('put', () => this.bucket.put(key, body, options))\n return object === null ? { status: 'precondition-failed' } : { status: 'stored', object }\n }\n\n /** Serialize once and default the content type to application/json without changing caller options. */\n async putJson(\n key: string,\n // oxlint-disable-next-line anti-slop/no-unknown-parameters -- This serialization boundary accepts any value and rejects values without a JSON representation.\n value: unknown,\n options?: R2PutOptions,\n ): Promise<R2PutResult> {\n let text: string | undefined\n try {\n text = JSON.stringify(value)\n } catch (cause) {\n throw new R2JsonError({ operation: 'encode', cause })\n }\n if (text === undefined) throw new R2JsonError({ operation: 'encode', cause: undefined })\n return this.put(key, text, { ...options, httpMetadata: jsonMetadata(options?.httpMetadata) })\n }\n\n /** Delete keys in one binding call. Missing keys succeed; R2 enforces its batch limit. */\n delete(keys: string | string[]): Promise<void> {\n return callR2('delete', () => this.bucket.delete(keys))\n }\n\n /** Return one native page with the requested metadata, cursor, and delimiter results. */\n list(options?: R2ListOptions): Promise<R2Objects> {\n return callR2('list', () => this.bucket.list(options))\n }\n\n /** Create once. A failed response can leave an upload whose ID the caller never received. */\n async createMultipartUpload(\n key: string,\n options?: R2MultipartOptions,\n ): Promise<R2MultipartClient> {\n const upload = await callR2('createMultipartUpload', () =>\n this.bucket.createMultipartUpload(key, options),\n )\n return new R2MultipartClient(upload)\n }\n\n /** Construct a handle without I/O. Each subsequent operation checks whether the upload exists. */\n resumeMultipartUpload(key: string, uploadId: string): R2MultipartClient {\n return new R2MultipartClient(this.bucket.resumeMultipartUpload(key, uploadId))\n }\n}\n\n/** An upload handle that binds its key and ID once. Every operation makes one attempt. */\nexport class R2MultipartClient {\n constructor(private readonly upload: R2MultipartUpload) {}\n\n /** The destination object key. */\n get key(): string {\n return this.upload.key\n }\n\n /** The provider ID to persist when the application needs to resume this upload. */\n get uploadId(): string {\n return this.upload.uploadId\n }\n\n /** Upload one part without buffering or replaying its body. */\n uploadPart(\n partNumber: number,\n body: Exclude<R2Value, null>,\n options?: R2UploadPartOptions,\n ): Promise<R2UploadedPart> {\n return callR2('uploadPart', () => this.upload.uploadPart(partNumber, body, options))\n }\n\n /** Sort a copy of the parts and return native metadata after completion. Callers own uncertain outcomes. */\n complete(parts: ReadonlyArray<R2UploadedPart>): Promise<R2Object> {\n return callR2('complete', () =>\n this.upload.complete([...parts].sort((a, b) => a.partNumber - b.partNumber)),\n )\n }\n\n /** Ensure no active upload remains. NoSuchUpload succeeds; a completed object remains intact. */\n async abort(): Promise<void> {\n try {\n await callR2('abort', () => this.upload.abort())\n } catch (cause) {\n if (cause instanceof R2Error && cause.operation === 'abort' && cause.r2?.code === 10024)\n return\n throw cause\n }\n }\n}\n\nasync function callR2<T>(operation: R2Operation, work: () => Promise<T>): Promise<T> {\n try {\n return await work()\n } catch (cause) {\n if (isAbortError(cause) || cause instanceof AppError) throw cause\n throw new R2Error({ operation, cause })\n }\n}\n\nfunction jsonMetadata(metadata: R2HTTPMetadata | Headers | undefined): R2HTTPMetadata | Headers {\n if (metadata instanceof Headers) {\n const headers = new Headers(metadata)\n if (!headers.has('content-type')) headers.set('content-type', 'application/json')\n return headers\n }\n return { ...metadata, contentType: metadata?.contentType ?? 'application/json' }\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { Xt as AppError, Zt as FailureClassification } from "../index-DUFS0SJ_.js";
2
+ //#region src/infrastructure/cloudflare/r2-errors.d.ts
3
+ /** The storage operation that failed. */
4
+ export type R2Operation = 'head' | 'get' | 'getJson' | 'put' | 'delete' | 'list' | 'createMultipartUpload' | 'uploadPart' | 'complete' | 'abort';
5
+ /** Provider diagnostics from the outer failure, without request bodies or credentials. */
6
+ export type R2Failure = {
7
+ readonly code: number;
8
+ readonly action?: string | undefined;
9
+ };
10
+ /** A raw binding failure after one attempt. Classification does not establish whether a mutation committed. */
11
+ export declare class R2Error extends AppError {
12
+ readonly _tag = "R2Error";
13
+ readonly classification: FailureClassification;
14
+ readonly operation: R2Operation;
15
+ readonly r2?: R2Failure | undefined;
16
+ readonly cause: unknown;
17
+ constructor(input: {
18
+ operation: R2Operation;
19
+ cause: unknown;
20
+ });
21
+ }
22
+ /** JSON serialization or syntax failed. Binding and body-read failures use R2Error. */
23
+ export declare class R2JsonError extends AppError {
24
+ readonly _tag = "R2JsonError";
25
+ readonly classification = "terminal";
26
+ readonly operation: 'encode' | 'decode';
27
+ readonly cause: unknown;
28
+ constructor(input: {
29
+ operation: 'encode' | 'decode';
30
+ cause: unknown;
31
+ });
32
+ }
33
+ //#endregion
34
+ //# sourceMappingURL=r2-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2-errors.d.ts","names":[],"sources":["../../src/infrastructure/cloudflare/r2-errors.ts"],"mappings":";;;YAKY;;YAaA;WACD;WACA;;;qBA8DE,gBAAgB;WAClB;WACA,gBAAgB;WAChB,WAAW;WACX,KAAK;WACI;EAElB,YAAY;IAAS,WAAW;IAAa;;;;qBAUlC,oBAAoB;WACtB;WACA;WACA;WACS;EAElB,YAAY;IAAS;IAAgC"}
@@ -0,0 +1,90 @@
1
+ import { nt as AppError, u as classifyBoundaryError, v as isAbortError } from "../core-UUW2unJd.js";
2
+ import { z } from "zod";
3
+
4
+ //#region src/infrastructure/cloudflare/r2-errors.ts
5
+ const R2FailureSchema = z.object({
6
+ code: z.number().int().positive(),
7
+ action: z.string().optional()
8
+ });
9
+ const classifications = /* @__PURE__ */ new Map([
10
+ [10001, "transient"],
11
+ [10043, "transient"],
12
+ [10054, "transient"],
13
+ [10058, "transient"],
14
+ [10013, "transient"],
15
+ [10002, "terminal"],
16
+ [10003, "terminal"],
17
+ [10018, "terminal"],
18
+ [10035, "terminal"],
19
+ [10042, "terminal"],
20
+ [10005, "terminal"],
21
+ [10006, "terminal"],
22
+ [10008, "terminal"],
23
+ [10009, "terminal"],
24
+ [10073, "terminal"],
25
+ [10007, "terminal"],
26
+ [10020, "terminal"],
27
+ [100100, "terminal"],
28
+ [10012, "terminal"],
29
+ [10069, "terminal"],
30
+ [10033, "terminal"],
31
+ [10014, "terminal"],
32
+ [10037, "terminal"],
33
+ [10039, "terminal"],
34
+ [10031, "terminal"],
35
+ [10011, "terminal"],
36
+ [10024, "terminal"],
37
+ [10025, "terminal"],
38
+ [10048, "terminal"]
39
+ ]);
40
+ function extractR2Failure(cause) {
41
+ if (!(cause instanceof Error)) return void 0;
42
+ const parsed = R2FailureSchema.safeParse(cause);
43
+ if (parsed.success) return parsed.data;
44
+ const match = /\((\d{5,6})\)\s*$/.exec(cause.message);
45
+ return match?.[1] === void 0 ? void 0 : { code: Number(match[1]) };
46
+ }
47
+ function classifyR2Code(cause) {
48
+ const failure = extractR2Failure(cause);
49
+ return failure === void 0 ? void 0 : classifications.get(failure.code) ?? "unknown";
50
+ }
51
+ function classifyR2Error(cause) {
52
+ if (isAbortError(cause)) return "terminal";
53
+ if (cause instanceof AppError) return cause.classification;
54
+ return classifyBoundaryError({
55
+ cause,
56
+ classifyBoundary: classifyR2Code,
57
+ includeCloudflare: true
58
+ });
59
+ }
60
+ /** A raw binding failure after one attempt. Classification does not establish whether a mutation committed. */
61
+ var R2Error = class extends AppError {
62
+ _tag = "R2Error";
63
+ classification;
64
+ operation;
65
+ r2;
66
+ cause;
67
+ constructor(input) {
68
+ super(`R2 ${input.operation} failed`, { cause: input.cause });
69
+ this.operation = input.operation;
70
+ this.cause = input.cause;
71
+ this.r2 = extractR2Failure(input.cause);
72
+ this.classification = classifyR2Error(input.cause);
73
+ }
74
+ };
75
+ /** JSON serialization or syntax failed. Binding and body-read failures use R2Error. */
76
+ var R2JsonError = class extends AppError {
77
+ _tag = "R2JsonError";
78
+ classification = "terminal";
79
+ operation;
80
+ cause;
81
+ constructor(input) {
82
+ super(`R2 JSON ${input.operation} failed`, { cause: input.cause });
83
+ this.operation = input.operation;
84
+ this.cause = input.cause;
85
+ }
86
+ };
87
+
88
+ //#endregion
89
+ export { R2Error, R2JsonError };
90
+ //# sourceMappingURL=r2-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2-errors.js","names":[],"sources":["../../src/infrastructure/cloudflare/r2-errors.ts"],"sourcesContent":["import { z } from 'zod'\n\nimport { AppError, classifyBoundaryError, isAbortError, type FailureClassification } from '@/core'\n\n/** The storage operation that failed. */\nexport type R2Operation =\n | 'head'\n | 'get'\n | 'getJson'\n | 'put'\n | 'delete'\n | 'list'\n | 'createMultipartUpload'\n | 'uploadPart'\n | 'complete'\n | 'abort'\n\n/** Provider diagnostics from the outer failure, without request bodies or credentials. */\nexport type R2Failure = {\n readonly code: number\n readonly action?: string | undefined\n}\n\nconst R2FailureSchema = z.object({\n code: z.number().int().positive(),\n action: z.string().optional(),\n})\n\n// R2 codes describe failures; the caller owns retry decisions and repeat safety.\nconst classifications: ReadonlyMap<number, FailureClassification> = new Map([\n [10001, 'transient'],\n [10043, 'transient'],\n [10054, 'transient'],\n [10058, 'transient'],\n [10013, 'transient'],\n [10002, 'terminal'],\n [10003, 'terminal'],\n [10018, 'terminal'],\n [10035, 'terminal'],\n [10042, 'terminal'],\n [10005, 'terminal'],\n [10006, 'terminal'],\n [10008, 'terminal'],\n [10009, 'terminal'],\n [10073, 'terminal'],\n [10007, 'terminal'],\n [10020, 'terminal'],\n [100100, 'terminal'],\n [10012, 'terminal'],\n [10069, 'terminal'],\n [10033, 'terminal'],\n [10014, 'terminal'],\n [10037, 'terminal'],\n [10039, 'terminal'],\n [10031, 'terminal'],\n [10011, 'terminal'],\n [10024, 'terminal'],\n [10025, 'terminal'],\n [10048, 'terminal'],\n])\n\nfunction extractR2Failure(cause: unknown): R2Failure | undefined {\n if (!(cause instanceof Error)) return undefined\n const parsed = R2FailureSchema.safeParse(cause)\n if (parsed.success) return parsed.data\n // Cloudflare documents the numeric suffix for bindings that omit structured fields.\n const match = /\\((\\d{5,6})\\)\\s*$/.exec(cause.message)\n return match?.[1] === undefined ? undefined : { code: Number(match[1]) }\n}\n\nfunction classifyR2Code(cause: unknown): FailureClassification | undefined {\n const failure = extractR2Failure(cause)\n return failure === undefined ? undefined : (classifications.get(failure.code) ?? 'unknown')\n}\n\nfunction classifyR2Error(cause: unknown): FailureClassification {\n if (isAbortError(cause)) return 'terminal'\n if (cause instanceof AppError) return cause.classification\n return classifyBoundaryError({ cause, classifyBoundary: classifyR2Code, includeCloudflare: true })\n}\n\n/** A raw binding failure after one attempt. Classification does not establish whether a mutation committed. */\nexport class R2Error extends AppError {\n readonly _tag = 'R2Error'\n readonly classification: FailureClassification\n readonly operation: R2Operation\n readonly r2?: R2Failure | undefined\n override readonly cause: unknown\n\n constructor(input: { operation: R2Operation; cause: unknown }) {\n super(`R2 ${input.operation} failed`, { cause: input.cause })\n this.operation = input.operation\n this.cause = input.cause\n this.r2 = extractR2Failure(input.cause)\n this.classification = classifyR2Error(input.cause)\n }\n}\n\n/** JSON serialization or syntax failed. Binding and body-read failures use R2Error. */\nexport class R2JsonError extends AppError {\n readonly _tag = 'R2JsonError'\n readonly classification = 'terminal'\n readonly operation: 'encode' | 'decode'\n override readonly cause: unknown\n\n constructor(input: { operation: 'encode' | 'decode'; cause: unknown }) {\n super(`R2 JSON ${input.operation} failed`, { cause: input.cause })\n this.operation = input.operation\n this.cause = input.cause\n }\n}\n"],"mappings":";;;;AAuBA,MAAM,kBAAkB,EAAE,OAAO;CAC/B,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAChC,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;AAC9B,CAAC;AAGD,MAAM,kCAA8D,IAAI,IAAI;CAC1E,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,WAAW;CACnB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,QAAQ,UAAU;CACnB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;CAClB,CAAC,OAAO,UAAU;AACpB,CAAC;AAED,SAAS,iBAAiB,OAAuC;CAC/D,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CACtC,MAAM,SAAS,gBAAgB,UAAU,KAAK;CAC9C,IAAI,OAAO,SAAS,OAAO,OAAO;CAElC,MAAM,QAAQ,oBAAoB,KAAK,MAAM,OAAO;CACpD,OAAO,QAAQ,OAAO,SAAY,SAAY,EAAE,MAAM,OAAO,MAAM,EAAE,EAAE;AACzE;AAEA,SAAS,eAAe,OAAmD;CACzE,MAAM,UAAU,iBAAiB,KAAK;CACtC,OAAO,YAAY,SAAY,SAAa,gBAAgB,IAAI,QAAQ,IAAI,KAAK;AACnF;AAEA,SAAS,gBAAgB,OAAuC;CAC9D,IAAI,aAAa,KAAK,GAAG,OAAO;CAChC,IAAI,iBAAiB,UAAU,OAAO,MAAM;CAC5C,OAAO,sBAAsB;EAAE;EAAO,kBAAkB;EAAgB,mBAAmB;CAAK,CAAC;AACnG;;AAGA,IAAa,UAAb,cAA6B,SAAS;CACpC,AAAS,OAAO;CAChB,AAAS;CACT,AAAS;CACT,AAAS;CACT,AAAkB;CAElB,YAAY,OAAmD;EAC7D,MAAM,MAAM,MAAM,UAAU,UAAU,EAAE,OAAO,MAAM,MAAM,CAAC;EAC5D,KAAK,YAAY,MAAM;EACvB,KAAK,QAAQ,MAAM;EACnB,KAAK,KAAK,iBAAiB,MAAM,KAAK;EACtC,KAAK,iBAAiB,gBAAgB,MAAM,KAAK;CACnD;AACF;;AAGA,IAAa,cAAb,cAAiC,SAAS;CACxC,AAAS,OAAO;CAChB,AAAS,iBAAiB;CAC1B,AAAS;CACT,AAAkB;CAElB,YAAY,OAA2D;EACrE,MAAM,WAAW,MAAM,UAAU,UAAU,EAAE,OAAO,MAAM,MAAM,CAAC;EACjE,KAAK,YAAY,MAAM;EACvB,KAAK,QAAQ,MAAM;CACrB;AACF"}
@@ -1,4 +1,4 @@
1
- import { X as MessageId } from "../index-DvJUcSW1.js";
1
+ import { q as MessageId } from "../index-DUFS0SJ_.js";
2
2
  import { z } from "zod";
3
3
  //#region src/infrastructure/cloudflare/scheduled/scheduled-handler.d.ts
4
4
  /** The work one schedule runs. Nothing returns to the platform, so each run reports its own outcome. */
@@ -1,4 +1,4 @@
1
- import { a as createLogger } from "../core-ClSP5-V9.js";
1
+ import { r as createLogger } from "../core-UUW2unJd.js";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/infrastructure/cloudflare/scheduled/scheduled-handler.ts
@@ -17,7 +17,7 @@ function defineSchedules(schedules) {
17
17
  handler: (runs) => async (controller, deps) => {
18
18
  const { cron } = controller;
19
19
  if (!schema.validate(cron)) {
20
- logger.error("scheduled_unknown_cron", { details: { cron } });
20
+ logger.error("scheduled_unknown_cron", { cron });
21
21
  return;
22
22
  }
23
23
  await runs[cron](deps, new Date(controller.scheduledTime));
@@ -1 +1 @@
1
- {"version":3,"file":"scheduled.js","names":[],"sources":["../../src/infrastructure/cloudflare/scheduled/scheduled-handler.ts"],"sourcesContent":["import { z } from 'zod'\n\nimport { createLogger, type MessageId } from '@/core'\n\nconst logger = createLogger('scheduled')\n\n/** The work one schedule runs. Nothing returns to the platform, so each run reports its own outcome. */\nexport type ScheduledRun<Deps> = (deps: Deps, at: Date) => Promise<void>\n\n/** The cron expressions of a schedule map, as literal types. */\nexport type ScheduleExpression<Schedules extends Readonly<Record<string, string>>> =\n Schedules[keyof Schedules]\n\n/** The Cloudflare `scheduled` export, with the application's dependencies as its second argument. */\nexport type ScheduledHandler<Deps> = (controller: ScheduledController, deps: Deps) => Promise<void>\n\n/** What `defineSchedules` returns: the map, its parser, and the handler factory. */\nexport type AppSchedules<Schedules extends Readonly<Record<string, string>>> = {\n readonly schedules: Schedules\n /** Parses `controller.cron`, a bare string from the platform, into a known expression. */\n readonly schema: z.ZodType<ScheduleExpression<Schedules>>\n /**\n * Route each schedule to its work. One run per expression, so a schedule added without\n * work fails to compile. An expression Wrangler fires that the map does not know is logged\n * once and ignored. A run that throws rejects, so Cloudflare marks the invocation failed.\n */\n handler<Deps>(\n runs: Readonly<Record<ScheduleExpression<Schedules>, ScheduledRun<Deps>>>,\n ): ScheduledHandler<Deps>\n}\n\n/**\n * Define the application's schedules once: `EVERY_MINUTE: '* * * * *'`. Keys name the time,\n * not the work, because one schedule may drive several commands. Wrangler's `triggers.crons`\n * must hold exactly these expressions. Cron does not retry: a run that sweeps many items\n * catches each item's failure itself.\n */\nexport function defineSchedules<const Schedules extends Readonly<Record<string, string>>>(\n schedules: Schedules,\n): AppSchedules<Schedules> {\n const schema = z.enum(schedules)\n return {\n schedules,\n schema,\n handler: (runs) => async (controller, deps) => {\n const { cron } = controller\n if (!schema.validate(cron)) {\n logger.error('scheduled_unknown_cron', { details: { cron } })\n return\n }\n await runs[cron](deps, new Date(controller.scheduledTime))\n },\n }\n}\n\n/**\n * The id of a command a schedule issues: `cron:<Name>:<scheduledTime>`. A doubled trigger for\n * the same time is then a duplicate by construction, and the unit of work skips it.\n */\nexport function scheduledMessageId(name: string, at: Date): MessageId {\n return `cron:${name}:${String(at.getTime())}`\n}\n"],"mappings":";;;;AAIA,MAAM,SAAS,aAAa,WAAW;;;;;;;AAiCvC,SAAgB,gBACd,WACyB;CACzB,MAAM,SAAS,EAAE,KAAK,SAAS;CAC/B,OAAO;EACL;EACA;EACA,UAAU,SAAS,OAAO,YAAY,SAAS;GAC7C,MAAM,EAAE,SAAS;GACjB,IAAI,CAAC,OAAO,SAAS,IAAI,GAAG;IAC1B,OAAO,MAAM,0BAA0B,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC5D;GACF;GACA,MAAM,KAAK,KAAK,CAAC,MAAM,IAAI,KAAK,WAAW,aAAa,CAAC;EAC3D;CACF;AACF;;;;;AAMA,SAAgB,mBAAmB,MAAc,IAAqB;CACpE,OAAO,QAAQ,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC5C"}
1
+ {"version":3,"file":"scheduled.js","names":[],"sources":["../../src/infrastructure/cloudflare/scheduled/scheduled-handler.ts"],"sourcesContent":["import { z } from 'zod'\n\nimport { createLogger, type MessageId } from '@/core'\n\nconst logger = createLogger('scheduled')\n\n/** The work one schedule runs. Nothing returns to the platform, so each run reports its own outcome. */\nexport type ScheduledRun<Deps> = (deps: Deps, at: Date) => Promise<void>\n\n/** The cron expressions of a schedule map, as literal types. */\nexport type ScheduleExpression<Schedules extends Readonly<Record<string, string>>> =\n Schedules[keyof Schedules]\n\n/** The Cloudflare `scheduled` export, with the application's dependencies as its second argument. */\nexport type ScheduledHandler<Deps> = (controller: ScheduledController, deps: Deps) => Promise<void>\n\n/** What `defineSchedules` returns: the map, its parser, and the handler factory. */\nexport type AppSchedules<Schedules extends Readonly<Record<string, string>>> = {\n readonly schedules: Schedules\n /** Parses `controller.cron`, a bare string from the platform, into a known expression. */\n readonly schema: z.ZodType<ScheduleExpression<Schedules>>\n /**\n * Route each schedule to its work. One run per expression, so a schedule added without\n * work fails to compile. An expression Wrangler fires that the map does not know is logged\n * once and ignored. A run that throws rejects, so Cloudflare marks the invocation failed.\n */\n handler<Deps>(\n runs: Readonly<Record<ScheduleExpression<Schedules>, ScheduledRun<Deps>>>,\n ): ScheduledHandler<Deps>\n}\n\n/**\n * Define the application's schedules once: `EVERY_MINUTE: '* * * * *'`. Keys name the time,\n * not the work, because one schedule may drive several commands. Wrangler's `triggers.crons`\n * must hold exactly these expressions. Cron does not retry: a run that sweeps many items\n * catches each item's failure itself.\n */\nexport function defineSchedules<const Schedules extends Readonly<Record<string, string>>>(\n schedules: Schedules,\n): AppSchedules<Schedules> {\n const schema = z.enum(schedules)\n return {\n schedules,\n schema,\n handler: (runs) => async (controller, deps) => {\n const { cron } = controller\n if (!schema.validate(cron)) {\n logger.error('scheduled_unknown_cron', { cron })\n return\n }\n await runs[cron](deps, new Date(controller.scheduledTime))\n },\n }\n}\n\n/**\n * The id of a command a schedule issues: `cron:<Name>:<scheduledTime>`. A doubled trigger for\n * the same time is then a duplicate by construction, and the unit of work skips it.\n */\nexport function scheduledMessageId(name: string, at: Date): MessageId {\n return `cron:${name}:${String(at.getTime())}`\n}\n"],"mappings":";;;;AAIA,MAAM,SAAS,aAAa,WAAW;;;;;;;AAiCvC,SAAgB,gBACd,WACyB;CACzB,MAAM,SAAS,EAAE,KAAK,SAAS;CAC/B,OAAO;EACL;EACA;EACA,UAAU,SAAS,OAAO,YAAY,SAAS;GAC7C,MAAM,EAAE,SAAS;GACjB,IAAI,CAAC,OAAO,SAAS,IAAI,GAAG;IAC1B,OAAO,MAAM,0BAA0B,EAAE,KAAK,CAAC;IAC/C;GACF;GACA,MAAM,KAAK,KAAK,CAAC,MAAM,IAAI,KAAK,WAAW,aAAa,CAAC;EAC3D;CACF;AACF;;;;;AAMA,SAAgB,mBAAmB,MAAc,IAAqB;CACpE,OAAO,QAAQ,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC5C"}
@@ -1,6 +1,6 @@
1
- import { X as MessageId, at as DatabaseFailure, it as DatabaseError, q as DomainEvent, rt as D1ConstraintKind } from "./index-DvJUcSW1.js";
2
- import { A as IOutboxRepository, M as OutboxClaim, N as OutboxMessage, P as IMessageStore, k as DeadLetterReason, w as ITransaction } from "./index-Cchdg2aQ.js";
3
- import { n as eventCollector, r as DrizzleSchema, t as OutboxRepositoryOptions } from "./outbox-repository-options-AkihEaik.js";
1
+ import { W as DomainEvent, et as D1ConstraintKind, nt as DatabaseFailure, q as MessageId, tt as DatabaseError } from "./index-DUFS0SJ_.js";
2
+ import { A as IOutboxRepository, M as OutboxClaim, N as OutboxMessage, P as IMessageStore, k as DeadLetterReason, w as ITransaction } from "./index-CYHXOH08.js";
3
+ import { n as eventCollector, r as DrizzleSchema, t as OutboxRepositoryOptions } from "./outbox-repository-options-DjBgFhMi.js";
4
4
  import { BatchItem } from "drizzle-orm/batch";
5
5
  import { AnyD1Database, DrizzleD1Database } from "drizzle-orm/d1";
6
6
  //#region src/infrastructure/cloudflare/d1-database-types.d.ts
@@ -294,8 +294,13 @@ export declare class D1BatchTransaction<Schema extends DrizzleSchema> implements
294
294
  }
295
295
  //#endregion
296
296
  //#region src/infrastructure/cloudflare/d1-database-execution.d.ts
297
- /** Execute once without retries; convert recognized D1 failures and preserve other exceptions unchanged. */
298
- export declare function executeDatabase<T>(operation: string, work: () => PromiseLike<T>): Promise<T>;
297
+ /**
298
+ * Convert database failures and preserve other exceptions. The default executes once.
299
+ * Opt-in transient retries allow three attempts with full jitter and backoff caps of 250 ms and 500 ms.
300
+ */
301
+ export declare function executeDatabase<T>(operation: string, work: () => PromiseLike<T>, options?: {
302
+ readonly retry?: 'never' | 'transient';
303
+ }): Promise<T>;
299
304
  //#endregion
300
305
  //#region src/infrastructure/cloudflare/d1-outbox-repository.d.ts
301
306
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.d.ts","names":[],"sources":["../src/infrastructure/cloudflare/d1-database-types.ts","../src/infrastructure/cloudflare/base-d1-repository.ts","../src/infrastructure/cloudflare/classify-d1-error.ts","../src/infrastructure/cloudflare/d1-message-store.ts","../src/infrastructure/cloudflare/d1-schema.ts","../src/infrastructure/cloudflare/d1-batch-transaction.ts","../src/infrastructure/cloudflare/d1-database-execution.ts","../src/infrastructure/cloudflare/d1-outbox-repository.ts"],"mappings":";;;;;;;KAKY,eAAe;;KAGf,KAAK,eAAe,iBAAiB,kBAAkB;;KAGvD,SAAS,eAAe,iBAAiB,KACnD,KAAK;;KAKK,KAAK,eAAe;WACrB,IAAI,KAAK;;;;;UCRH;EACf,iBAAiB,cAAc;EAC/B;;;;;;8BAOoB,iBAAiB,eAAe;qBACrB,IAAI,KAAK;EAAxC,YAA+B,IAAI,KAAK;;YAG9B,MAAM,WAAW;;YAKjB,KAAK,GAAG,mBAAmB,aAAa,YAAY,KAAK,QAAQ;;YAKjE,eAAe,WAAW;;;;;;;;wBCVtB,gBAAgB,gBAAgB,qBAAyB;;;;qBCb5D,eAAe,eAAe,uBACjC,iBAAiB,mBACd;EAEL,MAAM,WAAW,YAAY;EAc7B,cAAc,QAAQ,cAAc,eAAe;;;;;qBCtB9C,qDAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAAjB;;;;;;;;;;;;;;;;qBAYA,iDAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCPb,mBAAmB,eAAe,0BAA0B,aACvE,KAAK;mBAEwB;EAA7B,YAA6B,IAAI,KAAK;EAEhC,YAAY,GAAG,OAAO,IAAI,KAAK,YAAY,QAAQ,KAAK,QAAQ;;;;;wBCblD,gBAAgB,GACpC,mBACA,YAAY,YAAY,KACvB,QAAQ;;;;;;;qBCYE,mBAAmB,eAAe,0BAA0B;mBAIpD;mBAEA;mBALF;EAEjB,YACmB,IAAI,KAAK,SAC1B,UAAS,yBACQ,YAAW;;EAMxB,aAAa,oBAAoB,QAAQ;;EAmDzC,cAAc,OAAO,aAAa,UAAU,cAAc,iBAAiB;;EAgB3E,aAAa,OAAO,aAAa,UAAU,cAAc,iBAAiB;;EA6B1E,WACJ,OAAO,aACP,UAAU,cAAc,gBACxB,QAAQ,kBACR,uBACC;;EAqBG,sBAAsB,QAAQ,MAAM,oBAAoB;;EAqBxD,mBAAmB,QAAQ,MAAM,oBAAoB;;EAqBrD,qBAAqB,QAAQ,MAAM,oBAAoB"}
1
+ {"version":3,"file":"cloudflare.d.ts","names":[],"sources":["../src/infrastructure/cloudflare/d1-database-types.ts","../src/infrastructure/cloudflare/base-d1-repository.ts","../src/infrastructure/cloudflare/classify-d1-error.ts","../src/infrastructure/cloudflare/d1-message-store.ts","../src/infrastructure/cloudflare/d1-schema.ts","../src/infrastructure/cloudflare/d1-batch-transaction.ts","../src/infrastructure/cloudflare/d1-database-execution.ts","../src/infrastructure/cloudflare/d1-outbox-repository.ts"],"mappings":";;;;;;;KAKY,eAAe;;KAGf,KAAK,eAAe,iBAAiB,kBAAkB;;KAGvD,SAAS,eAAe,iBAAiB,KACnD,KAAK;;KAKK,KAAK,eAAe;WACrB,IAAI,KAAK;;;;;UCRH;EACf,iBAAiB,cAAc;EAC/B;;;;;;8BAOoB,iBAAiB,eAAe;qBACrB,IAAI,KAAK;EAAxC,YAA+B,IAAI,KAAK;;YAG9B,MAAM,WAAW;;YAKjB,KAAK,GAAG,mBAAmB,aAAa,YAAY,KAAK,QAAQ;;YAKjE,eAAe,WAAW;;;;;;;;wBCVtB,gBAAgB,gBAAgB,qBAAyB;;;;qBCb5D,eAAe,eAAe,uBACjC,iBAAiB,mBACd;EAEL,MAAM,WAAW,YAAY;EAc7B,cAAc,QAAQ,cAAc,eAAe;;;;;qBCtB9C,qDAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAAjB;;;;;;;;;;;;;;;;qBAYA,iDAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCPb,mBAAmB,eAAe,0BAA0B,aACvE,KAAK;mBAEwB;EAA7B,YAA6B,IAAI,KAAK;EAEhC,YAAY,GAAG,OAAO,IAAI,KAAK,YAAY,QAAQ,KAAK,QAAQ;;;;;;;;wBCNlD,gBAAgB,GACpC,mBACA,YAAY,YAAY,IACxB;WAAoB;IACnB,QAAQ;;;;;;;qBCIE,mBAAmB,eAAe,0BAA0B;mBAIpD;mBAEA;mBALF;EAEjB,YACmB,IAAI,KAAK,SAC1B,UAAS,yBACQ,YAAW;;EAMxB,aAAa,oBAAoB,QAAQ;;EAmDzC,cAAc,OAAO,aAAa,UAAU,cAAc,iBAAiB;;EAgB3E,aAAa,OAAO,aAAa,UAAU,cAAc,iBAAiB;;EA6B1E,WACJ,OAAO,aACP,UAAU,cAAc,gBACxB,QAAQ,kBACR,uBACC;;EAqBG,sBAAsB,QAAQ,MAAM,oBAAoB;;EAqBxD,mBAAmB,QAAQ,MAAM,oBAAoB;;EAqBrD,qBAAqB,QAAQ,MAAM,oBAAoB"}
@@ -1,6 +1,7 @@
1
- import { c as exponentialDelayCapMs, k as DatabaseError, rt as AppError } from "./core-ClSP5-V9.js";
2
- import { o as MessageAlreadyProcessedError } from "./application-Cf9SER-i.js";
3
- import { a as stageD1Statement, i as runWithUnitOfWorkContext, n as collectD1Statements, r as eventCollector, t as OUTBOX_REPOSITORY_DEFAULTS } from "./outbox-repository-options-ICOF8g2h.js";
1
+ import { O as DatabaseError, nt as AppError, s as exponentialDelayCapMs } from "./core-UUW2unJd.js";
2
+ import { o as MessageAlreadyProcessedError } from "./application-CNdzHIXk.js";
3
+ import { a as stageD1Statement, i as runWithUnitOfWorkContext, n as collectD1Statements, r as eventCollector, t as OUTBOX_REPOSITORY_DEFAULTS } from "./outbox-repository-options-CgdqU20T.js";
4
+ import { Result } from "better-result";
4
5
  import { DrizzleQueryError } from "drizzle-orm/errors";
5
6
  import { and, asc, eq, inArray, isNull, lt, lte, or, sql } from "drizzle-orm";
6
7
  import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
@@ -68,13 +69,23 @@ function classificationOf(message) {
68
69
 
69
70
  //#endregion
70
71
  //#region src/infrastructure/cloudflare/d1-database-execution.ts
71
- /** Execute once without retries; convert recognized D1 failures and preserve other exceptions unchanged. */
72
- async function executeDatabase(operation, work) {
73
- try {
74
- return await work();
75
- } catch (cause) {
76
- throw classifyD1Error(cause, operation) ?? cause;
77
- }
72
+ /**
73
+ * Convert database failures and preserve other exceptions. The default executes once.
74
+ * Opt-in transient retries allow three attempts with full jitter and backoff caps of 250 ms and 500 ms.
75
+ */
76
+ async function executeDatabase(operation, work, options = {}) {
77
+ const result = await Result.tryPromise({
78
+ try: async () => await work(),
79
+ catch: (cause) => classifyD1Error(cause, operation) ?? cause
80
+ }, { retry: {
81
+ times: options.retry === "transient" ? 2 : 0,
82
+ delayMs: 250,
83
+ backoff: "exponential",
84
+ jitter: true,
85
+ shouldRetry: (error) => error instanceof DatabaseError && error.classification === "transient"
86
+ } });
87
+ if (result.isErr()) throw result.error;
88
+ return result.value;
78
89
  }
79
90
 
80
91
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.js","names":[],"sources":["../src/infrastructure/cloudflare/classify-d1-error.ts","../src/infrastructure/cloudflare/d1-database-execution.ts","../src/infrastructure/cloudflare/base-d1-repository.ts","../src/infrastructure/cloudflare/d1-schema.ts","../src/infrastructure/cloudflare/d1-message-store.ts","../src/infrastructure/cloudflare/d1-batch-transaction.ts","../src/infrastructure/cloudflare/d1-outbox-repository.ts"],"sourcesContent":["import { DrizzleQueryError } from 'drizzle-orm/errors'\n\nimport { AppError, DatabaseError, type D1ConstraintKind, type FailureClassification } from '@/core'\n\nconst KIND_BY_CODE: ReadonlyMap<string, D1ConstraintKind> = new Map([\n ['SQLITE_CONSTRAINT_PRIMARYKEY', 'unique'],\n ['SQLITE_CONSTRAINT_UNIQUE', 'unique'],\n ['SQLITE_CONSTRAINT_NOTNULL', 'not_null'],\n ['SQLITE_CONSTRAINT_FOREIGNKEY', 'foreign_key'],\n ['SQLITE_CONSTRAINT_CHECK', 'check'],\n])\n\nconst KIND_BY_PROSE: ReadonlyArray<readonly [string, D1ConstraintKind]> = [\n ['UNIQUE constraint failed', 'unique'],\n ['NOT NULL constraint failed', 'not_null'],\n ['FOREIGN KEY constraint failed', 'foreign_key'],\n ['CHECK constraint failed', 'check'],\n]\n\n/**\n * Recognize D1 failures and preserve the original cause. Unrecognized callback exceptions remain unchanged.\n * D1 exposes error codes and constraint locations in text, so parsing stays at this boundary.\n */\nexport function classifyD1Error(cause: unknown, operation = 'D1 query'): DatabaseError | undefined {\n if (cause instanceof AppError || (cause instanceof Error && cause.name === 'AbortError'))\n return undefined\n const message = messagesOf(cause)\n const kind = kindOf(message)\n const classification = kind === undefined ? classificationOf(message) : 'terminal'\n const code = message.match(/\\b(?:SQLITE_[A-Z_]+|D1_[A-Z_]+)\\b/)?.[0]\n if (code === undefined && classification === 'unknown' && !(cause instanceof DrizzleQueryError))\n return undefined\n // A composite unique constraint must not be mistaken for the processed-message primary key.\n const location = message.match(/constraint failed: (\\w+)\\.(\\w+)(?=\\s*(?::\\s*SQLITE_|$|\\n))/)\n const check = kind === 'check' ? message.match(/CHECK constraint failed: ([A-Za-z_]\\w*)/) : null\n return new DatabaseError(\n operation,\n classification,\n {\n database: 'd1',\n code,\n kind,\n table: location?.[1],\n column: location?.[2],\n constraintName: check?.[1],\n },\n { cause },\n )\n}\n\nfunction kindOf(message: string): D1ConstraintKind | undefined {\n const code = message.match(/SQLITE_CONSTRAINT_\\w+/)?.[0]\n if (code !== undefined) return KIND_BY_CODE.get(code)\n return KIND_BY_PROSE.find(([prose]) => message.includes(prose))?.[1]\n}\n\n// Drizzle query messages contain SQL and values, which are not evidence about the database failure.\nfunction messagesOf(cause: unknown): string {\n const messages: string[] = []\n const seen = new Set<unknown>()\n let current = cause\n while (current instanceof Error && !seen.has(current)) {\n seen.add(current)\n if (!(current instanceof DrizzleQueryError)) messages.push(current.message)\n current = current.cause\n }\n // oxlint-disable-next-line anti-slop/no-runtime-typeof -- D1 can expose a raw string cause; this boundary accepts only that representation.\n if (typeof current === 'string') messages.push(current)\n return messages.join('\\n')\n}\n\n// Cloudflare documents these conditions separately from transient storage resets.\nconst TERMINAL_FAILURE =\n /\\bSQLITE_(?:ERROR|MISMATCH|TOOBIG|FULL|AUTH|PERM|READONLY)\\b|\\bD1_(?:TYPE_ERROR|COLUMN_NOTFOUND)\\b|no such (?:table|column|function)|syntax error|datatype mismatch|exceeded (?:timeout|its memory limit|its CPU time limit|maximum DB size)|maximum account storage limit|free tier daily row (?:read|write) limit/i\nconst TRANSIENT_FAILURE =\n /Network connection lost\\.|Replica disconnected from primary\\.|D1 DB reset because its code was updated\\.|Internal error (?:while starting up|in) D1 DB storage caused object to be reset\\.|Cannot resolve D1 DB due to transient issue on remote node\\.|Can't read from request stream because client disconnected\\./i\n\nfunction classificationOf(message: string): FailureClassification {\n if (TERMINAL_FAILURE.test(message)) return 'terminal'\n if (TRANSIENT_FAILURE.test(message)) return 'transient'\n return 'unknown'\n}\n","import { classifyD1Error } from './classify-d1-error.ts'\n\n/** Execute once without retries; convert recognized D1 failures and preserve other exceptions unchanged. */\nexport async function executeDatabase<T>(\n operation: string,\n work: () => PromiseLike<T>,\n): Promise<T> {\n try {\n return await work()\n } catch (cause) {\n throw classifyD1Error(cause, operation) ?? cause\n }\n}\n","import type { BatchItem } from 'drizzle-orm/batch'\n\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { eventCollector, stageD1Statement } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db } from './d1-database-types.ts'\n\n/** A domain aggregate that accumulates events for the unit of work to drain. */\nexport interface AggregateEvents {\n collectEvents(): ReadonlyArray<DomainEvent>\n clearEvents(): void\n}\n\n/**\n * Shared plumbing for a repository that writes inside a D1 unit of work.\n * Writes are staged, never run; the boundary commits them as one batch. Reads run on `db` now.\n */\nexport abstract class BaseD1Repository<Schema extends DrizzleSchema> {\n constructor(protected readonly db: D1Db<Schema>) {}\n\n /** Add a statement to the current D1 batch without executing it. */\n protected stage(statement: BatchItem<'sqlite'>): void {\n stageD1Statement(statement)\n }\n\n /** Execute one read with database error conversion and no automatic retries. */\n protected read<T>(operation: string, query: () => PromiseLike<T>): Promise<T> {\n return executeDatabase(operation, query)\n }\n\n /** Drain an aggregate's pending events into the active boundary. */\n protected dispatchEvents(aggregate: AggregateEvents): void {\n const events = aggregate.collectEvents()\n if (events.length === 0) return\n for (const event of events) eventCollector.emit(event)\n aggregate.clearEvents()\n }\n}\n","import { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'\n\nimport type { DomainEvent } from '@/core'\n\n/** One row per committed message. The primary key is the idempotency claim. */\nexport const processedMessages = sqliteTable(\n 'processed_messages',\n {\n messageId: text('message_id').primaryKey(),\n processedAt: integer('processed_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n },\n (table) => [index('idx_processed_messages_processed_at').on(table.processedAt)],\n)\n\n/** Events committed with their state change, waiting for the relay to deliver them. */\nexport const messageOutbox = sqliteTable(\n 'message_outbox',\n {\n id: text('id').primaryKey(),\n payload: text('payload', { mode: 'json' }).$type<DomainEvent>().notNull(),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n availableAt: integer('available_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n attempts: integer('attempts').notNull().default(0),\n claimToken: text('claim_token'),\n claimExpiresAt: integer('claim_expires_at', { mode: 'timestamp' }),\n publishedAt: integer('published_at', { mode: 'timestamp' }),\n failedAt: integer('failed_at', { mode: 'timestamp' }),\n lastError: text('last_error'),\n },\n (table) => [\n index('idx_message_outbox_due')\n .on(table.availableAt, table.createdAt)\n .where(sql`${table.publishedAt} IS NULL`),\n index('idx_message_outbox_claim_token').on(table.claimToken),\n ],\n)\n","import { eq } from 'drizzle-orm'\n\nimport { MessageAlreadyProcessedError, type IMessageStore, type MessageId } from '@/application'\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { BaseD1Repository } from './base-d1-repository.ts'\nimport { messageOutbox, processedMessages } from './d1-schema.ts'\n\n/** Processed messages and outbox events share one D1 batch; the primary key prevents concurrent duplicate commits. */\nexport class D1MessageStore<Schema extends DrizzleSchema>\n extends BaseD1Repository<Schema>\n implements IMessageStore\n{\n async claim(messageId: MessageId): Promise<void> {\n const processed = await this.read('claim message', () =>\n this.db\n .select({ messageId: processedMessages.messageId })\n .from(processedMessages)\n .where(eq(processedMessages.messageId, messageId))\n .get(),\n )\n if (processed !== undefined) throw new MessageAlreadyProcessedError(messageId)\n\n // Concurrent deliveries can pass the read; the primary key enforces the claim at commit.\n this.stage(this.db.insert(processedMessages).values({ messageId }))\n }\n\n async persistEvents(events: ReadonlyArray<DomainEvent>): Promise<void> {\n if (events.length === 0) return\n this.stage(\n this.db\n .insert(messageOutbox)\n .values(events.map((event) => ({ id: event.id, payload: event }))),\n )\n }\n}\n","import type { ITransaction } from '@/application'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { collectD1Statements, runWithUnitOfWorkContext } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db, D1Tx } from './d1-database-types.ts'\n\n/**\n * The D1 transaction boundary: every write is staged during `work` and committed as one\n * `db.batch()`. A throw inside `work` means the batch never runs.\n */\nexport class D1BatchTransaction<Schema extends DrizzleSchema> implements ITransaction<\n D1Tx<Schema>\n> {\n constructor(private readonly db: D1Db<Schema>) {}\n\n async transaction<T>(work: (tx: D1Tx<Schema>) => Promise<T>): Promise<T> {\n return runWithUnitOfWorkContext('d1', async () => {\n const value = await work({ db: this.db })\n const [first, ...rest] = collectD1Statements()\n if (first !== undefined) {\n await executeDatabase('commit unit of work', () => this.db.batch([first, ...rest]))\n }\n return value\n })\n }\n}\n","import { and, asc, eq, inArray, isNull, lt, lte, or, sql } from 'drizzle-orm'\n\nimport type { DeadLetterReason, IOutboxRepository, OutboxClaim, OutboxMessage } from '@/application'\nimport { exponentialDelayCapMs } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport {\n OUTBOX_REPOSITORY_DEFAULTS,\n type OutboxRepositoryOptions,\n} from '../outbox-repository-options.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db } from './d1-database-types.ts'\nimport { messageOutbox, processedMessages } from './d1-schema.ts'\n\n/**\n * Typist's atomic D1 claims on a plain connection, outside a unit of work.\n * Every write runs once; lease expiry and the scheduled sweep recover interrupted operations.\n */\nexport class D1OutboxRepository<Schema extends DrizzleSchema> implements IOutboxRepository {\n private readonly options: Required<OutboxRepositoryOptions>\n\n constructor(\n private readonly db: D1Db<Schema>,\n options: OutboxRepositoryOptions = {},\n private readonly now: () => Date = () => new Date(),\n ) {\n this.options = { ...OUTBOX_REPOSITORY_DEFAULTS, ...options }\n }\n\n /** Lease due rows in one statement; increment attempts even when reclaiming an expired lease. */\n async claimPending(batchSize: number): Promise<OutboxClaim> {\n const token = crypto.randomUUID()\n const now = this.now()\n const rows = await executeDatabase('claim pending outbox messages', () =>\n this.db\n .update(messageOutbox)\n .set({\n claimToken: token,\n claimExpiresAt: deadline(now.getTime() + this.options.leaseMs),\n attempts: sql`${messageOutbox.attempts} + 1`,\n })\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(\n and(\n isNull(messageOutbox.publishedAt),\n isNull(messageOutbox.failedAt),\n lte(messageOutbox.availableAt, now),\n or(isNull(messageOutbox.claimExpiresAt), lte(messageOutbox.claimExpiresAt, now)),\n ),\n )\n .orderBy(\n asc(messageOutbox.availableAt),\n asc(messageOutbox.createdAt),\n asc(messageOutbox.id),\n )\n .limit(batchSize),\n ),\n )\n .returning({\n payload: messageOutbox.payload,\n attempts: messageOutbox.attempts,\n availableAt: messageOutbox.availableAt,\n createdAt: messageOutbox.createdAt,\n }),\n )\n // UPDATE RETURNING does not preserve the selection order.\n rows.sort(\n (a, b) =>\n a.availableAt.getTime() - b.availableAt.getTime() ||\n a.createdAt.getTime() - b.createdAt.getTime() ||\n a.payload.id.localeCompare(b.payload.id),\n )\n return { token, messages: rows.map(({ payload, attempts }) => ({ payload, attempts })) }\n }\n\n /** Mark only supplied rows that still hold this token as published. */\n async markPublished(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void> {\n if (messages.length === 0) return\n await executeDatabase('mark outbox messages published', () =>\n this.db\n .update(messageOutbox)\n .set({ publishedAt: this.now(), claimToken: null, claimExpiresAt: null })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(messages)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n\n /** Release supplied rows with per-row backoff; a stale token cannot change a newer claim. */\n async releaseClaim(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void> {\n const now = this.now()\n for (const attempts of new Set(messages.map((row) => row.attempts))) {\n const rows = messages.filter((row) => row.attempts === attempts)\n const delay = exponentialDelayCapMs(\n this.options.retryBaseDelayMs,\n this.options.retryMaxDelayMs,\n attempts,\n )\n // oxlint-disable-next-line no-await-in-loop -- Stop releasing rows on the first database failure.\n await executeDatabase('release outbox claim', () =>\n this.db\n .update(messageOutbox)\n .set({\n availableAt: deadline(now.getTime() + delay),\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(rows)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n }\n\n /** Retain the full payload and record a closed reason with a readable error message. */\n async deadLetter(\n claim: OutboxClaim,\n messages: ReadonlyArray<OutboxMessage>,\n reason: DeadLetterReason,\n errorMessage: string,\n ): Promise<void> {\n if (messages.length === 0) return\n await executeDatabase('dead-letter outbox messages', () =>\n this.db\n .update(messageOutbox)\n .set({\n failedAt: this.now(),\n lastError: `${reason}: ${errorMessage}`,\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(messages)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n\n /** Delete at most batchSize published rows, strictly before the cutoff. */\n async deletePublishedBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete published outbox messages', () =>\n this.db\n .delete(messageOutbox)\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(lt(messageOutbox.publishedAt, cutoff))\n .orderBy(asc(messageOutbox.publishedAt), asc(messageOutbox.id))\n .limit(batchSize),\n ),\n )\n .returning({ id: messageOutbox.id }),\n )\n return rows.length\n }\n\n /** Delete at most batchSize failed rows; pending and published rows remain untouched. */\n async deleteFailedBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete failed outbox messages', () =>\n this.db\n .delete(messageOutbox)\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(and(isNull(messageOutbox.publishedAt), lt(messageOutbox.failedAt, cutoff)))\n .orderBy(asc(messageOutbox.failedAt), asc(messageOutbox.id))\n .limit(batchSize),\n ),\n )\n .returning({ id: messageOutbox.id }),\n )\n return rows.length\n }\n\n /** Delete at most batchSize receipts, strictly before the cutoff. */\n async deleteReceiptsBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete message receipts', () =>\n this.db\n .delete(processedMessages)\n .where(\n inArray(\n processedMessages.messageId,\n this.db\n .select({ messageId: processedMessages.messageId })\n .from(processedMessages)\n .where(lt(processedMessages.processedAt, cutoff))\n .orderBy(asc(processedMessages.processedAt), asc(processedMessages.messageId))\n .limit(batchSize),\n ),\n )\n .returning({ messageId: processedMessages.messageId }),\n )\n return rows.length\n }\n}\n\n// One JSON parameter keeps large claims below D1's 100-parameter limit.\nfunction outboxIds(messages: ReadonlyArray<OutboxMessage>) {\n return sql`(select value from json_each(${JSON.stringify(messages.map((row) => row.payload.id))}))`\n}\n\n// D1 timestamps repository seconds; round future deadlines up so a lease or delay never ends early.\nfunction deadline(ms: number): Date {\n return new Date(Math.ceil(ms / 1_000) * 1_000)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cloudflare.js","names":[],"sources":["../src/infrastructure/cloudflare/classify-d1-error.ts","../src/infrastructure/cloudflare/d1-database-execution.ts","../src/infrastructure/cloudflare/base-d1-repository.ts","../src/infrastructure/cloudflare/d1-schema.ts","../src/infrastructure/cloudflare/d1-message-store.ts","../src/infrastructure/cloudflare/d1-batch-transaction.ts","../src/infrastructure/cloudflare/d1-outbox-repository.ts"],"sourcesContent":["import { DrizzleQueryError } from 'drizzle-orm/errors'\n\nimport { AppError, DatabaseError, type D1ConstraintKind, type FailureClassification } from '@/core'\n\nconst KIND_BY_CODE: ReadonlyMap<string, D1ConstraintKind> = new Map([\n ['SQLITE_CONSTRAINT_PRIMARYKEY', 'unique'],\n ['SQLITE_CONSTRAINT_UNIQUE', 'unique'],\n ['SQLITE_CONSTRAINT_NOTNULL', 'not_null'],\n ['SQLITE_CONSTRAINT_FOREIGNKEY', 'foreign_key'],\n ['SQLITE_CONSTRAINT_CHECK', 'check'],\n])\n\nconst KIND_BY_PROSE: ReadonlyArray<readonly [string, D1ConstraintKind]> = [\n ['UNIQUE constraint failed', 'unique'],\n ['NOT NULL constraint failed', 'not_null'],\n ['FOREIGN KEY constraint failed', 'foreign_key'],\n ['CHECK constraint failed', 'check'],\n]\n\n/**\n * Recognize D1 failures and preserve the original cause. Unrecognized callback exceptions remain unchanged.\n * D1 exposes error codes and constraint locations in text, so parsing stays at this boundary.\n */\nexport function classifyD1Error(cause: unknown, operation = 'D1 query'): DatabaseError | undefined {\n if (cause instanceof AppError || (cause instanceof Error && cause.name === 'AbortError'))\n return undefined\n const message = messagesOf(cause)\n const kind = kindOf(message)\n const classification = kind === undefined ? classificationOf(message) : 'terminal'\n const code = message.match(/\\b(?:SQLITE_[A-Z_]+|D1_[A-Z_]+)\\b/)?.[0]\n if (code === undefined && classification === 'unknown' && !(cause instanceof DrizzleQueryError))\n return undefined\n // A composite unique constraint must not be mistaken for the processed-message primary key.\n const location = message.match(/constraint failed: (\\w+)\\.(\\w+)(?=\\s*(?::\\s*SQLITE_|$|\\n))/)\n const check = kind === 'check' ? message.match(/CHECK constraint failed: ([A-Za-z_]\\w*)/) : null\n return new DatabaseError(\n operation,\n classification,\n {\n database: 'd1',\n code,\n kind,\n table: location?.[1],\n column: location?.[2],\n constraintName: check?.[1],\n },\n { cause },\n )\n}\n\nfunction kindOf(message: string): D1ConstraintKind | undefined {\n const code = message.match(/SQLITE_CONSTRAINT_\\w+/)?.[0]\n if (code !== undefined) return KIND_BY_CODE.get(code)\n return KIND_BY_PROSE.find(([prose]) => message.includes(prose))?.[1]\n}\n\n// Drizzle query messages contain SQL and values, which are not evidence about the database failure.\nfunction messagesOf(cause: unknown): string {\n const messages: string[] = []\n const seen = new Set<unknown>()\n let current = cause\n while (current instanceof Error && !seen.has(current)) {\n seen.add(current)\n if (!(current instanceof DrizzleQueryError)) messages.push(current.message)\n current = current.cause\n }\n // oxlint-disable-next-line anti-slop/no-runtime-typeof -- D1 can expose a raw string cause; this boundary accepts only that representation.\n if (typeof current === 'string') messages.push(current)\n return messages.join('\\n')\n}\n\n// Cloudflare documents these conditions separately from transient storage resets.\nconst TERMINAL_FAILURE =\n /\\bSQLITE_(?:ERROR|MISMATCH|TOOBIG|FULL|AUTH|PERM|READONLY)\\b|\\bD1_(?:TYPE_ERROR|COLUMN_NOTFOUND)\\b|no such (?:table|column|function)|syntax error|datatype mismatch|exceeded (?:timeout|its memory limit|its CPU time limit|maximum DB size)|maximum account storage limit|free tier daily row (?:read|write) limit/i\nconst TRANSIENT_FAILURE =\n /Network connection lost\\.|Replica disconnected from primary\\.|D1 DB reset because its code was updated\\.|Internal error (?:while starting up|in) D1 DB storage caused object to be reset\\.|Cannot resolve D1 DB due to transient issue on remote node\\.|Can't read from request stream because client disconnected\\./i\n\nfunction classificationOf(message: string): FailureClassification {\n if (TERMINAL_FAILURE.test(message)) return 'terminal'\n if (TRANSIENT_FAILURE.test(message)) return 'transient'\n return 'unknown'\n}\n","import { Result } from 'better-result'\n\nimport { DatabaseError } from '@/core'\n\nimport { classifyD1Error } from './classify-d1-error.ts'\n\n/**\n * Convert database failures and preserve other exceptions. The default executes once.\n * Opt-in transient retries allow three attempts with full jitter and backoff caps of 250 ms and 500 ms.\n */\nexport async function executeDatabase<T>(\n operation: string,\n work: () => PromiseLike<T>,\n options: { readonly retry?: 'never' | 'transient' } = {},\n): Promise<T> {\n const result = await Result.tryPromise(\n {\n try: async () => await work(),\n catch: (cause) => classifyD1Error(cause, operation) ?? cause,\n },\n {\n retry: {\n times: options.retry === 'transient' ? 2 : 0,\n delayMs: 250,\n backoff: 'exponential',\n jitter: true,\n shouldRetry: (error) =>\n error instanceof DatabaseError && error.classification === 'transient',\n },\n },\n )\n if (result.isErr()) throw result.error\n return result.value\n}\n","import type { BatchItem } from 'drizzle-orm/batch'\n\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { eventCollector, stageD1Statement } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db } from './d1-database-types.ts'\n\n/** A domain aggregate that accumulates events for the unit of work to drain. */\nexport interface AggregateEvents {\n collectEvents(): ReadonlyArray<DomainEvent>\n clearEvents(): void\n}\n\n/**\n * Shared plumbing for a repository that writes inside a D1 unit of work.\n * Writes are staged, never run; the boundary commits them as one batch. Reads run on `db` now.\n */\nexport abstract class BaseD1Repository<Schema extends DrizzleSchema> {\n constructor(protected readonly db: D1Db<Schema>) {}\n\n /** Add a statement to the current D1 batch without executing it. */\n protected stage(statement: BatchItem<'sqlite'>): void {\n stageD1Statement(statement)\n }\n\n /** Execute one read with database error conversion and no automatic retries. */\n protected read<T>(operation: string, query: () => PromiseLike<T>): Promise<T> {\n return executeDatabase(operation, query)\n }\n\n /** Drain an aggregate's pending events into the active boundary. */\n protected dispatchEvents(aggregate: AggregateEvents): void {\n const events = aggregate.collectEvents()\n if (events.length === 0) return\n for (const event of events) eventCollector.emit(event)\n aggregate.clearEvents()\n }\n}\n","import { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'\n\nimport type { DomainEvent } from '@/core'\n\n/** One row per committed message. The primary key is the idempotency claim. */\nexport const processedMessages = sqliteTable(\n 'processed_messages',\n {\n messageId: text('message_id').primaryKey(),\n processedAt: integer('processed_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n },\n (table) => [index('idx_processed_messages_processed_at').on(table.processedAt)],\n)\n\n/** Events committed with their state change, waiting for the relay to deliver them. */\nexport const messageOutbox = sqliteTable(\n 'message_outbox',\n {\n id: text('id').primaryKey(),\n payload: text('payload', { mode: 'json' }).$type<DomainEvent>().notNull(),\n createdAt: integer('created_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n availableAt: integer('available_at', { mode: 'timestamp' })\n .notNull()\n .$defaultFn(() => new Date()),\n attempts: integer('attempts').notNull().default(0),\n claimToken: text('claim_token'),\n claimExpiresAt: integer('claim_expires_at', { mode: 'timestamp' }),\n publishedAt: integer('published_at', { mode: 'timestamp' }),\n failedAt: integer('failed_at', { mode: 'timestamp' }),\n lastError: text('last_error'),\n },\n (table) => [\n index('idx_message_outbox_due')\n .on(table.availableAt, table.createdAt)\n .where(sql`${table.publishedAt} IS NULL`),\n index('idx_message_outbox_claim_token').on(table.claimToken),\n ],\n)\n","import { eq } from 'drizzle-orm'\n\nimport { MessageAlreadyProcessedError, type IMessageStore, type MessageId } from '@/application'\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { BaseD1Repository } from './base-d1-repository.ts'\nimport { messageOutbox, processedMessages } from './d1-schema.ts'\n\n/** Processed messages and outbox events share one D1 batch; the primary key prevents concurrent duplicate commits. */\nexport class D1MessageStore<Schema extends DrizzleSchema>\n extends BaseD1Repository<Schema>\n implements IMessageStore\n{\n async claim(messageId: MessageId): Promise<void> {\n const processed = await this.read('claim message', () =>\n this.db\n .select({ messageId: processedMessages.messageId })\n .from(processedMessages)\n .where(eq(processedMessages.messageId, messageId))\n .get(),\n )\n if (processed !== undefined) throw new MessageAlreadyProcessedError(messageId)\n\n // Concurrent deliveries can pass the read; the primary key enforces the claim at commit.\n this.stage(this.db.insert(processedMessages).values({ messageId }))\n }\n\n async persistEvents(events: ReadonlyArray<DomainEvent>): Promise<void> {\n if (events.length === 0) return\n this.stage(\n this.db\n .insert(messageOutbox)\n .values(events.map((event) => ({ id: event.id, payload: event }))),\n )\n }\n}\n","import type { ITransaction } from '@/application'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { collectD1Statements, runWithUnitOfWorkContext } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db, D1Tx } from './d1-database-types.ts'\n\n/**\n * The D1 transaction boundary: every write is staged during `work` and committed as one\n * `db.batch()`. A throw inside `work` means the batch never runs.\n */\nexport class D1BatchTransaction<Schema extends DrizzleSchema> implements ITransaction<\n D1Tx<Schema>\n> {\n constructor(private readonly db: D1Db<Schema>) {}\n\n async transaction<T>(work: (tx: D1Tx<Schema>) => Promise<T>): Promise<T> {\n return runWithUnitOfWorkContext('d1', async () => {\n const value = await work({ db: this.db })\n const [first, ...rest] = collectD1Statements()\n if (first !== undefined) {\n await executeDatabase('commit unit of work', () => this.db.batch([first, ...rest]))\n }\n return value\n })\n }\n}\n","import { and, asc, eq, inArray, isNull, lt, lte, or, sql } from 'drizzle-orm'\n\nimport type { DeadLetterReason, IOutboxRepository, OutboxClaim, OutboxMessage } from '@/application'\nimport { exponentialDelayCapMs } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport {\n OUTBOX_REPOSITORY_DEFAULTS,\n type OutboxRepositoryOptions,\n} from '../outbox-repository-options.ts'\nimport { executeDatabase } from './d1-database-execution.ts'\nimport type { D1Db } from './d1-database-types.ts'\nimport { messageOutbox, processedMessages } from './d1-schema.ts'\n\n/**\n * Typist's atomic D1 claims on a plain connection, outside a unit of work.\n * Every write runs once; lease expiry and the scheduled sweep recover interrupted operations.\n */\nexport class D1OutboxRepository<Schema extends DrizzleSchema> implements IOutboxRepository {\n private readonly options: Required<OutboxRepositoryOptions>\n\n constructor(\n private readonly db: D1Db<Schema>,\n options: OutboxRepositoryOptions = {},\n private readonly now: () => Date = () => new Date(),\n ) {\n this.options = { ...OUTBOX_REPOSITORY_DEFAULTS, ...options }\n }\n\n /** Lease due rows in one statement; increment attempts even when reclaiming an expired lease. */\n async claimPending(batchSize: number): Promise<OutboxClaim> {\n const token = crypto.randomUUID()\n const now = this.now()\n const rows = await executeDatabase('claim pending outbox messages', () =>\n this.db\n .update(messageOutbox)\n .set({\n claimToken: token,\n claimExpiresAt: deadline(now.getTime() + this.options.leaseMs),\n attempts: sql`${messageOutbox.attempts} + 1`,\n })\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(\n and(\n isNull(messageOutbox.publishedAt),\n isNull(messageOutbox.failedAt),\n lte(messageOutbox.availableAt, now),\n or(isNull(messageOutbox.claimExpiresAt), lte(messageOutbox.claimExpiresAt, now)),\n ),\n )\n .orderBy(\n asc(messageOutbox.availableAt),\n asc(messageOutbox.createdAt),\n asc(messageOutbox.id),\n )\n .limit(batchSize),\n ),\n )\n .returning({\n payload: messageOutbox.payload,\n attempts: messageOutbox.attempts,\n availableAt: messageOutbox.availableAt,\n createdAt: messageOutbox.createdAt,\n }),\n )\n // UPDATE RETURNING does not preserve the selection order.\n rows.sort(\n (a, b) =>\n a.availableAt.getTime() - b.availableAt.getTime() ||\n a.createdAt.getTime() - b.createdAt.getTime() ||\n a.payload.id.localeCompare(b.payload.id),\n )\n return { token, messages: rows.map(({ payload, attempts }) => ({ payload, attempts })) }\n }\n\n /** Mark only supplied rows that still hold this token as published. */\n async markPublished(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void> {\n if (messages.length === 0) return\n await executeDatabase('mark outbox messages published', () =>\n this.db\n .update(messageOutbox)\n .set({ publishedAt: this.now(), claimToken: null, claimExpiresAt: null })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(messages)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n\n /** Release supplied rows with per-row backoff; a stale token cannot change a newer claim. */\n async releaseClaim(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void> {\n const now = this.now()\n for (const attempts of new Set(messages.map((row) => row.attempts))) {\n const rows = messages.filter((row) => row.attempts === attempts)\n const delay = exponentialDelayCapMs(\n this.options.retryBaseDelayMs,\n this.options.retryMaxDelayMs,\n attempts,\n )\n // oxlint-disable-next-line no-await-in-loop -- Stop releasing rows on the first database failure.\n await executeDatabase('release outbox claim', () =>\n this.db\n .update(messageOutbox)\n .set({\n availableAt: deadline(now.getTime() + delay),\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(rows)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n }\n\n /** Retain the full payload and record a closed reason with a readable error message. */\n async deadLetter(\n claim: OutboxClaim,\n messages: ReadonlyArray<OutboxMessage>,\n reason: DeadLetterReason,\n errorMessage: string,\n ): Promise<void> {\n if (messages.length === 0) return\n await executeDatabase('dead-letter outbox messages', () =>\n this.db\n .update(messageOutbox)\n .set({\n failedAt: this.now(),\n lastError: `${reason}: ${errorMessage}`,\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(\n and(\n inArray(messageOutbox.id, outboxIds(messages)),\n eq(messageOutbox.claimToken, claim.token),\n ),\n ),\n )\n }\n\n /** Delete at most batchSize published rows, strictly before the cutoff. */\n async deletePublishedBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete published outbox messages', () =>\n this.db\n .delete(messageOutbox)\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(lt(messageOutbox.publishedAt, cutoff))\n .orderBy(asc(messageOutbox.publishedAt), asc(messageOutbox.id))\n .limit(batchSize),\n ),\n )\n .returning({ id: messageOutbox.id }),\n )\n return rows.length\n }\n\n /** Delete at most batchSize failed rows; pending and published rows remain untouched. */\n async deleteFailedBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete failed outbox messages', () =>\n this.db\n .delete(messageOutbox)\n .where(\n inArray(\n messageOutbox.id,\n this.db\n .select({ id: messageOutbox.id })\n .from(messageOutbox)\n .where(and(isNull(messageOutbox.publishedAt), lt(messageOutbox.failedAt, cutoff)))\n .orderBy(asc(messageOutbox.failedAt), asc(messageOutbox.id))\n .limit(batchSize),\n ),\n )\n .returning({ id: messageOutbox.id }),\n )\n return rows.length\n }\n\n /** Delete at most batchSize receipts, strictly before the cutoff. */\n async deleteReceiptsBefore(cutoff: Date, batchSize: number): Promise<number> {\n const rows = await executeDatabase('delete message receipts', () =>\n this.db\n .delete(processedMessages)\n .where(\n inArray(\n processedMessages.messageId,\n this.db\n .select({ messageId: processedMessages.messageId })\n .from(processedMessages)\n .where(lt(processedMessages.processedAt, cutoff))\n .orderBy(asc(processedMessages.processedAt), asc(processedMessages.messageId))\n .limit(batchSize),\n ),\n )\n .returning({ messageId: processedMessages.messageId }),\n )\n return rows.length\n }\n}\n\n// One JSON parameter keeps large claims below D1's 100-parameter limit.\nfunction outboxIds(messages: ReadonlyArray<OutboxMessage>) {\n return sql`(select value from json_each(${JSON.stringify(messages.map((row) => row.payload.id))}))`\n}\n\n// D1 timestamps repository seconds; round future deadlines up so a lease or delay never ends early.\nfunction deadline(ms: number): Date {\n return new Date(Math.ceil(ms / 1_000) * 1_000)\n}\n"],"mappings":"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"}