@ponder/client 0.9.0-next.1 → 0.9.0-next.2
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.d.ts +108 -4
- package/dist/index.js +108 -14
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/src/index.ts +178 -23
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
2
|
+
export { alias, except, exceptAll, intersect, intersectAll, union, unionAll } from 'drizzle-orm/pg-core';
|
|
1
3
|
import { PgRemoteDatabase } from 'drizzle-orm/pg-proxy';
|
|
2
4
|
export { and, asc, avg, avgDistinct, between, count, countDistinct, desc, eq, exists, gt, gte, inArray, isNotNull, isNull, like, lt, lte, max, min, ne, not, notBetween, notExists, notIlike, notInArray, or, relations, sql, sum, sumDistinct } from 'drizzle-orm';
|
|
3
|
-
export { alias, except, exceptAll, intersect, intersectAll, union, unionAll } from 'drizzle-orm/pg-core';
|
|
4
5
|
|
|
5
6
|
declare const setDatabaseSchema: <T extends {
|
|
6
7
|
[name: string]: unknown;
|
|
@@ -12,11 +13,114 @@ type Schema = {
|
|
|
12
13
|
type Prettify<T> = {
|
|
13
14
|
[K in keyof T]: T[K];
|
|
14
15
|
} & {};
|
|
16
|
+
type ClientDb<schema extends Schema = Schema> = Prettify<Omit<PgRemoteDatabase<schema>, "insert" | "update" | "delete" | "transaction" | "refreshMaterializedView" | "_">>;
|
|
15
17
|
type Client<schema extends Schema = Schema> = {
|
|
16
|
-
|
|
18
|
+
/** Query the database. */
|
|
19
|
+
db: ClientDb<schema>;
|
|
20
|
+
/** Subscribe to live updates. */
|
|
21
|
+
live: <result>(query: (db: ClientDb<schema>) => Promise<result>, onData: (result: result) => void, onError?: (error: Error) => void) => {
|
|
22
|
+
unsubscribe: () => void;
|
|
23
|
+
};
|
|
17
24
|
};
|
|
18
|
-
|
|
25
|
+
/**
|
|
26
|
+
* A table that tracks the status of each chain.
|
|
27
|
+
*
|
|
28
|
+
* @property {number} chainId - The chain ID.
|
|
29
|
+
* @property {number} blockNumber - The closest-to-tip indexed block number.
|
|
30
|
+
* @property {number} blockTimestamp - The closest-to-tip indexed block timestamp.
|
|
31
|
+
* @property {boolean} ready - `true` if the chain has completed the historical backfill.
|
|
32
|
+
*/
|
|
33
|
+
declare const status: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
34
|
+
name: "_ponder_status";
|
|
35
|
+
schema: undefined;
|
|
36
|
+
columns: {
|
|
37
|
+
chainId: drizzle_orm_pg_core.PgColumn<{
|
|
38
|
+
name: "chainId";
|
|
39
|
+
tableName: "_ponder_status";
|
|
40
|
+
dataType: "number";
|
|
41
|
+
columnType: "PgBigInt53";
|
|
42
|
+
data: number;
|
|
43
|
+
driverParam: string | number;
|
|
44
|
+
notNull: true;
|
|
45
|
+
hasDefault: false;
|
|
46
|
+
isPrimaryKey: true;
|
|
47
|
+
isAutoincrement: false;
|
|
48
|
+
hasRuntimeDefault: false;
|
|
49
|
+
enumValues: undefined;
|
|
50
|
+
baseColumn: never;
|
|
51
|
+
identity: undefined;
|
|
52
|
+
generated: undefined;
|
|
53
|
+
}, {}, {}>;
|
|
54
|
+
blockNumber: drizzle_orm_pg_core.PgColumn<{
|
|
55
|
+
name: "blockNumber";
|
|
56
|
+
tableName: "_ponder_status";
|
|
57
|
+
dataType: "number";
|
|
58
|
+
columnType: "PgBigInt53";
|
|
59
|
+
data: number;
|
|
60
|
+
driverParam: string | number;
|
|
61
|
+
notNull: false;
|
|
62
|
+
hasDefault: false;
|
|
63
|
+
isPrimaryKey: false;
|
|
64
|
+
isAutoincrement: false;
|
|
65
|
+
hasRuntimeDefault: false;
|
|
66
|
+
enumValues: undefined;
|
|
67
|
+
baseColumn: never;
|
|
68
|
+
identity: undefined;
|
|
69
|
+
generated: undefined;
|
|
70
|
+
}, {}, {}>;
|
|
71
|
+
blockTimestamp: drizzle_orm_pg_core.PgColumn<{
|
|
72
|
+
name: "blockTimestamp";
|
|
73
|
+
tableName: "_ponder_status";
|
|
74
|
+
dataType: "number";
|
|
75
|
+
columnType: "PgBigInt53";
|
|
76
|
+
data: number;
|
|
77
|
+
driverParam: string | number;
|
|
78
|
+
notNull: false;
|
|
79
|
+
hasDefault: false;
|
|
80
|
+
isPrimaryKey: false;
|
|
81
|
+
isAutoincrement: false;
|
|
82
|
+
hasRuntimeDefault: false;
|
|
83
|
+
enumValues: undefined;
|
|
84
|
+
baseColumn: never;
|
|
85
|
+
identity: undefined;
|
|
86
|
+
generated: undefined;
|
|
87
|
+
}, {}, {}>;
|
|
88
|
+
ready: drizzle_orm_pg_core.PgColumn<{
|
|
89
|
+
name: "ready";
|
|
90
|
+
tableName: "_ponder_status";
|
|
91
|
+
dataType: "boolean";
|
|
92
|
+
columnType: "PgBoolean";
|
|
93
|
+
data: boolean;
|
|
94
|
+
driverParam: boolean;
|
|
95
|
+
notNull: true;
|
|
96
|
+
hasDefault: false;
|
|
97
|
+
isPrimaryKey: false;
|
|
98
|
+
isAutoincrement: false;
|
|
99
|
+
hasRuntimeDefault: false;
|
|
100
|
+
enumValues: undefined;
|
|
101
|
+
baseColumn: never;
|
|
102
|
+
identity: undefined;
|
|
103
|
+
generated: undefined;
|
|
104
|
+
}, {}, {}>;
|
|
105
|
+
};
|
|
106
|
+
dialect: "pg";
|
|
107
|
+
}>;
|
|
108
|
+
/**
|
|
109
|
+
* Create a client for querying Ponder apps.
|
|
110
|
+
*
|
|
111
|
+
* @param baseUrl - The URL of the Ponder app.
|
|
112
|
+
* @param schema - The schema of the Ponder app.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { createClient } from "@ponder/client";
|
|
117
|
+
* import * as schema from "../ponder.schema";
|
|
118
|
+
*
|
|
119
|
+
* const client = createClient("https://...", { schema });
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
declare const createClient: <schema extends Schema>(baseUrl: string, { schema }: {
|
|
19
123
|
schema: schema;
|
|
20
124
|
}) => Client<schema>;
|
|
21
125
|
|
|
22
|
-
export { type Client, createClient, setDatabaseSchema };
|
|
126
|
+
export { type Client, createClient, setDatabaseSchema, status };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import { pgTable } from "drizzle-orm/pg-core";
|
|
2
4
|
import { drizzle } from "drizzle-orm/pg-proxy";
|
|
3
5
|
import {
|
|
4
|
-
sql,
|
|
6
|
+
sql as sql2,
|
|
5
7
|
eq,
|
|
6
8
|
gt,
|
|
7
9
|
gte,
|
|
@@ -56,18 +58,109 @@ var setDatabaseSchema = (schema, schemaName) => {
|
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
// src/index.ts
|
|
59
|
-
var
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return
|
|
61
|
+
var getEventSource = async () => {
|
|
62
|
+
let SSE;
|
|
63
|
+
if (typeof window === "undefined") {
|
|
64
|
+
const undici = await import(
|
|
65
|
+
/* webpackIgnore: true */
|
|
66
|
+
"undici"
|
|
67
|
+
);
|
|
68
|
+
SSE = undici.EventSource;
|
|
69
|
+
} else {
|
|
70
|
+
SSE = EventSource;
|
|
71
|
+
}
|
|
72
|
+
return SSE;
|
|
73
|
+
};
|
|
74
|
+
var getUrl = (baseUrl, method, query) => {
|
|
75
|
+
const url = new URL(`${baseUrl}/client/${method}`);
|
|
76
|
+
if (query) {
|
|
77
|
+
url.searchParams.set("sql", JSON.stringify(query));
|
|
78
|
+
}
|
|
79
|
+
return url;
|
|
80
|
+
};
|
|
81
|
+
var status = pgTable("_ponder_status", (t) => ({
|
|
82
|
+
chainId: t.bigint({ mode: "number" }).primaryKey(),
|
|
83
|
+
blockNumber: t.bigint({ mode: "number" }),
|
|
84
|
+
blockTimestamp: t.bigint({ mode: "number" }),
|
|
85
|
+
ready: t.boolean().notNull()
|
|
86
|
+
}));
|
|
87
|
+
status[Symbol.for("ponder:onchain")] = true;
|
|
88
|
+
var createClient = (baseUrl, { schema }) => {
|
|
89
|
+
const noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {
|
|
90
|
+
schema,
|
|
91
|
+
casing: "snake_case"
|
|
92
|
+
});
|
|
93
|
+
const dialect = noopDatabase.dialect;
|
|
94
|
+
const session = noopDatabase.session;
|
|
95
|
+
let sse;
|
|
96
|
+
let liveCount = 0;
|
|
97
|
+
const client = {
|
|
98
|
+
db: drizzle(
|
|
99
|
+
async (sql3, params, _, typings) => {
|
|
100
|
+
const builtQuery = { sql: sql3, params, typings };
|
|
101
|
+
const response = await fetch(getUrl(baseUrl, "db", builtQuery), {
|
|
102
|
+
method: "POST"
|
|
103
|
+
});
|
|
104
|
+
if (response.ok === false) {
|
|
105
|
+
const error = new Error(await response.text());
|
|
106
|
+
error.stack = void 0;
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
const result = await response.json();
|
|
110
|
+
return {
|
|
111
|
+
...result,
|
|
112
|
+
rows: result.rows.map((row) => Object.values(row))
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
{ schema, casing: "snake_case" }
|
|
116
|
+
),
|
|
117
|
+
live: (_query, onData, onError) => {
|
|
118
|
+
const query = _query(noopDatabase);
|
|
119
|
+
const sequel = typeof query === "string" ? sql.raw(query) : query.getSQL();
|
|
120
|
+
const builtQuery = dialect.sqlToQuery(sequel);
|
|
121
|
+
if (builtQuery.sql === 'select "chain_id", "block_number", "block_timestamp", "ready" from "_ponder_status"' && builtQuery.params.length === 0) {
|
|
122
|
+
const addEventListeners = () => {
|
|
123
|
+
sse.addEventListener("message", (event) => {
|
|
124
|
+
const data = JSON.parse(event.data);
|
|
125
|
+
if (data.status === "error") {
|
|
126
|
+
const error = new Error(data.error);
|
|
127
|
+
error.stack = void 0;
|
|
128
|
+
onError?.(error);
|
|
129
|
+
} else {
|
|
130
|
+
onData(data.result);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
sse.addEventListener("error", () => {
|
|
134
|
+
onError?.(new Error("server disconnected"));
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
liveCount++;
|
|
138
|
+
if (sse === void 0) {
|
|
139
|
+
getEventSource().then((SSE) => {
|
|
140
|
+
sse = new SSE(getUrl(baseUrl, "live"));
|
|
141
|
+
addEventListeners();
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
addEventListeners();
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
unsubscribe: () => {
|
|
148
|
+
if (--liveCount === 0)
|
|
149
|
+
sse?.close();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
} else {
|
|
153
|
+
return client.live(
|
|
154
|
+
(db) => db.select().from(status),
|
|
155
|
+
() => {
|
|
156
|
+
_query(client.db).then(onData).catch(onError);
|
|
157
|
+
},
|
|
158
|
+
onError
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
return client;
|
|
71
164
|
};
|
|
72
165
|
export {
|
|
73
166
|
alias,
|
|
@@ -105,7 +198,8 @@ export {
|
|
|
105
198
|
or,
|
|
106
199
|
relations,
|
|
107
200
|
setDatabaseSchema,
|
|
108
|
-
sql,
|
|
201
|
+
sql2 as sql,
|
|
202
|
+
status,
|
|
109
203
|
sum,
|
|
110
204
|
sumDistinct,
|
|
111
205
|
union,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/setDatabaseSchema.ts"],"sourcesContent":["import { type PgRemoteDatabase, drizzle } from \"drizzle-orm/pg-proxy\";\n\ntype Schema = { [name: string]: unknown };\n\ntype Prettify<T> = {\n [K in keyof T]: T[K];\n} & {};\n\
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/setDatabaseSchema.ts"],"sourcesContent":["import { type QueryWithTypings, type SQLWrapper, sql } from \"drizzle-orm\";\nimport { type PgDialect, type PgSession, pgTable } from \"drizzle-orm/pg-core\";\nimport { type PgRemoteDatabase, drizzle } from \"drizzle-orm/pg-proxy\";\n\nconst getEventSource = async () => {\n let SSE: typeof EventSource;\n if (typeof window === \"undefined\") {\n const undici = await import(/* webpackIgnore: true */ \"undici\");\n // @ts-ignore\n SSE = undici.EventSource;\n } else {\n SSE = EventSource;\n }\n\n return SSE;\n};\n\ntype Schema = { [name: string]: unknown };\n\ntype Prettify<T> = {\n [K in keyof T]: T[K];\n} & {};\n\ntype ClientDb<schema extends Schema = Schema> = Prettify<\n Omit<\n PgRemoteDatabase<schema>,\n | \"insert\"\n | \"update\"\n | \"delete\"\n | \"transaction\"\n | \"refreshMaterializedView\"\n | \"_\"\n >\n>;\n\nexport type Client<schema extends Schema = Schema> = {\n /** Query the database. */\n db: ClientDb<schema>;\n /** Subscribe to live updates. */\n live: <result>(\n query: (db: ClientDb<schema>) => Promise<result>,\n onData: (result: result) => void,\n onError?: (error: Error) => void,\n ) => {\n unsubscribe: () => void;\n };\n};\n\nconst getUrl = (\n baseUrl: string,\n method: \"live\" | \"db\" | \"status\",\n query?: QueryWithTypings,\n) => {\n const url = new URL(`${baseUrl}/client/${method}`);\n if (query) {\n url.searchParams.set(\"sql\", JSON.stringify(query));\n }\n return url;\n};\n\n/**\n * A table that tracks the status of each chain.\n *\n * @property {number} chainId - The chain ID.\n * @property {number} blockNumber - The closest-to-tip indexed block number.\n * @property {number} blockTimestamp - The closest-to-tip indexed block timestamp.\n * @property {boolean} ready - `true` if the chain has completed the historical backfill.\n */\nexport const status = pgTable(\"_ponder_status\", (t) => ({\n chainId: t.bigint({ mode: \"number\" }).primaryKey(),\n blockNumber: t.bigint({ mode: \"number\" }),\n blockTimestamp: t.bigint({ mode: \"number\" }),\n ready: t.boolean().notNull(),\n}));\n\n// @ts-ignore\nstatus[Symbol.for(\"ponder:onchain\")] = true;\n\n/**\n * Create a client for querying Ponder apps.\n *\n * @param baseUrl - The URL of the Ponder app.\n * @param schema - The schema of the Ponder app.\n *\n * @example\n * ```ts\n * import { createClient } from \"@ponder/client\";\n * import * as schema from \"../ponder.schema\";\n *\n * const client = createClient(\"https://...\", { schema });\n * ```\n */\nexport const createClient = <schema extends Schema>(\n baseUrl: string,\n { schema }: { schema: schema },\n): Client<schema> => {\n const noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {\n schema,\n casing: \"snake_case\",\n });\n\n // @ts-ignore\n const dialect: PgDialect = noopDatabase.dialect;\n // @ts-ignore\n const session: PgSession = noopDatabase.session;\n\n let sse: EventSource | undefined;\n let liveCount = 0;\n\n const client: Client<schema> = {\n db: drizzle(\n async (sql, params, _, typings) => {\n const builtQuery = { sql, params, typings };\n const response = await fetch(getUrl(baseUrl, \"db\", builtQuery), {\n method: \"POST\",\n });\n\n if (response.ok === false) {\n const error = new Error(await response.text());\n error.stack = undefined;\n throw error;\n }\n\n const result = await response.json();\n\n return {\n ...result,\n rows: result.rows.map((row: object) => Object.values(row)),\n };\n },\n { schema, casing: \"snake_case\" },\n ),\n live: (_query, onData, onError) => {\n // https://github.com/drizzle-team/drizzle-orm/blob/04c91434c7ac10aeb2923efd1d19a7ebf10ea9d4/drizzle-orm/src/pg-core/db.ts#L602-L621\n\n const query = _query(noopDatabase) as unknown as SQLWrapper | string;\n const sequel =\n typeof query === \"string\" ? sql.raw(query) : query.getSQL();\n const builtQuery = dialect.sqlToQuery(sequel);\n\n if (\n builtQuery.sql ===\n 'select \"chain_id\", \"block_number\", \"block_timestamp\", \"ready\" from \"_ponder_status\"' &&\n builtQuery.params.length === 0\n ) {\n const addEventListeners = () => {\n sse!.addEventListener(\"message\", (event) => {\n const data = JSON.parse(event.data) as\n | { status: \"success\"; result: unknown }\n | { status: \"error\"; error: string };\n\n if (data.status === \"error\") {\n const error = new Error(data.error);\n error.stack = undefined;\n onError?.(error);\n } else {\n // @ts-ignore\n onData(data.result);\n }\n });\n\n sse!.addEventListener(\"error\", () => {\n onError?.(new Error(\"server disconnected\"));\n });\n };\n\n liveCount++;\n if (sse === undefined) {\n getEventSource().then((SSE) => {\n sse = new SSE(getUrl(baseUrl, \"live\"));\n addEventListeners();\n });\n } else {\n addEventListeners();\n }\n\n return {\n unsubscribe: () => {\n if (--liveCount === 0) sse?.close();\n },\n };\n } else {\n return client.live(\n (db) => db.select().from(status),\n () => {\n _query(client.db).then(onData).catch(onError);\n },\n onError,\n );\n }\n },\n };\n\n return client;\n};\n\nexport {\n sql,\n eq,\n gt,\n gte,\n lt,\n lte,\n ne,\n isNull,\n isNotNull,\n inArray,\n notInArray,\n exists,\n notExists,\n between,\n notBetween,\n like,\n notIlike,\n not,\n asc,\n desc,\n and,\n or,\n count,\n countDistinct,\n avg,\n avgDistinct,\n sum,\n sumDistinct,\n max,\n min,\n relations,\n} from \"drizzle-orm\";\n\nexport {\n alias,\n union,\n unionAll,\n intersect,\n intersectAll,\n except,\n exceptAll,\n} from \"drizzle-orm/pg-core\";\n\nexport { setDatabaseSchema } from \"./setDatabaseSchema.js\";\n","import { Table, is } from \"drizzle-orm\";\n\nconst Schema = Symbol.for(\"drizzle:Schema\");\n\nexport const setDatabaseSchema = <T extends { [name: string]: unknown }>(\n schema: T,\n schemaName: string,\n): T => {\n for (const table of Object.values(schema)) {\n if (is(table, Table)) {\n // @ts-ignore\n table[Schema] = schemaName;\n }\n }\n return schema;\n};\n"],"mappings":"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","names":["sql","sql"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ponder/client",
|
|
3
|
-
"version": "0.9.0-next.
|
|
3
|
+
"version": "0.9.0-next.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"import": "./dist/index.js"
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
|
+
"browser": {
|
|
30
|
+
"undici": false
|
|
31
|
+
},
|
|
29
32
|
"peerDependencies": {
|
|
30
33
|
"typescript": ">=5.0.4"
|
|
31
34
|
},
|
|
@@ -35,7 +38,8 @@
|
|
|
35
38
|
}
|
|
36
39
|
},
|
|
37
40
|
"dependencies": {
|
|
38
|
-
"drizzle-orm": "0.36.4"
|
|
41
|
+
"drizzle-orm": "0.36.4",
|
|
42
|
+
"undici": "^7.2.0"
|
|
39
43
|
},
|
|
40
44
|
"devDependencies": {
|
|
41
45
|
"tsup": "^8.0.1"
|
package/src/index.ts
CHANGED
|
@@ -1,42 +1,197 @@
|
|
|
1
|
+
import { type QueryWithTypings, type SQLWrapper, sql } from "drizzle-orm";
|
|
2
|
+
import { type PgDialect, type PgSession, pgTable } from "drizzle-orm/pg-core";
|
|
1
3
|
import { type PgRemoteDatabase, drizzle } from "drizzle-orm/pg-proxy";
|
|
2
4
|
|
|
5
|
+
const getEventSource = async () => {
|
|
6
|
+
let SSE: typeof EventSource;
|
|
7
|
+
if (typeof window === "undefined") {
|
|
8
|
+
const undici = await import(/* webpackIgnore: true */ "undici");
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
SSE = undici.EventSource;
|
|
11
|
+
} else {
|
|
12
|
+
SSE = EventSource;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return SSE;
|
|
16
|
+
};
|
|
17
|
+
|
|
3
18
|
type Schema = { [name: string]: unknown };
|
|
4
19
|
|
|
5
20
|
type Prettify<T> = {
|
|
6
21
|
[K in keyof T]: T[K];
|
|
7
22
|
} & {};
|
|
8
23
|
|
|
24
|
+
type ClientDb<schema extends Schema = Schema> = Prettify<
|
|
25
|
+
Omit<
|
|
26
|
+
PgRemoteDatabase<schema>,
|
|
27
|
+
| "insert"
|
|
28
|
+
| "update"
|
|
29
|
+
| "delete"
|
|
30
|
+
| "transaction"
|
|
31
|
+
| "refreshMaterializedView"
|
|
32
|
+
| "_"
|
|
33
|
+
>
|
|
34
|
+
>;
|
|
35
|
+
|
|
9
36
|
export type Client<schema extends Schema = Schema> = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
37
|
+
/** Query the database. */
|
|
38
|
+
db: ClientDb<schema>;
|
|
39
|
+
/** Subscribe to live updates. */
|
|
40
|
+
live: <result>(
|
|
41
|
+
query: (db: ClientDb<schema>) => Promise<result>,
|
|
42
|
+
onData: (result: result) => void,
|
|
43
|
+
onError?: (error: Error) => void,
|
|
44
|
+
) => {
|
|
45
|
+
unsubscribe: () => void;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getUrl = (
|
|
50
|
+
baseUrl: string,
|
|
51
|
+
method: "live" | "db" | "status",
|
|
52
|
+
query?: QueryWithTypings,
|
|
53
|
+
) => {
|
|
54
|
+
const url = new URL(`${baseUrl}/client/${method}`);
|
|
55
|
+
if (query) {
|
|
56
|
+
url.searchParams.set("sql", JSON.stringify(query));
|
|
57
|
+
}
|
|
58
|
+
return url;
|
|
21
59
|
};
|
|
22
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A table that tracks the status of each chain.
|
|
63
|
+
*
|
|
64
|
+
* @property {number} chainId - The chain ID.
|
|
65
|
+
* @property {number} blockNumber - The closest-to-tip indexed block number.
|
|
66
|
+
* @property {number} blockTimestamp - The closest-to-tip indexed block timestamp.
|
|
67
|
+
* @property {boolean} ready - `true` if the chain has completed the historical backfill.
|
|
68
|
+
*/
|
|
69
|
+
export const status = pgTable("_ponder_status", (t) => ({
|
|
70
|
+
chainId: t.bigint({ mode: "number" }).primaryKey(),
|
|
71
|
+
blockNumber: t.bigint({ mode: "number" }),
|
|
72
|
+
blockTimestamp: t.bigint({ mode: "number" }),
|
|
73
|
+
ready: t.boolean().notNull(),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// @ts-ignore
|
|
77
|
+
status[Symbol.for("ponder:onchain")] = true;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create a client for querying Ponder apps.
|
|
81
|
+
*
|
|
82
|
+
* @param baseUrl - The URL of the Ponder app.
|
|
83
|
+
* @param schema - The schema of the Ponder app.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* import { createClient } from "@ponder/client";
|
|
88
|
+
* import * as schema from "../ponder.schema";
|
|
89
|
+
*
|
|
90
|
+
* const client = createClient("https://...", { schema });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
23
93
|
export const createClient = <schema extends Schema>(
|
|
24
|
-
|
|
94
|
+
baseUrl: string,
|
|
25
95
|
{ schema }: { schema: schema },
|
|
26
96
|
): Client<schema> => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
97
|
+
const noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {
|
|
98
|
+
schema,
|
|
99
|
+
casing: "snake_case",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// @ts-ignore
|
|
103
|
+
const dialect: PgDialect = noopDatabase.dialect;
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
const session: PgSession = noopDatabase.session;
|
|
106
|
+
|
|
107
|
+
let sse: EventSource | undefined;
|
|
108
|
+
let liveCount = 0;
|
|
109
|
+
|
|
110
|
+
const client: Client<schema> = {
|
|
111
|
+
db: drizzle(
|
|
112
|
+
async (sql, params, _, typings) => {
|
|
113
|
+
const builtQuery = { sql, params, typings };
|
|
114
|
+
const response = await fetch(getUrl(baseUrl, "db", builtQuery), {
|
|
115
|
+
method: "POST",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (response.ok === false) {
|
|
119
|
+
const error = new Error(await response.text());
|
|
120
|
+
error.stack = undefined;
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const result = await response.json();
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
...result,
|
|
128
|
+
rows: result.rows.map((row: object) => Object.values(row)),
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
{ schema, casing: "snake_case" },
|
|
132
|
+
),
|
|
133
|
+
live: (_query, onData, onError) => {
|
|
134
|
+
// https://github.com/drizzle-team/drizzle-orm/blob/04c91434c7ac10aeb2923efd1d19a7ebf10ea9d4/drizzle-orm/src/pg-core/db.ts#L602-L621
|
|
135
|
+
|
|
136
|
+
const query = _query(noopDatabase) as unknown as SQLWrapper | string;
|
|
137
|
+
const sequel =
|
|
138
|
+
typeof query === "string" ? sql.raw(query) : query.getSQL();
|
|
139
|
+
const builtQuery = dialect.sqlToQuery(sequel);
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
builtQuery.sql ===
|
|
143
|
+
'select "chain_id", "block_number", "block_timestamp", "ready" from "_ponder_status"' &&
|
|
144
|
+
builtQuery.params.length === 0
|
|
145
|
+
) {
|
|
146
|
+
const addEventListeners = () => {
|
|
147
|
+
sse!.addEventListener("message", (event) => {
|
|
148
|
+
const data = JSON.parse(event.data) as
|
|
149
|
+
| { status: "success"; result: unknown }
|
|
150
|
+
| { status: "error"; error: string };
|
|
151
|
+
|
|
152
|
+
if (data.status === "error") {
|
|
153
|
+
const error = new Error(data.error);
|
|
154
|
+
error.stack = undefined;
|
|
155
|
+
onError?.(error);
|
|
156
|
+
} else {
|
|
157
|
+
// @ts-ignore
|
|
158
|
+
onData(data.result);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
sse!.addEventListener("error", () => {
|
|
163
|
+
onError?.(new Error("server disconnected"));
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
liveCount++;
|
|
168
|
+
if (sse === undefined) {
|
|
169
|
+
getEventSource().then((SSE) => {
|
|
170
|
+
sse = new SSE(getUrl(baseUrl, "live"));
|
|
171
|
+
addEventListeners();
|
|
172
|
+
});
|
|
173
|
+
} else {
|
|
174
|
+
addEventListeners();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
unsubscribe: () => {
|
|
179
|
+
if (--liveCount === 0) sse?.close();
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
} else {
|
|
183
|
+
return client.live(
|
|
184
|
+
(db) => db.select().from(status),
|
|
185
|
+
() => {
|
|
186
|
+
_query(client.db).then(onData).catch(onError);
|
|
187
|
+
},
|
|
188
|
+
onError,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
35
191
|
},
|
|
36
|
-
|
|
37
|
-
);
|
|
192
|
+
};
|
|
38
193
|
|
|
39
|
-
return
|
|
194
|
+
return client;
|
|
40
195
|
};
|
|
41
196
|
|
|
42
197
|
export {
|