@pafi-dev/issuer-postgres 0.1.2 → 0.3.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
@@ -28,6 +28,7 @@ var __decorateClass = (decorators, target, key, kind) => {
28
28
  // src/index.ts
29
29
  var src_exports = {};
30
30
  __export(src_exports, {
31
+ CreateRedemptionHistory1746230400001: () => CreateRedemptionHistory1746230400001,
31
32
  IndexerCursorEntity: () => IndexerCursorEntity,
32
33
  InitialSchema1700000000000: () => InitialSchema1700000000000,
33
34
  LedgerJournalEntity: () => LedgerJournalEntity,
@@ -37,6 +38,8 @@ __export(src_exports, {
37
38
  PendingCreditEntity: () => PendingCreditEntity,
38
39
  PostgresCursorStore: () => PostgresCursorStore,
39
40
  PostgresPointLedger: () => PostgresPointLedger,
41
+ PostgresRedemptionHistoryStore: () => PostgresRedemptionHistoryStore,
42
+ RedemptionHistoryEntity: () => RedemptionHistoryEntity,
40
43
  UserBalanceEntity: () => UserBalanceEntity
41
44
  });
42
45
  module.exports = __toCommonJS(src_exports);
@@ -105,7 +108,22 @@ __decorateClass([
105
108
  ], LockedMintEntity.prototype, "userOpHash", 2);
106
109
  LockedMintEntity = __decorateClass([
107
110
  (0, import_typeorm.Entity)({ name: "locked_mint_requests" }),
108
- (0, import_typeorm.Index)(["userAddress", "status"])
111
+ (0, import_typeorm.Index)("IDX_locked_mint_user_token_status_expires", [
112
+ "userAddress",
113
+ "tokenAddress",
114
+ "status",
115
+ "expiresAt"
116
+ ]),
117
+ (0, import_typeorm.Index)("IDX_locked_mint_user_token_amount_status", [
118
+ "userAddress",
119
+ "tokenAddress",
120
+ "amount",
121
+ "status"
122
+ ]),
123
+ (0, import_typeorm.Index)("IDX_locked_mint_pending_expires", ["expiresAt"], {
124
+ where: `"status" = 'PENDING'`
125
+ }),
126
+ (0, import_typeorm.Index)("IDX_locked_mint_user_op_hash", ["userOpHash"])
109
127
  ], LockedMintEntity);
110
128
 
111
129
  // src/entities/pending-credit.entity.ts
@@ -258,10 +276,61 @@ __decorateClass([
258
276
  ], LedgerJournalEntity.prototype, "createdAt", 2);
259
277
  LedgerJournalEntity = __decorateClass([
260
278
  (0, import_typeorm4.Entity)({ name: "ledger_journal" }),
261
- (0, import_typeorm4.Index)(["userAddress", "createdAt"])
279
+ (0, import_typeorm4.Index)(["userAddress", "createdAt"]),
280
+ (0, import_typeorm4.Index)(
281
+ "UQ_ledger_journal_user_token_tx_reason",
282
+ ["userAddress", "tokenAddress", "txHash", "reason"],
283
+ {
284
+ unique: true,
285
+ where: '"tx_hash" IS NOT NULL'
286
+ }
287
+ )
262
288
  ], LedgerJournalEntity);
263
289
 
264
290
  // src/postgresPointLedger.ts
291
+ var RETRIABLE_PG_CODES = /* @__PURE__ */ new Set(["40P01", "40001"]);
292
+ var UNIQUE_VIOLATION = "23505";
293
+ var JOURNAL_IDEMPOTENCY_CONSTRAINT = "UQ_ledger_journal_user_token_tx_reason";
294
+ var LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT = "UQ_ledger_journal_user_tx_reason";
295
+ function isRetriablePgError(err) {
296
+ const e = err;
297
+ if (!e) return false;
298
+ if (e.code && RETRIABLE_PG_CODES.has(e.code)) return true;
299
+ if (e.message && /deadlock detected|could not serialize/i.test(e.message)) {
300
+ return true;
301
+ }
302
+ return false;
303
+ }
304
+ function isJournalIdempotencyViolation(err) {
305
+ const e = err;
306
+ if (!e) return false;
307
+ const code = e.code ?? e.driverError?.code;
308
+ if (code !== UNIQUE_VIOLATION) return false;
309
+ const constraint = e.constraint ?? e.driverError?.constraint;
310
+ if (constraint === JOURNAL_IDEMPOTENCY_CONSTRAINT || constraint === LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT) {
311
+ return true;
312
+ }
313
+ const message = e.message ?? e.driverError?.message ?? "";
314
+ return message.includes(JOURNAL_IDEMPOTENCY_CONSTRAINT) || message.includes(LEGACY_JOURNAL_IDEMPOTENCY_CONSTRAINT);
315
+ }
316
+ async function withDeadlockRetry(dataSource, fn, maxAttempts = 3) {
317
+ let attempt = 0;
318
+ let delayMs = 25;
319
+ for (; ; ) {
320
+ attempt++;
321
+ try {
322
+ return await dataSource.transaction(fn);
323
+ } catch (err) {
324
+ if (attempt >= maxAttempts || !isRetriablePgError(err)) {
325
+ throw err;
326
+ }
327
+ await new Promise(
328
+ (r) => setTimeout(r, delayMs + Math.floor(Math.random() * delayMs))
329
+ );
330
+ delayMs *= 2;
331
+ }
332
+ }
333
+ }
265
334
  var PostgresPointLedger = class {
266
335
  constructor(dataSource, options = {}) {
267
336
  this.dataSource = dataSource;
@@ -274,13 +343,47 @@ var PostgresPointLedger = class {
274
343
  // ---------------------------------------------------------------------
275
344
  async getBalance(userAddress, tokenAddress) {
276
345
  const { user, token } = normalize(userAddress, tokenAddress);
277
- await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set({ status: "EXPIRED" }).where("status = :pending", { pending: "PENDING" }).andWhere("expires_at <= :now", { now: /* @__PURE__ */ new Date() }).execute();
278
- const balanceRow = await this.dataSource.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
346
+ const [balanceRow, locked] = await Promise.all([
347
+ this.dataSource.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } }),
348
+ this.sumPendingLocks(user, token)
349
+ ]);
279
350
  const total = balanceRow?.balance ?? 0n;
280
- const locked = await this.sumPendingLocks(user, token);
281
351
  const available = total - locked;
282
352
  return available < 0n ? 0n : available;
283
353
  }
354
+ /**
355
+ * Background sweep — marks all expired PENDING locks as EXPIRED in
356
+ * a single UPDATE. Issuers SHOULD call this periodically (e.g.
357
+ * every 1-5 minutes via a cron / NestJS `@Interval`) to keep the
358
+ * lock table from growing unbounded.
359
+ *
360
+ * A single sweep amortizes the write cost vs `getBalance` doing a
361
+ * wide UPDATE on every read. Returns the number of rows transitioned.
362
+ *
363
+ * @example
364
+ * ```ts
365
+ * import { Interval } from "@nestjs/schedule";
366
+ *
367
+ * @Injectable()
368
+ * export class LockSweepService {
369
+ * constructor(private readonly ledger: PostgresPointLedger) {}
370
+ *
371
+ * @Interval(5 * 60 * 1000) // 5 minutes
372
+ * async sweep() {
373
+ * const swept = await this.ledger.markExpiredLocks();
374
+ * this.logger.debug(`expired ${swept} mint locks`);
375
+ * }
376
+ * }
377
+ * ```
378
+ */
379
+ async markExpiredLocks() {
380
+ const result = await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set({ status: "EXPIRED" }).where("status = :pending", { pending: "PENDING" }).andWhere("expires_at <= :now", { now: /* @__PURE__ */ new Date() }).execute();
381
+ const swept = result.affected ?? 0;
382
+ if (swept > 0) {
383
+ this.logger?.debug?.(`markExpiredLocks: swept ${swept} mint locks`);
384
+ }
385
+ return swept;
386
+ }
284
387
  async getLockedRequests(userAddress, tokenAddress) {
285
388
  const { user, token } = normalize(userAddress, tokenAddress);
286
389
  const rows = await this.dataSource.getRepository(LockedMintEntity).find({
@@ -346,8 +449,8 @@ var PostgresPointLedger = class {
346
449
  throw new Error("creditBalance: amount must be positive");
347
450
  }
348
451
  const { user, token } = normalize(userAddress, tokenAddress);
349
- await this.dataSource.transaction(async (tx) => {
350
- const existing = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
452
+ await withDeadlockRetry(this.dataSource, async (tx) => {
453
+ const existing = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
351
454
  const next = (existing?.balance ?? 0n) + amount;
352
455
  await tx.getRepository(UserBalanceEntity).upsert(
353
456
  { userAddress: user, tokenAddress: token, balance: next },
@@ -370,8 +473,8 @@ var PostgresPointLedger = class {
370
473
  throw new Error("lockForMinting: lockDurationMs must be positive");
371
474
  }
372
475
  const { user, token } = normalize(userAddress, tokenAddress);
373
- return this.dataSource.transaction(async (tx) => {
374
- const balanceRow = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
476
+ return withDeadlockRetry(this.dataSource, async (tx) => {
477
+ const balanceRow = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
375
478
  const total = balanceRow?.balance ?? 0n;
376
479
  const pendingTotal = await tx.getRepository(LockedMintEntity).createQueryBuilder("lock").select("COALESCE(SUM(CAST(lock.amount AS NUMERIC)), 0)", "sum").where("lock.user_address = :user", { user }).andWhere("lock.token_address = :token", { token }).andWhere("lock.status = :pending", { pending: "PENDING" }).andWhere("lock.expires_at > :now", { now: /* @__PURE__ */ new Date() }).getRawOne();
377
480
  const locked = pendingTotal ? BigInt(pendingTotal.sum) : 0n;
@@ -405,43 +508,72 @@ var PostgresPointLedger = class {
405
508
  throw new Error("deductBalance: amount must be positive");
406
509
  }
407
510
  const { user, token } = normalize(userAddress, tokenAddress);
408
- await this.dataSource.transaction(async (tx) => {
409
- const balance = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
410
- if (!balance || balance.balance < amount) {
411
- throw new Error(
412
- `Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`
511
+ try {
512
+ await withDeadlockRetry(this.dataSource, async (tx) => {
513
+ const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
514
+ const already = await tx.getRepository(LedgerJournalEntity).findOne({
515
+ where: {
516
+ userAddress: user,
517
+ tokenAddress: token,
518
+ txHash,
519
+ reason: "MINT_CONFIRMED"
520
+ }
521
+ });
522
+ if (already) {
523
+ this.logger?.debug?.(
524
+ `deductBalance: idempotent skip tx=${txHash} user=${user} token=${token}`
525
+ );
526
+ return;
527
+ }
528
+ if (!balance || balance.balance < amount) {
529
+ throw new Error(
530
+ `Cannot deduct ${amount} from balance ${balance?.balance ?? 0n}`
531
+ );
532
+ }
533
+ await tx.getRepository(UserBalanceEntity).update(
534
+ { userAddress: user, tokenAddress: token },
535
+ { balance: balance.balance - amount }
413
536
  );
414
- }
415
- await tx.getRepository(UserBalanceEntity).update(
416
- { userAddress: user, tokenAddress: token },
417
- { balance: balance.balance - amount }
418
- );
419
- await tx.getRepository(LedgerJournalEntity).insert({
420
- userAddress: user,
421
- tokenAddress: token,
422
- delta: -amount,
423
- reason: "MINT_CONFIRMED",
424
- txHash
425
- });
426
- const match = await tx.getRepository(LockedMintEntity).findOne({
427
- where: {
537
+ await tx.getRepository(LedgerJournalEntity).insert({
428
538
  userAddress: user,
429
539
  tokenAddress: token,
430
- amount,
431
- status: "PENDING"
432
- },
433
- order: { createdAt: "ASC" }
540
+ delta: -amount,
541
+ reason: "MINT_CONFIRMED",
542
+ txHash
543
+ });
544
+ const match = await tx.getRepository(LockedMintEntity).findOne({
545
+ where: {
546
+ userAddress: user,
547
+ tokenAddress: token,
548
+ amount,
549
+ status: "PENDING"
550
+ },
551
+ order: { createdAt: "ASC" }
552
+ });
553
+ if (match) {
554
+ await tx.getRepository(LockedMintEntity).update({ id: match.id }, { status: "MINTED", txHash });
555
+ }
434
556
  });
435
- if (match) {
436
- await tx.getRepository(LockedMintEntity).update({ id: match.id }, { status: "MINTED", txHash });
557
+ } catch (err) {
558
+ if (isJournalIdempotencyViolation(err)) {
559
+ this.logger?.debug?.(
560
+ `deductBalance: idempotent (concurrent race) tx=${txHash} user=${user}`
561
+ );
562
+ return;
437
563
  }
438
- });
564
+ throw err;
565
+ }
439
566
  this.logger?.log?.(`deduct ${user}[${token}] -${amount} tx=${txHash}`);
440
567
  }
441
568
  async updateMintStatus(lockId, status, txHash) {
442
569
  const update = { status };
443
570
  if (txHash) update.txHash = txHash;
444
- await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId }, update);
571
+ const result = await this.dataSource.getRepository(LockedMintEntity).createQueryBuilder().update().set(update).where("id = :id", { id: lockId }).andWhere("status = :pending", { pending: "PENDING" }).execute();
572
+ if ((result.affected ?? 0) === 0) {
573
+ this.logger?.debug?.(
574
+ `updateMintStatus: lock ${lockId} not in PENDING (terminal state), no-op`
575
+ );
576
+ }
445
577
  }
446
578
  async bindMintUserOpHash(lockId, userOpHash) {
447
579
  await this.dataSource.getRepository(LockedMintEntity).update({ id: lockId }, { userOpHash });
@@ -473,8 +605,8 @@ var PostgresPointLedger = class {
473
605
  return row.id;
474
606
  }
475
607
  async resolveCreditByBurnTx(lockId, txHash) {
476
- await this.dataSource.transaction(async (tx) => {
477
- const credit = await tx.getRepository(PendingCreditEntity).findOne({ where: { id: lockId } });
608
+ const run = async () => withDeadlockRetry(this.dataSource, async (tx) => {
609
+ const credit = await tx.getRepository(PendingCreditEntity).createQueryBuilder("credit").setLock("pessimistic_write").where("credit.id = :id", { id: lockId }).getOne();
478
610
  if (!credit) {
479
611
  throw new Error(
480
612
  `resolveCreditByBurnTx: unknown pending credit ${lockId}`
@@ -508,7 +640,7 @@ var PostgresPointLedger = class {
508
640
  );
509
641
  return;
510
642
  }
511
- const balance = await tx.getRepository(UserBalanceEntity).findOne({ where: { userAddress: user, tokenAddress: token } });
643
+ const balance = await tx.getRepository(UserBalanceEntity).createQueryBuilder("balance").setLock("pessimistic_write").where("balance.user_address = :user", { user }).andWhere("balance.token_address = :token", { token }).getOne();
512
644
  const next = (balance?.balance ?? 0n) + credit.amount;
513
645
  await tx.getRepository(UserBalanceEntity).upsert(
514
646
  { userAddress: user, tokenAddress: token, balance: next },
@@ -526,6 +658,18 @@ var PostgresPointLedger = class {
526
658
  { status: "RESOLVED", txHash, resolvedAt: /* @__PURE__ */ new Date() }
527
659
  );
528
660
  });
661
+ try {
662
+ await run();
663
+ } catch (err) {
664
+ if (isJournalIdempotencyViolation(err)) {
665
+ this.logger?.debug?.(
666
+ `resolveCreditByBurnTx: concurrent race on tx=${txHash} lock=${lockId}, replaying via sibling defense`
667
+ );
668
+ await run();
669
+ } else {
670
+ throw err;
671
+ }
672
+ }
529
673
  this.logger?.log?.(`resolve pending credit ${lockId} tx=${txHash}`);
530
674
  }
531
675
  /**
@@ -634,13 +778,114 @@ var PostgresCursorStore = class _PostgresCursorStore {
634
778
  }
635
779
  };
636
780
 
781
+ // src/postgresRedemptionHistoryStore.ts
782
+ var import_viem2 = require("viem");
783
+
784
+ // src/entities/redemption-history.entity.ts
785
+ var import_typeorm6 = require("typeorm");
786
+ var RedemptionHistoryEntity = class {
787
+ id;
788
+ userAddress;
789
+ tokenAddress;
790
+ amountPt;
791
+ createdAtUnixSec;
792
+ reservationId;
793
+ rowCreatedAt;
794
+ };
795
+ __decorateClass([
796
+ (0, import_typeorm6.PrimaryGeneratedColumn)("uuid")
797
+ ], RedemptionHistoryEntity.prototype, "id", 2);
798
+ __decorateClass([
799
+ (0, import_typeorm6.Column)({ name: "user_address", type: "varchar", length: 42 })
800
+ ], RedemptionHistoryEntity.prototype, "userAddress", 2);
801
+ __decorateClass([
802
+ (0, import_typeorm6.Column)({ name: "token_address", type: "varchar", length: 42, nullable: true })
803
+ ], RedemptionHistoryEntity.prototype, "tokenAddress", 2);
804
+ __decorateClass([
805
+ (0, import_typeorm6.Column)({
806
+ name: "amount_pt",
807
+ type: "numeric",
808
+ precision: 78,
809
+ scale: 0,
810
+ transformer: {
811
+ to: (value) => value.toString(),
812
+ from: (value) => BigInt(value)
813
+ }
814
+ })
815
+ ], RedemptionHistoryEntity.prototype, "amountPt", 2);
816
+ __decorateClass([
817
+ (0, import_typeorm6.Column)({ name: "created_at_unix_sec", type: "bigint" })
818
+ ], RedemptionHistoryEntity.prototype, "createdAtUnixSec", 2);
819
+ __decorateClass([
820
+ (0, import_typeorm6.Column)({
821
+ name: "reservation_id",
822
+ type: "varchar",
823
+ length: 64,
824
+ nullable: true
825
+ })
826
+ ], RedemptionHistoryEntity.prototype, "reservationId", 2);
827
+ __decorateClass([
828
+ (0, import_typeorm6.CreateDateColumn)({ name: "row_created_at", type: "timestamptz" })
829
+ ], RedemptionHistoryEntity.prototype, "rowCreatedAt", 2);
830
+ RedemptionHistoryEntity = __decorateClass([
831
+ (0, import_typeorm6.Entity)({ name: "redemption_history" }),
832
+ (0, import_typeorm6.Index)("idx_redemption_history_user_created", [
833
+ "userAddress",
834
+ "createdAtUnixSec"
835
+ ])
836
+ ], RedemptionHistoryEntity);
837
+
838
+ // src/postgresRedemptionHistoryStore.ts
839
+ var PostgresRedemptionHistoryStore = class {
840
+ constructor(dataSource) {
841
+ this.dataSource = dataSource;
842
+ }
843
+ dataSource;
844
+ async sumRedeemedSince(user, sinceUnixSec, pointTokenAddress) {
845
+ const repo = this.dataSource.getRepository(RedemptionHistoryEntity);
846
+ const qb = repo.createQueryBuilder("rh").select("COALESCE(SUM(rh.amount_pt), 0)", "sum").where("rh.user_address = :user", { user: (0, import_viem2.getAddress)(user) }).andWhere("rh.created_at_unix_sec >= :since", { since: sinceUnixSec });
847
+ if (pointTokenAddress !== void 0) {
848
+ qb.andWhere("rh.token_address = :token", {
849
+ token: (0, import_viem2.getAddress)(pointTokenAddress)
850
+ });
851
+ } else {
852
+ }
853
+ const row = await qb.getRawOne() ?? { sum: "0" };
854
+ return BigInt(row.sum ?? "0");
855
+ }
856
+ async getLastRedeemedAtUnixSec(user, pointTokenAddress) {
857
+ const repo = this.dataSource.getRepository(RedemptionHistoryEntity);
858
+ const qb = repo.createQueryBuilder("rh").select("rh.created_at_unix_sec", "ts").where("rh.user_address = :user", { user: (0, import_viem2.getAddress)(user) }).orderBy("rh.created_at_unix_sec", "DESC").limit(1);
859
+ if (pointTokenAddress !== void 0) {
860
+ qb.andWhere("rh.token_address = :token", {
861
+ token: (0, import_viem2.getAddress)(pointTokenAddress)
862
+ });
863
+ }
864
+ const row = await qb.getRawOne();
865
+ if (!row || row.ts === null) return null;
866
+ return Number(row.ts);
867
+ }
868
+ async recordRedemption(entry) {
869
+ const repo = this.dataSource.getRepository(RedemptionHistoryEntity);
870
+ const row = repo.create({
871
+ userAddress: (0, import_viem2.getAddress)(entry.user),
872
+ tokenAddress: entry.pointTokenAddress ? (0, import_viem2.getAddress)(entry.pointTokenAddress) : null,
873
+ amountPt: entry.amountPt,
874
+ createdAtUnixSec: String(entry.unixSec),
875
+ reservationId: entry.reservationId ?? null
876
+ });
877
+ await repo.save(row);
878
+ }
879
+ };
880
+
637
881
  // src/entities/index.ts
638
882
  var PAFI_ENTITIES = [
639
883
  LockedMintEntity,
640
884
  PendingCreditEntity,
641
885
  UserBalanceEntity,
642
886
  LedgerJournalEntity,
643
- IndexerCursorEntity
887
+ IndexerCursorEntity,
888
+ RedemptionHistoryEntity
644
889
  ];
645
890
 
646
891
  // src/migrations/1700000000000-InitialSchema.ts
@@ -746,10 +991,136 @@ var InitialSchema1700000000000 = class {
746
991
  }
747
992
  };
748
993
 
994
+ // src/migrations/1746230400001-CreateRedemptionHistory.ts
995
+ var CreateRedemptionHistory1746230400001 = class {
996
+ name = "CreateRedemptionHistory1746230400001";
997
+ async up(queryRunner) {
998
+ await queryRunner.query(`
999
+ CREATE TABLE IF NOT EXISTS redemption_history (
1000
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
1001
+ user_address varchar(42) NOT NULL,
1002
+ token_address varchar(42),
1003
+ amount_pt numeric(78, 0) NOT NULL,
1004
+ created_at_unix_sec bigint NOT NULL,
1005
+ reservation_id varchar(64),
1006
+ row_created_at timestamptz NOT NULL DEFAULT NOW(),
1007
+ CONSTRAINT redemption_history_amount_positive CHECK (amount_pt > 0)
1008
+ )
1009
+ `);
1010
+ await queryRunner.query(`
1011
+ CREATE INDEX IF NOT EXISTS idx_redemption_history_user_created
1012
+ ON redemption_history (user_address, created_at_unix_sec DESC)
1013
+ `);
1014
+ }
1015
+ async down(queryRunner) {
1016
+ await queryRunner.query(
1017
+ `DROP INDEX IF EXISTS idx_redemption_history_user_created`
1018
+ );
1019
+ await queryRunner.query(`DROP TABLE IF EXISTS redemption_history`);
1020
+ }
1021
+ };
1022
+
1023
+ // src/migrations/1747500000000-AddJournalIdempotencyIndex.ts
1024
+ var AddJournalIdempotencyIndex1747500000000 = class {
1025
+ name = "AddJournalIdempotencyIndex1747500000000";
1026
+ async up(queryRunner) {
1027
+ await queryRunner.query(`
1028
+ CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_tx_reason"
1029
+ ON "ledger_journal" ("user_address", "tx_hash", "reason")
1030
+ WHERE "tx_hash" IS NOT NULL
1031
+ `);
1032
+ }
1033
+ async down(queryRunner) {
1034
+ await queryRunner.query(
1035
+ `DROP INDEX IF EXISTS "UQ_ledger_journal_user_tx_reason"`
1036
+ );
1037
+ }
1038
+ };
1039
+
1040
+ // src/migrations/1747600000000-AddLockedMintCompositeIndexes.ts
1041
+ var AddLockedMintCompositeIndexes1747600000000 = class {
1042
+ name = "AddLockedMintCompositeIndexes1747600000000";
1043
+ /**
1044
+ * CONCURRENTLY index DDL cannot run inside a transaction. Tell
1045
+ * TypeORM to issue these statements directly.
1046
+ */
1047
+ transaction = false;
1048
+ async up(queryRunner) {
1049
+ await queryRunner.query(`
1050
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS
1051
+ "IDX_locked_mint_user_token_status_expires"
1052
+ ON "locked_mint_requests"
1053
+ ("user_address", "token_address", "status", "expires_at")
1054
+ `);
1055
+ await queryRunner.query(`
1056
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS
1057
+ "IDX_locked_mint_user_token_amount_status"
1058
+ ON "locked_mint_requests"
1059
+ ("user_address", "token_address", "amount", "status")
1060
+ `);
1061
+ await queryRunner.query(`
1062
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS
1063
+ "IDX_locked_mint_pending_expires"
1064
+ ON "locked_mint_requests" ("expires_at")
1065
+ WHERE "status" = 'PENDING'
1066
+ `);
1067
+ await queryRunner.query(
1068
+ `DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_status"`
1069
+ );
1070
+ }
1071
+ async down(queryRunner) {
1072
+ await queryRunner.query(`
1073
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS "IDX_locked_mint_user_status"
1074
+ ON "locked_mint_requests" ("user_address", "token_address", "status")
1075
+ `);
1076
+ await queryRunner.query(
1077
+ `DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_pending_expires"`
1078
+ );
1079
+ await queryRunner.query(
1080
+ `DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_token_amount_status"`
1081
+ );
1082
+ await queryRunner.query(
1083
+ `DROP INDEX CONCURRENTLY IF EXISTS "IDX_locked_mint_user_token_status_expires"`
1084
+ );
1085
+ }
1086
+ };
1087
+
1088
+ // src/migrations/1747700000000-FixIdempotencyAddTokenAddress.ts
1089
+ var FixIdempotencyAddTokenAddress1747700000000 = class {
1090
+ name = "FixIdempotencyAddTokenAddress1747700000000";
1091
+ async up(queryRunner) {
1092
+ await queryRunner.query(
1093
+ `DROP INDEX IF EXISTS "UQ_ledger_journal_user_tx_reason"`
1094
+ );
1095
+ await queryRunner.query(`
1096
+ CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_token_tx_reason"
1097
+ ON "ledger_journal" ("user_address", "token_address", "tx_hash", "reason")
1098
+ WHERE "tx_hash" IS NOT NULL
1099
+ `);
1100
+ }
1101
+ async down(queryRunner) {
1102
+ await queryRunner.query(
1103
+ `DROP INDEX IF EXISTS "UQ_ledger_journal_user_token_tx_reason"`
1104
+ );
1105
+ await queryRunner.query(`
1106
+ CREATE UNIQUE INDEX IF NOT EXISTS "UQ_ledger_journal_user_tx_reason"
1107
+ ON "ledger_journal" ("user_address", "tx_hash", "reason")
1108
+ WHERE "tx_hash" IS NOT NULL
1109
+ `);
1110
+ }
1111
+ };
1112
+
749
1113
  // src/migrations/index.ts
750
- var PAFI_MIGRATIONS = [InitialSchema1700000000000];
1114
+ var PAFI_MIGRATIONS = [
1115
+ InitialSchema1700000000000,
1116
+ CreateRedemptionHistory1746230400001,
1117
+ AddJournalIdempotencyIndex1747500000000,
1118
+ AddLockedMintCompositeIndexes1747600000000,
1119
+ FixIdempotencyAddTokenAddress1747700000000
1120
+ ];
751
1121
  // Annotate the CommonJS export names for ESM import in node:
752
1122
  0 && (module.exports = {
1123
+ CreateRedemptionHistory1746230400001,
753
1124
  IndexerCursorEntity,
754
1125
  InitialSchema1700000000000,
755
1126
  LedgerJournalEntity,
@@ -759,6 +1130,8 @@ var PAFI_MIGRATIONS = [InitialSchema1700000000000];
759
1130
  PendingCreditEntity,
760
1131
  PostgresCursorStore,
761
1132
  PostgresPointLedger,
1133
+ PostgresRedemptionHistoryStore,
1134
+ RedemptionHistoryEntity,
762
1135
  UserBalanceEntity
763
1136
  });
764
1137
  //# sourceMappingURL=index.cjs.map