ponder 0.14.13 → 0.15.0
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/CHANGELOG.md +16 -0
- package/dist/esm/bin/commands/createViews.js +28 -11
- package/dist/esm/bin/commands/createViews.js.map +1 -1
- package/dist/esm/bin/commands/dev.js +42 -22
- package/dist/esm/bin/commands/dev.js.map +1 -1
- package/dist/esm/bin/commands/prune.js +3 -0
- package/dist/esm/bin/commands/prune.js.map +1 -1
- package/dist/esm/bin/commands/serve.js +4 -1
- package/dist/esm/bin/commands/serve.js.map +1 -1
- package/dist/esm/bin/commands/start.js +18 -6
- package/dist/esm/bin/commands/start.js.map +1 -1
- package/dist/esm/bin/isolatedController.js +200 -0
- package/dist/esm/bin/isolatedController.js.map +1 -0
- package/dist/esm/bin/isolatedWorker.js +146 -0
- package/dist/esm/bin/isolatedWorker.js.map +1 -0
- package/dist/esm/build/config.js +322 -402
- package/dist/esm/build/config.js.map +1 -1
- package/dist/esm/build/index.js +8 -11
- package/dist/esm/build/index.js.map +1 -1
- package/dist/esm/build/pre.js +1 -4
- package/dist/esm/build/pre.js.map +1 -1
- package/dist/esm/build/schema.js +25 -3
- package/dist/esm/build/schema.js.map +1 -1
- package/dist/esm/client/index.js +306 -42
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/database/actions.js +264 -104
- package/dist/esm/database/actions.js.map +1 -1
- package/dist/esm/database/index.js +39 -33
- package/dist/esm/database/index.js.map +1 -1
- package/dist/esm/database/queryBuilder.js +1 -0
- package/dist/esm/database/queryBuilder.js.map +1 -1
- package/dist/esm/drizzle/index.js +11 -7
- package/dist/esm/drizzle/index.js.map +1 -1
- package/dist/esm/drizzle/onchain.js +18 -0
- package/dist/esm/drizzle/onchain.js.map +1 -1
- package/dist/esm/indexing/client.js +32 -25
- package/dist/esm/indexing/client.js.map +1 -1
- package/dist/esm/indexing/index.js +110 -178
- package/dist/esm/indexing/index.js.map +1 -1
- package/dist/esm/indexing/profile.js +1 -1
- package/dist/esm/indexing/profile.js.map +1 -1
- package/dist/esm/indexing-store/cache.js +196 -274
- package/dist/esm/indexing-store/cache.js.map +1 -1
- package/dist/esm/indexing-store/historical.js +17 -13
- package/dist/esm/indexing-store/historical.js.map +1 -1
- package/dist/esm/indexing-store/index.js +10 -1
- package/dist/esm/indexing-store/index.js.map +1 -1
- package/dist/esm/indexing-store/profile.js +3 -3
- package/dist/esm/indexing-store/profile.js.map +1 -1
- package/dist/esm/indexing-store/realtime.js +27 -2
- package/dist/esm/indexing-store/realtime.js.map +1 -1
- package/dist/esm/internal/errors.js +28 -0
- package/dist/esm/internal/errors.js.map +1 -1
- package/dist/esm/internal/metrics.js +279 -82
- package/dist/esm/internal/metrics.js.map +1 -1
- package/dist/esm/internal/options.js +1 -0
- package/dist/esm/internal/options.js.map +1 -1
- package/dist/esm/internal/telemetry.js +1 -1
- package/dist/esm/internal/telemetry.js.map +1 -1
- package/dist/esm/rpc/http.js +130 -0
- package/dist/esm/rpc/http.js.map +1 -0
- package/dist/esm/rpc/index.js +38 -7
- package/dist/esm/rpc/index.js.map +1 -1
- package/dist/esm/runtime/events.js +179 -212
- package/dist/esm/runtime/events.js.map +1 -1
- package/dist/esm/runtime/filter.js +71 -0
- package/dist/esm/runtime/filter.js.map +1 -1
- package/dist/esm/runtime/fragments.js +78 -73
- package/dist/esm/runtime/fragments.js.map +1 -1
- package/dist/esm/runtime/historical.js +306 -130
- package/dist/esm/runtime/historical.js.map +1 -1
- package/dist/esm/runtime/index.js +183 -58
- package/dist/esm/runtime/index.js.map +1 -1
- package/dist/esm/runtime/isolated.js +462 -0
- package/dist/esm/runtime/isolated.js.map +1 -0
- package/dist/esm/runtime/multichain.js +80 -73
- package/dist/esm/runtime/multichain.js.map +1 -1
- package/dist/esm/runtime/omnichain.js +82 -75
- package/dist/esm/runtime/omnichain.js.map +1 -1
- package/dist/esm/runtime/realtime.js +198 -66
- package/dist/esm/runtime/realtime.js.map +1 -1
- package/dist/esm/sync-historical/index.js +416 -457
- package/dist/esm/sync-historical/index.js.map +1 -1
- package/dist/esm/sync-realtime/bloom.js +3 -3
- package/dist/esm/sync-realtime/bloom.js.map +1 -1
- package/dist/esm/sync-realtime/index.js +27 -46
- package/dist/esm/sync-realtime/index.js.map +1 -1
- package/dist/esm/sync-store/index.js +112 -63
- package/dist/esm/sync-store/index.js.map +1 -1
- package/dist/esm/utils/abi.js +20 -32
- package/dist/esm/utils/abi.js.map +1 -1
- package/dist/esm/utils/chunk.js +8 -0
- package/dist/esm/utils/chunk.js.map +1 -0
- package/dist/esm/utils/promiseAllSettledWithThrow.js +19 -0
- package/dist/esm/utils/promiseAllSettledWithThrow.js.map +1 -0
- package/dist/esm/{client/parse.js → utils/sql-parse.js} +94 -80
- package/dist/esm/utils/sql-parse.js.map +1 -0
- package/dist/types/bin/commands/createViews.d.ts.map +1 -1
- package/dist/types/bin/commands/dev.d.ts.map +1 -1
- package/dist/types/bin/commands/prune.d.ts.map +1 -1
- package/dist/types/bin/commands/serve.d.ts.map +1 -1
- package/dist/types/bin/commands/start.d.ts.map +1 -1
- package/dist/types/bin/isolatedController.d.ts +13 -0
- package/dist/types/bin/isolatedController.d.ts.map +1 -0
- package/dist/types/bin/isolatedWorker.d.ts +9 -0
- package/dist/types/bin/isolatedWorker.d.ts.map +1 -0
- package/dist/types/build/config.d.ts +29 -11
- package/dist/types/build/config.d.ts.map +1 -1
- package/dist/types/build/index.d.ts +3 -2
- package/dist/types/build/index.d.ts.map +1 -1
- package/dist/types/build/pre.d.ts +1 -1
- package/dist/types/build/pre.d.ts.map +1 -1
- package/dist/types/build/schema.d.ts +5 -3
- package/dist/types/build/schema.d.ts.map +1 -1
- package/dist/types/client/index.d.ts +1 -1
- package/dist/types/client/index.d.ts.map +1 -1
- package/dist/types/config/index.d.ts +3 -3
- package/dist/types/config/index.d.ts.map +1 -1
- package/dist/types/database/actions.d.ts +53 -7
- package/dist/types/database/actions.d.ts.map +1 -1
- package/dist/types/database/index.d.ts +21 -21
- package/dist/types/database/index.d.ts.map +1 -1
- package/dist/types/database/queryBuilder.d.ts.map +1 -1
- package/dist/types/drizzle/index.d.ts +4 -5
- package/dist/types/drizzle/index.d.ts.map +1 -1
- package/dist/types/drizzle/onchain.d.ts +6 -0
- package/dist/types/drizzle/onchain.d.ts.map +1 -1
- package/dist/types/indexing/client.d.ts.map +1 -1
- package/dist/types/indexing/index.d.ts +2 -5
- package/dist/types/indexing/index.d.ts.map +1 -1
- package/dist/types/indexing-store/cache.d.ts +3 -2
- package/dist/types/indexing-store/cache.d.ts.map +1 -1
- package/dist/types/indexing-store/historical.d.ts +2 -1
- package/dist/types/indexing-store/historical.d.ts.map +1 -1
- package/dist/types/indexing-store/index.d.ts +1 -0
- package/dist/types/indexing-store/index.d.ts.map +1 -1
- package/dist/types/indexing-store/realtime.d.ts +2 -1
- package/dist/types/indexing-store/realtime.d.ts.map +1 -1
- package/dist/types/internal/errors.d.ts +5 -0
- package/dist/types/internal/errors.d.ts.map +1 -1
- package/dist/types/internal/metrics.d.ts +21 -0
- package/dist/types/internal/metrics.d.ts.map +1 -1
- package/dist/types/internal/options.d.ts +2 -0
- package/dist/types/internal/options.d.ts.map +1 -1
- package/dist/types/internal/types.d.ts +66 -58
- package/dist/types/internal/types.d.ts.map +1 -1
- package/dist/types/rpc/http.d.ts +17 -0
- package/dist/types/rpc/http.d.ts.map +1 -0
- package/dist/types/rpc/index.d.ts.map +1 -1
- package/dist/types/runtime/events.d.ts +4 -4
- package/dist/types/runtime/events.d.ts.map +1 -1
- package/dist/types/runtime/filter.d.ts +5 -1
- package/dist/types/runtime/filter.d.ts.map +1 -1
- package/dist/types/runtime/fragments.d.ts +4 -3
- package/dist/types/runtime/fragments.d.ts.map +1 -1
- package/dist/types/runtime/historical.d.ts +29 -13
- package/dist/types/runtime/historical.d.ts.map +1 -1
- package/dist/types/runtime/index.d.ts +49 -6
- package/dist/types/runtime/index.d.ts.map +1 -1
- package/dist/types/runtime/init.d.ts +5 -5
- package/dist/types/runtime/init.d.ts.map +1 -1
- package/dist/types/runtime/isolated.d.ts +14 -0
- package/dist/types/runtime/isolated.d.ts.map +1 -0
- package/dist/types/runtime/multichain.d.ts.map +1 -1
- package/dist/types/runtime/omnichain.d.ts.map +1 -1
- package/dist/types/runtime/realtime.d.ts +21 -10
- package/dist/types/runtime/realtime.d.ts.map +1 -1
- package/dist/types/sync-historical/index.d.ts +18 -8
- package/dist/types/sync-historical/index.d.ts.map +1 -1
- package/dist/types/sync-realtime/bloom.d.ts.map +1 -1
- package/dist/types/sync-realtime/index.d.ts +2 -2
- package/dist/types/sync-realtime/index.d.ts.map +1 -1
- package/dist/types/sync-store/index.d.ts +9 -9
- package/dist/types/sync-store/index.d.ts.map +1 -1
- package/dist/types/utils/abi.d.ts +3 -34
- package/dist/types/utils/abi.d.ts.map +1 -1
- package/dist/types/utils/chunk.d.ts +2 -0
- package/dist/types/utils/chunk.d.ts.map +1 -0
- package/dist/types/utils/promiseAllSettledWithThrow.d.ts +8 -0
- package/dist/types/utils/promiseAllSettledWithThrow.d.ts.map +1 -0
- package/dist/types/utils/sql-parse.d.ts +21 -0
- package/dist/types/utils/sql-parse.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/bin/commands/createViews.ts +35 -15
- package/src/bin/commands/dev.ts +43 -21
- package/src/bin/commands/prune.ts +6 -0
- package/src/bin/commands/serve.ts +4 -1
- package/src/bin/commands/start.ts +20 -5
- package/src/bin/isolatedController.ts +300 -0
- package/src/bin/isolatedWorker.ts +192 -0
- package/src/build/config.ts +570 -632
- package/src/build/index.ts +14 -14
- package/src/build/pre.ts +1 -4
- package/src/build/schema.ts +49 -4
- package/src/client/index.ts +386 -48
- package/src/config/index.ts +3 -3
- package/src/database/actions.ts +469 -120
- package/src/database/index.ts +85 -58
- package/src/database/queryBuilder.ts +1 -0
- package/src/drizzle/index.ts +15 -7
- package/src/drizzle/onchain.ts +19 -0
- package/src/indexing/client.ts +38 -25
- package/src/indexing/index.ts +137 -230
- package/src/indexing/profile.ts +1 -1
- package/src/indexing-store/cache.ts +285 -414
- package/src/indexing-store/historical.ts +20 -10
- package/src/indexing-store/index.ts +16 -0
- package/src/indexing-store/profile.ts +3 -3
- package/src/indexing-store/realtime.ts +28 -0
- package/src/internal/errors.ts +26 -0
- package/src/internal/metrics.ts +341 -111
- package/src/internal/options.ts +4 -0
- package/src/internal/telemetry.ts +1 -1
- package/src/internal/types.ts +70 -87
- package/src/rpc/http.ts +164 -0
- package/src/rpc/index.ts +39 -7
- package/src/runtime/events.ts +195 -240
- package/src/runtime/filter.ts +85 -1
- package/src/runtime/fragments.ts +109 -113
- package/src/runtime/historical.ts +467 -189
- package/src/runtime/index.ts +337 -69
- package/src/runtime/init.ts +5 -5
- package/src/runtime/isolated.ts +768 -0
- package/src/runtime/multichain.ts +137 -102
- package/src/runtime/omnichain.ts +138 -106
- package/src/runtime/realtime.ts +322 -123
- package/src/sync-historical/index.ts +556 -692
- package/src/sync-realtime/bloom.ts +7 -3
- package/src/sync-realtime/index.ts +31 -46
- package/src/sync-store/index.ts +189 -95
- package/src/utils/abi.ts +33 -90
- package/src/utils/chunk.ts +7 -0
- package/src/utils/promiseAllSettledWithThrow.ts +27 -0
- package/src/{client/parse.ts → utils/sql-parse.ts} +100 -90
- package/dist/esm/client/parse.js.map +0 -1
- package/dist/types/client/parse.d.ts +0 -14
- package/dist/types/client/parse.d.ts.map +0 -1
package/src/client/index.ts
CHANGED
|
@@ -1,13 +1,56 @@
|
|
|
1
|
+
import type { PonderApp5 } from "@/database/index.js";
|
|
2
|
+
import { getLiveQueryChannelName } from "@/drizzle/onchain.js";
|
|
1
3
|
import type { Schema } from "@/internal/types.js";
|
|
2
4
|
import type { ReadonlyDrizzle } from "@/types/db.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
import {
|
|
6
|
+
type PromiseWithResolvers,
|
|
7
|
+
promiseWithResolvers,
|
|
8
|
+
} from "@/utils/promiseWithResolvers.js";
|
|
9
|
+
import {
|
|
10
|
+
getSQLQueryRelations,
|
|
11
|
+
validateAllowableSQLQuery,
|
|
12
|
+
} from "@/utils/sql-parse.js";
|
|
13
|
+
import {
|
|
14
|
+
type QueryWithTypings,
|
|
15
|
+
getTableName,
|
|
16
|
+
getViewName,
|
|
17
|
+
isTable,
|
|
18
|
+
isView,
|
|
19
|
+
} from "drizzle-orm";
|
|
20
|
+
import {
|
|
21
|
+
type PgDialect,
|
|
22
|
+
type PgSession,
|
|
23
|
+
type PgView,
|
|
24
|
+
getViewConfig,
|
|
25
|
+
pgSchema,
|
|
26
|
+
pgTable,
|
|
27
|
+
} from "drizzle-orm/pg-core";
|
|
6
28
|
import { createMiddleware } from "hono/factory";
|
|
7
29
|
import { streamSSE } from "hono/streaming";
|
|
8
30
|
import type * as pg from "pg";
|
|
9
31
|
import superjson from "superjson";
|
|
10
|
-
|
|
32
|
+
|
|
33
|
+
type QueryString = string;
|
|
34
|
+
type QueryResult = unknown;
|
|
35
|
+
|
|
36
|
+
const MAX_LIVE_QUERIES = 1000;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @dev This is copied to avoid bundling another dependency.
|
|
40
|
+
*/
|
|
41
|
+
const getPonderMetaTable = (schema?: string) => {
|
|
42
|
+
if (schema === undefined || schema === "public") {
|
|
43
|
+
return pgTable("_ponder_meta", (t) => ({
|
|
44
|
+
key: t.text().primaryKey().$type<"app">(),
|
|
45
|
+
value: t.jsonb().$type<PonderApp5>().notNull(),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return pgSchema(schema).table("_ponder_meta", (t) => ({
|
|
50
|
+
key: t.text().primaryKey().$type<"app">(),
|
|
51
|
+
value: t.jsonb().$type<PonderApp5>().notNull(),
|
|
52
|
+
}));
|
|
53
|
+
};
|
|
11
54
|
|
|
12
55
|
/**
|
|
13
56
|
* Middleware for `@ponder/client`.
|
|
@@ -31,6 +74,7 @@ import { validateQuery } from "./parse.js";
|
|
|
31
74
|
*/
|
|
32
75
|
export const client = ({
|
|
33
76
|
db,
|
|
77
|
+
schema,
|
|
34
78
|
}: { db: ReadonlyDrizzle<Schema>; schema: Schema }) => {
|
|
35
79
|
if (
|
|
36
80
|
globalThis.PONDER_COMMON === undefined ||
|
|
@@ -42,23 +86,147 @@ export const client = ({
|
|
|
42
86
|
);
|
|
43
87
|
}
|
|
44
88
|
|
|
89
|
+
const tables = Object.values(schema).filter(isTable);
|
|
90
|
+
const views = Object.values(schema).filter(isView);
|
|
91
|
+
const tableNames = new Set(tables.map(getTableName));
|
|
92
|
+
const viewNames = new Set(views.map(getViewName));
|
|
93
|
+
|
|
94
|
+
// Note: Add system tables to the live query registry.
|
|
95
|
+
tableNames.add("_ponder_checkpoint");
|
|
96
|
+
|
|
45
97
|
// @ts-ignore
|
|
46
98
|
const session: PgSession = db._.session;
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
const dialect: PgDialect = session.dialect;
|
|
47
101
|
const driver = globalThis.PONDER_DATABASE.driver;
|
|
48
|
-
let statusResolver = promiseWithResolvers<void>();
|
|
49
102
|
|
|
50
|
-
const
|
|
103
|
+
const perTableResolver = new Map<string, PromiseWithResolvers<void>>();
|
|
104
|
+
const perViewTables = new Map<string, Set<string>>();
|
|
51
105
|
|
|
52
|
-
|
|
106
|
+
/** `true` if the app is indexing live blocks. */
|
|
107
|
+
let liveQueryCount = 0;
|
|
108
|
+
let isReady = false;
|
|
109
|
+
|
|
110
|
+
(async () => {
|
|
111
|
+
while (globalThis.PONDER_COMMON.apiShutdown.isKilled === false) {
|
|
112
|
+
try {
|
|
113
|
+
isReady = await globalThis.PONDER_DATABASE.readonlyQB.wrap(
|
|
114
|
+
{ label: "select_ready" },
|
|
115
|
+
(db) =>
|
|
116
|
+
db
|
|
117
|
+
.select()
|
|
118
|
+
.from(getPonderMetaTable())
|
|
119
|
+
.then((result) => result[0]!.value.is_ready === 1),
|
|
120
|
+
);
|
|
121
|
+
} catch {}
|
|
122
|
+
if (isReady) return;
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
|
|
127
|
+
const cache = new Map<QueryString, WeakRef<Promise<unknown>>>();
|
|
128
|
+
const perQueryReferences = new Map<QueryString, Set<string>>();
|
|
129
|
+
|
|
130
|
+
const registry = new FinalizationRegistry<QueryString>((queryString) => {
|
|
131
|
+
// Note: When a cache entry is garbage collected, delete the key from `perQueryReferences`.
|
|
132
|
+
cache.delete(queryString);
|
|
133
|
+
perQueryReferences.delete(queryString);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
for (const table of tableNames) {
|
|
137
|
+
perTableResolver.set(table, promiseWithResolvers<void>());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parseViewPromise = (async () => {
|
|
141
|
+
const unresolvedViewRelations = new Map<string, Set<string>>();
|
|
142
|
+
for (const view of views) {
|
|
143
|
+
const query = dialect.sqlToQuery(getViewConfig(view as PgView).query!);
|
|
144
|
+
const relations = await getSQLQueryRelations(query.sql);
|
|
145
|
+
|
|
146
|
+
unresolvedViewRelations.set(getViewName(view), relations);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Recursively resolve nested views (views that reference other views).
|
|
151
|
+
*
|
|
152
|
+
* @dev This assumes views cannot be infinitely cursive - an invariant enforced by Postgres.
|
|
153
|
+
*/
|
|
154
|
+
const resolveRelation = (relation: string): Set<string> => {
|
|
155
|
+
if (perViewTables.has(relation)) {
|
|
156
|
+
return perViewTables.get(relation)!;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (tableNames.has(relation)) {
|
|
160
|
+
return new Set([relation]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (viewNames.has(relation)) {
|
|
164
|
+
const result = new Set<string>();
|
|
165
|
+
for (const _relation of unresolvedViewRelations.get(relation)!) {
|
|
166
|
+
for (const __relation of resolveRelation(_relation)) {
|
|
167
|
+
result.add(__relation);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return new Set();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
for (const [viewName, relations] of unresolvedViewRelations) {
|
|
177
|
+
const resolvedRelations = new Set<string>();
|
|
178
|
+
for (const relation of relations) {
|
|
179
|
+
for (const _relation of resolveRelation(relation)) {
|
|
180
|
+
resolvedRelations.add(_relation);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
perViewTables.set(viewName, resolvedRelations);
|
|
184
|
+
}
|
|
185
|
+
})();
|
|
53
186
|
|
|
54
187
|
if (driver.dialect === "pglite") {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
188
|
+
const channel = getLiveQueryChannelName(
|
|
189
|
+
globalThis.PONDER_NAMESPACE_BUILD.schema,
|
|
190
|
+
);
|
|
191
|
+
driver.instance.query(`LISTEN "${channel}"`);
|
|
192
|
+
|
|
193
|
+
driver.instance.onNotification((_, payload) => {
|
|
194
|
+
const tables = JSON.parse(payload!) as string[];
|
|
195
|
+
tables.push("_ponder_checkpoint");
|
|
196
|
+
let invalidQueryCount = 0;
|
|
197
|
+
|
|
198
|
+
for (const [queryString, referencedTables] of perQueryReferences) {
|
|
199
|
+
let isQueryInvalid = false;
|
|
200
|
+
for (const table of tables) {
|
|
201
|
+
if (referencedTables.has(table)) {
|
|
202
|
+
isQueryInvalid = true;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (isQueryInvalid) {
|
|
208
|
+
invalidQueryCount++;
|
|
209
|
+
|
|
210
|
+
const resultPromise = cache.get(queryString)?.deref();
|
|
211
|
+
if (resultPromise) registry.unregister(resultPromise);
|
|
212
|
+
|
|
213
|
+
cache.delete(queryString);
|
|
214
|
+
perQueryReferences.delete(queryString);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
for (const table of tables) {
|
|
219
|
+
perTableResolver.get(table)!.resolve();
|
|
220
|
+
perTableResolver.set(table, promiseWithResolvers<void>());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (invalidQueryCount > 0) {
|
|
224
|
+
globalThis.PONDER_COMMON.logger.debug({
|
|
225
|
+
msg: "Updated live queries",
|
|
226
|
+
tables: JSON.stringify(Array.from(tables)),
|
|
227
|
+
query_count: invalidQueryCount,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
62
230
|
});
|
|
63
231
|
} else {
|
|
64
232
|
(async () => {
|
|
@@ -84,11 +252,46 @@ export const client = ({
|
|
|
84
252
|
msg: `Established listen connection for "@ponder/client" middleware`,
|
|
85
253
|
});
|
|
86
254
|
|
|
87
|
-
client.on("notification", () => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
255
|
+
client.on("notification", (notification) => {
|
|
256
|
+
const tables = JSON.parse(notification.payload!) as string[];
|
|
257
|
+
tables.push("_ponder_checkpoint");
|
|
258
|
+
let invalidQueryCount = 0;
|
|
259
|
+
|
|
260
|
+
for (const [
|
|
261
|
+
queryString,
|
|
262
|
+
referencedTables,
|
|
263
|
+
] of perQueryReferences) {
|
|
264
|
+
let isQueryInvalid = false;
|
|
265
|
+
for (const table of tables) {
|
|
266
|
+
if (referencedTables.has(table)) {
|
|
267
|
+
isQueryInvalid = true;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (isQueryInvalid) {
|
|
273
|
+
invalidQueryCount++;
|
|
274
|
+
|
|
275
|
+
const resultPromise = cache.get(queryString)?.deref();
|
|
276
|
+
if (resultPromise) registry.unregister(resultPromise);
|
|
277
|
+
|
|
278
|
+
cache.delete(queryString);
|
|
279
|
+
perQueryReferences.delete(queryString);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for (const table of tables) {
|
|
284
|
+
perTableResolver.get(table)!.resolve();
|
|
285
|
+
perTableResolver.set(table, promiseWithResolvers<void>());
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (invalidQueryCount > 0) {
|
|
289
|
+
globalThis.PONDER_COMMON.logger.debug({
|
|
290
|
+
msg: "Updated live queries",
|
|
291
|
+
tables: JSON.stringify(tables),
|
|
292
|
+
query_count: invalidQueryCount,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
92
295
|
});
|
|
93
296
|
|
|
94
297
|
client.on("error", async (error) => {
|
|
@@ -105,6 +308,10 @@ export const client = ({
|
|
|
105
308
|
resolve();
|
|
106
309
|
});
|
|
107
310
|
|
|
311
|
+
const channel = getLiveQueryChannelName(
|
|
312
|
+
globalThis.PONDER_NAMESPACE_BUILD.schema,
|
|
313
|
+
);
|
|
314
|
+
|
|
108
315
|
await client.query(`LISTEN "${channel}"`);
|
|
109
316
|
} catch (error) {
|
|
110
317
|
globalThis.PONDER_COMMON.logger.warn({
|
|
@@ -124,7 +331,25 @@ export const client = ({
|
|
|
124
331
|
})();
|
|
125
332
|
}
|
|
126
333
|
|
|
334
|
+
const getQueryResult = (query: QueryWithTypings): Promise<QueryResult> => {
|
|
335
|
+
if (driver.dialect === "pglite") {
|
|
336
|
+
return session.prepareQuery(query, undefined, undefined, false).execute();
|
|
337
|
+
} else {
|
|
338
|
+
return globalThis.PONDER_DATABASE.readonlyQB.raw.transaction(
|
|
339
|
+
(tx) => {
|
|
340
|
+
return tx._.session
|
|
341
|
+
.prepareQuery(query, undefined, undefined, false)
|
|
342
|
+
.execute();
|
|
343
|
+
},
|
|
344
|
+
{ accessMode: "read only" },
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
127
349
|
return createMiddleware(async (c, next) => {
|
|
350
|
+
const crypto = await import(/* webpackIgnore: true */ "node:crypto");
|
|
351
|
+
await parseViewPromise;
|
|
352
|
+
|
|
128
353
|
if (c.req.path === "/sql/db") {
|
|
129
354
|
const queryString = c.req.query("sql");
|
|
130
355
|
if (queryString === undefined) {
|
|
@@ -133,43 +358,45 @@ export const client = ({
|
|
|
133
358
|
const query = superjson.parse(queryString) as QueryWithTypings;
|
|
134
359
|
|
|
135
360
|
try {
|
|
136
|
-
await
|
|
361
|
+
await validateAllowableSQLQuery(query.sql);
|
|
137
362
|
} catch (error) {
|
|
138
363
|
(error as Error).stack = undefined;
|
|
139
364
|
return c.text((error as Error).message, 500);
|
|
140
365
|
}
|
|
141
366
|
|
|
142
|
-
|
|
367
|
+
const relations = await getSQLQueryRelations(query.sql);
|
|
368
|
+
const referencedTables = new Set<string>();
|
|
369
|
+
for (const relation of relations) {
|
|
370
|
+
if (tableNames.has(relation)) {
|
|
371
|
+
referencedTables.add(relation);
|
|
372
|
+
} else if (viewNames.has(relation)) {
|
|
373
|
+
for (const tableName of perViewTables.get(relation)!) {
|
|
374
|
+
referencedTables.add(tableName);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
143
378
|
|
|
144
|
-
|
|
145
|
-
if (_resultPromise === undefined) {
|
|
146
|
-
cache.delete(queryString);
|
|
379
|
+
let resultPromise: Promise<unknown>;
|
|
147
380
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
381
|
+
if (isReady === false) {
|
|
382
|
+
resultPromise = getQueryResult(query);
|
|
383
|
+
} else if (cache.has(queryString)) {
|
|
384
|
+
const resultRef = cache.get(queryString)!.deref();
|
|
151
385
|
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
386
|
+
if (resultRef === undefined) {
|
|
387
|
+
cache.delete(queryString);
|
|
388
|
+
resultPromise = getQueryResult(query);
|
|
389
|
+
cache.set(queryString, new WeakRef(resultPromise));
|
|
390
|
+
perQueryReferences.set(queryString, referencedTables);
|
|
391
|
+
registry.register(resultPromise, queryString);
|
|
158
392
|
} else {
|
|
159
|
-
|
|
160
|
-
.transaction(
|
|
161
|
-
(tx) => {
|
|
162
|
-
return tx._.session
|
|
163
|
-
.prepareQuery(query, undefined, undefined, false)
|
|
164
|
-
.execute();
|
|
165
|
-
},
|
|
166
|
-
{ accessMode: "read only" },
|
|
167
|
-
)
|
|
168
|
-
.then(pwr.resolve)
|
|
169
|
-
.catch(pwr.reject);
|
|
393
|
+
resultPromise = resultRef;
|
|
170
394
|
}
|
|
171
395
|
} else {
|
|
172
|
-
resultPromise =
|
|
396
|
+
resultPromise = getQueryResult(query);
|
|
397
|
+
cache.set(queryString, new WeakRef(resultPromise));
|
|
398
|
+
perQueryReferences.set(queryString, referencedTables);
|
|
399
|
+
registry.register(resultPromise, queryString);
|
|
173
400
|
}
|
|
174
401
|
|
|
175
402
|
try {
|
|
@@ -181,16 +408,127 @@ export const client = ({
|
|
|
181
408
|
}
|
|
182
409
|
|
|
183
410
|
if (c.req.path === "/sql/live") {
|
|
411
|
+
if (isReady === false) {
|
|
412
|
+
return c.text(
|
|
413
|
+
"Live queries are not available until the backfill is complete",
|
|
414
|
+
503,
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (liveQueryCount >= MAX_LIVE_QUERIES) {
|
|
419
|
+
return c.text("Maximum number of live queries reached", 503);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
liveQueryCount++;
|
|
423
|
+
|
|
184
424
|
c.header("Content-Type", "text/event-stream");
|
|
185
425
|
c.header("Cache-Control", "no-cache");
|
|
186
426
|
c.header("Connection", "keep-alive");
|
|
187
427
|
|
|
428
|
+
const queryString = c.req.query("sql");
|
|
429
|
+
if (queryString === undefined) {
|
|
430
|
+
return c.text('Missing "sql" query parameter', 400);
|
|
431
|
+
}
|
|
432
|
+
const query = superjson.parse(queryString) as QueryWithTypings;
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
await validateAllowableSQLQuery(query.sql);
|
|
436
|
+
} catch (error) {
|
|
437
|
+
(error as Error).stack = undefined;
|
|
438
|
+
return c.text((error as Error).message, 500);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const relations = await getSQLQueryRelations(query.sql);
|
|
442
|
+
const referencedTables = new Set<string>();
|
|
443
|
+
for (const relation of relations) {
|
|
444
|
+
if (tableNames.has(relation)) {
|
|
445
|
+
referencedTables.add(relation);
|
|
446
|
+
} else if (viewNames.has(relation)) {
|
|
447
|
+
for (const tableName of perViewTables.get(relation)!) {
|
|
448
|
+
referencedTables.add(tableName);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
let result: QueryResult;
|
|
454
|
+
if (cache.has(queryString)) {
|
|
455
|
+
const resultRef = cache.get(queryString)!.deref();
|
|
456
|
+
|
|
457
|
+
if (resultRef === undefined) {
|
|
458
|
+
cache.delete(queryString);
|
|
459
|
+
const resultPromise = getQueryResult(query);
|
|
460
|
+
cache.set(queryString, new WeakRef(resultPromise));
|
|
461
|
+
perQueryReferences.set(queryString, referencedTables);
|
|
462
|
+
registry.register(resultPromise, queryString);
|
|
463
|
+
result = await resultPromise;
|
|
464
|
+
} else {
|
|
465
|
+
result = await resultRef;
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
const resultPromise = getQueryResult(query);
|
|
469
|
+
cache.set(queryString, new WeakRef(resultPromise));
|
|
470
|
+
perQueryReferences.set(queryString, referencedTables);
|
|
471
|
+
registry.register(resultPromise, queryString);
|
|
472
|
+
result = await resultPromise;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
let resultHash = crypto
|
|
476
|
+
.createHash("MD5")
|
|
477
|
+
// @ts-ignore
|
|
478
|
+
.update(JSON.stringify(result.rows))
|
|
479
|
+
.digest("hex")
|
|
480
|
+
.slice(0, 10);
|
|
481
|
+
|
|
188
482
|
return streamSSE(c, async (stream) => {
|
|
483
|
+
stream.onAbort(() => {
|
|
484
|
+
liveQueryCount--;
|
|
485
|
+
});
|
|
486
|
+
|
|
189
487
|
while (stream.closed === false && stream.aborted === false) {
|
|
488
|
+
await Promise.race(
|
|
489
|
+
Array.from(referencedTables).map(
|
|
490
|
+
(relation) => perTableResolver.get(relation)!.promise,
|
|
491
|
+
),
|
|
492
|
+
);
|
|
493
|
+
|
|
190
494
|
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
495
|
+
let resultPromise: Promise<unknown>;
|
|
496
|
+
if (cache.has(queryString)) {
|
|
497
|
+
const resultRef = cache.get(queryString)!.deref();
|
|
498
|
+
|
|
499
|
+
if (resultRef === undefined) {
|
|
500
|
+
cache.delete(queryString);
|
|
501
|
+
resultPromise = getQueryResult(query);
|
|
502
|
+
cache.set(queryString, new WeakRef(resultPromise));
|
|
503
|
+
perQueryReferences.set(queryString, referencedTables);
|
|
504
|
+
registry.register(resultPromise, queryString);
|
|
505
|
+
} else {
|
|
506
|
+
resultPromise = resultRef;
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
resultPromise = getQueryResult(query);
|
|
510
|
+
cache.set(queryString, new WeakRef(resultPromise));
|
|
511
|
+
perQueryReferences.set(queryString, referencedTables);
|
|
512
|
+
registry.register(resultPromise, queryString);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const result = await resultPromise;
|
|
516
|
+
|
|
517
|
+
const _resultHash = crypto
|
|
518
|
+
.createHash("MD5")
|
|
519
|
+
// @ts-ignore
|
|
520
|
+
.update(JSON.stringify(result.rows))
|
|
521
|
+
.digest("hex")
|
|
522
|
+
.slice(0, 10);
|
|
523
|
+
|
|
524
|
+
if (_resultHash === resultHash) continue;
|
|
525
|
+
resultHash = _resultHash;
|
|
526
|
+
|
|
527
|
+
// @ts-ignore
|
|
528
|
+
await stream.writeSSE({ data: JSON.stringify(result) });
|
|
529
|
+
} catch {
|
|
530
|
+
stream.abort();
|
|
531
|
+
}
|
|
194
532
|
}
|
|
195
533
|
});
|
|
196
534
|
}
|
package/src/config/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { GetEventFilter } from "./eventFilter.js";
|
|
|
7
7
|
|
|
8
8
|
export type Config = {
|
|
9
9
|
database?: DatabaseConfig;
|
|
10
|
-
ordering?: "omnichain" | "multichain";
|
|
10
|
+
ordering?: "omnichain" | "multichain" | "experimental_isolated";
|
|
11
11
|
chains: { [chainName: string]: ChainConfig<unknown> };
|
|
12
12
|
contracts: { [contractName: string]: GetContract };
|
|
13
13
|
accounts: { [accountName: string]: AccountConfig<unknown> };
|
|
@@ -18,7 +18,7 @@ export type Config = {
|
|
|
18
18
|
|
|
19
19
|
export type CreateConfigReturnType<chains, contracts, accounts, blocks> = {
|
|
20
20
|
database?: DatabaseConfig;
|
|
21
|
-
ordering?: "omnichain" | "multichain";
|
|
21
|
+
ordering?: "omnichain" | "multichain" | "experimental_isolated";
|
|
22
22
|
chains: chains;
|
|
23
23
|
contracts: contracts;
|
|
24
24
|
accounts: accounts;
|
|
@@ -32,7 +32,7 @@ export const createConfig = <
|
|
|
32
32
|
const blocks = {},
|
|
33
33
|
>(config: {
|
|
34
34
|
database?: DatabaseConfig;
|
|
35
|
-
ordering?: "omnichain" | "multichain";
|
|
35
|
+
ordering?: "omnichain" | "multichain" | "experimental_isolated";
|
|
36
36
|
// TODO: add jsdoc to these properties.
|
|
37
37
|
chains: ChainsConfig<Narrow<chains>>;
|
|
38
38
|
contracts?: ContractsConfig<chains, Narrow<contracts>>;
|