@apibara/plugin-drizzle 2.1.0-beta.4 → 2.1.0-beta.40
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 +261 -64
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +166 -4
- package/dist/index.d.mts +166 -4
- package/dist/index.d.ts +166 -4
- package/dist/index.mjs +250 -56
- 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 +160 -18
- 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
|
+
};
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
}
|
|
109
|
+
|
|
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();
|
|
@@ -443,40 +580,92 @@ function useDrizzleStorage(_db) {
|
|
|
443
580
|
}
|
|
444
581
|
return context[DRIZZLE_PROPERTY];
|
|
445
582
|
}
|
|
583
|
+
function useTestDrizzleStorage() {
|
|
584
|
+
const context = useIndexerContext();
|
|
585
|
+
if (!context[DRIZZLE_STORAGE_DB_PROPERTY]) {
|
|
586
|
+
throw new DrizzleStorageError(
|
|
587
|
+
"drizzle storage db is not available. Did you register the plugin?"
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
return context[DRIZZLE_STORAGE_DB_PROPERTY];
|
|
591
|
+
}
|
|
446
592
|
function drizzleStorage({
|
|
447
593
|
db,
|
|
448
594
|
persistState: enablePersistence = true,
|
|
449
595
|
indexerName: identifier = "default",
|
|
450
|
-
schema,
|
|
451
|
-
idColumn
|
|
596
|
+
schema: _schema,
|
|
597
|
+
idColumn,
|
|
598
|
+
migrate: migrateOptions
|
|
452
599
|
}) {
|
|
453
600
|
return defineIndexerPlugin((indexer) => {
|
|
454
601
|
let tableNames = [];
|
|
455
602
|
let indexerId = "";
|
|
603
|
+
const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
|
|
604
|
+
let prevFinality;
|
|
605
|
+
const schema = _schema ?? db._.schema ?? {};
|
|
606
|
+
const idColumnMap = {
|
|
607
|
+
"*": typeof idColumn === "string" ? idColumn : "id",
|
|
608
|
+
...typeof idColumn === "object" ? idColumn : {}
|
|
609
|
+
};
|
|
456
610
|
try {
|
|
457
|
-
tableNames = Object.values(schema
|
|
458
|
-
(table) => table.dbName
|
|
459
|
-
);
|
|
611
|
+
tableNames = Object.values(schema).map((table) => table.dbName);
|
|
460
612
|
} catch (error) {
|
|
461
613
|
throw new DrizzleStorageError("Failed to get table names from schema", {
|
|
462
614
|
cause: error
|
|
463
615
|
});
|
|
464
616
|
}
|
|
465
|
-
|
|
466
|
-
const
|
|
617
|
+
for (const table of Object.values(schema)) {
|
|
618
|
+
const columns = table.columns;
|
|
619
|
+
const tableIdColumn = getIdColumnForTable(table.dbName, idColumnMap);
|
|
620
|
+
const columnExists = Object.values(columns).some(
|
|
621
|
+
(column) => column.name === tableIdColumn
|
|
622
|
+
);
|
|
623
|
+
if (!columnExists) {
|
|
624
|
+
throw new DrizzleStorageError(
|
|
625
|
+
`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\`.`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
indexer.hooks.hook("plugins:init", async () => {
|
|
630
|
+
const internalContext = useInternalContext();
|
|
631
|
+
const context = useIndexerContext();
|
|
632
|
+
const logger = useLogger();
|
|
633
|
+
context[DRIZZLE_STORAGE_DB_PROPERTY] = db;
|
|
634
|
+
const { indexerName: indexerFileName, availableIndexers } = internalContext;
|
|
467
635
|
indexerId = generateIndexerId(indexerFileName, identifier);
|
|
468
636
|
let retries = 0;
|
|
637
|
+
let migrationsApplied = false;
|
|
638
|
+
let cleanupApplied = false;
|
|
469
639
|
while (retries <= MAX_RETRIES) {
|
|
470
640
|
try {
|
|
641
|
+
if (migrateOptions && !migrationsApplied) {
|
|
642
|
+
await migrate(db, migrateOptions);
|
|
643
|
+
migrationsApplied = true;
|
|
644
|
+
logger.success("Migrations applied");
|
|
645
|
+
}
|
|
471
646
|
await withTransaction(db, async (tx) => {
|
|
472
647
|
await initializeReorgRollbackTable(tx, indexerId);
|
|
473
648
|
if (enablePersistence) {
|
|
474
649
|
await initializePersistentState(tx);
|
|
475
650
|
}
|
|
651
|
+
if (alwaysReindex && !cleanupApplied) {
|
|
652
|
+
logger.warn(
|
|
653
|
+
`Reindexing: Deleting all data from tables - ${tableNames.join(", ")}`
|
|
654
|
+
);
|
|
655
|
+
await cleanupStorage(tx, tableNames, indexerId);
|
|
656
|
+
if (enablePersistence) {
|
|
657
|
+
await resetPersistence({ tx, indexerId });
|
|
658
|
+
}
|
|
659
|
+
cleanupApplied = true;
|
|
660
|
+
logger.success("Tables have been cleaned up for reindexing");
|
|
661
|
+
}
|
|
476
662
|
});
|
|
477
663
|
break;
|
|
478
664
|
} catch (error) {
|
|
479
665
|
if (retries === MAX_RETRIES) {
|
|
666
|
+
if (error instanceof DrizzleStorageError) {
|
|
667
|
+
throw error;
|
|
668
|
+
}
|
|
480
669
|
throw new DrizzleStorageError(
|
|
481
670
|
"Initialization failed after 5 retries",
|
|
482
671
|
{
|
|
@@ -512,7 +701,7 @@ function drizzleStorage({
|
|
|
512
701
|
return;
|
|
513
702
|
}
|
|
514
703
|
await withTransaction(db, async (tx) => {
|
|
515
|
-
await invalidate(tx, cursor,
|
|
704
|
+
await invalidate(tx, cursor, idColumnMap, indexerId);
|
|
516
705
|
if (enablePersistence) {
|
|
517
706
|
await invalidateState({ tx, cursor, indexerId });
|
|
518
707
|
}
|
|
@@ -533,7 +722,7 @@ function drizzleStorage({
|
|
|
533
722
|
}
|
|
534
723
|
});
|
|
535
724
|
indexer.hooks.hook("message:finalize", async ({ message }) => {
|
|
536
|
-
const { cursor } = message
|
|
725
|
+
const { cursor } = message;
|
|
537
726
|
if (!cursor) {
|
|
538
727
|
throw new DrizzleStorageError("Finalized Cursor is undefined");
|
|
539
728
|
}
|
|
@@ -545,12 +734,12 @@ function drizzleStorage({
|
|
|
545
734
|
});
|
|
546
735
|
});
|
|
547
736
|
indexer.hooks.hook("message:invalidate", async ({ message }) => {
|
|
548
|
-
const { cursor } = message
|
|
737
|
+
const { cursor } = message;
|
|
549
738
|
if (!cursor) {
|
|
550
739
|
throw new DrizzleStorageError("Invalidate Cursor is undefined");
|
|
551
740
|
}
|
|
552
741
|
await withTransaction(db, async (tx) => {
|
|
553
|
-
await invalidate(tx, cursor,
|
|
742
|
+
await invalidate(tx, cursor, idColumnMap, indexerId);
|
|
554
743
|
if (enablePersistence) {
|
|
555
744
|
await invalidateState({ tx, cursor, indexerId });
|
|
556
745
|
}
|
|
@@ -559,30 +748,34 @@ function drizzleStorage({
|
|
|
559
748
|
indexer.hooks.hook("handler:middleware", async ({ use }) => {
|
|
560
749
|
use(async (context, next) => {
|
|
561
750
|
try {
|
|
562
|
-
const { endCursor, finality } = context;
|
|
751
|
+
const { endCursor, finality, cursor } = context;
|
|
563
752
|
if (!endCursor) {
|
|
564
753
|
throw new DrizzleStorageError("End Cursor is undefined");
|
|
565
754
|
}
|
|
566
755
|
await withTransaction(db, async (tx) => {
|
|
567
756
|
context[DRIZZLE_PROPERTY] = { db: tx };
|
|
757
|
+
if (prevFinality === "pending") {
|
|
758
|
+
await invalidate(tx, cursor, idColumnMap, indexerId);
|
|
759
|
+
}
|
|
568
760
|
if (finality !== "finalized") {
|
|
569
761
|
await registerTriggers(
|
|
570
762
|
tx,
|
|
571
763
|
tableNames,
|
|
572
764
|
endCursor,
|
|
573
|
-
|
|
765
|
+
idColumnMap,
|
|
574
766
|
indexerId
|
|
575
767
|
);
|
|
576
768
|
}
|
|
577
769
|
await next();
|
|
578
770
|
delete context[DRIZZLE_PROPERTY];
|
|
579
|
-
if (enablePersistence) {
|
|
771
|
+
if (enablePersistence && finality !== "pending") {
|
|
580
772
|
await persistState({
|
|
581
773
|
tx,
|
|
582
774
|
endCursor,
|
|
583
775
|
indexerId
|
|
584
776
|
});
|
|
585
777
|
}
|
|
778
|
+
prevFinality = finality;
|
|
586
779
|
});
|
|
587
780
|
if (finality !== "finalized") {
|
|
588
781
|
await removeTriggers(db, tableNames, indexerId);
|
|
@@ -598,4 +791,5 @@ function drizzleStorage({
|
|
|
598
791
|
});
|
|
599
792
|
}
|
|
600
793
|
|
|
601
|
-
export { drizzleStorage, useDrizzleStorage };
|
|
794
|
+
export { drizzle, drizzleStorage, migrate, useDrizzleStorage, useTestDrizzleStorage };
|
|
795
|
+
//# sourceMappingURL=index.mjs.map
|