@modainteract/moda-interact-shared 2.4.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-G7FCWVBU.js +32 -0
- package/dist/chunk-G7FCWVBU.js.map +1 -0
- package/dist/{chunk-SU2DAOEC.js → chunk-NHIGHAEQ.js} +5 -1
- package/dist/{chunk-SU2DAOEC.js.map → chunk-NHIGHAEQ.js.map} +1 -1
- package/dist/chunk-QG53EVA6.js +56 -0
- package/dist/chunk-QG53EVA6.js.map +1 -0
- package/dist/identity/index.d.ts +52 -0
- package/dist/identity/index.js +61 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/incidents/index.d.ts +1 -0
- package/dist/incidents/index.js +18 -9
- package/dist/incidents/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +14 -1
- package/dist/shopify/index.d.ts +5 -0
- package/dist/shopify/index.js +15 -2
- package/dist/shopify/node.d.ts +10 -1
- package/dist/shopify/node.js +12 -1
- package/dist/shopify/node.js.map +1 -1
- package/dist/whatsapp-consent-sync.schema-DEU84xTL.d.ts +27 -0
- package/fixtures/identity-v1.json +48 -0
- package/package.json +9 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isShopifyCustomerNumericId
|
|
3
|
+
} from "./chunk-QG53EVA6.js";
|
|
4
|
+
|
|
5
|
+
// src/shopify/whatsapp-consent-sync.schema.ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
var SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION = 1;
|
|
8
|
+
var SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC = "customers_whats_app_marketing_consent/update";
|
|
9
|
+
var ShopifyWhatsAppConsentSyncJobV1Schema = z.object({
|
|
10
|
+
schemaVersion: z.literal(SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION),
|
|
11
|
+
shopId: z.string().min(1).max(128).regex(/^\S+$/),
|
|
12
|
+
shopifyCustomerId: z.string().refine(isShopifyCustomerNumericId, {
|
|
13
|
+
message: "Must be a canonical Shopify numeric Customer ID"
|
|
14
|
+
}),
|
|
15
|
+
deliveryId: z.string().min(1).max(256).regex(/^\S+$/),
|
|
16
|
+
triggeredAt: z.iso.datetime({ offset: true })
|
|
17
|
+
}).strict();
|
|
18
|
+
function parseShopifyWhatsAppConsentSyncJobV1(input) {
|
|
19
|
+
return ShopifyWhatsAppConsentSyncJobV1Schema.parse(input);
|
|
20
|
+
}
|
|
21
|
+
function safeParseShopifyWhatsAppConsentSyncJobV1(input) {
|
|
22
|
+
return ShopifyWhatsAppConsentSyncJobV1Schema.safeParse(input);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION,
|
|
27
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC,
|
|
28
|
+
ShopifyWhatsAppConsentSyncJobV1Schema,
|
|
29
|
+
parseShopifyWhatsAppConsentSyncJobV1,
|
|
30
|
+
safeParseShopifyWhatsAppConsentSyncJobV1
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=chunk-G7FCWVBU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shopify/whatsapp-consent-sync.schema.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { isShopifyCustomerNumericId } from \"../identity/shopify-customer.js\";\n\nexport const SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION = 1 as const;\nexport const SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC =\n \"customers_whats_app_marketing_consent/update\" as const;\n\n/**\n * A webhook notification to re-read CURRENT Shopify consent in Background.\n * These fields contain no consent decision, phone, checkout or recovery data;\n * parsing this job never authorises a recovery or an outbound message.\n */\nexport const ShopifyWhatsAppConsentSyncJobV1Schema = z\n .object({\n schemaVersion: z.literal(SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION),\n shopId: z.string().min(1).max(128).regex(/^\\S+$/),\n shopifyCustomerId: z.string().refine(isShopifyCustomerNumericId, {\n message: \"Must be a canonical Shopify numeric Customer ID\",\n }),\n deliveryId: z.string().min(1).max(256).regex(/^\\S+$/),\n triggeredAt: z.iso.datetime({ offset: true }),\n })\n .strict();\n\nexport type ShopifyWhatsAppConsentSyncJobV1 =\n z.infer<typeof ShopifyWhatsAppConsentSyncJobV1Schema>;\n\nexport function parseShopifyWhatsAppConsentSyncJobV1(\n input: unknown,\n): ShopifyWhatsAppConsentSyncJobV1 {\n return ShopifyWhatsAppConsentSyncJobV1Schema.parse(input);\n}\n\nexport function safeParseShopifyWhatsAppConsentSyncJobV1(input: unknown) {\n return ShopifyWhatsAppConsentSyncJobV1Schema.safeParse(input);\n}\n"],"mappings":";;;;;AAAA,SAAS,SAAS;AAGX,IAAM,+CAA+C;AACrD,IAAM,sCACX;AAOK,IAAM,wCAAwC,EAClD,OAAO;AAAA,EACN,eAAe,EAAE,QAAQ,4CAA4C;AAAA,EACrE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,MAAM,OAAO;AAAA,EAChD,mBAAmB,EAAE,OAAO,EAAE,OAAO,4BAA4B;AAAA,IAC/D,SAAS;AAAA,EACX,CAAC;AAAA,EACD,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,MAAM,OAAO;AAAA,EACpD,aAAa,EAAE,IAAI,SAAS,EAAE,QAAQ,KAAK,CAAC;AAC9C,CAAC,EACA,OAAO;AAKH,SAAS,qCACd,OACiC;AACjC,SAAO,sCAAsC,MAAM,KAAK;AAC1D;AAEO,SAAS,yCAAyC,OAAgB;AACvE,SAAO,sCAAsC,UAAU,KAAK;AAC9D;","names":[]}
|
|
@@ -344,6 +344,10 @@ var SHOPIFY_WEBHOOK_QUEUE_CONTRACTS = {
|
|
|
344
344
|
queueName: "order-events",
|
|
345
345
|
jobName: "order-completed"
|
|
346
346
|
},
|
|
347
|
+
WHATSAPP_CONSENT_SYNC: {
|
|
348
|
+
queueName: "checkout-events",
|
|
349
|
+
jobName: "shopify-whatsapp-consent-sync"
|
|
350
|
+
},
|
|
347
351
|
SHOPIFY_DISCOUNT_SYNC: {
|
|
348
352
|
queueName: "shopify-discount-sync",
|
|
349
353
|
jobName: "reconcile-shopify-discounts"
|
|
@@ -465,4 +469,4 @@ export {
|
|
|
465
469
|
parseShopifyDiscountSyncJob,
|
|
466
470
|
safeParseShopifyDiscountSyncJob
|
|
467
471
|
};
|
|
468
|
-
//# sourceMappingURL=chunk-
|
|
472
|
+
//# sourceMappingURL=chunk-NHIGHAEQ.js.map
|
|
@@ -1 +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"]}
|
|
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 WHATSAPP_CONSENT_SYNC: {\n queueName: \"checkout-events\",\n jobName: \"shopify-whatsapp-consent-sync\",\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,56 @@
|
|
|
1
|
+
// src/identity/shopify-customer.ts
|
|
2
|
+
var SHOPIFY_CUSTOMER_ID_MAX = 9223372036854775807n;
|
|
3
|
+
var CUSTOMER_GID_PREFIX = "gid://shopify/Customer/";
|
|
4
|
+
var CANONICAL_DECIMAL_ID = /^[1-9][0-9]*$/;
|
|
5
|
+
var ShopifyCustomerIdentityError = class extends Error {
|
|
6
|
+
constructor(code) {
|
|
7
|
+
super(code);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "ShopifyCustomerIdentityError";
|
|
10
|
+
}
|
|
11
|
+
code;
|
|
12
|
+
};
|
|
13
|
+
function parseShopifyCustomerIdentity(input) {
|
|
14
|
+
const identifier = typeof input === "number" ? Number.isSafeInteger(input) && input > 0 ? String(input) : "" : typeof input === "string" ? input.startsWith(CUSTOMER_GID_PREFIX) ? input.slice(CUSTOMER_GID_PREFIX.length) : input : "";
|
|
15
|
+
if (!CANONICAL_DECIMAL_ID.test(identifier)) {
|
|
16
|
+
throw new ShopifyCustomerIdentityError("INVALID_CUSTOMER_ID");
|
|
17
|
+
}
|
|
18
|
+
if (identifier.length > 19 || BigInt(identifier) > SHOPIFY_CUSTOMER_ID_MAX) {
|
|
19
|
+
throw new ShopifyCustomerIdentityError("CUSTOMER_ID_OUT_OF_RANGE");
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze({ numericId: identifier, gid: `${CUSTOMER_GID_PREFIX}${identifier}` });
|
|
22
|
+
}
|
|
23
|
+
function toShopifyCustomerGid(input) {
|
|
24
|
+
return parseShopifyCustomerIdentity(input).gid;
|
|
25
|
+
}
|
|
26
|
+
function toShopifyCustomerNumericId(input) {
|
|
27
|
+
return parseShopifyCustomerIdentity(input).numericId;
|
|
28
|
+
}
|
|
29
|
+
function isShopifyCustomerNumericId(input) {
|
|
30
|
+
if (typeof input !== "string") return false;
|
|
31
|
+
try {
|
|
32
|
+
return parseShopifyCustomerIdentity(input).numericId === input;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function areSameShopifyCustomerWithinShop(first, second) {
|
|
38
|
+
for (const shopId of [first.shopId, second.shopId]) {
|
|
39
|
+
if (typeof shopId !== "string" || shopId.length === 0 || shopId.length > 128 || shopId.trim() !== shopId || /\s/.test(shopId)) {
|
|
40
|
+
throw new ShopifyCustomerIdentityError("INVALID_SHOP_ID");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const firstId = toShopifyCustomerNumericId(first.customerId);
|
|
44
|
+
const secondId = toShopifyCustomerNumericId(second.customerId);
|
|
45
|
+
return first.shopId === second.shopId && firstId === secondId;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
ShopifyCustomerIdentityError,
|
|
50
|
+
parseShopifyCustomerIdentity,
|
|
51
|
+
toShopifyCustomerGid,
|
|
52
|
+
toShopifyCustomerNumericId,
|
|
53
|
+
isShopifyCustomerNumericId,
|
|
54
|
+
areSameShopifyCustomerWithinShop
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=chunk-QG53EVA6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/identity/shopify-customer.ts"],"sourcesContent":["/** Shopify's numeric customer IDs are decimal strings, not JavaScript numbers.\n * The signed 64-bit bound avoids accidentally accepting out-of-range IDs in\n * consumers that persist or compare them with PostgreSQL BIGINT values.\n */\nconst SHOPIFY_CUSTOMER_ID_MAX = 9_223_372_036_854_775_807n;\nconst CUSTOMER_GID_PREFIX = \"gid://shopify/Customer/\";\nconst CANONICAL_DECIMAL_ID = /^[1-9][0-9]*$/;\n\nexport type ShopifyCustomerIdentityErrorCode =\n | \"INVALID_CUSTOMER_ID\"\n | \"CUSTOMER_ID_OUT_OF_RANGE\"\n | \"INVALID_SHOP_ID\";\n\n/** Never echo an untrusted customer identifier in an error message. */\nexport class ShopifyCustomerIdentityError extends Error {\n constructor(readonly code: ShopifyCustomerIdentityErrorCode) {\n super(code);\n this.name = \"ShopifyCustomerIdentityError\";\n }\n}\n\nexport type ShopifyCustomerIdentity = Readonly<{\n numericId: string;\n gid: string;\n}>;\n\n/**\n * Accept exact numeric webhook IDs or exact GraphQL Customer GIDs. A JSON\n * number is accepted only if JS can represent it without precision loss.\n */\nexport function parseShopifyCustomerIdentity(input: unknown): ShopifyCustomerIdentity {\n const identifier = typeof input === \"number\"\n ? Number.isSafeInteger(input) && input > 0\n ? String(input)\n : \"\"\n : typeof input === \"string\"\n ? input.startsWith(CUSTOMER_GID_PREFIX)\n ? input.slice(CUSTOMER_GID_PREFIX.length)\n : input\n : \"\";\n\n if (!CANONICAL_DECIMAL_ID.test(identifier)) {\n throw new ShopifyCustomerIdentityError(\"INVALID_CUSTOMER_ID\");\n }\n\n // Guard length before BigInt parsing of any externally controlled string.\n if (identifier.length > 19 || BigInt(identifier) > SHOPIFY_CUSTOMER_ID_MAX) {\n throw new ShopifyCustomerIdentityError(\"CUSTOMER_ID_OUT_OF_RANGE\");\n }\n\n return Object.freeze({ numericId: identifier, gid: `${CUSTOMER_GID_PREFIX}${identifier}` });\n}\n\nexport function toShopifyCustomerGid(input: unknown): string {\n return parseShopifyCustomerIdentity(input).gid;\n}\n\nexport function toShopifyCustomerNumericId(input: unknown): string {\n return parseShopifyCustomerIdentity(input).numericId;\n}\n\n/** Used by event schemas, which require the canonical decimal string form. */\nexport function isShopifyCustomerNumericId(input: unknown): input is string {\n if (typeof input !== \"string\") return false;\n try {\n return parseShopifyCustomerIdentity(input).numericId === input;\n } catch {\n return false;\n }\n}\n\n/**\n * Identity comparison is always scoped to a shop. It does NOT authenticate\n * either shop or establish that the caller may access either customer.\n */\nexport function areSameShopifyCustomerWithinShop(\n first: { shopId: string; customerId: unknown },\n second: { shopId: string; customerId: unknown },\n): boolean {\n for (const shopId of [first.shopId, second.shopId]) {\n if (typeof shopId !== \"string\" || shopId.length === 0 || shopId.length > 128 ||\n shopId.trim() !== shopId || /\\s/.test(shopId)) {\n throw new ShopifyCustomerIdentityError(\"INVALID_SHOP_ID\");\n }\n }\n\n const firstId = toShopifyCustomerNumericId(first.customerId);\n const secondId = toShopifyCustomerNumericId(second.customerId);\n return first.shopId === second.shopId && firstId === secondId;\n}\n"],"mappings":";AAIA,IAAM,0BAA0B;AAChC,IAAM,sBAAsB;AAC5B,IAAM,uBAAuB;AAQtB,IAAM,+BAAN,cAA2C,MAAM;AAAA,EACtD,YAAqB,MAAwC;AAC3D,UAAM,IAAI;AADS;AAEnB,SAAK,OAAO;AAAA,EACd;AAAA,EAHqB;AAIvB;AAWO,SAAS,6BAA6B,OAAyC;AACpF,QAAM,aAAa,OAAO,UAAU,WAChC,OAAO,cAAc,KAAK,KAAK,QAAQ,IACrC,OAAO,KAAK,IACZ,KACF,OAAO,UAAU,WACf,MAAM,WAAW,mBAAmB,IAClC,MAAM,MAAM,oBAAoB,MAAM,IACtC,QACF;AAEN,MAAI,CAAC,qBAAqB,KAAK,UAAU,GAAG;AAC1C,UAAM,IAAI,6BAA6B,qBAAqB;AAAA,EAC9D;AAGA,MAAI,WAAW,SAAS,MAAM,OAAO,UAAU,IAAI,yBAAyB;AAC1E,UAAM,IAAI,6BAA6B,0BAA0B;AAAA,EACnE;AAEA,SAAO,OAAO,OAAO,EAAE,WAAW,YAAY,KAAK,GAAG,mBAAmB,GAAG,UAAU,GAAG,CAAC;AAC5F;AAEO,SAAS,qBAAqB,OAAwB;AAC3D,SAAO,6BAA6B,KAAK,EAAE;AAC7C;AAEO,SAAS,2BAA2B,OAAwB;AACjE,SAAO,6BAA6B,KAAK,EAAE;AAC7C;AAGO,SAAS,2BAA2B,OAAiC;AAC1E,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI;AACF,WAAO,6BAA6B,KAAK,EAAE,cAAc;AAAA,EAC3D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,iCACd,OACA,QACS;AACT,aAAW,UAAU,CAAC,MAAM,QAAQ,OAAO,MAAM,GAAG;AAClD,QAAI,OAAO,WAAW,YAAY,OAAO,WAAW,KAAK,OAAO,SAAS,OACrE,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK,MAAM,GAAG;AACjD,YAAM,IAAI,6BAA6B,iBAAiB;AAAA,IAC1D;AAAA,EACF;AAEA,QAAM,UAAU,2BAA2B,MAAM,UAAU;AAC3D,QAAM,WAAW,2BAA2B,OAAO,UAAU;AAC7D,SAAO,MAAM,WAAW,OAAO,UAAU,YAAY;AACvD;","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
type RecipientPhoneRejection = "INVALID_FORMAT" | "COUNTRY_REQUIRED" | "UNSUPPORTED_COUNTRY" | "COUNTRY_MISMATCH" | "EXTENSION_UNSUPPORTED" | "INVALID_NUMBER";
|
|
2
|
+
type RecipientPhoneResult = Readonly<{
|
|
3
|
+
ok: true;
|
|
4
|
+
e164: string;
|
|
5
|
+
}> | Readonly<{
|
|
6
|
+
ok: false;
|
|
7
|
+
reason: RecipientPhoneRejection;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Canonical *number* identity only: it does not validate ownership,
|
|
11
|
+
* WhatsApp reachability, local STOP, or any marketing consent.
|
|
12
|
+
*
|
|
13
|
+
* `verifiedCountryCode` must come from a trustworthy country source, not from
|
|
14
|
+
* language/locale guesses. A national number without one always fails closed.
|
|
15
|
+
* Provider wa_id/digits-only conversion belongs in service-specific adapters.
|
|
16
|
+
*/
|
|
17
|
+
declare function normalizeRecipientPhone(input: unknown, context?: {
|
|
18
|
+
verifiedCountryCode?: string;
|
|
19
|
+
}): RecipientPhoneResult;
|
|
20
|
+
|
|
21
|
+
type ShopifyCustomerIdentityErrorCode = "INVALID_CUSTOMER_ID" | "CUSTOMER_ID_OUT_OF_RANGE" | "INVALID_SHOP_ID";
|
|
22
|
+
/** Never echo an untrusted customer identifier in an error message. */
|
|
23
|
+
declare class ShopifyCustomerIdentityError extends Error {
|
|
24
|
+
readonly code: ShopifyCustomerIdentityErrorCode;
|
|
25
|
+
constructor(code: ShopifyCustomerIdentityErrorCode);
|
|
26
|
+
}
|
|
27
|
+
type ShopifyCustomerIdentity = Readonly<{
|
|
28
|
+
numericId: string;
|
|
29
|
+
gid: string;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Accept exact numeric webhook IDs or exact GraphQL Customer GIDs. A JSON
|
|
33
|
+
* number is accepted only if JS can represent it without precision loss.
|
|
34
|
+
*/
|
|
35
|
+
declare function parseShopifyCustomerIdentity(input: unknown): ShopifyCustomerIdentity;
|
|
36
|
+
declare function toShopifyCustomerGid(input: unknown): string;
|
|
37
|
+
declare function toShopifyCustomerNumericId(input: unknown): string;
|
|
38
|
+
/** Used by event schemas, which require the canonical decimal string form. */
|
|
39
|
+
declare function isShopifyCustomerNumericId(input: unknown): input is string;
|
|
40
|
+
/**
|
|
41
|
+
* Identity comparison is always scoped to a shop. It does NOT authenticate
|
|
42
|
+
* either shop or establish that the caller may access either customer.
|
|
43
|
+
*/
|
|
44
|
+
declare function areSameShopifyCustomerWithinShop(first: {
|
|
45
|
+
shopId: string;
|
|
46
|
+
customerId: unknown;
|
|
47
|
+
}, second: {
|
|
48
|
+
shopId: string;
|
|
49
|
+
customerId: unknown;
|
|
50
|
+
}): boolean;
|
|
51
|
+
|
|
52
|
+
export { type RecipientPhoneRejection, type RecipientPhoneResult, type ShopifyCustomerIdentity, ShopifyCustomerIdentityError, type ShopifyCustomerIdentityErrorCode, areSameShopifyCustomerWithinShop, isShopifyCustomerNumericId, normalizeRecipientPhone, parseShopifyCustomerIdentity, toShopifyCustomerGid, toShopifyCustomerNumericId };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ShopifyCustomerIdentityError,
|
|
3
|
+
areSameShopifyCustomerWithinShop,
|
|
4
|
+
isShopifyCustomerNumericId,
|
|
5
|
+
parseShopifyCustomerIdentity,
|
|
6
|
+
toShopifyCustomerGid,
|
|
7
|
+
toShopifyCustomerNumericId
|
|
8
|
+
} from "../chunk-QG53EVA6.js";
|
|
9
|
+
|
|
10
|
+
// src/identity/recipient-phone.ts
|
|
11
|
+
import {
|
|
12
|
+
isSupportedCountry,
|
|
13
|
+
parsePhoneNumberFromString
|
|
14
|
+
} from "libphonenumber-js/max";
|
|
15
|
+
function normalizeRecipientPhone(input, context = {}) {
|
|
16
|
+
if (typeof input !== "string" || input.length > 64) {
|
|
17
|
+
return { ok: false, reason: "INVALID_FORMAT" };
|
|
18
|
+
}
|
|
19
|
+
const raw = input.trim();
|
|
20
|
+
if (raw.length === 0) return { ok: false, reason: "INVALID_FORMAT" };
|
|
21
|
+
if (/\b(?:ext\.?|extension|x)\s*\d/i.test(raw) || /[#;]/.test(raw)) {
|
|
22
|
+
return { ok: false, reason: "EXTENSION_UNSUPPORTED" };
|
|
23
|
+
}
|
|
24
|
+
if (!/^\+?[0-9() .-]+$/.test(raw)) {
|
|
25
|
+
return { ok: false, reason: "INVALID_FORMAT" };
|
|
26
|
+
}
|
|
27
|
+
const isInternational = raw.startsWith("+");
|
|
28
|
+
const suppliedCountry = context.verifiedCountryCode;
|
|
29
|
+
if (!isInternational && suppliedCountry === void 0) {
|
|
30
|
+
return { ok: false, reason: "COUNTRY_REQUIRED" };
|
|
31
|
+
}
|
|
32
|
+
if (suppliedCountry !== void 0 && (!/^[A-Z]{2}$/.test(suppliedCountry) || !isSupportedCountry(suppliedCountry))) {
|
|
33
|
+
return { ok: false, reason: "UNSUPPORTED_COUNTRY" };
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const phone = parsePhoneNumberFromString(raw, {
|
|
37
|
+
...suppliedCountry ? { defaultCountry: suppliedCountry } : {},
|
|
38
|
+
extract: false
|
|
39
|
+
});
|
|
40
|
+
if (!phone || !phone.isValid() || !/^\+[1-9][0-9]{1,14}$/.test(phone.number)) {
|
|
41
|
+
return { ok: false, reason: "INVALID_NUMBER" };
|
|
42
|
+
}
|
|
43
|
+
if (phone.ext) return { ok: false, reason: "EXTENSION_UNSUPPORTED" };
|
|
44
|
+
if (suppliedCountry && phone.country !== suppliedCountry) {
|
|
45
|
+
return { ok: false, reason: "COUNTRY_MISMATCH" };
|
|
46
|
+
}
|
|
47
|
+
return { ok: true, e164: phone.number };
|
|
48
|
+
} catch {
|
|
49
|
+
return { ok: false, reason: "INVALID_NUMBER" };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
ShopifyCustomerIdentityError,
|
|
54
|
+
areSameShopifyCustomerWithinShop,
|
|
55
|
+
isShopifyCustomerNumericId,
|
|
56
|
+
normalizeRecipientPhone,
|
|
57
|
+
parseShopifyCustomerIdentity,
|
|
58
|
+
toShopifyCustomerGid,
|
|
59
|
+
toShopifyCustomerNumericId
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/identity/recipient-phone.ts"],"sourcesContent":["import {\n isSupportedCountry,\n parsePhoneNumberFromString,\n type CountryCode,\n} from \"libphonenumber-js/max\";\n\nexport type RecipientPhoneRejection =\n | \"INVALID_FORMAT\"\n | \"COUNTRY_REQUIRED\"\n | \"UNSUPPORTED_COUNTRY\"\n | \"COUNTRY_MISMATCH\"\n | \"EXTENSION_UNSUPPORTED\"\n | \"INVALID_NUMBER\";\n\nexport type RecipientPhoneResult =\n | Readonly<{ ok: true; e164: string }>\n | Readonly<{ ok: false; reason: RecipientPhoneRejection }>;\n\n/**\n * Canonical *number* identity only: it does not validate ownership,\n * WhatsApp reachability, local STOP, or any marketing consent.\n *\n * `verifiedCountryCode` must come from a trustworthy country source, not from\n * language/locale guesses. A national number without one always fails closed.\n * Provider wa_id/digits-only conversion belongs in service-specific adapters.\n */\nexport function normalizeRecipientPhone(\n input: unknown,\n context: { verifiedCountryCode?: string } = {},\n): RecipientPhoneResult {\n if (typeof input !== \"string\" || input.length > 64) {\n return { ok: false, reason: \"INVALID_FORMAT\" };\n }\n\n const raw = input.trim();\n if (raw.length === 0) return { ok: false, reason: \"INVALID_FORMAT\" };\n // Reject letters, dial strings, extensions and characters other than normal\n // phone presentation punctuation. Do not silently remove an extension.\n if (/\\b(?:ext\\.?|extension|x)\\s*\\d/i.test(raw) || /[#;]/.test(raw)) {\n return { ok: false, reason: \"EXTENSION_UNSUPPORTED\" };\n }\n if (!/^\\+?[0-9() .-]+$/.test(raw)) {\n return { ok: false, reason: \"INVALID_FORMAT\" };\n }\n\n const isInternational = raw.startsWith(\"+\");\n const suppliedCountry = context.verifiedCountryCode;\n if (!isInternational && suppliedCountry === undefined) {\n return { ok: false, reason: \"COUNTRY_REQUIRED\" };\n }\n if (suppliedCountry !== undefined &&\n (!/^[A-Z]{2}$/.test(suppliedCountry) ||\n !isSupportedCountry(suppliedCountry as CountryCode))) {\n return { ok: false, reason: \"UNSUPPORTED_COUNTRY\" };\n }\n\n try {\n const phone = parsePhoneNumberFromString(raw, {\n ...(suppliedCountry ? { defaultCountry: suppliedCountry as CountryCode } : {}),\n extract: false,\n });\n if (!phone || !phone.isValid() || !/^\\+[1-9][0-9]{1,14}$/.test(phone.number)) {\n return { ok: false, reason: \"INVALID_NUMBER\" };\n }\n if (phone.ext) return { ok: false, reason: \"EXTENSION_UNSUPPORTED\" };\n // With a verified context, an unrelated international number must not be\n // silently attributed to that country. The caller can omit the context\n // entirely when normalising an explicitly international number.\n if (suppliedCountry && phone.country !== suppliedCountry) {\n return { ok: false, reason: \"COUNTRY_MISMATCH\" };\n }\n return { ok: true, e164: phone.number };\n } catch {\n return { ok: false, reason: \"INVALID_NUMBER\" };\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAsBA,SAAS,wBACd,OACA,UAA4C,CAAC,GACvB;AACtB,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI;AAClD,WAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,EAC/C;AAEA,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,IAAI,WAAW,EAAG,QAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAGnE,MAAI,iCAAiC,KAAK,GAAG,KAAK,OAAO,KAAK,GAAG,GAAG;AAClE,WAAO,EAAE,IAAI,OAAO,QAAQ,wBAAwB;AAAA,EACtD;AACA,MAAI,CAAC,mBAAmB,KAAK,GAAG,GAAG;AACjC,WAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,EAC/C;AAEA,QAAM,kBAAkB,IAAI,WAAW,GAAG;AAC1C,QAAM,kBAAkB,QAAQ;AAChC,MAAI,CAAC,mBAAmB,oBAAoB,QAAW;AACrD,WAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB;AAAA,EACjD;AACA,MAAI,oBAAoB,WACnB,CAAC,aAAa,KAAK,eAAe,KAClC,CAAC,mBAAmB,eAA8B,IAAI;AACzD,WAAO,EAAE,IAAI,OAAO,QAAQ,sBAAsB;AAAA,EACpD;AAEA,MAAI;AACF,UAAM,QAAQ,2BAA2B,KAAK;AAAA,MAC5C,GAAI,kBAAkB,EAAE,gBAAgB,gBAA+B,IAAI,CAAC;AAAA,MAC5E,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,KAAK,CAAC,uBAAuB,KAAK,MAAM,MAAM,GAAG;AAC5E,aAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,IAC/C;AACA,QAAI,MAAM,IAAK,QAAO,EAAE,IAAI,OAAO,QAAQ,wBAAwB;AAInE,QAAI,mBAAmB,MAAM,YAAY,iBAAiB;AACxD,aAAO,EAAE,IAAI,OAAO,QAAQ,mBAAmB;AAAA,IACjD;AACA,WAAO,EAAE,IAAI,MAAM,MAAM,MAAM,OAAO;AAAA,EACxC,QAAQ;AACN,WAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,EAC/C;AACF;","names":[]}
|
|
@@ -116,6 +116,7 @@ declare const OperationalIncidentOccurrenceV1Schema: z.ZodObject<{
|
|
|
116
116
|
occurrenceId: z.ZodString;
|
|
117
117
|
correlationId: z.ZodString;
|
|
118
118
|
occurredAt: z.ZodISODateTime;
|
|
119
|
+
errorMessage: z.ZodOptional<z.ZodString>;
|
|
119
120
|
}, z.core.$strict>;
|
|
120
121
|
type OperationalIncidentOccurrenceV1 = z.infer<typeof OperationalIncidentOccurrenceV1Schema>;
|
|
121
122
|
declare function parseOperationalIncidentOccurrenceV1(input: unknown): OperationalIncidentOccurrenceV1;
|
package/dist/incidents/index.js
CHANGED
|
@@ -64,6 +64,7 @@ function createOperationalIncidentReporter(options) {
|
|
|
64
64
|
|
|
65
65
|
// src/incidents/postgres.ts
|
|
66
66
|
import { randomUUID } from "crypto";
|
|
67
|
+
import { setTimeout as delay } from "timers/promises";
|
|
67
68
|
async function rows(tx, statement, ...params) {
|
|
68
69
|
return tx.$queryRawUnsafe(statement, ...params);
|
|
69
70
|
}
|
|
@@ -91,8 +92,8 @@ var PgIncidentTransaction = class {
|
|
|
91
92
|
INSERT INTO support."OperationalIncident" (
|
|
92
93
|
"id", "incidentKey", "sourceService", "environment", "category", "reasonCode",
|
|
93
94
|
"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)
|
|
95
|
+
"firstOccurredAt", "lastOccurredAt", "lastCorrelationId", "lastErrorMessage")
|
|
96
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,'OPEN',1,$10::timestamptz,$10::timestamptz,$11,$12)
|
|
96
97
|
ON CONFLICT ("incidentKey") DO NOTHING RETURNING *`,
|
|
97
98
|
randomUUID(),
|
|
98
99
|
incidentKey,
|
|
@@ -104,7 +105,8 @@ var PgIncidentTransaction = class {
|
|
|
104
105
|
occurrence.shopId,
|
|
105
106
|
occurrence.severity,
|
|
106
107
|
occurrence.occurredAt,
|
|
107
|
-
occurrence.correlationId
|
|
108
|
+
occurrence.correlationId,
|
|
109
|
+
occurrence.errorMessage ?? null
|
|
108
110
|
);
|
|
109
111
|
this.newIncident = inserted.length > 0;
|
|
110
112
|
const found = await rows(
|
|
@@ -122,13 +124,14 @@ var PgIncidentTransaction = class {
|
|
|
122
124
|
this.tx,
|
|
123
125
|
`
|
|
124
126
|
INSERT INTO support."OperationalIncidentEvent"
|
|
125
|
-
("id", "incidentId", "eventType", "correlationId", "occurredAt")
|
|
126
|
-
VALUES ($1::uuid, $2, 'OCCURRED', $3, $4::timestamptz)
|
|
127
|
+
("id", "incidentId", "eventType", "correlationId", "occurredAt", "errorMessage")
|
|
128
|
+
VALUES ($1::uuid, $2, 'OCCURRED', $3, $4::timestamptz, $5)
|
|
127
129
|
ON CONFLICT ("id") DO NOTHING RETURNING "id"`,
|
|
128
130
|
occurrence.occurrenceId,
|
|
129
131
|
this.incidentId,
|
|
130
132
|
occurrence.correlationId,
|
|
131
|
-
occurrence.occurredAt
|
|
133
|
+
occurrence.occurredAt,
|
|
134
|
+
occurrence.errorMessage ?? null
|
|
132
135
|
);
|
|
133
136
|
if (!created.length) {
|
|
134
137
|
if (this.newIncident) throw new Error("duplicate occurrence cannot create new incident");
|
|
@@ -164,6 +167,8 @@ var PgIncidentTransaction = class {
|
|
|
164
167
|
"occurrenceCount" = "occurrenceCount" + $2::integer,
|
|
165
168
|
"lastOccurredAt" = GREATEST("lastOccurredAt", $3::timestamptz),
|
|
166
169
|
"lastCorrelationId" = $4,
|
|
170
|
+
"lastErrorMessage" = CASE WHEN $3::timestamptz >= "lastOccurredAt" AND $6::text IS NOT NULL
|
|
171
|
+
THEN $6::text ELSE "lastErrorMessage" END,
|
|
167
172
|
"severity" = $5,
|
|
168
173
|
"status" = 'OPEN', "resolvedAt" = NULL,
|
|
169
174
|
"resolvedByPlatformAdminId" = NULL, "resolutionNote" = NULL,
|
|
@@ -173,7 +178,8 @@ var PgIncidentTransaction = class {
|
|
|
173
178
|
newlyCreated ? 0 : 1,
|
|
174
179
|
occurrence.occurredAt,
|
|
175
180
|
occurrence.correlationId,
|
|
176
|
-
occurrence.severity
|
|
181
|
+
occurrence.severity,
|
|
182
|
+
occurrence.errorMessage ?? null
|
|
177
183
|
);
|
|
178
184
|
if (updated.length !== 1) throw new Error("incident update failed");
|
|
179
185
|
return snapshot(updated[0]);
|
|
@@ -206,6 +212,7 @@ function createPrismaOperationalIncidentPersistence(options) {
|
|
|
206
212
|
}, { isolationLevel: "Serializable", timeout: 15e3 });
|
|
207
213
|
} catch (error) {
|
|
208
214
|
if (attempt >= maxAttempts || !retryable(error)) throw error;
|
|
215
|
+
await delay(attempt * 10);
|
|
209
216
|
}
|
|
210
217
|
}
|
|
211
218
|
}
|
|
@@ -228,8 +235,10 @@ var OperationalIncidentOccurrenceV1Schema = z.strictObject({
|
|
|
228
235
|
severity: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]),
|
|
229
236
|
occurrenceId: opaqueId,
|
|
230
237
|
correlationId: identifier,
|
|
231
|
-
occurredAt: z.iso.datetime({ offset: true })
|
|
232
|
-
//
|
|
238
|
+
occurredAt: z.iso.datetime({ offset: true }),
|
|
239
|
+
// Already redacted, single-line exception excerpt from a trusted server producer.
|
|
240
|
+
// Never accept arbitrary metadata bags, headers, stacks or Error objects.
|
|
241
|
+
errorMessage: z.string().min(1).max(512).regex(/^[^\r\n\x00-\x1f\x7f]*$/).optional()
|
|
233
242
|
});
|
|
234
243
|
function parseOperationalIncidentOccurrenceV1(input) {
|
|
235
244
|
return OperationalIncidentOccurrenceV1Schema.parse(input);
|
|
@@ -1 +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":[]}
|
|
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 // Already redacted, single-line exception excerpt from a trusted server producer.\n // Never accept arbitrary metadata bags, headers, stacks or Error objects.\n errorMessage: z.string().min(1).max(512).regex(/^[^\\r\\n\\x00-\\x1f\\x7f]*$/).optional(),\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 { setTimeout as delay } from \"node:timers/promises\";\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\", \"lastErrorMessage\")\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,'OPEN',1,$10::timestamptz,$10::timestamptz,$11,$12)\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, occurrence.errorMessage ?? null);\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\", \"errorMessage\")\n VALUES ($1::uuid, $2, 'OCCURRED', $3, $4::timestamptz, $5)\n ON CONFLICT (\"id\") DO NOTHING RETURNING \"id\"`,\n occurrence.occurrenceId, this.incidentId, occurrence.correlationId, occurrence.occurredAt, occurrence.errorMessage ?? null);\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 \"lastErrorMessage\" = CASE WHEN $3::timestamptz >= \"lastOccurredAt\" AND $6::text IS NOT NULL\n THEN $6::text ELSE \"lastErrorMessage\" END,\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, occurrence.errorMessage ?? null);\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 await delay(attempt * 10);\n }\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,6 @@ export { APP_PRICING_BILLING_PERIOD_DRAIN_WINDOW_MS, ARCH007_BILLING_CONTRACT_SC
|
|
|
3
3
|
export { AuthoredSupportBodySchema, MERCHANT_COMMUNICATIONS_JOB_NAMES, MERCHANT_COMMUNICATIONS_QUEUE_NAME, MERCHANT_COMMUNICATIONS_SCHEMA_VERSION, MerchantMessageTranslationContract, MerchantMessageTranslationContractSchema, MerchantMessageTranslationStatus, MerchantMessageTranslationStatusSchema, MerchantSupportMessageContract, MerchantSupportMessageContractSchema, MerchantSupportMessageKind, MerchantSupportMessageKindSchema, MerchantSupportMessageState, MerchantSupportMessageStateSchema, MerchantTranslationBatchContract, MerchantTranslationBatchContractSchema, MerchantTranslationBatchStatus, MerchantTranslationBatchStatusSchema, MerchantTranslationDirection, MerchantTranslationDirectionSchema, MerchantTranslationReconciliationRequestContract, MerchantTranslationReconciliationRequestContractSchema, MerchantTranslationReconciliationScope, MerchantTranslationReconciliationScopeSchema, MerchantTranslationReconciliationStatus, MerchantTranslationReconciliationStatusSchema, PLATFORM_SUPPORT_LANGUAGE_TAG, TranslatedSupportBodySchema, TranslationBatchPollJob, TranslationBatchPollJobSchema, TranslationBatchResultsJob, TranslationBatchResultsJobSchema, TranslationBatchSubmitJob, TranslationBatchSubmitJobSchema, TranslationDispatchJob, TranslationDispatchJobSchema, TranslationReconcileJob, TranslationReconcileJobSchema, countUnicodeGraphemes, requiresMerchantTranslation } from './merchant-communications.js';
|
|
4
4
|
export { EffectiveRecoveryPolicy, EffectiveRecoveryPolicySchema, RECOVERY_OFFER_MODES, RecoveryOfferMode, RecoveryOfferModeSchema, parseEffectiveRecoveryPolicy, safeParseEffectiveRecoveryPolicy } from './recovery-policy.js';
|
|
5
5
|
export { CheckoutCreatedPayloadV2, CheckoutCreatedPayloadV2Schema, CheckoutObservedPayload, CheckoutObservedPayloadSchema, CheckoutUpdatedPayloadV2, CheckoutUpdatedPayloadV2Schema, CheckoutUpdatedPayloadV3, CheckoutUpdatedPayloadV3Schema, MAX_LINE_ITEMS, MAX_URL_LENGTH, OrderCompletedPayload, OrderCompletedPayloadSchema, OrderCompletedPayloadV2, OrderCompletedPayloadV2Schema, SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION, SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V1, SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V2, SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V3, SHOPIFY_COMMERCE_EVENT_TYPES, SHOPIFY_DISCOUNT_SYNC_REASONS, SHOPIFY_DISCOUNT_SYNC_WEBHOOK_TOPICS, SHOPIFY_RECOVERY_EVENT_TYPES_V2, SHOPIFY_RECOVERY_EVENT_TYPES_V3, SHOPIFY_WEBHOOK_OUTBOX_DESTINATIONS, SHOPIFY_WEBHOOK_QUEUE_CONTRACTS, ShopifyCartActivityEventV2, ShopifyCartActivityEventV2Schema, ShopifyCartActivityPayloadV2, ShopifyCartActivityPayloadV2Schema, ShopifyCheckoutCreatedEventV2, ShopifyCheckoutCreatedEventV2Schema, ShopifyCheckoutLineItem, ShopifyCheckoutLineItemSchema, ShopifyCheckoutLineItemsSchema, ShopifyCheckoutObservedEvent, ShopifyCheckoutObservedEventSchema, ShopifyCheckoutUpdatedEventV2, ShopifyCheckoutUpdatedEventV2Schema, ShopifyCheckoutUpdatedEventV3, ShopifyCheckoutUpdatedEventV3Schema, ShopifyCommerceEvent, ShopifyCommerceEventSchema, ShopifyCommerceEventType, ShopifyCustomerReference, ShopifyCustomerReferenceSchema, ShopifyDiscountSyncJob, ShopifyDiscountSyncJobSchema, ShopifyDiscountSyncReasonSchema, ShopifyDiscountSyncWebhookTopicSchema, ShopifyMoney, ShopifyMoneySchema, ShopifyOrderCompletedEvent, ShopifyOrderCompletedEventSchema, ShopifyOrderCompletedEventV2, ShopifyOrderCompletedEventV2Schema, ShopifyRecoveryEvent, ShopifyRecoveryEventSchema, ShopifyRecoveryEventTypeV2, ShopifyRecoveryEventV2, ShopifyRecoveryEventV2Schema, ShopifyTenant, ShopifyTenantSchema, createShopifyCartActivityOrderingKey, createShopifyCommerceOrderingKey, createShopifyOrderCorrelationOrderingKey, createShopifyOrderOrderingKey, createShopifyPendingRecoveryOrderingKey, isCartActivityEventV2, isCheckoutCreatedEventV2, isCheckoutObservedEvent, isCheckoutUpdatedEventV2, isOrderCompletedEvent, isOrderCompletedEventV2, parseShopifyCommerceEvent, parseShopifyDiscountSyncJob, parseShopifyRecoveryEvent, parseShopifyRecoveryEventV2, safeParseShopifyCommerceEvent, safeParseShopifyDiscountSyncJob, safeParseShopifyRecoveryEvent, safeParseShopifyRecoveryEventV2 } from './shopify/index.js';
|
|
6
|
+
export { S as SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION, a as SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC, b as ShopifyWhatsAppConsentSyncJobV1, c as ShopifyWhatsAppConsentSyncJobV1Schema, p as parseShopifyWhatsAppConsentSyncJobV1, s as safeParseShopifyWhatsAppConsentSyncJobV1 } from './whatsapp-consent-sync.schema-DEU84xTL.js';
|
|
6
7
|
import 'zod';
|
|
7
8
|
import 'intl-messageformat';
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,15 @@ import {
|
|
|
56
56
|
safeParseShopifyDiscountSyncJob,
|
|
57
57
|
safeParseShopifyRecoveryEvent,
|
|
58
58
|
safeParseShopifyRecoveryEventV2
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-NHIGHAEQ.js";
|
|
60
|
+
import {
|
|
61
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION,
|
|
62
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC,
|
|
63
|
+
ShopifyWhatsAppConsentSyncJobV1Schema,
|
|
64
|
+
parseShopifyWhatsAppConsentSyncJobV1,
|
|
65
|
+
safeParseShopifyWhatsAppConsentSyncJobV1
|
|
66
|
+
} from "./chunk-G7FCWVBU.js";
|
|
67
|
+
import "./chunk-QG53EVA6.js";
|
|
60
68
|
import {
|
|
61
69
|
APP_PRICING_BILLING_PERIOD_DRAIN_WINDOW_MS,
|
|
62
70
|
ARCH007_BILLING_CONTRACT_SCHEMA_VERSION,
|
|
@@ -204,6 +212,8 @@ export {
|
|
|
204
212
|
SHOPIFY_RECOVERY_EVENT_TYPES_V3,
|
|
205
213
|
SHOPIFY_WEBHOOK_OUTBOX_DESTINATIONS,
|
|
206
214
|
SHOPIFY_WEBHOOK_QUEUE_CONTRACTS,
|
|
215
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION,
|
|
216
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC,
|
|
207
217
|
ShopifyCartActivityEventV2Schema,
|
|
208
218
|
ShopifyCartActivityPayloadV2Schema,
|
|
209
219
|
ShopifyCheckoutCreatedEventV2Schema,
|
|
@@ -223,6 +233,7 @@ export {
|
|
|
223
233
|
ShopifyRecoveryEventSchema,
|
|
224
234
|
ShopifyRecoveryEventV2Schema,
|
|
225
235
|
ShopifyTenantSchema,
|
|
236
|
+
ShopifyWhatsAppConsentSyncJobV1Schema,
|
|
226
237
|
TimeZoneIdSchema,
|
|
227
238
|
TranslatedSupportBodySchema,
|
|
228
239
|
TranslationBatchPollJobSchema,
|
|
@@ -268,6 +279,7 @@ export {
|
|
|
268
279
|
parseShopifyDiscountSyncJob,
|
|
269
280
|
parseShopifyRecoveryEvent,
|
|
270
281
|
parseShopifyRecoveryEventV2,
|
|
282
|
+
parseShopifyWhatsAppConsentSyncJobV1,
|
|
271
283
|
requiresMerchantTranslation,
|
|
272
284
|
resolveLocaleDirection,
|
|
273
285
|
resolveModaConfigurationLocale,
|
|
@@ -278,6 +290,7 @@ export {
|
|
|
278
290
|
safeParseShopifyDiscountSyncJob,
|
|
279
291
|
safeParseShopifyRecoveryEvent,
|
|
280
292
|
safeParseShopifyRecoveryEventV2,
|
|
293
|
+
safeParseShopifyWhatsAppConsentSyncJobV1,
|
|
281
294
|
validateIcuCatalogue
|
|
282
295
|
};
|
|
283
296
|
//# sourceMappingURL=index.js.map
|
package/dist/shopify/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
export { S as SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION, a as SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC, b as ShopifyWhatsAppConsentSyncJobV1, c as ShopifyWhatsAppConsentSyncJobV1Schema, p as parseShopifyWhatsAppConsentSyncJobV1, s as safeParseShopifyWhatsAppConsentSyncJobV1 } from '../whatsapp-consent-sync.schema-DEU84xTL.js';
|
|
2
3
|
|
|
3
4
|
declare const SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V1: 1;
|
|
4
5
|
declare const SHOPIFY_COMMERCE_EVENT_SCHEMA_VERSION_V2: 2;
|
|
@@ -1154,6 +1155,10 @@ declare const SHOPIFY_WEBHOOK_QUEUE_CONTRACTS: {
|
|
|
1154
1155
|
readonly queueName: "order-events";
|
|
1155
1156
|
readonly jobName: "order-completed";
|
|
1156
1157
|
};
|
|
1158
|
+
readonly WHATSAPP_CONSENT_SYNC: {
|
|
1159
|
+
readonly queueName: "checkout-events";
|
|
1160
|
+
readonly jobName: "shopify-whatsapp-consent-sync";
|
|
1161
|
+
};
|
|
1157
1162
|
readonly SHOPIFY_DISCOUNT_SYNC: {
|
|
1158
1163
|
readonly queueName: "shopify-discount-sync";
|
|
1159
1164
|
readonly jobName: "reconcile-shopify-discounts";
|
package/dist/shopify/index.js
CHANGED
|
@@ -56,7 +56,15 @@ import {
|
|
|
56
56
|
safeParseShopifyDiscountSyncJob,
|
|
57
57
|
safeParseShopifyRecoveryEvent,
|
|
58
58
|
safeParseShopifyRecoveryEventV2
|
|
59
|
-
} from "../chunk-
|
|
59
|
+
} from "../chunk-NHIGHAEQ.js";
|
|
60
|
+
import {
|
|
61
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION,
|
|
62
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC,
|
|
63
|
+
ShopifyWhatsAppConsentSyncJobV1Schema,
|
|
64
|
+
parseShopifyWhatsAppConsentSyncJobV1,
|
|
65
|
+
safeParseShopifyWhatsAppConsentSyncJobV1
|
|
66
|
+
} from "../chunk-G7FCWVBU.js";
|
|
67
|
+
import "../chunk-QG53EVA6.js";
|
|
60
68
|
import "../chunk-4KSODB2R.js";
|
|
61
69
|
export {
|
|
62
70
|
CheckoutCreatedPayloadV2Schema,
|
|
@@ -78,6 +86,8 @@ export {
|
|
|
78
86
|
SHOPIFY_RECOVERY_EVENT_TYPES_V3,
|
|
79
87
|
SHOPIFY_WEBHOOK_OUTBOX_DESTINATIONS,
|
|
80
88
|
SHOPIFY_WEBHOOK_QUEUE_CONTRACTS,
|
|
89
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION,
|
|
90
|
+
SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC,
|
|
81
91
|
ShopifyCartActivityEventV2Schema,
|
|
82
92
|
ShopifyCartActivityPayloadV2Schema,
|
|
83
93
|
ShopifyCheckoutCreatedEventV2Schema,
|
|
@@ -97,6 +107,7 @@ export {
|
|
|
97
107
|
ShopifyRecoveryEventSchema,
|
|
98
108
|
ShopifyRecoveryEventV2Schema,
|
|
99
109
|
ShopifyTenantSchema,
|
|
110
|
+
ShopifyWhatsAppConsentSyncJobV1Schema,
|
|
100
111
|
createShopifyCartActivityOrderingKey,
|
|
101
112
|
createShopifyCommerceOrderingKey,
|
|
102
113
|
createShopifyOrderCorrelationOrderingKey,
|
|
@@ -112,9 +123,11 @@ export {
|
|
|
112
123
|
parseShopifyDiscountSyncJob,
|
|
113
124
|
parseShopifyRecoveryEvent,
|
|
114
125
|
parseShopifyRecoveryEventV2,
|
|
126
|
+
parseShopifyWhatsAppConsentSyncJobV1,
|
|
115
127
|
safeParseShopifyCommerceEvent,
|
|
116
128
|
safeParseShopifyDiscountSyncJob,
|
|
117
129
|
safeParseShopifyRecoveryEvent,
|
|
118
|
-
safeParseShopifyRecoveryEventV2
|
|
130
|
+
safeParseShopifyRecoveryEventV2,
|
|
131
|
+
safeParseShopifyWhatsAppConsentSyncJobV1
|
|
119
132
|
};
|
|
120
133
|
//# sourceMappingURL=index.js.map
|
package/dist/shopify/node.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { b as ShopifyWhatsAppConsentSyncJobV1 } from '../whatsapp-consent-sync.schema-DEU84xTL.js';
|
|
2
|
+
import 'zod';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Legacy webhook delivery job id used by earlier receipt/outbox flows.
|
|
3
6
|
* Prefer the resource-scoped helpers below for current queue publication.
|
|
@@ -12,5 +15,11 @@ declare function createShopifyDiscountSyncJobId(event: {
|
|
|
12
15
|
requestedAt: string;
|
|
13
16
|
deliveryId?: string | null;
|
|
14
17
|
}): string;
|
|
18
|
+
/**
|
|
19
|
+
* One deduplication identity per authenticated shop + webhook delivery.
|
|
20
|
+
* Length-safe JSON tuple serialization avoids `a:b` vs `a`/`b:c` collisions;
|
|
21
|
+
* the job ID never reveals the customer ID, delivery ID or phone.
|
|
22
|
+
*/
|
|
23
|
+
declare function createShopifyWhatsAppConsentSyncJobId(input: ShopifyWhatsAppConsentSyncJobV1): string;
|
|
15
24
|
|
|
16
|
-
export { createPendingRecoveryCandidateJobId, createShopifyCheckoutJobId, createShopifyDiscountSyncJobId, createShopifyOrderJobId, createShopifyWebhookJobId };
|
|
25
|
+
export { createPendingRecoveryCandidateJobId, createShopifyCheckoutJobId, createShopifyDiscountSyncJobId, createShopifyOrderJobId, createShopifyWebhookJobId, createShopifyWhatsAppConsentSyncJobId };
|
package/dist/shopify/node.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ShopifyWhatsAppConsentSyncJobV1Schema
|
|
3
|
+
} from "../chunk-G7FCWVBU.js";
|
|
4
|
+
import "../chunk-QG53EVA6.js";
|
|
5
|
+
|
|
1
6
|
// src/shopify/node.ts
|
|
2
7
|
import { createHash } from "crypto";
|
|
3
8
|
function createShopifyWebhookJobDigest(prefix, first, second) {
|
|
@@ -37,11 +42,17 @@ function createShopifyDiscountSyncJobId(event) {
|
|
|
37
42
|
}
|
|
38
43
|
return createShopifyWebhookJobDigest("discount-sync", `${event.shopId}:${event.reason}`, event.requestedAt);
|
|
39
44
|
}
|
|
45
|
+
function createShopifyWhatsAppConsentSyncJobId(input) {
|
|
46
|
+
const job = ShopifyWhatsAppConsentSyncJobV1Schema.parse(input);
|
|
47
|
+
const digest = createHash("sha256").update(JSON.stringify([job.shopId, job.deliveryId])).digest("hex");
|
|
48
|
+
return `shopify-whatsapp-consent-sync-${digest}`;
|
|
49
|
+
}
|
|
40
50
|
export {
|
|
41
51
|
createPendingRecoveryCandidateJobId,
|
|
42
52
|
createShopifyCheckoutJobId,
|
|
43
53
|
createShopifyDiscountSyncJobId,
|
|
44
54
|
createShopifyOrderJobId,
|
|
45
|
-
createShopifyWebhookJobId
|
|
55
|
+
createShopifyWebhookJobId,
|
|
56
|
+
createShopifyWhatsAppConsentSyncJobId
|
|
46
57
|
};
|
|
47
58
|
//# sourceMappingURL=node.js.map
|
package/dist/shopify/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shopify/node.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\nfunction createShopifyWebhookJobDigest(prefix: string, first: string, second: string): string {\n if (typeof first !== \"string\" || first.length === 0) {\n throw new Error(`${prefix}: first input must be a non-empty string`);\n }\n\n if (typeof second !== \"string\" || second.length === 0) {\n throw new Error(`${prefix}: second input must be a non-empty string`);\n }\n\n const digest = createHash(\"sha256\").update(`${first}:${second}`).digest(\"hex\");\n return `${prefix}-${digest}`;\n}\n\n/**\n * Legacy webhook delivery job id used by earlier receipt/outbox flows.\n * Prefer the resource-scoped helpers below for current queue publication.\n */\nexport function createShopifyWebhookJobId(appKey: string, deliveryId: string): string {\n return createShopifyWebhookJobDigest(\"shopify\", appKey, deliveryId);\n}\n\nexport function createShopifyCheckoutJobId(shopId: string, checkoutToken: string): string {\n return createShopifyWebhookJobDigest(\"checkout\", shopId, checkoutToken);\n}\n\nexport function createShopifyOrderJobId(shopId: string, orderGid: string): string {\n return createShopifyWebhookJobDigest(\"order-created\", shopId, orderGid);\n}\n\nexport function createPendingRecoveryCandidateJobId(\n shopId: string,\n checkoutToken: string,\n): string {\n return createShopifyWebhookJobDigest(\"pending-recovery\", shopId, checkoutToken);\n}\n\nexport function createShopifyDiscountSyncJobId(event: {\n shopId: string;\n reason: \"SUBSCRIPTION_ACTIVATED\" | \"REINSTALL_RECONCILED\" | \"SCOPES_UPDATED\" | \"ADMIN_REQUESTED\" | \"DISCOUNT_WEBHOOK\";\n requestedAt: string;\n deliveryId?: string | null;\n}): string {\n if (typeof event.shopId !== \"string\" || event.shopId.trim().length === 0) {\n throw new Error(\"createShopifyDiscountSyncJobId: shopId must be a non-empty string\");\n }\n\n if (event.reason === \"DISCOUNT_WEBHOOK\") {\n if (typeof event.deliveryId !== \"string\" || event.deliveryId.trim().length === 0) {\n throw new Error(\"createShopifyDiscountSyncJobId: deliveryId is required for DISCOUNT_WEBHOOK\");\n }\n return createShopifyWebhookJobDigest(\"discount-sync\", event.shopId, event.deliveryId);\n }\n\n if (typeof event.requestedAt !== \"string\" || event.requestedAt.trim().length === 0) {\n throw new Error(\"createShopifyDiscountSyncJobId: requestedAt must be a non-empty ISO datetime string\");\n }\n\n return createShopifyWebhookJobDigest(\"discount-sync\", `${event.shopId}:${event.reason}`, event.requestedAt);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/shopify/node.ts"],"sourcesContent":["import { ShopifyWhatsAppConsentSyncJobV1Schema, type ShopifyWhatsAppConsentSyncJobV1 } from \"./whatsapp-consent-sync.schema.js\";\nimport { createHash } from \"node:crypto\";\n\nfunction createShopifyWebhookJobDigest(prefix: string, first: string, second: string): string {\n if (typeof first !== \"string\" || first.length === 0) {\n throw new Error(`${prefix}: first input must be a non-empty string`);\n }\n\n if (typeof second !== \"string\" || second.length === 0) {\n throw new Error(`${prefix}: second input must be a non-empty string`);\n }\n\n const digest = createHash(\"sha256\").update(`${first}:${second}`).digest(\"hex\");\n return `${prefix}-${digest}`;\n}\n\n/**\n * Legacy webhook delivery job id used by earlier receipt/outbox flows.\n * Prefer the resource-scoped helpers below for current queue publication.\n */\nexport function createShopifyWebhookJobId(appKey: string, deliveryId: string): string {\n return createShopifyWebhookJobDigest(\"shopify\", appKey, deliveryId);\n}\n\nexport function createShopifyCheckoutJobId(shopId: string, checkoutToken: string): string {\n return createShopifyWebhookJobDigest(\"checkout\", shopId, checkoutToken);\n}\n\nexport function createShopifyOrderJobId(shopId: string, orderGid: string): string {\n return createShopifyWebhookJobDigest(\"order-created\", shopId, orderGid);\n}\n\nexport function createPendingRecoveryCandidateJobId(\n shopId: string,\n checkoutToken: string,\n): string {\n return createShopifyWebhookJobDigest(\"pending-recovery\", shopId, checkoutToken);\n}\n\nexport function createShopifyDiscountSyncJobId(event: {\n shopId: string;\n reason: \"SUBSCRIPTION_ACTIVATED\" | \"REINSTALL_RECONCILED\" | \"SCOPES_UPDATED\" | \"ADMIN_REQUESTED\" | \"DISCOUNT_WEBHOOK\";\n requestedAt: string;\n deliveryId?: string | null;\n}): string {\n if (typeof event.shopId !== \"string\" || event.shopId.trim().length === 0) {\n throw new Error(\"createShopifyDiscountSyncJobId: shopId must be a non-empty string\");\n }\n\n if (event.reason === \"DISCOUNT_WEBHOOK\") {\n if (typeof event.deliveryId !== \"string\" || event.deliveryId.trim().length === 0) {\n throw new Error(\"createShopifyDiscountSyncJobId: deliveryId is required for DISCOUNT_WEBHOOK\");\n }\n return createShopifyWebhookJobDigest(\"discount-sync\", event.shopId, event.deliveryId);\n }\n\n if (typeof event.requestedAt !== \"string\" || event.requestedAt.trim().length === 0) {\n throw new Error(\"createShopifyDiscountSyncJobId: requestedAt must be a non-empty ISO datetime string\");\n }\n\n return createShopifyWebhookJobDigest(\"discount-sync\", `${event.shopId}:${event.reason}`, event.requestedAt);\n}\n\n/**\n * One deduplication identity per authenticated shop + webhook delivery.\n * Length-safe JSON tuple serialization avoids `a:b` vs `a`/`b:c` collisions;\n * the job ID never reveals the customer ID, delivery ID or phone.\n */\nexport function createShopifyWhatsAppConsentSyncJobId(\n input: ShopifyWhatsAppConsentSyncJobV1,\n): string {\n const job = ShopifyWhatsAppConsentSyncJobV1Schema.parse(input);\n const digest = createHash(\"sha256\")\n .update(JSON.stringify([job.shopId, job.deliveryId]))\n .digest(\"hex\");\n return `shopify-whatsapp-consent-sync-${digest}`;\n}\n"],"mappings":";;;;;;AACA,SAAS,kBAAkB;AAE3B,SAAS,8BAA8B,QAAgB,OAAe,QAAwB;AAC5F,MAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAAG;AACnD,UAAM,IAAI,MAAM,GAAG,MAAM,0CAA0C;AAAA,EACrE;AAEA,MAAI,OAAO,WAAW,YAAY,OAAO,WAAW,GAAG;AACrD,UAAM,IAAI,MAAM,GAAG,MAAM,2CAA2C;AAAA,EACtE;AAEA,QAAM,SAAS,WAAW,QAAQ,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,EAAE,EAAE,OAAO,KAAK;AAC7E,SAAO,GAAG,MAAM,IAAI,MAAM;AAC5B;AAMO,SAAS,0BAA0B,QAAgB,YAA4B;AACpF,SAAO,8BAA8B,WAAW,QAAQ,UAAU;AACpE;AAEO,SAAS,2BAA2B,QAAgB,eAA+B;AACxF,SAAO,8BAA8B,YAAY,QAAQ,aAAa;AACxE;AAEO,SAAS,wBAAwB,QAAgB,UAA0B;AAChF,SAAO,8BAA8B,iBAAiB,QAAQ,QAAQ;AACxE;AAEO,SAAS,oCACd,QACA,eACQ;AACR,SAAO,8BAA8B,oBAAoB,QAAQ,aAAa;AAChF;AAEO,SAAS,+BAA+B,OAKpC;AACT,MAAI,OAAO,MAAM,WAAW,YAAY,MAAM,OAAO,KAAK,EAAE,WAAW,GAAG;AACxE,UAAM,IAAI,MAAM,mEAAmE;AAAA,EACrF;AAEA,MAAI,MAAM,WAAW,oBAAoB;AACvC,QAAI,OAAO,MAAM,eAAe,YAAY,MAAM,WAAW,KAAK,EAAE,WAAW,GAAG;AAChF,YAAM,IAAI,MAAM,6EAA6E;AAAA,IAC/F;AACA,WAAO,8BAA8B,iBAAiB,MAAM,QAAQ,MAAM,UAAU;AAAA,EACtF;AAEA,MAAI,OAAO,MAAM,gBAAgB,YAAY,MAAM,YAAY,KAAK,EAAE,WAAW,GAAG;AAClF,UAAM,IAAI,MAAM,qFAAqF;AAAA,EACvG;AAEA,SAAO,8BAA8B,iBAAiB,GAAG,MAAM,MAAM,IAAI,MAAM,MAAM,IAAI,MAAM,WAAW;AAC5G;AAOO,SAAS,sCACd,OACQ;AACR,QAAM,MAAM,sCAAsC,MAAM,KAAK;AAC7D,QAAM,SAAS,WAAW,QAAQ,EAC/B,OAAO,KAAK,UAAU,CAAC,IAAI,QAAQ,IAAI,UAAU,CAAC,CAAC,EACnD,OAAO,KAAK;AACf,SAAO,iCAAiC,MAAM;AAChD;","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION: 1;
|
|
4
|
+
declare const SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC: "customers_whats_app_marketing_consent/update";
|
|
5
|
+
/**
|
|
6
|
+
* A webhook notification to re-read CURRENT Shopify consent in Background.
|
|
7
|
+
* These fields contain no consent decision, phone, checkout or recovery data;
|
|
8
|
+
* parsing this job never authorises a recovery or an outbound message.
|
|
9
|
+
*/
|
|
10
|
+
declare const ShopifyWhatsAppConsentSyncJobV1Schema: z.ZodObject<{
|
|
11
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
12
|
+
shopId: z.ZodString;
|
|
13
|
+
shopifyCustomerId: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
14
|
+
deliveryId: z.ZodString;
|
|
15
|
+
triggeredAt: z.ZodISODateTime;
|
|
16
|
+
}, z.core.$strict>;
|
|
17
|
+
type ShopifyWhatsAppConsentSyncJobV1 = z.infer<typeof ShopifyWhatsAppConsentSyncJobV1Schema>;
|
|
18
|
+
declare function parseShopifyWhatsAppConsentSyncJobV1(input: unknown): ShopifyWhatsAppConsentSyncJobV1;
|
|
19
|
+
declare function safeParseShopifyWhatsAppConsentSyncJobV1(input: unknown): z.ZodSafeParseResult<{
|
|
20
|
+
schemaVersion: 1;
|
|
21
|
+
shopId: string;
|
|
22
|
+
shopifyCustomerId: string;
|
|
23
|
+
deliveryId: string;
|
|
24
|
+
triggeredAt: string;
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
export { SHOPIFY_WHATSAPP_CONSENT_SYNC_SCHEMA_VERSION as S, SHOPIFY_WHATSAPP_CONSENT_SYNC_TOPIC as a, type ShopifyWhatsAppConsentSyncJobV1 as b, ShopifyWhatsAppConsentSyncJobV1Schema as c, parseShopifyWhatsAppConsentSyncJobV1 as p, safeParseShopifyWhatsAppConsentSyncJobV1 as s };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"description": "Portable exact-recipient and Shopify Customer ID golden vectors. Illustrative values for pure validation tests only; never send or call them. Equivalence does not imply consent, reachability or ownership.",
|
|
4
|
+
"phoneCases": [
|
|
5
|
+
{ "id": "gb-national", "input": "07400 123456", "verifiedCountryCode": "GB", "e164": "+447400123456" },
|
|
6
|
+
{ "id": "gb-international", "input": "+44 7400 123456", "verifiedCountryCode": null, "e164": "+447400123456" },
|
|
7
|
+
{ "id": "gb-compact-international", "input": "+447400123456", "verifiedCountryCode": "GB", "e164": "+447400123456" },
|
|
8
|
+
{ "id": "gb-reserved-fictional", "input": "+44 7700 900123", "verifiedCountryCode": null, "reject": "INVALID_NUMBER" },
|
|
9
|
+
{ "id": "us-national", "input": "(213) 373-4253", "verifiedCountryCode": "US", "e164": "+12133734253" },
|
|
10
|
+
{ "id": "us-international", "input": "+1 213 373 4253", "verifiedCountryCode": null, "e164": "+12133734253" },
|
|
11
|
+
{ "id": "fr-national", "input": "06 12 34 56 78", "verifiedCountryCode": "FR", "e164": "+33612345678" },
|
|
12
|
+
{ "id": "fr-international", "input": "+33 6 12 34 56 78", "verifiedCountryCode": null, "e164": "+33612345678" },
|
|
13
|
+
{ "id": "national-country-unknown", "input": "07400 123456", "verifiedCountryCode": null, "reject": "COUNTRY_REQUIRED" },
|
|
14
|
+
{ "id": "unverified-country-code", "input": "07400 123456", "verifiedCountryCode": "ZZ", "reject": "UNSUPPORTED_COUNTRY" },
|
|
15
|
+
{ "id": "foreign-context", "input": "+44 7400 123456", "verifiedCountryCode": "US", "reject": "COUNTRY_MISMATCH" },
|
|
16
|
+
{ "id": "extension", "input": "07400 123456 ext. 42", "verifiedCountryCode": "GB", "reject": "EXTENSION_UNSUPPORTED" },
|
|
17
|
+
{ "id": "extension-hash", "input": "+447400123456#42", "verifiedCountryCode": null, "reject": "EXTENSION_UNSUPPORTED" },
|
|
18
|
+
{ "id": "ambiguous-short-number", "input": "900123", "verifiedCountryCode": "GB", "reject": "INVALID_NUMBER" },
|
|
19
|
+
{ "id": "invalid-international-country", "input": "+999123456789", "verifiedCountryCode": null, "reject": "INVALID_NUMBER" },
|
|
20
|
+
{ "id": "embedded-free-text", "input": "Call +447400123456", "verifiedCountryCode": null, "reject": "INVALID_FORMAT" }
|
|
21
|
+
],
|
|
22
|
+
"shopifyCustomerCases": [
|
|
23
|
+
{ "id": "numeric-webhook-number", "input": 123456789, "numericId": "123456789", "gid": "gid://shopify/Customer/123456789" },
|
|
24
|
+
{ "id": "numeric-webhook-string", "input": "123456789", "numericId": "123456789", "gid": "gid://shopify/Customer/123456789" },
|
|
25
|
+
{ "id": "graphql-customer-gid", "input": "gid://shopify/Customer/123456789", "numericId": "123456789", "gid": "gid://shopify/Customer/123456789" },
|
|
26
|
+
{ "id": "64bit-boundary-string", "input": "9223372036854775807", "numericId": "9223372036854775807", "gid": "gid://shopify/Customer/9223372036854775807" }
|
|
27
|
+
],
|
|
28
|
+
"rejectedShopifyCustomerCases": [
|
|
29
|
+
{ "id": "wrong-resource", "input": "gid://shopify/Order/123", "code": "INVALID_CUSTOMER_ID" },
|
|
30
|
+
{ "id": "missing-resource-id", "input": "gid://shopify/Customer/", "code": "INVALID_CUSTOMER_ID" },
|
|
31
|
+
{ "id": "zero", "input": "0", "code": "INVALID_CUSTOMER_ID" },
|
|
32
|
+
{ "id": "leading-zeroes", "input": "000123", "code": "INVALID_CUSTOMER_ID" },
|
|
33
|
+
{ "id": "query-suffix", "input": "gid://shopify/Customer/123?shop=other", "code": "INVALID_CUSTOMER_ID" },
|
|
34
|
+
{ "id": "principal-string", "input": "shop_user_123", "code": "INVALID_CUSTOMER_ID" },
|
|
35
|
+
{ "id": "negative", "input": -1, "code": "INVALID_CUSTOMER_ID" },
|
|
36
|
+
{ "id": "unsafe-js-number", "input": 9007199254740992, "code": "INVALID_CUSTOMER_ID" },
|
|
37
|
+
{ "id": "overflow", "input": "9223372036854775808", "code": "CUSTOMER_ID_OUT_OF_RANGE" }
|
|
38
|
+
],
|
|
39
|
+
"shopifyScopeCases": [
|
|
40
|
+
{ "id": "numeric-gid-same-shop", "shopA": "shop-a", "shopB": "shop-a", "customerA": "123456789", "customerB": "gid://shopify/Customer/123456789", "equal": true },
|
|
41
|
+
{ "id": "same-id-different-shop", "shopA": "shop-a", "shopB": "shop-b", "customerA": "123456789", "customerB": "gid://shopify/Customer/123456789", "equal": false },
|
|
42
|
+
{ "id": "different-customer", "shopA": "shop-a", "shopB": "shop-a", "customerA": "123456789", "customerB": "123456790", "equal": false }
|
|
43
|
+
],
|
|
44
|
+
"recipientLifecycleCases": [
|
|
45
|
+
{ "id": "number-rotated", "before": "+447400123456", "after": "+447400123457", "sameNumber": false, "grantCarriesAcrossPrincipal": false },
|
|
46
|
+
{ "id": "reassigned-to-other-person", "before": "+447400123456", "after": "+447400123456", "sameNumber": true, "grantCarriesAcrossPrincipal": false }
|
|
47
|
+
]
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modainteract/moda-interact-shared",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
"types": "./dist/testing/node.d.ts",
|
|
41
41
|
"import": "./dist/testing/node.js"
|
|
42
42
|
},
|
|
43
|
+
"./identity": {
|
|
44
|
+
"types": "./dist/identity/index.d.ts",
|
|
45
|
+
"import": "./dist/identity/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./identity/fixtures": "./fixtures/identity-v1.json",
|
|
43
48
|
"./shopify": {
|
|
44
49
|
"types": "./dist/shopify/index.d.ts",
|
|
45
50
|
"import": "./dist/shopify/index.js"
|
|
@@ -106,7 +111,8 @@
|
|
|
106
111
|
}
|
|
107
112
|
},
|
|
108
113
|
"files": [
|
|
109
|
-
"dist"
|
|
114
|
+
"dist",
|
|
115
|
+
"fixtures/identity-v1.json"
|
|
110
116
|
],
|
|
111
117
|
"scripts": {
|
|
112
118
|
"build": "tsup",
|
|
@@ -142,6 +148,7 @@
|
|
|
142
148
|
"@prisma/instrumentation": "^6.19.3",
|
|
143
149
|
"bullmq-otel": "^2.0.1",
|
|
144
150
|
"intl-messageformat": "^10.7.18",
|
|
151
|
+
"libphonenumber-js": "1.12.30",
|
|
145
152
|
"semver": "7.8.5",
|
|
146
153
|
"winston": "3.19.0",
|
|
147
154
|
"winston-loki": "6.1.7",
|