@ponder/client 0.0.0-kjs-early-indexes-20250522153448
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/LICENSE +21 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
- package/src/index.ts +220 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Cantrip, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { SQLWrapper, QueryWithTypings } from 'drizzle-orm';
|
|
2
|
+
export { and, asc, avg, avgDistinct, between, count, countDistinct, desc, eq, exists, gt, gte, ilike, inArray, isNotNull, isNull, like, lt, lte, max, min, ne, not, notBetween, notExists, notIlike, notInArray, notLike, or, relations, sql, sum, sumDistinct } from 'drizzle-orm';
|
|
3
|
+
import { PgRemoteDatabase } from 'drizzle-orm/pg-proxy';
|
|
4
|
+
export { alias, except, exceptAll, intersect, intersectAll, union, unionAll } from 'drizzle-orm/pg-core';
|
|
5
|
+
|
|
6
|
+
type Schema = {
|
|
7
|
+
[name: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
type Prettify<T> = {
|
|
10
|
+
[K in keyof T]: T[K];
|
|
11
|
+
} & {};
|
|
12
|
+
type Status = {
|
|
13
|
+
[chainName: string]: {
|
|
14
|
+
id: number;
|
|
15
|
+
block: {
|
|
16
|
+
number: number;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
type ClientDb<schema extends Schema = Schema> = Prettify<Omit<PgRemoteDatabase<schema>, "insert" | "update" | "delete" | "transaction" | "refreshMaterializedView" | "_">>;
|
|
22
|
+
type Client<schema extends Schema = Schema> = {
|
|
23
|
+
/** Query the database. */
|
|
24
|
+
db: ClientDb<schema>;
|
|
25
|
+
/**
|
|
26
|
+
* Subscribe to live updates.
|
|
27
|
+
*
|
|
28
|
+
* @param queryFn - The query to subscribe to.
|
|
29
|
+
* @param onData - The callback to call with each new query result
|
|
30
|
+
* @param onError - The callback to call when an error occurs.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { createClient } from "@ponder/client";
|
|
35
|
+
* import * as schema from "../ponder.schema";
|
|
36
|
+
*
|
|
37
|
+
* const client = createClient("https://.../sql", { schema });
|
|
38
|
+
*
|
|
39
|
+
* client.live(
|
|
40
|
+
* (db) => db.select().from(schema.account),
|
|
41
|
+
* (result) => console.log(result),
|
|
42
|
+
* (error) => console.error(error),
|
|
43
|
+
* );
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
live: <result>(queryFn: (db: ClientDb<schema>) => Promise<result>, onData: (result: result) => void, onError?: (error: Error) => void) => {
|
|
47
|
+
unsubscribe: () => void;
|
|
48
|
+
};
|
|
49
|
+
/** Get the status of all chains. */
|
|
50
|
+
getStatus: () => Promise<Status>;
|
|
51
|
+
};
|
|
52
|
+
declare const compileQuery: (query: SQLWrapper) => QueryWithTypings;
|
|
53
|
+
/**
|
|
54
|
+
* Create a client for querying Ponder apps.
|
|
55
|
+
*
|
|
56
|
+
* @param baseUrl - The URL of the Ponder app.
|
|
57
|
+
* @param schema - The schema of the Ponder app.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { createClient } from "@ponder/client";
|
|
62
|
+
* import * as schema from "../ponder.schema";
|
|
63
|
+
*
|
|
64
|
+
* const client = createClient("https://.../sql", { schema });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
declare const createClient: <schema extends Schema>(baseUrl: string, { schema }: {
|
|
68
|
+
schema: schema;
|
|
69
|
+
}) => Client<schema>;
|
|
70
|
+
|
|
71
|
+
export { type Client, type Status, compileQuery, createClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { drizzle } from "drizzle-orm/pg-proxy";
|
|
3
|
+
import { EventSource } from "eventsource";
|
|
4
|
+
import superjson from "superjson";
|
|
5
|
+
import {
|
|
6
|
+
sql,
|
|
7
|
+
eq,
|
|
8
|
+
gt,
|
|
9
|
+
gte,
|
|
10
|
+
lt,
|
|
11
|
+
lte,
|
|
12
|
+
ne,
|
|
13
|
+
isNull,
|
|
14
|
+
isNotNull,
|
|
15
|
+
inArray,
|
|
16
|
+
notInArray,
|
|
17
|
+
exists,
|
|
18
|
+
notExists,
|
|
19
|
+
between,
|
|
20
|
+
notBetween,
|
|
21
|
+
like,
|
|
22
|
+
notLike,
|
|
23
|
+
ilike,
|
|
24
|
+
notIlike,
|
|
25
|
+
not,
|
|
26
|
+
asc,
|
|
27
|
+
desc,
|
|
28
|
+
and,
|
|
29
|
+
or,
|
|
30
|
+
count,
|
|
31
|
+
countDistinct,
|
|
32
|
+
avg,
|
|
33
|
+
avgDistinct,
|
|
34
|
+
sum,
|
|
35
|
+
sumDistinct,
|
|
36
|
+
max,
|
|
37
|
+
min,
|
|
38
|
+
relations
|
|
39
|
+
} from "drizzle-orm";
|
|
40
|
+
import {
|
|
41
|
+
alias,
|
|
42
|
+
union,
|
|
43
|
+
unionAll,
|
|
44
|
+
intersect,
|
|
45
|
+
intersectAll,
|
|
46
|
+
except,
|
|
47
|
+
exceptAll
|
|
48
|
+
} from "drizzle-orm/pg-core";
|
|
49
|
+
var getUrl = (baseUrl, method, query) => {
|
|
50
|
+
const url = new URL(baseUrl);
|
|
51
|
+
url.pathname = `${url.pathname}/${method}`;
|
|
52
|
+
if (query) {
|
|
53
|
+
url.searchParams.set("sql", superjson.stringify(query));
|
|
54
|
+
}
|
|
55
|
+
return url;
|
|
56
|
+
};
|
|
57
|
+
var noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {
|
|
58
|
+
casing: "snake_case"
|
|
59
|
+
});
|
|
60
|
+
var dialect = noopDatabase.dialect;
|
|
61
|
+
var compileQuery = (query) => {
|
|
62
|
+
return dialect.sqlToQuery(query.getSQL());
|
|
63
|
+
};
|
|
64
|
+
var createClient = (baseUrl, { schema }) => {
|
|
65
|
+
let sse;
|
|
66
|
+
let liveCount = 0;
|
|
67
|
+
const client = {
|
|
68
|
+
db: drizzle(
|
|
69
|
+
async (sql2, params, _, typings) => {
|
|
70
|
+
const builtQuery = { sql: sql2, params, typings };
|
|
71
|
+
const response = await fetch(getUrl(baseUrl, "db", builtQuery), {
|
|
72
|
+
method: "POST"
|
|
73
|
+
});
|
|
74
|
+
if (response.ok === false) {
|
|
75
|
+
const error = new Error(await response.text());
|
|
76
|
+
error.stack = void 0;
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
const result = await response.json();
|
|
80
|
+
return {
|
|
81
|
+
...result,
|
|
82
|
+
rows: result.rows.map((row) => Object.values(row))
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
{ schema, casing: "snake_case" }
|
|
86
|
+
),
|
|
87
|
+
live: (queryFn, onData, onError) => {
|
|
88
|
+
if (sse === void 0) {
|
|
89
|
+
sse = new EventSource(getUrl(baseUrl, "live"));
|
|
90
|
+
}
|
|
91
|
+
const onDataListener = (_event) => {
|
|
92
|
+
queryFn(client.db).then(onData).catch(onError);
|
|
93
|
+
};
|
|
94
|
+
const onErrorListener = (_event) => {
|
|
95
|
+
onError?.(new Error("server disconnected"));
|
|
96
|
+
};
|
|
97
|
+
sse?.addEventListener("message", onDataListener);
|
|
98
|
+
sse?.addEventListener("error", onErrorListener);
|
|
99
|
+
liveCount = liveCount + 1;
|
|
100
|
+
return {
|
|
101
|
+
unsubscribe: () => {
|
|
102
|
+
sse?.removeEventListener("message", onDataListener);
|
|
103
|
+
sse?.removeEventListener("error", onErrorListener);
|
|
104
|
+
liveCount = liveCount - 1;
|
|
105
|
+
if (liveCount === 0) {
|
|
106
|
+
sse?.close();
|
|
107
|
+
sse = void 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
getStatus: async () => {
|
|
113
|
+
const response = await fetch(getUrl(baseUrl, "status"), {
|
|
114
|
+
method: "POST"
|
|
115
|
+
});
|
|
116
|
+
return response.json();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
return client;
|
|
120
|
+
};
|
|
121
|
+
export {
|
|
122
|
+
alias,
|
|
123
|
+
and,
|
|
124
|
+
asc,
|
|
125
|
+
avg,
|
|
126
|
+
avgDistinct,
|
|
127
|
+
between,
|
|
128
|
+
compileQuery,
|
|
129
|
+
count,
|
|
130
|
+
countDistinct,
|
|
131
|
+
createClient,
|
|
132
|
+
desc,
|
|
133
|
+
eq,
|
|
134
|
+
except,
|
|
135
|
+
exceptAll,
|
|
136
|
+
exists,
|
|
137
|
+
gt,
|
|
138
|
+
gte,
|
|
139
|
+
ilike,
|
|
140
|
+
inArray,
|
|
141
|
+
intersect,
|
|
142
|
+
intersectAll,
|
|
143
|
+
isNotNull,
|
|
144
|
+
isNull,
|
|
145
|
+
like,
|
|
146
|
+
lt,
|
|
147
|
+
lte,
|
|
148
|
+
max,
|
|
149
|
+
min,
|
|
150
|
+
ne,
|
|
151
|
+
not,
|
|
152
|
+
notBetween,
|
|
153
|
+
notExists,
|
|
154
|
+
notIlike,
|
|
155
|
+
notInArray,
|
|
156
|
+
notLike,
|
|
157
|
+
or,
|
|
158
|
+
relations,
|
|
159
|
+
sql,
|
|
160
|
+
sum,
|
|
161
|
+
sumDistinct,
|
|
162
|
+
union,
|
|
163
|
+
unionAll
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { QueryWithTypings, SQLWrapper } from \"drizzle-orm\";\nimport type { PgDialect } from \"drizzle-orm/pg-core\";\nimport { type PgRemoteDatabase, drizzle } from \"drizzle-orm/pg-proxy\";\nimport { EventSource } from \"eventsource\";\nimport superjson from \"superjson\";\n\ntype Schema = { [name: string]: unknown };\n\ntype Prettify<T> = {\n [K in keyof T]: T[K];\n} & {};\n\nexport type Status = {\n [chainName: string]: {\n id: number;\n block: { number: number; timestamp: number };\n };\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 /**\n * Subscribe to live updates.\n *\n * @param queryFn - The query to subscribe to.\n * @param onData - The callback to call with each new query result\n * @param onError - The callback to call when an error occurs.\n *\n * @example\n * ```ts\n * import { createClient } from \"@ponder/client\";\n * import * as schema from \"../ponder.schema\";\n *\n * const client = createClient(\"https://.../sql\", { schema });\n *\n * client.live(\n * (db) => db.select().from(schema.account),\n * (result) => console.log(result),\n * (error) => console.error(error),\n * );\n * ```\n */\n live: <result>(\n queryFn: (db: ClientDb<schema>) => Promise<result>,\n onData: (result: result) => void,\n onError?: (error: Error) => void,\n ) => {\n unsubscribe: () => void;\n };\n /** Get the status of all chains. */\n getStatus: () => Promise<Status>;\n};\n\nconst getUrl = (\n baseUrl: string,\n method: \"live\" | \"db\" | \"status\",\n query?: QueryWithTypings,\n) => {\n const url = new URL(baseUrl);\n url.pathname = `${url.pathname}/${method}`;\n if (query) {\n url.searchParams.set(\"sql\", superjson.stringify(query));\n }\n return url;\n};\n\nconst noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {\n casing: \"snake_case\",\n});\n\n// @ts-ignore\nconst dialect: PgDialect = noopDatabase.dialect;\n\nexport const compileQuery = (query: SQLWrapper) => {\n return dialect.sqlToQuery(query.getSQL());\n};\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://.../sql\", { schema });\n * ```\n */\nexport const createClient = <schema extends Schema>(\n baseUrl: string,\n { schema }: { schema: schema },\n): Client<schema> => {\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: (queryFn, onData, onError) => {\n if (sse === undefined) {\n sse = new EventSource(getUrl(baseUrl, \"live\"));\n }\n\n const onDataListener = (_event: MessageEvent) => {\n queryFn(client.db).then(onData).catch(onError);\n };\n\n const onErrorListener = (_event: MessageEvent) => {\n onError?.(new Error(\"server disconnected\"));\n };\n\n sse?.addEventListener(\"message\", onDataListener);\n sse?.addEventListener(\"error\", onErrorListener);\n liveCount = liveCount + 1;\n\n return {\n unsubscribe: () => {\n sse?.removeEventListener(\"message\", onDataListener);\n sse?.removeEventListener(\"error\", onErrorListener);\n liveCount = liveCount - 1;\n if (liveCount === 0) {\n sse?.close();\n sse = undefined;\n }\n },\n };\n },\n getStatus: async () => {\n const response = await fetch(getUrl(baseUrl, \"status\"), {\n method: \"POST\",\n });\n\n return response.json();\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 notLike,\n ilike,\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"],"mappings":"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","names":["sql"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ponder/client",
|
|
3
|
+
"version": "0.0.0-kjs-early-indexes-20250522153448",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/ponder-sh/ponder",
|
|
11
|
+
"directory": "packages/client"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/**/*.ts",
|
|
16
|
+
"!src/**/*.test.ts",
|
|
17
|
+
"!src/**/*.test-d.ts",
|
|
18
|
+
"!src/_test/**/*"
|
|
19
|
+
],
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"typings": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"typescript": ">=5.0.4"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"typescript": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"drizzle-orm": "0.41.0",
|
|
39
|
+
"eventsource": "^3.0.5",
|
|
40
|
+
"superjson": "^2.2.2"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"tsup": "^8.0.1"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"test": "vitest",
|
|
48
|
+
"test:typecheck": "vitest --typecheck.only",
|
|
49
|
+
"typecheck": "tsc --noEmit"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { QueryWithTypings, SQLWrapper } from "drizzle-orm";
|
|
2
|
+
import type { PgDialect } from "drizzle-orm/pg-core";
|
|
3
|
+
import { type PgRemoteDatabase, drizzle } from "drizzle-orm/pg-proxy";
|
|
4
|
+
import { EventSource } from "eventsource";
|
|
5
|
+
import superjson from "superjson";
|
|
6
|
+
|
|
7
|
+
type Schema = { [name: string]: unknown };
|
|
8
|
+
|
|
9
|
+
type Prettify<T> = {
|
|
10
|
+
[K in keyof T]: T[K];
|
|
11
|
+
} & {};
|
|
12
|
+
|
|
13
|
+
export type Status = {
|
|
14
|
+
[chainName: string]: {
|
|
15
|
+
id: number;
|
|
16
|
+
block: { number: number; timestamp: number };
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ClientDb<schema extends Schema = Schema> = Prettify<
|
|
21
|
+
Omit<
|
|
22
|
+
PgRemoteDatabase<schema>,
|
|
23
|
+
| "insert"
|
|
24
|
+
| "update"
|
|
25
|
+
| "delete"
|
|
26
|
+
| "transaction"
|
|
27
|
+
| "refreshMaterializedView"
|
|
28
|
+
| "_"
|
|
29
|
+
>
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
export type Client<schema extends Schema = Schema> = {
|
|
33
|
+
/** Query the database. */
|
|
34
|
+
db: ClientDb<schema>;
|
|
35
|
+
/**
|
|
36
|
+
* Subscribe to live updates.
|
|
37
|
+
*
|
|
38
|
+
* @param queryFn - The query to subscribe to.
|
|
39
|
+
* @param onData - The callback to call with each new query result
|
|
40
|
+
* @param onError - The callback to call when an error occurs.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { createClient } from "@ponder/client";
|
|
45
|
+
* import * as schema from "../ponder.schema";
|
|
46
|
+
*
|
|
47
|
+
* const client = createClient("https://.../sql", { schema });
|
|
48
|
+
*
|
|
49
|
+
* client.live(
|
|
50
|
+
* (db) => db.select().from(schema.account),
|
|
51
|
+
* (result) => console.log(result),
|
|
52
|
+
* (error) => console.error(error),
|
|
53
|
+
* );
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
live: <result>(
|
|
57
|
+
queryFn: (db: ClientDb<schema>) => Promise<result>,
|
|
58
|
+
onData: (result: result) => void,
|
|
59
|
+
onError?: (error: Error) => void,
|
|
60
|
+
) => {
|
|
61
|
+
unsubscribe: () => void;
|
|
62
|
+
};
|
|
63
|
+
/** Get the status of all chains. */
|
|
64
|
+
getStatus: () => Promise<Status>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const getUrl = (
|
|
68
|
+
baseUrl: string,
|
|
69
|
+
method: "live" | "db" | "status",
|
|
70
|
+
query?: QueryWithTypings,
|
|
71
|
+
) => {
|
|
72
|
+
const url = new URL(baseUrl);
|
|
73
|
+
url.pathname = `${url.pathname}/${method}`;
|
|
74
|
+
if (query) {
|
|
75
|
+
url.searchParams.set("sql", superjson.stringify(query));
|
|
76
|
+
}
|
|
77
|
+
return url;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {
|
|
81
|
+
casing: "snake_case",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// @ts-ignore
|
|
85
|
+
const dialect: PgDialect = noopDatabase.dialect;
|
|
86
|
+
|
|
87
|
+
export const compileQuery = (query: SQLWrapper) => {
|
|
88
|
+
return dialect.sqlToQuery(query.getSQL());
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a client for querying Ponder apps.
|
|
93
|
+
*
|
|
94
|
+
* @param baseUrl - The URL of the Ponder app.
|
|
95
|
+
* @param schema - The schema of the Ponder app.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* import { createClient } from "@ponder/client";
|
|
100
|
+
* import * as schema from "../ponder.schema";
|
|
101
|
+
*
|
|
102
|
+
* const client = createClient("https://.../sql", { schema });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export const createClient = <schema extends Schema>(
|
|
106
|
+
baseUrl: string,
|
|
107
|
+
{ schema }: { schema: schema },
|
|
108
|
+
): Client<schema> => {
|
|
109
|
+
let sse: EventSource | undefined;
|
|
110
|
+
let liveCount = 0;
|
|
111
|
+
|
|
112
|
+
const client: Client<schema> = {
|
|
113
|
+
db: drizzle(
|
|
114
|
+
async (sql, params, _, typings) => {
|
|
115
|
+
const builtQuery = { sql, params, typings };
|
|
116
|
+
const response = await fetch(getUrl(baseUrl, "db", builtQuery), {
|
|
117
|
+
method: "POST",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (response.ok === false) {
|
|
121
|
+
const error = new Error(await response.text());
|
|
122
|
+
error.stack = undefined;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = await response.json();
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...result,
|
|
130
|
+
rows: result.rows.map((row: object) => Object.values(row)),
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
{ schema, casing: "snake_case" },
|
|
134
|
+
),
|
|
135
|
+
live: (queryFn, onData, onError) => {
|
|
136
|
+
if (sse === undefined) {
|
|
137
|
+
sse = new EventSource(getUrl(baseUrl, "live"));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const onDataListener = (_event: MessageEvent) => {
|
|
141
|
+
queryFn(client.db).then(onData).catch(onError);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const onErrorListener = (_event: MessageEvent) => {
|
|
145
|
+
onError?.(new Error("server disconnected"));
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
sse?.addEventListener("message", onDataListener);
|
|
149
|
+
sse?.addEventListener("error", onErrorListener);
|
|
150
|
+
liveCount = liveCount + 1;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
unsubscribe: () => {
|
|
154
|
+
sse?.removeEventListener("message", onDataListener);
|
|
155
|
+
sse?.removeEventListener("error", onErrorListener);
|
|
156
|
+
liveCount = liveCount - 1;
|
|
157
|
+
if (liveCount === 0) {
|
|
158
|
+
sse?.close();
|
|
159
|
+
sse = undefined;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
getStatus: async () => {
|
|
165
|
+
const response = await fetch(getUrl(baseUrl, "status"), {
|
|
166
|
+
method: "POST",
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return response.json();
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return client;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export {
|
|
177
|
+
sql,
|
|
178
|
+
eq,
|
|
179
|
+
gt,
|
|
180
|
+
gte,
|
|
181
|
+
lt,
|
|
182
|
+
lte,
|
|
183
|
+
ne,
|
|
184
|
+
isNull,
|
|
185
|
+
isNotNull,
|
|
186
|
+
inArray,
|
|
187
|
+
notInArray,
|
|
188
|
+
exists,
|
|
189
|
+
notExists,
|
|
190
|
+
between,
|
|
191
|
+
notBetween,
|
|
192
|
+
like,
|
|
193
|
+
notLike,
|
|
194
|
+
ilike,
|
|
195
|
+
notIlike,
|
|
196
|
+
not,
|
|
197
|
+
asc,
|
|
198
|
+
desc,
|
|
199
|
+
and,
|
|
200
|
+
or,
|
|
201
|
+
count,
|
|
202
|
+
countDistinct,
|
|
203
|
+
avg,
|
|
204
|
+
avgDistinct,
|
|
205
|
+
sum,
|
|
206
|
+
sumDistinct,
|
|
207
|
+
max,
|
|
208
|
+
min,
|
|
209
|
+
relations,
|
|
210
|
+
} from "drizzle-orm";
|
|
211
|
+
|
|
212
|
+
export {
|
|
213
|
+
alias,
|
|
214
|
+
union,
|
|
215
|
+
unionAll,
|
|
216
|
+
intersect,
|
|
217
|
+
intersectAll,
|
|
218
|
+
except,
|
|
219
|
+
exceptAll,
|
|
220
|
+
} from "drizzle-orm/pg-core";
|