@apibara/plugin-sqlite 2.0.0-beta.27 → 2.0.0-beta.28
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 +126 -21
- package/dist/index.d.cts +3 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.mjs +126 -21
- package/package.json +3 -3
- package/src/index.ts +87 -17
- package/src/kv.ts +32 -0
- package/src/persistence.ts +58 -11
package/dist/index.cjs
CHANGED
|
@@ -68,6 +68,21 @@ class KeyValueStore {
|
|
|
68
68
|
this.db.prepare(statements$1.del).run(Number(this.endCursor.orderKey), key);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
function finalizeKV(db, cursor) {
|
|
72
|
+
assertInTransaction(db);
|
|
73
|
+
db.prepare(statements$1.finalize).run(
|
|
74
|
+
Number(cursor.orderKey)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function invalidateKV(db, cursor) {
|
|
78
|
+
assertInTransaction(db);
|
|
79
|
+
db.prepare(statements$1.invalidateDelete).run(
|
|
80
|
+
Number(cursor.orderKey)
|
|
81
|
+
);
|
|
82
|
+
db.prepare(statements$1.invalidateUpdate).run(
|
|
83
|
+
Number(cursor.orderKey)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
71
86
|
const statements$1 = {
|
|
72
87
|
createTable: `
|
|
73
88
|
CREATE TABLE IF NOT EXISTS kvs (
|
|
@@ -91,7 +106,17 @@ const statements$1 = {
|
|
|
91
106
|
del: `
|
|
92
107
|
UPDATE kvs
|
|
93
108
|
SET to_block = ?
|
|
94
|
-
WHERE k = ? AND to_block IS NULL
|
|
109
|
+
WHERE k = ? AND to_block IS NULL`,
|
|
110
|
+
finalize: `
|
|
111
|
+
DELETE FROM kvs
|
|
112
|
+
WHERE to_block < ?`,
|
|
113
|
+
invalidateDelete: `
|
|
114
|
+
DELETE FROM kvs
|
|
115
|
+
WHERE from_block > ?`,
|
|
116
|
+
invalidateUpdate: `
|
|
117
|
+
UPDATE kvs
|
|
118
|
+
SET to_block = NULL
|
|
119
|
+
WHERE to_block > ?`
|
|
95
120
|
};
|
|
96
121
|
|
|
97
122
|
const DEFAULT_INDEXER_ID = "default";
|
|
@@ -100,31 +125,33 @@ function initializePersistentState(db) {
|
|
|
100
125
|
db.exec(statements.createCheckpointsTable);
|
|
101
126
|
db.exec(statements.createFiltersTable);
|
|
102
127
|
}
|
|
103
|
-
function persistState(
|
|
128
|
+
function persistState(props) {
|
|
129
|
+
const { db, endCursor, filter, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
104
130
|
assertInTransaction(db);
|
|
105
131
|
db.prepare(statements.putCheckpoint).run(
|
|
106
|
-
|
|
132
|
+
indexerName,
|
|
107
133
|
Number(endCursor.orderKey),
|
|
108
134
|
endCursor.uniqueKey
|
|
109
135
|
);
|
|
110
136
|
if (filter) {
|
|
111
137
|
db.prepare(statements.updateFilterToBlock).run(
|
|
112
138
|
Number(endCursor.orderKey),
|
|
113
|
-
|
|
139
|
+
indexerName
|
|
114
140
|
);
|
|
115
141
|
db.prepare(statements.insertFilter).run(
|
|
116
|
-
|
|
142
|
+
indexerName,
|
|
117
143
|
serialize(filter),
|
|
118
144
|
Number(endCursor.orderKey)
|
|
119
145
|
);
|
|
120
146
|
}
|
|
121
147
|
}
|
|
122
|
-
function getState(
|
|
148
|
+
function getState(props) {
|
|
149
|
+
const { db, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
123
150
|
assertInTransaction(db);
|
|
124
151
|
const storedCursor = db.prepare(
|
|
125
152
|
statements.getCheckpoint
|
|
126
|
-
).get(
|
|
127
|
-
const storedFilter = db.prepare(statements.getFilter).get(
|
|
153
|
+
).get(indexerName);
|
|
154
|
+
const storedFilter = db.prepare(statements.getFilter).get(indexerName);
|
|
128
155
|
let cursor;
|
|
129
156
|
let filter;
|
|
130
157
|
if (storedCursor?.order_key) {
|
|
@@ -138,6 +165,26 @@ function getState(db) {
|
|
|
138
165
|
}
|
|
139
166
|
return { cursor, filter };
|
|
140
167
|
}
|
|
168
|
+
function finalizeState(props) {
|
|
169
|
+
const { cursor, db, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
170
|
+
assertInTransaction(db);
|
|
171
|
+
db.prepare(statements.finalizeFilter).run(
|
|
172
|
+
indexerName,
|
|
173
|
+
Number(cursor.orderKey)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
function invalidateState(props) {
|
|
177
|
+
const { cursor, db, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
178
|
+
assertInTransaction(db);
|
|
179
|
+
db.prepare(statements.invalidateFilterDelete).run(
|
|
180
|
+
indexerName,
|
|
181
|
+
Number(cursor.orderKey)
|
|
182
|
+
);
|
|
183
|
+
db.prepare(statements.invalidateFilterUpdate).run(
|
|
184
|
+
indexerName,
|
|
185
|
+
Number(cursor.orderKey)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
141
188
|
const statements = {
|
|
142
189
|
createCheckpointsTable: `
|
|
143
190
|
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
@@ -182,7 +229,17 @@ const statements = {
|
|
|
182
229
|
from_block = excluded.from_block`,
|
|
183
230
|
delFilter: `
|
|
184
231
|
DELETE FROM filters
|
|
185
|
-
WHERE id =
|
|
232
|
+
WHERE id = ?`,
|
|
233
|
+
finalizeFilter: `
|
|
234
|
+
DELETE FROM filters
|
|
235
|
+
WHERE id = ? AND to_block < ?`,
|
|
236
|
+
invalidateFilterDelete: `
|
|
237
|
+
DELETE FROM filters
|
|
238
|
+
WHERE id = ? AND from_block > ?`,
|
|
239
|
+
invalidateFilterUpdate: `
|
|
240
|
+
UPDATE filters
|
|
241
|
+
SET to_block = NULL
|
|
242
|
+
WHERE id = ? AND to_block > ?`
|
|
186
243
|
};
|
|
187
244
|
|
|
188
245
|
const KV_PROPERTY = "_kv_sqlite";
|
|
@@ -200,7 +257,8 @@ function sqliteStorage({
|
|
|
200
257
|
persistState: enablePersistState = true,
|
|
201
258
|
keyValueStore: enableKeyValueStore = true,
|
|
202
259
|
serialize: serializeFn = serialize,
|
|
203
|
-
deserialize: deserializeFn = deserialize
|
|
260
|
+
deserialize: deserializeFn = deserialize,
|
|
261
|
+
indexerName
|
|
204
262
|
}) {
|
|
205
263
|
return plugins.defineIndexerPlugin((indexer) => {
|
|
206
264
|
indexer.hooks.hook("run:before", async () => {
|
|
@@ -218,7 +276,7 @@ function sqliteStorage({
|
|
|
218
276
|
return;
|
|
219
277
|
}
|
|
220
278
|
return await withTransaction(database, async (db) => {
|
|
221
|
-
const { cursor, filter } = getState(db);
|
|
279
|
+
const { cursor, filter } = getState({ db, indexerName });
|
|
222
280
|
if (cursor) {
|
|
223
281
|
request.startingCursor = cursor;
|
|
224
282
|
}
|
|
@@ -227,6 +285,62 @@ function sqliteStorage({
|
|
|
227
285
|
}
|
|
228
286
|
});
|
|
229
287
|
});
|
|
288
|
+
indexer.hooks.hook("connect:after", async ({ request }) => {
|
|
289
|
+
const cursor = request.startingCursor;
|
|
290
|
+
if (!cursor) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
await withTransaction(database, async (db) => {
|
|
294
|
+
if (enablePersistState) {
|
|
295
|
+
invalidateState({ db, cursor, indexerName });
|
|
296
|
+
}
|
|
297
|
+
if (enableKeyValueStore) {
|
|
298
|
+
invalidateKV(db, cursor);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
303
|
+
if (!enablePersistState) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
assertInTransaction(database);
|
|
307
|
+
if (endCursor && request.filter[1]) {
|
|
308
|
+
persistState({
|
|
309
|
+
db: database,
|
|
310
|
+
endCursor,
|
|
311
|
+
indexerName,
|
|
312
|
+
filter: request.filter[1]
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
indexer.hooks.hook("message:finalize", async ({ message }) => {
|
|
317
|
+
const { cursor } = message.finalize;
|
|
318
|
+
if (!cursor) {
|
|
319
|
+
throw new SqliteStorageError("finalized cursor is undefined");
|
|
320
|
+
}
|
|
321
|
+
await withTransaction(database, async (db) => {
|
|
322
|
+
if (enablePersistState) {
|
|
323
|
+
finalizeState({ db, cursor, indexerName });
|
|
324
|
+
}
|
|
325
|
+
if (enableKeyValueStore) {
|
|
326
|
+
finalizeKV(db, cursor);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
indexer.hooks.hook("message:invalidate", async ({ message }) => {
|
|
331
|
+
const { cursor } = message.invalidate;
|
|
332
|
+
if (!cursor) {
|
|
333
|
+
throw new SqliteStorageError("invalidate cursor is undefined");
|
|
334
|
+
}
|
|
335
|
+
await withTransaction(database, async (db) => {
|
|
336
|
+
if (enablePersistState) {
|
|
337
|
+
invalidateState({ db, cursor, indexerName });
|
|
338
|
+
}
|
|
339
|
+
if (enableKeyValueStore) {
|
|
340
|
+
invalidateKV(db, cursor);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
});
|
|
230
344
|
indexer.hooks.hook("handler:middleware", ({ use }) => {
|
|
231
345
|
use(async (ctx, next) => {
|
|
232
346
|
if (!ctx.finality) {
|
|
@@ -249,7 +363,7 @@ function sqliteStorage({
|
|
|
249
363
|
}
|
|
250
364
|
await next();
|
|
251
365
|
if (enablePersistState) {
|
|
252
|
-
persistState(db, ctx.endCursor);
|
|
366
|
+
persistState({ db, endCursor: ctx.endCursor, indexerName });
|
|
253
367
|
}
|
|
254
368
|
if (enableKeyValueStore) {
|
|
255
369
|
delete ctx[KV_PROPERTY];
|
|
@@ -257,15 +371,6 @@ function sqliteStorage({
|
|
|
257
371
|
});
|
|
258
372
|
});
|
|
259
373
|
});
|
|
260
|
-
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
261
|
-
if (!enablePersistState) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
assertInTransaction(database);
|
|
265
|
-
if (endCursor && request.filter[1]) {
|
|
266
|
-
persistState(database, endCursor, request.filter[1]);
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
374
|
});
|
|
270
375
|
}
|
|
271
376
|
|
package/dist/index.d.cts
CHANGED
|
@@ -22,6 +22,7 @@ type SqliteStorageOptions = {
|
|
|
22
22
|
database: Database;
|
|
23
23
|
keyValueStore?: boolean;
|
|
24
24
|
persistState?: boolean;
|
|
25
|
+
indexerName?: string;
|
|
25
26
|
serialize?: SerializeFn;
|
|
26
27
|
deserialize?: DeserializeFn;
|
|
27
28
|
};
|
|
@@ -34,7 +35,8 @@ type SqliteStorageOptions = {
|
|
|
34
35
|
* @param options.keyValueStore - Whether to enable the Key-Value store. Defaults to true.
|
|
35
36
|
* @param options.serialize - A function to serialize the value to the KV.
|
|
36
37
|
* @param options.deserialize - A function to deserialize the value from the KV.
|
|
38
|
+
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
37
39
|
*/
|
|
38
|
-
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
40
|
+
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, indexerName, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
39
41
|
|
|
40
42
|
export { KeyValueStore, type SqliteStorageOptions, sqliteStorage, useSqliteKeyValueStore };
|
package/dist/index.d.mts
CHANGED
|
@@ -22,6 +22,7 @@ type SqliteStorageOptions = {
|
|
|
22
22
|
database: Database;
|
|
23
23
|
keyValueStore?: boolean;
|
|
24
24
|
persistState?: boolean;
|
|
25
|
+
indexerName?: string;
|
|
25
26
|
serialize?: SerializeFn;
|
|
26
27
|
deserialize?: DeserializeFn;
|
|
27
28
|
};
|
|
@@ -34,7 +35,8 @@ type SqliteStorageOptions = {
|
|
|
34
35
|
* @param options.keyValueStore - Whether to enable the Key-Value store. Defaults to true.
|
|
35
36
|
* @param options.serialize - A function to serialize the value to the KV.
|
|
36
37
|
* @param options.deserialize - A function to deserialize the value from the KV.
|
|
38
|
+
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
37
39
|
*/
|
|
38
|
-
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
40
|
+
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, indexerName, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
39
41
|
|
|
40
42
|
export { KeyValueStore, type SqliteStorageOptions, sqliteStorage, useSqliteKeyValueStore };
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ type SqliteStorageOptions = {
|
|
|
22
22
|
database: Database;
|
|
23
23
|
keyValueStore?: boolean;
|
|
24
24
|
persistState?: boolean;
|
|
25
|
+
indexerName?: string;
|
|
25
26
|
serialize?: SerializeFn;
|
|
26
27
|
deserialize?: DeserializeFn;
|
|
27
28
|
};
|
|
@@ -34,7 +35,8 @@ type SqliteStorageOptions = {
|
|
|
34
35
|
* @param options.keyValueStore - Whether to enable the Key-Value store. Defaults to true.
|
|
35
36
|
* @param options.serialize - A function to serialize the value to the KV.
|
|
36
37
|
* @param options.deserialize - A function to deserialize the value from the KV.
|
|
38
|
+
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
37
39
|
*/
|
|
38
|
-
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
40
|
+
declare function sqliteStorage<TFilter, TBlock>({ database, persistState: enablePersistState, keyValueStore: enableKeyValueStore, serialize: serializeFn, deserialize: deserializeFn, indexerName, }: SqliteStorageOptions): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
39
41
|
|
|
40
42
|
export { KeyValueStore, type SqliteStorageOptions, sqliteStorage, useSqliteKeyValueStore };
|
package/dist/index.mjs
CHANGED
|
@@ -66,6 +66,21 @@ class KeyValueStore {
|
|
|
66
66
|
this.db.prepare(statements$1.del).run(Number(this.endCursor.orderKey), key);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
function finalizeKV(db, cursor) {
|
|
70
|
+
assertInTransaction(db);
|
|
71
|
+
db.prepare(statements$1.finalize).run(
|
|
72
|
+
Number(cursor.orderKey)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
function invalidateKV(db, cursor) {
|
|
76
|
+
assertInTransaction(db);
|
|
77
|
+
db.prepare(statements$1.invalidateDelete).run(
|
|
78
|
+
Number(cursor.orderKey)
|
|
79
|
+
);
|
|
80
|
+
db.prepare(statements$1.invalidateUpdate).run(
|
|
81
|
+
Number(cursor.orderKey)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
69
84
|
const statements$1 = {
|
|
70
85
|
createTable: `
|
|
71
86
|
CREATE TABLE IF NOT EXISTS kvs (
|
|
@@ -89,7 +104,17 @@ const statements$1 = {
|
|
|
89
104
|
del: `
|
|
90
105
|
UPDATE kvs
|
|
91
106
|
SET to_block = ?
|
|
92
|
-
WHERE k = ? AND to_block IS NULL
|
|
107
|
+
WHERE k = ? AND to_block IS NULL`,
|
|
108
|
+
finalize: `
|
|
109
|
+
DELETE FROM kvs
|
|
110
|
+
WHERE to_block < ?`,
|
|
111
|
+
invalidateDelete: `
|
|
112
|
+
DELETE FROM kvs
|
|
113
|
+
WHERE from_block > ?`,
|
|
114
|
+
invalidateUpdate: `
|
|
115
|
+
UPDATE kvs
|
|
116
|
+
SET to_block = NULL
|
|
117
|
+
WHERE to_block > ?`
|
|
93
118
|
};
|
|
94
119
|
|
|
95
120
|
const DEFAULT_INDEXER_ID = "default";
|
|
@@ -98,31 +123,33 @@ function initializePersistentState(db) {
|
|
|
98
123
|
db.exec(statements.createCheckpointsTable);
|
|
99
124
|
db.exec(statements.createFiltersTable);
|
|
100
125
|
}
|
|
101
|
-
function persistState(
|
|
126
|
+
function persistState(props) {
|
|
127
|
+
const { db, endCursor, filter, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
102
128
|
assertInTransaction(db);
|
|
103
129
|
db.prepare(statements.putCheckpoint).run(
|
|
104
|
-
|
|
130
|
+
indexerName,
|
|
105
131
|
Number(endCursor.orderKey),
|
|
106
132
|
endCursor.uniqueKey
|
|
107
133
|
);
|
|
108
134
|
if (filter) {
|
|
109
135
|
db.prepare(statements.updateFilterToBlock).run(
|
|
110
136
|
Number(endCursor.orderKey),
|
|
111
|
-
|
|
137
|
+
indexerName
|
|
112
138
|
);
|
|
113
139
|
db.prepare(statements.insertFilter).run(
|
|
114
|
-
|
|
140
|
+
indexerName,
|
|
115
141
|
serialize(filter),
|
|
116
142
|
Number(endCursor.orderKey)
|
|
117
143
|
);
|
|
118
144
|
}
|
|
119
145
|
}
|
|
120
|
-
function getState(
|
|
146
|
+
function getState(props) {
|
|
147
|
+
const { db, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
121
148
|
assertInTransaction(db);
|
|
122
149
|
const storedCursor = db.prepare(
|
|
123
150
|
statements.getCheckpoint
|
|
124
|
-
).get(
|
|
125
|
-
const storedFilter = db.prepare(statements.getFilter).get(
|
|
151
|
+
).get(indexerName);
|
|
152
|
+
const storedFilter = db.prepare(statements.getFilter).get(indexerName);
|
|
126
153
|
let cursor;
|
|
127
154
|
let filter;
|
|
128
155
|
if (storedCursor?.order_key) {
|
|
@@ -136,6 +163,26 @@ function getState(db) {
|
|
|
136
163
|
}
|
|
137
164
|
return { cursor, filter };
|
|
138
165
|
}
|
|
166
|
+
function finalizeState(props) {
|
|
167
|
+
const { cursor, db, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
168
|
+
assertInTransaction(db);
|
|
169
|
+
db.prepare(statements.finalizeFilter).run(
|
|
170
|
+
indexerName,
|
|
171
|
+
Number(cursor.orderKey)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
function invalidateState(props) {
|
|
175
|
+
const { cursor, db, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
176
|
+
assertInTransaction(db);
|
|
177
|
+
db.prepare(statements.invalidateFilterDelete).run(
|
|
178
|
+
indexerName,
|
|
179
|
+
Number(cursor.orderKey)
|
|
180
|
+
);
|
|
181
|
+
db.prepare(statements.invalidateFilterUpdate).run(
|
|
182
|
+
indexerName,
|
|
183
|
+
Number(cursor.orderKey)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
139
186
|
const statements = {
|
|
140
187
|
createCheckpointsTable: `
|
|
141
188
|
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
@@ -180,7 +227,17 @@ const statements = {
|
|
|
180
227
|
from_block = excluded.from_block`,
|
|
181
228
|
delFilter: `
|
|
182
229
|
DELETE FROM filters
|
|
183
|
-
WHERE id =
|
|
230
|
+
WHERE id = ?`,
|
|
231
|
+
finalizeFilter: `
|
|
232
|
+
DELETE FROM filters
|
|
233
|
+
WHERE id = ? AND to_block < ?`,
|
|
234
|
+
invalidateFilterDelete: `
|
|
235
|
+
DELETE FROM filters
|
|
236
|
+
WHERE id = ? AND from_block > ?`,
|
|
237
|
+
invalidateFilterUpdate: `
|
|
238
|
+
UPDATE filters
|
|
239
|
+
SET to_block = NULL
|
|
240
|
+
WHERE id = ? AND to_block > ?`
|
|
184
241
|
};
|
|
185
242
|
|
|
186
243
|
const KV_PROPERTY = "_kv_sqlite";
|
|
@@ -198,7 +255,8 @@ function sqliteStorage({
|
|
|
198
255
|
persistState: enablePersistState = true,
|
|
199
256
|
keyValueStore: enableKeyValueStore = true,
|
|
200
257
|
serialize: serializeFn = serialize,
|
|
201
|
-
deserialize: deserializeFn = deserialize
|
|
258
|
+
deserialize: deserializeFn = deserialize,
|
|
259
|
+
indexerName
|
|
202
260
|
}) {
|
|
203
261
|
return defineIndexerPlugin((indexer) => {
|
|
204
262
|
indexer.hooks.hook("run:before", async () => {
|
|
@@ -216,7 +274,7 @@ function sqliteStorage({
|
|
|
216
274
|
return;
|
|
217
275
|
}
|
|
218
276
|
return await withTransaction(database, async (db) => {
|
|
219
|
-
const { cursor, filter } = getState(db);
|
|
277
|
+
const { cursor, filter } = getState({ db, indexerName });
|
|
220
278
|
if (cursor) {
|
|
221
279
|
request.startingCursor = cursor;
|
|
222
280
|
}
|
|
@@ -225,6 +283,62 @@ function sqliteStorage({
|
|
|
225
283
|
}
|
|
226
284
|
});
|
|
227
285
|
});
|
|
286
|
+
indexer.hooks.hook("connect:after", async ({ request }) => {
|
|
287
|
+
const cursor = request.startingCursor;
|
|
288
|
+
if (!cursor) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
await withTransaction(database, async (db) => {
|
|
292
|
+
if (enablePersistState) {
|
|
293
|
+
invalidateState({ db, cursor, indexerName });
|
|
294
|
+
}
|
|
295
|
+
if (enableKeyValueStore) {
|
|
296
|
+
invalidateKV(db, cursor);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
301
|
+
if (!enablePersistState) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
assertInTransaction(database);
|
|
305
|
+
if (endCursor && request.filter[1]) {
|
|
306
|
+
persistState({
|
|
307
|
+
db: database,
|
|
308
|
+
endCursor,
|
|
309
|
+
indexerName,
|
|
310
|
+
filter: request.filter[1]
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
indexer.hooks.hook("message:finalize", async ({ message }) => {
|
|
315
|
+
const { cursor } = message.finalize;
|
|
316
|
+
if (!cursor) {
|
|
317
|
+
throw new SqliteStorageError("finalized cursor is undefined");
|
|
318
|
+
}
|
|
319
|
+
await withTransaction(database, async (db) => {
|
|
320
|
+
if (enablePersistState) {
|
|
321
|
+
finalizeState({ db, cursor, indexerName });
|
|
322
|
+
}
|
|
323
|
+
if (enableKeyValueStore) {
|
|
324
|
+
finalizeKV(db, cursor);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
indexer.hooks.hook("message:invalidate", async ({ message }) => {
|
|
329
|
+
const { cursor } = message.invalidate;
|
|
330
|
+
if (!cursor) {
|
|
331
|
+
throw new SqliteStorageError("invalidate cursor is undefined");
|
|
332
|
+
}
|
|
333
|
+
await withTransaction(database, async (db) => {
|
|
334
|
+
if (enablePersistState) {
|
|
335
|
+
invalidateState({ db, cursor, indexerName });
|
|
336
|
+
}
|
|
337
|
+
if (enableKeyValueStore) {
|
|
338
|
+
invalidateKV(db, cursor);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
});
|
|
228
342
|
indexer.hooks.hook("handler:middleware", ({ use }) => {
|
|
229
343
|
use(async (ctx, next) => {
|
|
230
344
|
if (!ctx.finality) {
|
|
@@ -247,7 +361,7 @@ function sqliteStorage({
|
|
|
247
361
|
}
|
|
248
362
|
await next();
|
|
249
363
|
if (enablePersistState) {
|
|
250
|
-
persistState(db, ctx.endCursor);
|
|
364
|
+
persistState({ db, endCursor: ctx.endCursor, indexerName });
|
|
251
365
|
}
|
|
252
366
|
if (enableKeyValueStore) {
|
|
253
367
|
delete ctx[KV_PROPERTY];
|
|
@@ -255,15 +369,6 @@ function sqliteStorage({
|
|
|
255
369
|
});
|
|
256
370
|
});
|
|
257
371
|
});
|
|
258
|
-
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
259
|
-
if (!enablePersistState) {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
assertInTransaction(database);
|
|
263
|
-
if (endCursor && request.filter[1]) {
|
|
264
|
-
persistState(database, endCursor, request.filter[1]);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
372
|
});
|
|
268
373
|
}
|
|
269
374
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apibara/plugin-sqlite",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"better-sqlite3": "^9.0.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@apibara/indexer": "2.0.0-beta.
|
|
39
|
-
"@apibara/protocol": "2.0.0-beta.
|
|
38
|
+
"@apibara/indexer": "2.0.0-beta.29",
|
|
39
|
+
"@apibara/protocol": "2.0.0-beta.29"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,10 +3,17 @@ import { defineIndexerPlugin } from "@apibara/indexer/plugins";
|
|
|
3
3
|
import { isCursor } from "@apibara/protocol";
|
|
4
4
|
import type { Database as SqliteDatabase } from "better-sqlite3";
|
|
5
5
|
|
|
6
|
-
import { KeyValueStore, initializeKeyValueStore } from "./kv";
|
|
7
6
|
import {
|
|
7
|
+
KeyValueStore,
|
|
8
|
+
finalizeKV,
|
|
9
|
+
initializeKeyValueStore,
|
|
10
|
+
invalidateKV,
|
|
11
|
+
} from "./kv";
|
|
12
|
+
import {
|
|
13
|
+
finalizeState,
|
|
8
14
|
getState,
|
|
9
15
|
initializePersistentState,
|
|
16
|
+
invalidateState,
|
|
10
17
|
persistState,
|
|
11
18
|
} from "./persistence";
|
|
12
19
|
import {
|
|
@@ -38,6 +45,7 @@ export type SqliteStorageOptions = {
|
|
|
38
45
|
database: SqliteDatabase;
|
|
39
46
|
keyValueStore?: boolean;
|
|
40
47
|
persistState?: boolean;
|
|
48
|
+
indexerName?: string;
|
|
41
49
|
|
|
42
50
|
serialize?: SerializeFn;
|
|
43
51
|
deserialize?: DeserializeFn;
|
|
@@ -52,6 +60,7 @@ export type SqliteStorageOptions = {
|
|
|
52
60
|
* @param options.keyValueStore - Whether to enable the Key-Value store. Defaults to true.
|
|
53
61
|
* @param options.serialize - A function to serialize the value to the KV.
|
|
54
62
|
* @param options.deserialize - A function to deserialize the value from the KV.
|
|
63
|
+
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
55
64
|
*/
|
|
56
65
|
export function sqliteStorage<TFilter, TBlock>({
|
|
57
66
|
database,
|
|
@@ -59,6 +68,7 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
59
68
|
keyValueStore: enableKeyValueStore = true,
|
|
60
69
|
serialize: serializeFn = serialize,
|
|
61
70
|
deserialize: deserializeFn = deserialize,
|
|
71
|
+
indexerName,
|
|
62
72
|
}: SqliteStorageOptions) {
|
|
63
73
|
return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
|
|
64
74
|
indexer.hooks.hook("run:before", async () => {
|
|
@@ -79,7 +89,7 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
return await withTransaction(database, async (db) => {
|
|
82
|
-
const { cursor, filter } = getState<TFilter>(db);
|
|
92
|
+
const { cursor, filter } = getState<TFilter>({ db, indexerName });
|
|
83
93
|
|
|
84
94
|
if (cursor) {
|
|
85
95
|
request.startingCursor = cursor;
|
|
@@ -91,6 +101,80 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
91
101
|
});
|
|
92
102
|
});
|
|
93
103
|
|
|
104
|
+
indexer.hooks.hook("connect:after", async ({ request }) => {
|
|
105
|
+
// On restart, we need to invalidate data for blocks that were processed but not persisted.
|
|
106
|
+
const cursor = request.startingCursor;
|
|
107
|
+
|
|
108
|
+
if (!cursor) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await withTransaction(database, async (db) => {
|
|
113
|
+
if (enablePersistState) {
|
|
114
|
+
invalidateState({ db, cursor, indexerName });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (enableKeyValueStore) {
|
|
118
|
+
invalidateKV(db, cursor);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
124
|
+
if (!enablePersistState) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// The connect factory hook is called while indexing a block, so the database should be in a transaction
|
|
129
|
+
// created by the middleware.
|
|
130
|
+
assertInTransaction(database);
|
|
131
|
+
|
|
132
|
+
if (endCursor && request.filter[1]) {
|
|
133
|
+
persistState({
|
|
134
|
+
db: database,
|
|
135
|
+
endCursor,
|
|
136
|
+
indexerName,
|
|
137
|
+
filter: request.filter[1],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
indexer.hooks.hook("message:finalize", async ({ message }) => {
|
|
143
|
+
const { cursor } = message.finalize;
|
|
144
|
+
|
|
145
|
+
if (!cursor) {
|
|
146
|
+
throw new SqliteStorageError("finalized cursor is undefined");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await withTransaction(database, async (db) => {
|
|
150
|
+
if (enablePersistState) {
|
|
151
|
+
finalizeState({ db, cursor, indexerName });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (enableKeyValueStore) {
|
|
155
|
+
finalizeKV(db, cursor);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
indexer.hooks.hook("message:invalidate", async ({ message }) => {
|
|
161
|
+
const { cursor } = message.invalidate;
|
|
162
|
+
|
|
163
|
+
if (!cursor) {
|
|
164
|
+
throw new SqliteStorageError("invalidate cursor is undefined");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
await withTransaction(database, async (db) => {
|
|
168
|
+
if (enablePersistState) {
|
|
169
|
+
invalidateState({ db, cursor, indexerName });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (enableKeyValueStore) {
|
|
173
|
+
invalidateKV(db, cursor);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
94
178
|
indexer.hooks.hook("handler:middleware", ({ use }) => {
|
|
95
179
|
use(async (ctx, next) => {
|
|
96
180
|
if (!ctx.finality) {
|
|
@@ -117,7 +201,7 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
117
201
|
await next();
|
|
118
202
|
|
|
119
203
|
if (enablePersistState) {
|
|
120
|
-
persistState(db, ctx.endCursor);
|
|
204
|
+
persistState({ db, endCursor: ctx.endCursor, indexerName });
|
|
121
205
|
}
|
|
122
206
|
|
|
123
207
|
if (enableKeyValueStore) {
|
|
@@ -126,19 +210,5 @@ export function sqliteStorage<TFilter, TBlock>({
|
|
|
126
210
|
});
|
|
127
211
|
});
|
|
128
212
|
});
|
|
129
|
-
|
|
130
|
-
indexer.hooks.hook("connect:factory", ({ request, endCursor }) => {
|
|
131
|
-
if (!enablePersistState) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// The connect factory hook is called while indexing a block, so the database should be in a transaction
|
|
136
|
-
// created by the middleware.
|
|
137
|
-
assertInTransaction(database);
|
|
138
|
-
|
|
139
|
-
if (endCursor && request.filter[1]) {
|
|
140
|
-
persistState(database, endCursor, request.filter[1]);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
213
|
});
|
|
144
214
|
}
|
package/src/kv.ts
CHANGED
|
@@ -50,6 +50,28 @@ export class KeyValueStore {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export function finalizeKV(db: Database, cursor: Cursor) {
|
|
54
|
+
assertInTransaction(db);
|
|
55
|
+
|
|
56
|
+
db.prepare<[number], KeyValueRow>(statements.finalize).run(
|
|
57
|
+
Number(cursor.orderKey),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function invalidateKV(db: Database, cursor: Cursor) {
|
|
62
|
+
assertInTransaction(db);
|
|
63
|
+
|
|
64
|
+
// Delete entries that started after the invalidation cursor
|
|
65
|
+
db.prepare<[number], KeyValueRow>(statements.invalidateDelete).run(
|
|
66
|
+
Number(cursor.orderKey),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Update entries that were supposed to end after the invalidation cursor
|
|
70
|
+
db.prepare<[number], KeyValueRow>(statements.invalidateUpdate).run(
|
|
71
|
+
Number(cursor.orderKey),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
type KeyValueRow = {
|
|
54
76
|
from_block: number;
|
|
55
77
|
to_block: number;
|
|
@@ -81,4 +103,14 @@ const statements = {
|
|
|
81
103
|
UPDATE kvs
|
|
82
104
|
SET to_block = ?
|
|
83
105
|
WHERE k = ? AND to_block IS NULL`,
|
|
106
|
+
finalize: `
|
|
107
|
+
DELETE FROM kvs
|
|
108
|
+
WHERE to_block < ?`,
|
|
109
|
+
invalidateDelete: `
|
|
110
|
+
DELETE FROM kvs
|
|
111
|
+
WHERE from_block > ?`,
|
|
112
|
+
invalidateUpdate: `
|
|
113
|
+
UPDATE kvs
|
|
114
|
+
SET to_block = NULL
|
|
115
|
+
WHERE to_block > ?`,
|
|
84
116
|
};
|
package/src/persistence.ts
CHANGED
|
@@ -11,15 +11,18 @@ export function initializePersistentState(db: Database) {
|
|
|
11
11
|
db.exec(statements.createFiltersTable);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function persistState<TFilter>(
|
|
15
|
-
db: Database
|
|
16
|
-
endCursor: Cursor
|
|
17
|
-
filter?: TFilter
|
|
18
|
-
|
|
14
|
+
export function persistState<TFilter>(props: {
|
|
15
|
+
db: Database;
|
|
16
|
+
endCursor: Cursor;
|
|
17
|
+
filter?: TFilter;
|
|
18
|
+
indexerName?: string;
|
|
19
|
+
}) {
|
|
20
|
+
const { db, endCursor, filter, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
21
|
+
|
|
19
22
|
assertInTransaction(db);
|
|
20
23
|
|
|
21
24
|
db.prepare(statements.putCheckpoint).run(
|
|
22
|
-
|
|
25
|
+
indexerName,
|
|
23
26
|
Number(endCursor.orderKey),
|
|
24
27
|
endCursor.uniqueKey,
|
|
25
28
|
);
|
|
@@ -27,26 +30,30 @@ export function persistState<TFilter>(
|
|
|
27
30
|
if (filter) {
|
|
28
31
|
db.prepare(statements.updateFilterToBlock).run(
|
|
29
32
|
Number(endCursor.orderKey),
|
|
30
|
-
|
|
33
|
+
indexerName,
|
|
31
34
|
);
|
|
32
35
|
db.prepare(statements.insertFilter).run(
|
|
33
|
-
|
|
36
|
+
indexerName,
|
|
34
37
|
serialize(filter as Record<string, unknown>),
|
|
35
38
|
Number(endCursor.orderKey),
|
|
36
39
|
);
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
export function getState<TFilter>(
|
|
43
|
+
export function getState<TFilter>(props: {
|
|
44
|
+
db: Database;
|
|
45
|
+
indexerName?: string;
|
|
46
|
+
}) {
|
|
47
|
+
const { db, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
41
48
|
assertInTransaction(db);
|
|
42
49
|
const storedCursor = db
|
|
43
50
|
.prepare<string, { order_key?: number; unique_key?: string }>(
|
|
44
51
|
statements.getCheckpoint,
|
|
45
52
|
)
|
|
46
|
-
.get(
|
|
53
|
+
.get(indexerName);
|
|
47
54
|
const storedFilter = db
|
|
48
55
|
.prepare<string, { filter: string }>(statements.getFilter)
|
|
49
|
-
.get(
|
|
56
|
+
.get(indexerName);
|
|
50
57
|
|
|
51
58
|
let cursor: Cursor | undefined;
|
|
52
59
|
let filter: TFilter | undefined;
|
|
@@ -65,6 +72,36 @@ export function getState<TFilter>(db: Database) {
|
|
|
65
72
|
return { cursor, filter };
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
export function finalizeState(props: {
|
|
76
|
+
db: Database;
|
|
77
|
+
cursor: Cursor;
|
|
78
|
+
indexerName?: string;
|
|
79
|
+
}) {
|
|
80
|
+
const { cursor, db, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
81
|
+
assertInTransaction(db);
|
|
82
|
+
db.prepare<[string, number]>(statements.finalizeFilter).run(
|
|
83
|
+
indexerName,
|
|
84
|
+
Number(cursor.orderKey),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function invalidateState(props: {
|
|
89
|
+
db: Database;
|
|
90
|
+
cursor: Cursor;
|
|
91
|
+
indexerName?: string;
|
|
92
|
+
}) {
|
|
93
|
+
const { cursor, db, indexerName = DEFAULT_INDEXER_ID } = props;
|
|
94
|
+
assertInTransaction(db);
|
|
95
|
+
db.prepare<[string, number]>(statements.invalidateFilterDelete).run(
|
|
96
|
+
indexerName,
|
|
97
|
+
Number(cursor.orderKey),
|
|
98
|
+
);
|
|
99
|
+
db.prepare<[string, number]>(statements.invalidateFilterUpdate).run(
|
|
100
|
+
indexerName,
|
|
101
|
+
Number(cursor.orderKey),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
68
105
|
const statements = {
|
|
69
106
|
createCheckpointsTable: `
|
|
70
107
|
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
@@ -110,4 +147,14 @@ const statements = {
|
|
|
110
147
|
delFilter: `
|
|
111
148
|
DELETE FROM filters
|
|
112
149
|
WHERE id = ?`,
|
|
150
|
+
finalizeFilter: `
|
|
151
|
+
DELETE FROM filters
|
|
152
|
+
WHERE id = ? AND to_block < ?`,
|
|
153
|
+
invalidateFilterDelete: `
|
|
154
|
+
DELETE FROM filters
|
|
155
|
+
WHERE id = ? AND from_block > ?`,
|
|
156
|
+
invalidateFilterUpdate: `
|
|
157
|
+
UPDATE filters
|
|
158
|
+
SET to_block = NULL
|
|
159
|
+
WHERE id = ? AND to_block > ?`,
|
|
113
160
|
};
|