@apibara/plugin-drizzle 2.1.0-beta.3 → 2.1.0-beta.31
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 +251 -64
- package/dist/index.cjs.map +1 -0
- 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 +240 -55
- package/dist/index.mjs.map +1 -0
- package/dist/shared/plugin-drizzle.2d226351.mjs +6 -0
- package/dist/shared/plugin-drizzle.2d226351.mjs.map +1 -0
- package/dist/shared/plugin-drizzle.cae20704.cjs +10 -0
- package/dist/shared/plugin-drizzle.cae20704.cjs.map +1 -0
- package/dist/testing.cjs +14 -0
- package/dist/testing.cjs.map +1 -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 +12 -0
- package/dist/testing.mjs.map +1 -0
- package/package.json +21 -6
- package/src/constants.ts +3 -0
- package/src/helper.ts +219 -0
- package/src/index.ts +142 -17
- package/src/persistence.ts +60 -18
- package/src/storage.ts +88 -23
- package/src/testing.ts +13 -0
- package/src/utils.ts +19 -0
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 {
|
|
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 = "
|
|
38
|
-
const FILTERS_TABLE_NAME = "
|
|
39
|
-
const SCHEMA_VERSION_TABLE_NAME = "
|
|
40
|
-
const
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
266
|
-
SELECT 'D',
|
|
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
|
|
269
|
-
SELECT 'U',
|
|
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
|
|
272
|
-
SELECT 'I',
|
|
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,
|
|
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('${
|
|
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,
|
|
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
|
|
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 ${
|
|
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(
|
|
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}.${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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,5 @@ function drizzleStorage({
|
|
|
598
782
|
});
|
|
599
783
|
}
|
|
600
784
|
|
|
601
|
-
export { drizzleStorage, useDrizzleStorage };
|
|
785
|
+
export { drizzle, drizzleStorage, migrate, useDrizzleStorage };
|
|
786
|
+
//# sourceMappingURL=index.mjs.map
|