@pafi-dev/issuer-postgres 0.2.0 → 0.4.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/index.cjs CHANGED
@@ -45,6 +45,7 @@ __export(src_exports, {
45
45
  module.exports = __toCommonJS(src_exports);
46
46
 
47
47
  // src/postgresPointLedger.ts
48
+ var import_typeorm5 = require("typeorm");
48
49
  var import_viem = require("viem");
49
50
 
50
51
  // src/entities/locked-mint.entity.ts
@@ -108,7 +109,22 @@ __decorateClass([
108
109
  ], LockedMintEntity.prototype, "userOpHash", 2);
109
110
  LockedMintEntity = __decorateClass([
110
111
  (0, import_typeorm.Entity)({ name: "locked_mint_requests" }),
111
- (0, import_typeorm.Index)(["userAddress", "status"])
112
+ (0, import_typeorm.Index)("IDX_locked_mint_user_token_status_expires", [
113
+ "userAddress",
114
+ "tokenAddress",
115
+ "status",
116
+ "expiresAt"
117
+ ]),
118
+ (0, import_typeorm.Index)("IDX_locked_mint_user_token_amount_status", [
119
+ "userAddress",
120
+ "tokenAddress",
121
+ "amount",
122
+ "status"
123
+ ]),
124
+ (0, import_typeorm.Index)("IDX_locked_mint_pending_expires", ["expiresAt"], {
125
+ where: `"status" = 'PENDING'`
126
+ }),
127
+ (0, import_typeorm.Index)("IDX_locked_mint_user_op_hash", ["userOpHash"])
112
128
  ], LockedMintEntity);
113
129
 
114
130
  // src/entities/pending-credit.entity.ts
@@ -261,11 +277,22 @@ __decorateClass([
261
277
  ], LedgerJournalEntity.prototype, "createdAt", 2);
262
278
  LedgerJournalEntity = __decorateClass([
263
279
  (0, import_typeorm4.Entity)({ name: "ledger_journal" }),
264
- (0, import_typeorm4.Index)(["userAddress", "createdAt"])
280
+ (0, import_typeorm4.Index)(["userAddress", "createdAt"]),
281
+ (0, import_typeorm4.Index)(
282
+ "UQ_ledger_journal_user_token_tx_reason",
283
+ ["userAddress", "tokenAddress", "txHash", "reason"],
284
+ {
285
+ unique: true,
286
+ where: '"tx_hash" IS NOT NULL'
287
+ }
288
+ )
265
289
  ], LedgerJournalEntity);
266
290
 
267
291
  // src/postgresPointLedger.ts
268
292
  var RETRIABLE_PG_CODES = /* @__PURE__ */ new Set(["40P01", "40001"]);
293
+ var UNIQUE_VIOLATION = "23505";
294
+ var JOURNAL_IDEMPOTENCY_CONSTRAINT = "UQ_ledger_journal_user_token_tx_reason";
295
+ var LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT = "UQ_ledger_journal_user_tx_reason";
269
296
  function isRetriablePgError(err) {
270
297
  const e = err;
271
298
  if (!e) return false;
@@ -275,6 +302,18 @@ function isRetriablePgError(err) {
275
302
  }
276
303
  return false;
277
304
  }
305
+ function isJournalIdempotencyViolation(err) {
306
+ const e = err;
307
+ if (!e) return false;
308
+ const code = e.code ?? e.driverError?.code;
309
+ if (code !== UNIQUE_VIOLATION) return false;
310
+ const constraint = e.constraint ?? e.driverError?.constraint;
311
+ if (constraint === JOURNAL_IDEMPOTENCY_CONSTRAINT || constraint === LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT) {
312
+ return true;
313
+ }
314
+ const message = e.message ?? e.driverError?.message ?? "";
315
+ return message.includes(JOURNAL_IDEMPOTENCY_CONSTRAINT) || message.includes(LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT);
316
+ }
278
317
  async function withDeadlockRetry(dataSource, fn, maxAttempts = 3) {
279
318
  let attempt = 0;
280
319
  let delayMs = 25;
@@ -346,6 +385,40 @@ var PostgresPointLedger = class {
346
385
  }
347
386
  return swept;
348
387
  }
388
+ /**
389
+ * Audit PACI5-20 — symmetric counterpart of `markExpiredLocks` for
390
+ * the redeem/burn (PendingCredit) side. Marks all expired PENDING
391
+ * pending_credits as EXPIRED so abandoned reservations can no
392
+ * longer hijack later burns' attribution in `findPendingCreditLockId`.
393
+ *
394
+ * The README has always promised this sweep; the pre-fix code only
395
+ * shipped the mint-side `markExpiredLocks`, leaving credits to
396
+ * accumulate forever (storage bloat) and stay matchable past their
397
+ * deadline (attribution corruption). Wire it into the BurnIndexer
398
+ * tick the same way `markExpiredLocks` is wired into the
399
+ * MintIndexer tick — typically every 1-5 minutes.
400
+ *
401
+ * Defense-in-depth: `findPendingCreditLockId` also filters
402
+ * `expires_at > now()` so a missed-tick window can't reintroduce
403
+ * the hijack.
404
+ *
405
+ * @example
406
+ * ```ts
407
+ * @Interval(60_000)
408
+ * async sweep() {
409
+ * await this.ledger.markExpiredLocks();
410
+ * await this.ledger.markExpiredCredits();
411
+ * }
412
+ * ```
413
+ */
414
+ async markExpiredCredits() {
415
+ const result = await this.dataSource.getRepository(PendingCreditEntity).createQueryBuilder().update().set({ status: "EXPIRED" }).where("status = :pending", { pending: "PENDING" }).andWhere("expires_at <= :now", { now: /* @__PURE__ */ new Date() }).execute();
416
+ const swept = result.affected ?? 0;
417
+ if (swept > 0) {
418
+ this.logger?.debug?.(`markExpiredCredits: swept ${swept} pending credits`);
419
+ }
420
+ return swept;
421
+ }
349
422
  async getLockedRequests(userAddress, tokenAddress) {
350
423
  const { user, token } = normalize(userAddress, tokenAddress);
351
424
  const rows = await this.dataSource.getRepository(LockedMintEntity).find({
@@ -470,43 +543,72 @@ var PostgresPointLedger = class {
470
543
  throw new Error("deductBalance: amount must be positive");
471
544
  }
472
545
  const { user, token } = normalize(userAddress, tokenAddress);
473
- await withDeadlockRetry(this.dataSource, async (tx) => {
474
- const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
475
- if (!balance || balance.balance < amount) {
476
- throw new Error(
477
- `Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`
546
+ try {
547
+ await withDeadlockRetry(this.dataSource, async (tx) => {
548
+ const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
549
+ const already = await tx.getRepository(LedgerJournalEntity).findOne({
550
+ where: {
551
+ userAddress: user,
552
+ tokenAddress: token,
553
+ txHash,
554
+ reason: "MINT_CONFIRMED"
555
+ }
556
+ });
557
+ if (already) {
558
+ this.logger?.debug?.(
559
+ `deductBalance: idempotent skip tx=${txHash} user=${user} token=${token}`
560
+ );
561
+ return;
562
+ }
563
+ if (!balance || balance.balance < amount) {
564
+ throw new Error(
565
+ `Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`
566
+ );
567
+ }
568
+ await tx.getRepository(UserBalanceEntity).update(
569
+ { userAddress: user, tokenAddress: token },
570
+ { balance: balance.balance - amount }
478
571
  );
479
- }
480
- await tx.getRepository(UserBalanceEntity).update(
481
- { userAddress: user, tokenAddress: token },
482
- { balance: balance.balance - amount }
483
- );
484
- await tx.getRepository(LedgerJournalEntity).insert({
485
- userAddress: user,
486
- tokenAddress: token,
487
- delta: -amount,
488
- reason: "MINT_CONFIRMED",
489
- txHash
490
- });
491
- const match = await tx.getRepository(LockedMintEntity).findOne({
492
- where: {
572
+ await tx.getRepository(LedgerJournalEntity).insert({
493
573
  userAddress: user,
494
574
  tokenAddress: token,
495
- amount,
496
- status: "PENDING"
497
- },
498
- order: { createdAt: "ASC" }
575
+ delta: -amount,
576
+ reason: "MINT_CONFIRMED",
577
+ txHash
578
+ });
579
+ const match = await tx.getRepository(LockedMintEntity).findOne({
580
+ where: {
581
+ userAddress: user,
582
+ tokenAddress: token,
583
+ amount,
584
+ status: "PENDING"
585
+ },
586
+ order: { createdAt: "ASC" }
587
+ });
588
+ if (match) {
589
+ await tx.getRepository(LockedMintEntity).update({ id: match.id }, { status: "MINTED", txHash });
590
+ }
499
591
  });
500
- if (match) {
501
- await tx.getRepository(LockedMintEntity).update({ id: match.id }, { status: "MINTED", txHash });
592
+ } catch (err) {
593
+ if (isJournalIdempotencyViolation(err)) {
594
+ this.logger?.debug?.(
595
+ `deductBalance: idempotent (concurrent race) tx=${txHash} user=${user}`
596
+ );
597
+ return;
502
598
  }
503
- });
599
+ throw err;
600
+ }
504
601
  this.logger?.log?.(`deduct ${user}[${token}] -${amount} tx=${txHash}`);
505
602
  }
506
603
  async updateMintStatus(lockId, status, txHash) {
507
604
  const update = { status };
508
605
  if (txHash) update.txHash = txHash;
509
- await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId }, update);
606
+ const result = await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set(update).where("id = :id", { id: lockId }).andWhere("status = :pending", { pending: "PENDING" }).execute();
607
+ if ((result.affected ?? 0) === 0) {
608
+ this.logger?.debug?.(
609
+ `updateMintStatus: lock ${lockId} not in PENDING (terminal state), no-op`
610
+ );
611
+ }
510
612
  }
511
613
  async bindMintUserOpHash(lockId, userOpHash) {
512
614
  await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId }, { userOpHash });
@@ -538,7 +640,7 @@ var PostgresPointLedger = class {
538
640
  return row.id;
539
641
  }
540
642
  async resolveCreditByBurnTx(lockId, txHash) {
541
- await withDeadlockRetry(this.dataSource, async (tx) => {
643
+ const run = async () => withDeadlockRetry(this.dataSource, async (tx) => {
542
644
  const credit = await tx.getRepository(PendingCreditEntity).createQueryBuilder("credit").setLock("pessimistic_write").where("credit.id = :id", { id: lockId }).getOne();
543
645
  if (!credit) {
544
646
  throw new Error(
@@ -591,13 +693,46 @@ var PostgresPointLedger = class {
591
693
  { status: "RESOLVED", txHash, resolvedAt: /* @__PURE__ */ new Date() }
592
694
  );
593
695
  });
696
+ try {
697
+ await run();
698
+ } catch (err) {
699
+ if (isJournalIdempotencyViolation(err)) {
700
+ this.logger?.debug?.(
701
+ `resolveCreditByBurnTx: concurrent race on tx=${txHash} lock=${lockId}, replaying via sibling defense`
702
+ );
703
+ await run();
704
+ } else {
705
+ throw err;
706
+ }
707
+ }
594
708
  this.logger?.log?.(`resolve pending credit ${lockId} tx=${txHash}`);
595
709
  }
596
710
  /**
597
711
  * Used by `BurnIndexer.matchLockId` to resolve an on-chain burn
598
- * event back to a pending credit row. Returns the oldest matching
599
- * `(user, token, amount, status: PENDING)` lockId, or undefined
600
- * when no match exists (unsolicited burn — indexer skips).
712
+ * event back to a pending credit row. Returns the **newest**
713
+ * matching `(user, token, amount, status: PENDING, expires_at > now)`
714
+ * lockId, or undefined when no match exists (unsolicited burn —
715
+ * indexer skips).
716
+ *
717
+ * Audit PACI5-20 — two filters narrowed since the pre-fix shipped:
718
+ *
719
+ * 1. `expires_at > now()` — abandoned PENDING reservations past
720
+ * their deadline no longer hijack a fresh burn's attribution.
721
+ * This is defense-in-depth in case `markExpiredCredits` hasn't
722
+ * run recently (missed-tick window).
723
+ *
724
+ * 2. `ORDER BY createdAt DESC` (was ASC) — when multiple PENDING
725
+ * reservations match the same (user, token, amount) within
726
+ * the validity window, prefer the most recent one. Matches
727
+ * user mental model: "I just submitted /redeem, my recent
728
+ * reservation should win" rather than resurrecting a stale
729
+ * one. With the sweep + filter the typical steady state has
730
+ * at most one matching row, so this only affects edge cases
731
+ * (very fast re-submit, or concurrent flows).
732
+ *
733
+ * Combined with the partial UNIQUE index on `(txHash, status =
734
+ * RESOLVED)` from PACI5-7, the attribution can no longer drift
735
+ * across sessions even under adversarial conditions.
601
736
  */
602
737
  async findPendingCreditLockId(userAddress, amount, tokenAddress) {
603
738
  const { user, token } = normalize(userAddress, tokenAddress);
@@ -606,9 +741,10 @@ var PostgresPointLedger = class {
606
741
  userAddress: user,
607
742
  tokenAddress: token,
608
743
  amount,
609
- status: "PENDING"
744
+ status: "PENDING",
745
+ expiresAt: (0, import_typeorm5.MoreThan)(/* @__PURE__ */ new Date())
610
746
  },
611
- order: { createdAt: "ASC" }
747
+ order: { createdAt: "DESC" }
612
748
  });
613
749
  return row?.id;
614
750
  }
@@ -647,17 +783,17 @@ function normalize(userAddress, tokenAddress) {
647
783
  }
648
784
 
649
785
  // src/entities/indexer-cursor.entity.ts
650
- var import_typeorm5 = require("typeorm");
786
+ var import_typeorm6 = require("typeorm");
651
787
  var IndexerCursorEntity = class {
652
788
  id;
653
789
  nextBlock;
654
790
  updatedAt;
655
791
  };
656
792
  __decorateClass([
657
- (0, import_typeorm5.PrimaryColumn)({ type: "varchar", length: 64 })
793
+ (0, import_typeorm6.PrimaryColumn)({ type: "varchar", length: 64 })
658
794
  ], IndexerCursorEntity.prototype, "id", 2);
659
795
  __decorateClass([
660
- (0, import_typeorm5.Column)({
796
+ (0, import_typeorm6.Column)({
661
797
  name: "next_block",
662
798
  type: "numeric",
663
799
  precision: 78,
@@ -669,10 +805,10 @@ __decorateClass([
669
805
  })
670
806
  ], IndexerCursorEntity.prototype, "nextBlock", 2);
671
807
  __decorateClass([
672
- (0, import_typeorm5.UpdateDateColumn)({ name: "updated_at" })
808
+ (0, import_typeorm6.UpdateDateColumn)({ name: "updated_at" })
673
809
  ], IndexerCursorEntity.prototype, "updatedAt", 2);
674
810
  IndexerCursorEntity = __decorateClass([
675
- (0, import_typeorm5.Entity)({ name: "indexer_cursors" })
811
+ (0, import_typeorm6.Entity)({ name: "indexer_cursors" })
676
812
  ], IndexerCursorEntity);
677
813
 
678
814
  // src/postgresCursorStore.ts
@@ -703,7 +839,7 @@ var PostgresCursorStore = class _PostgresCursorStore {
703
839
  var import_viem2 = require("viem");
704
840
 
705
841
  // src/entities/redemption-history.entity.ts
706
- var import_typeorm6 = require("typeorm");
842
+ var import_typeorm7 = require("typeorm");
707
843
  var RedemptionHistoryEntity = class {
708
844
  id;
709
845
  userAddress;
@@ -714,16 +850,16 @@ var RedemptionHistoryEntity = class {
714
850
  rowCreatedAt;
715
851
  };
716
852
  __decorateClass([
717
- (0, import_typeorm6.PrimaryGeneratedColumn)("uuid")
853
+ (0, import_typeorm7.PrimaryGeneratedColumn)("uuid")
718
854
  ], RedemptionHistoryEntity.prototype, "id", 2);
719
855
  __decorateClass([
720
- (0, import_typeorm6.Column)({ name: "user_address", type: "varchar", length: 42 })
856
+ (0, import_typeorm7.Column)({ name: "user_address", type: "varchar", length: 42 })
721
857
  ], RedemptionHistoryEntity.prototype, "userAddress", 2);
722
858
  __decorateClass([
723
- (0, import_typeorm6.Column)({ name: "token_address", type: "varchar", length: 42, nullable: true })
859
+ (0, import_typeorm7.Column)({ name: "token_address", type: "varchar", length: 42, nullable: true })
724
860
  ], RedemptionHistoryEntity.prototype, "tokenAddress", 2);
725
861
  __decorateClass([
726
- (0, import_typeorm6.Column)({
862
+ (0, import_typeorm7.Column)({
727
863
  name: "amount_pt",
728
864
  type: "numeric",
729
865
  precision: 78,
@@ -735,10 +871,10 @@ __decorateClass([
735
871
  })
736
872
  ], RedemptionHistoryEntity.prototype, "amountPt", 2);
737
873
  __decorateClass([
738
- (0, import_typeorm6.Column)({ name: "created_at_unix_sec", type: "bigint" })
874
+ (0, import_typeorm7.Column)({ name: "created_at_unix_sec", type: "bigint" })
739
875
  ], RedemptionHistoryEntity.prototype, "createdAtUnixSec", 2);
740
876
  __decorateClass([
741
- (0, import_typeorm6.Column)({
877
+ (0, import_typeorm7.Column)({
742
878
  name: "reservation_id",
743
879
  type: "varchar",
744
880
  length: 64,
@@ -746,11 +882,11 @@ __decorateClass([
746
882
  })
747
883
  ], RedemptionHistoryEntity.prototype, "reservationId", 2);
748
884
  __decorateClass([
749
- (0, import_typeorm6.CreateDateColumn)({ name: "row_created_at", type: "timestamptz" })
885
+ (0, import_typeorm7.CreateDateColumn)({ name: "row_created_at", type: "timestamptz" })
750
886
  ], RedemptionHistoryEntity.prototype, "rowCreatedAt", 2);
751
887
  RedemptionHistoryEntity = __decorateClass([
752
- (0, import_typeorm6.Entity)({ name: "redemption_history" }),
753
- (0, import_typeorm6.Index)("idx_redemption_history_user_created", [
888
+ (0, import_typeorm7.Entity)({ name: "redemption_history" }),
889
+ (0, import_typeorm7.Index)("idx_redemption_history_user_created", [
754
890
  "userAddress",
755
891
  "createdAtUnixSec"
756
892
  ])
@@ -941,10 +1077,103 @@ var CreateRedemptionHistory1746230400001 = class {
941
1077
  }
942
1078
  };
943
1079
 
1080
+ // src/migrations/1747500000000-AddJournalIdempotencyIndex.ts
1081
+ var AddJournalIdempotencyIndex1747500000000 = class {
1082
+ name = "AddJournalIdempotencyIndex1747500000000";
1083
+ async up(queryRunner) {
1084
+ await queryRunner.query(`
1085
+ CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_tx_reason"
1086
+ ON "ledger_journal" ("user_address", "tx_hash", "reason")
1087
+ WHERE "tx_hash" IS NOT NULL
1088
+ `);
1089
+ }
1090
+ async down(queryRunner) {
1091
+ await queryRunner.query(
1092
+ `DROP INDEX IF EXISTS "UQ_ledger_journal_user_tx_reason"`
1093
+ );
1094
+ }
1095
+ };
1096
+
1097
+ // src/migrations/1747600000000-AddLockedMintCompositeIndexes.ts
1098
+ var AddLockedMintCompositeIndexes1747600000000 = class {
1099
+ name = "AddLockedMintCompositeIndexes1747600000000";
1100
+ /**
1101
+ * CONCURRENTLY index DDL cannot run inside a transaction. Tell
1102
+ * TypeORM to issue these statements directly.
1103
+ */
1104
+ transaction = false;
1105
+ async up(queryRunner) {
1106
+ await queryRunner.query(`
1107
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS
1108
+ "IDX_locked_mint_user_token_status_expires"
1109
+ ON "locked_mint_requests"
1110
+ ("user_address", "token_address", "status", "expires_at")
1111
+ `);
1112
+ await queryRunner.query(`
1113
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS
1114
+ "IDX_locked_mint_user_token_amount_status"
1115
+ ON "locked_mint_requests"
1116
+ ("user_address", "token_address", "amount", "status")
1117
+ `);
1118
+ await queryRunner.query(`
1119
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS
1120
+ "IDX_locked_mint_pending_expires"
1121
+ ON "locked_mint_requests" ("expires_at")
1122
+ WHERE "status" = 'PENDING'
1123
+ `);
1124
+ await queryRunner.query(
1125
+ `DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_status"`
1126
+ );
1127
+ }
1128
+ async down(queryRunner) {
1129
+ await queryRunner.query(`
1130
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS "IDX_locked_mint_user_status"
1131
+ ON "locked_mint_requests" ("user_address", "token_address", "status")
1132
+ `);
1133
+ await queryRunner.query(
1134
+ `DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_pending_expires"`
1135
+ );
1136
+ await queryRunner.query(
1137
+ `DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_token_amount_status"`
1138
+ );
1139
+ await queryRunner.query(
1140
+ `DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_token_status_expires"`
1141
+ );
1142
+ }
1143
+ };
1144
+
1145
+ // src/migrations/1747700000000-FixIdempotencyAddTokenAddress.ts
1146
+ var FixIdempotencyAddTokenAddress1747700000000 = class {
1147
+ name = "FixIdempotencyAddTokenAddress1747700000000";
1148
+ async up(queryRunner) {
1149
+ await queryRunner.query(
1150
+ `DROP INDEX IF EXISTS "UQ_ledger_journal_user_tx_reason"`
1151
+ );
1152
+ await queryRunner.query(`
1153
+ CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_token_tx_reason"
1154
+ ON "ledger_journal" ("user_address", "token_address", "tx_hash", "reason")
1155
+ WHERE "tx_hash" IS NOT NULL
1156
+ `);
1157
+ }
1158
+ async down(queryRunner) {
1159
+ await queryRunner.query(
1160
+ `DROP INDEX IF EXISTS "UQ_ledger_journal_user_token_tx_reason"`
1161
+ );
1162
+ await queryRunner.query(`
1163
+ CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_tx_reason"
1164
+ ON "ledger_journal" ("user_address", "tx_hash", "reason")
1165
+ WHERE "tx_hash" IS NOT NULL
1166
+ `);
1167
+ }
1168
+ };
1169
+
944
1170
  // src/migrations/index.ts
945
1171
  var PAFI_MIGRATIONS = [
946
1172
  InitialSchema1700000000000,
947
- CreateRedemptionHistory1746230400001
1173
+ CreateRedemptionHistory1746230400001,
1174
+ AddJournalIdempotencyIndex1747500000000,
1175
+ AddLockedMintCompositeIndexes1747600000000,
1176
+ FixIdempotencyAddTokenAddress1747700000000
948
1177
  ];
949
1178
  // Annotate the CommonJS export names for ESM import in node:
950
1179
  0 && (module.exports = {