better-ship 0.37.0 → 0.38.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 (103) hide show
  1. package/dist/{application-B5hnNUdI.js → application-Dsz-o9Aj.js} +8 -5
  2. package/dist/{application-B5hnNUdI.js.map → application-Dsz-o9Aj.js.map} +1 -1
  3. package/dist/application.d.ts +3 -3
  4. package/dist/application.js +1 -1
  5. package/dist/{auth-secondary-storage-b9ptwPSB.d.ts → auth-secondary-storage-CcNkkEk0.d.ts} +2 -2
  6. package/dist/{auth-secondary-storage-b9ptwPSB.d.ts.map → auth-secondary-storage-CcNkkEk0.d.ts.map} +1 -1
  7. package/dist/better-auth/apple-client-secret.d.ts +1 -1
  8. package/dist/better-auth/apple-client-secret.js +1 -1
  9. package/dist/better-auth/auth-client.js +1 -1
  10. package/dist/better-auth/rate-limit.d.ts +1 -1
  11. package/dist/better-auth/rate-limit.js +1 -1
  12. package/dist/better-auth/secondary-storage.d.ts +1 -1
  13. package/dist/better-auth.js +1 -1
  14. package/dist/billing/client.d.ts +1 -1
  15. package/dist/billing/client.js +1 -1
  16. package/dist/browser-support/floor.d.ts +1 -1
  17. package/dist/browser-support/floor.js +1 -1
  18. package/dist/browser-support/oxlint.js +1 -1
  19. package/dist/cloudflare/auth-secondary-storage.d.ts +1 -1
  20. package/dist/cloudflare/auth-secondary-storage.js +1 -1
  21. package/dist/cloudflare/durable-object-client.js +2 -2
  22. package/dist/cloudflare/durable-object-errors.d.ts +1 -1
  23. package/dist/cloudflare/durable-object-errors.js +1 -1
  24. package/dist/cloudflare/kv-client.js +2 -2
  25. package/dist/cloudflare/kv-errors.d.ts +1 -1
  26. package/dist/cloudflare/kv-errors.js +1 -1
  27. package/dist/cloudflare/queue.d.ts +2 -2
  28. package/dist/cloudflare/queue.js +2 -2
  29. package/dist/cloudflare/r2-client.js +2 -2
  30. package/dist/cloudflare/r2-errors.d.ts +1 -1
  31. package/dist/cloudflare/r2-errors.js +1 -1
  32. package/dist/cloudflare/rate-limiter.d.ts +1 -1
  33. package/dist/cloudflare/rate-limiter.js +1 -1
  34. package/dist/cloudflare/scheduled.d.ts +1 -1
  35. package/dist/cloudflare/scheduled.js +1 -1
  36. package/dist/cloudflare/turnstile.d.ts +1 -1
  37. package/dist/cloudflare/turnstile.js +1 -1
  38. package/dist/cloudflare.d.ts +3 -3
  39. package/dist/cloudflare.js +3 -3
  40. package/dist/{core-BiN6S_cm.js → core-DOBZeYSX.js} +2 -7
  41. package/dist/core-DOBZeYSX.js.map +1 -0
  42. package/dist/core.d.ts +2 -2
  43. package/dist/core.js +2 -2
  44. package/dist/{durable-object-errors-DkMFXQnK.js → durable-object-errors-CcfTu0PQ.js} +2 -2
  45. package/dist/{durable-object-errors-DkMFXQnK.js.map → durable-object-errors-CcfTu0PQ.js.map} +1 -1
  46. package/dist/{floor-Cuw8EIhg.js → floor-BTBtPI9_.js} +2 -2
  47. package/dist/{floor-Cuw8EIhg.js.map → floor-BTBtPI9_.js.map} +1 -1
  48. package/dist/http.d.ts +2 -2
  49. package/dist/http.js +2 -2
  50. package/dist/{index-BosM_da2.d.ts → index-BMY7ylgk.d.ts} +19 -7
  51. package/dist/index-BMY7ylgk.d.ts.map +1 -0
  52. package/dist/{index-DUFS0SJ_.d.ts → index-CtIdbnEZ.d.ts} +17 -11
  53. package/dist/index-CtIdbnEZ.d.ts.map +1 -0
  54. package/dist/{kv-errors-BUTpFy0t.js → kv-errors-B2k_2lyY.js} +2 -2
  55. package/dist/{kv-errors-BUTpFy0t.js.map → kv-errors-B2k_2lyY.js.map} +1 -1
  56. package/dist/{metrics-B89jvpZQ.d.ts → metrics-CdTF_lWR.d.ts} +2 -2
  57. package/dist/{metrics-B89jvpZQ.d.ts.map → metrics-CdTF_lWR.d.ts.map} +1 -1
  58. package/dist/{outbox-repository-options-BNpCWTht.js → outbox-repository-options-Ca67c3c9.js} +2 -2
  59. package/dist/{outbox-repository-options-BNpCWTht.js.map → outbox-repository-options-Ca67c3c9.js.map} +1 -1
  60. package/dist/{outbox-repository-options-A2fA1UDX.d.ts → outbox-repository-options-q-CGUoCr.d.ts} +3 -3
  61. package/dist/{outbox-repository-options-A2fA1UDX.d.ts.map → outbox-repository-options-q-CGUoCr.d.ts.map} +1 -1
  62. package/dist/{payment-service.error-CqF7GkNp.js → payment-service.error-BR10zqxk.js} +2 -2
  63. package/dist/{payment-service.error-CqF7GkNp.js.map → payment-service.error-BR10zqxk.js.map} +1 -1
  64. package/dist/postgres.d.ts +3 -3
  65. package/dist/postgres.js +3 -3
  66. package/dist/posthog/metrics.d.ts +1 -1
  67. package/dist/posthog/server.d.ts +6 -6
  68. package/dist/posthog/server.d.ts.map +1 -1
  69. package/dist/posthog/server.js +15 -13
  70. package/dist/posthog/server.js.map +1 -1
  71. package/dist/{r2-errors-rk7n7rvk.js → r2-errors-BF49eqU_.js} +2 -2
  72. package/dist/{r2-errors-rk7n7rvk.js.map → r2-errors-BF49eqU_.js.map} +1 -1
  73. package/dist/{request-4vJEWxyZ.js → request-BYQodKDU.js} +2 -2
  74. package/dist/{request-4vJEWxyZ.js.map → request-BYQodKDU.js.map} +1 -1
  75. package/dist/resend.d.ts +3 -3
  76. package/dist/resend.js +2 -2
  77. package/dist/sentry/client.d.ts +1 -1
  78. package/dist/sentry/client.js +1 -1
  79. package/dist/sentry/server.d.ts +2 -2
  80. package/dist/sentry/tanstack.d.ts +1 -1
  81. package/dist/stripe/payment-service.error.d.ts +1 -1
  82. package/dist/stripe/payment-service.error.js +1 -1
  83. package/dist/stripe/payment-service.js +1 -1
  84. package/dist/stripe/payment-webhook.d.ts +1 -1
  85. package/dist/stripe/payment-webhook.js +1 -1
  86. package/dist/stripe/request.js +1 -1
  87. package/dist/tanstack.d.ts +1 -1
  88. package/dist/tanstack.js +1 -1
  89. package/dist/testing/outbox-repository.d.ts +2 -2
  90. package/dist/testing/payment-service.js +1 -1
  91. package/dist/{webhook-verification-lWso44-q.js → webhook-verification-C4R6OO7f.js} +2 -2
  92. package/dist/{webhook-verification-lWso44-q.js.map → webhook-verification-C4R6OO7f.js.map} +1 -1
  93. package/dist/{webhook-verification-D1c7BY4q.d.ts → webhook-verification-DL4L3zbu.d.ts} +2 -2
  94. package/dist/{webhook-verification-D1c7BY4q.d.ts.map → webhook-verification-DL4L3zbu.d.ts.map} +1 -1
  95. package/package.json +1 -1
  96. package/src/application/analytics-service.ts +35 -8
  97. package/src/application/index.ts +5 -1
  98. package/src/core/analytics-event.ts +17 -13
  99. package/src/core/index.ts +1 -3
  100. package/src/infrastructure/posthog/server.ts +28 -17
  101. package/dist/core-BiN6S_cm.js.map +0 -1
  102. package/dist/index-BosM_da2.d.ts.map +0 -1
  103. package/dist/index-DUFS0SJ_.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
