@apibara/plugin-drizzle 2.1.0-beta.5 → 2.1.0-beta.51

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
@@ -4,8 +4,9 @@ const indexer = require('@apibara/indexer');
4
4
  const plugins = require('@apibara/indexer/plugins');
5
5
  const internal = require('@apibara/indexer/internal');
6
6
  const plugins$1 = require('@apibara/indexer/internal/plugins');
7
- const protocol = require('@apibara/protocol');
7
+ const constants = require('./shared/plugin-drizzle.cae20704.cjs');
8
8
  const drizzleOrm = require('drizzle-orm');
9
+ const protocol = require('@apibara/protocol');
9
10
  const pgCore = require('drizzle-orm/pg-core');
10
11
 
11
12
  class DrizzleStorageError extends Error {
@@ -35,16 +36,89 @@ function serialize(obj) {
35
36
  function sleep(ms) {
36
37
  return new Promise((resolve) => setTimeout(resolve, ms));
37
38
  }
39
+ const getIdColumnForTable = (tableName, idColumn) => {
40
+ if (idColumn[tableName]) {
41
+ return idColumn[tableName];
42
+ }
43
+ return idColumn["*"];
44
+ };
45
+
46
+ function drizzle(options) {
47
+ const {
48
+ connectionString = process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://",
49
+ schema,
50
+ type = "pglite",
51
+ config,
52
+ poolConfig
53
+ } = options ?? {};
54
+ if (isPgliteConnectionString(connectionString) && type === "pglite") {
55
+ const { drizzle: drizzlePGLite } = require("drizzle-orm/pglite");
56
+ return drizzlePGLite({
57
+ schema,
58
+ connection: {
59
+ dataDir: connectionString || "memory://pglite"
60
+ },
61
+ ...config || {}
62
+ });
63
+ }
64
+ const { Pool } = require("pg");
65
+ const { drizzle: drizzleNode } = require("drizzle-orm/node-postgres");
66
+ const pool = new Pool({
67
+ connectionString,
68
+ ...poolConfig || {}
69
+ });
70
+ return drizzleNode(pool, { schema, ...config || {} });
71
+ }
72
+ async function migrate(db, options) {
73
+ const isPglite = isDrizzleKind(db, "PgliteDatabase");
74
+ try {
75
+ if (isPglite) {
76
+ const { migrate: migratePGLite } = require("drizzle-orm/pglite/migrator");
77
+ await migratePGLite(db, options);
78
+ } else {
79
+ const {
80
+ migrate: migrateNode
81
+ } = require("drizzle-orm/node-postgres/migrator");
82
+ await migrateNode(db, options);
83
+ }
84
+ } catch (error) {
85
+ throw new DrizzleStorageError(
86
+ "Failed to apply migrations! Please check if you have generated migrations using drizzle:generate",
87
+ {
88
+ cause: error
89
+ }
90
+ );
91
+ }
92
+ }
93
+ function isPgliteConnectionString(conn) {
94
+ return conn.startsWith("memory://") || conn.startsWith("file://") || conn.startsWith("idb://");
95
+ }
96
+ function isDrizzleKind(value, entityKindValue) {
97
+ if (!value || typeof value !== "object") {
98
+ return false;
99
+ }
100
+ let cls = Object.getPrototypeOf(value).constructor;
101
+ if (cls) {
102
+ while (cls) {
103
+ if (drizzleOrm.entityKind in cls && cls[drizzleOrm.entityKind] === entityKindValue) {
104
+ return true;
105
+ }
106
+ cls = Object.getPrototypeOf(cls);
107
+ }
108
+ }
109
+ return false;
110
+ }
38
111
 
39
- const CHECKPOINTS_TABLE_NAME = "__indexer_checkpoints";
40
- const FILTERS_TABLE_NAME = "__indexer_filters";
41
- const SCHEMA_VERSION_TABLE_NAME = "__indexer_schema_version";
42
- const checkpoints = pgCore.pgTable(CHECKPOINTS_TABLE_NAME, {
112
+ const CHECKPOINTS_TABLE_NAME = "checkpoints";
113
+ const FILTERS_TABLE_NAME = "filters";
114
+ const SCHEMA_VERSION_TABLE_NAME = "schema_version";
115
+ const schema$1 = pgCore.pgSchema(constants.SCHEMA_NAME);
116
+ const checkpoints = schema$1.table(CHECKPOINTS_TABLE_NAME, {
43
117
  id: pgCore.text("id").notNull().primaryKey(),
44
118
  orderKey: pgCore.integer("order_key").notNull(),
45
119
  uniqueKey: pgCore.text("unique_key")
46
120
  });
47
- const filters = pgCore.pgTable(
121
+ const filters = schema$1.table(
48
122
  FILTERS_TABLE_NAME,
49
123
  {
50
124
  id: pgCore.text("id").notNull(),
@@ -58,7 +132,16 @@ const filters = pgCore.pgTable(
58
132
  }
59
133
  ]
60
134
  );
61
- const schemaVersion = pgCore.pgTable(SCHEMA_VERSION_TABLE_NAME, {
135
+ const chainReorganizations = schema$1.table("chain_reorganizations", {
136
+ id: pgCore.serial("id").primaryKey(),
137
+ indexerId: pgCore.text("indexer_id").notNull(),
138
+ oldHeadOrderKey: pgCore.integer("old_head_order_key"),
139
+ oldHeadUniqueKey: pgCore.text("old_head_unique_key").$type().default(null),
140
+ newHeadOrderKey: pgCore.integer("new_head_order_key").notNull(),
141
+ newHeadUniqueKey: pgCore.text("new_head_unique_key").$type().default(null),
142
+ recordedAt: pgCore.timestamp("recorded_at").defaultNow().notNull()
143
+ });
144
+ const schemaVersion = schema$1.table(SCHEMA_VERSION_TABLE_NAME, {
62
145
  k: pgCore.integer("k").notNull().primaryKey(),
63
146
  version: pgCore.integer("version").notNull()
64
147
  });
@@ -69,12 +152,38 @@ const MIGRATIONS = [
69
152
  // Add more migration arrays for future versions
70
153
  ];
71
154
  async function initializePersistentState(tx) {
72
- await tx.execute(`
73
- CREATE TABLE IF NOT EXISTS ${SCHEMA_VERSION_TABLE_NAME} (
155
+ await tx.execute(
156
+ drizzleOrm.sql.raw(`
157
+ CREATE SCHEMA IF NOT EXISTS ${constants.SCHEMA_NAME};
158
+ `)
159
+ );
160
+ await tx.execute(
161
+ drizzleOrm.sql.raw(`
162
+ CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${SCHEMA_VERSION_TABLE_NAME} (
74
163
  k INTEGER PRIMARY KEY,
75
164
  version INTEGER NOT NULL
76
165
  );
77
- `);
166
+ `)
167
+ );
168
+ await tx.execute(
169
+ drizzleOrm.sql.raw(`
170
+ CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.chain_reorganizations (
171
+ id SERIAL PRIMARY KEY,
172
+ indexer_id TEXT NOT NULL,
173
+ old_head_order_key INTEGER,
174
+ old_head_unique_key TEXT DEFAULT NULL,
175
+ new_head_order_key INTEGER NOT NULL,
176
+ new_head_unique_key TEXT DEFAULT NULL,
177
+ recorded_at TIMESTAMP NOT NULL DEFAULT NOW()
178
+ );
179
+ `)
180
+ );
181
+ await tx.execute(
182
+ drizzleOrm.sql.raw(`
183
+ CREATE INDEX IF NOT EXISTS idx_chain_reorgs_indexer_id
184
+ ON ${constants.SCHEMA_NAME}.chain_reorganizations(indexer_id);
185
+ `)
186
+ );
78
187
  const versionRows = await tx.select().from(schemaVersion).where(drizzleOrm.eq(schemaVersion.k, 0));
79
188
  const storedVersion = versionRows[0]?.version ?? -1;
80
189
  if (storedVersion > CURRENT_SCHEMA_VERSION) {
@@ -84,22 +193,26 @@ async function initializePersistentState(tx) {
84
193
  }
85
194
  try {
86
195
  if (storedVersion === -1) {
87
- await tx.execute(`
88
- CREATE TABLE IF NOT EXISTS ${CHECKPOINTS_TABLE_NAME} (
196
+ await tx.execute(
197
+ drizzleOrm.sql.raw(`
198
+ CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${CHECKPOINTS_TABLE_NAME} (
89
199
  id TEXT PRIMARY KEY,
90
200
  order_key INTEGER NOT NULL,
91
201
  unique_key TEXT
92
202
  );
93
- `);
94
- await tx.execute(`
95
- CREATE TABLE IF NOT EXISTS ${FILTERS_TABLE_NAME} (
203
+ `)
204
+ );
205
+ await tx.execute(
206
+ drizzleOrm.sql.raw(`
207
+ CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${FILTERS_TABLE_NAME} (
96
208
  id TEXT NOT NULL,
97
209
  filter TEXT NOT NULL,
98
210
  from_block INTEGER NOT NULL,
99
211
  to_block INTEGER DEFAULT NULL,
100
212
  PRIMARY KEY (id, from_block)
101
213
  );
102
- `);
214
+ `)
215
+ );
103
216
  await tx.insert(schemaVersion).values({
104
217
  k: 0,
105
218
  version: CURRENT_SCHEMA_VERSION
@@ -122,6 +235,22 @@ async function initializePersistentState(tx) {
122
235
  );
123
236
  }
124
237
  }
238
+ async function recordChainReorganization(props) {
239
+ const { tx, indexerId, oldHead, newHead } = props;
240
+ try {
241
+ await tx.insert(chainReorganizations).values({
242
+ indexerId,
243
+ oldHeadOrderKey: oldHead ? Number(oldHead.orderKey) : null,
244
+ oldHeadUniqueKey: oldHead?.uniqueKey ? oldHead.uniqueKey : null,
245
+ newHeadOrderKey: Number(newHead.orderKey),
246
+ newHeadUniqueKey: newHead.uniqueKey ? newHead.uniqueKey : null
247
+ });
248
+ } catch (error) {
249
+ throw new DrizzleStorageError("Failed to record chain reorganization", {
250
+ cause: error
251
+ });
252
+ }
253
+ }
125
254
  async function persistState(props) {
126
255
  const { tx, endCursor, filter, indexerId } = props;
127
256
  try {
@@ -134,7 +263,9 @@ async function persistState(props) {
134
263
  target: checkpoints.id,
135
264
  set: {
136
265
  orderKey: Number(endCursor.orderKey),
137
- uniqueKey: endCursor.uniqueKey
266
+ // Explicitly set the unique key to `null` to indicate that it has been deleted
267
+ // Otherwise drizzle will not update its value.
268
+ uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null
138
269
  }
139
270
  });
140
271
  if (filter) {
@@ -180,6 +311,10 @@ async function getState(props) {
180
311
  async function invalidateState(props) {
181
312
  const { tx, cursor, indexerId } = props;
182
313
  try {
314
+ await tx.update(checkpoints).set({
315
+ orderKey: Number(cursor.orderKey),
316
+ uniqueKey: cursor.uniqueKey ? cursor.uniqueKey : null
317
+ }).where(drizzleOrm.eq(checkpoints.id, indexerId));
183
318
  await tx.delete(filters).where(
184
319
  drizzleOrm.and(
185
320
  drizzleOrm.eq(filters.id, indexerId),
@@ -213,11 +348,24 @@ async function finalizeState(props) {
213
348
  });
214
349
  }
215
350
  }
351
+ async function resetPersistence(props) {
352
+ const { tx, indexerId } = props;
353
+ try {
354
+ await tx.delete(checkpoints).where(drizzleOrm.eq(checkpoints.id, indexerId));
355
+ await tx.delete(filters).where(drizzleOrm.eq(filters.id, indexerId));
356
+ } catch (error) {
357
+ throw new DrizzleStorageError("Failed to reset persistence state", {
358
+ cause: error
359
+ });
360
+ }
361
+ }
216
362
 
363
+ const ROLLBACK_TABLE_NAME = "reorg_rollback";
364
+ const schema = pgCore.pgSchema(constants.SCHEMA_NAME);
217
365
  function getReorgTriggerName(table, indexerId) {
218
366
  return `${table}_reorg_${indexerId}`;
219
367
  }
220
- pgCore.pgTable("__reorg_rollback", {
368
+ schema.table(ROLLBACK_TABLE_NAME, {
221
369
  n: pgCore.serial("n").primaryKey(),
222
370
  op: pgCore.char("op", { length: 1 }).$type().notNull(),
223
371
  table_name: pgCore.text("table_name").notNull(),
@@ -228,9 +376,12 @@ pgCore.pgTable("__reorg_rollback", {
228
376
  });
229
377
  async function initializeReorgRollbackTable(tx, indexerId) {
230
378
  try {
379
+ await tx.execute(`
380
+ CREATE SCHEMA IF NOT EXISTS ${constants.SCHEMA_NAME};
381
+ `);
231
382
  await tx.execute(
232
383
  drizzleOrm.sql.raw(`
233
- CREATE TABLE IF NOT EXISTS __reorg_rollback(
384
+ CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(
234
385
  n SERIAL PRIMARY KEY,
235
386
  op CHAR(1) NOT NULL,
236
387
  table_name TEXT NOT NULL,
@@ -243,7 +394,7 @@ async function initializeReorgRollbackTable(tx, indexerId) {
243
394
  );
244
395
  await tx.execute(
245
396
  drizzleOrm.sql.raw(`
246
- CREATE INDEX IF NOT EXISTS idx_reorg_rollback_indexer_id_cursor ON __reorg_rollback(indexer_id, cursor);
397
+ CREATE INDEX IF NOT EXISTS idx_reorg_rollback_indexer_id_cursor ON ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(indexer_id, cursor);
247
398
  `)
248
399
  );
249
400
  } catch (error) {
@@ -254,24 +405,25 @@ async function initializeReorgRollbackTable(tx, indexerId) {
254
405
  try {
255
406
  await tx.execute(
256
407
  drizzleOrm.sql.raw(`
257
- CREATE OR REPLACE FUNCTION reorg_checkpoint()
408
+ CREATE OR REPLACE FUNCTION ${constants.SCHEMA_NAME}.reorg_checkpoint()
258
409
  RETURNS TRIGGER AS $$
259
410
  DECLARE
260
- id_col TEXT := TG_ARGV[0]::TEXT;
261
- order_key INTEGER := TG_ARGV[1]::INTEGER;
262
- indexer_id TEXT := TG_ARGV[2]::TEXT;
411
+ table_name TEXT := TG_ARGV[0]::TEXT;
412
+ id_col TEXT := TG_ARGV[1]::TEXT;
413
+ order_key INTEGER := TG_ARGV[2]::INTEGER;
414
+ indexer_id TEXT := TG_ARGV[3]::TEXT;
263
415
  new_id_value TEXT := row_to_json(NEW.*)->>id_col;
264
416
  old_id_value TEXT := row_to_json(OLD.*)->>id_col;
265
417
  BEGIN
266
418
  IF (TG_OP = 'DELETE') THEN
267
- INSERT INTO __reorg_rollback(op, table_name, cursor, row_id, row_value, indexer_id)
268
- SELECT 'D', TG_TABLE_NAME, order_key, old_id_value, row_to_json(OLD.*), indexer_id;
419
+ INSERT INTO ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
420
+ SELECT 'D', table_name, order_key, old_id_value, row_to_json(OLD.*), indexer_id;
269
421
  ELSIF (TG_OP = 'UPDATE') THEN
270
- INSERT INTO __reorg_rollback(op, table_name, cursor, row_id, row_value, indexer_id)
271
- SELECT 'U', TG_TABLE_NAME, order_key, new_id_value, row_to_json(OLD.*), indexer_id;
422
+ INSERT INTO ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
423
+ SELECT 'U', table_name, order_key, new_id_value, row_to_json(OLD.*), indexer_id;
272
424
  ELSIF (TG_OP = 'INSERT') THEN
273
- INSERT INTO __reorg_rollback(op, table_name, cursor, row_id, row_value, indexer_id)
274
- SELECT 'I', TG_TABLE_NAME, order_key, new_id_value, null, indexer_id;
425
+ INSERT INTO ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
426
+ SELECT 'I', table_name, order_key, new_id_value, null, indexer_id;
275
427
  END IF;
276
428
  RETURN NULL;
277
429
  END;
@@ -287,9 +439,10 @@ async function initializeReorgRollbackTable(tx, indexerId) {
287
439
  );
288
440
  }
289
441
  }
290
- async function registerTriggers(tx, tables, endCursor, idColumn, indexerId) {
442
+ async function registerTriggers(tx, tables, endCursor, idColumnMap, indexerId) {
291
443
  try {
292
444
  for (const table of tables) {
445
+ const tableIdColumn = getIdColumnForTable(table, idColumnMap);
293
446
  await tx.execute(
294
447
  drizzleOrm.sql.raw(
295
448
  `DROP TRIGGER IF EXISTS ${getReorgTriggerName(table, indexerId)} ON ${table};`
@@ -300,7 +453,7 @@ async function registerTriggers(tx, tables, endCursor, idColumn, indexerId) {
300
453
  CREATE CONSTRAINT TRIGGER ${getReorgTriggerName(table, indexerId)}
301
454
  AFTER INSERT OR UPDATE OR DELETE ON ${table}
302
455
  DEFERRABLE INITIALLY DEFERRED
303
- FOR EACH ROW EXECUTE FUNCTION reorg_checkpoint('${idColumn}', ${`${Number(endCursor.orderKey)}`}, '${indexerId}');
456
+ FOR EACH ROW EXECUTE FUNCTION ${constants.SCHEMA_NAME}.reorg_checkpoint('${table}', '${tableIdColumn}', ${Number(endCursor.orderKey)}, '${indexerId}');
304
457
  `)
305
458
  );
306
459
  }
@@ -325,11 +478,11 @@ async function removeTriggers(db, tables, indexerId) {
325
478
  });
326
479
  }
327
480
  }
328
- async function invalidate(tx, cursor, idColumn, indexerId) {
481
+ async function invalidate(tx, cursor, idColumnMap, indexerId) {
329
482
  const { rows: result } = await tx.execute(
330
483
  drizzleOrm.sql.raw(`
331
484
  WITH deleted AS (
332
- DELETE FROM __reorg_rollback
485
+ DELETE FROM ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
333
486
  WHERE cursor > ${Number(cursor.orderKey)}
334
487
  AND indexer_id = '${indexerId}'
335
488
  RETURNING *
@@ -343,6 +496,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
343
496
  );
344
497
  }
345
498
  for (const op of result) {
499
+ const tableIdColumn = getIdColumnForTable(op.table_name, idColumnMap);
346
500
  switch (op.op) {
347
501
  case "I":
348
502
  try {
@@ -352,7 +506,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
352
506
  await tx.execute(
353
507
  drizzleOrm.sql.raw(`
354
508
  DELETE FROM ${op.table_name}
355
- WHERE ${idColumn} = '${op.row_id}'
509
+ WHERE ${tableIdColumn} = '${op.row_id}'
356
510
  `)
357
511
  );
358
512
  } catch (error) {
@@ -392,7 +546,9 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
392
546
  );
393
547
  }
394
548
  const rowValue = typeof op.row_value === "string" ? JSON.parse(op.row_value) : op.row_value;
395
- const nonIdKeys = Object.keys(rowValue).filter((k) => k !== idColumn);
549
+ const nonIdKeys = Object.keys(rowValue).filter(
550
+ (k) => k !== tableIdColumn
551
+ );
396
552
  const fields = nonIdKeys.map((c) => `${c} = prev.${c}`).join(", ");
397
553
  const query = drizzleOrm.sql.raw(`
398
554
  UPDATE ${op.table_name}
@@ -400,7 +556,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
400
556
  FROM (
401
557
  SELECT * FROM json_populate_record(null::${op.table_name}, '${JSON.stringify(op.row_value)}'::json)
402
558
  ) as prev
403
- WHERE ${op.table_name}.${idColumn} = '${op.row_id}'
559
+ WHERE ${op.table_name}.${tableIdColumn} = '${op.row_id}'
404
560
  `);
405
561
  await tx.execute(query);
406
562
  } catch (error) {
@@ -422,7 +578,7 @@ async function finalize(tx, cursor, indexerId) {
422
578
  try {
423
579
  await tx.execute(
424
580
  drizzleOrm.sql.raw(`
425
- DELETE FROM __reorg_rollback
581
+ DELETE FROM ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
426
582
  WHERE cursor <= ${Number(cursor.orderKey)}
427
583
  AND indexer_id = '${indexerId}'
428
584
  `)
@@ -433,52 +589,134 @@ async function finalize(tx, cursor, indexerId) {
433
589
  });
434
590
  }
435
591
  }
592
+ async function cleanupStorage(tx, tables, indexerId) {
593
+ try {
594
+ for (const table of tables) {
595
+ await tx.execute(
596
+ drizzleOrm.sql.raw(
597
+ `DROP TRIGGER IF EXISTS ${getReorgTriggerName(table, indexerId)} ON ${table};`
598
+ )
599
+ );
600
+ }
601
+ await tx.execute(
602
+ drizzleOrm.sql.raw(`
603
+ DELETE FROM ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
604
+ WHERE indexer_id = '${indexerId}'
605
+ `)
606
+ );
607
+ for (const table of tables) {
608
+ try {
609
+ await tx.execute(drizzleOrm.sql.raw(`TRUNCATE TABLE ${table} CASCADE;`));
610
+ } catch (error) {
611
+ throw new DrizzleStorageError(`Failed to truncate table ${table}`, {
612
+ cause: error
613
+ });
614
+ }
615
+ }
616
+ } catch (error) {
617
+ throw new DrizzleStorageError("Failed to clean up storage", {
618
+ cause: error
619
+ });
620
+ }
621
+ }
436
622
 
437
- const DRIZZLE_PROPERTY = "_drizzle";
438
623
  const MAX_RETRIES = 5;
439
624
  function useDrizzleStorage(_db) {
440
625
  const context = indexer.useIndexerContext();
441
- if (!context[DRIZZLE_PROPERTY]) {
626
+ if (!context[constants.DRIZZLE_PROPERTY]) {
442
627
  throw new DrizzleStorageError(
443
628
  "drizzle storage is not available. Did you register the plugin?"
444
629
  );
445
630
  }
446
- return context[DRIZZLE_PROPERTY];
631
+ return context[constants.DRIZZLE_PROPERTY];
632
+ }
633
+ function useTestDrizzleStorage() {
634
+ const context = indexer.useIndexerContext();
635
+ if (!context[constants.DRIZZLE_STORAGE_DB_PROPERTY]) {
636
+ throw new DrizzleStorageError(
637
+ "drizzle storage db is not available. Did you register the plugin?"
638
+ );
639
+ }
640
+ return context[constants.DRIZZLE_STORAGE_DB_PROPERTY];
447
641
  }
448
642
  function drizzleStorage({
449
643
  db,
450
644
  persistState: enablePersistence = true,
451
645
  indexerName: identifier = "default",
452
- schema,
453
- idColumn = "id"
646
+ schema: _schema,
647
+ idColumn,
648
+ migrate: migrateOptions,
649
+ recordChainReorganizations = false
454
650
  }) {
455
- return plugins.defineIndexerPlugin((indexer) => {
651
+ return plugins.defineIndexerPlugin((indexer$1) => {
456
652
  let tableNames = [];
457
653
  let indexerId = "";
654
+ const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
655
+ let prevFinality;
656
+ const schema = _schema ?? db._.schema ?? {};
657
+ const idColumnMap = {
658
+ "*": typeof idColumn === "string" ? idColumn : "id",
659
+ ...typeof idColumn === "object" ? idColumn : {}
660
+ };
458
661
  try {
459
- tableNames = Object.values(schema ?? db._.schema ?? {}).map(
460
- (table) => table.dbName
461
- );
662
+ tableNames = Object.values(schema).map((table) => table.dbName);
462
663
  } catch (error) {
463
664
  throw new DrizzleStorageError("Failed to get table names from schema", {
464
665
  cause: error
465
666
  });
466
667
  }
467
- indexer.hooks.hook("run:before", async () => {
468
- const { indexerName: indexerFileName, availableIndexers } = plugins$1.useInternalContext();
668
+ for (const table of Object.values(schema)) {
669
+ const columns = table.columns;
670
+ const tableIdColumn = getIdColumnForTable(table.dbName, idColumnMap);
671
+ const columnExists = Object.values(columns).some(
672
+ (column) => column.name === tableIdColumn
673
+ );
674
+ if (!columnExists) {
675
+ throw new DrizzleStorageError(
676
+ `Column \`"${tableIdColumn}"\` does not exist in table \`"${table.dbName}"\`. Make sure the table has the specified column or provide a valid \`idColumn\` mapping to \`drizzleStorage\`.`
677
+ );
678
+ }
679
+ }
680
+ indexer$1.hooks.hook("plugins:init", async () => {
681
+ const internalContext = plugins$1.useInternalContext();
682
+ const context = indexer.useIndexerContext();
683
+ const logger = plugins.useLogger();
684
+ context[constants.DRIZZLE_STORAGE_DB_PROPERTY] = db;
685
+ const { indexerName: indexerFileName, availableIndexers } = internalContext;
469
686
  indexerId = internal.generateIndexerId(indexerFileName, identifier);
470
687
  let retries = 0;
688
+ let migrationsApplied = false;
689
+ let cleanupApplied = false;
471
690
  while (retries <= MAX_RETRIES) {
472
691
  try {
692
+ if (migrateOptions && !migrationsApplied) {
693
+ await migrate(db, migrateOptions);
694
+ migrationsApplied = true;
695
+ logger.success("Migrations applied");
696
+ }
473
697
  await withTransaction(db, async (tx) => {
474
698
  await initializeReorgRollbackTable(tx, indexerId);
475
699
  if (enablePersistence) {
476
700
  await initializePersistentState(tx);
477
701
  }
702
+ if (alwaysReindex && !cleanupApplied) {
703
+ logger.warn(
704
+ `Reindexing: Deleting all data from tables - ${tableNames.join(", ")}`
705
+ );
706
+ await cleanupStorage(tx, tableNames, indexerId);
707
+ if (enablePersistence) {
708
+ await resetPersistence({ tx, indexerId });
709
+ }
710
+ cleanupApplied = true;
711
+ logger.success("Tables have been cleaned up for reindexing");
712
+ }
478
713
  });
479
714
  break;
480
715
  } catch (error) {
481
716
  if (retries === MAX_RETRIES) {
717
+ if (error instanceof DrizzleStorageError) {
718
+ throw error;
719
+ }
482
720
  throw new DrizzleStorageError(
483
721
  "Initialization failed after 5 retries",
484
722
  {
@@ -491,7 +729,7 @@ function drizzleStorage({
491
729
  }
492
730
  }
493
731
  });
494
- indexer.hooks.hook("connect:before", async ({ request }) => {
732
+ indexer$1.hooks.hook("connect:before", async ({ request }) => {
495
733
  if (!enablePersistence) {
496
734
  return;
497
735
  }
@@ -508,19 +746,19 @@ function drizzleStorage({
508
746
  }
509
747
  });
510
748
  });
511
- indexer.hooks.hook("connect:after", async ({ request }) => {
749
+ indexer$1.hooks.hook("connect:after", async ({ request }) => {
512
750
  const cursor = request.startingCursor;
513
751
  if (!cursor) {
514
752
  return;
515
753
  }
516
754
  await withTransaction(db, async (tx) => {
517
- await invalidate(tx, cursor, idColumn, indexerId);
755
+ await invalidate(tx, cursor, idColumnMap, indexerId);
518
756
  if (enablePersistence) {
519
757
  await invalidateState({ tx, cursor, indexerId });
520
758
  }
521
759
  });
522
760
  });
523
- indexer.hooks.hook("connect:factory", async ({ request, endCursor }) => {
761
+ indexer$1.hooks.hook("connect:factory", async ({ request, endCursor }) => {
524
762
  if (!enablePersistence) {
525
763
  return;
526
764
  }
@@ -534,8 +772,8 @@ function drizzleStorage({
534
772
  });
535
773
  }
536
774
  });
537
- indexer.hooks.hook("message:finalize", async ({ message }) => {
538
- const { cursor } = message.finalize;
775
+ indexer$1.hooks.hook("message:finalize", async ({ message }) => {
776
+ const { cursor } = message;
539
777
  if (!cursor) {
540
778
  throw new DrizzleStorageError("Finalized Cursor is undefined");
541
779
  }
@@ -546,59 +784,79 @@ function drizzleStorage({
546
784
  }
547
785
  });
548
786
  });
549
- indexer.hooks.hook("message:invalidate", async ({ message }) => {
550
- const { cursor } = message.invalidate;
787
+ indexer$1.hooks.hook("message:invalidate", async ({ message }) => {
788
+ const { cursor } = message;
551
789
  if (!cursor) {
552
790
  throw new DrizzleStorageError("Invalidate Cursor is undefined");
553
791
  }
554
792
  await withTransaction(db, async (tx) => {
555
- await invalidate(tx, cursor, idColumn, indexerId);
793
+ let oldHead;
794
+ if (recordChainReorganizations) {
795
+ const { cursor: currentCursor } = await getState({
796
+ tx,
797
+ indexerId
798
+ });
799
+ oldHead = currentCursor;
800
+ await recordChainReorganization({
801
+ tx,
802
+ indexerId,
803
+ oldHead,
804
+ newHead: cursor
805
+ });
806
+ }
807
+ await invalidate(tx, cursor, idColumnMap, indexerId);
556
808
  if (enablePersistence) {
557
809
  await invalidateState({ tx, cursor, indexerId });
558
810
  }
559
811
  });
560
812
  });
561
- indexer.hooks.hook("handler:middleware", async ({ use }) => {
813
+ indexer$1.hooks.hook("handler:middleware", async ({ use }) => {
562
814
  use(async (context, next) => {
563
815
  try {
564
- const { endCursor, finality } = context;
816
+ const { endCursor, finality, cursor } = context;
565
817
  if (!endCursor) {
566
818
  throw new DrizzleStorageError("End Cursor is undefined");
567
819
  }
568
820
  await withTransaction(db, async (tx) => {
569
- context[DRIZZLE_PROPERTY] = { db: tx };
821
+ context[constants.DRIZZLE_PROPERTY] = { db: tx };
822
+ if (prevFinality === "pending") {
823
+ await invalidate(tx, cursor, idColumnMap, indexerId);
824
+ }
570
825
  if (finality !== "finalized") {
571
826
  await registerTriggers(
572
827
  tx,
573
828
  tableNames,
574
829
  endCursor,
575
- idColumn,
830
+ idColumnMap,
576
831
  indexerId
577
832
  );
578
833
  }
579
834
  await next();
580
- delete context[DRIZZLE_PROPERTY];
581
- if (enablePersistence) {
835
+ delete context[constants.DRIZZLE_PROPERTY];
836
+ if (enablePersistence && finality !== "pending") {
582
837
  await persistState({
583
838
  tx,
584
839
  endCursor,
585
840
  indexerId
586
841
  });
587
842
  }
843
+ prevFinality = finality;
588
844
  });
589
845
  if (finality !== "finalized") {
590
846
  await removeTriggers(db, tableNames, indexerId);
591
847
  }
592
848
  } catch (error) {
593
849
  await removeTriggers(db, tableNames, indexerId);
594
- throw new DrizzleStorageError("Failed to run handler:middleware", {
595
- cause: error
596
- });
850
+ throw error;
597
851
  }
598
852
  });
599
853
  });
600
854
  });
601
855
  }
602
856
 
857
+ exports.drizzle = drizzle;
603
858
  exports.drizzleStorage = drizzleStorage;
859
+ exports.migrate = migrate;
604
860
  exports.useDrizzleStorage = useDrizzleStorage;
861
+ exports.useTestDrizzleStorage = useTestDrizzleStorage;
862
+ //# sourceMappingURL=index.cjs.map