@apibara/plugin-drizzle 2.0.0-beta.27 → 2.0.0-beta.29
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 +535 -4
- package/dist/index.d.cts +27 -2
- package/dist/index.d.mts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.mjs +535 -5
- package/package.json +10 -5
- package/src/index.ts +258 -3
- package/src/persistence.ts +245 -137
- package/src/storage.ts +279 -0
- package/src/utils.ts +42 -2
- package/src/persistence.test.ts +0 -7
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,542 @@
|
|
|
1
|
+
import { useIndexerContext } from '@apibara/indexer';
|
|
2
|
+
import { defineIndexerPlugin } from '@apibara/indexer/plugins';
|
|
3
|
+
import { and, eq, isNull, gt, lt, sql } from 'drizzle-orm';
|
|
4
|
+
import { pgTable, text, integer, primaryKey, serial, char, jsonb } from 'drizzle-orm/pg-core';
|
|
5
|
+
|
|
1
6
|
class DrizzleStorageError extends Error {
|
|
2
|
-
constructor(message) {
|
|
3
|
-
super(message);
|
|
7
|
+
constructor(message, options) {
|
|
8
|
+
super(message, options);
|
|
4
9
|
this.name = "DrizzleStorageError";
|
|
5
10
|
}
|
|
6
11
|
}
|
|
12
|
+
async function withTransaction(db, cb) {
|
|
13
|
+
return await db.transaction(async (txnDb) => {
|
|
14
|
+
return await cb(txnDb);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function deserialize(str) {
|
|
18
|
+
return JSON.parse(
|
|
19
|
+
str,
|
|
20
|
+
(_, value) => typeof value === "string" && value.match(/^\d+n$/) ? BigInt(value.slice(0, -1)) : value
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function serialize(obj) {
|
|
24
|
+
return JSON.stringify(
|
|
25
|
+
obj,
|
|
26
|
+
(_, value) => typeof value === "bigint" ? `${value.toString()}n` : value,
|
|
27
|
+
" "
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const CHECKPOINTS_TABLE_NAME = "__indexer_checkpoints";
|
|
32
|
+
const FILTERS_TABLE_NAME = "__indexer_filters";
|
|
33
|
+
const SCHEMA_VERSION_TABLE_NAME = "__indexer_schema_version";
|
|
34
|
+
const checkpoints = pgTable(CHECKPOINTS_TABLE_NAME, {
|
|
35
|
+
id: text("id").notNull().primaryKey(),
|
|
36
|
+
orderKey: integer("order_key").notNull(),
|
|
37
|
+
uniqueKey: text("unique_key").$type().notNull().default(void 0)
|
|
38
|
+
});
|
|
39
|
+
const filters = pgTable(
|
|
40
|
+
FILTERS_TABLE_NAME,
|
|
41
|
+
{
|
|
42
|
+
id: text("id").notNull(),
|
|
43
|
+
filter: text("filter").notNull(),
|
|
44
|
+
fromBlock: integer("from_block").notNull(),
|
|
45
|
+
toBlock: integer("to_block").$type().default(null)
|
|
46
|
+
},
|
|
47
|
+
(table) => [
|
|
48
|
+
{
|
|
49
|
+
pk: primaryKey({ columns: [table.id, table.fromBlock] })
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
);
|
|
53
|
+
const schemaVersion = pgTable(SCHEMA_VERSION_TABLE_NAME, {
|
|
54
|
+
k: integer("k").notNull().primaryKey(),
|
|
55
|
+
version: integer("version").notNull()
|
|
56
|
+
});
|
|
57
|
+
const CURRENT_SCHEMA_VERSION = 0;
|
|
58
|
+
const MIGRATIONS = [
|
|
59
|
+
// migrations[0]: v0 -> v1 (for future use)
|
|
60
|
+
[]
|
|
61
|
+
// Add more migration arrays for future versions
|
|
62
|
+
];
|
|
63
|
+
async function initializePersistentState(tx) {
|
|
64
|
+
await tx.execute(`
|
|
65
|
+
CREATE TABLE IF NOT EXISTS ${SCHEMA_VERSION_TABLE_NAME} (
|
|
66
|
+
k INTEGER PRIMARY KEY,
|
|
67
|
+
version INTEGER NOT NULL
|
|
68
|
+
);
|
|
69
|
+
`);
|
|
70
|
+
const versionRows = await tx.select().from(schemaVersion).where(eq(schemaVersion.k, 0));
|
|
71
|
+
const storedVersion = versionRows[0]?.version ?? -1;
|
|
72
|
+
if (storedVersion > CURRENT_SCHEMA_VERSION) {
|
|
73
|
+
throw new DrizzleStorageError(
|
|
74
|
+
`Database Persistence schema version v${storedVersion} is newer than supported version v${CURRENT_SCHEMA_VERSION}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
if (storedVersion === -1) {
|
|
79
|
+
await tx.execute(`
|
|
80
|
+
CREATE TABLE IF NOT EXISTS ${CHECKPOINTS_TABLE_NAME} (
|
|
81
|
+
id TEXT PRIMARY KEY,
|
|
82
|
+
order_key INTEGER NOT NULL,
|
|
83
|
+
unique_key TEXT NOT NULL DEFAULT ''
|
|
84
|
+
);
|
|
85
|
+
`);
|
|
86
|
+
await tx.execute(`
|
|
87
|
+
CREATE TABLE IF NOT EXISTS ${FILTERS_TABLE_NAME} (
|
|
88
|
+
id TEXT NOT NULL,
|
|
89
|
+
filter TEXT NOT NULL,
|
|
90
|
+
from_block INTEGER NOT NULL,
|
|
91
|
+
to_block INTEGER DEFAULT NULL,
|
|
92
|
+
PRIMARY KEY (id, from_block)
|
|
93
|
+
);
|
|
94
|
+
`);
|
|
95
|
+
await tx.insert(schemaVersion).values({
|
|
96
|
+
k: 0,
|
|
97
|
+
version: CURRENT_SCHEMA_VERSION
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
let currentVersion = storedVersion;
|
|
101
|
+
while (currentVersion < CURRENT_SCHEMA_VERSION) {
|
|
102
|
+
const migrationStatements = MIGRATIONS[currentVersion];
|
|
103
|
+
for (const statement of migrationStatements) {
|
|
104
|
+
await tx.execute(statement);
|
|
105
|
+
}
|
|
106
|
+
currentVersion++;
|
|
107
|
+
}
|
|
108
|
+
await tx.update(schemaVersion).set({ version: CURRENT_SCHEMA_VERSION }).where(eq(schemaVersion.k, 0));
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new DrizzleStorageError(
|
|
112
|
+
"Failed to initialize or migrate database schema",
|
|
113
|
+
{ cause: error }
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function persistState(props) {
|
|
118
|
+
const { tx, endCursor, filter, indexerName } = props;
|
|
119
|
+
try {
|
|
120
|
+
if (endCursor) {
|
|
121
|
+
await tx.insert(checkpoints).values({
|
|
122
|
+
id: indexerName,
|
|
123
|
+
orderKey: Number(endCursor.orderKey),
|
|
124
|
+
uniqueKey: endCursor.uniqueKey
|
|
125
|
+
}).onConflictDoUpdate({
|
|
126
|
+
target: checkpoints.id,
|
|
127
|
+
set: {
|
|
128
|
+
orderKey: Number(endCursor.orderKey),
|
|
129
|
+
uniqueKey: endCursor.uniqueKey
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
if (filter) {
|
|
133
|
+
await tx.update(filters).set({ toBlock: Number(endCursor.orderKey) }).where(and(eq(filters.id, indexerName), isNull(filters.toBlock)));
|
|
134
|
+
await tx.insert(filters).values({
|
|
135
|
+
id: indexerName,
|
|
136
|
+
filter: serialize(filter),
|
|
137
|
+
fromBlock: Number(endCursor.orderKey),
|
|
138
|
+
toBlock: null
|
|
139
|
+
}).onConflictDoUpdate({
|
|
140
|
+
target: [filters.id, filters.fromBlock],
|
|
141
|
+
set: {
|
|
142
|
+
filter: serialize(filter),
|
|
143
|
+
fromBlock: Number(endCursor.orderKey),
|
|
144
|
+
toBlock: null
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new DrizzleStorageError("Failed to persist state", {
|
|
151
|
+
cause: error
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function getState(props) {
|
|
156
|
+
const { tx, indexerName } = props;
|
|
157
|
+
try {
|
|
158
|
+
const checkpointRows = await tx.select().from(checkpoints).where(eq(checkpoints.id, indexerName));
|
|
159
|
+
const cursor = checkpointRows[0] ? {
|
|
160
|
+
orderKey: BigInt(checkpointRows[0].orderKey),
|
|
161
|
+
uniqueKey: checkpointRows[0].uniqueKey
|
|
162
|
+
} : void 0;
|
|
163
|
+
const filterRows = await tx.select().from(filters).where(and(eq(filters.id, indexerName), isNull(filters.toBlock)));
|
|
164
|
+
const filter = filterRows[0] ? deserialize(filterRows[0].filter) : void 0;
|
|
165
|
+
return { cursor, filter };
|
|
166
|
+
} catch (error) {
|
|
167
|
+
throw new DrizzleStorageError("Failed to get persistent state", {
|
|
168
|
+
cause: error
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async function invalidateState(props) {
|
|
173
|
+
const { tx, cursor, indexerName } = props;
|
|
174
|
+
try {
|
|
175
|
+
await tx.delete(filters).where(
|
|
176
|
+
and(
|
|
177
|
+
eq(filters.id, indexerName),
|
|
178
|
+
gt(filters.fromBlock, Number(cursor.orderKey))
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
await tx.update(filters).set({ toBlock: null }).where(
|
|
182
|
+
and(
|
|
183
|
+
eq(filters.id, indexerName),
|
|
184
|
+
gt(filters.toBlock, Number(cursor.orderKey))
|
|
185
|
+
)
|
|
186
|
+
);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw new DrizzleStorageError("Failed to invalidate state", {
|
|
189
|
+
cause: error
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function finalizeState(props) {
|
|
194
|
+
const { tx, cursor, indexerName } = props;
|
|
195
|
+
try {
|
|
196
|
+
await tx.delete(filters).where(
|
|
197
|
+
and(
|
|
198
|
+
eq(filters.id, indexerName),
|
|
199
|
+
lt(filters.toBlock, Number(cursor.orderKey))
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw new DrizzleStorageError("Failed to finalize state", {
|
|
204
|
+
cause: error
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
7
208
|
|
|
8
|
-
|
|
9
|
-
|
|
209
|
+
pgTable("__reorg_rollback", {
|
|
210
|
+
n: serial("n").primaryKey(),
|
|
211
|
+
op: char("op", { length: 1 }).$type().notNull(),
|
|
212
|
+
table_name: text("table_name").notNull(),
|
|
213
|
+
cursor: integer("cursor").notNull(),
|
|
214
|
+
row_id: text("row_id"),
|
|
215
|
+
row_value: jsonb("row_value")
|
|
216
|
+
});
|
|
217
|
+
async function initializeReorgRollbackTable(tx) {
|
|
218
|
+
try {
|
|
219
|
+
await tx.execute(
|
|
220
|
+
sql.raw(`
|
|
221
|
+
CREATE TABLE IF NOT EXISTS __reorg_rollback(
|
|
222
|
+
n SERIAL PRIMARY KEY,
|
|
223
|
+
op CHAR(1) NOT NULL,
|
|
224
|
+
table_name TEXT NOT NULL,
|
|
225
|
+
cursor INTEGER NOT NULL,
|
|
226
|
+
row_id TEXT,
|
|
227
|
+
row_value JSONB
|
|
228
|
+
);
|
|
229
|
+
`)
|
|
230
|
+
);
|
|
231
|
+
await tx.execute(
|
|
232
|
+
sql.raw(`
|
|
233
|
+
CREATE OR REPLACE FUNCTION reorg_checkpoint()
|
|
234
|
+
RETURNS TRIGGER AS $$
|
|
235
|
+
DECLARE
|
|
236
|
+
id_col TEXT := TG_ARGV[0]::TEXT;
|
|
237
|
+
order_key INTEGER := TG_ARGV[1]::INTEGER;
|
|
238
|
+
new_id_value TEXT := row_to_json(NEW.*)->>id_col;
|
|
239
|
+
old_id_value TEXT := row_to_json(OLD.*)->>id_col;
|
|
240
|
+
BEGIN
|
|
241
|
+
IF (TG_OP = 'DELETE') THEN
|
|
242
|
+
INSERT INTO __reorg_rollback(op, table_name, cursor, row_id, row_value)
|
|
243
|
+
SELECT 'D', TG_TABLE_NAME, order_key, old_id_value, row_to_json(OLD.*);
|
|
244
|
+
ELSIF (TG_OP = 'UPDATE') THEN
|
|
245
|
+
INSERT INTO __reorg_rollback(op, table_name, cursor, row_id, row_value)
|
|
246
|
+
SELECT 'U', TG_TABLE_NAME, order_key, new_id_value, row_to_json(OLD.*);
|
|
247
|
+
ELSIF (TG_OP = 'INSERT') THEN
|
|
248
|
+
INSERT INTO __reorg_rollback(op, table_name, cursor, row_id, row_value)
|
|
249
|
+
SELECT 'I', TG_TABLE_NAME, order_key, new_id_value, null;
|
|
250
|
+
END IF;
|
|
251
|
+
RETURN NULL;
|
|
252
|
+
END;
|
|
253
|
+
$$ LANGUAGE plpgsql;
|
|
254
|
+
`)
|
|
255
|
+
);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
throw new DrizzleStorageError("Failed to initialize reorg rollback table", {
|
|
258
|
+
cause: error
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async function registerTriggers(tx, tables, endCursor, idColumn) {
|
|
263
|
+
try {
|
|
264
|
+
for (const table of tables) {
|
|
265
|
+
await tx.execute(
|
|
266
|
+
sql.raw(`DROP TRIGGER IF EXISTS ${table}_reorg ON ${table};`)
|
|
267
|
+
);
|
|
268
|
+
await tx.execute(
|
|
269
|
+
sql.raw(`
|
|
270
|
+
CREATE CONSTRAINT TRIGGER ${table}_reorg
|
|
271
|
+
AFTER INSERT OR UPDATE OR DELETE ON ${table}
|
|
272
|
+
DEFERRABLE INITIALLY DEFERRED
|
|
273
|
+
FOR EACH ROW EXECUTE FUNCTION reorg_checkpoint('${idColumn}', ${`${Number(endCursor.orderKey)}`});
|
|
274
|
+
`)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
throw new DrizzleStorageError("Failed to register triggers", {
|
|
279
|
+
cause: error
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function removeTriggers(db, tables) {
|
|
284
|
+
try {
|
|
285
|
+
for (const table of tables) {
|
|
286
|
+
await db.execute(
|
|
287
|
+
sql.raw(`DROP TRIGGER IF EXISTS ${table}_reorg ON ${table};`)
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
throw new DrizzleStorageError("Failed to remove triggers", {
|
|
292
|
+
cause: error
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function invalidate(tx, cursor, idColumn) {
|
|
297
|
+
const { rows: result } = await tx.execute(
|
|
298
|
+
sql.raw(`
|
|
299
|
+
WITH deleted AS (
|
|
300
|
+
DELETE FROM __reorg_rollback
|
|
301
|
+
WHERE cursor > ${Number(cursor.orderKey)}
|
|
302
|
+
RETURNING *
|
|
303
|
+
)
|
|
304
|
+
SELECT * FROM deleted ORDER BY n DESC;
|
|
305
|
+
`)
|
|
306
|
+
);
|
|
307
|
+
if (!Array.isArray(result)) {
|
|
308
|
+
throw new DrizzleStorageError(
|
|
309
|
+
"Invalid result format from reorg_rollback query"
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
for (const op of result) {
|
|
313
|
+
switch (op.op) {
|
|
314
|
+
case "I":
|
|
315
|
+
try {
|
|
316
|
+
if (!op.row_id) {
|
|
317
|
+
throw new DrizzleStorageError("Insert operation has no row_id");
|
|
318
|
+
}
|
|
319
|
+
await tx.execute(
|
|
320
|
+
sql.raw(`
|
|
321
|
+
DELETE FROM ${op.table_name}
|
|
322
|
+
WHERE ${idColumn} = '${op.row_id}'
|
|
323
|
+
`)
|
|
324
|
+
);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
throw new DrizzleStorageError(
|
|
327
|
+
"Failed to invalidate | Operation - I",
|
|
328
|
+
{
|
|
329
|
+
cause: error
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
case "D":
|
|
335
|
+
try {
|
|
336
|
+
if (!op.row_value) {
|
|
337
|
+
throw new DrizzleStorageError("Delete operation has no row_value");
|
|
338
|
+
}
|
|
339
|
+
await tx.execute(
|
|
340
|
+
sql.raw(`
|
|
341
|
+
INSERT INTO ${op.table_name}
|
|
342
|
+
SELECT * FROM json_populate_record(null::${op.table_name}, '${JSON.stringify(op.row_value)}'::json)
|
|
343
|
+
`)
|
|
344
|
+
);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
throw new DrizzleStorageError(
|
|
347
|
+
"Failed to invalidate | Operation - D",
|
|
348
|
+
{
|
|
349
|
+
cause: error
|
|
350
|
+
}
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
case "U":
|
|
355
|
+
try {
|
|
356
|
+
if (!op.row_value || !op.row_id) {
|
|
357
|
+
throw new DrizzleStorageError(
|
|
358
|
+
"Update operation has no row_value or row_id"
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
const rowValue = typeof op.row_value === "string" ? JSON.parse(op.row_value) : op.row_value;
|
|
362
|
+
const nonIdKeys = Object.keys(rowValue).filter((k) => k !== idColumn);
|
|
363
|
+
const fields = nonIdKeys.map((c) => `${c} = prev.${c}`).join(", ");
|
|
364
|
+
const query = sql.raw(`
|
|
365
|
+
UPDATE ${op.table_name}
|
|
366
|
+
SET ${fields}
|
|
367
|
+
FROM (
|
|
368
|
+
SELECT * FROM json_populate_record(null::${op.table_name}, '${JSON.stringify(op.row_value)}'::json)
|
|
369
|
+
) as prev
|
|
370
|
+
WHERE ${op.table_name}.${idColumn} = '${op.row_id}'
|
|
371
|
+
`);
|
|
372
|
+
await tx.execute(query);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
throw new DrizzleStorageError(
|
|
375
|
+
"Failed to invalidate | Operation - U",
|
|
376
|
+
{
|
|
377
|
+
cause: error
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
default: {
|
|
383
|
+
throw new DrizzleStorageError(`Unknown operation: ${op.op}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async function finalize(tx, cursor) {
|
|
389
|
+
try {
|
|
390
|
+
await tx.execute(
|
|
391
|
+
sql.raw(`
|
|
392
|
+
DELETE FROM __reorg_rollback
|
|
393
|
+
WHERE cursor <= ${Number(cursor.orderKey)}
|
|
394
|
+
`)
|
|
395
|
+
);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
throw new DrizzleStorageError("Failed to finalize", {
|
|
398
|
+
cause: error
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const DRIZZLE_PROPERTY = "_drizzle";
|
|
404
|
+
function useDrizzleStorage(_db) {
|
|
405
|
+
const context = useIndexerContext();
|
|
406
|
+
if (!context[DRIZZLE_PROPERTY]) {
|
|
407
|
+
throw new DrizzleStorageError(
|
|
408
|
+
"drizzle storage is not available. Did you register the plugin?"
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
return context[DRIZZLE_PROPERTY];
|
|
412
|
+
}
|
|
413
|
+
function drizzleStorage({
|
|
414
|
+
db,
|
|
415
|
+
persistState: enablePersistence = true,
|
|
416
|
+
indexerName = "default",
|
|
417
|
+
schema,
|
|
418
|
+
idColumn = "id"
|
|
419
|
+
}) {
|
|
420
|
+
return defineIndexerPlugin((indexer) => {
|
|
421
|
+
let tableNames = [];
|
|
422
|
+
try {
|
|
423
|
+
tableNames = Object.values(schema ?? db._.schema ?? {}).map(
|
|
424
|
+
(table) => table.dbName
|
|
425
|
+
);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
throw new DrizzleStorageError("Failed to get table names from schema", {
|
|
428
|
+
cause: error
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
indexer.hooks.hook("run:before", async () => {
|
|
432
|
+
await withTransaction(db, async (tx) => {
|
|
433
|
+
await initializeReorgRollbackTable(tx);
|
|
434
|
+
if (enablePersistence) {
|
|
435
|
+
await initializePersistentState(tx);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
indexer.hooks.hook("connect:before", async ({ request }) => {
|
|
440
|
+
if (!enablePersistence) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
await withTransaction(db, async (tx) => {
|
|
444
|
+
const { cursor, filter } = await getState({
|
|
445
|
+
tx,
|
|
446
|
+
indexerName
|
|
447
|
+
});
|
|
448
|
+
if (cursor) {
|
|
449
|
+
request.startingCursor = cursor;
|
|
450
|
+
}
|
|
451
|
+
if (filter) {
|
|
452
|
+
request.filter[1] = filter;
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
indexer.hooks.hook("connect:after", async ({ request }) => {
|
|
457
|
+
const cursor = request.startingCursor;
|
|
458
|
+
if (!cursor) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
await withTransaction(db, async (tx) => {
|
|
462
|
+
await invalidate(tx, cursor, idColumn);
|
|
463
|
+
if (enablePersistence) {
|
|
464
|
+
await invalidateState({ tx, cursor, indexerName });
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
indexer.hooks.hook("connect:factory", async ({ request, endCursor }) => {
|
|
469
|
+
if (!enablePersistence) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const { db: tx } = useDrizzleStorage();
|
|
473
|
+
if (endCursor && request.filter[1]) {
|
|
474
|
+
await persistState({
|
|
475
|
+
tx,
|
|
476
|
+
endCursor,
|
|
477
|
+
filter: request.filter[1],
|
|
478
|
+
indexerName
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
indexer.hooks.hook("message:finalize", async ({ message }) => {
|
|
483
|
+
const { cursor } = message.finalize;
|
|
484
|
+
if (!cursor) {
|
|
485
|
+
throw new DrizzleStorageError("Finalized Cursor is undefined");
|
|
486
|
+
}
|
|
487
|
+
await withTransaction(db, async (tx) => {
|
|
488
|
+
await finalize(tx, cursor);
|
|
489
|
+
if (enablePersistence) {
|
|
490
|
+
await finalizeState({ tx, cursor, indexerName });
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
indexer.hooks.hook("message:invalidate", async ({ message }) => {
|
|
495
|
+
const { cursor } = message.invalidate;
|
|
496
|
+
if (!cursor) {
|
|
497
|
+
throw new DrizzleStorageError("Invalidate Cursor is undefined");
|
|
498
|
+
}
|
|
499
|
+
await withTransaction(db, async (tx) => {
|
|
500
|
+
await invalidate(tx, cursor, idColumn);
|
|
501
|
+
if (enablePersistence) {
|
|
502
|
+
await invalidateState({ tx, cursor, indexerName });
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
indexer.hooks.hook("handler:middleware", async ({ use }) => {
|
|
507
|
+
use(async (context, next) => {
|
|
508
|
+
try {
|
|
509
|
+
const { endCursor, finality } = context;
|
|
510
|
+
if (!endCursor) {
|
|
511
|
+
throw new DrizzleStorageError("End Cursor is undefined");
|
|
512
|
+
}
|
|
513
|
+
await withTransaction(db, async (tx) => {
|
|
514
|
+
context[DRIZZLE_PROPERTY] = { db: tx };
|
|
515
|
+
if (finality !== "finalized") {
|
|
516
|
+
await registerTriggers(tx, tableNames, endCursor, idColumn);
|
|
517
|
+
}
|
|
518
|
+
await next();
|
|
519
|
+
delete context[DRIZZLE_PROPERTY];
|
|
520
|
+
if (enablePersistence) {
|
|
521
|
+
await persistState({
|
|
522
|
+
tx,
|
|
523
|
+
endCursor,
|
|
524
|
+
indexerName
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
if (finality !== "finalized") {
|
|
529
|
+
await removeTriggers(db, tableNames);
|
|
530
|
+
}
|
|
531
|
+
} catch (error) {
|
|
532
|
+
await removeTriggers(db, tableNames);
|
|
533
|
+
throw new DrizzleStorageError("Failed to run handler:middleware", {
|
|
534
|
+
cause: error
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
});
|
|
10
540
|
}
|
|
11
541
|
|
|
12
|
-
export { drizzleStorage };
|
|
542
|
+
export { drizzleStorage, useDrizzleStorage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apibara/plugin-drizzle",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -25,17 +25,22 @@
|
|
|
25
25
|
"test": "vitest",
|
|
26
26
|
"test:ci": "vitest run"
|
|
27
27
|
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"drizzle-orm": "^0.37.0",
|
|
30
|
+
"pg": "^8.13.1"
|
|
31
|
+
},
|
|
28
32
|
"devDependencies": {
|
|
29
33
|
"@electric-sql/pglite": "^0.2.14",
|
|
30
34
|
"@types/node": "^20.14.0",
|
|
35
|
+
"@types/pg": "^8.11.10",
|
|
36
|
+
"drizzle-orm": "^0.37.0",
|
|
37
|
+
"pg": "^8.13.1",
|
|
31
38
|
"unbuild": "^2.0.0",
|
|
32
39
|
"vitest": "^1.6.0"
|
|
33
40
|
},
|
|
34
41
|
"dependencies": {
|
|
35
|
-
"@apibara/indexer": "2.0.0-beta.
|
|
36
|
-
"@apibara/protocol": "2.0.0-beta.
|
|
37
|
-
"drizzle-orm": "^0.37.0",
|
|
38
|
-
"pg": "^8.13.1",
|
|
42
|
+
"@apibara/indexer": "2.0.0-beta.30",
|
|
43
|
+
"@apibara/protocol": "2.0.0-beta.30",
|
|
39
44
|
"postgres-range": "^1.1.4"
|
|
40
45
|
}
|
|
41
46
|
}
|