@apibara/plugin-drizzle 2.1.0-beta.2 → 2.1.0-beta.21
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 +231 -55
- package/dist/index.d.cts +165 -4
- package/dist/index.d.mts +165 -4
- package/dist/index.d.ts +165 -4
- package/dist/index.mjs +217 -47
- package/dist/shared/plugin-drizzle.2d226351.mjs +5 -0
- package/dist/shared/plugin-drizzle.cae20704.cjs +9 -0
- package/dist/testing.cjs +13 -0
- package/dist/testing.d.cts +6 -0
- package/dist/testing.d.mts +6 -0
- package/dist/testing.d.ts +6 -0
- package/dist/testing.mjs +11 -0
- package/package.json +12 -6
- package/src/constants.ts +3 -0
- package/src/helper.ts +202 -0
- package/src/index.ts +140 -15
- package/src/persistence.ts +60 -18
- package/src/storage.ts +81 -17
- package/src/testing.ts +13 -0
- package/src/utils.ts +19 -0
package/dist/index.cjs
CHANGED
|
@@ -4,10 +4,21 @@ 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 constants = require('./shared/plugin-drizzle.cae20704.cjs');
|
|
8
|
+
const pglite$1 = require('@electric-sql/pglite');
|
|
9
|
+
const nodePostgres = require('drizzle-orm/node-postgres');
|
|
10
|
+
const migrator$1 = require('drizzle-orm/node-postgres/migrator');
|
|
11
|
+
const pglite = require('drizzle-orm/pglite');
|
|
12
|
+
const migrator = require('drizzle-orm/pglite/migrator');
|
|
13
|
+
const pg = require('pg');
|
|
7
14
|
const protocol = require('@apibara/protocol');
|
|
8
15
|
const drizzleOrm = require('drizzle-orm');
|
|
9
16
|
const pgCore = require('drizzle-orm/pg-core');
|
|
10
17
|
|
|
18
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
19
|
+
|
|
20
|
+
const pg__default = /*#__PURE__*/_interopDefaultCompat(pg);
|
|
21
|
+
|
|
11
22
|
class DrizzleStorageError extends Error {
|
|
12
23
|
constructor(message, options) {
|
|
13
24
|
super(message, options);
|
|
@@ -35,16 +46,70 @@ function serialize(obj) {
|
|
|
35
46
|
function sleep(ms) {
|
|
36
47
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
48
|
}
|
|
49
|
+
const getIdColumnForTable = (tableName, idColumn) => {
|
|
50
|
+
if (idColumn[tableName]) {
|
|
51
|
+
return idColumn[tableName];
|
|
52
|
+
}
|
|
53
|
+
return idColumn["*"];
|
|
54
|
+
};
|
|
38
55
|
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
function drizzle(options) {
|
|
57
|
+
const {
|
|
58
|
+
connectionString = process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://",
|
|
59
|
+
schema,
|
|
60
|
+
type = "pglite",
|
|
61
|
+
config,
|
|
62
|
+
poolConfig
|
|
63
|
+
} = options ?? {};
|
|
64
|
+
if (isPostgresConnectionString(connectionString) || type === "node-postgres") {
|
|
65
|
+
const pool = new pg__default.Pool({
|
|
66
|
+
connectionString,
|
|
67
|
+
...poolConfig || {}
|
|
68
|
+
});
|
|
69
|
+
return nodePostgres.drizzle(pool, { schema, ...config || {} });
|
|
70
|
+
}
|
|
71
|
+
if (type === "pglite") {
|
|
72
|
+
return pglite.drizzle({
|
|
73
|
+
schema,
|
|
74
|
+
connection: {
|
|
75
|
+
dataDir: connectionString || "memory://pglite"
|
|
76
|
+
},
|
|
77
|
+
...config || {}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
throw new Error("Invalid database type");
|
|
81
|
+
}
|
|
82
|
+
async function migrate(db, options) {
|
|
83
|
+
const isPglite = !!("$client" in db && db.$client instanceof pglite$1.PGlite);
|
|
84
|
+
try {
|
|
85
|
+
if (isPglite) {
|
|
86
|
+
await migrator.migrate(db, options);
|
|
87
|
+
} else {
|
|
88
|
+
await migrator$1.migrate(db, options);
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw new DrizzleStorageError(
|
|
92
|
+
"Failed to apply migrations! Please check if you have generated migrations using drizzle:generate",
|
|
93
|
+
{
|
|
94
|
+
cause: error
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function isPostgresConnectionString(conn) {
|
|
100
|
+
return conn.startsWith("postgres://") || conn.startsWith("postgresql://");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const CHECKPOINTS_TABLE_NAME = "checkpoints";
|
|
104
|
+
const FILTERS_TABLE_NAME = "filters";
|
|
105
|
+
const SCHEMA_VERSION_TABLE_NAME = "schema_version";
|
|
106
|
+
const schema$1 = pgCore.pgSchema(constants.SCHEMA_NAME);
|
|
107
|
+
const checkpoints = schema$1.table(CHECKPOINTS_TABLE_NAME, {
|
|
43
108
|
id: pgCore.text("id").notNull().primaryKey(),
|
|
44
109
|
orderKey: pgCore.integer("order_key").notNull(),
|
|
45
110
|
uniqueKey: pgCore.text("unique_key")
|
|
46
111
|
});
|
|
47
|
-
const filters =
|
|
112
|
+
const filters = schema$1.table(
|
|
48
113
|
FILTERS_TABLE_NAME,
|
|
49
114
|
{
|
|
50
115
|
id: pgCore.text("id").notNull(),
|
|
@@ -58,7 +123,7 @@ const filters = pgCore.pgTable(
|
|
|
58
123
|
}
|
|
59
124
|
]
|
|
60
125
|
);
|
|
61
|
-
const schemaVersion =
|
|
126
|
+
const schemaVersion = schema$1.table(SCHEMA_VERSION_TABLE_NAME, {
|
|
62
127
|
k: pgCore.integer("k").notNull().primaryKey(),
|
|
63
128
|
version: pgCore.integer("version").notNull()
|
|
64
129
|
});
|
|
@@ -69,12 +134,19 @@ const MIGRATIONS = [
|
|
|
69
134
|
// Add more migration arrays for future versions
|
|
70
135
|
];
|
|
71
136
|
async function initializePersistentState(tx) {
|
|
72
|
-
await tx.execute(
|
|
73
|
-
|
|
137
|
+
await tx.execute(
|
|
138
|
+
drizzleOrm.sql.raw(`
|
|
139
|
+
CREATE SCHEMA IF NOT EXISTS ${constants.SCHEMA_NAME};
|
|
140
|
+
`)
|
|
141
|
+
);
|
|
142
|
+
await tx.execute(
|
|
143
|
+
drizzleOrm.sql.raw(`
|
|
144
|
+
CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${SCHEMA_VERSION_TABLE_NAME} (
|
|
74
145
|
k INTEGER PRIMARY KEY,
|
|
75
146
|
version INTEGER NOT NULL
|
|
76
147
|
);
|
|
77
|
-
`)
|
|
148
|
+
`)
|
|
149
|
+
);
|
|
78
150
|
const versionRows = await tx.select().from(schemaVersion).where(drizzleOrm.eq(schemaVersion.k, 0));
|
|
79
151
|
const storedVersion = versionRows[0]?.version ?? -1;
|
|
80
152
|
if (storedVersion > CURRENT_SCHEMA_VERSION) {
|
|
@@ -84,22 +156,26 @@ async function initializePersistentState(tx) {
|
|
|
84
156
|
}
|
|
85
157
|
try {
|
|
86
158
|
if (storedVersion === -1) {
|
|
87
|
-
await tx.execute(
|
|
88
|
-
|
|
159
|
+
await tx.execute(
|
|
160
|
+
drizzleOrm.sql.raw(`
|
|
161
|
+
CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${CHECKPOINTS_TABLE_NAME} (
|
|
89
162
|
id TEXT PRIMARY KEY,
|
|
90
163
|
order_key INTEGER NOT NULL,
|
|
91
164
|
unique_key TEXT
|
|
92
165
|
);
|
|
93
|
-
`)
|
|
94
|
-
|
|
95
|
-
|
|
166
|
+
`)
|
|
167
|
+
);
|
|
168
|
+
await tx.execute(
|
|
169
|
+
drizzleOrm.sql.raw(`
|
|
170
|
+
CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${FILTERS_TABLE_NAME} (
|
|
96
171
|
id TEXT NOT NULL,
|
|
97
172
|
filter TEXT NOT NULL,
|
|
98
173
|
from_block INTEGER NOT NULL,
|
|
99
174
|
to_block INTEGER DEFAULT NULL,
|
|
100
175
|
PRIMARY KEY (id, from_block)
|
|
101
176
|
);
|
|
102
|
-
`)
|
|
177
|
+
`)
|
|
178
|
+
);
|
|
103
179
|
await tx.insert(schemaVersion).values({
|
|
104
180
|
k: 0,
|
|
105
181
|
version: CURRENT_SCHEMA_VERSION
|
|
@@ -134,7 +210,9 @@ async function persistState(props) {
|
|
|
134
210
|
target: checkpoints.id,
|
|
135
211
|
set: {
|
|
136
212
|
orderKey: Number(endCursor.orderKey),
|
|
137
|
-
|
|
213
|
+
// Explicitly set the unique key to `null` to indicate that it has been deleted
|
|
214
|
+
// Otherwise drizzle will not update its value.
|
|
215
|
+
uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null
|
|
138
216
|
}
|
|
139
217
|
});
|
|
140
218
|
if (filter) {
|
|
@@ -213,11 +291,24 @@ async function finalizeState(props) {
|
|
|
213
291
|
});
|
|
214
292
|
}
|
|
215
293
|
}
|
|
294
|
+
async function resetPersistence(props) {
|
|
295
|
+
const { tx, indexerId } = props;
|
|
296
|
+
try {
|
|
297
|
+
await tx.delete(checkpoints).where(drizzleOrm.eq(checkpoints.id, indexerId));
|
|
298
|
+
await tx.delete(filters).where(drizzleOrm.eq(filters.id, indexerId));
|
|
299
|
+
} catch (error) {
|
|
300
|
+
throw new DrizzleStorageError("Failed to reset persistence state", {
|
|
301
|
+
cause: error
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
216
305
|
|
|
306
|
+
const ROLLBACK_TABLE_NAME = "reorg_rollback";
|
|
307
|
+
const schema = pgCore.pgSchema(constants.SCHEMA_NAME);
|
|
217
308
|
function getReorgTriggerName(table, indexerId) {
|
|
218
309
|
return `${table}_reorg_${indexerId}`;
|
|
219
310
|
}
|
|
220
|
-
|
|
311
|
+
schema.table(ROLLBACK_TABLE_NAME, {
|
|
221
312
|
n: pgCore.serial("n").primaryKey(),
|
|
222
313
|
op: pgCore.char("op", { length: 1 }).$type().notNull(),
|
|
223
314
|
table_name: pgCore.text("table_name").notNull(),
|
|
@@ -228,9 +319,12 @@ pgCore.pgTable("__reorg_rollback", {
|
|
|
228
319
|
});
|
|
229
320
|
async function initializeReorgRollbackTable(tx, indexerId) {
|
|
230
321
|
try {
|
|
322
|
+
await tx.execute(`
|
|
323
|
+
CREATE SCHEMA IF NOT EXISTS ${constants.SCHEMA_NAME};
|
|
324
|
+
`);
|
|
231
325
|
await tx.execute(
|
|
232
326
|
drizzleOrm.sql.raw(`
|
|
233
|
-
CREATE TABLE IF NOT EXISTS
|
|
327
|
+
CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(
|
|
234
328
|
n SERIAL PRIMARY KEY,
|
|
235
329
|
op CHAR(1) NOT NULL,
|
|
236
330
|
table_name TEXT NOT NULL,
|
|
@@ -243,7 +337,7 @@ async function initializeReorgRollbackTable(tx, indexerId) {
|
|
|
243
337
|
);
|
|
244
338
|
await tx.execute(
|
|
245
339
|
drizzleOrm.sql.raw(`
|
|
246
|
-
CREATE INDEX IF NOT EXISTS idx_reorg_rollback_indexer_id_cursor ON
|
|
340
|
+
CREATE INDEX IF NOT EXISTS idx_reorg_rollback_indexer_id_cursor ON ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(indexer_id, cursor);
|
|
247
341
|
`)
|
|
248
342
|
);
|
|
249
343
|
} catch (error) {
|
|
@@ -254,7 +348,7 @@ async function initializeReorgRollbackTable(tx, indexerId) {
|
|
|
254
348
|
try {
|
|
255
349
|
await tx.execute(
|
|
256
350
|
drizzleOrm.sql.raw(`
|
|
257
|
-
CREATE OR REPLACE FUNCTION reorg_checkpoint()
|
|
351
|
+
CREATE OR REPLACE FUNCTION ${constants.SCHEMA_NAME}.reorg_checkpoint()
|
|
258
352
|
RETURNS TRIGGER AS $$
|
|
259
353
|
DECLARE
|
|
260
354
|
id_col TEXT := TG_ARGV[0]::TEXT;
|
|
@@ -264,13 +358,13 @@ async function initializeReorgRollbackTable(tx, indexerId) {
|
|
|
264
358
|
old_id_value TEXT := row_to_json(OLD.*)->>id_col;
|
|
265
359
|
BEGIN
|
|
266
360
|
IF (TG_OP = 'DELETE') THEN
|
|
267
|
-
INSERT INTO
|
|
361
|
+
INSERT INTO ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
|
|
268
362
|
SELECT 'D', TG_TABLE_NAME, order_key, old_id_value, row_to_json(OLD.*), indexer_id;
|
|
269
363
|
ELSIF (TG_OP = 'UPDATE') THEN
|
|
270
|
-
INSERT INTO
|
|
364
|
+
INSERT INTO ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
|
|
271
365
|
SELECT 'U', TG_TABLE_NAME, order_key, new_id_value, row_to_json(OLD.*), indexer_id;
|
|
272
366
|
ELSIF (TG_OP = 'INSERT') THEN
|
|
273
|
-
INSERT INTO
|
|
367
|
+
INSERT INTO ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
|
|
274
368
|
SELECT 'I', TG_TABLE_NAME, order_key, new_id_value, null, indexer_id;
|
|
275
369
|
END IF;
|
|
276
370
|
RETURN NULL;
|
|
@@ -287,9 +381,10 @@ async function initializeReorgRollbackTable(tx, indexerId) {
|
|
|
287
381
|
);
|
|
288
382
|
}
|
|
289
383
|
}
|
|
290
|
-
async function registerTriggers(tx, tables, endCursor,
|
|
384
|
+
async function registerTriggers(tx, tables, endCursor, idColumnMap, indexerId) {
|
|
291
385
|
try {
|
|
292
386
|
for (const table of tables) {
|
|
387
|
+
const tableIdColumn = getIdColumnForTable(table, idColumnMap);
|
|
293
388
|
await tx.execute(
|
|
294
389
|
drizzleOrm.sql.raw(
|
|
295
390
|
`DROP TRIGGER IF EXISTS ${getReorgTriggerName(table, indexerId)} ON ${table};`
|
|
@@ -300,7 +395,7 @@ async function registerTriggers(tx, tables, endCursor, idColumn, indexerId) {
|
|
|
300
395
|
CREATE CONSTRAINT TRIGGER ${getReorgTriggerName(table, indexerId)}
|
|
301
396
|
AFTER INSERT OR UPDATE OR DELETE ON ${table}
|
|
302
397
|
DEFERRABLE INITIALLY DEFERRED
|
|
303
|
-
FOR EACH ROW EXECUTE FUNCTION reorg_checkpoint('${
|
|
398
|
+
FOR EACH ROW EXECUTE FUNCTION ${constants.SCHEMA_NAME}.reorg_checkpoint('${tableIdColumn}', ${Number(endCursor.orderKey)}, '${indexerId}');
|
|
304
399
|
`)
|
|
305
400
|
);
|
|
306
401
|
}
|
|
@@ -325,11 +420,11 @@ async function removeTriggers(db, tables, indexerId) {
|
|
|
325
420
|
});
|
|
326
421
|
}
|
|
327
422
|
}
|
|
328
|
-
async function invalidate(tx, cursor,
|
|
423
|
+
async function invalidate(tx, cursor, idColumnMap, indexerId) {
|
|
329
424
|
const { rows: result } = await tx.execute(
|
|
330
425
|
drizzleOrm.sql.raw(`
|
|
331
426
|
WITH deleted AS (
|
|
332
|
-
DELETE FROM
|
|
427
|
+
DELETE FROM ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
|
|
333
428
|
WHERE cursor > ${Number(cursor.orderKey)}
|
|
334
429
|
AND indexer_id = '${indexerId}'
|
|
335
430
|
RETURNING *
|
|
@@ -343,6 +438,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
|
|
|
343
438
|
);
|
|
344
439
|
}
|
|
345
440
|
for (const op of result) {
|
|
441
|
+
const tableIdColumn = getIdColumnForTable(op.table_name, idColumnMap);
|
|
346
442
|
switch (op.op) {
|
|
347
443
|
case "I":
|
|
348
444
|
try {
|
|
@@ -352,7 +448,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
|
|
|
352
448
|
await tx.execute(
|
|
353
449
|
drizzleOrm.sql.raw(`
|
|
354
450
|
DELETE FROM ${op.table_name}
|
|
355
|
-
WHERE ${
|
|
451
|
+
WHERE ${tableIdColumn} = '${op.row_id}'
|
|
356
452
|
`)
|
|
357
453
|
);
|
|
358
454
|
} catch (error) {
|
|
@@ -392,7 +488,9 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
|
|
|
392
488
|
);
|
|
393
489
|
}
|
|
394
490
|
const rowValue = typeof op.row_value === "string" ? JSON.parse(op.row_value) : op.row_value;
|
|
395
|
-
const nonIdKeys = Object.keys(rowValue).filter(
|
|
491
|
+
const nonIdKeys = Object.keys(rowValue).filter(
|
|
492
|
+
(k) => k !== tableIdColumn
|
|
493
|
+
);
|
|
396
494
|
const fields = nonIdKeys.map((c) => `${c} = prev.${c}`).join(", ");
|
|
397
495
|
const query = drizzleOrm.sql.raw(`
|
|
398
496
|
UPDATE ${op.table_name}
|
|
@@ -400,7 +498,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
|
|
|
400
498
|
FROM (
|
|
401
499
|
SELECT * FROM json_populate_record(null::${op.table_name}, '${JSON.stringify(op.row_value)}'::json)
|
|
402
500
|
) as prev
|
|
403
|
-
WHERE ${op.table_name}.${
|
|
501
|
+
WHERE ${op.table_name}.${tableIdColumn} = '${op.row_id}'
|
|
404
502
|
`);
|
|
405
503
|
await tx.execute(query);
|
|
406
504
|
} catch (error) {
|
|
@@ -422,7 +520,7 @@ async function finalize(tx, cursor, indexerId) {
|
|
|
422
520
|
try {
|
|
423
521
|
await tx.execute(
|
|
424
522
|
drizzleOrm.sql.raw(`
|
|
425
|
-
DELETE FROM
|
|
523
|
+
DELETE FROM ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
|
|
426
524
|
WHERE cursor <= ${Number(cursor.orderKey)}
|
|
427
525
|
AND indexer_id = '${indexerId}'
|
|
428
526
|
`)
|
|
@@ -433,52 +531,124 @@ async function finalize(tx, cursor, indexerId) {
|
|
|
433
531
|
});
|
|
434
532
|
}
|
|
435
533
|
}
|
|
534
|
+
async function cleanupStorage(tx, tables, indexerId) {
|
|
535
|
+
try {
|
|
536
|
+
for (const table of tables) {
|
|
537
|
+
await tx.execute(
|
|
538
|
+
drizzleOrm.sql.raw(
|
|
539
|
+
`DROP TRIGGER IF EXISTS ${getReorgTriggerName(table, indexerId)} ON ${table};`
|
|
540
|
+
)
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
await tx.execute(
|
|
544
|
+
drizzleOrm.sql.raw(`
|
|
545
|
+
DELETE FROM ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
|
|
546
|
+
WHERE indexer_id = '${indexerId}'
|
|
547
|
+
`)
|
|
548
|
+
);
|
|
549
|
+
for (const table of tables) {
|
|
550
|
+
try {
|
|
551
|
+
await tx.execute(drizzleOrm.sql.raw(`TRUNCATE TABLE ${table} CASCADE;`));
|
|
552
|
+
} catch (error) {
|
|
553
|
+
throw new DrizzleStorageError(`Failed to truncate table ${table}`, {
|
|
554
|
+
cause: error
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
} catch (error) {
|
|
559
|
+
throw new DrizzleStorageError("Failed to clean up storage", {
|
|
560
|
+
cause: error
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
436
564
|
|
|
437
|
-
const DRIZZLE_PROPERTY = "_drizzle";
|
|
438
565
|
const MAX_RETRIES = 5;
|
|
439
566
|
function useDrizzleStorage(_db) {
|
|
440
567
|
const context = indexer.useIndexerContext();
|
|
441
|
-
if (!context[DRIZZLE_PROPERTY]) {
|
|
568
|
+
if (!context[constants.DRIZZLE_PROPERTY]) {
|
|
442
569
|
throw new DrizzleStorageError(
|
|
443
570
|
"drizzle storage is not available. Did you register the plugin?"
|
|
444
571
|
);
|
|
445
572
|
}
|
|
446
|
-
return context[DRIZZLE_PROPERTY];
|
|
573
|
+
return context[constants.DRIZZLE_PROPERTY];
|
|
447
574
|
}
|
|
448
575
|
function drizzleStorage({
|
|
449
576
|
db,
|
|
450
577
|
persistState: enablePersistence = true,
|
|
451
578
|
indexerName: identifier = "default",
|
|
452
|
-
schema,
|
|
453
|
-
idColumn
|
|
579
|
+
schema: _schema,
|
|
580
|
+
idColumn,
|
|
581
|
+
migrate: migrateOptions
|
|
454
582
|
}) {
|
|
455
|
-
return plugins.defineIndexerPlugin((indexer) => {
|
|
583
|
+
return plugins.defineIndexerPlugin((indexer$1) => {
|
|
456
584
|
let tableNames = [];
|
|
457
585
|
let indexerId = "";
|
|
586
|
+
const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
|
|
587
|
+
let prevFinality;
|
|
588
|
+
const schema = _schema ?? db._.schema ?? {};
|
|
589
|
+
const idColumnMap = {
|
|
590
|
+
"*": typeof idColumn === "string" ? idColumn : "id",
|
|
591
|
+
...typeof idColumn === "object" ? idColumn : {}
|
|
592
|
+
};
|
|
458
593
|
try {
|
|
459
|
-
tableNames = Object.values(schema
|
|
460
|
-
(table) => table.dbName
|
|
461
|
-
);
|
|
594
|
+
tableNames = Object.values(schema).map((table) => table.dbName);
|
|
462
595
|
} catch (error) {
|
|
463
596
|
throw new DrizzleStorageError("Failed to get table names from schema", {
|
|
464
597
|
cause: error
|
|
465
598
|
});
|
|
466
599
|
}
|
|
467
|
-
|
|
468
|
-
const
|
|
600
|
+
for (const table of Object.values(schema)) {
|
|
601
|
+
const columns = table.columns;
|
|
602
|
+
const tableIdColumn = getIdColumnForTable(table.dbName, idColumnMap);
|
|
603
|
+
const columnExists = Object.values(columns).some(
|
|
604
|
+
(column) => column.name === tableIdColumn
|
|
605
|
+
);
|
|
606
|
+
if (!columnExists) {
|
|
607
|
+
throw new DrizzleStorageError(
|
|
608
|
+
`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\`.`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
indexer$1.hooks.hook("run:before", async () => {
|
|
613
|
+
const internalContext = plugins$1.useInternalContext();
|
|
614
|
+
const context = indexer.useIndexerContext();
|
|
615
|
+
const logger = plugins.useLogger();
|
|
616
|
+
context[constants.DRIZZLE_STORAGE_DB_PROPERTY] = db;
|
|
617
|
+
const { indexerName: indexerFileName, availableIndexers } = internalContext;
|
|
469
618
|
indexerId = internal.generateIndexerId(indexerFileName, identifier);
|
|
470
619
|
let retries = 0;
|
|
620
|
+
let migrationsApplied = false;
|
|
621
|
+
let cleanupApplied = false;
|
|
471
622
|
while (retries <= MAX_RETRIES) {
|
|
472
623
|
try {
|
|
624
|
+
if (migrateOptions && !migrationsApplied) {
|
|
625
|
+
await migrate(db, migrateOptions);
|
|
626
|
+
migrationsApplied = true;
|
|
627
|
+
logger.success("Migrations applied");
|
|
628
|
+
}
|
|
473
629
|
await withTransaction(db, async (tx) => {
|
|
474
630
|
await initializeReorgRollbackTable(tx, indexerId);
|
|
475
631
|
if (enablePersistence) {
|
|
476
632
|
await initializePersistentState(tx);
|
|
477
633
|
}
|
|
634
|
+
if (alwaysReindex && !cleanupApplied) {
|
|
635
|
+
logger.warn(
|
|
636
|
+
`Reindexing: Deleting all data from tables - ${tableNames.join(", ")}`
|
|
637
|
+
);
|
|
638
|
+
await cleanupStorage(tx, tableNames, indexerId);
|
|
639
|
+
if (enablePersistence) {
|
|
640
|
+
await resetPersistence({ tx, indexerId });
|
|
641
|
+
}
|
|
642
|
+
cleanupApplied = true;
|
|
643
|
+
logger.success("Tables have been cleaned up for reindexing");
|
|
644
|
+
}
|
|
478
645
|
});
|
|
479
646
|
break;
|
|
480
647
|
} catch (error) {
|
|
481
648
|
if (retries === MAX_RETRIES) {
|
|
649
|
+
if (error instanceof DrizzleStorageError) {
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
482
652
|
throw new DrizzleStorageError(
|
|
483
653
|
"Initialization failed after 5 retries",
|
|
484
654
|
{
|
|
@@ -491,7 +661,7 @@ function drizzleStorage({
|
|
|
491
661
|
}
|
|
492
662
|
}
|
|
493
663
|
});
|
|
494
|
-
indexer.hooks.hook("connect:before", async ({ request }) => {
|
|
664
|
+
indexer$1.hooks.hook("connect:before", async ({ request }) => {
|
|
495
665
|
if (!enablePersistence) {
|
|
496
666
|
return;
|
|
497
667
|
}
|
|
@@ -508,19 +678,19 @@ function drizzleStorage({
|
|
|
508
678
|
}
|
|
509
679
|
});
|
|
510
680
|
});
|
|
511
|
-
indexer.hooks.hook("connect:after", async ({ request }) => {
|
|
681
|
+
indexer$1.hooks.hook("connect:after", async ({ request }) => {
|
|
512
682
|
const cursor = request.startingCursor;
|
|
513
683
|
if (!cursor) {
|
|
514
684
|
return;
|
|
515
685
|
}
|
|
516
686
|
await withTransaction(db, async (tx) => {
|
|
517
|
-
await invalidate(tx, cursor,
|
|
687
|
+
await invalidate(tx, cursor, idColumnMap, indexerId);
|
|
518
688
|
if (enablePersistence) {
|
|
519
689
|
await invalidateState({ tx, cursor, indexerId });
|
|
520
690
|
}
|
|
521
691
|
});
|
|
522
692
|
});
|
|
523
|
-
indexer.hooks.hook("connect:factory", async ({ request, endCursor }) => {
|
|
693
|
+
indexer$1.hooks.hook("connect:factory", async ({ request, endCursor }) => {
|
|
524
694
|
if (!enablePersistence) {
|
|
525
695
|
return;
|
|
526
696
|
}
|
|
@@ -534,7 +704,7 @@ function drizzleStorage({
|
|
|
534
704
|
});
|
|
535
705
|
}
|
|
536
706
|
});
|
|
537
|
-
indexer.hooks.hook("message:finalize", async ({ message }) => {
|
|
707
|
+
indexer$1.hooks.hook("message:finalize", async ({ message }) => {
|
|
538
708
|
const { cursor } = message.finalize;
|
|
539
709
|
if (!cursor) {
|
|
540
710
|
throw new DrizzleStorageError("Finalized Cursor is undefined");
|
|
@@ -546,45 +716,49 @@ function drizzleStorage({
|
|
|
546
716
|
}
|
|
547
717
|
});
|
|
548
718
|
});
|
|
549
|
-
indexer.hooks.hook("message:invalidate", async ({ message }) => {
|
|
719
|
+
indexer$1.hooks.hook("message:invalidate", async ({ message }) => {
|
|
550
720
|
const { cursor } = message.invalidate;
|
|
551
721
|
if (!cursor) {
|
|
552
722
|
throw new DrizzleStorageError("Invalidate Cursor is undefined");
|
|
553
723
|
}
|
|
554
724
|
await withTransaction(db, async (tx) => {
|
|
555
|
-
await invalidate(tx, cursor,
|
|
725
|
+
await invalidate(tx, cursor, idColumnMap, indexerId);
|
|
556
726
|
if (enablePersistence) {
|
|
557
727
|
await invalidateState({ tx, cursor, indexerId });
|
|
558
728
|
}
|
|
559
729
|
});
|
|
560
730
|
});
|
|
561
|
-
indexer.hooks.hook("handler:middleware", async ({ use }) => {
|
|
731
|
+
indexer$1.hooks.hook("handler:middleware", async ({ use }) => {
|
|
562
732
|
use(async (context, next) => {
|
|
563
733
|
try {
|
|
564
|
-
const { endCursor, finality } = context;
|
|
734
|
+
const { endCursor, finality, cursor } = context;
|
|
565
735
|
if (!endCursor) {
|
|
566
736
|
throw new DrizzleStorageError("End Cursor is undefined");
|
|
567
737
|
}
|
|
568
738
|
await withTransaction(db, async (tx) => {
|
|
569
|
-
context[DRIZZLE_PROPERTY] = { db: tx };
|
|
739
|
+
context[constants.DRIZZLE_PROPERTY] = { db: tx };
|
|
740
|
+
if (prevFinality === "pending") {
|
|
741
|
+
await invalidate(tx, cursor, idColumnMap, indexerId);
|
|
742
|
+
}
|
|
570
743
|
if (finality !== "finalized") {
|
|
571
744
|
await registerTriggers(
|
|
572
745
|
tx,
|
|
573
746
|
tableNames,
|
|
574
747
|
endCursor,
|
|
575
|
-
|
|
748
|
+
idColumnMap,
|
|
576
749
|
indexerId
|
|
577
750
|
);
|
|
578
751
|
}
|
|
579
752
|
await next();
|
|
580
|
-
delete context[DRIZZLE_PROPERTY];
|
|
581
|
-
if (enablePersistence) {
|
|
753
|
+
delete context[constants.DRIZZLE_PROPERTY];
|
|
754
|
+
if (enablePersistence && finality !== "pending") {
|
|
582
755
|
await persistState({
|
|
583
756
|
tx,
|
|
584
757
|
endCursor,
|
|
585
758
|
indexerId
|
|
586
759
|
});
|
|
587
760
|
}
|
|
761
|
+
prevFinality = finality;
|
|
588
762
|
});
|
|
589
763
|
if (finality !== "finalized") {
|
|
590
764
|
await removeTriggers(db, tableNames, indexerId);
|
|
@@ -600,5 +774,7 @@ function drizzleStorage({
|
|
|
600
774
|
});
|
|
601
775
|
}
|
|
602
776
|
|
|
777
|
+
exports.drizzle = drizzle;
|
|
603
778
|
exports.drizzleStorage = drizzleStorage;
|
|
779
|
+
exports.migrate = migrate;
|
|
604
780
|
exports.useDrizzleStorage = useDrizzleStorage;
|