@apibara/plugin-drizzle 2.1.0-beta.3 → 2.1.0-beta.30

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.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  import { useIndexerContext } from '@apibara/indexer';
2
- import { defineIndexerPlugin } from '@apibara/indexer/plugins';
2
+ import { defineIndexerPlugin, useLogger } from '@apibara/indexer/plugins';
3
3
  import { generateIndexerId } from '@apibara/indexer/internal';
4
4
  import { useInternalContext } from '@apibara/indexer/internal/plugins';
5
+ import { S as SCHEMA_NAME, D as DRIZZLE_PROPERTY, a as DRIZZLE_STORAGE_DB_PROPERTY } from './shared/plugin-drizzle.2d226351.mjs';
6
+ import { entityKind, sql, eq, and, isNull, gt, lt } from 'drizzle-orm';
5
7
  import { normalizeCursor } from '@apibara/protocol';
6
- import { eq, and, isNull, gt, lt, sql } from 'drizzle-orm';
7
- import { pgTable, text, integer, primaryKey, serial, char, jsonb } from 'drizzle-orm/pg-core';
8
+ import { pgSchema, text, integer, primaryKey, serial, char, jsonb } from 'drizzle-orm/pg-core';
8
9
 
9
10
  class DrizzleStorageError extends Error {
10
11
  constructor(message, options) {
@@ -33,16 +34,89 @@ function serialize(obj) {
33
34
  function sleep(ms) {
34
35
  return new Promise((resolve) => setTimeout(resolve, ms));
35
36
  }
37
+ const getIdColumnForTable = (tableName, idColumn) => {
38
+ if (idColumn[tableName]) {
39
+ return idColumn[tableName];
40
+ }
41
+ return idColumn["*"];
42
+ };
43
+
44
+ function drizzle(options) {
45
+ const {
46
+ connectionString = process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://",
47
+ schema,
48
+ type = "pglite",
49
+ config,
50
+ poolConfig
51
+ } = options ?? {};
52
+ if (isPgliteConnectionString(connectionString) && type === "pglite") {
53
+ const { drizzle: drizzlePGLite } = require("drizzle-orm/pglite");
54
+ return drizzlePGLite({
55
+ schema,
56
+ connection: {
57
+ dataDir: connectionString || "memory://pglite"
58
+ },
59
+ ...config || {}
60
+ });
61
+ }
62
+ const { Pool } = require("pg");
63
+ const { drizzle: drizzleNode } = require("drizzle-orm/node-postgres");
64
+ const pool = new Pool({
65
+ connectionString,
66
+ ...poolConfig || {}
67
+ });
68
+ return drizzleNode(pool, { schema, ...config || {} });
69
+ }
70
+ async function migrate(db, options) {
71
+ const isPglite = isDrizzleKind(db, "PgliteDatabase");
72
+ try {
73
+ if (isPglite) {
74
+ const { migrate: migratePGLite } = require("drizzle-orm/pglite/migrator");
75
+ await migratePGLite(db, options);
76
+ } else {
77
+ const {
78
+ migrate: migrateNode
79
+ } = require("drizzle-orm/node-postgres/migrator");
80
+ await migrateNode(db, options);
81
+ }
82
+ } catch (error) {
83
+ throw new DrizzleStorageError(
84
+ "Failed to apply migrations! Please check if you have generated migrations using drizzle:generate",
85
+ {
86
+ cause: error
87
+ }
88
+ );
89
+ }
90
+ }
91
+ function isPgliteConnectionString(conn) {
92
+ return conn.startsWith("memory://") || conn.startsWith("file://") || conn.startsWith("idb://");
93
+ }
94
+ function isDrizzleKind(value, entityKindValue) {
95
+ if (!value || typeof value !== "object") {
96
+ return false;
97
+ }
98
+ let cls = Object.getPrototypeOf(value).constructor;
99
+ if (cls) {
100
+ while (cls) {
101
+ if (entityKind in cls && cls[entityKind] === entityKindValue) {
102
+ return true;
103
+ }
104
+ cls = Object.getPrototypeOf(cls);
105
+ }
106
+ }
107
+ return false;
108
+ }
36
109
 
37
- const CHECKPOINTS_TABLE_NAME = "__indexer_checkpoints";
38
- const FILTERS_TABLE_NAME = "__indexer_filters";
39
- const SCHEMA_VERSION_TABLE_NAME = "__indexer_schema_version";
40
- const checkpoints = pgTable(CHECKPOINTS_TABLE_NAME, {
110
+ const CHECKPOINTS_TABLE_NAME = "checkpoints";
111
+ const FILTERS_TABLE_NAME = "filters";
112
+ const SCHEMA_VERSION_TABLE_NAME = "schema_version";
113
+ const schema$1 = pgSchema(SCHEMA_NAME);
114
+ const checkpoints = schema$1.table(CHECKPOINTS_TABLE_NAME, {
41
115
  id: text("id").notNull().primaryKey(),
42
116
  orderKey: integer("order_key").notNull(),
43
117
  uniqueKey: text("unique_key")
44
118
  });
45
- const filters = pgTable(
119
+ const filters = schema$1.table(
46
120
  FILTERS_TABLE_NAME,
47
121
  {
48
122
  id: text("id").notNull(),
@@ -56,7 +130,7 @@ const filters = pgTable(
56
130
  }
57
131
  ]
58
132
  );
59
- const schemaVersion = pgTable(SCHEMA_VERSION_TABLE_NAME, {
133
+ const schemaVersion = schema$1.table(SCHEMA_VERSION_TABLE_NAME, {
60
134
  k: integer("k").notNull().primaryKey(),
61
135
  version: integer("version").notNull()
62
136
  });
@@ -67,12 +141,19 @@ const MIGRATIONS = [
67
141
  // Add more migration arrays for future versions
68
142
  ];
69
143
  async function initializePersistentState(tx) {
70
- await tx.execute(`
71
- CREATE TABLE IF NOT EXISTS ${SCHEMA_VERSION_TABLE_NAME} (
144
+ await tx.execute(
145
+ sql.raw(`
146
+ CREATE SCHEMA IF NOT EXISTS ${SCHEMA_NAME};
147
+ `)
148
+ );
149
+ await tx.execute(
150
+ sql.raw(`
151
+ CREATE TABLE IF NOT EXISTS ${SCHEMA_NAME}.${SCHEMA_VERSION_TABLE_NAME} (
72
152
  k INTEGER PRIMARY KEY,
73
153
  version INTEGER NOT NULL
74
154
  );
75
- `);
155
+ `)
156
+ );
76
157
  const versionRows = await tx.select().from(schemaVersion).where(eq(schemaVersion.k, 0));
77
158
  const storedVersion = versionRows[0]?.version ?? -1;
78
159
  if (storedVersion > CURRENT_SCHEMA_VERSION) {
@@ -82,22 +163,26 @@ async function initializePersistentState(tx) {
82
163
  }
83
164
  try {
84
165
  if (storedVersion === -1) {
85
- await tx.execute(`
86
- CREATE TABLE IF NOT EXISTS ${CHECKPOINTS_TABLE_NAME} (
166
+ await tx.execute(
167
+ sql.raw(`
168
+ CREATE TABLE IF NOT EXISTS ${SCHEMA_NAME}.${CHECKPOINTS_TABLE_NAME} (
87
169
  id TEXT PRIMARY KEY,
88
170
  order_key INTEGER NOT NULL,
89
171
  unique_key TEXT
90
172
  );
91
- `);
92
- await tx.execute(`
93
- CREATE TABLE IF NOT EXISTS ${FILTERS_TABLE_NAME} (
173
+ `)
174
+ );
175
+ await tx.execute(
176
+ sql.raw(`
177
+ CREATE TABLE IF NOT EXISTS ${SCHEMA_NAME}.${FILTERS_TABLE_NAME} (
94
178
  id TEXT NOT NULL,
95
179
  filter TEXT NOT NULL,
96
180
  from_block INTEGER NOT NULL,
97
181
  to_block INTEGER DEFAULT NULL,
98
182
  PRIMARY KEY (id, from_block)
99
183
  );
100
- `);
184
+ `)
185
+ );
101
186
  await tx.insert(schemaVersion).values({
102
187
  k: 0,
103
188
  version: CURRENT_SCHEMA_VERSION
@@ -132,7 +217,9 @@ async function persistState(props) {
132
217
  target: checkpoints.id,
133
218
  set: {
134
219
  orderKey: Number(endCursor.orderKey),
135
- uniqueKey: endCursor.uniqueKey
220
+ // Explicitly set the unique key to `null` to indicate that it has been deleted
221
+ // Otherwise drizzle will not update its value.
222
+ uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null
136
223
  }
137
224
  });
138
225
  if (filter) {
@@ -211,11 +298,24 @@ async function finalizeState(props) {
211
298
  });
212
299
  }
213
300
  }
301
+ async function resetPersistence(props) {
302
+ const { tx, indexerId } = props;
303
+ try {
304
+ await tx.delete(checkpoints).where(eq(checkpoints.id, indexerId));
305
+ await tx.delete(filters).where(eq(filters.id, indexerId));
306
+ } catch (error) {
307
+ throw new DrizzleStorageError("Failed to reset persistence state", {
308
+ cause: error
309
+ });
310
+ }
311
+ }
214
312
 
313
+ const ROLLBACK_TABLE_NAME = "reorg_rollback";
314
+ const schema = pgSchema(SCHEMA_NAME);
215
315
  function getReorgTriggerName(table, indexerId) {
216
316
  return `${table}_reorg_${indexerId}`;
217
317
  }
218
- pgTable("__reorg_rollback", {
318
+ schema.table(ROLLBACK_TABLE_NAME, {
219
319
  n: serial("n").primaryKey(),
220
320
  op: char("op", { length: 1 }).$type().notNull(),
221
321
  table_name: text("table_name").notNull(),
@@ -226,9 +326,12 @@ pgTable("__reorg_rollback", {
226
326
  });
227
327
  async function initializeReorgRollbackTable(tx, indexerId) {
228
328
  try {
329
+ await tx.execute(`
330
+ CREATE SCHEMA IF NOT EXISTS ${SCHEMA_NAME};
331
+ `);
229
332
  await tx.execute(
230
333
  sql.raw(`
231
- CREATE TABLE IF NOT EXISTS __reorg_rollback(
334
+ CREATE TABLE IF NOT EXISTS ${SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(
232
335
  n SERIAL PRIMARY KEY,
233
336
  op CHAR(1) NOT NULL,
234
337
  table_name TEXT NOT NULL,
@@ -241,7 +344,7 @@ async function initializeReorgRollbackTable(tx, indexerId) {
241
344
  );
242
345
  await tx.execute(
243
346
  sql.raw(`
244
- CREATE INDEX IF NOT EXISTS idx_reorg_rollback_indexer_id_cursor ON __reorg_rollback(indexer_id, cursor);
347
+ CREATE INDEX IF NOT EXISTS idx_reorg_rollback_indexer_id_cursor ON ${SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(indexer_id, cursor);
245
348
  `)
246
349
  );
247
350
  } catch (error) {
@@ -252,24 +355,25 @@ async function initializeReorgRollbackTable(tx, indexerId) {
252
355
  try {
253
356
  await tx.execute(
254
357
  sql.raw(`
255
- CREATE OR REPLACE FUNCTION reorg_checkpoint()
358
+ CREATE OR REPLACE FUNCTION ${SCHEMA_NAME}.reorg_checkpoint()
256
359
  RETURNS TRIGGER AS $$
257
360
  DECLARE
258
- id_col TEXT := TG_ARGV[0]::TEXT;
259
- order_key INTEGER := TG_ARGV[1]::INTEGER;
260
- indexer_id TEXT := TG_ARGV[2]::TEXT;
361
+ table_name TEXT := TG_ARGV[0]::TEXT;
362
+ id_col TEXT := TG_ARGV[1]::TEXT;
363
+ order_key INTEGER := TG_ARGV[2]::INTEGER;
364
+ indexer_id TEXT := TG_ARGV[3]::TEXT;
261
365
  new_id_value TEXT := row_to_json(NEW.*)->>id_col;
262
366
  old_id_value TEXT := row_to_json(OLD.*)->>id_col;
263
367
  BEGIN
264
368
  IF (TG_OP = 'DELETE') THEN
265
- INSERT INTO __reorg_rollback(op, table_name, cursor, row_id, row_value, indexer_id)
266
- SELECT 'D', TG_TABLE_NAME, order_key, old_id_value, row_to_json(OLD.*), indexer_id;
369
+ INSERT INTO ${SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
370
+ SELECT 'D', table_name, order_key, old_id_value, row_to_json(OLD.*), indexer_id;
267
371
  ELSIF (TG_OP = 'UPDATE') THEN
268
- INSERT INTO __reorg_rollback(op, table_name, cursor, row_id, row_value, indexer_id)
269
- SELECT 'U', TG_TABLE_NAME, order_key, new_id_value, row_to_json(OLD.*), indexer_id;
372
+ INSERT INTO ${SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
373
+ SELECT 'U', table_name, order_key, new_id_value, row_to_json(OLD.*), indexer_id;
270
374
  ELSIF (TG_OP = 'INSERT') THEN
271
- INSERT INTO __reorg_rollback(op, table_name, cursor, row_id, row_value, indexer_id)
272
- SELECT 'I', TG_TABLE_NAME, order_key, new_id_value, null, indexer_id;
375
+ INSERT INTO ${SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
376
+ SELECT 'I', table_name, order_key, new_id_value, null, indexer_id;
273
377
  END IF;
274
378
  RETURN NULL;
275
379
  END;
@@ -285,9 +389,10 @@ async function initializeReorgRollbackTable(tx, indexerId) {
285
389
  );
286
390
  }
287
391
  }
288
- async function registerTriggers(tx, tables, endCursor, idColumn, indexerId) {
392
+ async function registerTriggers(tx, tables, endCursor, idColumnMap, indexerId) {
289
393
  try {
290
394
  for (const table of tables) {
395
+ const tableIdColumn = getIdColumnForTable(table, idColumnMap);
291
396
  await tx.execute(
292
397
  sql.raw(
293
398
  `DROP TRIGGER IF EXISTS ${getReorgTriggerName(table, indexerId)} ON ${table};`
@@ -298,7 +403,7 @@ async function registerTriggers(tx, tables, endCursor, idColumn, indexerId) {
298
403
  CREATE CONSTRAINT TRIGGER ${getReorgTriggerName(table, indexerId)}
299
404
  AFTER INSERT OR UPDATE OR DELETE ON ${table}
300
405
  DEFERRABLE INITIALLY DEFERRED
301
- FOR EACH ROW EXECUTE FUNCTION reorg_checkpoint('${idColumn}', ${`${Number(endCursor.orderKey)}`}, '${indexerId}');
406
+ FOR EACH ROW EXECUTE FUNCTION ${SCHEMA_NAME}.reorg_checkpoint('${table}', '${tableIdColumn}', ${Number(endCursor.orderKey)}, '${indexerId}');
302
407
  `)
303
408
  );
304
409
  }
@@ -323,11 +428,11 @@ async function removeTriggers(db, tables, indexerId) {
323
428
  });
324
429
  }
325
430
  }
326
- async function invalidate(tx, cursor, idColumn, indexerId) {
431
+ async function invalidate(tx, cursor, idColumnMap, indexerId) {
327
432
  const { rows: result } = await tx.execute(
328
433
  sql.raw(`
329
434
  WITH deleted AS (
330
- DELETE FROM __reorg_rollback
435
+ DELETE FROM ${SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
331
436
  WHERE cursor > ${Number(cursor.orderKey)}
332
437
  AND indexer_id = '${indexerId}'
333
438
  RETURNING *
@@ -341,6 +446,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
341
446
  );
342
447
  }
343
448
  for (const op of result) {
449
+ const tableIdColumn = getIdColumnForTable(op.table_name, idColumnMap);
344
450
  switch (op.op) {
345
451
  case "I":
346
452
  try {
@@ -350,7 +456,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
350
456
  await tx.execute(
351
457
  sql.raw(`
352
458
  DELETE FROM ${op.table_name}
353
- WHERE ${idColumn} = '${op.row_id}'
459
+ WHERE ${tableIdColumn} = '${op.row_id}'
354
460
  `)
355
461
  );
356
462
  } catch (error) {
@@ -390,7 +496,9 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
390
496
  );
391
497
  }
392
498
  const rowValue = typeof op.row_value === "string" ? JSON.parse(op.row_value) : op.row_value;
393
- const nonIdKeys = Object.keys(rowValue).filter((k) => k !== idColumn);
499
+ const nonIdKeys = Object.keys(rowValue).filter(
500
+ (k) => k !== tableIdColumn
501
+ );
394
502
  const fields = nonIdKeys.map((c) => `${c} = prev.${c}`).join(", ");
395
503
  const query = sql.raw(`
396
504
  UPDATE ${op.table_name}
@@ -398,7 +506,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
398
506
  FROM (
399
507
  SELECT * FROM json_populate_record(null::${op.table_name}, '${JSON.stringify(op.row_value)}'::json)
400
508
  ) as prev
401
- WHERE ${op.table_name}.${idColumn} = '${op.row_id}'
509
+ WHERE ${op.table_name}.${tableIdColumn} = '${op.row_id}'
402
510
  `);
403
511
  await tx.execute(query);
404
512
  } catch (error) {
@@ -420,7 +528,7 @@ async function finalize(tx, cursor, indexerId) {
420
528
  try {
421
529
  await tx.execute(
422
530
  sql.raw(`
423
- DELETE FROM __reorg_rollback
531
+ DELETE FROM ${SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
424
532
  WHERE cursor <= ${Number(cursor.orderKey)}
425
533
  AND indexer_id = '${indexerId}'
426
534
  `)
@@ -431,8 +539,37 @@ async function finalize(tx, cursor, indexerId) {
431
539
  });
432
540
  }
433
541
  }
542
+ async function cleanupStorage(tx, tables, indexerId) {
543
+ try {
544
+ for (const table of tables) {
545
+ await tx.execute(
546
+ sql.raw(
547
+ `DROP TRIGGER IF EXISTS ${getReorgTriggerName(table, indexerId)} ON ${table};`
548
+ )
549
+ );
550
+ }
551
+ await tx.execute(
552
+ sql.raw(`
553
+ DELETE FROM ${SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
554
+ WHERE indexer_id = '${indexerId}'
555
+ `)
556
+ );
557
+ for (const table of tables) {
558
+ try {
559
+ await tx.execute(sql.raw(`TRUNCATE TABLE ${table} CASCADE;`));
560
+ } catch (error) {
561
+ throw new DrizzleStorageError(`Failed to truncate table ${table}`, {
562
+ cause: error
563
+ });
564
+ }
565
+ }
566
+ } catch (error) {
567
+ throw new DrizzleStorageError("Failed to clean up storage", {
568
+ cause: error
569
+ });
570
+ }
571
+ }
434
572
 
435
- const DRIZZLE_PROPERTY = "_drizzle";
436
573
  const MAX_RETRIES = 5;
437
574
  function useDrizzleStorage(_db) {
438
575
  const context = useIndexerContext();
@@ -447,36 +584,79 @@ function drizzleStorage({
447
584
  db,
448
585
  persistState: enablePersistence = true,
449
586
  indexerName: identifier = "default",
450
- schema,
451
- idColumn = "id"
587
+ schema: _schema,
588
+ idColumn,
589
+ migrate: migrateOptions
452
590
  }) {
453
591
  return defineIndexerPlugin((indexer) => {
454
592
  let tableNames = [];
455
593
  let indexerId = "";
594
+ const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
595
+ let prevFinality;
596
+ const schema = _schema ?? db._.schema ?? {};
597
+ const idColumnMap = {
598
+ "*": typeof idColumn === "string" ? idColumn : "id",
599
+ ...typeof idColumn === "object" ? idColumn : {}
600
+ };
456
601
  try {
457
- tableNames = Object.values(schema ?? db._.schema ?? {}).map(
458
- (table) => table.dbName
459
- );
602
+ tableNames = Object.values(schema).map((table) => table.dbName);
460
603
  } catch (error) {
461
604
  throw new DrizzleStorageError("Failed to get table names from schema", {
462
605
  cause: error
463
606
  });
464
607
  }
608
+ for (const table of Object.values(schema)) {
609
+ const columns = table.columns;
610
+ const tableIdColumn = getIdColumnForTable(table.dbName, idColumnMap);
611
+ const columnExists = Object.values(columns).some(
612
+ (column) => column.name === tableIdColumn
613
+ );
614
+ if (!columnExists) {
615
+ throw new DrizzleStorageError(
616
+ `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\`.`
617
+ );
618
+ }
619
+ }
465
620
  indexer.hooks.hook("run:before", async () => {
466
- const { indexerName: indexerFileName, availableIndexers } = useInternalContext();
621
+ const internalContext = useInternalContext();
622
+ const context = useIndexerContext();
623
+ const logger = useLogger();
624
+ context[DRIZZLE_STORAGE_DB_PROPERTY] = db;
625
+ const { indexerName: indexerFileName, availableIndexers } = internalContext;
467
626
  indexerId = generateIndexerId(indexerFileName, identifier);
468
627
  let retries = 0;
628
+ let migrationsApplied = false;
629
+ let cleanupApplied = false;
469
630
  while (retries <= MAX_RETRIES) {
470
631
  try {
632
+ if (migrateOptions && !migrationsApplied) {
633
+ await migrate(db, migrateOptions);
634
+ migrationsApplied = true;
635
+ logger.success("Migrations applied");
636
+ }
471
637
  await withTransaction(db, async (tx) => {
472
638
  await initializeReorgRollbackTable(tx, indexerId);
473
639
  if (enablePersistence) {
474
640
  await initializePersistentState(tx);
475
641
  }
642
+ if (alwaysReindex && !cleanupApplied) {
643
+ logger.warn(
644
+ `Reindexing: Deleting all data from tables - ${tableNames.join(", ")}`
645
+ );
646
+ await cleanupStorage(tx, tableNames, indexerId);
647
+ if (enablePersistence) {
648
+ await resetPersistence({ tx, indexerId });
649
+ }
650
+ cleanupApplied = true;
651
+ logger.success("Tables have been cleaned up for reindexing");
652
+ }
476
653
  });
477
654
  break;
478
655
  } catch (error) {
479
656
  if (retries === MAX_RETRIES) {
657
+ if (error instanceof DrizzleStorageError) {
658
+ throw error;
659
+ }
480
660
  throw new DrizzleStorageError(
481
661
  "Initialization failed after 5 retries",
482
662
  {
@@ -512,7 +692,7 @@ function drizzleStorage({
512
692
  return;
513
693
  }
514
694
  await withTransaction(db, async (tx) => {
515
- await invalidate(tx, cursor, idColumn, indexerId);
695
+ await invalidate(tx, cursor, idColumnMap, indexerId);
516
696
  if (enablePersistence) {
517
697
  await invalidateState({ tx, cursor, indexerId });
518
698
  }
@@ -533,7 +713,7 @@ function drizzleStorage({
533
713
  }
534
714
  });
535
715
  indexer.hooks.hook("message:finalize", async ({ message }) => {
536
- const { cursor } = message.finalize;
716
+ const { cursor } = message;
537
717
  if (!cursor) {
538
718
  throw new DrizzleStorageError("Finalized Cursor is undefined");
539
719
  }
@@ -545,12 +725,12 @@ function drizzleStorage({
545
725
  });
546
726
  });
547
727
  indexer.hooks.hook("message:invalidate", async ({ message }) => {
548
- const { cursor } = message.invalidate;
728
+ const { cursor } = message;
549
729
  if (!cursor) {
550
730
  throw new DrizzleStorageError("Invalidate Cursor is undefined");
551
731
  }
552
732
  await withTransaction(db, async (tx) => {
553
- await invalidate(tx, cursor, idColumn, indexerId);
733
+ await invalidate(tx, cursor, idColumnMap, indexerId);
554
734
  if (enablePersistence) {
555
735
  await invalidateState({ tx, cursor, indexerId });
556
736
  }
@@ -559,30 +739,34 @@ function drizzleStorage({
559
739
  indexer.hooks.hook("handler:middleware", async ({ use }) => {
560
740
  use(async (context, next) => {
561
741
  try {
562
- const { endCursor, finality } = context;
742
+ const { endCursor, finality, cursor } = context;
563
743
  if (!endCursor) {
564
744
  throw new DrizzleStorageError("End Cursor is undefined");
565
745
  }
566
746
  await withTransaction(db, async (tx) => {
567
747
  context[DRIZZLE_PROPERTY] = { db: tx };
748
+ if (prevFinality === "pending") {
749
+ await invalidate(tx, cursor, idColumnMap, indexerId);
750
+ }
568
751
  if (finality !== "finalized") {
569
752
  await registerTriggers(
570
753
  tx,
571
754
  tableNames,
572
755
  endCursor,
573
- idColumn,
756
+ idColumnMap,
574
757
  indexerId
575
758
  );
576
759
  }
577
760
  await next();
578
761
  delete context[DRIZZLE_PROPERTY];
579
- if (enablePersistence) {
762
+ if (enablePersistence && finality !== "pending") {
580
763
  await persistState({
581
764
  tx,
582
765
  endCursor,
583
766
  indexerId
584
767
  });
585
768
  }
769
+ prevFinality = finality;
586
770
  });
587
771
  if (finality !== "finalized") {
588
772
  await removeTriggers(db, tableNames, indexerId);
@@ -598,4 +782,4 @@ function drizzleStorage({
598
782
  });
599
783
  }
600
784
 
601
- export { drizzleStorage, useDrizzleStorage };
785
+ export { drizzle, drizzleStorage, migrate, useDrizzleStorage };
@@ -0,0 +1,5 @@
1
+ const DRIZZLE_PROPERTY = "_drizzle";
2
+ const DRIZZLE_STORAGE_DB_PROPERTY = "_drizzleStorageDB";
3
+ const SCHEMA_NAME = "airfoil";
4
+
5
+ export { DRIZZLE_PROPERTY as D, SCHEMA_NAME as S, DRIZZLE_STORAGE_DB_PROPERTY as a };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const DRIZZLE_PROPERTY = "_drizzle";
4
+ const DRIZZLE_STORAGE_DB_PROPERTY = "_drizzleStorageDB";
5
+ const SCHEMA_NAME = "airfoil";
6
+
7
+ exports.DRIZZLE_PROPERTY = DRIZZLE_PROPERTY;
8
+ exports.DRIZZLE_STORAGE_DB_PROPERTY = DRIZZLE_STORAGE_DB_PROPERTY;
9
+ exports.SCHEMA_NAME = SCHEMA_NAME;
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const constants = require('./shared/plugin-drizzle.cae20704.cjs');
4
+
5
+ function getTestDatabase(context) {
6
+ const db = context[constants.DRIZZLE_STORAGE_DB_PROPERTY];
7
+ if (!db) {
8
+ throw new Error("Drizzle database not found in context");
9
+ }
10
+ return db;
11
+ }
12
+
13
+ exports.getTestDatabase = getTestDatabase;
@@ -0,0 +1,6 @@
1
+ import { VcrResult } from '@apibara/indexer/testing';
2
+ import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
3
+
4
+ declare function getTestDatabase(context: VcrResult): PgDatabase<PgQueryResultHKT>;
5
+
6
+ export { getTestDatabase };
@@ -0,0 +1,6 @@
1
+ import { VcrResult } from '@apibara/indexer/testing';
2
+ import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
3
+
4
+ declare function getTestDatabase(context: VcrResult): PgDatabase<PgQueryResultHKT>;
5
+
6
+ export { getTestDatabase };
@@ -0,0 +1,6 @@
1
+ import { VcrResult } from '@apibara/indexer/testing';
2
+ import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
3
+
4
+ declare function getTestDatabase(context: VcrResult): PgDatabase<PgQueryResultHKT>;
5
+
6
+ export { getTestDatabase };
@@ -0,0 +1,11 @@
1
+ import { a as DRIZZLE_STORAGE_DB_PROPERTY } from './shared/plugin-drizzle.2d226351.mjs';
2
+
3
+ function getTestDatabase(context) {
4
+ const db = context[DRIZZLE_STORAGE_DB_PROPERTY];
5
+ if (!db) {
6
+ throw new Error("Drizzle database not found in context");
7
+ }
8
+ return db;
9
+ }
10
+
11
+ export { getTestDatabase };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apibara/plugin-drizzle",
3
- "version": "2.1.0-beta.3",
3
+ "version": "2.1.0-beta.30",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -15,6 +15,12 @@
15
15
  "import": "./dist/index.mjs",
16
16
  "require": "./dist/index.cjs",
17
17
  "default": "./dist/index.mjs"
18
+ },
19
+ "./testing": {
20
+ "types": "./dist/testing.d.ts",
21
+ "import": "./dist/testing.mjs",
22
+ "require": "./dist/testing.cjs",
23
+ "default": "./dist/testing.mjs"
18
24
  }
19
25
  },
20
26
  "scripts": {
@@ -26,21 +32,30 @@
26
32
  "test:ci": "vitest run"
27
33
  },
28
34
  "peerDependencies": {
29
- "drizzle-orm": "^0.37.0",
30
- "pg": "^8.13.1"
35
+ "@electric-sql/pglite": ">=0.2.0",
36
+ "drizzle-orm": "<1",
37
+ "pg": ">=8"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@electric-sql/pglite": {
41
+ "optional": true
42
+ },
43
+ "pg": {
44
+ "optional": true
45
+ }
31
46
  },
32
47
  "devDependencies": {
33
48
  "@electric-sql/pglite": "^0.2.17",
34
49
  "@types/node": "^20.14.0",
35
50
  "@types/pg": "^8.11.10",
36
- "drizzle-orm": "^0.37.0",
51
+ "drizzle-orm": "^0.40.1",
37
52
  "pg": "^8.13.1",
38
53
  "unbuild": "^2.0.0",
39
54
  "vitest": "^1.6.0"
40
55
  },
41
56
  "dependencies": {
42
- "@apibara/indexer": "2.1.0-beta.3",
43
- "@apibara/protocol": "2.1.0-beta.3",
57
+ "@apibara/indexer": "2.1.0-beta.30",
58
+ "@apibara/protocol": "2.1.0-beta.30",
44
59
  "postgres-range": "^1.1.4"
45
60
  }
46
61
  }
@@ -0,0 +1,3 @@
1
+ export const DRIZZLE_PROPERTY = "_drizzle";
2
+ export const DRIZZLE_STORAGE_DB_PROPERTY = "_drizzleStorageDB";
3
+ export const SCHEMA_NAME = "airfoil";