@apibara/plugin-drizzle 2.1.0-beta.2 → 2.1.0-beta.20
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 +194 -43
- package/dist/index.d.cts +134 -3
- package/dist/index.d.mts +134 -3
- package/dist/index.d.ts +134 -3
- package/dist/index.mjs +180 -35
- 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 +75 -5
- package/src/persistence.ts +60 -18
- package/src/storage.ts +63 -11
- package/src/testing.ts +13 -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);
|
|
@@ -36,15 +47,63 @@ function sleep(ms) {
|
|
|
36
47
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
48
|
}
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
function drizzle(options) {
|
|
51
|
+
const {
|
|
52
|
+
connectionString = process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://",
|
|
53
|
+
schema,
|
|
54
|
+
type = "pglite",
|
|
55
|
+
config,
|
|
56
|
+
poolConfig
|
|
57
|
+
} = options ?? {};
|
|
58
|
+
if (isPostgresConnectionString(connectionString) || type === "node-postgres") {
|
|
59
|
+
const pool = new pg__default.Pool({
|
|
60
|
+
connectionString,
|
|
61
|
+
...poolConfig || {}
|
|
62
|
+
});
|
|
63
|
+
return nodePostgres.drizzle(pool, { schema, ...config || {} });
|
|
64
|
+
}
|
|
65
|
+
if (type === "pglite") {
|
|
66
|
+
return pglite.drizzle({
|
|
67
|
+
schema,
|
|
68
|
+
connection: {
|
|
69
|
+
dataDir: connectionString || "memory://pglite"
|
|
70
|
+
},
|
|
71
|
+
...config || {}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
throw new Error("Invalid database type");
|
|
75
|
+
}
|
|
76
|
+
async function migrate(db, options) {
|
|
77
|
+
const isPglite = !!("$client" in db && db.$client instanceof pglite$1.PGlite);
|
|
78
|
+
try {
|
|
79
|
+
if (isPglite) {
|
|
80
|
+
await migrator.migrate(db, options);
|
|
81
|
+
} else {
|
|
82
|
+
await migrator$1.migrate(db, options);
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new DrizzleStorageError(
|
|
86
|
+
"Failed to apply migrations! Please check if you have generated migrations using drizzle:generate",
|
|
87
|
+
{
|
|
88
|
+
cause: error
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function isPostgresConnectionString(conn) {
|
|
94
|
+
return conn.startsWith("postgres://") || conn.startsWith("postgresql://");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const CHECKPOINTS_TABLE_NAME = "checkpoints";
|
|
98
|
+
const FILTERS_TABLE_NAME = "filters";
|
|
99
|
+
const SCHEMA_VERSION_TABLE_NAME = "schema_version";
|
|
100
|
+
const schema$1 = pgCore.pgSchema(constants.SCHEMA_NAME);
|
|
101
|
+
const checkpoints = schema$1.table(CHECKPOINTS_TABLE_NAME, {
|
|
43
102
|
id: pgCore.text("id").notNull().primaryKey(),
|
|
44
103
|
orderKey: pgCore.integer("order_key").notNull(),
|
|
45
104
|
uniqueKey: pgCore.text("unique_key")
|
|
46
105
|
});
|
|
47
|
-
const filters =
|
|
106
|
+
const filters = schema$1.table(
|
|
48
107
|
FILTERS_TABLE_NAME,
|
|
49
108
|
{
|
|
50
109
|
id: pgCore.text("id").notNull(),
|
|
@@ -58,7 +117,7 @@ const filters = pgCore.pgTable(
|
|
|
58
117
|
}
|
|
59
118
|
]
|
|
60
119
|
);
|
|
61
|
-
const schemaVersion =
|
|
120
|
+
const schemaVersion = schema$1.table(SCHEMA_VERSION_TABLE_NAME, {
|
|
62
121
|
k: pgCore.integer("k").notNull().primaryKey(),
|
|
63
122
|
version: pgCore.integer("version").notNull()
|
|
64
123
|
});
|
|
@@ -69,12 +128,19 @@ const MIGRATIONS = [
|
|
|
69
128
|
// Add more migration arrays for future versions
|
|
70
129
|
];
|
|
71
130
|
async function initializePersistentState(tx) {
|
|
72
|
-
await tx.execute(
|
|
73
|
-
|
|
131
|
+
await tx.execute(
|
|
132
|
+
drizzleOrm.sql.raw(`
|
|
133
|
+
CREATE SCHEMA IF NOT EXISTS ${constants.SCHEMA_NAME};
|
|
134
|
+
`)
|
|
135
|
+
);
|
|
136
|
+
await tx.execute(
|
|
137
|
+
drizzleOrm.sql.raw(`
|
|
138
|
+
CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${SCHEMA_VERSION_TABLE_NAME} (
|
|
74
139
|
k INTEGER PRIMARY KEY,
|
|
75
140
|
version INTEGER NOT NULL
|
|
76
141
|
);
|
|
77
|
-
`)
|
|
142
|
+
`)
|
|
143
|
+
);
|
|
78
144
|
const versionRows = await tx.select().from(schemaVersion).where(drizzleOrm.eq(schemaVersion.k, 0));
|
|
79
145
|
const storedVersion = versionRows[0]?.version ?? -1;
|
|
80
146
|
if (storedVersion > CURRENT_SCHEMA_VERSION) {
|
|
@@ -84,22 +150,26 @@ async function initializePersistentState(tx) {
|
|
|
84
150
|
}
|
|
85
151
|
try {
|
|
86
152
|
if (storedVersion === -1) {
|
|
87
|
-
await tx.execute(
|
|
88
|
-
|
|
153
|
+
await tx.execute(
|
|
154
|
+
drizzleOrm.sql.raw(`
|
|
155
|
+
CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${CHECKPOINTS_TABLE_NAME} (
|
|
89
156
|
id TEXT PRIMARY KEY,
|
|
90
157
|
order_key INTEGER NOT NULL,
|
|
91
158
|
unique_key TEXT
|
|
92
159
|
);
|
|
93
|
-
`)
|
|
94
|
-
|
|
95
|
-
|
|
160
|
+
`)
|
|
161
|
+
);
|
|
162
|
+
await tx.execute(
|
|
163
|
+
drizzleOrm.sql.raw(`
|
|
164
|
+
CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${FILTERS_TABLE_NAME} (
|
|
96
165
|
id TEXT NOT NULL,
|
|
97
166
|
filter TEXT NOT NULL,
|
|
98
167
|
from_block INTEGER NOT NULL,
|
|
99
168
|
to_block INTEGER DEFAULT NULL,
|
|
100
169
|
PRIMARY KEY (id, from_block)
|
|
101
170
|
);
|
|
102
|
-
`)
|
|
171
|
+
`)
|
|
172
|
+
);
|
|
103
173
|
await tx.insert(schemaVersion).values({
|
|
104
174
|
k: 0,
|
|
105
175
|
version: CURRENT_SCHEMA_VERSION
|
|
@@ -134,7 +204,9 @@ async function persistState(props) {
|
|
|
134
204
|
target: checkpoints.id,
|
|
135
205
|
set: {
|
|
136
206
|
orderKey: Number(endCursor.orderKey),
|
|
137
|
-
|
|
207
|
+
// Explicitly set the unique key to `null` to indicate that it has been deleted
|
|
208
|
+
// Otherwise drizzle will not update its value.
|
|
209
|
+
uniqueKey: endCursor.uniqueKey ? endCursor.uniqueKey : null
|
|
138
210
|
}
|
|
139
211
|
});
|
|
140
212
|
if (filter) {
|
|
@@ -213,11 +285,24 @@ async function finalizeState(props) {
|
|
|
213
285
|
});
|
|
214
286
|
}
|
|
215
287
|
}
|
|
288
|
+
async function resetPersistence(props) {
|
|
289
|
+
const { tx, indexerId } = props;
|
|
290
|
+
try {
|
|
291
|
+
await tx.delete(checkpoints).where(drizzleOrm.eq(checkpoints.id, indexerId));
|
|
292
|
+
await tx.delete(filters).where(drizzleOrm.eq(filters.id, indexerId));
|
|
293
|
+
} catch (error) {
|
|
294
|
+
throw new DrizzleStorageError("Failed to reset persistence state", {
|
|
295
|
+
cause: error
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
216
299
|
|
|
300
|
+
const ROLLBACK_TABLE_NAME = "reorg_rollback";
|
|
301
|
+
const schema = pgCore.pgSchema(constants.SCHEMA_NAME);
|
|
217
302
|
function getReorgTriggerName(table, indexerId) {
|
|
218
303
|
return `${table}_reorg_${indexerId}`;
|
|
219
304
|
}
|
|
220
|
-
|
|
305
|
+
schema.table(ROLLBACK_TABLE_NAME, {
|
|
221
306
|
n: pgCore.serial("n").primaryKey(),
|
|
222
307
|
op: pgCore.char("op", { length: 1 }).$type().notNull(),
|
|
223
308
|
table_name: pgCore.text("table_name").notNull(),
|
|
@@ -228,9 +313,12 @@ pgCore.pgTable("__reorg_rollback", {
|
|
|
228
313
|
});
|
|
229
314
|
async function initializeReorgRollbackTable(tx, indexerId) {
|
|
230
315
|
try {
|
|
316
|
+
await tx.execute(`
|
|
317
|
+
CREATE SCHEMA IF NOT EXISTS ${constants.SCHEMA_NAME};
|
|
318
|
+
`);
|
|
231
319
|
await tx.execute(
|
|
232
320
|
drizzleOrm.sql.raw(`
|
|
233
|
-
CREATE TABLE IF NOT EXISTS
|
|
321
|
+
CREATE TABLE IF NOT EXISTS ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(
|
|
234
322
|
n SERIAL PRIMARY KEY,
|
|
235
323
|
op CHAR(1) NOT NULL,
|
|
236
324
|
table_name TEXT NOT NULL,
|
|
@@ -243,7 +331,7 @@ async function initializeReorgRollbackTable(tx, indexerId) {
|
|
|
243
331
|
);
|
|
244
332
|
await tx.execute(
|
|
245
333
|
drizzleOrm.sql.raw(`
|
|
246
|
-
CREATE INDEX IF NOT EXISTS idx_reorg_rollback_indexer_id_cursor ON
|
|
334
|
+
CREATE INDEX IF NOT EXISTS idx_reorg_rollback_indexer_id_cursor ON ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(indexer_id, cursor);
|
|
247
335
|
`)
|
|
248
336
|
);
|
|
249
337
|
} catch (error) {
|
|
@@ -254,7 +342,7 @@ async function initializeReorgRollbackTable(tx, indexerId) {
|
|
|
254
342
|
try {
|
|
255
343
|
await tx.execute(
|
|
256
344
|
drizzleOrm.sql.raw(`
|
|
257
|
-
CREATE OR REPLACE FUNCTION reorg_checkpoint()
|
|
345
|
+
CREATE OR REPLACE FUNCTION ${constants.SCHEMA_NAME}.reorg_checkpoint()
|
|
258
346
|
RETURNS TRIGGER AS $$
|
|
259
347
|
DECLARE
|
|
260
348
|
id_col TEXT := TG_ARGV[0]::TEXT;
|
|
@@ -264,13 +352,13 @@ async function initializeReorgRollbackTable(tx, indexerId) {
|
|
|
264
352
|
old_id_value TEXT := row_to_json(OLD.*)->>id_col;
|
|
265
353
|
BEGIN
|
|
266
354
|
IF (TG_OP = 'DELETE') THEN
|
|
267
|
-
INSERT INTO
|
|
355
|
+
INSERT INTO ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
|
|
268
356
|
SELECT 'D', TG_TABLE_NAME, order_key, old_id_value, row_to_json(OLD.*), indexer_id;
|
|
269
357
|
ELSIF (TG_OP = 'UPDATE') THEN
|
|
270
|
-
INSERT INTO
|
|
358
|
+
INSERT INTO ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
|
|
271
359
|
SELECT 'U', TG_TABLE_NAME, order_key, new_id_value, row_to_json(OLD.*), indexer_id;
|
|
272
360
|
ELSIF (TG_OP = 'INSERT') THEN
|
|
273
|
-
INSERT INTO
|
|
361
|
+
INSERT INTO ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}(op, table_name, cursor, row_id, row_value, indexer_id)
|
|
274
362
|
SELECT 'I', TG_TABLE_NAME, order_key, new_id_value, null, indexer_id;
|
|
275
363
|
END IF;
|
|
276
364
|
RETURN NULL;
|
|
@@ -300,7 +388,7 @@ async function registerTriggers(tx, tables, endCursor, idColumn, indexerId) {
|
|
|
300
388
|
CREATE CONSTRAINT TRIGGER ${getReorgTriggerName(table, indexerId)}
|
|
301
389
|
AFTER INSERT OR UPDATE OR DELETE ON ${table}
|
|
302
390
|
DEFERRABLE INITIALLY DEFERRED
|
|
303
|
-
FOR EACH ROW EXECUTE FUNCTION reorg_checkpoint('${idColumn}', ${`${Number(endCursor.orderKey)}`}, '${indexerId}');
|
|
391
|
+
FOR EACH ROW EXECUTE FUNCTION ${constants.SCHEMA_NAME}.reorg_checkpoint('${idColumn}', ${`${Number(endCursor.orderKey)}`}, '${indexerId}');
|
|
304
392
|
`)
|
|
305
393
|
);
|
|
306
394
|
}
|
|
@@ -329,7 +417,7 @@ async function invalidate(tx, cursor, idColumn, indexerId) {
|
|
|
329
417
|
const { rows: result } = await tx.execute(
|
|
330
418
|
drizzleOrm.sql.raw(`
|
|
331
419
|
WITH deleted AS (
|
|
332
|
-
DELETE FROM
|
|
420
|
+
DELETE FROM ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
|
|
333
421
|
WHERE cursor > ${Number(cursor.orderKey)}
|
|
334
422
|
AND indexer_id = '${indexerId}'
|
|
335
423
|
RETURNING *
|
|
@@ -422,7 +510,7 @@ async function finalize(tx, cursor, indexerId) {
|
|
|
422
510
|
try {
|
|
423
511
|
await tx.execute(
|
|
424
512
|
drizzleOrm.sql.raw(`
|
|
425
|
-
DELETE FROM
|
|
513
|
+
DELETE FROM ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
|
|
426
514
|
WHERE cursor <= ${Number(cursor.orderKey)}
|
|
427
515
|
AND indexer_id = '${indexerId}'
|
|
428
516
|
`)
|
|
@@ -433,28 +521,60 @@ async function finalize(tx, cursor, indexerId) {
|
|
|
433
521
|
});
|
|
434
522
|
}
|
|
435
523
|
}
|
|
524
|
+
async function cleanupStorage(tx, tables, indexerId) {
|
|
525
|
+
try {
|
|
526
|
+
for (const table of tables) {
|
|
527
|
+
await tx.execute(
|
|
528
|
+
drizzleOrm.sql.raw(
|
|
529
|
+
`DROP TRIGGER IF EXISTS ${getReorgTriggerName(table, indexerId)} ON ${table};`
|
|
530
|
+
)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
await tx.execute(
|
|
534
|
+
drizzleOrm.sql.raw(`
|
|
535
|
+
DELETE FROM ${constants.SCHEMA_NAME}.${ROLLBACK_TABLE_NAME}
|
|
536
|
+
WHERE indexer_id = '${indexerId}'
|
|
537
|
+
`)
|
|
538
|
+
);
|
|
539
|
+
for (const table of tables) {
|
|
540
|
+
try {
|
|
541
|
+
await tx.execute(drizzleOrm.sql.raw(`TRUNCATE TABLE ${table} CASCADE;`));
|
|
542
|
+
} catch (error) {
|
|
543
|
+
throw new DrizzleStorageError(`Failed to truncate table ${table}`, {
|
|
544
|
+
cause: error
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch (error) {
|
|
549
|
+
throw new DrizzleStorageError("Failed to clean up storage", {
|
|
550
|
+
cause: error
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
436
554
|
|
|
437
|
-
const DRIZZLE_PROPERTY = "_drizzle";
|
|
438
555
|
const MAX_RETRIES = 5;
|
|
439
556
|
function useDrizzleStorage(_db) {
|
|
440
557
|
const context = indexer.useIndexerContext();
|
|
441
|
-
if (!context[DRIZZLE_PROPERTY]) {
|
|
558
|
+
if (!context[constants.DRIZZLE_PROPERTY]) {
|
|
442
559
|
throw new DrizzleStorageError(
|
|
443
560
|
"drizzle storage is not available. Did you register the plugin?"
|
|
444
561
|
);
|
|
445
562
|
}
|
|
446
|
-
return context[DRIZZLE_PROPERTY];
|
|
563
|
+
return context[constants.DRIZZLE_PROPERTY];
|
|
447
564
|
}
|
|
448
565
|
function drizzleStorage({
|
|
449
566
|
db,
|
|
450
567
|
persistState: enablePersistence = true,
|
|
451
568
|
indexerName: identifier = "default",
|
|
452
569
|
schema,
|
|
453
|
-
idColumn = "id"
|
|
570
|
+
idColumn = "id",
|
|
571
|
+
migrate: migrateOptions
|
|
454
572
|
}) {
|
|
455
|
-
return plugins.defineIndexerPlugin((indexer) => {
|
|
573
|
+
return plugins.defineIndexerPlugin((indexer$1) => {
|
|
456
574
|
let tableNames = [];
|
|
457
575
|
let indexerId = "";
|
|
576
|
+
const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
|
|
577
|
+
let prevFinality;
|
|
458
578
|
try {
|
|
459
579
|
tableNames = Object.values(schema ?? db._.schema ?? {}).map(
|
|
460
580
|
(table) => table.dbName
|
|
@@ -464,12 +584,34 @@ function drizzleStorage({
|
|
|
464
584
|
cause: error
|
|
465
585
|
});
|
|
466
586
|
}
|
|
467
|
-
indexer.hooks.hook("run:before", async () => {
|
|
468
|
-
const
|
|
587
|
+
indexer$1.hooks.hook("run:before", async () => {
|
|
588
|
+
const internalContext = plugins$1.useInternalContext();
|
|
589
|
+
const context = indexer.useIndexerContext();
|
|
590
|
+
const logger = plugins.useLogger();
|
|
591
|
+
context[constants.DRIZZLE_STORAGE_DB_PROPERTY] = db;
|
|
592
|
+
const { indexerName: indexerFileName, availableIndexers } = internalContext;
|
|
469
593
|
indexerId = internal.generateIndexerId(indexerFileName, identifier);
|
|
594
|
+
if (alwaysReindex) {
|
|
595
|
+
logger.warn(
|
|
596
|
+
`Reindexing: Deleting all data from tables - ${tableNames.join(", ")}`
|
|
597
|
+
);
|
|
598
|
+
await withTransaction(db, async (tx) => {
|
|
599
|
+
await cleanupStorage(tx, tableNames, indexerId);
|
|
600
|
+
if (enablePersistence) {
|
|
601
|
+
await resetPersistence({ tx, indexerId });
|
|
602
|
+
}
|
|
603
|
+
logger.success("Tables have been cleaned up for reindexing");
|
|
604
|
+
});
|
|
605
|
+
}
|
|
470
606
|
let retries = 0;
|
|
607
|
+
let migrationsApplied = false;
|
|
471
608
|
while (retries <= MAX_RETRIES) {
|
|
472
609
|
try {
|
|
610
|
+
if (migrateOptions && !migrationsApplied) {
|
|
611
|
+
await migrate(db, migrateOptions);
|
|
612
|
+
migrationsApplied = true;
|
|
613
|
+
logger.success("Migrations applied");
|
|
614
|
+
}
|
|
473
615
|
await withTransaction(db, async (tx) => {
|
|
474
616
|
await initializeReorgRollbackTable(tx, indexerId);
|
|
475
617
|
if (enablePersistence) {
|
|
@@ -479,6 +621,9 @@ function drizzleStorage({
|
|
|
479
621
|
break;
|
|
480
622
|
} catch (error) {
|
|
481
623
|
if (retries === MAX_RETRIES) {
|
|
624
|
+
if (error instanceof DrizzleStorageError) {
|
|
625
|
+
throw error;
|
|
626
|
+
}
|
|
482
627
|
throw new DrizzleStorageError(
|
|
483
628
|
"Initialization failed after 5 retries",
|
|
484
629
|
{
|
|
@@ -491,7 +636,7 @@ function drizzleStorage({
|
|
|
491
636
|
}
|
|
492
637
|
}
|
|
493
638
|
});
|
|
494
|
-
indexer.hooks.hook("connect:before", async ({ request }) => {
|
|
639
|
+
indexer$1.hooks.hook("connect:before", async ({ request }) => {
|
|
495
640
|
if (!enablePersistence) {
|
|
496
641
|
return;
|
|
497
642
|
}
|
|
@@ -508,7 +653,7 @@ function drizzleStorage({
|
|
|
508
653
|
}
|
|
509
654
|
});
|
|
510
655
|
});
|
|
511
|
-
indexer.hooks.hook("connect:after", async ({ request }) => {
|
|
656
|
+
indexer$1.hooks.hook("connect:after", async ({ request }) => {
|
|
512
657
|
const cursor = request.startingCursor;
|
|
513
658
|
if (!cursor) {
|
|
514
659
|
return;
|
|
@@ -520,7 +665,7 @@ function drizzleStorage({
|
|
|
520
665
|
}
|
|
521
666
|
});
|
|
522
667
|
});
|
|
523
|
-
indexer.hooks.hook("connect:factory", async ({ request, endCursor }) => {
|
|
668
|
+
indexer$1.hooks.hook("connect:factory", async ({ request, endCursor }) => {
|
|
524
669
|
if (!enablePersistence) {
|
|
525
670
|
return;
|
|
526
671
|
}
|
|
@@ -534,7 +679,7 @@ function drizzleStorage({
|
|
|
534
679
|
});
|
|
535
680
|
}
|
|
536
681
|
});
|
|
537
|
-
indexer.hooks.hook("message:finalize", async ({ message }) => {
|
|
682
|
+
indexer$1.hooks.hook("message:finalize", async ({ message }) => {
|
|
538
683
|
const { cursor } = message.finalize;
|
|
539
684
|
if (!cursor) {
|
|
540
685
|
throw new DrizzleStorageError("Finalized Cursor is undefined");
|
|
@@ -546,7 +691,7 @@ function drizzleStorage({
|
|
|
546
691
|
}
|
|
547
692
|
});
|
|
548
693
|
});
|
|
549
|
-
indexer.hooks.hook("message:invalidate", async ({ message }) => {
|
|
694
|
+
indexer$1.hooks.hook("message:invalidate", async ({ message }) => {
|
|
550
695
|
const { cursor } = message.invalidate;
|
|
551
696
|
if (!cursor) {
|
|
552
697
|
throw new DrizzleStorageError("Invalidate Cursor is undefined");
|
|
@@ -558,15 +703,18 @@ function drizzleStorage({
|
|
|
558
703
|
}
|
|
559
704
|
});
|
|
560
705
|
});
|
|
561
|
-
indexer.hooks.hook("handler:middleware", async ({ use }) => {
|
|
706
|
+
indexer$1.hooks.hook("handler:middleware", async ({ use }) => {
|
|
562
707
|
use(async (context, next) => {
|
|
563
708
|
try {
|
|
564
|
-
const { endCursor, finality } = context;
|
|
709
|
+
const { endCursor, finality, cursor } = context;
|
|
565
710
|
if (!endCursor) {
|
|
566
711
|
throw new DrizzleStorageError("End Cursor is undefined");
|
|
567
712
|
}
|
|
568
713
|
await withTransaction(db, async (tx) => {
|
|
569
|
-
context[DRIZZLE_PROPERTY] = { db: tx };
|
|
714
|
+
context[constants.DRIZZLE_PROPERTY] = { db: tx };
|
|
715
|
+
if (prevFinality === "pending") {
|
|
716
|
+
await invalidate(tx, cursor, idColumn, indexerId);
|
|
717
|
+
}
|
|
570
718
|
if (finality !== "finalized") {
|
|
571
719
|
await registerTriggers(
|
|
572
720
|
tx,
|
|
@@ -577,14 +725,15 @@ function drizzleStorage({
|
|
|
577
725
|
);
|
|
578
726
|
}
|
|
579
727
|
await next();
|
|
580
|
-
delete context[DRIZZLE_PROPERTY];
|
|
581
|
-
if (enablePersistence) {
|
|
728
|
+
delete context[constants.DRIZZLE_PROPERTY];
|
|
729
|
+
if (enablePersistence && finality !== "pending") {
|
|
582
730
|
await persistState({
|
|
583
731
|
tx,
|
|
584
732
|
endCursor,
|
|
585
733
|
indexerId
|
|
586
734
|
});
|
|
587
735
|
}
|
|
736
|
+
prevFinality = finality;
|
|
588
737
|
});
|
|
589
738
|
if (finality !== "finalized") {
|
|
590
739
|
await removeTriggers(db, tableNames, indexerId);
|
|
@@ -600,5 +749,7 @@ function drizzleStorage({
|
|
|
600
749
|
});
|
|
601
750
|
}
|
|
602
751
|
|
|
752
|
+
exports.drizzle = drizzle;
|
|
603
753
|
exports.drizzleStorage = drizzleStorage;
|
|
754
|
+
exports.migrate = migrate;
|
|
604
755
|
exports.useDrizzleStorage = useDrizzleStorage;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,17 +1,147 @@
|
|
|
1
1
|
import * as _apibara_indexer_plugins from '@apibara/indexer/plugins';
|
|
2
|
-
import { TablesRelationalConfig, ExtractTablesWithRelations } from 'drizzle-orm';
|
|
2
|
+
import { DrizzleConfig, TablesRelationalConfig, ExtractTablesWithRelations } from 'drizzle-orm';
|
|
3
3
|
import { PgQueryResultHKT, PgTransaction, PgDatabase } from 'drizzle-orm/pg-core';
|
|
4
|
+
import { PGliteOptions, PGlite } from '@electric-sql/pglite';
|
|
5
|
+
import { MigrationConfig } from 'drizzle-orm/migrator';
|
|
6
|
+
import { NodePgDatabase as NodePgDatabase$1 } from 'drizzle-orm/node-postgres';
|
|
7
|
+
import { PgliteDatabase as PgliteDatabase$1 } from 'drizzle-orm/pglite';
|
|
8
|
+
import pg from 'pg';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Union type of all possible drizzle database options
|
|
12
|
+
*/
|
|
13
|
+
type DrizzleOptions = PgliteDrizzleOptions | NodePgDrizzleOptions;
|
|
14
|
+
/**
|
|
15
|
+
* Configuration options for Node-Postgres database connection
|
|
16
|
+
*/
|
|
17
|
+
type NodePgDrizzleOptions = {
|
|
18
|
+
/**
|
|
19
|
+
* Type of database to use -
|
|
20
|
+
* - "pglite" - PGLite database
|
|
21
|
+
* - "node-postgres" - Node-Postgres database
|
|
22
|
+
* @default "pglite"
|
|
23
|
+
*/
|
|
24
|
+
type: "node-postgres";
|
|
25
|
+
/**
|
|
26
|
+
* Connection string to use for the database
|
|
27
|
+
* @default ""
|
|
28
|
+
*/
|
|
29
|
+
connectionString?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Pool configuration options for Node-Postgres
|
|
32
|
+
*/
|
|
33
|
+
poolConfig?: pg.PoolConfig;
|
|
34
|
+
/**
|
|
35
|
+
* Additional drizzle configuration options
|
|
36
|
+
*/
|
|
37
|
+
config?: Omit<DrizzleConfig, "schema">;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Configuration options for PGLite database connection
|
|
41
|
+
*/
|
|
42
|
+
type PgliteDrizzleOptions = {
|
|
43
|
+
/**
|
|
44
|
+
* Type of database to use -
|
|
45
|
+
* - "pglite" - PGLite database
|
|
46
|
+
* - "node-postgres" - Node-Postgres database
|
|
47
|
+
*/
|
|
48
|
+
type?: "pglite";
|
|
49
|
+
/**
|
|
50
|
+
* Connection string to use for the database
|
|
51
|
+
* @default process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://pglite"
|
|
52
|
+
*/
|
|
53
|
+
connectionString?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Pool configuration is not supported for PGLite
|
|
56
|
+
*/
|
|
57
|
+
poolConfig?: never;
|
|
58
|
+
/**
|
|
59
|
+
* Additional drizzle configuration options with PGLite specific connection options
|
|
60
|
+
*/
|
|
61
|
+
config?: Omit<DrizzleConfig, "schema"> & {
|
|
62
|
+
connection?: (PGliteOptions & {
|
|
63
|
+
dataDir?: string;
|
|
64
|
+
}) | string;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Extended PGLite database type with client information
|
|
69
|
+
*/
|
|
70
|
+
type PgliteDatabase<TSchema extends Record<string, unknown>> = PgliteDatabase$1<TSchema> & {
|
|
71
|
+
$client: PGlite;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Extended Node-Postgres database type with client information
|
|
75
|
+
*/
|
|
76
|
+
type NodePgDatabase<TSchema extends Record<string, unknown>> = NodePgDatabase$1<TSchema> & {
|
|
77
|
+
$client: pg.Pool;
|
|
78
|
+
};
|
|
79
|
+
type Database<TOptions extends DrizzleOptions, TSchema extends Record<string, unknown>> = TOptions extends PgliteDrizzleOptions ? PgliteDatabase<TSchema> : NodePgDatabase<TSchema>;
|
|
80
|
+
/**
|
|
81
|
+
* Creates a new Drizzle database instance based on the provided options
|
|
82
|
+
*
|
|
83
|
+
* @important connectionString defaults to process.env["POSTGRES_CONNECTION_STRING"], if not set, it defaults to "memory://" (in-memory pglite)
|
|
84
|
+
*
|
|
85
|
+
* @param options - Configuration options for the database connection
|
|
86
|
+
* @returns A configured Drizzle database instance
|
|
87
|
+
* @throws {Error} If an invalid database type is specified
|
|
88
|
+
*/
|
|
89
|
+
declare function drizzle<TSchema extends Record<string, unknown>, TOptions extends DrizzleOptions>(options?: TOptions & {
|
|
90
|
+
/**
|
|
91
|
+
* Schema to use for the database
|
|
92
|
+
* @default {}
|
|
93
|
+
*/
|
|
94
|
+
schema?: TSchema;
|
|
95
|
+
}): Database<TOptions, TSchema>;
|
|
96
|
+
/**
|
|
97
|
+
* Options for database migration
|
|
98
|
+
*/
|
|
99
|
+
type MigrateOptions = MigrationConfig;
|
|
100
|
+
/**
|
|
101
|
+
* Performs database migration based on the provided configuration
|
|
102
|
+
* @param db - The database instance to migrate
|
|
103
|
+
* @param options - Migration configuration options
|
|
104
|
+
*
|
|
105
|
+
* @important This function runs migrations on the database instance provided to the `drizzleStorage` plugin.
|
|
106
|
+
* It automatically detects the type of database and runs the appropriate migrate function
|
|
107
|
+
* (PGLite or Node-Postgres).
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* await migrate(db, { migrationsFolder: "./drizzle" });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
declare function migrate<TSchema extends Record<string, unknown>>(db: PgliteDatabase<TSchema> | NodePgDatabase<TSchema>, options: MigrateOptions): Promise<void>;
|
|
4
115
|
|
|
5
116
|
type DrizzleStorage<TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>> = {
|
|
6
117
|
db: PgTransaction<TQueryResult, TFullSchema, TSchema>;
|
|
7
118
|
};
|
|
8
119
|
declare function useDrizzleStorage<TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>>(_db?: PgDatabase<TQueryResult, TFullSchema, TSchema>): DrizzleStorage<TQueryResult, TFullSchema, TSchema>;
|
|
9
120
|
interface DrizzleStorageOptions<TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>> {
|
|
121
|
+
/**
|
|
122
|
+
* The Drizzle database instance.
|
|
123
|
+
*/
|
|
10
124
|
db: PgDatabase<TQueryResult, TFullSchema, TSchema>;
|
|
125
|
+
/**
|
|
126
|
+
* Whether to persist the indexer's state. Defaults to true.
|
|
127
|
+
*/
|
|
11
128
|
persistState?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* The name of the indexer. Default value is 'default'.
|
|
131
|
+
*/
|
|
12
132
|
indexerName?: string;
|
|
133
|
+
/**
|
|
134
|
+
* The schema of the database.
|
|
135
|
+
*/
|
|
13
136
|
schema?: Record<string, unknown>;
|
|
137
|
+
/**
|
|
138
|
+
* The column to use as the id. Defaults to 'id'.
|
|
139
|
+
*/
|
|
14
140
|
idColumn?: string;
|
|
141
|
+
/**
|
|
142
|
+
* The options for the database migration. When provided, the database will automatically run migrations before the indexer runs.
|
|
143
|
+
*/
|
|
144
|
+
migrate?: MigrateOptions;
|
|
15
145
|
}
|
|
16
146
|
/**
|
|
17
147
|
* Creates a plugin that uses Drizzle as the storage layer.
|
|
@@ -22,7 +152,8 @@ interface DrizzleStorageOptions<TQueryResult extends PgQueryResultHKT, TFullSche
|
|
|
22
152
|
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
23
153
|
* @param options.schema - The schema of the database.
|
|
24
154
|
* @param options.idColumn - The column to use as the id. Defaults to 'id'.
|
|
155
|
+
* @param options.migrate - The options for the database migration. when provided, the database will automatically run migrations before the indexer runs.
|
|
25
156
|
*/
|
|
26
|
-
declare function drizzleStorage<TFilter, TBlock, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>>({ db, persistState: enablePersistence, indexerName: identifier, schema, idColumn, }: DrizzleStorageOptions<TQueryResult, TFullSchema, TSchema>): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
157
|
+
declare function drizzleStorage<TFilter, TBlock, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>>({ db, persistState: enablePersistence, indexerName: identifier, schema, idColumn, migrate: migrateOptions, }: DrizzleStorageOptions<TQueryResult, TFullSchema, TSchema>): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
27
158
|
|
|
28
|
-
export { type DrizzleStorage, type DrizzleStorageOptions, drizzleStorage, useDrizzleStorage };
|
|
159
|
+
export { type Database, type DrizzleOptions, type DrizzleStorage, type DrizzleStorageOptions, type MigrateOptions, type NodePgDatabase, type NodePgDrizzleOptions, type PgliteDatabase, type PgliteDrizzleOptions, drizzle, drizzleStorage, migrate, useDrizzleStorage };
|