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