- import "./index-DUFS0SJ_.js";
2
- import { F as IEventCollector } from "./index-BosM_da2.js";
1
+ import "./index-CtIdbnEZ.js";
2
+ import { F as IEventCollector } from "./index-BMY7ylgk.js";
3
3
  import { BatchItem } from "drizzle-orm/batch";
4
4
  //#region src/infrastructure/drizzle-schema.d.ts
5
5
  /**
@@ -23,4 +23,4 @@ declare const OUTBOX_REPOSITORY_DEFAULTS: {
23
23
  type OutboxRepositoryOptions = Readonly<Partial<typeof OUTBOX_REPOSITORY_DEFAULTS>>;
24
24
  //#endregion
25
25
  export { eventCollector as n, DrizzleSchema as r, OutboxRepositoryOptions as t };
26
- //# sourceMappingURL=outbox-repository-options-A2fA1UDX.d.ts.map
26
+ //# sourceMappingURL=outbox-repository-options-q-CGUoCr.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"outbox-repository-options-A2fA1UDX.d.ts","names":[],"sources":["../src/infrastructure/drizzle-schema.ts","../src/infrastructure/unit-of-work-context.ts","../src/infrastructure/outbox-repository-options.ts"],"mappings":";;;;;;;;KAKY,gBAAgB;;;;cCuBf,gBAAgB;;;;cC3BhB;EACX;EACA;EACA;;;KAIU,0BAA0B,SAAS,eAAe"}
1
+ {"version":3,"file":"outbox-repository-options-q-CGUoCr.d.ts","names":[],"sources":["../src/infrastructure/drizzle-schema.ts","../src/infrastructure/unit-of-work-context.ts","../src/infrastructure/outbox-repository-options.ts"],"mappings":";;;;;;;;KAKY,gBAAgB;;;;cCuBf,gBAAgB;;;;cC3BhB;EACX;EACA;EACA;;;KAIU,0BAA0B,SAAS,eAAe"}
@@ -1,4 +1,4 @@
1
- import { nt as AppError } from "./core-BiN6S_cm.js";
1
+ import { tt as AppError } from "./core-DOBZeYSX.js";
2
2
  import Stripe from "stripe";
3
3
 
4
4
  //#region src/infrastructure/stripe/payment-service.error.ts
@@ -27,4 +27,4 @@ function classifyStripeError(error) {
27
27
 
28
28
  //#endregion
29
29
  export { PaymentServiceError as t };
30
- //# sourceMappingURL=payment-service.error-CqF7GkNp.js.map
30
+ //# sourceMappingURL=payment-service.error-BR10zqxk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"payment-service.error-CqF7GkNp.js","names":[],"sources":["../src/infrastructure/stripe/payment-service.error.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { AppError, type FailureClassification } from '@/core'\n\n/** A final Stripe request failure. The caller logs once; native details remain in cause. */\nexport class PaymentServiceError extends AppError {\n readonly _tag = 'PaymentServiceError'\n readonly classification: FailureClassification\n\n /** Classify the final SDK error without scheduling another payment attempt. */\n constructor(\n readonly operation: string,\n cause: Stripe.errors.StripeError,\n ) {\n super(`Stripe request failed: ${operation}`, { cause })\n this.classification = classifyStripeError(cause)\n }\n}\n\nfunction classifyStripeError(error: Stripe.errors.StripeError): FailureClassification {\n if (error.headers?.['stripe-should-retry'] === 'false') return 'terminal'\n const status = error.statusCode\n if (status !== undefined && status >= 400 && status < 500 && status !== 409 && status !== 429)\n return 'terminal'\n if (\n error instanceof Stripe.errors.StripeAuthenticationError ||\n error instanceof Stripe.errors.StripePermissionError ||\n error instanceof Stripe.errors.StripeInvalidRequestError ||\n error instanceof Stripe.errors.StripeIdempotencyError ||\n error instanceof Stripe.errors.StripeCardError\n )\n return 'terminal'\n if (error.headers?.['stripe-should-retry'] === 'true') return 'transient'\n if (status === 409 || status === 429 || (status !== undefined && status >= 500))\n return 'transient'\n if (\n error instanceof Stripe.errors.StripeConnectionError ||\n error instanceof Stripe.errors.StripeAPIError ||\n error instanceof Stripe.errors.StripeRateLimitError\n )\n return 'transient'\n return 'unknown'\n}\n"],"mappings":";;;;;AAKA,IAAa,sBAAb,cAAyC,SAAS;CAMrC;CALX,AAAS,OAAO;CAChB,AAAS;;CAGT,YACE,AAAS,WACT,OACA;EACA,MAAM,0BAA0B,aAAa,EAAE,MAAM,CAAC;EAH7C;EAIT,KAAK,iBAAiB,oBAAoB,KAAK;CACjD;AACF;AAEA,SAAS,oBAAoB,OAAyD;CACpF,IAAI,MAAM,UAAU,2BAA2B,SAAS,OAAO;CAC/D,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,UAAa,UAAU,OAAO,SAAS,OAAO,WAAW,OAAO,WAAW,KACxF,OAAO;CACT,IACE,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,yBAC/B,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,0BAC/B,iBAAiB,OAAO,OAAO,iBAE/B,OAAO;CACT,IAAI,MAAM,UAAU,2BAA2B,QAAQ,OAAO;CAC9D,IAAI,WAAW,OAAO,WAAW,OAAQ,WAAW,UAAa,UAAU,KACzE,OAAO;CACT,IACE,iBAAiB,OAAO,OAAO,yBAC/B,iBAAiB,OAAO,OAAO,kBAC/B,iBAAiB,OAAO,OAAO,sBAE/B,OAAO;CACT,OAAO;AACT"}
1
+ {"version":3,"file":"payment-service.error-BR10zqxk.js","names":[],"sources":["../src/infrastructure/stripe/payment-service.error.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { AppError, type FailureClassification } from '@/core'\n\n/** A final Stripe request failure. The caller logs once; native details remain in cause. */\nexport class PaymentServiceError extends AppError {\n readonly _tag = 'PaymentServiceError'\n readonly classification: FailureClassification\n\n /** Classify the final SDK error without scheduling another payment attempt. */\n constructor(\n readonly operation: string,\n cause: Stripe.errors.StripeError,\n ) {\n super(`Stripe request failed: ${operation}`, { cause })\n this.classification = classifyStripeError(cause)\n }\n}\n\nfunction classifyStripeError(error: Stripe.errors.StripeError): FailureClassification {\n if (error.headers?.['stripe-should-retry'] === 'false') return 'terminal'\n const status = error.statusCode\n if (status !== undefined && status >= 400 && status < 500 && status !== 409 && status !== 429)\n return 'terminal'\n if (\n error instanceof Stripe.errors.StripeAuthenticationError ||\n error instanceof Stripe.errors.StripePermissionError ||\n error instanceof Stripe.errors.StripeInvalidRequestError ||\n error instanceof Stripe.errors.StripeIdempotencyError ||\n error instanceof Stripe.errors.StripeCardError\n )\n return 'terminal'\n if (error.headers?.['stripe-should-retry'] === 'true') return 'transient'\n if (status === 409 || status === 429 || (status !== undefined && status >= 500))\n return 'transient'\n if (\n error instanceof Stripe.errors.StripeConnectionError ||\n error instanceof Stripe.errors.StripeAPIError ||\n error instanceof Stripe.errors.StripeRateLimitError\n )\n return 'transient'\n return 'unknown'\n}\n"],"mappings":";;;;;AAKA,IAAa,sBAAb,cAAyC,SAAS;CAMrC;CALX,AAAS,OAAO;CAChB,AAAS;;CAGT,YACE,AAAS,WACT,OACA;EACA,MAAM,0BAA0B,aAAa,EAAE,MAAM,CAAC;EAH7C;EAIT,KAAK,iBAAiB,oBAAoB,KAAK;CACjD;AACF;AAEA,SAAS,oBAAoB,OAAyD;CACpF,IAAI,MAAM,UAAU,2BAA2B,SAAS,OAAO;CAC/D,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,UAAa,UAAU,OAAO,SAAS,OAAO,WAAW,OAAO,WAAW,KACxF,OAAO;CACT,IACE,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,yBAC/B,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,0BAC/B,iBAAiB,OAAO,OAAO,iBAE/B,OAAO;CACT,IAAI,MAAM,UAAU,2BAA2B,QAAQ,OAAO;CAC9D,IAAI,WAAW,OAAO,WAAW,OAAQ,WAAW,UAAa,UAAU,KACzE,OAAO;CACT,IACE,iBAAiB,OAAO,OAAO,yBAC/B,iBAAiB,OAAO,OAAO,kBAC/B,iBAAiB,OAAO,OAAO,sBAE/B,OAAO;CACT,OAAO;AACT"}
@@ -1,6 +1,6 @@
1
- import { W as DomainEvent, Xt as AppError, 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-BosM_da2.js";
3
- import { n as eventCollector, r as DrizzleSchema, t as OutboxRepositoryOptions } from "./outbox-repository-options-A2fA1UDX.js";
1
+ import { Jt as AppError, W as DomainEvent, nt as DatabaseFailure, q as MessageId, tt as DatabaseError } from "./index-CtIdbnEZ.js";
2
+ import { A as IOutboxRepository, M as OutboxClaim, N as OutboxMessage, P as IMessageStore, k as DeadLetterReason, w as ITransaction } from "./index-BMY7ylgk.js";
3
+ import { n as eventCollector, r as DrizzleSchema, t as OutboxRepositoryOptions } from "./outbox-repository-options-q-CGUoCr.js";
4
4
  import { ExtractTablesWithRelations } from "drizzle-orm";
5
5
  import { NodePgDatabase, NodePgTransaction } from "drizzle-orm/node-postgres";
6
6
  import { Pool } from "pg";
package/dist/postgres.js CHANGED
@@ -1,6 +1,6 @@
1
- import { O as DatabaseError, nt as AppError, s as exponentialDelayCapMs } from "./core-BiN6S_cm.js";
2
- import { o as MessageAlreadyProcessedError } from "./application-B5hnNUdI.js";
3
- import { i as runWithUnitOfWorkContext, r as eventCollector, t as OUTBOX_REPOSITORY_DEFAULTS } from "./outbox-repository-options-BNpCWTht.js";
1
+ import { O as DatabaseError, s as exponentialDelayCapMs, tt as AppError } from "./core-DOBZeYSX.js";
2
+ import { o as MessageAlreadyProcessedError } from "./application-Dsz-o9Aj.js";
3
+ import { i as runWithUnitOfWorkContext, r as eventCollector, t as OUTBOX_REPOSITORY_DEFAULTS } from "./outbox-repository-options-Ca67c3c9.js";
4
4
  import { Result } from "better-result";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { DrizzleQueryError } from "drizzle-orm/errors";
@@ -1,2 +1,2 @@
1
- import { t as PostHogMetricsService } from "../metrics-B89jvpZQ.js";
1
+ import { t as PostHogMetricsService } from "../metrics-CdTF_lWR.js";
2
2
  export { PostHogMetricsService };
@@ -1,6 +1,6 @@
1
- import { Gt as AnalyticsEventIdentity, Kt as AnalyticsEventInput, qt as AnalyticsProperties } from "../index-DUFS0SJ_.js";
2
- import { tt as AnalyticsService } from "../index-BosM_da2.js";
3
- import { t as PostHogMetricsService } from "../metrics-B89jvpZQ.js";
1
+ import { Kt as AnalyticsTarget, Ut as AnalyticsEventInput, Wt as AnalyticsProperties } from "../index-CtIdbnEZ.js";
2
+ import { nt as AnalyticsService } from "../index-BMY7ylgk.js";
3
+ import { t as PostHogMetricsService } from "../metrics-CdTF_lWR.js";
4
4
  import { PostHog, PostHogOptions } from "posthog-node";
5
5
  //#region src/infrastructure/posthog/server.d.ts
6
6
  /** Deployment values and SDK options; the runtime owns delivery promises. */
@@ -27,10 +27,10 @@ export declare class PostHogAnalyticsService implements AnalyticsService {
27
27
  identify(distinctId: string, properties?: AnalyticsProperties): void;
28
28
  /** Schedule an irreversible person merge; only alias IDs the application proves belong to one person. */
29
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;
30
+ /** Schedule an event; without an event ID the SDK sets a new UUID and the capture time. */
31
+ track(name: string, properties: AnalyticsEventInput['properties'], target: AnalyticsTarget): void;
32
32
  /** Schedule an event without creating a person profile for an attempt or resource ID. */
33
- trackAnonymous(event: AnalyticsEventInput, distinctId: string, identity?: AnalyticsEventIdentity): void;
33
+ trackAnonymous(name: string, properties: AnalyticsEventInput['properties'], target: AnalyticsTarget): void;
34
34
  private capture;
35
35
  }
36
36
  //#endregion
@@ -1 +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"}
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../../src/infrastructure/posthog/server.ts"],"mappings":";;;;;;iBAiBiB;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,MACE,cACA,YAAY,mCACZ,QAAQ;;EAMV,eACE,cACA,YAAY,mCACZ,QAAQ;UAKF"}
@@ -1,8 +1,10 @@
1
- import { tt as MissingConfigurationError } from "../core-BiN6S_cm.js";
1
+ import { et as MissingConfigurationError } from "../core-DOBZeYSX.js";
2
2
  import { t as PostHogMetricsService } from "../metrics-CWv37yf9.js";
3
+ import { v5, validate } from "uuid";
3
4
  import { PostHog } from "posthog-node";
4
5
 
5
6
  //#region src/infrastructure/posthog/server.ts
7
+ const EVENT_ID_NAMESPACE = "53d7af30-65cb-4176-b2c9-f1ed42367ab1";
6
8
  /** Worker analytics ported from Typist; disabled applications select Porte's NULL_ANALYTICS instead. */
7
9
  var PostHogAnalyticsService = class PostHogAnalyticsService {
8
10
  posthog;
@@ -49,28 +51,28 @@ var PostHogAnalyticsService = class PostHogAnalyticsService {
49
51
  alias: previousDistinctId
50
52
  }));
