@apibara/indexer 2.0.0-beta.22 → 2.0.0-beta.24

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.
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ const drizzleOrm = require('drizzle-orm');
4
+ const pgCore = require('drizzle-orm/pg-core');
5
+ const helper = require('../shared/indexer.219f58e0.cjs');
6
+ require('node:fs/promises');
7
+ require('node:path');
8
+ require('klona/full');
9
+ require('@apibara/protocol');
10
+ require('consola');
11
+ require('hookable');
12
+ require('node:assert');
13
+ require('../shared/indexer.077335f3.cjs');
14
+ require('../shared/indexer.943adee7.cjs');
15
+ require('node:fs');
16
+ require('@apibara/protocol/testing');
17
+ const plugins_index = require('./index.cjs');
18
+ require('node:async_hooks');
19
+ require('unctx');
20
+ require('@opentelemetry/api');
21
+
22
+ const checkpoints = pgCore.pgTable("checkpoints", {
23
+ id: pgCore.text("id").notNull().primaryKey(),
24
+ orderKey: pgCore.integer("order_key").notNull(),
25
+ uniqueKey: pgCore.text("unique_key").$type().notNull().default(void 0)
26
+ });
27
+ const filters = pgCore.pgTable(
28
+ "filters",
29
+ {
30
+ id: pgCore.text("id").notNull(),
31
+ filter: pgCore.text("filter").notNull(),
32
+ fromBlock: pgCore.integer("from_block").notNull(),
33
+ toBlock: pgCore.integer("to_block")
34
+ },
35
+ (table) => ({
36
+ pk: pgCore.primaryKey({ columns: [table.id, table.fromBlock] })
37
+ })
38
+ );
39
+ function drizzlePersistence({
40
+ database,
41
+ indexerName = "default"
42
+ }) {
43
+ return plugins_index.defineIndexerPlugin((indexer) => {
44
+ let store;
45
+ indexer.hooks.hook("run:before", async () => {
46
+ store = new DrizzlePersistence(database, indexerName);
47
+ });
48
+ indexer.hooks.hook("connect:before", async ({ request }) => {
49
+ const { cursor, filter } = await store.get();
50
+ if (cursor) {
51
+ request.startingCursor = cursor;
52
+ }
53
+ if (filter) {
54
+ request.filter[1] = filter;
55
+ }
56
+ });
57
+ indexer.hooks.hook("transaction:commit", async ({ endCursor }) => {
58
+ if (endCursor) {
59
+ await store.put({ cursor: endCursor });
60
+ }
61
+ });
62
+ indexer.hooks.hook("connect:factory", async ({ request, endCursor }) => {
63
+ if (request.filter[1]) {
64
+ await store.put({ cursor: endCursor, filter: request.filter[1] });
65
+ }
66
+ });
67
+ });
68
+ }
69
+ class DrizzlePersistence {
70
+ constructor(_db, _indexerName) {
71
+ this._db = _db;
72
+ this._indexerName = _indexerName;
73
+ }
74
+ async get() {
75
+ const cursor = await this._getCheckpoint();
76
+ const filter = await this._getFilter();
77
+ return { cursor, filter };
78
+ }
79
+ async put({ cursor, filter }) {
80
+ if (cursor) {
81
+ await this._putCheckpoint(cursor);
82
+ if (filter) {
83
+ await this._putFilter(filter, cursor);
84
+ }
85
+ }
86
+ }
87
+ // --- CHECKPOINTS TABLE METHODS ---
88
+ async _getCheckpoint() {
89
+ const rows = await this._db.select().from(checkpoints).where(drizzleOrm.eq(checkpoints.id, this._indexerName));
90
+ const row = rows[0];
91
+ if (!row)
92
+ return void 0;
93
+ return {
94
+ orderKey: BigInt(row.orderKey),
95
+ uniqueKey: row.uniqueKey
96
+ };
97
+ }
98
+ async _putCheckpoint(cursor) {
99
+ await this._db.insert(checkpoints).values({
100
+ id: this._indexerName,
101
+ orderKey: Number(cursor.orderKey),
102
+ uniqueKey: cursor.uniqueKey
103
+ }).onConflictDoUpdate({
104
+ target: checkpoints.id,
105
+ set: {
106
+ orderKey: Number(cursor.orderKey),
107
+ uniqueKey: cursor.uniqueKey
108
+ }
109
+ });
110
+ }
111
+ // --- FILTERS TABLE METHODS ---
112
+ async _getFilter() {
113
+ const rows = await this._db.select().from(filters).where(drizzleOrm.and(drizzleOrm.eq(filters.id, this._indexerName), drizzleOrm.isNull(filters.toBlock)));
114
+ const row = rows[0];
115
+ if (!row)
116
+ return void 0;
117
+ return helper.deserialize(row.filter);
118
+ }
119
+ async _putFilter(filter, endCursor) {
120
+ await this._db.update(filters).set({ toBlock: Number(endCursor.orderKey) }).where(drizzleOrm.and(drizzleOrm.eq(filters.id, this._indexerName), drizzleOrm.isNull(filters.toBlock)));
121
+ await this._db.insert(filters).values({
122
+ id: this._indexerName,
123
+ filter: helper.serialize(filter),
124
+ fromBlock: Number(endCursor.orderKey)
125
+ }).onConflictDoUpdate({
126
+ target: [filters.id, filters.fromBlock],
127
+ set: {
128
+ filter: helper.serialize(filter),
129
+ fromBlock: Number(endCursor.orderKey)
130
+ }
131
+ });
132
+ }
133
+ }
134
+
135
+ exports.DrizzlePersistence = DrizzlePersistence;
136
+ exports.checkpoints = checkpoints;
137
+ exports.drizzlePersistence = drizzlePersistence;
138
+ exports.filters = filters;
@@ -0,0 +1,157 @@
1
+ import { a as IndexerPlugin } from '../shared/indexer.3bc6abe3.cjs';
2
+ import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
3
+ import { PgQueryResultHKT, PgDatabase } from 'drizzle-orm/pg-core';
4
+ import { Cursor } from '@apibara/protocol';
5
+ import { TablesRelationalConfig, ExtractTablesWithRelations } from 'drizzle-orm';
6
+ import 'hookable';
7
+ import '../shared/indexer.47b4546b.cjs';
8
+
9
+ declare const checkpoints: drizzle_orm_pg_core.PgTableWithColumns<{
10
+ name: "checkpoints";
11
+ schema: undefined;
12
+ columns: {
13
+ id: drizzle_orm_pg_core.PgColumn<{
14
+ name: "id";
15
+ tableName: "checkpoints";
16
+ dataType: "string";
17
+ columnType: "PgText";
18
+ data: string;
19
+ driverParam: string;
20
+ notNull: true;
21
+ hasDefault: false;
22
+ isPrimaryKey: true;
23
+ isAutoincrement: false;
24
+ hasRuntimeDefault: false;
25
+ enumValues: [string, ...string[]];
26
+ baseColumn: never;
27
+ generated: undefined;
28
+ }, {}, {}>;
29
+ orderKey: drizzle_orm_pg_core.PgColumn<{
30
+ name: "order_key";
31
+ tableName: "checkpoints";
32
+ dataType: "number";
33
+ columnType: "PgInteger";
34
+ data: number;
35
+ driverParam: string | number;
36
+ notNull: true;
37
+ hasDefault: false;
38
+ isPrimaryKey: false;
39
+ isAutoincrement: false;
40
+ hasRuntimeDefault: false;
41
+ enumValues: undefined;
42
+ baseColumn: never;
43
+ generated: undefined;
44
+ }, {}, {}>;
45
+ uniqueKey: drizzle_orm_pg_core.PgColumn<{
46
+ name: "unique_key";
47
+ tableName: "checkpoints";
48
+ dataType: "string";
49
+ columnType: "PgText";
50
+ data: `0x${string}` | undefined;
51
+ driverParam: string;
52
+ notNull: true;
53
+ hasDefault: true;
54
+ isPrimaryKey: false;
55
+ isAutoincrement: false;
56
+ hasRuntimeDefault: false;
57
+ enumValues: [string, ...string[]];
58
+ baseColumn: never;
59
+ generated: undefined;
60
+ }, {}, {}>;
61
+ };
62
+ dialect: "pg";
63
+ }>;
64
+ declare const filters: drizzle_orm_pg_core.PgTableWithColumns<{
65
+ name: "filters";
66
+ schema: undefined;
67
+ columns: {
68
+ id: drizzle_orm_pg_core.PgColumn<{
69
+ name: "id";
70
+ tableName: "filters";
71
+ dataType: "string";
72
+ columnType: "PgText";
73
+ data: string;
74
+ driverParam: string;
75
+ notNull: true;
76
+ hasDefault: false;
77
+ isPrimaryKey: false;
78
+ isAutoincrement: false;
79
+ hasRuntimeDefault: false;
80
+ enumValues: [string, ...string[]];
81
+ baseColumn: never;
82
+ generated: undefined;
83
+ }, {}, {}>;
84
+ filter: drizzle_orm_pg_core.PgColumn<{
85
+ name: "filter";
86
+ tableName: "filters";
87
+ dataType: "string";
88
+ columnType: "PgText";
89
+ data: string;
90
+ driverParam: string;
91
+ notNull: true;
92
+ hasDefault: false;
93
+ isPrimaryKey: false;
94
+ isAutoincrement: false;
95
+ hasRuntimeDefault: false;
96
+ enumValues: [string, ...string[]];
97
+ baseColumn: never;
98
+ generated: undefined;
99
+ }, {}, {}>;
100
+ fromBlock: drizzle_orm_pg_core.PgColumn<{
101
+ name: "from_block";
102
+ tableName: "filters";
103
+ dataType: "number";
104
+ columnType: "PgInteger";
105
+ data: number;
106
+ driverParam: string | number;
107
+ notNull: true;
108
+ hasDefault: false;
109
+ isPrimaryKey: false;
110
+ isAutoincrement: false;
111
+ hasRuntimeDefault: false;
112
+ enumValues: undefined;
113
+ baseColumn: never;
114
+ generated: undefined;
115
+ }, {}, {}>;
116
+ toBlock: drizzle_orm_pg_core.PgColumn<{
117
+ name: "to_block";
118
+ tableName: "filters";
119
+ dataType: "number";
120
+ columnType: "PgInteger";
121
+ data: number;
122
+ driverParam: string | number;
123
+ notNull: false;
124
+ hasDefault: false;
125
+ isPrimaryKey: false;
126
+ isAutoincrement: false;
127
+ hasRuntimeDefault: false;
128
+ enumValues: undefined;
129
+ baseColumn: never;
130
+ generated: undefined;
131
+ }, {}, {}>;
132
+ };
133
+ dialect: "pg";
134
+ }>;
135
+ declare function drizzlePersistence<TFilter, TBlock, TTxnParams, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>>({ database, indexerName, }: {
136
+ database: PgDatabase<TQueryResult, TFullSchema, TSchema>;
137
+ indexerName?: string;
138
+ }): IndexerPlugin<TFilter, TBlock, TTxnParams>;
139
+ declare class DrizzlePersistence<TFilter, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>> {
140
+ private _db;
141
+ private _indexerName;
142
+ constructor(_db: PgDatabase<TQueryResult, TFullSchema, TSchema>, _indexerName: string);
143
+ get(): Promise<{
144
+ cursor?: Cursor;
145
+ filter?: TFilter;
146
+ }>;
147
+ put({ cursor, filter }: {
148
+ cursor?: Cursor;
149
+ filter?: TFilter;
150
+ }): Promise<void>;
151
+ private _getCheckpoint;
152
+ private _putCheckpoint;
153
+ private _getFilter;
154
+ private _putFilter;
155
+ }
156
+
157
+ export { DrizzlePersistence, checkpoints, drizzlePersistence, filters };
@@ -0,0 +1,157 @@
1
+ import { a as IndexerPlugin } from '../shared/indexer.b4be75f1.mjs';
2
+ import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
3
+ import { PgQueryResultHKT, PgDatabase } from 'drizzle-orm/pg-core';
4
+ import { Cursor } from '@apibara/protocol';
5
+ import { TablesRelationalConfig, ExtractTablesWithRelations } from 'drizzle-orm';
6
+ import 'hookable';
7
+ import '../shared/indexer.47b4546b.mjs';
8
+
9
+ declare const checkpoints: drizzle_orm_pg_core.PgTableWithColumns<{
10
+ name: "checkpoints";
11
+ schema: undefined;
12
+ columns: {
13
+ id: drizzle_orm_pg_core.PgColumn<{
14
+ name: "id";
15
+ tableName: "checkpoints";
16
+ dataType: "string";
17
+ columnType: "PgText";
18
+ data: string;
19
+ driverParam: string;
20
+ notNull: true;
21
+ hasDefault: false;
22
+ isPrimaryKey: true;
23
+ isAutoincrement: false;
24
+ hasRuntimeDefault: false;
25
+ enumValues: [string, ...string[]];
26
+ baseColumn: never;
27
+ generated: undefined;
28
+ }, {}, {}>;
29
+ orderKey: drizzle_orm_pg_core.PgColumn<{
30
+ name: "order_key";
31
+ tableName: "checkpoints";
32
+ dataType: "number";
33
+ columnType: "PgInteger";
34
+ data: number;
35
+ driverParam: string | number;
36
+ notNull: true;
37
+ hasDefault: false;
38
+ isPrimaryKey: false;
39
+ isAutoincrement: false;
40
+ hasRuntimeDefault: false;
41
+ enumValues: undefined;
42
+ baseColumn: never;
43
+ generated: undefined;
44
+ }, {}, {}>;
45
+ uniqueKey: drizzle_orm_pg_core.PgColumn<{
46
+ name: "unique_key";
47
+ tableName: "checkpoints";
48
+ dataType: "string";
49
+ columnType: "PgText";
50
+ data: `0x${string}` | undefined;
51
+ driverParam: string;
52
+ notNull: true;
53
+ hasDefault: true;
54
+ isPrimaryKey: false;
55
+ isAutoincrement: false;
56
+ hasRuntimeDefault: false;
57
+ enumValues: [string, ...string[]];
58
+ baseColumn: never;
59
+ generated: undefined;
60
+ }, {}, {}>;
61
+ };
62
+ dialect: "pg";
63
+ }>;
64
+ declare const filters: drizzle_orm_pg_core.PgTableWithColumns<{
65
+ name: "filters";
66
+ schema: undefined;
67
+ columns: {
68
+ id: drizzle_orm_pg_core.PgColumn<{
69
+ name: "id";
70
+ tableName: "filters";
71
+ dataType: "string";
72
+ columnType: "PgText";
73
+ data: string;
74
+ driverParam: string;
75
+ notNull: true;
76
+ hasDefault: false;
77
+ isPrimaryKey: false;
78
+ isAutoincrement: false;
79
+ hasRuntimeDefault: false;
80
+ enumValues: [string, ...string[]];
81
+ baseColumn: never;
82
+ generated: undefined;
83
+ }, {}, {}>;
84
+ filter: drizzle_orm_pg_core.PgColumn<{
85
+ name: "filter";
86
+ tableName: "filters";
87
+ dataType: "string";
88
+ columnType: "PgText";
89
+ data: string;
90
+ driverParam: string;
91
+ notNull: true;
92
+ hasDefault: false;
93
+ isPrimaryKey: false;
94
+ isAutoincrement: false;
95
+ hasRuntimeDefault: false;
96
+ enumValues: [string, ...string[]];
97
+ baseColumn: never;
98
+ generated: undefined;
99
+ }, {}, {}>;
100
+ fromBlock: drizzle_orm_pg_core.PgColumn<{
101
+ name: "from_block";
102
+ tableName: "filters";
103
+ dataType: "number";
104
+ columnType: "PgInteger";
105
+ data: number;
106
+ driverParam: string | number;
107
+ notNull: true;
108
+ hasDefault: false;
109
+ isPrimaryKey: false;
110
+ isAutoincrement: false;
111
+ hasRuntimeDefault: false;
112
+ enumValues: undefined;
113
+ baseColumn: never;
114
+ generated: undefined;
115
+ }, {}, {}>;
116
+ toBlock: drizzle_orm_pg_core.PgColumn<{
117
+ name: "to_block";
118
+ tableName: "filters";
119
+ dataType: "number";
120
+ columnType: "PgInteger";
121
+ data: number;
122
+ driverParam: string | number;
123
+ notNull: false;
124
+ hasDefault: false;
125
+ isPrimaryKey: false;
126
+ isAutoincrement: false;
127
+ hasRuntimeDefault: false;
128
+ enumValues: undefined;
129
+ baseColumn: never;
130
+ generated: undefined;
131
+ }, {}, {}>;
132
+ };
133
+ dialect: "pg";
134
+ }>;
135
+ declare function drizzlePersistence<TFilter, TBlock, TTxnParams, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>>({ database, indexerName, }: {
136
+ database: PgDatabase<TQueryResult, TFullSchema, TSchema>;
137
+ indexerName?: string;
138
+ }): IndexerPlugin<TFilter, TBlock, TTxnParams>;
139
+ declare class DrizzlePersistence<TFilter, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>> {
140
+ private _db;
141
+ private _indexerName;
142
+ constructor(_db: PgDatabase<TQueryResult, TFullSchema, TSchema>, _indexerName: string);
143
+ get(): Promise<{
144
+ cursor?: Cursor;
145
+ filter?: TFilter;
146
+ }>;
147
+ put({ cursor, filter }: {
148
+ cursor?: Cursor;
149
+ filter?: TFilter;
150
+ }): Promise<void>;
151
+ private _getCheckpoint;
152
+ private _putCheckpoint;
153
+ private _getFilter;
154
+ private _putFilter;
155
+ }
156
+
157
+ export { DrizzlePersistence, checkpoints, drizzlePersistence, filters };
@@ -0,0 +1,157 @@
1
+ import { a as IndexerPlugin } from '../shared/indexer.a05207df.js';
2
+ import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
3
+ import { PgQueryResultHKT, PgDatabase } from 'drizzle-orm/pg-core';
4
+ import { Cursor } from '@apibara/protocol';
5
+ import { TablesRelationalConfig, ExtractTablesWithRelations } from 'drizzle-orm';
6
+ import 'hookable';
7
+ import '../shared/indexer.47b4546b.js';
8
+
9
+ declare const checkpoints: drizzle_orm_pg_core.PgTableWithColumns<{
10
+ name: "checkpoints";
11
+ schema: undefined;
12
+ columns: {
13
+ id: drizzle_orm_pg_core.PgColumn<{
14
+ name: "id";
15
+ tableName: "checkpoints";
16
+ dataType: "string";
17
+ columnType: "PgText";
18
+ data: string;
19
+ driverParam: string;
20
+ notNull: true;
21
+ hasDefault: false;
22
+ isPrimaryKey: true;
23
+ isAutoincrement: false;
24
+ hasRuntimeDefault: false;
25
+ enumValues: [string, ...string[]];
26
+ baseColumn: never;
27
+ generated: undefined;
28
+ }, {}, {}>;
29
+ orderKey: drizzle_orm_pg_core.PgColumn<{
30
+ name: "order_key";
31
+ tableName: "checkpoints";
32
+ dataType: "number";
33
+ columnType: "PgInteger";
34
+ data: number;
35
+ driverParam: string | number;
36
+ notNull: true;
37
+ hasDefault: false;
38
+ isPrimaryKey: false;
39
+ isAutoincrement: false;
40
+ hasRuntimeDefault: false;
41
+ enumValues: undefined;
42
+ baseColumn: never;
43
+ generated: undefined;
44
+ }, {}, {}>;
45
+ uniqueKey: drizzle_orm_pg_core.PgColumn<{
46
+ name: "unique_key";
47
+ tableName: "checkpoints";
48
+ dataType: "string";
49
+ columnType: "PgText";
50
+ data: `0x${string}` | undefined;
51
+ driverParam: string;
52
+ notNull: true;
53
+ hasDefault: true;
54
+ isPrimaryKey: false;
55
+ isAutoincrement: false;
56
+ hasRuntimeDefault: false;
57
+ enumValues: [string, ...string[]];
58
+ baseColumn: never;
59
+ generated: undefined;
60
+ }, {}, {}>;
61
+ };
62
+ dialect: "pg";
63
+ }>;
64
+ declare const filters: drizzle_orm_pg_core.PgTableWithColumns<{
65
+ name: "filters";
66
+ schema: undefined;
67
+ columns: {
68
+ id: drizzle_orm_pg_core.PgColumn<{
69
+ name: "id";
70
+ tableName: "filters";
71
+ dataType: "string";
72
+ columnType: "PgText";
73
+ data: string;
74
+ driverParam: string;
75
+ notNull: true;
76
+ hasDefault: false;
77
+ isPrimaryKey: false;
78
+ isAutoincrement: false;
79
+ hasRuntimeDefault: false;
80
+ enumValues: [string, ...string[]];
81
+ baseColumn: never;
82
+ generated: undefined;
83
+ }, {}, {}>;
84
+ filter: drizzle_orm_pg_core.PgColumn<{
85
+ name: "filter";
86
+ tableName: "filters";
87
+ dataType: "string";
88
+ columnType: "PgText";
89
+ data: string;
90
+ driverParam: string;
91
+ notNull: true;
92
+ hasDefault: false;
93
+ isPrimaryKey: false;
94
+ isAutoincrement: false;
95
+ hasRuntimeDefault: false;
96
+ enumValues: [string, ...string[]];
97
+ baseColumn: never;
98
+ generated: undefined;
99
+ }, {}, {}>;
100
+ fromBlock: drizzle_orm_pg_core.PgColumn<{
101
+ name: "from_block";
102
+ tableName: "filters";
103
+ dataType: "number";
104
+ columnType: "PgInteger";
105
+ data: number;
106
+ driverParam: string | number;
107
+ notNull: true;
108
+ hasDefault: false;
109
+ isPrimaryKey: false;
110
+ isAutoincrement: false;
111
+ hasRuntimeDefault: false;
112
+ enumValues: undefined;
113
+ baseColumn: never;
114
+ generated: undefined;
115
+ }, {}, {}>;
116
+ toBlock: drizzle_orm_pg_core.PgColumn<{
117
+ name: "to_block";
118
+ tableName: "filters";
119
+ dataType: "number";
120
+ columnType: "PgInteger";
121
+ data: number;
122
+ driverParam: string | number;
123
+ notNull: false;
124
+ hasDefault: false;
125
+ isPrimaryKey: false;
126
+ isAutoincrement: false;
127
+ hasRuntimeDefault: false;
128
+ enumValues: undefined;
129
+ baseColumn: never;
130
+ generated: undefined;
131
+ }, {}, {}>;
132
+ };
133
+ dialect: "pg";
134
+ }>;
135
+ declare function drizzlePersistence<TFilter, TBlock, TTxnParams, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>>({ database, indexerName, }: {
136
+ database: PgDatabase<TQueryResult, TFullSchema, TSchema>;
137
+ indexerName?: string;
138
+ }): IndexerPlugin<TFilter, TBlock, TTxnParams>;
139
+ declare class DrizzlePersistence<TFilter, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>> {
140
+ private _db;
141
+ private _indexerName;
142
+ constructor(_db: PgDatabase<TQueryResult, TFullSchema, TSchema>, _indexerName: string);
143
+ get(): Promise<{
144
+ cursor?: Cursor;
145
+ filter?: TFilter;
146
+ }>;
147
+ put({ cursor, filter }: {
148
+ cursor?: Cursor;
149
+ filter?: TFilter;
150
+ }): Promise<void>;
151
+ private _getCheckpoint;
152
+ private _putCheckpoint;
153
+ private _getFilter;
154
+ private _putFilter;
155
+ }
156
+
157
+ export { DrizzlePersistence, checkpoints, drizzlePersistence, filters };
@@ -0,0 +1,133 @@
1
+ import { eq, and, isNull } from 'drizzle-orm';
2
+ import { pgTable, text, integer, primaryKey } from 'drizzle-orm/pg-core';
3
+ import { d as deserialize, s as serialize } from '../shared/indexer.0c0510df.mjs';
4
+ import 'node:fs/promises';
5
+ import 'node:path';
6
+ import 'klona/full';
7
+ import '@apibara/protocol';
8
+ import 'consola';
9
+ import 'hookable';
10
+ import 'node:assert';
11
+ import '../shared/indexer.a55ad619.mjs';
12
+ import '../shared/indexer.e2bc22c0.mjs';
13
+ import 'node:fs';
14
+ import '@apibara/protocol/testing';
15
+ import { defineIndexerPlugin } from './index.mjs';
16
+ import 'node:async_hooks';
17
+ import 'unctx';
18
+ import '@opentelemetry/api';
19
+
20
+ const checkpoints = pgTable("checkpoints", {
21
+ id: text("id").notNull().primaryKey(),
22
+ orderKey: integer("order_key").notNull(),
23
+ uniqueKey: text("unique_key").$type().notNull().default(void 0)
24
+ });
25
+ const filters = pgTable(
26
+ "filters",
27
+ {
28
+ id: text("id").notNull(),
29
+ filter: text("filter").notNull(),
30
+ fromBlock: integer("from_block").notNull(),
31
+ toBlock: integer("to_block")
32
+ },
33
+ (table) => ({
34
+ pk: primaryKey({ columns: [table.id, table.fromBlock] })
35
+ })
36
+ );
37
+ function drizzlePersistence({
38
+ database,
39
+ indexerName = "default"
40
+ }) {
41
+ return defineIndexerPlugin((indexer) => {
42
+ let store;
43
+ indexer.hooks.hook("run:before", async () => {
44
+ store = new DrizzlePersistence(database, indexerName);
45
+ });
46
+ indexer.hooks.hook("connect:before", async ({ request }) => {
47
+ const { cursor, filter } = await store.get();
48
+ if (cursor) {
49
+ request.startingCursor = cursor;
50
+ }
51
+ if (filter) {
52
+ request.filter[1] = filter;
53
+ }
54
+ });
55
+ indexer.hooks.hook("transaction:commit", async ({ endCursor }) => {
56
+ if (endCursor) {
57
+ await store.put({ cursor: endCursor });
58
+ }
59
+ });
60
+ indexer.hooks.hook("connect:factory", async ({ request, endCursor }) => {
61
+ if (request.filter[1]) {
62
+ await store.put({ cursor: endCursor, filter: request.filter[1] });
63
+ }
64
+ });
65
+ });
66
+ }
67
+ class DrizzlePersistence {
68
+ constructor(_db, _indexerName) {
69
+ this._db = _db;
70
+ this._indexerName = _indexerName;
71
+ }
72
+ async get() {
73
+ const cursor = await this._getCheckpoint();
74
+ const filter = await this._getFilter();
75
+ return { cursor, filter };
76
+ }
77
+ async put({ cursor, filter }) {
78
+ if (cursor) {
79
+ await this._putCheckpoint(cursor);
80
+ if (filter) {
81
+ await this._putFilter(filter, cursor);
82
+ }
83
+ }
84
+ }
85
+ // --- CHECKPOINTS TABLE METHODS ---
86
+ async _getCheckpoint() {
87
+ const rows = await this._db.select().from(checkpoints).where(eq(checkpoints.id, this._indexerName));
88
+ const row = rows[0];
89
+ if (!row)
90
+ return void 0;
91
+ return {
92
+ orderKey: BigInt(row.orderKey),
93
+ uniqueKey: row.uniqueKey
94
+ };
95
+ }
96
+ async _putCheckpoint(cursor) {
97
+ await this._db.insert(checkpoints).values({
98
+ id: this._indexerName,
99
+ orderKey: Number(cursor.orderKey),
100
+ uniqueKey: cursor.uniqueKey
101
+ }).onConflictDoUpdate({
102
+ target: checkpoints.id,
103
+ set: {
104
+ orderKey: Number(cursor.orderKey),
105
+ uniqueKey: cursor.uniqueKey
106
+ }
107
+ });
108
+ }
109
+ // --- FILTERS TABLE METHODS ---
110
+ async _getFilter() {
111
+ const rows = await this._db.select().from(filters).where(and(eq(filters.id, this._indexerName), isNull(filters.toBlock)));
112
+ const row = rows[0];
113
+ if (!row)
114
+ return void 0;
115
+ return deserialize(row.filter);
116
+ }
117
+ async _putFilter(filter, endCursor) {
118
+ await this._db.update(filters).set({ toBlock: Number(endCursor.orderKey) }).where(and(eq(filters.id, this._indexerName), isNull(filters.toBlock)));
119
+ await this._db.insert(filters).values({
120
+ id: this._indexerName,
121
+ filter: serialize(filter),
122
+ fromBlock: Number(endCursor.orderKey)
123
+ }).onConflictDoUpdate({
124
+ target: [filters.id, filters.fromBlock],
125
+ set: {
126
+ filter: serialize(filter),
127
+ fromBlock: Number(endCursor.orderKey)
128
+ }
129
+ });
130
+ }
131
+ }
132
+
133
+ export { DrizzlePersistence, checkpoints, drizzlePersistence, filters };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apibara/indexer",
3
- "version": "2.0.0-beta.22",
3
+ "version": "2.0.0-beta.24",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -69,6 +69,12 @@
69
69
  "import": "./dist/plugins/persistence.mjs",
