@modainteract/moda-interact-shared 1.4.0 → 2.0.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.
@@ -5,6 +5,7 @@ import {
5
5
  // src/shopify/constants.ts
6
6
  var SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V1 = 1;
7
7
  var SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V2 = 2;
8
+ var SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V3 = 3;
8
9
  var SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION = SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V1;
9
10
  var SHOPIFY_COMMERCE_EVENT_TYPES = {
10
11
  CHECKOUT_OBSERVED: "checkout.observed",
@@ -16,6 +17,9 @@ var SHOPIFY_RECOVERY_EVENT_TYPES_V2 = {
16
17
  CHECKOUT_UPDATED: "checkout.updated",
17
18
  ORDER_COMPLETED: "order.completed"
18
19
  };
20
+ var SHOPIFY_RECOVERY_EVENT_TYPES_V3 = {
21
+ CHECKOUT_UPDATED: "checkout.updated"
22
+ };
19
23
  var SHOPIFY_WEBHOOK_OUTBOX_DESTINATIONS = {
20
24
  CHECKOUT_EVENTS: "CHECKOUT_EVENTS",
21
25
  ORDER_EVENTS: "ORDER_EVENTS"
@@ -277,8 +281,52 @@ function createShopifyOrderCorrelationOrderingKey(input) {
277
281
  return `${input.shopId}:${input.orderId}`;
278
282
  }
279
283
 
280
- // src/shopify/queue-contracts.ts
284
+ // src/shopify/v3/checkout-updated.schema.ts
281
285
  import { z as z10 } from "zod";
286
+ var MAX_CART_TOKEN_LENGTH = 100;
287
+ var CheckoutUpdatedPayloadV3Schema = z10.object({
288
+ checkoutToken: z10.string().min(1),
289
+ cartToken: z10.string().min(1).max(MAX_CART_TOKEN_LENGTH).nullable().optional(),
290
+ checkoutCreatedAt: z10.iso.datetime().nullable().optional(),
291
+ abandonedCheckoutUrl: z10.url().max(MAX_URL_LENGTH).nullable().optional()
292
+ }).strict();
293
+
294
+ // src/shopify/v3/recovery-event.schema.ts
295
+ import { z as z11 } from "zod";
296
+ var ShopifyRecoveryEventBaseV3Schema = z11.object({
297
+ schemaVersion: z11.literal(SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V3),
298
+ receiptId: z11.string().min(1),
299
+ deliveryId: z11.string().min(1),
300
+ eventId: z11.string().min(1).nullable(),
301
+ source: z11.literal("shopify"),
302
+ providerTopic: z11.string().min(1),
303
+ tenant: ShopifyTenantSchema,
304
+ occurredAt: z11.iso.datetime().nullable(),
305
+ receivedAt: z11.iso.datetime(),
306
+ traceId: z11.string().min(1),
307
+ orderingKey: z11.string().min(1),
308
+ internationalContext: InternationalContextSchema.optional()
309
+ }).strict();
310
+ var ShopifyCheckoutUpdatedEventV3Schema = ShopifyRecoveryEventBaseV3Schema.extend({
311
+ eventType: z11.literal(SHOPIFY_RECOVERY_EVENT_TYPES_V3.CHECKOUT_UPDATED),
312
+ payload: CheckoutUpdatedPayloadV3Schema
313
+ }).strict();
314
+
315
+ // src/shopify/recovery-event.schema.ts
316
+ import { z as z12 } from "zod";
317
+ var ShopifyRecoveryEventSchema = z12.union([
318
+ ShopifyRecoveryEventV2Schema,
319
+ ShopifyCheckoutUpdatedEventV3Schema
320
+ ]);
321
+ function parseShopifyRecoveryEvent(input) {
322
+ return ShopifyRecoveryEventSchema.parse(input);
323
+ }
324
+ function safeParseShopifyRecoveryEvent(input) {
325
+ return ShopifyRecoveryEventSchema.safeParse(input);
326
+ }
327
+
328
+ // src/shopify/queue-contracts.ts
329
+ import { z as z13 } from "zod";
282
330
  var SHOPIFY_WEBHOOK_QUEUE_CONTRACTS = {
283
331
  CHECKOUT_EVENTS: {
284
332
  queueName: "checkout-events",
@@ -315,15 +363,15 @@ var SHOPIFY_DISCOUNT_SYNC_WEBHOOK_TOPICS = [
315
363
  "discounts/redeemcode_added",
316
364
  "discounts/redeemcode_removed"
317
365
  ];
318
- var ShopifyDiscountSyncReasonSchema = z10.enum(SHOPIFY_DISCOUNT_SYNC_REASONS);
319
- var ShopifyDiscountSyncWebhookTopicSchema = z10.enum(SHOPIFY_DISCOUNT_SYNC_WEBHOOK_TOPICS);
320
- var ShopifyDiscountSyncJobSchema = z10.object({
321
- schemaVersion: z10.literal(1),
322
- shopId: z10.string().trim().min(1).max(128),
323
- shopDomain: z10.string().trim().min(1).max(512),
366
+ var ShopifyDiscountSyncReasonSchema = z13.enum(SHOPIFY_DISCOUNT_SYNC_REASONS);
367
+ var ShopifyDiscountSyncWebhookTopicSchema = z13.enum(SHOPIFY_DISCOUNT_SYNC_WEBHOOK_TOPICS);
368
+ var ShopifyDiscountSyncJobSchema = z13.object({
369
+ schemaVersion: z13.literal(1),
370
+ shopId: z13.string().trim().min(1).max(128),
371
+ shopDomain: z13.string().trim().min(1).max(512),
324
372
  reason: ShopifyDiscountSyncReasonSchema,
325
- requestedAt: z10.iso.datetime({ offset: true }),
326
- deliveryId: z10.string().trim().min(1).nullable(),
373
+ requestedAt: z13.iso.datetime({ offset: true }),
374
+ deliveryId: z13.string().trim().min(1).nullable(),
327
375
  webhookTopic: ShopifyDiscountSyncWebhookTopicSchema.nullable()
328
376
  }).strict().superRefine((value, ctx) => {
329
377
  if (value.reason === "DISCOUNT_WEBHOOK") {
@@ -361,9 +409,11 @@ function safeParseShopifyDiscountSyncJob(input) {
361
409
  export {
362
410
  SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V1,
363
411
  SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V2,
412
+ SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V3,
364
413
  SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION,
365
414
  SHOPIFY_COMMERCE_EVENT_TYPES,
366
415
  SHOPIFY_RECOVERY_EVENT_TYPES_V2,
416
+ SHOPIFY_RECOVERY_EVENT_TYPES_V3,
367
417
  SHOPIFY_WEBHOOK_OUTBOX_DESTINATIONS,
368
418
  MAX_URL_LENGTH,
369
419
  MAX_LINE_ITEMS,
@@ -401,6 +451,11 @@ export {
401
451
  createShopifyPendingRecoveryOrderingKey,
402
452
  createShopifyCartActivityOrderingKey,
403
453
  createShopifyOrderCorrelationOrderingKey,
454
+ CheckoutUpdatedPayloadV3Schema,
455
+ ShopifyCheckoutUpdatedEventV3Schema,
456
+ ShopifyRecoveryEventSchema,
457
+ parseShopifyRecoveryEvent,
458
+ safeParseShopifyRecoveryEvent,
404
459
  SHOPIFY_WEBHOOK_QUEUE_CONTRACTS,
405
460
  SHOPIFY_DISCOUNT_SYNC_REASONS,
406
461
  SHOPIFY_DISCOUNT_SYNC_WEBHOOK_TOPICS,
@@ -410,4 +465,4 @@ export {
410
465
  parseShopifyDiscountSyncJob,
411
466
  safeParseShopifyDiscountSyncJob
412
467
  };
413
- //# sourceMappingURL=chunk-I4WVQ6D7.js.map
468
+ //# sourceMappingURL=chunk-SU2DAOEC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shopify/constants.ts","../src/shopify/common.schema.ts","../src/shopify/v1/checkout-observed.schema.ts","../src/shopify/v1/order-completed.schema.ts","../src/shopify/v1/commerce-event.schema.ts","../src/shopify/v2/checkout-created.schema.ts","../src/shopify/v2/checkout-updated.schema.ts","../src/shopify/v2/cart-activity.schema.ts","../src/shopify/v2/order-completed.schema.ts","../src/shopify/v2/recovery-event.schema.ts","../src/shopify/v3/checkout-updated.schema.ts","../src/shopify/v3/recovery-event.schema.ts","../src/shopify/recovery-event.schema.ts","../src/shopify/queue-contracts.ts"],"sourcesContent":["export const SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V1 = 1 as const;\nexport const SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V2 = 2 as const;\nexport const SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V3 = 3 as const;\n\n// Backward-compatible alias for existing v1 callers.\nexport const SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION =\n SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V1;\n\nexport const SHOPIFY_COMMERCE_EVENT_TYPES = {\n CHECKOUT_OBSERVED: \"checkout.observed\",\n ORDER_COMPLETED: \"order.completed\",\n} as const;\n\nexport const SHOPIFY_RECOVERY_EVENT_TYPES_V2 = {\n CART_ACTIVITY: \"cart.activity\",\n CHECKOUT_CREATED: \"checkout.created\",\n CHECKOUT_UPDATED: \"checkout.updated\",\n ORDER_COMPLETED: \"order.completed\",\n} as const;\n\nexport const SHOPIFY_RECOVERY_EVENT_TYPES_V3 = {\n CHECKOUT_UPDATED: \"checkout.updated\",\n} as const;\n\nexport const SHOPIFY_WEBHOOK_OUTBOX_DESTINATIONS = {\n CHECKOUT_EVENTS: \"CHECKOUT_EVENTS\",\n ORDER_EVENTS: \"ORDER_EVENTS\",\n} as const;\n","import { z } from \"zod\";\n\n/**\n * Reasonable upper bounds for externally controlled (Shopify-originated) strings.\n * These exist to keep queue messages small and predictable, not to validate business rules.\n */\nconst MAX_ID_LENGTH = 100;\nconst MAX_SHORT_TEXT_LENGTH = 320;\nconst MAX_TITLE_LENGTH = 500;\nconst MAX_URL_LENGTH = 2048;\nconst MAX_LINE_ITEMS = 250;\n\nconst decimalStringPattern = /^-?\\d+(\\.\\d+)?$/;\n\n/** Decimal amount encoded as a string; never a JS number to avoid float precision loss. */\nconst DecimalStringSchema = z\n .string()\n .min(1)\n .max(32)\n .regex(decimalStringPattern, \"must be a decimal string, e.g. \\\"12.34\\\"\");\n\nexport const ShopifyTenantSchema = z\n .object({\n shopId: z.string().min(1).max(MAX_ID_LENGTH),\n shopDomain: z\n .string()\n .min(1)\n .max(MAX_SHORT_TEXT_LENGTH)\n .toLowerCase()\n .trim(),\n })\n .strict();\n\nexport type ShopifyTenant = z.infer<typeof ShopifyTenantSchema>;\n\nexport const ShopifyCustomerReferenceSchema = z\n .object({\n shopifyCustomerId: z.string().min(1).max(MAX_ID_LENGTH).nullable(),\n email: z.string().email().max(MAX_SHORT_TEXT_LENGTH).nullable(),\n phone: z.string().min(1).max(64).nullable(),\n firstName: z.string().min(1).max(MAX_TITLE_LENGTH).nullable(),\n lastName: z.string().min(1).max(MAX_TITLE_LENGTH).nullable(),\n })\n .strict();\n\nexport type ShopifyCustomerReference = z.infer<typeof ShopifyCustomerReferenceSchema>;\n\nexport const ShopifyMoneySchema = z\n .object({\n amount: DecimalStringSchema,\n currencyCode: z\n .string()\n .regex(/^[A-Z]{3}$/, \"must be an uppercase three-letter currency code\"),\n })\n .strict();\n\nexport type ShopifyMoney = z.infer<typeof ShopifyMoneySchema>;\n\n/**\n * Fields limited to what checkout recovery and customer-facing basket context require.\n * Line-item arrays are bounded (see MAX_LINE_ITEMS) to prevent unexpectedly large queue messages.\n */\nexport const ShopifyCheckoutLineItemSchema = z\n .object({\n lineItemId: z.string().min(1).max(MAX_ID_LENGTH).nullable(),\n variantId: z.string().min(1).max(MAX_ID_LENGTH).nullable(),\n productId: z.string().min(1).max(MAX_ID_LENGTH).nullable(),\n title: z.string().min(1).max(MAX_TITLE_LENGTH),\n variantTitle: z.string().min(1).max(MAX_TITLE_LENGTH).nullable(),\n quantity: z.number().int().positive(),\n unitPrice: DecimalStringSchema.nullable(),\n sku: z.string().min(1).max(MAX_ID_LENGTH).nullable(),\n })\n .strict();\n\nexport type ShopifyCheckoutLineItem = z.infer<typeof ShopifyCheckoutLineItemSchema>;\n\nexport const ShopifyCheckoutLineItemsSchema = z\n .array(ShopifyCheckoutLineItemSchema)\n .max(MAX_LINE_ITEMS);\n\nexport { MAX_URL_LENGTH, MAX_LINE_ITEMS };\n","import { z } from \"zod\";\nimport {\n MAX_URL_LENGTH,\n ShopifyCheckoutLineItemsSchema,\n ShopifyCustomerReferenceSchema,\n ShopifyMoneySchema,\n} from \"../common.schema.js\";\n\nexport const CheckoutObservedPayloadSchema = z\n .object({\n checkoutToken: z.string().min(1),\n cartToken: z.string().min(1).nullable(),\n checkoutUrl: z.url().max(MAX_URL_LENGTH).nullable(),\n customer: ShopifyCustomerReferenceSchema.nullable(),\n total: ShopifyMoneySchema.nullable(),\n lineItems: ShopifyCheckoutLineItemsSchema,\n checkoutCreatedAt: z.iso.datetime().nullable(),\n checkoutUpdatedAt: z.iso.datetime().nullable(),\n completedAt: z.iso.datetime().nullable(),\n })\n .strict();\n\nexport type CheckoutObservedPayload = z.infer<typeof CheckoutObservedPayloadSchema>;\n","import { z } from \"zod\";\nimport { ShopifyMoneySchema } from \"../common.schema.js\";\n\nexport const OrderCompletedPayloadSchema = z\n .object({\n orderId: z.string().min(1),\n checkoutToken: z.string().min(1).nullable(),\n shopifyCustomerId: z.string().min(1).nullable(),\n total: ShopifyMoneySchema.nullable(),\n completedAt: z.iso.datetime(),\n })\n .strict();\n\nexport type OrderCompletedPayload = z.infer<typeof OrderCompletedPayloadSchema>;\n","import { z } from \"zod\";\nimport {\n SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION,\n SHOPIFY_COMMERCE_EVENT_TYPES,\n} from \"../constants.js\";\nimport { ShopifyTenantSchema } from \"../common.schema.js\";\nimport { CheckoutObservedPayloadSchema } from \"./checkout-observed.schema.js\";\nimport { OrderCompletedPayloadSchema } from \"./order-completed.schema.js\";\n\nconst ShopifyCommerceEventBaseSchema = z.object({\n schemaVersion: z.literal(SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION),\n receiptId: z.string().min(1),\n deliveryId: z.string().min(1),\n eventId: z.string().min(1).nullable(),\n source: z.literal(\"shopify\"),\n providerTopic: z.string().min(1),\n tenant: ShopifyTenantSchema,\n occurredAt: z.iso.datetime().nullable(),\n receivedAt: z.iso.datetime(),\n traceId: z.string().min(1),\n orderingKey: z.string().min(1),\n});\n\nexport const ShopifyCheckoutObservedEventSchema = ShopifyCommerceEventBaseSchema.extend({\n eventType: z.literal(SHOPIFY_COMMERCE_EVENT_TYPES.CHECKOUT_OBSERVED),\n payload: CheckoutObservedPayloadSchema,\n}).strict();\n\nexport type ShopifyCheckoutObservedEvent = z.infer<\n typeof ShopifyCheckoutObservedEventSchema\n>;\n\nexport const ShopifyOrderCompletedEventSchema = ShopifyCommerceEventBaseSchema.extend({\n eventType: z.literal(SHOPIFY_COMMERCE_EVENT_TYPES.ORDER_COMPLETED),\n payload: OrderCompletedPayloadSchema,\n}).strict();\n\nexport type ShopifyOrderCompletedEvent = z.infer<\n typeof ShopifyOrderCompletedEventSchema\n>;\n\nexport const ShopifyCommerceEventSchema = z.discriminatedUnion(\"eventType\", [\n ShopifyCheckoutObservedEventSchema,\n ShopifyOrderCompletedEventSchema,\n]);\n\nexport type ShopifyCommerceEvent = z.infer<typeof ShopifyCommerceEventSchema>;\nexport type ShopifyCommerceEventType = ShopifyCommerceEvent[\"eventType\"];\n\nexport function parseShopifyCommerceEvent(input: unknown): ShopifyCommerceEvent {\n return ShopifyCommerceEventSchema.parse(input);\n}\n\nexport function safeParseShopifyCommerceEvent(input: unknown) {\n return ShopifyCommerceEventSchema.safeParse(input);\n}\n\nexport function isCheckoutObservedEvent(\n event: ShopifyCommerceEvent,\n): event is ShopifyCheckoutObservedEvent {\n return event.eventType === SHOPIFY_COMMERCE_EVENT_TYPES.CHECKOUT_OBSERVED;\n}\n\nexport function isOrderCompletedEvent(\n event: ShopifyCommerceEvent,\n): event is ShopifyOrderCompletedEvent {\n return event.eventType === SHOPIFY_COMMERCE_EVENT_TYPES.ORDER_COMPLETED;\n}\n\n/**\n * Stable ordering key for a shop/checkout pair.\n * Checkout and order events for the same checkout must share this key, so\n * deliveryId is intentionally excluded.\n */\nexport function createShopifyCommerceOrderingKey(\n shopId: string,\n checkoutToken: string,\n): string {\n if (typeof shopId !== \"string\" || shopId.length === 0) {\n throw new Error(\n \"createShopifyCommerceOrderingKey: shopId must be a non-empty string\",\n );\n }\n\n if (typeof checkoutToken !== \"string\" || checkoutToken.length === 0) {\n throw new Error(\n \"createShopifyCommerceOrderingKey: checkoutToken must be a non-empty string\",\n );\n }\n\n return `${shopId}:${checkoutToken}`;\n}\n\nexport function createShopifyOrderOrderingKey(\n shopId: string,\n orderId: string,\n): string {\n if (typeof shopId !== \"string\" || shopId.length === 0) {\n throw new Error(\n \"createShopifyOrderOrderingKey: shopId must be a non-empty string\",\n );\n }\n\n if (typeof orderId !== \"string\" || orderId.length === 0) {\n throw new Error(\n \"createShopifyOrderOrderingKey: orderId must be a non-empty string\",\n );\n }\n\n return `${shopId}:${orderId}`;\n}","import { z } from \"zod\";\nimport { MAX_URL_LENGTH } from \"../common.schema.js\";\n\nexport const CheckoutCreatedPayloadV2Schema = z\n .object({\n checkoutToken: z.string().min(1),\n cartToken: z.string().min(1).nullable(),\n abandonedCheckoutUrl: z.url().max(MAX_URL_LENGTH).nullable(),\n checkoutCreatedAt: z.iso.datetime().nullable(),\n })\n .strict();\n\nexport type CheckoutCreatedPayloadV2 = z.infer<\n typeof CheckoutCreatedPayloadV2Schema\n>;\n","import { z } from \"zod\";\n\nexport const CheckoutUpdatedPayloadV2Schema = z\n .object({\n checkoutToken: z.string().min(1),\n })\n .strict();\n\nexport type CheckoutUpdatedPayloadV2 = z.infer<\n typeof CheckoutUpdatedPayloadV2Schema\n>;\n","import { z } from \"zod\";\n\nexport const ShopifyCartActivityPayloadV2Schema = z\n .object({\n cartToken: z.string().min(1),\n isEmpty: z.boolean().nullable(),\n })\n .strict();\n\nexport type ShopifyCartActivityPayloadV2 = z.infer<\n typeof ShopifyCartActivityPayloadV2Schema\n>;","import { z } from \"zod\";\n\nexport const OrderCompletedPayloadV2Schema = z\n .object({\n orderId: z.string().min(1),\n checkoutToken: z.string().min(1).nullable(),\n cartToken: z.string().min(1).nullable(),\n completedAt: z.iso.datetime(),\n })\n .strict();\n\nexport type OrderCompletedPayloadV2 = z.infer<typeof OrderCompletedPayloadV2Schema>;\n","import { z } from \"zod\";\nimport {\n SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V2,\n SHOPIFY_RECOVERY_EVENT_TYPES_V2,\n} from \"../constants.js\";\nimport { ShopifyTenantSchema } from \"../common.schema.js\";\nimport { InternationalContextSchema } from \"../../internationalization.js\";\nimport { CheckoutCreatedPayloadV2Schema } from \"./checkout-created.schema.js\";\nimport { CheckoutUpdatedPayloadV2Schema } from \"./checkout-updated.schema.js\";\nimport { OrderCompletedPayloadV2Schema } from \"./order-completed.schema.js\";\nimport { ShopifyCartActivityPayloadV2Schema } from \"./cart-activity.schema.js\";\n\nconst ShopifyRecoveryEventBaseV2Schema = z\n .object({\n schemaVersion: z.literal(SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V2),\n receiptId: z.string().min(1),\n deliveryId: z.string().min(1),\n eventId: z.string().min(1).nullable(),\n source: z.literal(\"shopify\"),\n providerTopic: z.string().min(1),\n tenant: ShopifyTenantSchema,\n occurredAt: z.iso.datetime().nullable(),\n receivedAt: z.iso.datetime(),\n traceId: z.string().min(1),\n orderingKey: z.string().min(1),\n internationalContext: InternationalContextSchema.optional(),\n })\n .strict();\n\nexport const ShopifyCheckoutCreatedEventV2Schema =\n ShopifyRecoveryEventBaseV2Schema.extend({\n eventType: z.literal(SHOPIFY_RECOVERY_EVENT_TYPES_V2.CHECKOUT_CREATED),\n payload: CheckoutCreatedPayloadV2Schema,\n }).strict();\n\nexport type ShopifyCheckoutCreatedEventV2 = z.infer<\n typeof ShopifyCheckoutCreatedEventV2Schema\n>;\n\nexport const ShopifyCartActivityEventV2Schema =\n ShopifyRecoveryEventBaseV2Schema.extend({\n eventType: z.literal(SHOPIFY_RECOVERY_EVENT_TYPES_V2.CART_ACTIVITY),\n payload: ShopifyCartActivityPayloadV2Schema,\n }).strict();\n\nexport type ShopifyCartActivityEventV2 = z.infer<\n typeof ShopifyCartActivityEventV2Schema\n>;\n\nexport const ShopifyCheckoutUpdatedEventV2Schema =\n ShopifyRecoveryEventBaseV2Schema.extend({\n eventType: z.literal(SHOPIFY_RECOVERY_EVENT_TYPES_V2.CHECKOUT_UPDATED),\n payload: CheckoutUpdatedPayloadV2Schema,\n }).strict();\n\nexport type ShopifyCheckoutUpdatedEventV2 = z.infer<\n typeof ShopifyCheckoutUpdatedEventV2Schema\n>;\n\nexport const ShopifyOrderCompletedEventV2Schema =\n ShopifyRecoveryEventBaseV2Schema.extend({\n eventType: z.literal(SHOPIFY_RECOVERY_EVENT_TYPES_V2.ORDER_COMPLETED),\n payload: OrderCompletedPayloadV2Schema,\n }).strict();\n\nexport type ShopifyOrderCompletedEventV2 = z.infer<\n typeof ShopifyOrderCompletedEventV2Schema\n>;\n\nexport const ShopifyRecoveryEventV2Schema = z.discriminatedUnion(\"eventType\", [\n ShopifyCartActivityEventV2Schema,\n ShopifyCheckoutCreatedEventV2Schema,\n ShopifyCheckoutUpdatedEventV2Schema,\n ShopifyOrderCompletedEventV2Schema,\n]);\n\nexport type ShopifyRecoveryEventV2 = z.infer<typeof ShopifyRecoveryEventV2Schema>;\nexport type ShopifyRecoveryEventTypeV2 = ShopifyRecoveryEventV2[\"eventType\"];\n\nexport function parseShopifyRecoveryEventV2(input: unknown): ShopifyRecoveryEventV2 {\n return ShopifyRecoveryEventV2Schema.parse(input);\n}\n\nexport function safeParseShopifyRecoveryEventV2(input: unknown) {\n return ShopifyRecoveryEventV2Schema.safeParse(input);\n}\n\nexport function isCheckoutCreatedEventV2(\n event: ShopifyRecoveryEventV2,\n): event is ShopifyCheckoutCreatedEventV2 {\n return event.eventType === SHOPIFY_RECOVERY_EVENT_TYPES_V2.CHECKOUT_CREATED;\n}\n\nexport function isCartActivityEventV2(\n event: ShopifyRecoveryEventV2,\n): event is ShopifyCartActivityEventV2 {\n return event.eventType === SHOPIFY_RECOVERY_EVENT_TYPES_V2.CART_ACTIVITY;\n}\n\nexport function isCheckoutUpdatedEventV2(\n event: ShopifyRecoveryEventV2,\n): event is ShopifyCheckoutUpdatedEventV2 {\n return event.eventType === SHOPIFY_RECOVERY_EVENT_TYPES_V2.CHECKOUT_UPDATED;\n}\n\nexport function isOrderCompletedEventV2(\n event: ShopifyRecoveryEventV2,\n): event is ShopifyOrderCompletedEventV2 {\n return event.eventType === SHOPIFY_RECOVERY_EVENT_TYPES_V2.ORDER_COMPLETED;\n}\n\nexport function createShopifyPendingRecoveryOrderingKey(\n shopId: string,\n checkoutToken: string,\n): string {\n if (typeof shopId !== \"string\" || shopId.length === 0) {\n throw new Error(\n \"createShopifyPendingRecoveryOrderingKey: shopId must be a non-empty string\",\n );\n }\n\n if (typeof checkoutToken !== \"string\" || checkoutToken.length === 0) {\n throw new Error(\n \"createShopifyPendingRecoveryOrderingKey: checkoutToken must be a non-empty string\",\n );\n }\n\n return `${shopId}:${checkoutToken}`;\n}\n\nexport function createShopifyCartActivityOrderingKey(\n shopId: string,\n cartToken: string,\n): string {\n if (typeof shopId !== \"string\" || shopId.length === 0) {\n throw new Error(\n \"createShopifyCartActivityOrderingKey: shopId must be a non-empty string\",\n );\n }\n\n if (typeof cartToken !== \"string\" || cartToken.length === 0) {\n throw new Error(\n \"createShopifyCartActivityOrderingKey: cartToken must be a non-empty string\",\n );\n }\n\n return `cart:${shopId.length}:${shopId}:${cartToken.length}:${cartToken}`;\n}\n\nexport function createShopifyOrderCorrelationOrderingKey(input: {\n shopId: string;\n orderId: string;\n checkoutToken: string | null;\n}): string {\n if (input.checkoutToken) {\n return createShopifyPendingRecoveryOrderingKey(input.shopId, input.checkoutToken);\n }\n\n if (typeof input.orderId !== \"string\" || input.orderId.length === 0) {\n throw new Error(\n \"createShopifyOrderCorrelationOrderingKey: orderId must be a non-empty string\",\n );\n }\n\n if (typeof input.shopId !== \"string\" || input.shopId.length === 0) {\n throw new Error(\n \"createShopifyOrderCorrelationOrderingKey: shopId must be a non-empty string\",\n );\n }\n\n return `${input.shopId}:${input.orderId}`;\n}\n","import { z } from \"zod\";\nimport { MAX_URL_LENGTH } from \"../common.schema.js\";\n\nconst MAX_CART_TOKEN_LENGTH = 100;\n\nexport const CheckoutUpdatedPayloadV3Schema = z\n .object({\n checkoutToken: z.string().min(1),\n cartToken: z.string().min(1).max(MAX_CART_TOKEN_LENGTH).nullable().optional(),\n checkoutCreatedAt: z.iso.datetime().nullable().optional(),\n abandonedCheckoutUrl: z.url().max(MAX_URL_LENGTH).nullable().optional(),\n })\n .strict();\n\nexport type CheckoutUpdatedPayloadV3 = z.infer<\n typeof CheckoutUpdatedPayloadV3Schema\n>;","import { z } from \"zod\";\nimport {\n SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V3,\n SHOPIFY_RECOVERY_EVENT_TYPES_V3,\n} from \"../constants.js\";\nimport { ShopifyTenantSchema } from \"../common.schema.js\";\nimport { InternationalContextSchema } from \"../../internationalization.js\";\nimport { CheckoutUpdatedPayloadV3Schema } from \"./checkout-updated.schema.js\";\n\nconst ShopifyRecoveryEventBaseV3Schema = z\n .object({\n schemaVersion: z.literal(SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V3),\n receiptId: z.string().min(1),\n deliveryId: z.string().min(1),\n eventId: z.string().min(1).nullable(),\n source: z.literal(\"shopify\"),\n providerTopic: z.string().min(1),\n tenant: ShopifyTenantSchema,\n occurredAt: z.iso.datetime().nullable(),\n receivedAt: z.iso.datetime(),\n traceId: z.string().min(1),\n orderingKey: z.string().min(1),\n internationalContext: InternationalContextSchema.optional(),\n })\n .strict();\n\nexport const ShopifyCheckoutUpdatedEventV3Schema =\n ShopifyRecoveryEventBaseV3Schema.extend({\n eventType: z.literal(SHOPIFY_RECOVERY_EVENT_TYPES_V3.CHECKOUT_UPDATED),\n payload: CheckoutUpdatedPayloadV3Schema,\n }).strict();\n\nexport type ShopifyCheckoutUpdatedEventV3 = z.infer<\n typeof ShopifyCheckoutUpdatedEventV3Schema\n>;\n","import { z } from \"zod\";\nimport {\n ShopifyRecoveryEventV2Schema,\n type ShopifyRecoveryEventV2,\n} from \"./v2/recovery-event.schema.js\";\nimport {\n ShopifyCheckoutUpdatedEventV3Schema,\n type ShopifyCheckoutUpdatedEventV3,\n} from \"./v3/recovery-event.schema.js\";\n\nexport const ShopifyRecoveryEventSchema = z.union([\n ShopifyRecoveryEventV2Schema,\n ShopifyCheckoutUpdatedEventV3Schema,\n]);\n\nexport type ShopifyRecoveryEvent =\n | ShopifyRecoveryEventV2\n | ShopifyCheckoutUpdatedEventV3;\n\nexport function parseShopifyRecoveryEvent(input: unknown): ShopifyRecoveryEvent {\n return ShopifyRecoveryEventSchema.parse(input);\n}\n\nexport function safeParseShopifyRecoveryEvent(input: unknown) {\n return ShopifyRecoveryEventSchema.safeParse(input);\n}","import { z } from \"zod\";\n\nexport const SHOPIFY_WEBHOOK_QUEUE_CONTRACTS = {\n CHECKOUT_EVENTS: {\n queueName: \"checkout-events\",\n jobName: \"checkout-created\",\n },\n CHECKOUT_UPDATED_EVENTS: {\n queueName: \"checkout-events\",\n jobName: \"checkout-updated\",\n },\n CART_ACTIVITY_EVENTS: {\n queueName: \"checkout-events\",\n jobName: \"cart-activity\",\n },\n ORDER_EVENTS: {\n queueName: \"order-events\",\n jobName: \"order-completed\",\n },\n SHOPIFY_DISCOUNT_SYNC: {\n queueName: \"shopify-discount-sync\",\n jobName: \"reconcile-shopify-discounts\",\n },\n} as const;\n\nexport const SHOPIFY_DISCOUNT_SYNC_REASONS = [\n \"SUBSCRIPTION_ACTIVATED\",\n \"REINSTALL_RECONCILED\",\n \"SCOPES_UPDATED\",\n \"ADMIN_REQUESTED\",\n \"DISCOUNT_WEBHOOK\",\n] as const;\n\nexport const SHOPIFY_DISCOUNT_SYNC_WEBHOOK_TOPICS = [\n \"discounts/create\",\n \"discounts/update\",\n \"discounts/delete\",\n \"discounts/redeemcode_added\",\n \"discounts/redeemcode_removed\",\n] as const;\n\nexport const ShopifyDiscountSyncReasonSchema = z.enum(SHOPIFY_DISCOUNT_SYNC_REASONS);\nexport const ShopifyDiscountSyncWebhookTopicSchema = z.enum(SHOPIFY_DISCOUNT_SYNC_WEBHOOK_TOPICS);\n\nexport const ShopifyDiscountSyncJobSchema = z\n .object({\n schemaVersion: z.literal(1),\n shopId: z.string().trim().min(1).max(128),\n shopDomain: z.string().trim().min(1).max(512),\n reason: ShopifyDiscountSyncReasonSchema,\n requestedAt: z.iso.datetime({ offset: true }),\n deliveryId: z.string().trim().min(1).nullable(),\n webhookTopic: ShopifyDiscountSyncWebhookTopicSchema.nullable(),\n })\n .strict()\n .superRefine((value, ctx) => {\n if (value.reason === \"DISCOUNT_WEBHOOK\") {\n if (!value.deliveryId || value.deliveryId.trim().length === 0) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"deliveryId\"],\n message: \"deliveryId is required when reason is DISCOUNT_WEBHOOK\",\n });\n }\n\n if (value.webhookTopic === null) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"webhookTopic\"],\n message: \"webhookTopic is required when reason is DISCOUNT_WEBHOOK\",\n });\n }\n } else {\n if (value.webhookTopic !== null) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"webhookTopic\"],\n message: \"webhookTopic must be null when reason is not DISCOUNT_WEBHOOK\",\n });\n }\n }\n });\n\nexport type ShopifyDiscountSyncJob = z.infer<typeof ShopifyDiscountSyncJobSchema>;\n\nexport function parseShopifyDiscountSyncJob(input: unknown): ShopifyDiscountSyncJob {\n return ShopifyDiscountSyncJobSchema.parse(input);\n}\n\nexport function safeParseShopifyDiscountSyncJob(input: unknown) {\n return ShopifyDiscountSyncJobSchema.safeParse(input);\n}\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z"]}
@@ -0,0 +1,124 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Durable outcome of one occurrence; duplicate deliveries have no side effects. */
4
+ type IncidentReportResult = {
5
+ incidentKey: string;
6
+ recorded: boolean;
7
+ occurrenceCount: number;
8
+ status: "OPEN" | "RESOLVED";
9
+ };
10
+ type IncidentSnapshot = {
11
+ incidentKey: string;
12
+ status: "OPEN" | "RESOLVED";
13
+ occurrenceCount: number;
14
+ lastOccurredAt: Date;
15
+ };
16
+ /**
17
+ * Transactional persistence contract, implemented by the consuming service.
18
+ * transactionForKey MUST serialize operations for the same key across processes,
19
+ * INCLUDING first creation; the callback must be retried on a conflicting insert.
20
+ * insertOccurrenceIfNew MUST enforce a GLOBAL unique occurrenceId (not only per
21
+ * incident); an occurrenceId already belonging to another key MUST throw.
22
+ * On creation loadOrCreate MUST initialise occurrenceCount=1; recordOccurrence
23
+ * MUST NOT increment that first occurrence again. If a duplicate is encountered
24
+ * when newlyCreated=true, the transaction MUST fail and roll back creation.
25
+ * Every operation in this port, including optional support notification, MUST
26
+ * participate in the SAME database transaction and roll back together.
27
+ */
28
+ interface IncidentTransaction {
29
+ loadOrCreate(input: {
30
+ incidentKey: string;
31
+ occurrence: OperationalIncidentOccurrenceV1;
32
+ }): Promise<IncidentSnapshot & {
33
+ newlyCreated: boolean;
34
+ }>;
35
+ insertOccurrenceIfNew(input: {
36
+ incidentKey: string;
37
+ occurrence: OperationalIncidentOccurrenceV1;
38
+ }): Promise<boolean>;
39
+ recordOccurrence(input: {
40
+ incidentKey: string;
41
+ occurrence: OperationalIncidentOccurrenceV1;
42
+ reopen: boolean;
43
+ newlyCreated: boolean;
44
+ }): Promise<IncidentSnapshot>;
45
+ /** Optional, safe internal support projection; at most one per incident key. */
46
+ notifySupportOnce?(input: {
47
+ incidentKey: string;
48
+ occurrence: OperationalIncidentOccurrenceV1;
49
+ }): Promise<void>;
50
+ }
51
+ interface IncidentPersistence {
52
+ transactionForKey<T>(key: string, fn: (tx: IncidentTransaction) => Promise<T>): Promise<T>;
53
+ }
54
+ interface IncidentReporterLogger {
55
+ error(event: string, fields: Record<string, unknown>): void;
56
+ }
57
+ /** Reporter never turns an operational-notification failure into the producer's failure. */
58
+ declare function createOperationalIncidentReporter(options: {
59
+ persistence: IncidentPersistence;
60
+ logger: IncidentReporterLogger;
61
+ notifySupport?: boolean;
62
+ }): {
63
+ report(raw: unknown): Promise<IncidentReportResult | null>;
64
+ };
65
+
66
+ /** A deliberately small interface; the application's Prisma transaction client supplies it. */
67
+ interface OperationalIncidentSqlClient {
68
+ $queryRawUnsafe<T = unknown>(query: string, ...parameters: unknown[]): Promise<T>;
69
+ }
70
+ interface OperationalIncidentPrismaClient {
71
+ $transaction<T>(fn: (tx: OperationalIncidentSqlClient) => Promise<T>, options?: {
72
+ maxWait?: number;
73
+ timeout?: number;
74
+ isolationLevel?: "Serializable";
75
+ }): Promise<T>;
76
+ }
77
+ /** Optional merchant-support projection. This callback MUST use the supplied transaction. */
78
+ type OperationalIncidentSupportWriter = (input: {
79
+ tx: OperationalIncidentSqlClient;
80
+ incidentId: string;
81
+ incidentKey: string;
82
+ occurrence: OperationalIncidentOccurrenceV1;
83
+ }) => Promise<void>;
84
+ /**
85
+ * PostgreSQL/Prisma adapter. No global Prisma client or credentials are created.
86
+ * All writes and an optional support projection occur within a single transaction.
87
+ * Uses a per-key transaction advisory lock and a row lock for cross-process safety.
88
+ */
89
+ declare function createPrismaOperationalIncidentPersistence(options: {
90
+ prisma: OperationalIncidentPrismaClient;
91
+ supportWriter?: OperationalIncidentSupportWriter;
92
+ maxAttempts?: number;
93
+ }): IncidentPersistence;
94
+
95
+ declare const OPERATIONAL_INCIDENT_CONTRACT_VERSION: "operational-incident.v1";
96
+ declare const OperationalIncidentOccurrenceV1Schema: z.ZodObject<{
97
+ contractVersion: z.ZodLiteral<"operational-incident.v1">;
98
+ sourceService: z.ZodString;
99
+ environment: z.ZodEnum<{
100
+ local: "local";
101
+ development: "development";
102
+ test: "test";
103
+ staging: "staging";
104
+ production: "production";
105
+ }>;
106
+ category: z.ZodString;
107
+ reasonCode: z.ZodString;
108
+ resourceKey: z.ZodString;
109
+ shopId: z.ZodNullable<z.ZodString>;
110
+ severity: z.ZodEnum<{
111
+ LOW: "LOW";
112
+ MEDIUM: "MEDIUM";
113
+ HIGH: "HIGH";
114
+ CRITICAL: "CRITICAL";
115
+ }>;
116
+ occurrenceId: z.ZodString;
117
+ correlationId: z.ZodString;
118
+ occurredAt: z.ZodISODateTime;
119
+ }, z.core.$strict>;
120
+ type OperationalIncidentOccurrenceV1 = z.infer<typeof OperationalIncidentOccurrenceV1Schema>;
121
+ declare function parseOperationalIncidentOccurrenceV1(input: unknown): OperationalIncidentOccurrenceV1;
122
+ declare function createOperationalIncidentKey(input: Pick<OperationalIncidentOccurrenceV1, "sourceService" | "environment" | "category" | "reasonCode" | "resourceKey" | "shopId">): string;
123
+
124
+ export { type IncidentPersistence, type IncidentReportResult, type IncidentReporterLogger, type IncidentSnapshot, type IncidentTransaction, OPERATIONAL_INCIDENT_CONTRACT_VERSION, type OperationalIncidentOccurrenceV1, OperationalIncidentOccurrenceV1Schema, type OperationalIncidentPrismaClient, type OperationalIncidentSqlClient, type OperationalIncidentSupportWriter, createOperationalIncidentKey, createOperationalIncidentReporter, createPrismaOperationalIncidentPersistence, parseOperationalIncidentOccurrenceV1 };
@@ -0,0 +1,264 @@
1
+ // src/incidents/index.ts
2
+ import { createHash } from "crypto";
3
+ import { z } from "zod";
4
+
5
+ // src/incidents/reporter.ts
6
+ function createOperationalIncidentReporter(options) {
7
+ return {
8
+ async report(raw) {
9
+ let occurrence;
10
+ try {
11
+ occurrence = parseOperationalIncidentOccurrenceV1(raw);
12
+ } catch {
13
+ options.logger.error("operational_incident.invalid_occurrence", {});
14
+ return null;
15
+ }
16
+ const incidentKey = createOperationalIncidentKey({
17
+ sourceService: occurrence.sourceService,
18
+ environment: occurrence.environment,
19
+ category: occurrence.category,
20
+ reasonCode: occurrence.reasonCode,
21
+ resourceKey: occurrence.resourceKey,
22
+ shopId: occurrence.shopId
23
+ });
24
+ try {
25
+ return await options.persistence.transactionForKey(incidentKey, async (tx) => {
26
+ const current = await tx.loadOrCreate({ incidentKey, occurrence });
27
+ const recorded = await tx.insertOccurrenceIfNew({ incidentKey, occurrence });
28
+ if (!recorded) {
29
+ return {
30
+ incidentKey,
31
+ recorded: false,
32
+ occurrenceCount: current.occurrenceCount,
33
+ status: current.status
34
+ };
35
+ }
36
+ const updated = await tx.recordOccurrence({
37
+ incidentKey,
38
+ occurrence,
39
+ reopen: current.status === "RESOLVED",
40
+ newlyCreated: current.newlyCreated
41
+ });
42
+ if (options.notifySupport && occurrence.shopId !== null && tx.notifySupportOnce) {
43
+ await tx.notifySupportOnce({ incidentKey, occurrence });
44
+ }
45
+ return {
46
+ incidentKey,
47
+ recorded: true,
48
+ occurrenceCount: updated.occurrenceCount,
49
+ status: "OPEN"
50
+ };
51
+ });
52
+ } catch {
53
+ options.logger.error("operational_incident.report_failed", {
54
+ incidentKey,
55
+ sourceService: occurrence.sourceService,
56
+ environment: occurrence.environment,
57
+ occurrenceId: occurrence.occurrenceId
58
+ });
59
+ return null;
60
+ }
61
+ }
62
+ };
63
+ }
64
+
65
+ // src/incidents/postgres.ts
66
+ import { randomUUID } from "crypto";
67
+ async function rows(tx, statement, ...params) {
68
+ return tx.$queryRawUnsafe(statement, ...params);
69
+ }
70
+ function snapshot(row) {
71
+ return {
72
+ incidentKey: row.incidentKey,
73
+ status: row.status,
74
+ occurrenceCount: row.occurrenceCount,
75
+ lastOccurredAt: row.lastOccurredAt
76
+ };
77
+ }
78
+ var PgIncidentTransaction = class {
79
+ constructor(tx, supportWriter) {
80
+ this.tx = tx;
81
+ this.supportWriter = supportWriter;
82
+ }
83
+ tx;
84
+ supportWriter;
85
+ incidentId = null;
86
+ newIncident = false;
87
+ async loadOrCreate({ incidentKey, occurrence }) {
88
+ const inserted = await rows(
89
+ this.tx,
90
+ `
91
+ INSERT INTO support."OperationalIncident" (
92
+ "id", "incidentKey", "sourceService", "environment", "category", "reasonCode",
93
+ "resourceKey", "shopId", "severity", "status", "occurrenceCount",
94
+ "firstOccurredAt", "lastOccurredAt", "lastCorrelationId")
95
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,'OPEN',1,$10::timestamptz,$10::timestamptz,$11)
96
+ ON CONFLICT ("incidentKey") DO NOTHING RETURNING *`,
97
+ randomUUID(),
98
+ incidentKey,
99
+ occurrence.sourceService,
100
+ occurrence.environment,
101
+ occurrence.category,
102
+ occurrence.reasonCode,
103
+ occurrence.resourceKey,
104
+ occurrence.shopId,
105
+ occurrence.severity,
106
+ occurrence.occurredAt,
107
+ occurrence.correlationId
108
+ );
109
+ this.newIncident = inserted.length > 0;
110
+ const found = await rows(
111
+ this.tx,
112
+ `SELECT * FROM support."OperationalIncident" WHERE "incidentKey" = $1 FOR UPDATE`,
113
+ incidentKey
114
+ );
115
+ if (found.length !== 1) throw new Error("incident row not found after upsert");
116
+ this.incidentId = found[0].id;
117
+ return { ...snapshot(found[0]), newlyCreated: this.newIncident };
118
+ }
119
+ async insertOccurrenceIfNew({ incidentKey, occurrence }) {
120
+ if (!this.incidentId) throw new Error("incident not loaded");
121
+ const created = await rows(
122
+ this.tx,
123
+ `
124
+ INSERT INTO support."OperationalIncidentEvent"
125
+ ("id", "incidentId", "eventType", "correlationId", "occurredAt")
126
+ VALUES ($1::uuid, $2, 'OCCURRED', $3, $4::timestamptz)
127
+ ON CONFLICT ("id") DO NOTHING RETURNING "id"`,
128
+ occurrence.occurrenceId,
129
+ this.incidentId,
130
+ occurrence.correlationId,
131
+ occurrence.occurredAt
132
+ );
133
+ if (!created.length) {
134
+ if (this.newIncident) throw new Error("duplicate occurrence cannot create new incident");
135
+ const existing = await rows(this.tx, `
136
+ SELECT i."incidentKey" FROM support."OperationalIncidentEvent" e
137
+ JOIN support."OperationalIncident" i ON i."id" = e."incidentId"
138
+ WHERE e."id" = $1::uuid`, occurrence.occurrenceId);
139
+ if (existing.length !== 1 || existing[0].incidentKey !== incidentKey) {
140
+ throw new Error("occurrence already belongs to another incident");
141
+ }
142
+ return false;
143
+ }
144
+ return true;
145
+ }
146
+ async recordOccurrence({ incidentKey, occurrence, reopen, newlyCreated }) {
147
+ if (!this.incidentId) throw new Error("incident not loaded");
148
+ if (reopen) {
149
+ await rows(
150
+ this.tx,
151
+ `INSERT INTO support."OperationalIncidentEvent"
152
+ ("id", "incidentId", "eventType", "correlationId", "occurredAt")
153
+ VALUES ($1::uuid,$2,'REOPENED',$3,$4::timestamptz)`,
154
+ randomUUID(),
155
+ this.incidentId,
156
+ occurrence.correlationId,
157
+ occurrence.occurredAt
158
+ );
159
+ }
160
+ const updated = await rows(
161
+ this.tx,
162
+ `
163
+ UPDATE support."OperationalIncident" SET
164
+ "occurrenceCount" = "occurrenceCount" + $2::integer,
165
+ "lastOccurredAt" = GREATEST("lastOccurredAt", $3::timestamptz),
166
+ "lastCorrelationId" = $4,
167
+ "severity" = $5,
168
+ "status" = 'OPEN', "resolvedAt" = NULL,
169
+ "resolvedByPlatformAdminId" = NULL, "resolutionNote" = NULL,
170
+ "revision" = "revision" + 1
171
+ WHERE "incidentKey" = $1 RETURNING *`,
172
+ incidentKey,
173
+ newlyCreated ? 0 : 1,
174
+ occurrence.occurredAt,
175
+ occurrence.correlationId,
176
+ occurrence.severity
177
+ );
178
+ if (updated.length !== 1) throw new Error("incident update failed");
179
+ return snapshot(updated[0]);
180
+ }
181
+ async notifySupportOnce(input) {
182
+ if (this.supportWriter && this.incidentId) await this.supportWriter({
183
+ tx: this.tx,
184
+ incidentId: this.incidentId,
185
+ incidentKey: input.incidentKey,
186
+ occurrence: input.occurrence
187
+ });
188
+ }
189
+ };
190
+ function retryable(error) {
191
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
192
+ return code === "P2034" || code === "40001" || code === "40P01";
193
+ }
194
+ function createPrismaOperationalIncidentPersistence(options) {
195
+ const maxAttempts = options.maxAttempts ?? 3;
196
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 10) {
197
+ throw new Error("maxAttempts must be between 1 and 10");
198
+ }
199
+ return {
200
+ async transactionForKey(key, fn) {
201
+ for (let attempt = 1; ; attempt++) {
202
+ try {
203
+ return await options.prisma.$transaction(async (tx) => {
204
+ await rows(tx, `SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))::text AS acquired`, key);
205
+ return fn(new PgIncidentTransaction(tx, options.supportWriter));
206
+ }, { isolationLevel: "Serializable", timeout: 15e3 });
207
+ } catch (error) {
208
+ if (attempt >= maxAttempts || !retryable(error)) throw error;
209
+ }
210
+ }
211
+ }
212
+ };
213
+ }
214
+
215
+ // src/incidents/index.ts
216
+ var OPERATIONAL_INCIDENT_CONTRACT_VERSION = "operational-incident.v1";
217
+ var identifier = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
218
+ var smallIdentifier = z.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
219
+ var opaqueId = z.string().uuid();
220
+ var OperationalIncidentOccurrenceV1Schema = z.strictObject({
221
+ contractVersion: z.literal(OPERATIONAL_INCIDENT_CONTRACT_VERSION),
222
+ sourceService: smallIdentifier,
223
+ environment: z.enum(["local", "development", "test", "staging", "production"]),
224
+ category: smallIdentifier,
225
+ reasonCode: identifier,
226
+ resourceKey: identifier,
227
+ shopId: z.string().min(1).max(128).nullable(),
228
+ severity: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]),
229
+ occurrenceId: opaqueId,
230
+ correlationId: identifier,
231
+ occurredAt: z.iso.datetime({ offset: true })
232
+ // Deliberately no arbitrary metadata bag, message, headers or exception object.
233
+ });
234
+ function parseOperationalIncidentOccurrenceV1(input) {
235
+ return OperationalIncidentOccurrenceV1Schema.parse(input);
236
+ }
237
+ function createOperationalIncidentKey(input) {
238
+ const parsed = OperationalIncidentOccurrenceV1Schema.pick({
239
+ sourceService: true,
240
+ environment: true,
241
+ category: true,
242
+ reasonCode: true,
243
+ resourceKey: true,
244
+ shopId: true
245
+ }).parse(input);
246
+ return createHash("sha256").update(JSON.stringify([
247
+ "operational-incident-key.v1",
248
+ parsed.sourceService,
249
+ parsed.environment,
250
+ parsed.category,
251
+ parsed.reasonCode,
252
+ parsed.shopId,
253
+ parsed.resourceKey
254
+ ])).digest("hex");
255
+ }
256
+ export {
257
+ OPERATIONAL_INCIDENT_CONTRACT_VERSION,
258
+ OperationalIncidentOccurrenceV1Schema,
259
+ createOperationalIncidentKey,
260
+ createOperationalIncidentReporter,
261
+ createPrismaOperationalIncidentPersistence,
262
+ parseOperationalIncidentOccurrenceV1
263
+ };
264
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/incidents/index.ts","../../src/incidents/reporter.ts","../../src/incidents/postgres.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n// ARCH-033 v1: safe, internal-only occurrence envelope. Producer classifies actionability.\nexport const OPERATIONAL_INCIDENT_CONTRACT_VERSION = \"operational-incident.v1\" as const;\nconst identifier = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);\nconst smallIdentifier = z.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);\nconst opaqueId = z.string().uuid();\nexport const OperationalIncidentOccurrenceV1Schema = z.strictObject({\n contractVersion: z.literal(OPERATIONAL_INCIDENT_CONTRACT_VERSION),\n sourceService: smallIdentifier,\n environment: z.enum([\"local\", \"development\", \"test\", \"staging\", \"production\"]),\n category: smallIdentifier,\n reasonCode: identifier,\n resourceKey: identifier,\n shopId: z.string().min(1).max(128).nullable(),\n severity: z.enum([\"LOW\", \"MEDIUM\", \"HIGH\", \"CRITICAL\"]),\n occurrenceId: opaqueId,\n correlationId: identifier,\n occurredAt: z.iso.datetime({ offset: true }),\n // Deliberately no arbitrary metadata bag, message, headers or exception object.\n});\nexport type OperationalIncidentOccurrenceV1 = z.infer<typeof OperationalIncidentOccurrenceV1Schema>;\nexport function parseOperationalIncidentOccurrenceV1(input: unknown): OperationalIncidentOccurrenceV1 {\n return OperationalIncidentOccurrenceV1Schema.parse(input);\n}\nexport function createOperationalIncidentKey(input: Pick<OperationalIncidentOccurrenceV1,\n \"sourceService\" | \"environment\" | \"category\" | \"reasonCode\" | \"resourceKey\" | \"shopId\">): string {\n const parsed = OperationalIncidentOccurrenceV1Schema.pick({\n sourceService: true, environment: true, category: true, reasonCode: true, resourceKey: true, shopId: true,\n }).parse(input);\n return createHash(\"sha256\").update(JSON.stringify([\n \"operational-incident-key.v1\", parsed.sourceService, parsed.environment, parsed.category,\n parsed.reasonCode, parsed.shopId, parsed.resourceKey,\n ])).digest(\"hex\");\n}\n\nexport { createOperationalIncidentReporter } from \"./reporter.js\";\nexport type {\n IncidentPersistence, IncidentTransaction, IncidentSnapshot, IncidentReportResult,\n IncidentReporterLogger,\n} from \"./reporter.js\";\n\nexport { createPrismaOperationalIncidentPersistence } from \"./postgres.js\";\nexport type { OperationalIncidentPrismaClient, OperationalIncidentSqlClient, OperationalIncidentSupportWriter } from \"./postgres.js\";\n","import { createOperationalIncidentKey, parseOperationalIncidentOccurrenceV1 } from \"./index.js\";\nimport type { OperationalIncidentOccurrenceV1 } from \"./index.js\";\n\n/** Durable outcome of one occurrence; duplicate deliveries have no side effects. */\nexport type IncidentReportResult = {\n incidentKey: string;\n recorded: boolean;\n occurrenceCount: number;\n status: \"OPEN\" | \"RESOLVED\";\n};\n\nexport type IncidentSnapshot = {\n incidentKey: string;\n status: \"OPEN\" | \"RESOLVED\";\n occurrenceCount: number;\n lastOccurredAt: Date;\n};\n\n/**\n * Transactional persistence contract, implemented by the consuming service.\n * transactionForKey MUST serialize operations for the same key across processes,\n * INCLUDING first creation; the callback must be retried on a conflicting insert.\n * insertOccurrenceIfNew MUST enforce a GLOBAL unique occurrenceId (not only per\n * incident); an occurrenceId already belonging to another key MUST throw.\n * On creation loadOrCreate MUST initialise occurrenceCount=1; recordOccurrence\n * MUST NOT increment that first occurrence again. If a duplicate is encountered\n * when newlyCreated=true, the transaction MUST fail and roll back creation.\n * Every operation in this port, including optional support notification, MUST\n * participate in the SAME database transaction and roll back together.\n */\nexport interface IncidentTransaction {\n loadOrCreate(input: { incidentKey: string; occurrence: OperationalIncidentOccurrenceV1 }): Promise<IncidentSnapshot & { newlyCreated: boolean }>;\n insertOccurrenceIfNew(input: { incidentKey: string; occurrence: OperationalIncidentOccurrenceV1 }): Promise<boolean>;\n recordOccurrence(input: {\n incidentKey: string;\n occurrence: OperationalIncidentOccurrenceV1;\n reopen: boolean;\n newlyCreated: boolean;\n }): Promise<IncidentSnapshot>;\n /** Optional, safe internal support projection; at most one per incident key. */\n notifySupportOnce?(input: { incidentKey: string; occurrence: OperationalIncidentOccurrenceV1 }): Promise<void>;\n}\nexport interface IncidentPersistence {\n transactionForKey<T>(key: string, fn: (tx: IncidentTransaction) => Promise<T>): Promise<T>;\n}\nexport interface IncidentReporterLogger {\n error(event: string, fields: Record<string, unknown>): void;\n}\n\n/** Reporter never turns an operational-notification failure into the producer's failure. */\nexport function createOperationalIncidentReporter(options: {\n persistence: IncidentPersistence;\n logger: IncidentReporterLogger;\n notifySupport?: boolean;\n}) {\n return {\n async report(raw: unknown): Promise<IncidentReportResult | null> {\n let occurrence: OperationalIncidentOccurrenceV1;\n try {\n occurrence = parseOperationalIncidentOccurrenceV1(raw);\n } catch {\n // Fail closed, and do not log potentially sensitive invalid payloads.\n options.logger.error(\"operational_incident.invalid_occurrence\", {});\n return null;\n }\n const incidentKey = createOperationalIncidentKey({\n sourceService: occurrence.sourceService, environment: occurrence.environment,\n category: occurrence.category, reasonCode: occurrence.reasonCode,\n resourceKey: occurrence.resourceKey, shopId: occurrence.shopId,\n });\n try {\n return await options.persistence.transactionForKey(incidentKey, async (tx) => {\n const current = await tx.loadOrCreate({ incidentKey, occurrence });\n const recorded = await tx.insertOccurrenceIfNew({ incidentKey, occurrence });\n if (!recorded) {\n return { incidentKey, recorded: false, occurrenceCount: current.occurrenceCount,\n status: current.status };\n }\n const updated = await tx.recordOccurrence({ incidentKey, occurrence,\n reopen: current.status === \"RESOLVED\", newlyCreated: current.newlyCreated });\n if (options.notifySupport && occurrence.shopId !== null && tx.notifySupportOnce) {\n await tx.notifySupportOnce({ incidentKey, occurrence });\n }\n return { incidentKey, recorded: true, occurrenceCount: updated.occurrenceCount,\n status: \"OPEN\" as const };\n });\n } catch {\n options.logger.error(\"operational_incident.report_failed\", {\n incidentKey, sourceService: occurrence.sourceService,\n environment: occurrence.environment, occurrenceId: occurrence.occurrenceId,\n });\n return null;\n }\n },\n };\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { OperationalIncidentOccurrenceV1 } from \"./index.js\";\nimport type { IncidentPersistence, IncidentSnapshot, IncidentTransaction } from \"./reporter.js\";\n\n/** A deliberately small interface; the application's Prisma transaction client supplies it. */\nexport interface OperationalIncidentSqlClient {\n $queryRawUnsafe<T = unknown>(query: string, ...parameters: unknown[]): Promise<T>;\n}\nexport interface OperationalIncidentPrismaClient {\n $transaction<T>(fn: (tx: OperationalIncidentSqlClient) => Promise<T>, options?: {\n maxWait?: number; timeout?: number; isolationLevel?: \"Serializable\";\n }): Promise<T>;\n}\n\n/** Optional merchant-support projection. This callback MUST use the supplied transaction. */\nexport type OperationalIncidentSupportWriter = (input: {\n tx: OperationalIncidentSqlClient;\n incidentId: string;\n incidentKey: string;\n occurrence: OperationalIncidentOccurrenceV1;\n}) => Promise<void>;\n\ninterface IncidentRow {\n id: string;\n incidentKey: string;\n status: \"OPEN\" | \"RESOLVED\";\n occurrenceCount: number;\n lastOccurredAt: Date;\n}\n\nasync function rows<T>(tx: OperationalIncidentSqlClient, statement: string, ...params: unknown[]): Promise<T[]> {\n return tx.$queryRawUnsafe<T[]>(statement, ...params);\n}\n\nfunction snapshot(row: IncidentRow): IncidentSnapshot {\n return { incidentKey: row.incidentKey, status: row.status,\n occurrenceCount: row.occurrenceCount, lastOccurredAt: row.lastOccurredAt };\n}\n\nclass PgIncidentTransaction implements IncidentTransaction {\n private incidentId: string | null = null;\n private newIncident = false;\n constructor(private readonly tx: OperationalIncidentSqlClient,\n private readonly supportWriter?: OperationalIncidentSupportWriter) {}\n\n async loadOrCreate({ incidentKey, occurrence }: {\n incidentKey: string; occurrence: OperationalIncidentOccurrenceV1;\n }): Promise<IncidentSnapshot & { newlyCreated: boolean }> {\n const inserted = await rows<IncidentRow>(this.tx, `\n INSERT INTO support.\"OperationalIncident\" (\n \"id\", \"incidentKey\", \"sourceService\", \"environment\", \"category\", \"reasonCode\",\n \"resourceKey\", \"shopId\", \"severity\", \"status\", \"occurrenceCount\",\n \"firstOccurredAt\", \"lastOccurredAt\", \"lastCorrelationId\")\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,'OPEN',1,$10::timestamptz,$10::timestamptz,$11)\n ON CONFLICT (\"incidentKey\") DO NOTHING RETURNING *`,\n randomUUID(), incidentKey, occurrence.sourceService, occurrence.environment,\n occurrence.category, occurrence.reasonCode, occurrence.resourceKey,\n occurrence.shopId, occurrence.severity, occurrence.occurredAt, occurrence.correlationId);\n this.newIncident = inserted.length > 0;\n const found = await rows<IncidentRow>(this.tx,\n `SELECT * FROM support.\"OperationalIncident\" WHERE \"incidentKey\" = $1 FOR UPDATE`, incidentKey);\n if (found.length !== 1) throw new Error(\"incident row not found after upsert\");\n this.incidentId = found[0]!.id;\n return { ...snapshot(found[0]!), newlyCreated: this.newIncident };\n }\n\n async insertOccurrenceIfNew({ incidentKey, occurrence }: {\n incidentKey: string; occurrence: OperationalIncidentOccurrenceV1;\n }): Promise<boolean> {\n if (!this.incidentId) throw new Error(\"incident not loaded\");\n const created = await rows<{ id: string }>(this.tx, `\n INSERT INTO support.\"OperationalIncidentEvent\"\n (\"id\", \"incidentId\", \"eventType\", \"correlationId\", \"occurredAt\")\n VALUES ($1::uuid, $2, 'OCCURRED', $3, $4::timestamptz)\n ON CONFLICT (\"id\") DO NOTHING RETURNING \"id\"`,\n occurrence.occurrenceId, this.incidentId, occurrence.correlationId, occurrence.occurredAt);\n if (!created.length) {\n if (this.newIncident) throw new Error(\"duplicate occurrence cannot create new incident\");\n const existing = await rows<{ incidentKey: string }>(this.tx, `\n SELECT i.\"incidentKey\" FROM support.\"OperationalIncidentEvent\" e\n JOIN support.\"OperationalIncident\" i ON i.\"id\" = e.\"incidentId\"\n WHERE e.\"id\" = $1::uuid`, occurrence.occurrenceId);\n if (existing.length !== 1 || existing[0]!.incidentKey !== incidentKey) {\n throw new Error(\"occurrence already belongs to another incident\");\n }\n return false;\n }\n return true;\n }\n\n async recordOccurrence({ incidentKey, occurrence, reopen, newlyCreated }: {\n incidentKey: string; occurrence: OperationalIncidentOccurrenceV1;\n reopen: boolean; newlyCreated: boolean;\n }): Promise<IncidentSnapshot> {\n if (!this.incidentId) throw new Error(\"incident not loaded\");\n if (reopen) {\n await rows(this.tx, `INSERT INTO support.\"OperationalIncidentEvent\"\n (\"id\", \"incidentId\", \"eventType\", \"correlationId\", \"occurredAt\")\n VALUES ($1::uuid,$2,'REOPENED',$3,$4::timestamptz)`,\n randomUUID(), this.incidentId, occurrence.correlationId, occurrence.occurredAt);\n }\n const updated = await rows<IncidentRow>(this.tx, `\n UPDATE support.\"OperationalIncident\" SET\n \"occurrenceCount\" = \"occurrenceCount\" + $2::integer,\n \"lastOccurredAt\" = GREATEST(\"lastOccurredAt\", $3::timestamptz),\n \"lastCorrelationId\" = $4,\n \"severity\" = $5,\n \"status\" = 'OPEN', \"resolvedAt\" = NULL,\n \"resolvedByPlatformAdminId\" = NULL, \"resolutionNote\" = NULL,\n \"revision\" = \"revision\" + 1\n WHERE \"incidentKey\" = $1 RETURNING *`, incidentKey,\n newlyCreated ? 0 : 1, occurrence.occurredAt, occurrence.correlationId, occurrence.severity);\n if (updated.length !== 1) throw new Error(\"incident update failed\");\n return snapshot(updated[0]!);\n }\n\n async notifySupportOnce(input: { incidentKey: string; occurrence: OperationalIncidentOccurrenceV1 }): Promise<void> {\n if (this.supportWriter && this.incidentId) await this.supportWriter({\n tx: this.tx, incidentId: this.incidentId,\n incidentKey: input.incidentKey, occurrence: input.occurrence,\n });\n }\n}\n\nfunction retryable(error: unknown): boolean {\n const code = typeof error === \"object\" && error !== null && \"code\" in error\n ? (error as { code?: unknown }).code : undefined;\n return code === \"P2034\" || code === \"40001\" || code === \"40P01\";\n}\n\n/**\n * PostgreSQL/Prisma adapter. No global Prisma client or credentials are created.\n * All writes and an optional support projection occur within a single transaction.\n * Uses a per-key transaction advisory lock and a row lock for cross-process safety.\n */\nexport function createPrismaOperationalIncidentPersistence(options: {\n prisma: OperationalIncidentPrismaClient;\n supportWriter?: OperationalIncidentSupportWriter;\n maxAttempts?: number;\n}): IncidentPersistence {\n const maxAttempts = options.maxAttempts ?? 3;\n if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 10) {\n throw new Error(\"maxAttempts must be between 1 and 10\");\n }\n return {\n async transactionForKey<T>(key: string, fn: (tx: IncidentTransaction) => Promise<T>): Promise<T> {\n for (let attempt = 1; ; attempt++) {\n try {\n return await options.prisma.$transaction(async (tx) => {\n await rows(tx, `SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))::text AS acquired`, key);\n return fn(new PgIncidentTransaction(tx, options.supportWriter));\n }, { isolationLevel: \"Serializable\", timeout: 15000 });\n } catch (error) {\n if (attempt >= maxAttempts || !retryable(error)) throw error;\n }\n }\n },\n };\n}\n"],"mappings":"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","names":[]}