better-ship 0.12.0 → 0.14.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 (116) hide show
  1. package/dist/application-C4JOf71r.js +291 -0
  2. package/dist/application-C4JOf71r.js.map +1 -0
  3. package/dist/application.d.ts +3 -3
  4. package/dist/application.js +2 -2
  5. package/dist/better-auth/apple-client-secret.d.ts +1 -1
  6. package/dist/better-auth/apple-client-secret.js +1 -1
  7. package/dist/better-auth/auth-client.js +1 -1
  8. package/dist/better-auth/rate-limit.d.ts +1 -1
  9. package/dist/better-auth/rate-limit.js +1 -1
  10. package/dist/better-auth.js +1 -1
  11. package/dist/cloudflare/auth-secondary-storage.d.ts +1 -1
  12. package/dist/cloudflare/auth-secondary-storage.js +1 -1
  13. package/dist/cloudflare/queue.d.ts +8 -3
  14. package/dist/cloudflare/queue.d.ts.map +1 -1
  15. package/dist/cloudflare/queue.js +26 -16
  16. package/dist/cloudflare/queue.js.map +1 -1
  17. package/dist/cloudflare/scheduled.d.ts +1 -1
  18. package/dist/cloudflare/scheduled.js +1 -1
  19. package/dist/cloudflare.d.ts +30 -4
  20. package/dist/cloudflare.d.ts.map +1 -1
  21. package/dist/cloudflare.js +98 -5
  22. package/dist/cloudflare.js.map +1 -1
  23. package/dist/{core-DG_cC8YT.js → core-hdaNiCOU.js} +22 -2
  24. package/dist/core-hdaNiCOU.js.map +1 -0
  25. package/dist/core.d.ts +2 -2
  26. package/dist/core.js +2 -2
  27. package/dist/http.d.ts +2 -2
  28. package/dist/http.js +2 -2
  29. package/dist/{index-DwWxIdQP.d.ts → index-C28HgVfY.d.ts} +145 -3
  30. package/dist/index-C28HgVfY.d.ts.map +1 -0
  31. package/dist/{index-Bl0K1RQW.d.ts → index-G1QG0P5g.d.ts} +36 -2
  32. package/dist/index-G1QG0P5g.d.ts.map +1 -0
  33. package/dist/outbox-repository-options-BZjfDqGf.d.ts +26 -0
  34. package/dist/outbox-repository-options-BZjfDqGf.d.ts.map +1 -0
  35. package/dist/{unit-of-work-context-BPE1In2f.js → outbox-repository-options-DOTpJH-e.js} +12 -3
  36. package/dist/outbox-repository-options-DOTpJH-e.js.map +1 -0
  37. package/dist/postgres.d.ts +30 -4
  38. package/dist/postgres.d.ts.map +1 -1
  39. package/dist/postgres.js +92 -5
  40. package/dist/postgres.js.map +1 -1
  41. package/dist/posthog/client.d.ts +22 -0
  42. package/dist/posthog/client.d.ts.map +1 -0
  43. package/dist/posthog/client.js +34 -0
  44. package/dist/posthog/client.js.map +1 -0
  45. package/dist/posthog/metrics.d.ts +18 -0
  46. package/dist/posthog/metrics.d.ts.map +1 -0
  47. package/dist/posthog/metrics.js +30 -0
  48. package/dist/posthog/metrics.js.map +1 -0
  49. package/dist/posthog/proxy.d.ts +17 -0
  50. package/dist/posthog/proxy.d.ts.map +1 -0
  51. package/dist/posthog/proxy.js +34 -0
  52. package/dist/posthog/proxy.js.map +1 -0
  53. package/dist/posthog/server.d.ts +37 -0
  54. package/dist/posthog/server.d.ts.map +1 -0
  55. package/dist/posthog/server.js +81 -0
  56. package/dist/posthog/server.js.map +1 -0
  57. package/dist/resend.d.ts +3 -3
  58. package/dist/resend.js +2 -2
  59. package/dist/sentry/client.d.ts +22 -0
  60. package/dist/sentry/client.d.ts.map +1 -0
  61. package/dist/sentry/client.js +48 -0
  62. package/dist/sentry/client.js.map +1 -0
  63. package/dist/sentry/server.d.ts +24 -0
  64. package/dist/sentry/server.d.ts.map +1 -0
  65. package/dist/sentry/server.js +48 -0
  66. package/dist/sentry/server.js.map +1 -0
  67. package/dist/sentry/vite.d.ts +11 -0
  68. package/dist/sentry/vite.d.ts.map +1 -0
  69. package/dist/sentry/vite.js +17 -0
  70. package/dist/sentry/vite.js.map +1 -0
  71. package/dist/tanstack.d.ts +1 -1
  72. package/dist/tanstack.js +2 -2
  73. package/dist/testing/outbox-repository.d.ts +44 -0
  74. package/dist/testing/outbox-repository.d.ts.map +1 -0
  75. package/dist/testing/outbox-repository.js +206 -0
  76. package/dist/testing/outbox-repository.js.map +1 -0
  77. package/dist/{webhook-verification-sZtwTI5n.d.ts → webhook-verification-D7DIhH6C.d.ts} +2 -2
  78. package/dist/{webhook-verification-sZtwTI5n.d.ts.map → webhook-verification-D7DIhH6C.d.ts.map} +1 -1
  79. package/dist/{webhook-verification-BQ9qZnVK.js → webhook-verification-trEgyz7I.js} +2 -2
  80. package/dist/{webhook-verification-BQ9qZnVK.js.map → webhook-verification-trEgyz7I.js.map} +1 -1
  81. package/package.json +42 -1
  82. package/src/application/analytics-service.ts +21 -0
  83. package/src/application/index.ts +31 -1
  84. package/src/application/metrics-service.ts +28 -0
  85. package/src/application/observability-service.ts +16 -0
  86. package/src/application/outbox-relay.ts +117 -0
  87. package/src/application/outbox-repository.ts +46 -0
  88. package/src/application/outbox-retention.ts +61 -0
  89. package/src/core/analytics-event.ts +37 -0
  90. package/src/core/backoff.ts +6 -1
  91. package/src/core/index.ts +10 -1
  92. package/src/core/missing-configuration.error.ts +12 -0
  93. package/src/infrastructure/cloudflare/d1-outbox-repository.ts +224 -0
  94. package/src/infrastructure/cloudflare/index.ts +2 -0
  95. package/src/infrastructure/cloudflare/queue/index.ts +1 -0
  96. package/src/infrastructure/cloudflare/queue/queue-client.ts +6 -2
  97. package/src/infrastructure/cloudflare/queue/wait-until-wake.ts +19 -0
  98. package/src/infrastructure/outbox-repository-options.ts +9 -0
  99. package/src/infrastructure/postgres/index.ts +2 -0
  100. package/src/infrastructure/postgres/postgres-outbox-repository.ts +216 -0
  101. package/src/infrastructure/posthog/client.ts +41 -0
  102. package/src/infrastructure/posthog/metrics.ts +30 -0
  103. package/src/infrastructure/posthog/proxy.ts +47 -0
  104. package/src/infrastructure/posthog/server.ts +107 -0
  105. package/src/infrastructure/sentry/client.ts +55 -0
  106. package/src/infrastructure/sentry/server.ts +51 -0
  107. package/src/infrastructure/sentry/vite.ts +17 -0
  108. package/src/infrastructure/testing/outbox-repository-contract.ts +228 -0
  109. package/dist/application-CIxo_BpK.js +0 -160
  110. package/dist/application-CIxo_BpK.js.map +0 -1
  111. package/dist/core-DG_cC8YT.js.map +0 -1
  112. package/dist/index-Bl0K1RQW.d.ts.map +0 -1
  113. package/dist/index-DwWxIdQP.d.ts.map +0 -1
  114. package/dist/unit-of-work-context-BPE1In2f.js.map +0 -1
  115. package/dist/unit-of-work-context-D5ytJIa-.d.ts +0 -16
  116. package/dist/unit-of-work-context-D5ytJIa-.d.ts.map +0 -1
@@ -1,6 +1,6 @@
1
- import { W as DomainEvent, nt as DatabaseFailure, q as MessageId, tt as DatabaseError } from "./index-Bl0K1RQW.js";
2
- import { C as IMessageStore, x as ITransaction } from "./index-DwWxIdQP.js";
3
- import { n as DrizzleSchema, t as eventCollector } from "./unit-of-work-context-D5ytJIa-.js";
1
+ import { G as DomainEvent, J as MessageId, nt as DatabaseError, rt as DatabaseFailure } from "./index-G1QG0P5g.js";
2
+ import { A as IOutboxRepository, M as OutboxClaim, N as OutboxMessage, P as IMessageStore, k as DeadLetterReason, w as ITransaction } from "./index-C28HgVfY.js";
3
+ import { n as eventCollector, r as DrizzleSchema, t as OutboxRepositoryOptions } from "./outbox-repository-options-BZjfDqGf.js";
4
4
  import { ExtractTablesWithRelations } from "drizzle-orm";
5
5
  import { PostgresJsDatabase, PostgresJsTransaction, drizzle } from "drizzle-orm/postgres-js";
6
6
  //#region src/infrastructure/postgres/postgres-database-types.d.ts