70
70
  "require": "./dist/plugins/persistence.cjs",
71
71
  "default": "./dist/plugins/persistence.mjs"
72
+ },
73
+ "./plugins/drizzle-persistence": {
74
+ "types": "./dist/plugins/drizzle-persistence.d.ts",
75
+ "import": "./dist/plugins/drizzle-persistence.mjs",
76
+ "require": "./dist/plugins/drizzle-persistence.cjs",
77
+ "default": "./dist/plugins/drizzle-persistence.mjs"
72
78
  }
73
79
  },
74
80
  "scripts": {
@@ -80,6 +86,7 @@
80
86
  "test:ci": "vitest run"
81
87
  },
82
88
  "devDependencies": {
89
+ "@electric-sql/pglite": "^0.2.14",
83
90
  "@types/better-sqlite3": "^7.6.11",
84
91
  "@types/node": "^20.14.0",
85
92
  "@types/pg": "^8.11.10",
@@ -92,7 +99,7 @@
92
99
  "vitest": "^1.6.0"
93
100
  },
94
101
  "dependencies": {
95
- "@apibara/protocol": "2.0.0-beta.22",
102
+ "@apibara/protocol": "2.0.0-beta.24",
96
103
  "@opentelemetry/api": "^1.9.0",
97
104
  "ci-info": "^4.1.0",
98
105
  "consola": "^3.2.3",
@@ -102,6 +109,7 @@
102
109
  "unctx": "^2.3.1"
103
110
  },
