@modainteract/moda-interact-shared 2.4.1 → 2.4.3

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.
@@ -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;
@@ -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
- // Deliberately no arbitrary metadata bag, message, headers or exception object.
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":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,SAAS;;;ACiDX,SAAS,kCAAkC,SAI/C;AACD,SAAO;AAAA,IACL,MAAM,OAAO,KAAoD;AAC/D,UAAI;AACJ,UAAI;AACF,qBAAa,qCAAqC,GAAG;AAAA,MACvD,QAAQ;AAEN,gBAAQ,OAAO,MAAM,2CAA2C,CAAC,CAAC;AAClE,eAAO;AAAA,MACT;AACA,YAAM,cAAc,6BAA6B;AAAA,QAC/C,eAAe,WAAW;AAAA,QAAe,aAAa,WAAW;AAAA,QACjE,UAAU,WAAW;AAAA,QAAU,YAAY,WAAW;AAAA,QACtD,aAAa,WAAW;AAAA,QAAa,QAAQ,WAAW;AAAA,MAC1D,CAAC;AACD,UAAI;AACF,eAAO,MAAM,QAAQ,YAAY,kBAAkB,aAAa,OAAO,OAAO;AAC5E,gBAAM,UAAU,MAAM,GAAG,aAAa,EAAE,aAAa,WAAW,CAAC;AACjE,gBAAM,WAAW,MAAM,GAAG,sBAAsB,EAAE,aAAa,WAAW,CAAC;AAC3E,cAAI,CAAC,UAAU;AACb,mBAAO;AAAA,cAAE;AAAA,cAAa,UAAU;AAAA,cAAO,iBAAiB,QAAQ;AAAA,cAC9D,QAAQ,QAAQ;AAAA,YAAO;AAAA,UAC3B;AACA,gBAAM,UAAU,MAAM,GAAG,iBAAiB;AAAA,YAAE;AAAA,YAAa;AAAA,YACvD,QAAQ,QAAQ,WAAW;AAAA,YAAY,cAAc,QAAQ;AAAA,UAAa,CAAC;AAC7E,cAAI,QAAQ,iBAAiB,WAAW,WAAW,QAAQ,GAAG,mBAAmB;AAC/E,kBAAM,GAAG,kBAAkB,EAAE,aAAa,WAAW,CAAC;AAAA,UACxD;AACA,iBAAO;AAAA,YAAE;AAAA,YAAa,UAAU;AAAA,YAAM,iBAAiB,QAAQ;AAAA,YAC7D,QAAQ;AAAA,UAAgB;AAAA,QAC5B,CAAC;AAAA,MACH,QAAQ;AACN,gBAAQ,OAAO,MAAM,sCAAsC;AAAA,UACzD;AAAA,UAAa,eAAe,WAAW;AAAA,UACvC,aAAa,WAAW;AAAA,UAAa,cAAc,WAAW;AAAA,QAChE,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;;;AC/FA,SAAS,kBAAkB;AA8B3B,eAAe,KAAQ,IAAkC,cAAsB,QAAiC;AAC9G,SAAO,GAAG,gBAAqB,WAAW,GAAG,MAAM;AACrD;AAEA,SAAS,SAAS,KAAoC;AACpD,SAAO;AAAA,IAAE,aAAa,IAAI;AAAA,IAAa,QAAQ,IAAI;AAAA,IACjD,iBAAiB,IAAI;AAAA,IAAiB,gBAAgB,IAAI;AAAA,EAAe;AAC7E;AAEA,IAAM,wBAAN,MAA2D;AAAA,EAGzD,YAA6B,IACV,eAAkD;AADxC;AACV;AAAA,EAAmD;AAAA,EADzC;AAAA,EACV;AAAA,EAHX,aAA4B;AAAA,EAC5B,cAAc;AAAA,EAItB,MAAM,aAAa,EAAE,aAAa,WAAW,GAEa;AACxD,UAAM,WAAW,MAAM;AAAA,MAAkB,KAAK;AAAA,MAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOhD,WAAW;AAAA,MAAG;AAAA,MAAa,WAAW;AAAA,MAAe,WAAW;AAAA,MAChE,WAAW;AAAA,MAAU,WAAW;AAAA,MAAY,WAAW;AAAA,MACvD,WAAW;AAAA,MAAQ,WAAW;AAAA,MAAU,WAAW;AAAA,MAAY,WAAW;AAAA,IAAa;AACzF,SAAK,cAAc,SAAS,SAAS;AACrC,UAAM,QAAQ,MAAM;AAAA,MAAkB,KAAK;AAAA,MACzC;AAAA,MAAmF;AAAA,IAAW;AAChG,QAAI,MAAM,WAAW,EAAG,OAAM,IAAI,MAAM,qCAAqC;AAC7E,SAAK,aAAa,MAAM,CAAC,EAAG;AAC5B,WAAO,EAAE,GAAG,SAAS,MAAM,CAAC,CAAE,GAAG,cAAc,KAAK,YAAY;AAAA,EAClE;AAAA,EAEA,MAAM,sBAAsB,EAAE,aAAa,WAAW,GAEjC;AACnB,QAAI,CAAC,KAAK,WAAY,OAAM,IAAI,MAAM,qBAAqB;AAC3D,UAAM,UAAU,MAAM;AAAA,MAAqB,KAAK;AAAA,MAAI;AAAA;AAAA;AAAA;AAAA;AAAA,MAKlD,WAAW;AAAA,MAAc,KAAK;AAAA,MAAY,WAAW;AAAA,MAAe,WAAW;AAAA,IAAU;AAC3F,QAAI,CAAC,QAAQ,QAAQ;AACnB,UAAI,KAAK,YAAa,OAAM,IAAI,MAAM,iDAAiD;AACvF,YAAM,WAAW,MAAM,KAA8B,KAAK,IAAI;AAAA;AAAA;AAAA,kCAGlC,WAAW,YAAY;AACnD,UAAI,SAAS,WAAW,KAAK,SAAS,CAAC,EAAG,gBAAgB,aAAa;AACrE,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,iBAAiB,EAAE,aAAa,YAAY,QAAQ,aAAa,GAGzC;AAC5B,QAAI,CAAC,KAAK,WAAY,OAAM,IAAI,MAAM,qBAAqB;AAC3D,QAAI,QAAQ;AACV,YAAM;AAAA,QAAK,KAAK;AAAA,QAAI;AAAA;AAAA;AAAA,QAGlB,WAAW;AAAA,QAAG,KAAK;AAAA,QAAY,WAAW;AAAA,QAAe,WAAW;AAAA,MAAU;AAAA,IAClF;AACA,UAAM,UAAU,MAAM;AAAA,MAAkB,KAAK;AAAA,MAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASR;AAAA,MACvC,eAAe,IAAI;AAAA,MAAG,WAAW;AAAA,MAAY,WAAW;AAAA,MAAe,WAAW;AAAA,IAAQ;AAC5F,QAAI,QAAQ,WAAW,EAAG,OAAM,IAAI,MAAM,wBAAwB;AAClE,WAAO,SAAS,QAAQ,CAAC,CAAE;AAAA,EAC7B;AAAA,EAEA,MAAM,kBAAkB,OAA4F;AAClH,QAAI,KAAK,iBAAiB,KAAK,WAAY,OAAM,KAAK,cAAc;AAAA,MAClE,IAAI,KAAK;AAAA,MAAI,YAAY,KAAK;AAAA,MAC9B,aAAa,MAAM;AAAA,MAAa,YAAY,MAAM;AAAA,IACpD,CAAC;AAAA,EACH;AACF;AAEA,SAAS,UAAU,OAAyB;AAC1C,QAAM,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,QACjE,MAA6B,OAAO;AACzC,SAAO,SAAS,WAAW,SAAS,WAAW,SAAS;AAC1D;AAOO,SAAS,2CAA2C,SAInC;AACtB,QAAM,cAAc,QAAQ,eAAe;AAC3C,MAAI,CAAC,OAAO,UAAU,WAAW,KAAK,cAAc,KAAK,cAAc,IAAI;AACzE,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,SAAO;AAAA,IACL,MAAM,kBAAqB,KAAa,IAAyD;AAC/F,eAAS,UAAU,KAAK,WAAW;AACjC,YAAI;AACF,iBAAO,MAAM,QAAQ,OAAO,aAAa,OAAO,OAAO;AACrD,kBAAM,KAAK,IAAI,iFAAiF,GAAG;AACnG,mBAAO,GAAG,IAAI,sBAAsB,IAAI,QAAQ,aAAa,CAAC;AAAA,UAChE,GAAG,EAAE,gBAAgB,gBAAgB,SAAS,KAAM,CAAC;AAAA,QACvD,SAAS,OAAO;AACd,cAAI,WAAW,eAAe,CAAC,UAAU,KAAK,EAAG,OAAM;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AF1JO,IAAM,wCAAwC;AACrD,IAAM,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,MAAM,+BAA+B;AACnF,IAAM,kBAAkB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,+BAA+B;AACvF,IAAM,WAAW,EAAE,OAAO,EAAE,KAAK;AAC1B,IAAM,wCAAwC,EAAE,aAAa;AAAA,EAClE,iBAAiB,EAAE,QAAQ,qCAAqC;AAAA,EAChE,eAAe;AAAA,EACf,aAAa,EAAE,KAAK,CAAC,SAAS,eAAe,QAAQ,WAAW,YAAY,CAAC;AAAA,EAC7E,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC5C,UAAU,EAAE,KAAK,CAAC,OAAO,UAAU,QAAQ,UAAU,CAAC;AAAA,EACtD,cAAc;AAAA,EACd,eAAe;AAAA,EACf,YAAY,EAAE,IAAI,SAAS,EAAE,QAAQ,KAAK,CAAC;AAAA;AAE7C,CAAC;AAEM,SAAS,qCAAqC,OAAiD;AACpG,SAAO,sCAAsC,MAAM,KAAK;AAC1D;AACO,SAAS,6BAA6B,OACsD;AACjG,QAAM,SAAS,sCAAsC,KAAK;AAAA,IACxD,eAAe;AAAA,IAAM,aAAa;AAAA,IAAM,UAAU;AAAA,IAAM,YAAY;AAAA,IAAM,aAAa;AAAA,IAAM,QAAQ;AAAA,EACvG,CAAC,EAAE,MAAM,KAAK;AACd,SAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,UAAU;AAAA,IAChD;AAAA,IAA+B,OAAO;AAAA,IAAe,OAAO;AAAA,IAAa,OAAO;AAAA,IAChF,OAAO;AAAA,IAAY,OAAO;AAAA,IAAQ,OAAO;AAAA,EAC3C,CAAC,CAAC,EAAE,OAAO,KAAK;AAClB;","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":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,SAAS;;;ACiDX,SAAS,kCAAkC,SAI/C;AACD,SAAO;AAAA,IACL,MAAM,OAAO,KAAoD;AAC/D,UAAI;AACJ,UAAI;AACF,qBAAa,qCAAqC,GAAG;AAAA,MACvD,QAAQ;AAEN,gBAAQ,OAAO,MAAM,2CAA2C,CAAC,CAAC;AAClE,eAAO;AAAA,MACT;AACA,YAAM,cAAc,6BAA6B;AAAA,QAC/C,eAAe,WAAW;AAAA,QAAe,aAAa,WAAW;AAAA,QACjE,UAAU,WAAW;AAAA,QAAU,YAAY,WAAW;AAAA,QACtD,aAAa,WAAW;AAAA,QAAa,QAAQ,WAAW;AAAA,MAC1D,CAAC;AACD,UAAI;AACF,eAAO,MAAM,QAAQ,YAAY,kBAAkB,aAAa,OAAO,OAAO;AAC5E,gBAAM,UAAU,MAAM,GAAG,aAAa,EAAE,aAAa,WAAW,CAAC;AACjE,gBAAM,WAAW,MAAM,GAAG,sBAAsB,EAAE,aAAa,WAAW,CAAC;AAC3E,cAAI,CAAC,UAAU;AACb,mBAAO;AAAA,cAAE;AAAA,cAAa,UAAU;AAAA,cAAO,iBAAiB,QAAQ;AAAA,cAC9D,QAAQ,QAAQ;AAAA,YAAO;AAAA,UAC3B;AACA,gBAAM,UAAU,MAAM,GAAG,iBAAiB;AAAA,YAAE;AAAA,YAAa;AAAA,YACvD,QAAQ,QAAQ,WAAW;AAAA,YAAY,cAAc,QAAQ;AAAA,UAAa,CAAC;AAC7E,cAAI,QAAQ,iBAAiB,WAAW,WAAW,QAAQ,GAAG,mBAAmB;AAC/E,kBAAM,GAAG,kBAAkB,EAAE,aAAa,WAAW,CAAC;AAAA,UACxD;AACA,iBAAO;AAAA,YAAE;AAAA,YAAa,UAAU;AAAA,YAAM,iBAAiB,QAAQ;AAAA,YAC7D,QAAQ;AAAA,UAAgB;AAAA,QAC5B,CAAC;AAAA,MACH,QAAQ;AACN,gBAAQ,OAAO,MAAM,sCAAsC;AAAA,UACzD;AAAA,UAAa,eAAe,WAAW;AAAA,UACvC,aAAa,WAAW;AAAA,UAAa,cAAc,WAAW;AAAA,QAChE,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;;;AC/FA,SAAS,kBAAkB;AAC3B,SAAS,cAAc,aAAa;AA8BpC,eAAe,KAAQ,IAAkC,cAAsB,QAAiC;AAC9G,SAAO,GAAG,gBAAqB,WAAW,GAAG,MAAM;AACrD;AAEA,SAAS,SAAS,KAAoC;AACpD,SAAO;AAAA,IAAE,aAAa,IAAI;AAAA,IAAa,QAAQ,IAAI;AAAA,IACjD,iBAAiB,IAAI;AAAA,IAAiB,gBAAgB,IAAI;AAAA,EAAe;AAC7E;AAEA,IAAM,wBAAN,MAA2D;AAAA,EAGzD,YAA6B,IACV,eAAkD;AADxC;AACV;AAAA,EAAmD;AAAA,EADzC;AAAA,EACV;AAAA,EAHX,aAA4B;AAAA,EAC5B,cAAc;AAAA,EAItB,MAAM,aAAa,EAAE,aAAa,WAAW,GAEa;AACxD,UAAM,WAAW,MAAM;AAAA,MAAkB,KAAK;AAAA,MAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOhD,WAAW;AAAA,MAAG;AAAA,MAAa,WAAW;AAAA,MAAe,WAAW;AAAA,MAChE,WAAW;AAAA,MAAU,WAAW;AAAA,MAAY,WAAW;AAAA,MACvD,WAAW;AAAA,MAAQ,WAAW;AAAA,MAAU,WAAW;AAAA,MAAY,WAAW;AAAA,MAAe,WAAW,gBAAgB;AAAA,IAAI;AAC1H,SAAK,cAAc,SAAS,SAAS;AACrC,UAAM,QAAQ,MAAM;AAAA,MAAkB,KAAK;AAAA,MACzC;AAAA,MAAmF;AAAA,IAAW;AAChG,QAAI,MAAM,WAAW,EAAG,OAAM,IAAI,MAAM,qCAAqC;AAC7E,SAAK,aAAa,MAAM,CAAC,EAAG;AAC5B,WAAO,EAAE,GAAG,SAAS,MAAM,CAAC,CAAE,GAAG,cAAc,KAAK,YAAY;AAAA,EAClE;AAAA,EAEA,MAAM,sBAAsB,EAAE,aAAa,WAAW,GAEjC;AACnB,QAAI,CAAC,KAAK,WAAY,OAAM,IAAI,MAAM,qBAAqB;AAC3D,UAAM,UAAU,MAAM;AAAA,MAAqB,KAAK;AAAA,MAAI;AAAA;AAAA;AAAA;AAAA;AAAA,MAKlD,WAAW;AAAA,MAAc,KAAK;AAAA,MAAY,WAAW;AAAA,MAAe,WAAW;AAAA,MAAY,WAAW,gBAAgB;AAAA,IAAI;AAC5H,QAAI,CAAC,QAAQ,QAAQ;AACnB,UAAI,KAAK,YAAa,OAAM,IAAI,MAAM,iDAAiD;AACvF,YAAM,WAAW,MAAM,KAA8B,KAAK,IAAI;AAAA;AAAA;AAAA,kCAGlC,WAAW,YAAY;AACnD,UAAI,SAAS,WAAW,KAAK,SAAS,CAAC,EAAG,gBAAgB,aAAa;AACrE,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,iBAAiB,EAAE,aAAa,YAAY,QAAQ,aAAa,GAGzC;AAC5B,QAAI,CAAC,KAAK,WAAY,OAAM,IAAI,MAAM,qBAAqB;AAC3D,QAAI,QAAQ;AACV,YAAM;AAAA,QAAK,KAAK;AAAA,QAAI;AAAA;AAAA;AAAA,QAGlB,WAAW;AAAA,QAAG,KAAK;AAAA,QAAY,WAAW;AAAA,QAAe,WAAW;AAAA,MAAU;AAAA,IAClF;AACA,UAAM,UAAU,MAAM;AAAA,MAAkB,KAAK;AAAA,MAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWR;AAAA,MACvC,eAAe,IAAI;AAAA,MAAG,WAAW;AAAA,MAAY,WAAW;AAAA,MAAe,WAAW;AAAA,MAAU,WAAW,gBAAgB;AAAA,IAAI;AAC7H,QAAI,QAAQ,WAAW,EAAG,OAAM,IAAI,MAAM,wBAAwB;AAClE,WAAO,SAAS,QAAQ,CAAC,CAAE;AAAA,EAC7B;AAAA,EAEA,MAAM,kBAAkB,OAA4F;AAClH,QAAI,KAAK,iBAAiB,KAAK,WAAY,OAAM,KAAK,cAAc;AAAA,MAClE,IAAI,KAAK;AAAA,MAAI,YAAY,KAAK;AAAA,MAC9B,aAAa,MAAM;AAAA,MAAa,YAAY,MAAM;AAAA,IACpD,CAAC;AAAA,EACH;AACF;AAEA,SAAS,UAAU,OAAyB;AAC1C,QAAM,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,QACjE,MAA6B,OAAO;AACzC,SAAO,SAAS,WAAW,SAAS,WAAW,SAAS;AAC1D;AAOO,SAAS,2CAA2C,SAInC;AACtB,QAAM,cAAc,QAAQ,eAAe;AAC3C,MAAI,CAAC,OAAO,UAAU,WAAW,KAAK,cAAc,KAAK,cAAc,IAAI;AACzE,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,SAAO;AAAA,IACL,MAAM,kBAAqB,KAAa,IAAyD;AAC/F,eAAS,UAAU,KAAK,WAAW;AACjC,YAAI;AACF,iBAAO,MAAM,QAAQ,OAAO,aAAa,OAAO,OAAO;AACrD,kBAAM,KAAK,IAAI,iFAAiF,GAAG;AACnG,mBAAO,GAAG,IAAI,sBAAsB,IAAI,QAAQ,aAAa,CAAC;AAAA,UAChE,GAAG,EAAE,gBAAgB,gBAAgB,SAAS,KAAM,CAAC;AAAA,QACvD,SAAS,OAAO;AACd,cAAI,WAAW,eAAe,CAAC,UAAU,KAAK,EAAG,OAAM;AACvD,gBAAM,MAAM,UAAU,EAAE;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AF9JO,IAAM,wCAAwC;AACrD,IAAM,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,MAAM,+BAA+B;AACnF,IAAM,kBAAkB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,+BAA+B;AACvF,IAAM,WAAW,EAAE,OAAO,EAAE,KAAK;AAC1B,IAAM,wCAAwC,EAAE,aAAa;AAAA,EAClE,iBAAiB,EAAE,QAAQ,qCAAqC;AAAA,EAChE,eAAe;AAAA,EACf,aAAa,EAAE,KAAK,CAAC,SAAS,eAAe,QAAQ,WAAW,YAAY,CAAC;AAAA,EAC7E,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC5C,UAAU,EAAE,KAAK,CAAC,OAAO,UAAU,QAAQ,UAAU,CAAC;AAAA,EACtD,cAAc;AAAA,EACd,eAAe;AAAA,EACf,YAAY,EAAE,IAAI,SAAS,EAAE,QAAQ,KAAK,CAAC;AAAA;AAAA;AAAA,EAG3C,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,MAAM,yBAAyB,EAAE,SAAS;AACrF,CAAC;AAEM,SAAS,qCAAqC,OAAiD;AACpG,SAAO,sCAAsC,MAAM,KAAK;AAC1D;AACO,SAAS,6BAA6B,OACsD;AACjG,QAAM,SAAS,sCAAsC,KAAK;AAAA,IACxD,eAAe;AAAA,IAAM,aAAa;AAAA,IAAM,UAAU;AAAA,IAAM,YAAY;AAAA,IAAM,aAAa;AAAA,IAAM,QAAQ;AAAA,EACvG,CAAC,EAAE,MAAM,KAAK;AACd,SAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,UAAU;AAAA,IAChD;AAAA,IAA+B,OAAO;AAAA,IAAe,OAAO;AAAA,IAAa,OAAO;AAAA,IAChF,OAAO;AAAA,IAAY,OAAO;AAAA,IAAQ,OAAO;AAAA,EAC3C,CAAC,CAAC,EAAE,OAAO,KAAK;AAClB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modainteract/moda-interact-shared",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",