@@ -285,5 +285,31 @@ export declare function executeDatabase<T>(operation: string, work: () => Promis
285
285
  /** Read SQLSTATE or driver codes; unknown callback exceptions remain unchanged. */
286
286
  export declare function classifyPostgresError(cause: unknown, operation: string): DatabaseError | undefined;
287
287
  //#endregion
288
- export { type AggregateEvents, DatabaseError, type DatabaseFailure, type PostgresConnection, type PostgresDb, type PostgresDbTransaction, type PostgresReadDb, type PostgresTx, eventCollector };
288
+ //#region src/infrastructure/postgres/postgres-outbox-repository.d.ts
289
+ /**
290
+ * Typist's lease protocol with riposte's SKIP LOCKED selection on a plain connection, outside a unit of work.
291
+ * Every write runs once; lease expiry and the scheduled sweep recover interrupted operations.
292
+ */
293
+ export declare class PostgresOutboxRepository implements IOutboxRepository {
294
+ private readonly db;
295
+ private readonly now;
296
+ private readonly options;
297
+ constructor(db: PostgresDb<DrizzleSchema>, options?: OutboxRepositoryOptions, now?: () => Date);
298
+ /** Lease due rows in one statement; increment attempts even when reclaiming an expired lease. */
299
+ claimPending(batchSize: number): Promise<OutboxClaim>;
300
+ /** Mark only supplied rows that still hold this token as published. */
301
+ markPublished(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void>;
302
+ /** Release supplied rows with per-row backoff; a stale token cannot change a newer claim. */
303
+ releaseClaim(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>): Promise<void>;
304
+ /** Retain the full payload and record a closed reason with a readable error message. */
305
+ deadLetter(claim: OutboxClaim, messages: ReadonlyArray<OutboxMessage>, reason: DeadLetterReason, errorMessage: string): Promise<void>;
306
+ /** Delete at most batchSize published rows, strictly before the cutoff. */
307
+ deletePublishedBefore(cutoff: Date, batchSize: number): Promise<number>;
308
+ /** Delete at most batchSize failed rows; pending and published rows remain untouched. */
309
+ deleteFailedBefore(cutoff: Date, batchSize: number): Promise<number>;
310
+ /** Delete at most batchSize receipts, strictly before the cutoff. */
311
+ deleteReceiptsBefore(cutoff: Date, batchSize: number): Promise<number>;
312
+ }
313
+ //#endregion
314
+ export { type AggregateEvents, DatabaseError, type DatabaseFailure, type OutboxRepositoryOptions, type PostgresConnection, type PostgresDb, type PostgresDbTransaction, type PostgresReadDb, type PostgresTx, eventCollector };
289
315
  //# sourceMappingURL=postgres.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.d.ts","names":[],"sources":["../src/infrastructure/postgres/postgres-database-types.ts","../src/infrastructure/postgres/base-postgres-repository.ts","../src/infrastructure/postgres/postgres-message-store.ts","../src/infrastructure/postgres/postgres-schema.ts","../src/infrastructure/postgres/postgres-transaction.ts","../src/infrastructure/postgres/postgres-database-execution.ts","../src/infrastructure/postgres/classify-postgres-error.ts"],"mappings":";;;;;;;KAMY,qBAAqB,kBAAkB;;KAGvC,WAAW,eAAe,iBAAiB,mBAAmB;;KAG9D,eAAe,eAAe,iBAAiB,KACzD,WAAW;;KAKD,sBAAsB,eAAe,iBAAiB,sBAChE,QACA,2BAA2B;;KAIjB,WAAW,eAAe;WAC3B,IAAI,sBAAsB;;;;;UCjBpB;EACf,iBAAiB,cAAc;EAC/B;;;8BAIoB,uBAAuB,eAAe;qBAC3B,IAAI,sBAAsB;EAAzD,YAA+B,IAAI,sBAAsB;;YAG/C,KAAK,GAAG,mBAAmB,aAAa,YAAY,KAAK,QAAQ;;YAKjE,eAAe,WAAW;;;;;qBCfzB,qBAAqB,eAAe,0BAA0B;mBAC5C;EAA7B,YAA6B,IAAI,sBAAsB;EAEjD,MAAM,WAAW,YAAY;EAU7B,cAAc,QAAQ,cAAc,eAAe;;;;;qBCf9C,iDAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;YAAjB;;;;;;;;;;;;;;;;qBAMA,6CAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCDb,oBAAoB,eAAe,0BAA0B,aACxE,WAAW;mBAEkB;EAA7B,YAA6B,IAAI,WAAW;EAE5C,YAAY,GAAG,OAAO,IAAI,WAAW,YAAY,QAAQ,KAAK,QAAQ;;;;;wBCblD,gBAAgB,GACpC,mBACA,YAAY,YAAY,KACvB,QAAQ;;;;wBC2BK,sBACd,gBACA,oBACC"}
1
+ {"version":3,"file":"postgres.d.ts","names":[],"sources":["../src/infrastructure/postgres/postgres-database-types.ts","../src/infrastructure/postgres/base-postgres-repository.ts","../src/infrastructure/postgres/postgres-message-store.ts","../src/infrastructure/postgres/postgres-schema.ts","../src/infrastructure/postgres/postgres-transaction.ts","../src/infrastructure/postgres/postgres-database-execution.ts","../src/infrastructure/postgres/classify-postgres-error.ts","../src/infrastructure/postgres/postgres-outbox-repository.ts"],"mappings":";;;;;;;KAMY,qBAAqB,kBAAkB;;KAGvC,WAAW,eAAe,iBAAiB,mBAAmB;;KAG9D,eAAe,eAAe,iBAAiB,KACzD,WAAW;;KAKD,sBAAsB,eAAe,iBAAiB,sBAChE,QACA,2BAA2B;;KAIjB,WAAW,eAAe;WAC3B,IAAI,sBAAsB;;;;;UCjBpB;EACf,iBAAiB,cAAc;EAC/B;;;8BAIoB,uBAAuB,eAAe;qBAC3B,IAAI,sBAAsB;EAAzD,YAA+B,IAAI,sBAAsB;;YAG/C,KAAK,GAAG,mBAAmB,aAAa,YAAY,KAAK,QAAQ;;YAKjE,eAAe,WAAW;;;;;qBCfzB,qBAAqB,eAAe,0BAA0B;mBAC5C;EAA7B,YAA6B,IAAI,sBAAsB;EAEjD,MAAM,WAAW,YAAY;EAU7B,cAAc,QAAQ,cAAc,eAAe;;;;;qBCf9C,iDAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;YAAjB;;;;;;;;;;;;;;;;qBAMA,6CAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCDb,oBAAoB,eAAe,0BAA0B,aACxE,WAAW;mBAEkB;EAA7B,YAA6B,IAAI,WAAW;EAE5C,YAAY,GAAG,OAAO,IAAI,WAAW,YAAY,QAAQ,KAAK,QAAQ;;;;;wBCblD,gBAAgB,GACpC,mBACA,YAAY,YAAY,KACvB,QAAQ;;;;wBC2BK,sBACd,gBACA,oBACC;;;;;;;qBClBU,oCAAoC;mBAI5B;mBAEA;mBALF;EAEjB,YACmB,IAAI,WAAW,gBAChC,UAAS,yBACQ,YAAW;;EAMxB,aAAa,oBAAoB,QAAQ;;EAoDzC,cAAc,OAAO,aAAa,UAAU,cAAc,iBAAiB;;EAmB3E,aAAa,OAAO,aAAa,UAAU,cAAc,iBAAiB;;EAwB1E,WACJ,OAAO,aACP,UAAU,cAAc,gBACxB,QAAQ,kBACR,uBACC;;EAwBG,sBAAsB,QAAQ,MAAM,oBAAoB;;EAqBxD,mBAAmB,QAAQ,MAAM,oBAAoB;;EAqBrD,qBAAqB,QAAQ,MAAM,oBAAoB"}
package/dist/postgres.js CHANGED
@@ -1,8 +1,8 @@
1
- import { E as DatabaseError, Q as AppError } from "./core-DG_cC8YT.js";
2
- import { i as MessageAlreadyProcessedError } from "./application-CIxo_BpK.js";
3
- import { n as eventCollector, r as runWithUnitOfWorkContext } from "./unit-of-work-context-BPE1In2f.js";
1
+ import { D as DatabaseError, c as exponentialDelayCapMs, tt as AppError } from "./core-hdaNiCOU.js";
2
+ import { o as MessageAlreadyProcessedError } from "./application-C4JOf71r.js";
3
+ import { i as runWithUnitOfWorkContext, r as eventCollector, t as OUTBOX_REPOSITORY_DEFAULTS } from "./outbox-repository-options-DOTpJH-e.js";
4
4
  import { DrizzleQueryError } from "drizzle-orm/errors";
5
- import { isNull } from "drizzle-orm";
5
+ import { and, asc, eq, inArray, isNull, lt, lte, or, sql } from "drizzle-orm";
6
6
  import { index, integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
7
7
 
8
8
  //#region src/infrastructure/postgres/classify-postgres-error.ts
@@ -165,5 +165,92 @@ var PostgresTransaction = class {
165
165
  };
166
166
 
167
167
  //#endregion
168
- export { BasePostgresRepository, DatabaseError, PostgresMessageStore, PostgresTransaction, classifyPostgresError, eventCollector, executeDatabase, messageOutbox, processedMessages };
168
+ //#region src/infrastructure/postgres/postgres-outbox-repository.ts
169
+ /**
170
+ * Typist's lease protocol with riposte's SKIP LOCKED selection on a plain connection, outside a unit of work.
171
+ * Every write runs once; lease expiry and the scheduled sweep recover interrupted operations.
172
+ */
173
+ var PostgresOutboxRepository = class {
174
+ db;
175
+ now;
176
+ options;
177
+ constructor(db, options = {}, now = () => /* @__PURE__ */ new Date()) {
178
+ this.db = db;
179
+ this.now = now;
180
+ this.options = {
181
+ ...OUTBOX_REPOSITORY_DEFAULTS,
182
+ ...options
183
+ };
184
+ }
185
+ /** Lease due rows in one statement; increment attempts even when reclaiming an expired lease. */
186
+ async claimPending(batchSize) {
187
+ const token = crypto.randomUUID();
188
+ const now = this.now();
189
+ const rows = await executeDatabase("claim pending outbox messages", () => this.db.update(messageOutbox).set({
190
+ claimToken: token,
191
+ claimExpiresAt: new Date(now.getTime() + this.options.leaseMs),
192
+ attempts: sql`${messageOutbox.attempts} + 1`
193
+ }).where(inArray(messageOutbox.id, this.db.select({ id: messageOutbox.id }).from(messageOutbox).where(and(isNull(messageOutbox.publishedAt), isNull(messageOutbox.failedAt), lte(messageOutbox.availableAt, now), or(isNull(messageOutbox.claimExpiresAt), lte(messageOutbox.claimExpiresAt, now)))).orderBy(asc(messageOutbox.availableAt), asc(messageOutbox.createdAt), asc(messageOutbox.id)).limit(batchSize).for("update", { skipLocked: true }))).returning({
194
+ payload: messageOutbox.payload,
195
+ attempts: messageOutbox.attempts,
196
+ availableAt: messageOutbox.availableAt,
197
+ createdAt: messageOutbox.createdAt
198
+ }));
199
+ rows.sort((a, b) => a.availableAt.getTime() - b.availableAt.getTime() || a.createdAt.getTime() - b.createdAt.getTime() || a.payload.id.localeCompare(b.payload.id));
200
+ return {
201
+ token,
202
+ messages: rows.map(({ payload, attempts }) => ({
203
+ payload,
204
+ attempts
205
+ }))
206
+ };
207
+ }
208
+ /** Mark only supplied rows that still hold this token as published. */
209
+ async markPublished(claim, messages) {
210
+ if (messages.length === 0) return;
211
+ await executeDatabase("mark outbox messages published", () => this.db.update(messageOutbox).set({
212
+ publishedAt: this.now(),
213
+ claimToken: null,
214
+ claimExpiresAt: null
215
+ }).where(and(inArray(messageOutbox.id, messages.map((row) => row.payload.id)), eq(messageOutbox.claimToken, claim.token))));
216
+ }
217
+ /** Release supplied rows with per-row backoff; a stale token cannot change a newer claim. */
218
+ async releaseClaim(claim, messages) {
219
+ const now = this.now();
220
+ for (const attempts of new Set(messages.map((row) => row.attempts))) {
221
+ const ids = messages.filter((row) => row.attempts === attempts).map((row) => row.payload.id);
222
+ const delay = exponentialDelayCapMs(this.options.retryBaseDelayMs, this.options.retryMaxDelayMs, attempts);
223
+ await executeDatabase("release outbox claim", () => this.db.update(messageOutbox).set({
224
+ availableAt: new Date(now.getTime() + delay),
225
+ claimToken: null,
226
+ claimExpiresAt: null
227
+ }).where(and(inArray(messageOutbox.id, ids), eq(messageOutbox.claimToken, claim.token))));
228
+ }
229
+ }
230
+ /** Retain the full payload and record a closed reason with a readable error message. */
231
+ async deadLetter(claim, messages, reason, errorMessage) {
232
+ if (messages.length === 0) return;
233
+ await executeDatabase("dead-letter outbox messages", () => this.db.update(messageOutbox).set({
234
+ failedAt: this.now(),
235
+ lastError: `${reason}: ${errorMessage}`,
236
+ claimToken: null,
237
+ claimExpiresAt: null
238
+ }).where(and(inArray(messageOutbox.id, messages.map((row) => row.payload.id)), eq(messageOutbox.claimToken, claim.token))));
239
+ }
240
+ /** Delete at most batchSize published rows, strictly before the cutoff. */
241
+ async deletePublishedBefore(cutoff, batchSize) {
242
+ return (await executeDatabase("delete published outbox messages", () => this.db.delete(messageOutbox).where(inArray(messageOutbox.id, this.db.select({ id: messageOutbox.id }).from(messageOutbox).where(lt(messageOutbox.publishedAt, cutoff)).orderBy(asc(messageOutbox.publishedAt), asc(messageOutbox.id)).limit(batchSize))).returning({ id: messageOutbox.id }))).length;
243
+ }
244
+ /** Delete at most batchSize failed rows; pending and published rows remain untouched. */
245
+ async deleteFailedBefore(cutoff, batchSize) {
246
+ return (await executeDatabase("delete failed outbox messages", () => this.db.delete(messageOutbox).where(inArray(messageOutbox.id, this.db.select({ id: messageOutbox.id }).from(messageOutbox).where(and(isNull(messageOutbox.publishedAt), lt(messageOutbox.failedAt, cutoff))).orderBy(asc(messageOutbox.failedAt), asc(messageOutbox.id)).limit(batchSize))).returning({ id: messageOutbox.id }))).length;
247
+ }
248
+ /** Delete at most batchSize receipts, strictly before the cutoff. */
249
+ async deleteReceiptsBefore(cutoff, batchSize) {
250
+ return (await executeDatabase("delete message receipts", () => this.db.delete(processedMessages).where(inArray(processedMessages.messageId, this.db.select({ messageId: processedMessages.messageId }).from(processedMessages).where(lt(processedMessages.processedAt, cutoff)).orderBy(asc(processedMessages.processedAt), asc(processedMessages.messageId)).limit(batchSize))).returning({ messageId: processedMessages.messageId }))).length;
251
+ }
252
+ };
253
+
254
+ //#endregion
255
+ export { BasePostgresRepository, DatabaseError, PostgresMessageStore, PostgresOutboxRepository, PostgresTransaction, classifyPostgresError, eventCollector, executeDatabase, messageOutbox, processedMessages };
169
256
  //# sourceMappingURL=postgres.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.js","names":[],"sources":["../src/infrastructure/postgres/classify-postgres-error.ts","../src/infrastructure/postgres/postgres-database-execution.ts","../src/infrastructure/postgres/base-postgres-repository.ts","../src/infrastructure/postgres/postgres-schema.ts","../src/infrastructure/postgres/postgres-message-store.ts","../src/infrastructure/postgres/postgres-transaction.ts"],"sourcesContent":["import { DrizzleQueryError } from 'drizzle-orm/errors'\n\nimport { AppError, DatabaseError, type FailureClassification } from '@/core'\n\nconst TRANSIENT_CODES = new Set([\n '40001',\n '40P01',\n '08000',\n '08001',\n '08003',\n '08006',\n '57P01',\n '57P02',\n '57P03',\n 'CONNECT_TIMEOUT',\n 'CONNECTION_CLOSED',\n 'ECONNRESET',\n 'ECONNREFUSED',\n 'EPIPE',\n 'ETIMEDOUT',\n 'EAI_AGAIN',\n])\nconst TERMINAL_CODES = new Set(['57014', 'CONNECTION_ENDED', 'CONNECTION_DESTROYED', 'ECANCELED'])\nconst DRIVER_CODES = new Set([\n 'CONNECT_TIMEOUT',\n 'CONNECTION_CLOSED',\n 'CONNECTION_ENDED',\n 'CONNECTION_DESTROYED',\n])\nconst TERMINAL_CLASSES = new Set(['0A', '22', '23', '25', '28', '3D', '3F', '42', '54'])\n\n/* oxlint-disable anti-slop/no-runtime-typeof -- Raw driver exceptions have no shared schema; this boundary validates the fields it exposes. */\n/** Read SQLSTATE or driver codes; unknown callback exceptions remain unchanged. */\nexport function classifyPostgresError(\n cause: unknown,\n operation: string,\n): DatabaseError | undefined {\n let error = cause\n const seen = new Set<unknown>()\n while (error instanceof DrizzleQueryError && !seen.has(error)) {\n seen.add(error)\n error = error.cause\n }\n if (error instanceof AppError || (error instanceof Error && error.name === 'AbortError'))\n return undefined\n const code =\n error instanceof Error && 'code' in error && typeof error.code === 'string'\n ? error.code\n : undefined\n const serverError =\n error instanceof Error &&\n error.name === 'PostgresError' &&\n code !== undefined &&\n /^[0-9A-Z]{5}$/.test(code)\n const driverError = code !== undefined && DRIVER_CODES.has(code)\n if (!(cause instanceof DrizzleQueryError) && !serverError && !driverError) return undefined\n\n return new DatabaseError(\n operation,\n classificationOf(code),\n {\n database: 'postgres',\n code,\n table_name:\n error instanceof Error && 'table_name' in error && typeof error.table_name === 'string'\n ? error.table_name\n : undefined,\n column_name:\n error instanceof Error && 'column_name' in error && typeof error.column_name === 'string'\n ? error.column_name\n : undefined,\n constraint_name:\n error instanceof Error &&\n 'constraint_name' in error &&\n typeof error.constraint_name === 'string'\n ? error.constraint_name\n : undefined,\n },\n { cause },\n )\n}\n\n/* oxlint-enable anti-slop/no-runtime-typeof */\n\nfunction classificationOf(code: string | undefined): FailureClassification {\n if (code === undefined) return 'unknown'\n if (TRANSIENT_CODES.has(code)) return 'transient'\n if (TERMINAL_CODES.has(code) || TERMINAL_CLASSES.has(code.slice(0, 2))) return 'terminal'\n return 'unknown'\n}\n","import { classifyPostgresError } from './classify-postgres-error.ts'\n\n/** Execute once without retries; convert recognized Postgres 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 classifyPostgresError(cause, operation) ?? cause\n }\n}\n","import type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { eventCollector } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './postgres-database-execution.ts'\nimport type { PostgresDbTransaction } from './postgres-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/** Shared plumbing for a repository that writes inside a Postgres unit of work. */\nexport abstract class BasePostgresRepository<Schema extends DrizzleSchema> {\n constructor(protected readonly db: PostgresDbTransaction<Schema>) {}\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 { isNull } from 'drizzle-orm'\nimport { index, integer, jsonb, pgTable, text, timestamp } from 'drizzle-orm/pg-core'\n\nimport type { DomainEvent } from '@/core'\n\n/** One row per committed message. The primary key is the idempotency claim. */\nexport const processedMessages = pgTable('processed_messages', {\n messageId: text('message_id').primaryKey(),\n processedAt: timestamp('processed_at', { withTimezone: true }).notNull().defaultNow(),\n})\n\n/** Events committed with their state change, waiting for the relay to deliver them. */\nexport const messageOutbox = pgTable(\n 'message_outbox',\n {\n id: text('id').primaryKey(),\n payload: jsonb('payload').$type<DomainEvent>().notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n availableAt: timestamp('available_at', { withTimezone: true }).notNull().defaultNow(),\n attempts: integer('attempts').notNull().default(0),\n claimToken: text('claim_token'),\n claimExpiresAt: timestamp('claim_expires_at', { withTimezone: true }),\n publishedAt: timestamp('published_at', { withTimezone: true }),\n failedAt: timestamp('failed_at', { withTimezone: true }),\n lastError: text('last_error'),\n },\n (table) => [\n index('idx_message_outbox_due')\n .on(table.availableAt, table.createdAt)\n .where(isNull(table.publishedAt)),\n index('idx_message_outbox_claim_token').on(table.claimToken),\n ],\n)\n","import { MessageAlreadyProcessedError, type IMessageStore, type MessageId } from '@/application'\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport type { PostgresDbTransaction } from './postgres-database-types.ts'\nimport { messageOutbox, processedMessages } from './postgres-schema.ts'\n\n/** Processed messages and outbox events share the active Postgres transaction; the insert result establishes the claim. */\nexport class PostgresMessageStore<Schema extends DrizzleSchema> implements IMessageStore {\n constructor(private readonly db: PostgresDbTransaction<Schema>) {}\n\n async claim(messageId: MessageId): Promise<void> {\n const claimed = await this.db\n .insert(processedMessages)\n .values({ messageId })\n .onConflictDoNothing()\n .returning({ messageId: processedMessages.messageId })\n // Thrown inside the transaction, so drizzle rolls back everything written before the claim.\n if (claimed.length === 0) throw new MessageAlreadyProcessedError(messageId)\n }\n\n async persistEvents(events: ReadonlyArray<DomainEvent>): Promise<void> {\n if (events.length === 0) return\n await this.db\n .insert(messageOutbox)\n .values(events.map((event) => ({ id: event.id, payload: event })))\n }\n}\n","import type { ITransaction } from '@/application'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { runWithUnitOfWorkContext } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './postgres-database-execution.ts'\nimport type { PostgresDb, PostgresTx } from './postgres-database-types.ts'\n\n/**\n * The Postgres transaction boundary uses native Drizzle transactions through postgres.js.\n * Drizzle commits when `work` returns and rolls back when it throws.\n */\nexport class PostgresTransaction<Schema extends DrizzleSchema> implements ITransaction<\n PostgresTx<Schema>\n> {\n constructor(private readonly db: PostgresDb<Schema>) {}\n\n transaction<T>(work: (tx: PostgresTx<Schema>) => Promise<T>): Promise<T> {\n return runWithUnitOfWorkContext('postgres', () =>\n executeDatabase('unit of work transaction', () => this.db.transaction((db) => work({ db }))),\n )\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"postgres.js","names":[],"sources":["../src/infrastructure/postgres/classify-postgres-error.ts","../src/infrastructure/postgres/postgres-database-execution.ts","../src/infrastructure/postgres/base-postgres-repository.ts","../src/infrastructure/postgres/postgres-schema.ts","../src/infrastructure/postgres/postgres-message-store.ts","../src/infrastructure/postgres/postgres-transaction.ts","../src/infrastructure/postgres/postgres-outbox-repository.ts"],"sourcesContent":["import { DrizzleQueryError } from 'drizzle-orm/errors'\n\nimport { AppError, DatabaseError, type FailureClassification } from '@/core'\n\nconst TRANSIENT_CODES = new Set([\n '40001',\n '40P01',\n '08000',\n '08001',\n '08003',\n '08006',\n '57P01',\n '57P02',\n '57P03',\n 'CONNECT_TIMEOUT',\n 'CONNECTION_CLOSED',\n 'ECONNRESET',\n 'ECONNREFUSED',\n 'EPIPE',\n 'ETIMEDOUT',\n 'EAI_AGAIN',\n])\nconst TERMINAL_CODES = new Set(['57014', 'CONNECTION_ENDED', 'CONNECTION_DESTROYED', 'ECANCELED'])\nconst DRIVER_CODES = new Set([\n 'CONNECT_TIMEOUT',\n 'CONNECTION_CLOSED',\n 'CONNECTION_ENDED',\n 'CONNECTION_DESTROYED',\n])\nconst TERMINAL_CLASSES = new Set(['0A', '22', '23', '25', '28', '3D', '3F', '42', '54'])\n\n/* oxlint-disable anti-slop/no-runtime-typeof -- Raw driver exceptions have no shared schema; this boundary validates the fields it exposes. */\n/** Read SQLSTATE or driver codes; unknown callback exceptions remain unchanged. */\nexport function classifyPostgresError(\n cause: unknown,\n operation: string,\n): DatabaseError | undefined {\n let error = cause\n const seen = new Set<unknown>()\n while (error instanceof DrizzleQueryError && !seen.has(error)) {\n seen.add(error)\n error = error.cause\n }\n if (error instanceof AppError || (error instanceof Error && error.name === 'AbortError'))\n return undefined\n const code =\n error instanceof Error && 'code' in error && typeof error.code === 'string'\n ? error.code\n : undefined\n const serverError =\n error instanceof Error &&\n error.name === 'PostgresError' &&\n code !== undefined &&\n /^[0-9A-Z]{5}$/.test(code)\n const driverError = code !== undefined && DRIVER_CODES.has(code)\n if (!(cause instanceof DrizzleQueryError) && !serverError && !driverError) return undefined\n\n return new DatabaseError(\n operation,\n classificationOf(code),\n {\n database: 'postgres',\n code,\n table_name:\n error instanceof Error && 'table_name' in error && typeof error.table_name === 'string'\n ? error.table_name\n : undefined,\n column_name:\n error instanceof Error && 'column_name' in error && typeof error.column_name === 'string'\n ? error.column_name\n : undefined,\n constraint_name:\n error instanceof Error &&\n 'constraint_name' in error &&\n typeof error.constraint_name === 'string'\n ? error.constraint_name\n : undefined,\n },\n { cause },\n )\n}\n\n/* oxlint-enable anti-slop/no-runtime-typeof */\n\nfunction classificationOf(code: string | undefined): FailureClassification {\n if (code === undefined) return 'unknown'\n if (TRANSIENT_CODES.has(code)) return 'transient'\n if (TERMINAL_CODES.has(code) || TERMINAL_CLASSES.has(code.slice(0, 2))) return 'terminal'\n return 'unknown'\n}\n","import { classifyPostgresError } from './classify-postgres-error.ts'\n\n/** Execute once without retries; convert recognized Postgres 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 classifyPostgresError(cause, operation) ?? cause\n }\n}\n","import type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { eventCollector } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './postgres-database-execution.ts'\nimport type { PostgresDbTransaction } from './postgres-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/** Shared plumbing for a repository that writes inside a Postgres unit of work. */\nexport abstract class BasePostgresRepository<Schema extends DrizzleSchema> {\n constructor(protected readonly db: PostgresDbTransaction<Schema>) {}\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 { isNull } from 'drizzle-orm'\nimport { index, integer, jsonb, pgTable, text, timestamp } from 'drizzle-orm/pg-core'\n\nimport type { DomainEvent } from '@/core'\n\n/** One row per committed message. The primary key is the idempotency claim. */\nexport const processedMessages = pgTable('processed_messages', {\n messageId: text('message_id').primaryKey(),\n processedAt: timestamp('processed_at', { withTimezone: true }).notNull().defaultNow(),\n})\n\n/** Events committed with their state change, waiting for the relay to deliver them. */\nexport const messageOutbox = pgTable(\n 'message_outbox',\n {\n id: text('id').primaryKey(),\n payload: jsonb('payload').$type<DomainEvent>().notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n availableAt: timestamp('available_at', { withTimezone: true }).notNull().defaultNow(),\n attempts: integer('attempts').notNull().default(0),\n claimToken: text('claim_token'),\n claimExpiresAt: timestamp('claim_expires_at', { withTimezone: true }),\n publishedAt: timestamp('published_at', { withTimezone: true }),\n failedAt: timestamp('failed_at', { withTimezone: true }),\n lastError: text('last_error'),\n },\n (table) => [\n index('idx_message_outbox_due')\n .on(table.availableAt, table.createdAt)\n .where(isNull(table.publishedAt)),\n index('idx_message_outbox_claim_token').on(table.claimToken),\n ],\n)\n","import { MessageAlreadyProcessedError, type IMessageStore, type MessageId } from '@/application'\nimport type { DomainEvent } from '@/core'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport type { PostgresDbTransaction } from './postgres-database-types.ts'\nimport { messageOutbox, processedMessages } from './postgres-schema.ts'\n\n/** Processed messages and outbox events share the active Postgres transaction; the insert result establishes the claim. */\nexport class PostgresMessageStore<Schema extends DrizzleSchema> implements IMessageStore {\n constructor(private readonly db: PostgresDbTransaction<Schema>) {}\n\n async claim(messageId: MessageId): Promise<void> {\n const claimed = await this.db\n .insert(processedMessages)\n .values({ messageId })\n .onConflictDoNothing()\n .returning({ messageId: processedMessages.messageId })\n // Thrown inside the transaction, so drizzle rolls back everything written before the claim.\n if (claimed.length === 0) throw new MessageAlreadyProcessedError(messageId)\n }\n\n async persistEvents(events: ReadonlyArray<DomainEvent>): Promise<void> {\n if (events.length === 0) return\n await this.db\n .insert(messageOutbox)\n .values(events.map((event) => ({ id: event.id, payload: event })))\n }\n}\n","import type { ITransaction } from '@/application'\n\nimport type { DrizzleSchema } from '../drizzle-schema.ts'\nimport { runWithUnitOfWorkContext } from '../unit-of-work-context.ts'\nimport { executeDatabase } from './postgres-database-execution.ts'\nimport type { PostgresDb, PostgresTx } from './postgres-database-types.ts'\n\n/**\n * The Postgres transaction boundary uses native Drizzle transactions through postgres.js.\n * Drizzle commits when `work` returns and rolls back when it throws.\n */\nexport class PostgresTransaction<Schema extends DrizzleSchema> implements ITransaction<\n PostgresTx<Schema>\n> {\n constructor(private readonly db: PostgresDb<Schema>) {}\n\n transaction<T>(work: (tx: PostgresTx<Schema>) => Promise<T>): Promise<T> {\n return runWithUnitOfWorkContext('postgres', () =>\n executeDatabase('unit of work transaction', () => this.db.transaction((db) => work({ db }))),\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 './postgres-database-execution.ts'\nimport type { PostgresDb } from './postgres-database-types.ts'\nimport { messageOutbox, processedMessages } from './postgres-schema.ts'\n\n/**\n * Typist's lease protocol with riposte's SKIP LOCKED selection 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 PostgresOutboxRepository implements IOutboxRepository {\n private readonly options: Required<OutboxRepositoryOptions>\n\n constructor(\n private readonly db: PostgresDb<DrizzleSchema>,\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: new Date(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 .for('update', { skipLocked: true }),\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(\n messageOutbox.id,\n messages.map((row) => row.payload.id),\n ),\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 ids = messages.filter((row) => row.attempts === attempts).map((row) => row.payload.id)\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: new Date(now.getTime() + delay),\n claimToken: null,\n claimExpiresAt: null,\n })\n .where(and(inArray(messageOutbox.id, ids), eq(messageOutbox.claimToken, claim.token))),\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(\n messageOutbox.id,\n messages.map((row) => row.payload.id),\n ),\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"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { PostHog, PostHogConfig } from "posthog-js";
2
+ import { PostHogProvider, usePostHog } from "@posthog/react";
3
+ //#region src/infrastructure/posthog/client.d.ts
4
+ /** The application controls enablement; disabled initialization needs no project key. */
5
+ export type PostHogClientConfig = {
6
+ readonly enabled: false;
7
+ } | {
8
+ readonly enabled: true;
9
+ readonly apiKey: string;
10
+ readonly environment: string;
11
+ readonly source: string;
12
+ readonly options?: Partial<PostHogConfig>;
13
+ };
14
+ /** Browser options from Typist and Porte; SDK option names and application overrides stay intact. */
15
+ export declare function createPostHogClientOptions(config: Extract<PostHogClientConfig, {
16
+ enabled: true;
17
+ }>): Partial<PostHogConfig>;
18
+ /** Initialize once in the browser entrypoint; pass the returned SDK client to PostHogProvider. */
19
+ export declare function initializePostHog(config: PostHogClientConfig): PostHog;
20
+ //#endregion
21
+ export { PostHogProvider, usePostHog };
22
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../../src/infrastructure/posthog/client.ts"],"mappings":";;;;YAGY;WACG;;WAEA;WACA;WACA;WACA;WACA,UAAU,QAAQ;;;wBAIjB,2BACd,QAAQ,QAAQ;EAAuB;KACtC,QAAQ;;wBAkBK,kBAAkB,QAAQ,sBAAsB"}
@@ -0,0 +1,34 @@
1
+ import posthog from "posthog-js";
2
+ import { PostHogProvider, usePostHog } from "@posthog/react";
3
+
4
+ //#region src/infrastructure/posthog/client.ts
5
+ /** Browser options from Typist and Porte; SDK option names and application overrides stay intact. */
6
+ function createPostHogClientOptions(config) {
7
+ return {
8
+ api_host: "/relay",
9
+ ui_host: "https://us.posthog.com",
10
+ person_profiles: "identified_only",
11
+ defaults: "2026-08-29",
12
+ disable_surveys: true,
13
+ advanced_disable_feature_flags: true,
14
+ capture_performance: { web_vitals: false },
15
+ ...config.options,
16
+ loaded: (client) => {
17
+ client.register({
18
+ environment: config.environment,
19
+ source: config.source
20
+ });
21
+ config.options?.loaded?.(client);
22
+ }
23
+ };
24
+ }
25
+ /** Initialize once in the browser entrypoint; pass the returned SDK client to PostHogProvider. */
26
+ function initializePostHog(config) {
27
+ if (!config.enabled) return posthog;
28
+ posthog.init(config.apiKey, createPostHogClientOptions(config));
29
+ return posthog;
30
+ }
31
+
32
+ //#endregion
33
+ export { PostHogProvider, createPostHogClientOptions, initializePostHog, usePostHog };
34
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../src/infrastructure/posthog/client.ts"],"sourcesContent":["import posthog, { type PostHog, type PostHogConfig } from 'posthog-js'\n\n/** The application controls enablement; disabled initialization needs no project key. */\nexport type PostHogClientConfig =\n | { readonly enabled: false }\n | {\n readonly enabled: true\n readonly apiKey: string\n readonly environment: string\n readonly source: string\n readonly options?: Partial<PostHogConfig>\n }\n\n/** Browser options from Typist and Porte; SDK option names and application overrides stay intact. */\nexport function createPostHogClientOptions(\n config: Extract<PostHogClientConfig, { enabled: true }>,\n): Partial<PostHogConfig> {\n return {\n api_host: '/relay',\n ui_host: 'https://us.posthog.com',\n person_profiles: 'identified_only',\n defaults: '2026-08-29',\n disable_surveys: true,\n advanced_disable_feature_flags: true,\n capture_performance: { web_vitals: false },\n ...config.options,\n loaded: (client) => {\n client.register({ environment: config.environment, source: config.source })\n config.options?.loaded?.(client)\n },\n }\n}\n\n/** Initialize once in the browser entrypoint; pass the returned SDK client to PostHogProvider. */\nexport function initializePostHog(config: PostHogClientConfig): PostHog {\n if (!config.enabled) return posthog\n posthog.init(config.apiKey, createPostHogClientOptions(config))\n return posthog\n}\n\nexport { PostHogProvider, usePostHog } from '@posthog/react'\n"],"mappings":";;;;;AAcA,SAAgB,2BACd,QACwB;CACxB,OAAO;EACL,UAAU;EACV,SAAS;EACT,iBAAiB;EACjB,UAAU;EACV,iBAAiB;EACjB,gCAAgC;EAChC,qBAAqB,EAAE,YAAY,MAAM;EACzC,GAAG,OAAO;EACV,SAAS,WAAW;GAClB,OAAO,SAAS;IAAE,aAAa,OAAO;IAAa,QAAQ,OAAO;GAAO,CAAC;GAC1E,OAAO,SAAS,SAAS,MAAM;EACjC;CACF;AACF;;AAGA,SAAgB,kBAAkB,QAAsC;CACtE,IAAI,CAAC,OAAO,SAAS,OAAO;CAC5B,QAAQ,KAAK,OAAO,QAAQ,2BAA2B,MAAM,CAAC;CAC9D,OAAO;AACT"}
@@ -0,0 +1,18 @@
1
+ import { J as MetricsService, q as MetricOptions } from "../index-C28HgVfY.js";
2
+ import { PostHog } from "posthog-node";
3
+ //#region src/infrastructure/posthog/metrics.d.ts
4
+ /** PostHog Metrics with Worker lifetime management, ported from Typist's metrics-service.ts. */
5
+ export declare class PostHogMetricsService implements MetricsService {
6
+ private readonly client;
7
+ private readonly waitUntil;
8
+ /** Use the analytics client's metrics; the runtime owns each flush promise. */
9
+ constructor(client: Pick<PostHog, 'metrics'>, waitUntil: (promise: Promise<unknown>) => void);
10
+ /** Record a total and schedule its flush. */
11
+ count(name: string, value?: number, options?: MetricOptions): void;
12
+ /** Record a current value and schedule its flush. */
13
+ gauge(name: string, value: number, options?: MetricOptions): void;
14
+ /** Record an observation and schedule its flush. */
15
+ histogram(name: string, value: number, options?: MetricOptions): void;
16
+ }
17
+ //#endregion
18
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","names":[],"sources":["../../src/infrastructure/posthog/metrics.ts"],"mappings":";;;;qBAKa,iCAAiC;mBAGzB;mBACA;;EAFnB,YACmB,QAAQ,KAAK,qBACb,YAAY,SAAS;;EAIxC,MAAM,cAAc,gBAAW,UAAU;;EAMzC,MAAM,cAAc,eAAe,UAAU;;EAM7C,UAAU,cAAc,eAAe,UAAU"}
@@ -0,0 +1,30 @@
1
+ //#region src/infrastructure/posthog/metrics.ts
2
+ /** PostHog Metrics with Worker lifetime management, ported from Typist's metrics-service.ts. */
3
+ var PostHogMetricsService = class {
4
+ client;
5
+ waitUntil;
6
+ /** Use the analytics client's metrics; the runtime owns each flush promise. */
7
+ constructor(client, waitUntil) {
8
+ this.client = client;
9
+ this.waitUntil = waitUntil;
10
+ }
11
+ /** Record a total and schedule its flush. */
12
+ count(name, value = 1, options) {
13
+ this.client.metrics.count(name, value, options);
14
+ this.waitUntil(this.client.metrics.flush());
15
+ }
16
+ /** Record a current value and schedule its flush. */
17
+ gauge(name, value, options) {
18
+ this.client.metrics.gauge(name, value, options);
19
+ this.waitUntil(this.client.metrics.flush());
20
+ }
21
+ /** Record an observation and schedule its flush. */
22
+ histogram(name, value, options) {
23
+ this.client.metrics.histogram(name, value, options);
24
+ this.waitUntil(this.client.metrics.flush());
25
+ }
26
+ };
27
+
28
+ //#endregion
29
+ export { PostHogMetricsService };
30
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","names":[],"sources":["../../src/infrastructure/posthog/metrics.ts"],"sourcesContent":["import type { PostHog } from 'posthog-node'\n\nimport type { MetricOptions, MetricsService } from '@/application'\n\n/** PostHog Metrics with Worker lifetime management, ported from Typist's metrics-service.ts. */\nexport class PostHogMetricsService implements MetricsService {\n /** Use the analytics client's metrics; the runtime owns each flush promise. */\n constructor(\n private readonly client: Pick<PostHog, 'metrics'>,\n private readonly waitUntil: (promise: Promise<unknown>) => void,\n ) {}\n\n /** Record a total and schedule its flush. */\n count(name: string, value = 1, options?: MetricOptions): void {\n this.client.metrics.count(name, value, options)\n this.waitUntil(this.client.metrics.flush())\n }\n\n /** Record a current value and schedule its flush. */\n gauge(name: string, value: number, options?: MetricOptions): void {\n this.client.metrics.gauge(name, value, options)\n this.waitUntil(this.client.metrics.flush())\n }\n\n /** Record an observation and schedule its flush. */\n histogram(name: string, value: number, options?: MetricOptions): void {\n this.client.metrics.histogram(name, value, options)\n this.waitUntil(this.client.metrics.flush())\n }\n}\n"],"mappings":";;AAKA,IAAa,wBAAb,MAA6D;CAGxC;CACA;;CAFnB,YACE,AAAiB,QACjB,AAAiB,WACjB;EAFiB;EACA;CAChB;;CAGH,MAAM,MAAc,QAAQ,GAAG,SAA+B;EAC5D,KAAK,OAAO,QAAQ,MAAM,MAAM,OAAO,OAAO;EAC9C,KAAK,UAAU,KAAK,OAAO,QAAQ,MAAM,CAAC;CAC5C;;CAGA,MAAM,MAAc,OAAe,SAA+B;EAChE,KAAK,OAAO,QAAQ,MAAM,MAAM,OAAO,OAAO;EAC9C,KAAK,UAAU,KAAK,OAAO,QAAQ,MAAM,CAAC;CAC5C;;CAGA,UAAU,MAAc,OAAe,SAA+B;EACpE,KAAK,OAAO,QAAQ,UAAU,MAAM,OAAO,OAAO;EAClD,KAAK,UAAU,KAAK,OAAO,QAAQ,MAAM,CAAC;CAC5C;AACF"}
@@ -0,0 +1,17 @@
1
+ //#region src/infrastructure/posthog/proxy.d.ts
2
+ /** Runtime resources and deployment hosts for Typist's same-origin proxy. */
3
+ export interface PostHogProxyOptions {
4
+ readonly cache: Pick<Cache, 'match' | 'put'>;
5
+ readonly waitUntil: (promise: Promise<void>) => void;
6
+ readonly basePath?: string;
7
+ readonly ingestHost?: string;
8
+ readonly assetHost?: string;
9
+ readonly fetch?: typeof globalThis.fetch;
10
+ }
11
+ /**
12
+ * Mount at /relay/$ with GET, POST, and OPTIONS handlers. Supply Cloudflare's asset cache and waitUntil.
13
+ * The application must use the same base path in the browser's api_host option.
14
+ */
15
+ export declare function proxyPostHogRequest(request: Request, options: PostHogProxyOptions): Promise<Response>;
16
+ //#endregion
17
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","names":[],"sources":["../../src/infrastructure/posthog/proxy.ts"],"mappings":";;iBACiB;WACN,OAAO,KAAK;WACZ,YAAY,SAAS;WACrB;WACA;WACA;WACA,eAAe,WAAW;;;;;;wBAOf,oBACpB,SAAS,SACT,SAAS,sBACR,QAAQ"}
@@ -0,0 +1,34 @@
1
+ //#region src/infrastructure/posthog/proxy.ts
2
+ /**
3
+ * Mount at /relay/$ with GET, POST, and OPTIONS handlers. Supply Cloudflare's asset cache and waitUntil.
4
+ * The application must use the same base path in the browser's api_host option.
5
+ */
6
+ async function proxyPostHogRequest(request, options) {
7
+ const url = new URL(request.url);
8
+ const basePath = options.basePath ?? "/relay";
9
+ if (!url.pathname.startsWith(`${basePath}/`)) return new Response(null, { status: 404 });
10
+ const pathname = url.pathname.slice(basePath.length);
11
+ const pathWithSearch = pathname + url.search;
12
+ const send = options.fetch ?? globalThis.fetch;
13
+ if (pathname.startsWith("/static/") || pathname.startsWith("/array/")) {
14
+ const cached = await options.cache.match(request);
15
+ if (cached) return cached;
16
+ const response = await send(`https://${options.assetHost ?? "us-assets.i.posthog.com"}${pathWithSearch}`);
17
+ options.waitUntil(options.cache.put(request, response.clone()));
18
+ return response;
19
+ }
20
+ const headers = new Headers(request.headers);
21
+ headers.delete("cookie");
22
+ headers.delete("authorization");
23
+ headers.set("X-Forwarded-For", request.headers.get("CF-Connecting-IP") ?? "");
24
+ return await send(`https://${options.ingestHost ?? "us.i.posthog.com"}${pathWithSearch}`, {
25
+ method: request.method,
26
+ headers,
27
+ body: request.method !== "GET" && request.method !== "HEAD" ? await request.arrayBuffer() : null,
28
+ redirect: request.redirect
29
+ });
30
+ }
31
+
32
+ //#endregion
33
+ export { proxyPostHogRequest };
34
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","names":[],"sources":["../../src/infrastructure/posthog/proxy.ts"],"sourcesContent":["/** Runtime resources and deployment hosts for Typist's same-origin proxy. */\nexport interface PostHogProxyOptions {\n readonly cache: Pick<Cache, 'match' | 'put'>\n readonly waitUntil: (promise: Promise<void>) => void\n readonly basePath?: string\n readonly ingestHost?: string\n readonly assetHost?: string\n readonly fetch?: typeof globalThis.fetch\n}\n\n/**\n * Mount at /relay/$ with GET, POST, and OPTIONS handlers. Supply Cloudflare's asset cache and waitUntil.\n * The application must use the same base path in the browser's api_host option.\n */\nexport async function proxyPostHogRequest(\n request: Request,\n options: PostHogProxyOptions,\n): Promise<Response> {\n const url = new URL(request.url)\n const basePath = options.basePath ?? '/relay'\n if (!url.pathname.startsWith(`${basePath}/`)) return new Response(null, { status: 404 })\n const pathname = url.pathname.slice(basePath.length)\n const pathWithSearch = pathname + url.search\n const send = options.fetch ?? globalThis.fetch\n\n if (pathname.startsWith('/static/') || pathname.startsWith('/array/')) {\n const cached = await options.cache.match(request)\n if (cached) return cached\n const response = await send(\n `https://${options.assetHost ?? 'us-assets.i.posthog.com'}${pathWithSearch}`,\n )\n options.waitUntil(options.cache.put(request, response.clone()))\n return response\n }\n\n const headers = new Headers(request.headers)\n headers.delete('cookie')\n headers.delete('authorization')\n headers.set('X-Forwarded-For', request.headers.get('CF-Connecting-IP') ?? '')\n return await send(`https://${options.ingestHost ?? 'us.i.posthog.com'}${pathWithSearch}`, {\n method: request.method,\n headers,\n body:\n request.method !== 'GET' && request.method !== 'HEAD' ? await request.arrayBuffer() : null,\n redirect: request.redirect,\n })\n}\n"],"mappings":";;;;;AAcA,eAAsB,oBACpB,SACA,SACmB;CACnB,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;CAC/B,MAAM,WAAW,QAAQ,YAAY;CACrC,IAAI,CAAC,IAAI,SAAS,WAAW,GAAG,SAAS,EAAE,GAAG,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;CACvF,MAAM,WAAW,IAAI,SAAS,MAAM,SAAS,MAAM;CACnD,MAAM,iBAAiB,WAAW,IAAI;CACtC,MAAM,OAAO,QAAQ,SAAS,WAAW;CAEzC,IAAI,SAAS,WAAW,UAAU,KAAK,SAAS,WAAW,SAAS,GAAG;EACrE,MAAM,SAAS,MAAM,QAAQ,MAAM,MAAM,OAAO;EAChD,IAAI,QAAQ,OAAO;EACnB,MAAM,WAAW,MAAM,KACrB,WAAW,QAAQ,aAAa,4BAA4B,gBAC9D;EACA,QAAQ,UAAU,QAAQ,MAAM,IAAI,SAAS,SAAS,MAAM,CAAC,CAAC;EAC9D,OAAO;CACT;CAEA,MAAM,UAAU,IAAI,QAAQ,QAAQ,OAAO;CAC3C,QAAQ,OAAO,QAAQ;CACvB,QAAQ,OAAO,eAAe;CAC9B,QAAQ,IAAI,mBAAmB,QAAQ,QAAQ,IAAI,kBAAkB,KAAK,EAAE;CAC5E,OAAO,MAAM,KAAK,WAAW,QAAQ,cAAc,qBAAqB,kBAAkB;EACxF,QAAQ,QAAQ;EAChB;EACA,MACE,QAAQ,WAAW,SAAS,QAAQ,WAAW,SAAS,MAAM,QAAQ,YAAY,IAAI;EACxF,UAAU,QAAQ;CACpB,CAAC;AACH"}
@@ -0,0 +1,37 @@
1
+ import { Jt as AnalyticsProperties, Kt as AnalyticsEventIdentity, qt as AnalyticsEventInput } from "../index-G1QG0P5g.js";
2
+ import { X as AnalyticsService } from "../index-C28HgVfY.js";
3
+ import { PostHogMetricsService } from "./metrics.js";
4
+ import { PostHog, PostHogOptions } from "posthog-node";
5
+ //#region src/infrastructure/posthog/server.d.ts
6
+ /** Deployment values and SDK options; the runtime owns delivery promises. */
7
+ export interface PostHogAnalyticsConfig {
8
+ readonly apiKey: string;
9
+ readonly environment: string;
10
+ readonly source: string;
11
+ readonly waitUntil: (promise: Promise<unknown>) => void;
12
+ readonly options?: Omit<PostHogOptions, 'waitUntil' | 'flushAt' | 'flushInterval' | 'metrics' | 'disabled'> & {
13
+ readonly metrics?: Omit<NonNullable<PostHogOptions['metrics']>, 'serviceName' | 'environment'>;
14
+ };
15
+ }
16
+ /** Worker analytics ported from Typist; disabled applications select Porte's NULL_ANALYTICS instead. */
17
+ export declare class PostHogAnalyticsService implements AnalyticsService {
18
+ /** Raw SDK access for integrations such as PostHog AI tracing. */
19
+ readonly posthog: PostHog;
20
+ private readonly waitUntil;
21
+ /** Metrics share this service's SDK client and delivery lifetime. */
22
+ readonly metrics: PostHogMetricsService;
23
+ private constructor();
24
+ /** Construct once per Worker invocation; a blank key throws MissingConfigurationError. */
25
+ static create(config: PostHogAnalyticsConfig): PostHogAnalyticsService;
26
+ /** Schedule person properties; the SDK's waitUntil option protects queued delivery. */
27
+ identify(distinctId: string, properties?: AnalyticsProperties): void;
28
+ /** Schedule an irreversible person merge; only alias IDs the application proves belong to one person. */
29
+ alias(distinctId: string, previousDistinctId: string): void;
30
+ /** Schedule an event; omit identity for SDK-generated UUID and capture time. */
31
+ track(event: AnalyticsEventInput, distinctId: string, identity?: AnalyticsEventIdentity): void;
32
+ /** Schedule an event without creating a person profile for an attempt or resource ID. */
33
+ trackAnonymous(event: AnalyticsEventInput, distinctId: string, identity?: AnalyticsEventIdentity): void;
34
+ private capture;
35
+ }
36
+ //#endregion
37
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../../src/infrastructure/posthog/server.ts"],"mappings":";;;;;;iBAaiB;WACN;WACA;WACA;WACA,YAAY,SAAS;WACrB,UAAU,KACjB;aAGS,UAAU,KAAK,YAAY;;;;qBAK3B,mCAAmC;;WAMnC,SAAS;mBACD;;WALV,SAAS;UAEX;;SASA,OAAO,QAAQ,yBAAyB;;EAqB/C,SAAS,oBAAoB,aAAa;;EAO1C,MAAM,oBAAoB;;EAK1B,MAAM,OAAO,qBAAqB,oBAAoB,WAAW;;EAKjE,eACE,OAAO,qBACP,oBACA,WAAW;UAKL"}
@@ -0,0 +1,81 @@
1
+ import { et as MissingConfigurationError } from "../core-hdaNiCOU.js";
2
+ import { PostHogMetricsService } from "./metrics.js";
3
+ import { PostHog } from "posthog-node";
4
+
5
+ //#region src/infrastructure/posthog/server.ts
6
+ /** Worker analytics ported from Typist; disabled applications select Porte's NULL_ANALYTICS instead. */
7
+ var PostHogAnalyticsService = class PostHogAnalyticsService {
8
+ posthog;
9
+ waitUntil;
10
+ /** Metrics share this service's SDK client and delivery lifetime. */
11
+ metrics;
12
+ constructor(posthog, waitUntil) {
13
+ this.posthog = posthog;
14
+ this.waitUntil = waitUntil;
15
+ this.metrics = new PostHogMetricsService(posthog, waitUntil);
16
+ }
17
+ /** Construct once per Worker invocation; a blank key throws MissingConfigurationError. */
18
+ static create(config) {
19
+ const apiKey = config.apiKey.trim();
20
+ if (apiKey.length === 0) throw new MissingConfigurationError("POSTHOG_API_KEY");
21
+ const client = new PostHog(apiKey, {
22
+ host: "https://us.i.posthog.com",
23
+ ...config.options,
24
+ flushAt: 1,
25
+ flushInterval: 0,
26
+ waitUntil: config.waitUntil,
27
+ metrics: {
28
+ ...config.options?.metrics,
29
+ environment: config.environment,
30
+ serviceName: config.source
31
+ }
32
+ });
33
+ client.register({
34
+ environment: config.environment,
35
+ source: config.source
36
+ });
37
+ return new PostHogAnalyticsService(client, config.waitUntil);
38
+ }
39
+ /** Schedule person properties; the SDK's waitUntil option protects queued delivery. */
40
+ identify(distinctId, properties) {
41
+ const payload = { distinctId };
42
+ if (properties !== void 0) payload.properties = properties;
43
+ this.posthog.identify(payload);
44
+ }
45
+ /** Schedule an irreversible person merge; only alias IDs the application proves belong to one person. */
46
+ alias(distinctId, previousDistinctId) {
47
+ this.waitUntil(this.posthog.aliasImmediate({
48
+ distinctId,
49
+ alias: previousDistinctId
50
+ }));
51
+ }
52
+ /** Schedule an event; omit identity for SDK-generated UUID and capture time. */
53
+ track(event, distinctId, identity) {
54
+ this.capture(event, distinctId, "person", identity);
55
+ }
56
+ /** Schedule an event without creating a person profile for an attempt or resource ID. */
57
+ trackAnonymous(event, distinctId, identity) {
58
+ this.capture(event, distinctId, "anonymous", identity);
59
+ }
60
+ capture(event, distinctId, profile, identity) {
61
+ const properties = { ...event.properties };
62
+ const payload = {
63
+ distinctId,
64
+ event: event.name,
65
+ properties
66
+ };
67
+ if (profile === "anonymous") payload.properties = {
68
+ ...properties,
69
+ $process_person_profile: false
70
+ };
71
+ if (identity !== void 0) {
72
+ payload.uuid = identity.uuid;
73
+ payload.timestamp = identity.timestamp;
74
+ }
75
+ this.waitUntil(this.posthog.captureImmediate(payload));
76
+ }
77
+ };
78
+
79
+ //#endregion
80
+ export { PostHogAnalyticsService };
81
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","names":[],"sources":["../../src/infrastructure/posthog/server.ts"],"sourcesContent":["import { PostHog, type PostHogOptions } from 'posthog-node'\n\nimport type { AnalyticsService } from '@/application'\nimport {\n MissingConfigurationError,\n type AnalyticsEventIdentity,\n type AnalyticsEventInput,\n type AnalyticsProperties,\n} from '@/core'\n\nimport { PostHogMetricsService } from './metrics.ts'\n\n/** Deployment values and SDK options; the runtime owns delivery promises. */\nexport interface PostHogAnalyticsConfig {\n readonly apiKey: string\n readonly environment: string\n readonly source: string\n readonly waitUntil: (promise: Promise<unknown>) => void\n readonly options?: Omit<\n PostHogOptions,\n 'waitUntil' | 'flushAt' | 'flushInterval' | 'metrics' | 'disabled'\n > & {\n readonly metrics?: Omit<NonNullable<PostHogOptions['metrics']>, 'serviceName' | 'environment'>\n }\n}\n\n/** Worker analytics ported from Typist; disabled applications select Porte's NULL_ANALYTICS instead. */\nexport class PostHogAnalyticsService implements AnalyticsService {\n /** Metrics share this service's SDK client and delivery lifetime. */\n readonly metrics: PostHogMetricsService\n\n private constructor(\n /** Raw SDK access for integrations such as PostHog AI tracing. */\n readonly posthog: PostHog,\n private readonly waitUntil: (promise: Promise<unknown>) => void,\n ) {\n this.metrics = new PostHogMetricsService(posthog, waitUntil)\n }\n\n /** Construct once per Worker invocation; a blank key throws MissingConfigurationError. */\n static create(config: PostHogAnalyticsConfig): PostHogAnalyticsService {\n const apiKey = config.apiKey.trim()\n if (apiKey.length === 0) throw new MissingConfigurationError('POSTHOG_API_KEY')\n const client = new PostHog(apiKey, {\n host: 'https://us.i.posthog.com',\n ...config.options,\n flushAt: 1,\n flushInterval: 0,\n waitUntil: config.waitUntil,\n metrics: {\n ...config.options?.metrics,\n environment: config.environment,\n serviceName: config.source,\n },\n })\n // The SDK applies in-memory super properties synchronously before this promise resolves.\n void client.register({ environment: config.environment, source: config.source })\n return new PostHogAnalyticsService(client, config.waitUntil)\n }\n\n /** Schedule person properties; the SDK's waitUntil option protects queued delivery. */\n identify(distinctId: string, properties?: AnalyticsProperties): void {\n const payload: Parameters<PostHog['identify']>[0] = { distinctId }\n if (properties !== undefined) payload.properties = properties\n this.posthog.identify(payload)\n }\n\n /** Schedule an irreversible person merge; only alias IDs the application proves belong to one person. */\n alias(distinctId: string, previousDistinctId: string): void {\n this.waitUntil(this.posthog.aliasImmediate({ distinctId, alias: previousDistinctId }))\n }\n\n /** Schedule an event; omit identity for SDK-generated UUID and capture time. */\n track(event: AnalyticsEventInput, distinctId: string, identity?: AnalyticsEventIdentity): void {\n this.capture(event, distinctId, 'person', identity)\n }\n\n /** Schedule an event without creating a person profile for an attempt or resource ID. */\n trackAnonymous(\n event: AnalyticsEventInput,\n distinctId: string,\n identity?: AnalyticsEventIdentity,\n ): void {\n this.capture(event, distinctId, 'anonymous', identity)\n }\n\n private capture(\n event: AnalyticsEventInput,\n distinctId: string,\n profile: 'person' | 'anonymous',\n identity?: AnalyticsEventIdentity,\n ): void {\n const properties = { ...event.properties }\n const payload: Parameters<PostHog['captureImmediate']>[0] = {\n distinctId,\n event: event.name,\n properties,\n }\n if (profile === 'anonymous')\n payload.properties = { ...properties, $process_person_profile: false }\n if (identity !== undefined) {\n payload.uuid = identity.uuid\n payload.timestamp = identity.timestamp\n }\n this.waitUntil(this.posthog.captureImmediate(payload))\n }\n}\n"],"mappings":";;;;;;AA2BA,IAAa,0BAAb,MAAa,wBAAoD;CAMpD;CACQ;;CALnB,AAAS;CAET,AAAQ,YAEN,AAAS,SACT,AAAiB,WACjB;EAFS;EACQ;EAEjB,KAAK,UAAU,IAAI,sBAAsB,SAAS,SAAS;CAC7D;;CAGA,OAAO,OAAO,QAAyD;EACrE,MAAM,SAAS,OAAO,OAAO,KAAK;EAClC,IAAI,OAAO,WAAW,GAAG,MAAM,IAAI,0BAA0B,iBAAiB;EAC9E,MAAM,SAAS,IAAI,QAAQ,QAAQ;GACjC,MAAM;GACN,GAAG,OAAO;GACV,SAAS;GACT,eAAe;GACf,WAAW,OAAO;GAClB,SAAS;IACP,GAAG,OAAO,SAAS;IACnB,aAAa,OAAO;IACpB,aAAa,OAAO;GACtB;EACF,CAAC;EAED,AAAK,OAAO,SAAS;GAAE,aAAa,OAAO;GAAa,QAAQ,OAAO;EAAO,CAAC;EAC/E,OAAO,IAAI,wBAAwB,QAAQ,OAAO,SAAS;CAC7D;;CAGA,SAAS,YAAoB,YAAwC;EACnE,MAAM,UAA8C,EAAE,WAAW;EACjE,IAAI,eAAe,QAAW,QAAQ,aAAa;EACnD,KAAK,QAAQ,SAAS,OAAO;CAC/B;;CAGA,MAAM,YAAoB,oBAAkC;EAC1D,KAAK,UAAU,KAAK,QAAQ,eAAe;GAAE;GAAY,OAAO;EAAmB,CAAC,CAAC;CACvF;;CAGA,MAAM,OAA4B,YAAoB,UAAyC;EAC7F,KAAK,QAAQ,OAAO,YAAY,UAAU,QAAQ;CACpD;;CAGA,eACE,OACA,YACA,UACM;EACN,KAAK,QAAQ,OAAO,YAAY,aAAa,QAAQ;CACvD;CAEA,AAAQ,QACN,OACA,YACA,SACA,UACM;EACN,MAAM,aAAa,EAAE,GAAG,MAAM,WAAW;EACzC,MAAM,UAAsD;GAC1D;GACA,OAAO,MAAM;GACb;EACF;EACA,IAAI,YAAY,aACd,QAAQ,aAAa;GAAE,GAAG;GAAY,yBAAyB;EAAM;EACvE,IAAI,aAAa,QAAW;GAC1B,QAAQ,OAAO,SAAS;GACxB,QAAQ,YAAY,SAAS;EAC/B;EACA,KAAK,UAAU,KAAK,QAAQ,iBAAiB,OAAO,CAAC;CACvD;AACF"}
package/dist/resend.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { jt as BounceType } from "./index-Bl0K1RQW.js";
2
- import { D as IEmailService, T as EmailAccepted, k as OutboundEmail } from "./index-DwWxIdQP.js";
3
- import { r as WebhookVerifier, t as WebhookVerification } from "./webhook-verification-sZtwTI5n.js";
1
+ import { Mt as BounceType } from "./index-G1QG0P5g.js";
2
+ import { B as OutboundEmail, I as EmailAccepted, R as IEmailService } from "./index-C28HgVfY.js";
3
+ import { r as WebhookVerifier, t as WebhookVerification } from "./webhook-verification-D7DIhH6C.js";
4
4
  import { Resend } from "resend";
5
5
  //#region src/infrastructure/resend/resend-email-service.d.ts
6
6
  /** The SDK operations required by the adapter; a test factory can return a plain object. */
package/dist/resend.js CHANGED
@@ -1,5 +1,5 @@
1
- import { J as EmailServiceError, K as parseBounceType, Z as EmailAddressSchema } from "./core-DG_cC8YT.js";
2
- import { t as WebhookVerificationError } from "./webhook-verification-BQ9qZnVK.js";
1
+ import { Q as EmailAddressSchema, Y as EmailServiceError, q as parseBounceType } from "./core-hdaNiCOU.js";
2
+ import { t as WebhookVerificationError } from "./webhook-verification-trEgyz7I.js";
3
3
  import { z } from "zod";
4
4
  import { Buffer } from "node:buffer";
5
5