@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/src/index.ts
CHANGED
|
@@ -1,5 +1,260 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useIndexerContext } from "@apibara/indexer";
|
|
2
|
+
import { defineIndexerPlugin } from "@apibara/indexer/plugins";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
import type {
|
|
5
|
+
ExtractTablesWithRelations,
|
|
6
|
+
TablesRelationalConfig,
|
|
7
|
+
} from "drizzle-orm";
|
|
8
|
+
|
|
9
|
+
import type { Cursor, DataFinality } from "@apibara/protocol";
|
|
10
|
+
import type {
|
|
11
|
+
PgDatabase,
|
|
12
|
+
PgQueryResultHKT,
|
|
13
|
+
PgTransaction,
|
|
14
|
+
} from "drizzle-orm/pg-core";
|
|
15
|
+
import {
|
|
16
|
+
finalizeState,
|
|
17
|
+
getState,
|
|
18
|
+
initializePersistentState,
|
|
19
|
+
invalidateState,
|
|
20
|
+
persistState,
|
|
21
|
+
} from "./persistence";
|
|
22
|
+
import {
|
|
23
|
+
finalize,
|
|
24
|
+
initializeReorgRollbackTable,
|
|
25
|
+
invalidate,
|
|
26
|
+
registerTriggers,
|
|
27
|
+
removeTriggers,
|
|
28
|
+
} from "./storage";
|
|
29
|
+
import { DrizzleStorageError, withTransaction } from "./utils";
|
|
30
|
+
|
|
31
|
+
const DRIZZLE_PROPERTY = "_drizzle";
|
|
32
|
+
|
|
33
|
+
export type DrizzleStorage<
|
|
34
|
+
TQueryResult extends PgQueryResultHKT,
|
|
35
|
+
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
36
|
+
TSchema extends
|
|
37
|
+
TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
|
38
|
+
> = {
|
|
39
|
+
db: PgTransaction<TQueryResult, TFullSchema, TSchema>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function useDrizzleStorage<
|
|
43
|
+
TQueryResult extends PgQueryResultHKT,
|
|
44
|
+
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
45
|
+
TSchema extends
|
|
46
|
+
TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
|
47
|
+
>(
|
|
48
|
+
_db?: PgDatabase<TQueryResult, TFullSchema, TSchema>,
|
|
49
|
+
): DrizzleStorage<TQueryResult, TFullSchema, TSchema> {
|
|
50
|
+
const context = useIndexerContext();
|
|
51
|
+
|
|
52
|
+
if (!context[DRIZZLE_PROPERTY]) {
|
|
53
|
+
throw new DrizzleStorageError(
|
|
54
|
+
"drizzle storage is not available. Did you register the plugin?",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return context[DRIZZLE_PROPERTY];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface DrizzleStorageOptions<
|
|
62
|
+
TQueryResult extends PgQueryResultHKT,
|
|
63
|
+
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
64
|
+
TSchema extends
|
|
65
|
+
TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
|
66
|
+
> {
|
|
67
|
+
db: PgDatabase<TQueryResult, TFullSchema, TSchema>;
|
|
68
|
+
persistState?: boolean;
|
|
69
|
+
indexerName?: string;
|
|
70
|
+
schema?: Record<string, unknown>;
|
|
71
|
+
idColumn?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a plugin that uses Drizzle as the storage layer.
|
|
76
|
+
*
|
|
77
|
+
* Supports storing the indexer's state and provides a simple Key-Value store.
|
|
78
|
+
* @param options.db - The Drizzle database instance.
|
|
79
|
+
* @param options.persistState - Whether to persist the indexer's state. Defaults to true.
|
|
80
|
+
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
81
|
+
* @param options.schema - The schema of the database.
|
|
82
|
+
* @param options.idColumn - The column to use as the id. Defaults to 'id'.
|
|
83
|
+
*/
|
|
84
|
+
export function drizzleStorage<
|
|
85
|
+
TFilter,
|
|
86
|
+
TBlock,
|
|
87
|
+
TQueryResult extends PgQueryResultHKT,
|
|
88
|
+
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
89
|
+
TSchema extends
|
|
90
|
+
TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
|
91
|
+
>({
|
|
92
|
+
db,
|
|
93
|
+
persistState: enablePersistence = true,
|
|
94
|
+
indexerName = "default",
|
|
95
|
+
schema,
|
|
96
|
+
idColumn = "id",
|
|
97
|
+
}: DrizzleStorageOptions<TQueryResult, TFullSchema, TSchema>) {
|
|
98
|
+
return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
|
|
99
|
+
let tableNames: string[] = [];
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
tableNames = Object.values((schema as TSchema) ?? db._.schema ?? {}).map(
|
|
103
|
+
(table) => table.dbName,
|
|
104
|
+
);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw new DrizzleStorageError("Failed to get table names from schema", {
|
|
107
|
+
cause: error,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
indexer.hooks.hook("run:before", async () => {
|
|
112
|
+
await withTransaction(db, async (tx) => {
|
|
113
|
+
await initializeReorgRollbackTable(tx);
|
|
114
|
+
if (enablePersistence) {
|
|
115
|
+
await initializePersistentState(tx);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
indexer.hooks.hook("connect:before", async ({ request }) => {
|
|
121
|
+
if (!enablePersistence) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await withTransaction(db, async (tx) => {
|
|
126
|
+
const { cursor, filter } = await getState<
|
|
127
|
+
TFilter,
|
|
128
|
+
TQueryResult,
|
|
129
|
+
TFullSchema,
|
|
130
|
+
TSchema
|
|
131
|
+
>({
|
|
132
|
+
tx,
|
|
133
|
+
indexerName,
|
|
134
|
+
});
|
|
135
|
+
if (cursor) {
|
|
136
|
+
request.startingCursor = cursor;
|
|
137
|
+
}
|
|
138
|
+
if (filter) {
|
|
139
|
+
request.filter[1] = filter;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
indexer.hooks.hook("connect:after", async ({ request }) => {
|
|
145
|
+
// On restart, we need to invalidate data for blocks that were processed but not persisted.
|
|
146
|
+
const cursor = request.startingCursor;
|
|
147
|
+
|
|
148
|
+
if (!cursor) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await withTransaction(db, async (tx) => {
|
|
153
|
+
await invalidate(tx, cursor, idColumn);
|
|
154
|
+
|
|
155
|
+
if (enablePersistence) {
|
|
156
|
+
await invalidateState({ tx, cursor, indexerName });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
indexer.hooks.hook("connect:factory", async ({ request, endCursor }) => {
|
|
162
|
+
if (!enablePersistence) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// We can call this hook because this hook is called inside the transaction of handler:middleware
|
|
166
|
+
// so we have access to the transaction from the context
|
|
167
|
+
const { db: tx } = useDrizzleStorage(db);
|
|
168
|
+
|
|
169
|
+
if (endCursor && request.filter[1]) {
|
|
170
|
+
await persistState({
|
|
171
|
+
tx,
|
|
172
|
+
endCursor,
|
|
173
|
+
filter: request.filter[1],
|
|
174
|
+
indexerName,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
indexer.hooks.hook("message:finalize", async ({ message }) => {
|
|
180
|
+
const { cursor } = message.finalize;
|
|
181
|
+
|
|
182
|
+
if (!cursor) {
|
|
183
|
+
throw new DrizzleStorageError("Finalized Cursor is undefined");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await withTransaction(db, async (tx) => {
|
|
187
|
+
await finalize(tx, cursor);
|
|
188
|
+
|
|
189
|
+
if (enablePersistence) {
|
|
190
|
+
await finalizeState({ tx, cursor, indexerName });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
indexer.hooks.hook("message:invalidate", async ({ message }) => {
|
|
196
|
+
const { cursor } = message.invalidate;
|
|
197
|
+
|
|
198
|
+
if (!cursor) {
|
|
199
|
+
throw new DrizzleStorageError("Invalidate Cursor is undefined");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await withTransaction(db, async (tx) => {
|
|
203
|
+
await invalidate(tx, cursor, idColumn);
|
|
204
|
+
|
|
205
|
+
if (enablePersistence) {
|
|
206
|
+
await invalidateState({ tx, cursor, indexerName });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
indexer.hooks.hook("handler:middleware", async ({ use }) => {
|
|
212
|
+
use(async (context, next) => {
|
|
213
|
+
try {
|
|
214
|
+
const { endCursor, finality } = context as {
|
|
215
|
+
endCursor: Cursor;
|
|
216
|
+
finality: DataFinality;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (!endCursor) {
|
|
220
|
+
throw new DrizzleStorageError("End Cursor is undefined");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await withTransaction(db, async (tx) => {
|
|
224
|
+
context[DRIZZLE_PROPERTY] = { db: tx } as DrizzleStorage<
|
|
225
|
+
TQueryResult,
|
|
226
|
+
TFullSchema,
|
|
227
|
+
TSchema
|
|
228
|
+
>;
|
|
229
|
+
|
|
230
|
+
if (finality !== "finalized") {
|
|
231
|
+
await registerTriggers(tx, tableNames, endCursor, idColumn);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
await next();
|
|
235
|
+
delete context[DRIZZLE_PROPERTY];
|
|
236
|
+
|
|
237
|
+
if (enablePersistence) {
|
|
238
|
+
await persistState({
|
|
239
|
+
tx,
|
|
240
|
+
endCursor,
|
|
241
|
+
indexerName,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (finality !== "finalized") {
|
|
247
|
+
// remove trigger outside of the transaction or it won't be triggered.
|
|
248
|
+
await removeTriggers(db, tableNames);
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
await removeTriggers(db, tableNames);
|
|
252
|
+
|
|
253
|
+
throw new DrizzleStorageError("Failed to run handler:middleware", {
|
|
254
|
+
cause: error,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
5
260
|
}
|
package/src/persistence.ts
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
/*
|
|
2
1
|
import type { Cursor } from "@apibara/protocol";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
eq,
|
|
8
|
-
isNull,
|
|
2
|
+
import { and, eq, gt, isNull, lt } from "drizzle-orm";
|
|
3
|
+
import type {
|
|
4
|
+
ExtractTablesWithRelations,
|
|
5
|
+
TablesRelationalConfig,
|
|
9
6
|
} from "drizzle-orm";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import { defineIndexerPlugin } from "./config";
|
|
20
|
-
|
|
21
|
-
export const checkpoints = pgTable("checkpoints", {
|
|
7
|
+
import type { PgQueryResultHKT, PgTransaction } from "drizzle-orm/pg-core";
|
|
8
|
+
import { integer, pgTable, primaryKey, text } from "drizzle-orm/pg-core";
|
|
9
|
+
import { DrizzleStorageError, deserialize, serialize } from "./utils";
|
|
10
|
+
|
|
11
|
+
const CHECKPOINTS_TABLE_NAME = "__indexer_checkpoints";
|
|
12
|
+
const FILTERS_TABLE_NAME = "__indexer_filters";
|
|
13
|
+
const SCHEMA_VERSION_TABLE_NAME = "__indexer_schema_version";
|
|
14
|
+
|
|
15
|
+
export const checkpoints = pgTable(CHECKPOINTS_TABLE_NAME, {
|
|
22
16
|
id: text("id").notNull().primaryKey(),
|
|
23
17
|
orderKey: integer("order_key").notNull(),
|
|
24
18
|
uniqueKey: text("unique_key")
|
|
@@ -28,167 +22,281 @@ export const checkpoints = pgTable("checkpoints", {
|
|
|
28
22
|
});
|
|
29
23
|
|
|
30
24
|
export const filters = pgTable(
|
|
31
|
-
|
|
25
|
+
FILTERS_TABLE_NAME,
|
|
32
26
|
{
|
|
33
27
|
id: text("id").notNull(),
|
|
34
28
|
filter: text("filter").notNull(),
|
|
35
29
|
fromBlock: integer("from_block").notNull(),
|
|
36
|
-
toBlock: integer("to_block"),
|
|
30
|
+
toBlock: integer("to_block").$type<number | null>().default(null),
|
|
37
31
|
},
|
|
38
|
-
(table) =>
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
(table) => [
|
|
33
|
+
{
|
|
34
|
+
pk: primaryKey({ columns: [table.id, table.fromBlock] }),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
41
37
|
);
|
|
42
38
|
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
export const schemaVersion = pgTable(SCHEMA_VERSION_TABLE_NAME, {
|
|
40
|
+
k: integer("k").notNull().primaryKey(),
|
|
41
|
+
version: integer("version").notNull(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const CURRENT_SCHEMA_VERSION = 0;
|
|
45
|
+
|
|
46
|
+
// migrations for future schema updates
|
|
47
|
+
const MIGRATIONS: string[][] = [
|
|
48
|
+
// migrations[0]: v0 -> v1 (for future use)
|
|
49
|
+
[],
|
|
50
|
+
// Add more migration arrays for future versions
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
export async function initializePersistentState<
|
|
47
54
|
TQueryResult extends PgQueryResultHKT,
|
|
48
55
|
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
49
56
|
TSchema extends
|
|
50
57
|
TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
|
51
|
-
>({
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let store: DrizzlePersistence<TFilter, TQueryResult, TFullSchema, TSchema>;
|
|
58
|
+
>(tx: PgTransaction<TQueryResult, TFullSchema, TSchema>) {
|
|
59
|
+
// Create schema version table
|
|
60
|
+
await tx.execute(`
|
|
61
|
+
CREATE TABLE IF NOT EXISTS ${SCHEMA_VERSION_TABLE_NAME} (
|
|
62
|
+
k INTEGER PRIMARY KEY,
|
|
63
|
+
version INTEGER NOT NULL
|
|
64
|
+
);
|
|
65
|
+
`);
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
// Get current schema version
|
|
68
|
+
const versionRows = await tx
|
|
69
|
+
.select()
|
|
70
|
+
.from(schemaVersion)
|
|
71
|
+
.where(eq(schemaVersion.k, 0));
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
const { cursor, filter } = await store.get();
|
|
73
|
+
const storedVersion = versionRows[0]?.version ?? -1;
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
// Check for incompatible version
|
|
76
|
+
if (storedVersion > CURRENT_SCHEMA_VERSION) {
|
|
77
|
+
throw new DrizzleStorageError(
|
|
78
|
+
`Database Persistence schema version v${storedVersion} is newer than supported version v${CURRENT_SCHEMA_VERSION}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
// Begin schema updates
|
|
83
|
+
try {
|
|
84
|
+
if (storedVersion === -1) {
|
|
85
|
+
// First time initialization
|
|
86
|
+
await tx.execute(`
|
|
87
|
+
CREATE TABLE IF NOT EXISTS ${CHECKPOINTS_TABLE_NAME} (
|
|
88
|
+
id TEXT PRIMARY KEY,
|
|
89
|
+
order_key INTEGER NOT NULL,
|
|
90
|
+
unique_key TEXT NOT NULL DEFAULT ''
|
|
91
|
+
);
|
|
92
|
+
`);
|
|
77
93
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
await tx.execute(`
|
|
95
|
+
CREATE TABLE IF NOT EXISTS ${FILTERS_TABLE_NAME} (
|
|
96
|
+
id TEXT NOT NULL,
|
|
97
|
+
filter TEXT NOT NULL,
|
|
98
|
+
from_block INTEGER NOT NULL,
|
|
99
|
+
to_block INTEGER DEFAULT NULL,
|
|
100
|
+
PRIMARY KEY (id, from_block)
|
|
101
|
+
);
|
|
102
|
+
`);
|
|
83
103
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
104
|
+
// Set initial schema version
|
|
105
|
+
await tx.insert(schemaVersion).values({
|
|
106
|
+
k: 0,
|
|
107
|
+
version: CURRENT_SCHEMA_VERSION,
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
// Run any necessary migrations
|
|
111
|
+
let currentVersion = storedVersion;
|
|
112
|
+
while (currentVersion < CURRENT_SCHEMA_VERSION) {
|
|
113
|
+
const migrationStatements = MIGRATIONS[currentVersion];
|
|
114
|
+
for (const statement of migrationStatements) {
|
|
115
|
+
await tx.execute(statement);
|
|
116
|
+
}
|
|
117
|
+
currentVersion++;
|
|
87
118
|
}
|
|
88
|
-
|
|
89
|
-
|
|
119
|
+
|
|
120
|
+
// Update schema version
|
|
121
|
+
await tx
|
|
122
|
+
.update(schemaVersion)
|
|
123
|
+
.set({ version: CURRENT_SCHEMA_VERSION })
|
|
124
|
+
.where(eq(schemaVersion.k, 0));
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw new DrizzleStorageError(
|
|
128
|
+
"Failed to initialize or migrate database schema",
|
|
129
|
+
{ cause: error },
|
|
130
|
+
);
|
|
131
|
+
}
|
|
90
132
|
}
|
|
91
133
|
|
|
92
|
-
export
|
|
134
|
+
export async function persistState<
|
|
93
135
|
TFilter,
|
|
94
136
|
TQueryResult extends PgQueryResultHKT,
|
|
95
137
|
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
96
138
|
TSchema extends
|
|
97
139
|
TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
|
98
|
-
> {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const cursor = await this._getCheckpoint();
|
|
106
|
-
const filter = await this._getFilter();
|
|
107
|
-
|
|
108
|
-
return { cursor, filter };
|
|
109
|
-
}
|
|
140
|
+
>(props: {
|
|
141
|
+
tx: PgTransaction<TQueryResult, TFullSchema, TSchema>;
|
|
142
|
+
endCursor: Cursor;
|
|
143
|
+
filter?: TFilter;
|
|
144
|
+
indexerName: string;
|
|
145
|
+
}) {
|
|
146
|
+
const { tx, endCursor, filter, indexerName } = props;
|
|
110
147
|
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
await
|
|
148
|
+
try {
|
|
149
|
+
if (endCursor) {
|
|
150
|
+
await tx
|
|
151
|
+
.insert(checkpoints)
|
|
152
|
+
.values({
|
|
153
|
+
id: indexerName,
|
|
154
|
+
orderKey: Number(endCursor.orderKey),
|
|
155
|
+
uniqueKey: endCursor.uniqueKey,
|
|
156
|
+
})
|
|
157
|
+
.onConflictDoUpdate({
|
|
158
|
+
target: checkpoints.id,
|
|
159
|
+
set: {
|
|
160
|
+
orderKey: Number(endCursor.orderKey),
|
|
161
|
+
uniqueKey: endCursor.uniqueKey,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
114
164
|
|
|
115
165
|
if (filter) {
|
|
116
|
-
await
|
|
166
|
+
await tx
|
|
167
|
+
.update(filters)
|
|
168
|
+
.set({ toBlock: Number(endCursor.orderKey) })
|
|
169
|
+
.where(and(eq(filters.id, indexerName), isNull(filters.toBlock)));
|
|
170
|
+
|
|
171
|
+
await tx
|
|
172
|
+
.insert(filters)
|
|
173
|
+
.values({
|
|
174
|
+
id: indexerName,
|
|
175
|
+
filter: serialize(filter),
|
|
176
|
+
fromBlock: Number(endCursor.orderKey),
|
|
177
|
+
toBlock: null,
|
|
178
|
+
})
|
|
179
|
+
.onConflictDoUpdate({
|
|
180
|
+
target: [filters.id, filters.fromBlock],
|
|
181
|
+
set: {
|
|
182
|
+
filter: serialize(filter),
|
|
183
|
+
fromBlock: Number(endCursor.orderKey),
|
|
184
|
+
toBlock: null,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
117
187
|
}
|
|
118
188
|
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
throw new DrizzleStorageError("Failed to persist state", {
|
|
191
|
+
cause: error,
|
|
192
|
+
});
|
|
119
193
|
}
|
|
194
|
+
}
|
|
120
195
|
|
|
121
|
-
|
|
196
|
+
export async function getState<
|
|
197
|
+
TFilter,
|
|
198
|
+
TQueryResult extends PgQueryResultHKT,
|
|
199
|
+
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
200
|
+
TSchema extends
|
|
201
|
+
TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
|
202
|
+
>(props: {
|
|
203
|
+
tx: PgTransaction<TQueryResult, TFullSchema, TSchema>;
|
|
204
|
+
indexerName: string;
|
|
205
|
+
}): Promise<{ cursor?: Cursor; filter?: TFilter }> {
|
|
206
|
+
const { tx, indexerName } = props;
|
|
122
207
|
|
|
123
|
-
|
|
124
|
-
const
|
|
208
|
+
try {
|
|
209
|
+
const checkpointRows = await tx
|
|
125
210
|
.select()
|
|
126
211
|
.from(checkpoints)
|
|
127
|
-
.where(eq(checkpoints.id,
|
|
128
|
-
|
|
129
|
-
const row = rows[0];
|
|
130
|
-
if (!row) return undefined;
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
orderKey: BigInt(row.orderKey),
|
|
134
|
-
uniqueKey: row.uniqueKey,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private async _putCheckpoint(cursor: Cursor) {
|
|
139
|
-
await this._db
|
|
140
|
-
.insert(checkpoints)
|
|
141
|
-
.values({
|
|
142
|
-
id: this._indexerName,
|
|
143
|
-
orderKey: Number(cursor.orderKey),
|
|
144
|
-
uniqueKey: cursor.uniqueKey,
|
|
145
|
-
})
|
|
146
|
-
.onConflictDoUpdate({
|
|
147
|
-
target: checkpoints.id,
|
|
148
|
-
set: {
|
|
149
|
-
orderKey: Number(cursor.orderKey),
|
|
150
|
-
uniqueKey: cursor.uniqueKey,
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
}
|
|
212
|
+
.where(eq(checkpoints.id, indexerName));
|
|
154
213
|
|
|
155
|
-
|
|
214
|
+
const cursor = checkpointRows[0]
|
|
215
|
+
? {
|
|
216
|
+
orderKey: BigInt(checkpointRows[0].orderKey),
|
|
217
|
+
uniqueKey: checkpointRows[0].uniqueKey,
|
|
218
|
+
}
|
|
219
|
+
: undefined;
|
|
156
220
|
|
|
157
|
-
|
|
158
|
-
const rows = await this._db
|
|
221
|
+
const filterRows = await tx
|
|
159
222
|
.select()
|
|
160
223
|
.from(filters)
|
|
161
|
-
.where(and(eq(filters.id,
|
|
224
|
+
.where(and(eq(filters.id, indexerName), isNull(filters.toBlock)));
|
|
162
225
|
|
|
163
|
-
const
|
|
226
|
+
const filter = filterRows[0]
|
|
227
|
+
? deserialize<TFilter>(filterRows[0].filter)
|
|
228
|
+
: undefined;
|
|
164
229
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
230
|
+
return { cursor, filter };
|
|
231
|
+
} catch (error) {
|
|
232
|
+
throw new DrizzleStorageError("Failed to get persistent state", {
|
|
233
|
+
cause: error,
|
|
234
|
+
});
|
|
168
235
|
}
|
|
236
|
+
}
|
|
169
237
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
238
|
+
export async function invalidateState<
|
|
239
|
+
TQueryResult extends PgQueryResultHKT,
|
|
240
|
+
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
241
|
+
TSchema extends
|
|
242
|
+
TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
|
243
|
+
>(props: {
|
|
244
|
+
tx: PgTransaction<TQueryResult, TFullSchema, TSchema>;
|
|
245
|
+
cursor: Cursor;
|
|
246
|
+
indexerName: string;
|
|
247
|
+
}) {
|
|
248
|
+
const { tx, cursor, indexerName } = props;
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await tx
|
|
252
|
+
.delete(filters)
|
|
253
|
+
.where(
|
|
254
|
+
and(
|
|
255
|
+
eq(filters.id, indexerName),
|
|
256
|
+
gt(filters.fromBlock, Number(cursor.orderKey)),
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
await tx
|
|
173
261
|
.update(filters)
|
|
174
|
-
.set({ toBlock:
|
|
175
|
-
.where(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
262
|
+
.set({ toBlock: null })
|
|
263
|
+
.where(
|
|
264
|
+
and(
|
|
265
|
+
eq(filters.id, indexerName),
|
|
266
|
+
gt(filters.toBlock, Number(cursor.orderKey)),
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
throw new DrizzleStorageError("Failed to invalidate state", {
|
|
271
|
+
cause: error,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function finalizeState<
|
|
277
|
+
TQueryResult extends PgQueryResultHKT,
|
|
278
|
+
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
279
|
+
TSchema extends
|
|
280
|
+
TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
|
281
|
+
>(props: {
|
|
282
|
+
tx: PgTransaction<TQueryResult, TFullSchema, TSchema>;
|
|
283
|
+
cursor: Cursor;
|
|
284
|
+
indexerName: string;
|
|
285
|
+
}) {
|
|
286
|
+
const { tx, cursor, indexerName } = props;
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
await tx
|
|
290
|
+
.delete(filters)
|
|
291
|
+
.where(
|
|
292
|
+
and(
|
|
293
|
+
eq(filters.id, indexerName),
|
|
294
|
+
lt(filters.toBlock, Number(cursor.orderKey)),
|
|
295
|
+
),
|
|
296
|
+
);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
throw new DrizzleStorageError("Failed to finalize state", {
|
|
299
|
+
cause: error,
|
|
300
|
+
});
|
|
192
301
|
}
|
|
193
302
|
}
|
|
194
|
-
*/
|