104
111
  "peerDependencies": {
112
+ "@electric-sql/pglite": "^0.2.14",
105
113
  "better-sqlite3": "^11.5.0",
106
114
  "csv-stringify": "^6.5.0",
107
115
  "drizzle-orm": "^0.35.2",
@@ -0,0 +1,192 @@
1
+ import type { Cursor } from "@apibara/protocol";
2
+ import {
3
+ type ExtractTablesWithRelations,
4
+ type TablesRelationalConfig,
5
+ and,
6
+ eq,
7
+ isNull,
8
+ } from "drizzle-orm";
9
+ import {
10
+ type PgDatabase,
11
+ type PgQueryResultHKT,
12
+ integer,
13
+ pgTable,
14
+ primaryKey,
15
+ text,
16
+ } from "drizzle-orm/pg-core";
17
+ import { deserialize, serialize } from "../vcr";
18
+ import { defineIndexerPlugin } from "./config";
19
+
20
+ export const checkpoints = pgTable("checkpoints", {
21
+ id: text("id").notNull().primaryKey(),
22
+ orderKey: integer("order_key").notNull(),
23
+ uniqueKey: text("unique_key")
24
+ .$type<`0x${string}` | undefined>()
25
+ .notNull()
26
+ .default(undefined),
27
+ });
28
+
29
+ export const filters = pgTable(
30
+ "filters",
31
+ {
32
+ id: text("id").notNull(),
33
+ filter: text("filter").notNull(),
34
+ fromBlock: integer("from_block").notNull(),
35
+ toBlock: integer("to_block"),
36
+ },
37
+ (table) => ({
38
+ pk: primaryKey({ columns: [table.id, table.fromBlock] }),
39
+ }),
40
+ );
41
+
42
+ export function drizzlePersistence<
43
+ TFilter,
44
+ TBlock,
45
+ TTxnParams,
46
+ TQueryResult extends PgQueryResultHKT,
47
+ TFullSchema extends Record<string, unknown> = Record<string, never>,
48
+ TSchema extends
49
+ TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
50
+ >({
51
+ database,
52
+ indexerName = "default",
53
+ }: {
54
+ database: PgDatabase<TQueryResult, TFullSchema, TSchema>;
55
+ indexerName?: string;
56
+ }) {
57
+ return defineIndexerPlugin<TFilter, TBlock, TTxnParams>((indexer) => {
58
+ let store: DrizzlePersistence<TFilter, TQueryResult, TFullSchema, TSchema>;
59
+
60
+ indexer.hooks.hook("run:before", async () => {
61
+ store = new DrizzlePersistence(database, indexerName);
62
+ // Tables are created by user via migrations in Drizzle
63
+ });
64
+
65
+ indexer.hooks.hook("connect:before", async ({ request }) => {
66
+ const { cursor, filter } = await store.get();
67
+
68
+ if (cursor) {
69
+ request.startingCursor = cursor;
70
+ }
71
+
72
+ if (filter) {
73
+ request.filter[1] = filter;
74
+ }
75
+ });
76
+
77
+ indexer.hooks.hook("transaction:commit", async ({ endCursor }) => {
78
+ if (endCursor) {
79
+ await store.put({ cursor: endCursor });
80
+ }
81
+ });
82
+
83
+ indexer.hooks.hook("connect:factory", async ({ request, endCursor }) => {
84
+ if (request.filter[1]) {
85
+ await store.put({ cursor: endCursor, filter: request.filter[1] });
86
+ }
87
+ });
88
+ });
89
+ }
90
+
91
+ export class DrizzlePersistence<
92
+ TFilter,
93
+ TQueryResult extends PgQueryResultHKT,
94
+ TFullSchema extends Record<string, unknown> = Record<string, never>,
95
+ TSchema extends
96
+ TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
97
+ > {
98
+ constructor(
99
+ private _db: PgDatabase<TQueryResult, TFullSchema, TSchema>,
100
+ private _indexerName: string,
101
+ ) {}
102
+
103
+ public async get(): Promise<{ cursor?: Cursor; filter?: TFilter }> {
104
+ const cursor = await this._getCheckpoint();
105
+ const filter = await this._getFilter();
106
+
107
+ return { cursor, filter };
108
+ }
109
+
110
+ public async put({ cursor, filter }: { cursor?: Cursor; filter?: TFilter }) {
111
+ if (cursor) {
112
+ await this._putCheckpoint(cursor);
113
+
114
+ if (filter) {
115
+ await this._putFilter(filter, cursor);
116
+ }
117
+ }
118
+ }
119
+
120
+ // --- CHECKPOINTS TABLE METHODS ---
121
+
122
+ private async _getCheckpoint(): Promise<Cursor | undefined> {
123
+ const rows = await this._db
124
+ .select()
125
+ .from(checkpoints)
126
+ .where(eq(checkpoints.id, this._indexerName));
127
+
128
+ const row = rows[0];
129
+ if (!row) return undefined;
130
+
131
+ return {
132
+ orderKey: BigInt(row.orderKey),
133
+ uniqueKey: row.uniqueKey,
134
+ };
135
+ }
136
+
137
+ private async _putCheckpoint(cursor: Cursor) {
138
+ await this._db
139
+ .insert(checkpoints)
140
+ .values({
141
+ id: this._indexerName,
142
+ orderKey: Number(cursor.orderKey),
143
+ uniqueKey: cursor.uniqueKey,
144
+ })
145
+ .onConflictDoUpdate({
146
+ target: checkpoints.id,
147
+ set: {
148
+ orderKey: Number(cursor.orderKey),
149
+ uniqueKey: cursor.uniqueKey,
150
+ },
151
+ });
152
+ }
153
+
154
+ // --- FILTERS TABLE METHODS ---
155
+
156
+ private async _getFilter(): Promise<TFilter | undefined> {
157
+ const rows = await this._db
158
+ .select()
159
+ .from(filters)
160
+ .where(and(eq(filters.id, this._indexerName), isNull(filters.toBlock)));
161
+
162
+ const row = rows[0];
163
+
164
+ if (!row) return undefined;
165
+
166
+ return deserialize(row.filter) as TFilter;
167
+ }
168
+
169
+ private async _putFilter(filter: TFilter, endCursor: Cursor) {
170
+ // Update existing filter's to_block
171
+ await this._db
172
+ .update(filters)
173
+ .set({ toBlock: Number(endCursor.orderKey) })
174
+ .where(and(eq(filters.id, this._indexerName), isNull(filters.toBlock)));
175
+
176
+ // Insert new filter
177
+ await this._db
178
+ .insert(filters)
179
+ .values({
180
+ id: this._indexerName,
181
+ filter: serialize(filter as Record<string, unknown>),
182
+ fromBlock: Number(endCursor.orderKey),
183
+ })
184
+ .onConflictDoUpdate({
185
+ target: [filters.id, filters.fromBlock],
186
+ set: {
187
+ filter: serialize(filter as Record<string, unknown>),
188
+ fromBlock: Number(endCursor.orderKey),
189
+ },
190
+ });
191
+ }
192
+ }