51
53
  }
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);
54
+ /** Schedule an event; without an event ID the SDK sets a new UUID and the capture time. */
55
+ track(name, properties, target) {
56
+ this.capture(name, properties, target, "person");
55
57
  }
56
58
  /** 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
+ trackAnonymous(name, properties, target) {
60
+ this.capture(name, properties, target, "anonymous");
59
61
  }
60
- capture(event, distinctId, profile, identity) {
61
- const properties = { ...event.properties };
62
+ capture(name, eventProperties, target, profile) {
63
+ const properties = { ...eventProperties };
62
64
  const payload = {
63
- distinctId,
64
- event: event.name,
65
+ distinctId: target.distinctId,
66
+ event: name,
65
67
  properties
66
68
  };
67
69
  if (profile === "anonymous") payload.properties = {
68
70
  ...properties,
69
71
  $process_person_profile: false
70
72
  };
71
- if (identity !== void 0) {
72
- payload.uuid = identity.uuid;
73
- payload.timestamp = identity.timestamp;
73
+ if (target.eventId !== void 0) {
74
+ payload.uuid = validate(target.eventId) ? target.eventId : v5(target.eventId, EVENT_ID_NAMESPACE);
75
+ payload.timestamp = new Date(target.occurredAt);
74
76
  }
75
77
  this.waitUntil(this.posthog.captureImmediate(payload));
76
78
  }
@@ -1 +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"}
1
+ {"version":3,"file":"server.js","names":["isUuid","uuidv5"],"sources":["../../src/infrastructure/posthog/server.ts"],"sourcesContent":["import { PostHog, type PostHogOptions } from 'posthog-node'\nimport { validate as isUuid, v5 as uuidv5 } from 'uuid'\n\nimport type { AnalyticsService } from '@/application'\nimport {\n MissingConfigurationError,\n type AnalyticsEventInput,\n type AnalyticsProperties,\n type AnalyticsTarget,\n} from '@/core'\n\nimport { PostHogMetricsService } from './metrics.ts'\n\n// Fixed forever: changing it changes every derived UUID, so PostHog would keep a repeat.\nconst EVENT_ID_NAMESPACE = '53d7af30-65cb-4176-b2c9-f1ed42367ab1'\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; without an event ID the SDK sets a new UUID and the capture time. */\n track(\n name: string,\n properties: AnalyticsEventInput['properties'],\n target: AnalyticsTarget,\n ): void {\n this.capture(name, properties, target, 'person')\n }\n\n /** Schedule an event without creating a person profile for an attempt or resource ID. */\n trackAnonymous(\n name: string,\n properties: AnalyticsEventInput['properties'],\n target: AnalyticsTarget,\n ): void {\n this.capture(name, properties, target, 'anonymous')\n }\n\n private capture(\n name: string,\n eventProperties: AnalyticsEventInput['properties'],\n target: AnalyticsTarget,\n profile: 'person' | 'anonymous',\n ): void {\n const properties = { ...eventProperties }\n const payload: Parameters<PostHog['captureImmediate']>[0] = {\n distinctId: target.distinctId,\n event: name,\n properties,\n }\n if (profile === 'anonymous')\n payload.properties = { ...properties, $process_person_profile: false }\n if (target.eventId !== undefined) {\n // PostHog drops a repeat only when its uuid and timestamp match the first event.\n payload.uuid = isUuid(target.eventId)\n ? target.eventId\n : uuidv5(target.eventId, EVENT_ID_NAMESPACE)\n payload.timestamp = new Date(target.occurredAt)\n }\n this.waitUntil(this.posthog.captureImmediate(payload))\n }\n}\n"],"mappings":";;;;;;AAcA,MAAM,qBAAqB;;AAiB3B,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,MACE,MACA,YACA,QACM;EACN,KAAK,QAAQ,MAAM,YAAY,QAAQ,QAAQ;CACjD;;CAGA,eACE,MACA,YACA,QACM;EACN,KAAK,QAAQ,MAAM,YAAY,QAAQ,WAAW;CACpD;CAEA,AAAQ,QACN,MACA,iBACA,QACA,SACM;EACN,MAAM,aAAa,EAAE,GAAG,gBAAgB;EACxC,MAAM,UAAsD;GAC1D,YAAY,OAAO;GACnB,OAAO;GACP;EACF;EACA,IAAI,YAAY,aACd,QAAQ,aAAa;GAAE,GAAG;GAAY,yBAAyB;EAAM;EACvE,IAAI,OAAO,YAAY,QAAW;GAEhC,QAAQ,OAAOA,SAAO,OAAO,OAAO,IAChC,OAAO,UACPC,GAAO,OAAO,SAAS,kBAAkB;GAC7C,QAAQ,YAAY,IAAI,KAAK,OAAO,UAAU;EAChD;EACA,KAAK,UAAU,KAAK,QAAQ,iBAAiB,OAAO,CAAC;CACvD;AACF"}
@@ -1,4 +1,4 @@
1
- import { nt as AppError, u as classifyBoundaryError, v as isAbortError } from "./core-BiN6S_cm.js";
1
+ import { tt as AppError, u as classifyBoundaryError, v as isAbortError } from "./core-DOBZeYSX.js";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/infrastructure/cloudflare/r2-errors.ts
@@ -87,4 +87,4 @@ var R2JsonError = class extends AppError {
87
87
 
88
88
  //#endregion
89
89
  export { R2JsonError as n, R2Error as t };
90
- //# sourceMappingURL=r2-errors-rk7n7rvk.js.map
90
+ //# sourceMappingURL=r2-errors-BF49eqU_.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"r2-errors-rk7n7rvk.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
+ {"version":3,"file":"r2-errors-BF49eqU_.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 { t as PaymentServiceError } from "./payment-service.error-CqF7GkNp.js";
1
+ import { t as PaymentServiceError } from "./payment-service.error-BR10zqxk.js";
2
2
  import Stripe from "stripe";
3
3
 
4
4
  //#region src/infrastructure/stripe/request.ts
@@ -18,4 +18,4 @@ async function stripeRequest(operation, work) {
18
18
 
19
19
  //#endregion
20
20
  export { stripeRequest as t };
21
- //# sourceMappingURL=request-4vJEWxyZ.js.map
21
+ //# sourceMappingURL=request-BYQodKDU.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"request-4vJEWxyZ.js","names":[],"sources":["../src/infrastructure/stripe/request.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { PaymentServiceError } from './payment-service.error.ts'\n\n/**\n * Invoke one SDK operation; the SDK owns retries configured on its client or request.\n * Convert every Stripe error to PaymentServiceError without logging; preserve the original error in cause.\n * Non-Stripe failures propagate unchanged.\n */\nexport async function stripeRequest<T>(operation: string, work: () => Promise<T>): Promise<T> {\n try {\n return await work()\n } catch (cause) {\n if (!(cause instanceof Stripe.errors.StripeError)) throw cause\n throw new PaymentServiceError(operation, cause)\n }\n}\n"],"mappings":";;;;;;;;;AASA,eAAsB,cAAiB,WAAmB,MAAoC;CAC5F,IAAI;EACF,OAAO,MAAM,KAAK;CACpB,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,OAAO,OAAO,cAAc,MAAM;EACzD,MAAM,IAAI,oBAAoB,WAAW,KAAK;CAChD;AACF"}
1
+ {"version":3,"file":"request-BYQodKDU.js","names":[],"sources":["../src/infrastructure/stripe/request.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { PaymentServiceError } from './payment-service.error.ts'\n\n/**\n * Invoke one SDK operation; the SDK owns retries configured on its client or request.\n * Convert every Stripe error to PaymentServiceError without logging; preserve the original error in cause.\n * Non-Stripe failures propagate unchanged.\n */\nexport async function stripeRequest<T>(operation: string, work: () => Promise<T>): Promise<T> {\n try {\n return await work()\n } catch (cause) {\n if (!(cause instanceof Stripe.errors.StripeError)) throw cause\n throw new PaymentServiceError(operation, cause)\n }\n}\n"],"mappings":";;;;;;;;;AASA,eAAsB,cAAiB,WAAmB,MAAoC;CAC5F,IAAI;EACF,OAAO,MAAM,KAAK;CACpB,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,OAAO,OAAO,cAAc,MAAM;EACzD,MAAM,IAAI,oBAAoB,WAAW,KAAK;CAChD;AACF"}
package/dist/resend.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { jt as BounceType } from "./index-DUFS0SJ_.js";
2
- import { B as OutboundEmail, H as ReceivedContent, I as EmailAccepted, R as IEmailService, U as ReceivedEmail } from "./index-BosM_da2.js";
3
- import { n as WebhookVerifier } from "./webhook-verification-D1c7BY4q.js";
1
+ import { jt as BounceType } from "./index-CtIdbnEZ.js";
2
+ import { B as OutboundEmail, H as ReceivedContent, I as EmailAccepted, R as IEmailService, U as ReceivedEmail } from "./index-BMY7ylgk.js";
3
+ import { n as WebhookVerifier } from "./webhook-verification-DL4L3zbu.js";
4
4
  import "resend";
5
5
  //#region src/infrastructure/resend/resend-email-service.d.ts
6
6
  /**
package/dist/resend.js CHANGED
@@ -1,5 +1,5 @@
1
- import { $ as EmailAddressSchema, J as parseBounceType, X as EmailServiceError } from "./core-BiN6S_cm.js";
2
- import { t as WebhookVerificationError } from "./webhook-verification-lWso44-q.js";
1
+ import { $ as EmailAddressSchema, J as parseBounceType, X as EmailServiceError } from "./core-DOBZeYSX.js";
2
+ import { t as WebhookVerificationError } from "./webhook-verification-C4R6OO7f.js";
3
3
  import { z } from "zod";
4
4
  import { Buffer } from "node:buffer";
5
5
 
@@ -1,4 +1,4 @@
1
- import { u as LoggerErrorHook } from "../index-DUFS0SJ_.js";
1
+ import { u as LoggerErrorHook } from "../index-CtIdbnEZ.js";
2
2
  import * as Sentry from "@sentry/tanstackstart-react";
3
3
  //#region src/infrastructure/sentry/client.d.ts
4
4
  /** Application settings use Sentry's names; enablement and environment belong to the application. */
@@ -1,4 +1,4 @@
1
- import { a as setLoggerErrorHook } from "../core-BiN6S_cm.js";
1
+ import { a as setLoggerErrorHook } from "../core-DOBZeYSX.js";
2
2
  import * as Sentry from "@sentry/tanstackstart-react";
3
3
  import { isNotFound, isRedirect } from "@tanstack/react-router";
4
4
 
@@ -1,5 +1,5 @@
1
- import { u as LoggerErrorHook } from "../index-DUFS0SJ_.js";
2
- import { J as ObservabilityService, Y as ObservabilityUser, q as ObservabilityScope } from "../index-BosM_da2.js";
1
+ import { u as LoggerErrorHook } from "../index-CtIdbnEZ.js";
2
+ import { J as ObservabilityService, Y as ObservabilityUser, q as ObservabilityScope } from "../index-BMY7ylgk.js";
3
3
  import * as Sentry from "@sentry/cloudflare";
4
4
  //#region src/infrastructure/sentry/server.d.ts
5
5
  /** Application-owned deployment settings with the full Cloudflare SDK option contract. */
@@ -1,4 +1,4 @@
1
- import { ot as ErrorTable } from "../index-DUFS0SJ_.js";
1
+ import { ot as ErrorTable } from "../index-CtIdbnEZ.js";
2
2
  import { ErrorEvent, EventHint } from "@sentry/cloudflare";
3
3
  //#region src/infrastructure/sentry/tanstack.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { Xt as AppError, Zt as FailureClassification } from "../index-DUFS0SJ_.js";
1
+ import { Jt as AppError, Yt as FailureClassification } from "../index-CtIdbnEZ.js";
2
2
  import Stripe from "stripe";
3
3
  //#region src/infrastructure/stripe/payment-service.error.d.ts
4
4
  /** A final Stripe request failure. The caller logs once; native details remain in cause. */
@@ -1,3 +1,3 @@
1
- import { t as PaymentServiceError } from "../payment-service.error-CqF7GkNp.js";
1
+ import { t as PaymentServiceError } from "../payment-service.error-BR10zqxk.js";
2
2
 
3
3
  export { PaymentServiceError };
@@ -1,4 +1,4 @@
1
- import { t as stripeRequest } from "../request-4vJEWxyZ.js";
1
+ import { t as stripeRequest } from "../request-BYQodKDU.js";
2
2
 
3
3
  //#region src/infrastructure/stripe/payment-service.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { n as WebhookVerifier } from "../webhook-verification-D1c7BY4q.js";
1
+ import { n as WebhookVerifier } from "../webhook-verification-DL4L3zbu.js";
2
2
  import Stripe from "stripe";
3
3
  //#region src/infrastructure/stripe/payment-webhook.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { t as WebhookVerificationError } from "../webhook-verification-lWso44-q.js";
1
+ import { t as WebhookVerificationError } from "../webhook-verification-C4R6OO7f.js";
2
2
  import Stripe from "stripe";
3
3
 
4
4
  //#region src/infrastructure/stripe/payment-webhook.ts
@@ -1,3 +1,3 @@
1
- import { t as stripeRequest } from "../request-4vJEWxyZ.js";
1
+ import { t as stripeRequest } from "../request-BYQodKDU.js";
2
2
 
3
3
  export { stripeRequest };
@@ -1,4 +1,4 @@
1
- import { ot as ErrorTable } from "./index-DUFS0SJ_.js";
1
+ import { ot as ErrorTable } from "./index-CtIdbnEZ.js";
2
2
  import { Register } from "@tanstack/react-start";
3
3
  import { BetterAuthCookies } from "better-auth";
4
4
  //#region src/infrastructure/tanstack/request-context.d.ts
package/dist/tanstack.js CHANGED
@@ -1,4 +1,4 @@
1
- import { H as StaleSessionError, R as ValidationError, W as AuthenticationError, nt as AppError, r as createLogger } from "./core-BiN6S_cm.js";
1
+ import { H as StaleSessionError, R as ValidationError, W as AuthenticationError, r as createLogger, tt as AppError } from "./core-DOBZeYSX.js";
2
2
  import { t as applyObservabilityUser } from "./request-user-B4rh1c2-.js";
3
3
  import { n as withSpan } from "./tracing-C_7B_odV.js";
4
4
  import { z } from "zod";
@@ -1,5 +1,5 @@
1
- import { W as DomainEvent } from "../index-DUFS0SJ_.js";
2
- import { A as IOutboxRepository } from "../index-BosM_da2.js";
1
+ import { W as DomainEvent } from "../index-CtIdbnEZ.js";
2
+ import { A as IOutboxRepository } from "../index-BMY7ylgk.js";
3
3
  //#region src/infrastructure/testing/outbox-repository-contract.d.ts
4
4
  /** Stored fields observed by the adapter contract, independent of a database library. */
5
5
  export type OutboxContractRow = {
@@ -1,4 +1,4 @@
1
- import { t as PaymentServiceError } from "../payment-service.error-CqF7GkNp.js";
1
+ import { t as PaymentServiceError } from "../payment-service.error-BR10zqxk.js";
2
2
  import { t as stripeFixture } from "../stripe-fixture-yFUknTJO.js";
3
3
  import Stripe from "stripe";
4
4
  import { deepStrictEqual, equal, ok, rejects } from "node:assert/strict";
@@ -1,4 +1,4 @@
1
- import { nt as AppError } from "./core-BiN6S_cm.js";
1
+ import { tt as AppError } from "./core-DOBZeYSX.js";
2
2
 
3
3
  //#region src/infrastructure/http/webhook-verification.ts
4
4
  /** A webhook cannot be trusted or parsed. The HTTP handler logs the rejection once. */
@@ -15,4 +15,4 @@ var WebhookVerificationError = class extends AppError {
15
15
 
16
16
  //#endregion
17
17
  export { WebhookVerificationError as t };
18
- //# sourceMappingURL=webhook-verification-lWso44-q.js.map
18
+ //# sourceMappingURL=webhook-verification-C4R6OO7f.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-verification-lWso44-q.js","names":[],"sources":["../src/infrastructure/http/webhook-verification.ts"],"sourcesContent":["import { AppError } from '@/core'\n\n/** A webhook cannot be trusted or parsed. The HTTP handler logs the rejection once. */\nexport class WebhookVerificationError extends AppError {\n readonly _tag = 'WebhookVerificationError'\n readonly classification = 'terminal'\n\n /** Keep secrets and payload details out of the public message. */\n constructor(\n readonly reason: 'missing_headers' | 'invalid_signature' | 'invalid_payload',\n cause?: unknown,\n ) {\n super(`Webhook rejected: ${reason}`, { cause })\n }\n}\n\n/** The provider owns signature verification, timestamp tolerance, and payload parsing. */\nexport interface WebhookVerifier<Verified> {\n /**\n * Verify the exact body before parsing it. Return null for an ignored webhook.\n * @throws WebhookVerificationError when the request cannot be trusted or parsed.\n */\n verify(body: string, headers: Headers): Promise<Verified | null>\n}\n"],"mappings":";;;;AAGA,IAAa,2BAAb,cAA8C,SAAS;CAM1C;CALX,AAAS,OAAO;CAChB,AAAS,iBAAiB;;CAG1B,YACE,AAAS,QACT,OACA;EACA,MAAM,qBAAqB,UAAU,EAAE,MAAM,CAAC;EAHrC;CAIX;AACF"}
1
+ {"version":3,"file":"webhook-verification-C4R6OO7f.js","names":[],"sources":["../src/infrastructure/http/webhook-verification.ts"],"sourcesContent":["import { AppError } from '@/core'\n\n/** A webhook cannot be trusted or parsed. The HTTP handler logs the rejection once. */\nexport class WebhookVerificationError extends AppError {\n readonly _tag = 'WebhookVerificationError'\n readonly classification = 'terminal'\n\n /** Keep secrets and payload details out of the public message. */\n constructor(\n readonly reason: 'missing_headers' | 'invalid_signature' | 'invalid_payload',\n cause?: unknown,\n ) {\n super(`Webhook rejected: ${reason}`, { cause })\n }\n}\n\n/** The provider owns signature verification, timestamp tolerance, and payload parsing. */\nexport interface WebhookVerifier<Verified> {\n /**\n * Verify the exact body before parsing it. Return null for an ignored webhook.\n * @throws WebhookVerificationError when the request cannot be trusted or parsed.\n */\n verify(body: string, headers: Headers): Promise<Verified | null>\n}\n"],"mappings":";;;;AAGA,IAAa,2BAAb,cAA8C,SAAS;CAM1C;CALX,AAAS,OAAO;CAChB,AAAS,iBAAiB;;CAG1B,YACE,AAAS,QACT,OACA;EACA,MAAM,qBAAqB,UAAU,EAAE,MAAM,CAAC;EAHrC;CAIX;AACF"}
@@ -1,4 +1,4 @@
1
- import { Xt as AppError } from "./index-DUFS0SJ_.js";
1
+ import { Jt as AppError } from "./index-CtIdbnEZ.js";
2
2
  //#region src/infrastructure/http/webhook-verification.d.ts
3
3
  /** A webhook cannot be trusted or parsed. The HTTP handler logs the rejection once. */
4
4
  declare class WebhookVerificationError extends AppError {
@@ -18,4 +18,4 @@ interface WebhookVerifier<Verified> {
18
18
  }
19
19
  //#endregion
20
20
  export { WebhookVerifier as n, WebhookVerificationError as t };
21
- //# sourceMappingURL=webhook-verification-D1c7BY4q.d.ts.map
21
+ //# sourceMappingURL=webhook-verification-DL4L3zbu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-verification-D1c7BY4q.d.ts","names":[],"sources":["../src/infrastructure/http/webhook-verification.ts"],"mappings":";;;cAGa,iCAAiC;WAMjC;WALF;WACA;;EAGT,YACW,qEACT;;;UAOa,gBAAgB;;;;;EAK/B,OAAO,cAAc,SAAS,UAAU,QAAQ"}
1
+ {"version":3,"file":"webhook-verification-DL4L3zbu.d.ts","names":[],"sources":["../src/infrastructure/http/webhook-verification.ts"],"mappings":";;;cAGa,iCAAiC;WAMjC;WALF;WACA;;EAGT,YACW,qEACT;;;UAOa,gBAAgB;;;;;EAK/B,OAAO,cAAc,SAAS,UAAU,QAAQ"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "better-ship",
4
- "version": "0.37.0",
4
+ "version": "0.38.0",
5
5
  "description": "Foundation for applications on Cloudflare Workers: logger, errors, message bus, infrastructure adapters, and UI primitives",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -1,4 +1,12 @@
1
- import type { AnalyticsEventIdentity, AnalyticsEventInput, AnalyticsProperties } from '@/core'
1
+ import type { AnalyticsEventInput, AnalyticsProperties, AnalyticsTarget } from '@/core'
2
+
3
+ /** The properties of one event name; an untyped service accepts any properties. */
4
+ export type AnalyticsPropertiesOf<
5
+ TEvent extends AnalyticsEventInput,
6
+ TName extends TEvent['name'],
7
+ > = string extends TEvent['name']
8
+ ? TEvent['properties']
9
+ : Extract<TEvent, { readonly name: TName }>['properties']
2
10
 
3
11
  /** Product analytics without an SDK dependency, ported from Typist and Porte. */
4
12
  export interface AnalyticsService<TEvent extends AnalyticsEventInput = AnalyticsEventInput> {
@@ -7,15 +15,34 @@ export interface AnalyticsService<TEvent extends AnalyticsEventInput = Analytics
7
15
  /** Merge a previous ID into this person; the application must establish that both identify the same person. */
8
16
  alias(distinctId: string, previousDistinctId: string): void
9
17
  /** Schedule an event for a person; returning does not confirm remote acceptance. */
10
- track(event: TEvent, distinctId: string, identity?: AnalyticsEventIdentity): void
18
+ track<TName extends TEvent['name']>(
19
+ name: TName,
20
+ properties: AnalyticsPropertiesOf<TEvent, TName>,
21
+ target: AnalyticsTarget,
22
+ ): void
11
23
  /** Schedule an event without creating a person profile for its distinct ID. */
12
- trackAnonymous(event: TEvent, distinctId: string, identity?: AnalyticsEventIdentity): void
24
+ trackAnonymous<TName extends TEvent['name']>(
25
+ name: TName,
26
+ properties: AnalyticsPropertiesOf<TEvent, TName>,
27
+ target: AnalyticsTarget,
28
+ ): void
13
29
  }
14
30
 
15
- /** Reports to nobody. The composition root selects this service when analytics is disabled. */
16
- export const NULL_ANALYTICS: AnalyticsService = {
31
+ /**
32
+ * Reports to nobody. The composition root selects this service when analytics is disabled.
33
+ * `satisfies` keeps its own type, so it serves an `AnalyticsService` of any event map.
34
+ */
35
+ export const NULL_ANALYTICS = {
17
36
  identify: () => {},
18
37
  alias: () => {},
19
- track: () => {},
20
- trackAnonymous: () => {},
21
- }
38
+ track: (
39
+ _name: string,
40
+ _properties: AnalyticsEventInput['properties'],
41
+ _target: AnalyticsTarget,
42
+ ) => {},
43
+ trackAnonymous: (
44
+ _name: string,
45
+ _properties: AnalyticsEventInput['properties'],
46
+ _target: AnalyticsTarget,
47
+ ) => {},
48
+ } satisfies AnalyticsService
@@ -1,5 +1,9 @@
1
1
  export type { CommandHandler, EventHandler, QueryHandler } from './handlers.ts'
2
- export { NULL_ANALYTICS, type AnalyticsService } from './analytics-service.ts'
2
+ export {
3
+ NULL_ANALYTICS,
4
+ type AnalyticsPropertiesOf,
5
+ type AnalyticsService,
6
+ } from './analytics-service.ts'
3
7
  export {
4
8
  NULL_METRICS_SERVICE,
5
9
  type MetricsService,
@@ -1,5 +1,3 @@
1
- import { z } from 'zod'
2
-
3
1
  /** An event name and its application-owned properties, ported from Typist's analytics/types.ts. */
4
2
  export interface AnalyticsEventInput<
5
3
  TName extends string = string,
@@ -24,14 +22,20 @@ export interface AnalyticsProperties {
24
22
  readonly [name: string]: AnalyticsProperty
25
23
  }
26
24
 
27
- /** Parse a persisted analytics event UUID; the SDK generates ordinary event UUIDs itself. */
28
- export const AnalyticsEventIdSchema = z.uuid().brand<'AnalyticsEventId'>()
29
-
30
- /** The identity of one analytics event across repeated application deliveries. */
31
- export type AnalyticsEventId = z.infer<typeof AnalyticsEventIdSchema>
32
-
33
- /** Supply both original values when replaying one event; never share them across different occurrences. */
34
- export interface AnalyticsEventIdentity {
35
- readonly uuid: AnalyticsEventId
36
- readonly timestamp: Date
37
- }
25
+ /**
26
+ * Who an event belongs to, and, for code that can run twice, which occurrence it is.
27
+ * Pass `eventId` and `occurredAt` together: the same pair always sends the same event,
28
+ * so the analytics provider drops a repeat. Any stable string is an event ID.
29
+ */
30
+ export type AnalyticsTarget =
31
+ | {
32
+ readonly distinctId: string
33
+ readonly eventId?: never
34
+ readonly occurredAt?: never
35
+ }
36
+ | {
37
+ readonly distinctId: string
38
+ readonly eventId: string
39
+ /** The business time of the occurrence, as a `Date` or an ISO 8601 string. */
40
+ readonly occurredAt: Date | string
41
+ }
package/src/core/index.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  export { AppError } from './app-error.ts'
2
2
  export { MissingConfigurationError } from './missing-configuration.error.ts'
3
3
  export {
4
- AnalyticsEventIdSchema,
5
- type AnalyticsEventId,
6
- type AnalyticsEventIdentity,
7
4
  type AnalyticsEventInput,
8
5
  type AnalyticsProperties,
9
6
  type AnalyticsProperty,
7
+ type AnalyticsTarget,
10
8
  } from './analytics-event.ts'
11
9
  export { EmailAddressSchema, type EmailAddress } from './email-address.ts'
12
10
  export {