@ponder/client 0.9.0-next.1 → 0.9.0-next.10

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 CHANGED
@@ -1,22 +1,56 @@
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';
1
3
  import { PgRemoteDatabase } from 'drizzle-orm/pg-proxy';
2
- 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
4
  export { alias, except, exceptAll, intersect, intersectAll, union, unionAll } from 'drizzle-orm/pg-core';
4
5
 
5
- declare const setDatabaseSchema: <T extends {
6
- [name: string]: unknown;
7
- }>(schema: T, schemaName: string) => T;
8
-
9
- type Schema = {
10
- [name: string]: unknown;
11
- };
12
6
  type Prettify<T> = {
13
7
  [K in keyof T]: T[K];
14
8
  } & {};
9
+ type Status = {
10
+ [network: string]: {
11
+ block: {
12
+ number: number;
13
+ timestamp: number;
14
+ } | null;
15
+ ready: boolean;
16
+ };
17
+ };
18
+ type ClientDb<schema extends Schema = Schema> = Prettify<Omit<PgRemoteDatabase<schema>, "insert" | "update" | "delete" | "transaction" | "refreshMaterializedView" | "_">>;
15
19
  type Client<schema extends Schema = Schema> = {
16
- db: Prettify<Omit<PgRemoteDatabase<schema>, "insert" | "update" | "delete" | "transaction" | "refreshMaterializedView" | "_">>;
20
+ /** Query the database. */
21
+ db: ClientDb<schema>;
22
+ /** Subscribe to live updates. */
23
+ live: <result>(queryFn: (db: ClientDb<schema>) => Promise<result>, onData: (result: result) => void, onError?: (error: Error) => void) => {
24
+ unsubscribe: () => void;
25
+ };
26
+ /** Get the status of all chains. */
27
+ getStatus: () => Promise<Status>;
17
28
  };
18
- declare const createClient: <schema extends Schema>(url: string, { schema }: {
29
+ declare const compileQuery: (query: SQLWrapper | string) => QueryWithTypings;
30
+ /**
31
+ * Create a client for querying Ponder apps.
32
+ *
33
+ * @param baseUrl - The URL of the Ponder app.
34
+ * @param schema - The schema of the Ponder app.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import { createClient } from "@ponder/client";
39
+ * import * as schema from "../ponder.schema";
40
+ *
41
+ * const client = createClient("https://...", { schema });
42
+ * ```
43
+ */
44
+ declare const createClient: <schema extends Schema>(baseUrl: string, { schema }: {
19
45
  schema: schema;
20
46
  }) => Client<schema>;
21
47
 
22
- export { type Client, createClient, setDatabaseSchema };
48
+ type Schema = {
49
+ [name: string]: unknown;
50
+ };
51
+ declare const Schema: unique symbol;
52
+ declare const setDatabaseSchema: <T extends {
53
+ [name: string]: unknown;
54
+ }>(schema: T, schemaName: string) => T;
55
+
56
+ export { type Client, type Status, compileQuery, createClient, setDatabaseSchema };
package/dist/index.js CHANGED
@@ -1,7 +1,12 @@
1
1
  // src/index.ts
2
+ import {
3
+ Table,
4
+ is,
5
+ sql
6
+ } from "drizzle-orm";
2
7
  import { drizzle } from "drizzle-orm/pg-proxy";
3
8
  import {
4
- sql,
9
+ sql as sql2,
5
10
  eq,
6
11
  gt,
7
12
  gte,
@@ -17,6 +22,8 @@ import {
17
22
  between,
18
23
  notBetween,
19
24
  like,
25
+ notLike,
26
+ ilike,
20
27
  notIlike,
21
28
  not,
22
29
  asc,
@@ -42,9 +49,98 @@ import {
42
49
  except,
43
50
  exceptAll
44
51
  } from "drizzle-orm/pg-core";
45
-
46
- // src/setDatabaseSchema.ts
47
- import { Table, is } from "drizzle-orm";
52
+ var getEventSource = async () => {
53
+ let SSE;
54
+ if (typeof window === "undefined") {
55
+ const undici = await import(
56
+ /* webpackIgnore: true */
57
+ "undici"
58
+ );
59
+ SSE = undici.EventSource;
60
+ } else {
61
+ SSE = EventSource;
62
+ }
63
+ return SSE;
64
+ };
65
+ var getUrl = (baseUrl, method, query) => {
66
+ const url = new URL(`${baseUrl}/client/${method}`);
67
+ if (query) {
68
+ url.searchParams.set("sql", JSON.stringify(query));
69
+ }
70
+ return url;
71
+ };
72
+ var noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {
73
+ casing: "snake_case"
74
+ });
75
+ var dialect = noopDatabase.dialect;
76
+ var compileQuery = (query) => {
77
+ const sequel = typeof query === "string" ? sql.raw(query) : query.getSQL();
78
+ return dialect.sqlToQuery(sequel);
79
+ };
80
+ var createClient = (baseUrl, { schema }) => {
81
+ let sse;
82
+ let liveCount = 0;
83
+ const client = {
84
+ db: drizzle(
85
+ async (sql3, params, _, typings) => {
86
+ const builtQuery = { sql: sql3, params, typings };
87
+ const response = await fetch(getUrl(baseUrl, "db", builtQuery), {
88
+ method: "POST"
89
+ });
90
+ if (response.ok === false) {
91
+ const error = new Error(await response.text());
92
+ error.stack = void 0;
93
+ throw error;
94
+ }
95
+ const result = await response.json();
96
+ return {
97
+ ...result,
98
+ rows: result.rows.map((row) => Object.values(row))
99
+ };
100
+ },
101
+ { schema, casing: "snake_case" }
102
+ ),
103
+ live: (queryFn, onData, onError) => {
104
+ const addEventListeners = () => {
105
+ sse.addEventListener("message", (event) => {
106
+ const data = JSON.parse(event.data);
107
+ if (data.status === "error") {
108
+ const error = new Error(data.error);
109
+ error.stack = void 0;
110
+ onError?.(error);
111
+ } else {
112
+ queryFn(client.db).then(onData).catch(onError);
113
+ }
114
+ });
115
+ sse.addEventListener("error", () => {
116
+ onError?.(new Error("server disconnected"));
117
+ });
118
+ };
119
+ liveCount++;
120
+ if (sse === void 0) {
121
+ getEventSource().then((SSE) => {
122
+ sse = new SSE(getUrl(baseUrl, "live"));
123
+ addEventListeners();
124
+ });
125
+ } else {
126
+ addEventListeners();
127
+ }
128
+ return {
129
+ unsubscribe: () => {
130
+ if (--liveCount === 0)
131
+ sse?.close();
132
+ }
133
+ };
134
+ },
135
+ getStatus: async () => {
136
+ const response = await fetch(getUrl(baseUrl, "status"), {
137
+ method: "POST"
138
+ });
139
+ return response.json();
140
+ }
141
+ };
142
+ return client;
143
+ };
48
144
  var Schema = Symbol.for("drizzle:Schema");
49
145
  var setDatabaseSchema = (schema, schemaName) => {
50
146
  for (const table of Object.values(schema)) {
@@ -54,21 +150,6 @@ var setDatabaseSchema = (schema, schemaName) => {
54
150
  }
55
151
  return schema;
56
152
  };
57
-
58
- // src/index.ts
59
- var createClient = (url, { schema }) => {
60
- const db = drizzle(
61
- async (sql2, params, method, typings) => {
62
- const result = await fetch(`${url}/client`, {
63
- method: "POST",
64
- body: JSON.stringify({ sql: sql2, params, method, typings })
65
- });
66
- return await result.json();
67
- },
68
- { schema, casing: "snake_case" }
69
- );
70
- return { db };
71
- };
72
153
  export {
73
154
  alias,
74
155
  and,
@@ -76,6 +157,7 @@ export {
76
157
  avg,
77
158
  avgDistinct,
78
159
  between,
160
+ compileQuery,
79
161
  count,
80
162
  countDistinct,
81
163
  createClient,
@@ -86,6 +168,7 @@ export {
86
168
  exists,
87
169
  gt,
88
170
  gte,
171
+ ilike,
89
172
  inArray,
90
173
  intersect,
91
174
  intersectAll,
@@ -102,10 +185,11 @@ export {
102
185
  notExists,
103
186
  notIlike,
104
187
  notInArray,
188
+ notLike,
105
189
  or,
106
190
  relations,
107
191
  setDatabaseSchema,
108
- sql,
192
+ sql2 as sql,
109
193
  sum,
110
194
  sumDistinct,
111
195
  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\nexport type Client<schema extends Schema = Schema> = {\n db: Prettify<\n Omit<\n PgRemoteDatabase<schema>,\n | \"insert\"\n | \"update\"\n | \"delete\"\n | \"transaction\"\n | \"refreshMaterializedView\"\n | \"_\"\n >\n >;\n};\n\nexport const createClient = <schema extends Schema>(\n url: string,\n { schema }: { schema: schema },\n): Client<schema> => {\n const db = drizzle(\n async (sql, params, method, typings) => {\n const result = await fetch(`${url}/client`, {\n method: \"POST\",\n body: JSON.stringify({ sql, params, method, typings }),\n });\n\n return await result.json();\n },\n { schema, casing: \"snake_case\" },\n );\n\n return { db };\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":";AAAA,SAAgC,eAAe;AAyC/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACnFP,SAAS,OAAO,UAAU;AAE1B,IAAM,SAAS,OAAO,IAAI,gBAAgB;AAEnC,IAAM,oBAAoB,CAC/B,QACA,eACM;AACN,aAAW,SAAS,OAAO,OAAO,MAAM,GAAG;AACzC,QAAI,GAAG,OAAO,KAAK,GAAG;AAEpB,YAAM,MAAM,IAAI;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AACT;;;ADOO,IAAM,eAAe,CAC1B,KACA,EAAE,OAAO,MACU;AACnB,QAAM,KAAK;AAAA,IACT,OAAOA,MAAK,QAAQ,QAAQ,YAAY;AACtC,YAAM,SAAS,MAAM,MAAM,GAAG,GAAG,WAAW;AAAA,QAC1C,QAAQ;AAAA,QACR,MAAM,KAAK,UAAU,EAAE,KAAAA,MAAK,QAAQ,QAAQ,QAAQ,CAAC;AAAA,MACvD,CAAC;AAED,aAAO,MAAM,OAAO,KAAK;AAAA,IAC3B;AAAA,IACA,EAAE,QAAQ,QAAQ,aAAa;AAAA,EACjC;AAEA,SAAO,EAAE,GAAG;AACd;","names":["sql"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n type QueryWithTypings,\n type SQLWrapper,\n Table,\n is,\n sql,\n} from \"drizzle-orm\";\nimport type { PgDialect } 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\nexport type Status = {\n [network: string]: {\n block: { number: number; timestamp: number } | null;\n ready: boolean;\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 /** Subscribe to live updates. */\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}/client/${method}`);\n if (query) {\n url.searchParams.set(\"sql\", JSON.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 | string) => {\n const sequel = typeof query === \"string\" ? sql.raw(query) : query.getSQL();\n return dialect.sqlToQuery(sequel);\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://...\", { 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 // https://github.com/drizzle-team/drizzle-orm/blob/04c91434c7ac10aeb2923efd1d19a7ebf10ea9d4/drizzle-orm/src/pg-core/db.ts#L602-L621\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 queryFn(client.db).then(onData).catch(onError);\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 },\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\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":";AAAA;AAAA,EAGE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAgC,eAAe;AA8K/C;AAAA,EACE,OAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAxNP,IAAM,iBAAiB,YAAY;AACjC,MAAI;AACJ,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,SAAS,MAAM;AAAA;AAAA,MAAiC;AAAA,IAAQ;AAE9D,UAAM,OAAO;AAAA,EACf,OAAO;AACL,UAAM;AAAA,EACR;AAEA,SAAO;AACT;AA0CA,IAAM,SAAS,CACb,SACA,QACA,UACG;AACH,QAAM,MAAM,IAAI,IAAI,GAAG,OAAO,WAAW,MAAM,EAAE;AACjD,MAAI,OAAO;AACT,QAAI,aAAa,IAAI,OAAO,KAAK,UAAU,KAAK,CAAC;AAAA,EACnD;AACA,SAAO;AACT;AAEA,IAAM,eAAe,QAAQ,MAAM,QAAQ,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG;AAAA,EAChE,QAAQ;AACV,CAAC;AAGD,IAAM,UAAqB,aAAa;AAEjC,IAAM,eAAe,CAAC,UAA+B;AAC1D,QAAM,SAAS,OAAO,UAAU,WAAW,IAAI,IAAI,KAAK,IAAI,MAAM,OAAO;AACzE,SAAO,QAAQ,WAAW,MAAM;AAClC;AAgBO,IAAM,eAAe,CAC1B,SACA,EAAE,OAAO,MACU;AACnB,MAAI;AACJ,MAAI,YAAY;AAEhB,QAAM,SAAyB;AAAA,IAC7B,IAAI;AAAA,MACF,OAAOA,MAAK,QAAQ,GAAG,YAAY;AACjC,cAAM,aAAa,EAAE,KAAAA,MAAK,QAAQ,QAAQ;AAC1C,cAAM,WAAW,MAAM,MAAM,OAAO,SAAS,MAAM,UAAU,GAAG;AAAA,UAC9D,QAAQ;AAAA,QACV,CAAC;AAED,YAAI,SAAS,OAAO,OAAO;AACzB,gBAAM,QAAQ,IAAI,MAAM,MAAM,SAAS,KAAK,CAAC;AAC7C,gBAAM,QAAQ;AACd,gBAAM;AAAA,QACR;AAEA,cAAM,SAAS,MAAM,SAAS,KAAK;AAEnC,eAAO;AAAA,UACL,GAAG;AAAA,UACH,MAAM,OAAO,KAAK,IAAI,CAAC,QAAgB,OAAO,OAAO,GAAG,CAAC;AAAA,QAC3D;AAAA,MACF;AAAA,MACA,EAAE,QAAQ,QAAQ,aAAa;AAAA,IACjC;AAAA,IACA,MAAM,CAAC,SAAS,QAAQ,YAAY;AAGlC,YAAM,oBAAoB,MAAM;AAC9B,YAAK,iBAAiB,WAAW,CAAC,UAAU;AAC1C,gBAAM,OAAO,KAAK,MAAM,MAAM,IAAI;AAIlC,cAAI,KAAK,WAAW,SAAS;AAC3B,kBAAM,QAAQ,IAAI,MAAM,KAAK,KAAK;AAClC,kBAAM,QAAQ;AACd,sBAAU,KAAK;AAAA,UACjB,OAAO;AACL,oBAAQ,OAAO,EAAE,EAAE,KAAK,MAAM,EAAE,MAAM,OAAO;AAAA,UAC/C;AAAA,QACF,CAAC;AAED,YAAK,iBAAiB,SAAS,MAAM;AACnC,oBAAU,IAAI,MAAM,qBAAqB,CAAC;AAAA,QAC5C,CAAC;AAAA,MACH;AAEA;AACA,UAAI,QAAQ,QAAW;AACrB,uBAAe,EAAE,KAAK,CAAC,QAAQ;AAC7B,gBAAM,IAAI,IAAI,OAAO,SAAS,MAAM,CAAC;AACrC,4BAAkB;AAAA,QACpB,CAAC;AAAA,MACH,OAAO;AACL,0BAAkB;AAAA,MACpB;AAEA,aAAO;AAAA,QACL,aAAa,MAAM;AACjB,cAAI,EAAE,cAAc;AAAG,iBAAK,MAAM;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW,YAAY;AACrB,YAAM,WAAW,MAAM,MAAM,OAAO,SAAS,QAAQ,GAAG;AAAA,QACtD,QAAQ;AAAA,MACV,CAAC;AAED,aAAO,SAAS,KAAK;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AACT;AAgDA,IAAM,SAAS,OAAO,IAAI,gBAAgB;AAEnC,IAAM,oBAAoB,CAC/B,QACA,eACM;AACN,aAAW,SAAS,OAAO,OAAO,MAAM,GAAG;AACzC,QAAI,GAAG,OAAO,KAAK,GAAG;AAEpB,YAAM,MAAM,IAAI;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AACT;","names":["sql"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponder/client",
3
- "version": "0.9.0-next.1",
3
+ "version": "0.9.0-next.10",
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,183 @@
1
+ import {
2
+ type QueryWithTypings,
3
+ type SQLWrapper,
4
+ Table,
5
+ is,
6
+ sql,
7
+ } from "drizzle-orm";
8
+ import type { PgDialect } from "drizzle-orm/pg-core";
1
9
  import { type PgRemoteDatabase, drizzle } from "drizzle-orm/pg-proxy";
2
10
 
11
+ const getEventSource = async () => {
12
+ let SSE: typeof EventSource;
13
+ if (typeof window === "undefined") {
14
+ const undici = await import(/* webpackIgnore: true */ "undici");
15
+ // @ts-ignore
16
+ SSE = undici.EventSource;
17
+ } else {
18
+ SSE = EventSource;
19
+ }
20
+
21
+ return SSE;
22
+ };
23
+
3
24
  type Schema = { [name: string]: unknown };
4
25
 
5
26
  type Prettify<T> = {
6
27
  [K in keyof T]: T[K];
7
28
  } & {};
8
29
 
30
+ export type Status = {
31
+ [network: string]: {
32
+ block: { number: number; timestamp: number } | null;
33
+ ready: boolean;
34
+ };
35
+ };
36
+
37
+ type ClientDb<schema extends Schema = Schema> = Prettify<
38
+ Omit<
39
+ PgRemoteDatabase<schema>,
40
+ | "insert"
41
+ | "update"
42
+ | "delete"
43
+ | "transaction"
44
+ | "refreshMaterializedView"
45
+ | "_"
46
+ >
47
+ >;
48
+
9
49
  export type Client<schema extends Schema = Schema> = {
10
- db: Prettify<
11
- Omit<
12
- PgRemoteDatabase<schema>,
13
- | "insert"
14
- | "update"
15
- | "delete"
16
- | "transaction"
17
- | "refreshMaterializedView"
18
- | "_"
19
- >
20
- >;
50
+ /** Query the database. */
51
+ db: ClientDb<schema>;
52
+ /** Subscribe to live updates. */
53
+ live: <result>(
54
+ queryFn: (db: ClientDb<schema>) => Promise<result>,
55
+ onData: (result: result) => void,
56
+ onError?: (error: Error) => void,
57
+ ) => {
58
+ unsubscribe: () => void;
59
+ };
60
+ /** Get the status of all chains. */
61
+ getStatus: () => Promise<Status>;
62
+ };
63
+
64
+ const getUrl = (
65
+ baseUrl: string,
66
+ method: "live" | "db" | "status",
67
+ query?: QueryWithTypings,
68
+ ) => {
69
+ const url = new URL(`${baseUrl}/client/${method}`);
70
+ if (query) {
71
+ url.searchParams.set("sql", JSON.stringify(query));
72
+ }
73
+ return url;
74
+ };
75
+
76
+ const noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {
77
+ casing: "snake_case",
78
+ });
79
+
80
+ // @ts-ignore
81
+ const dialect: PgDialect = noopDatabase.dialect;
82
+
83
+ export const compileQuery = (query: SQLWrapper | string) => {
84
+ const sequel = typeof query === "string" ? sql.raw(query) : query.getSQL();
85
+ return dialect.sqlToQuery(sequel);
21
86
  };
22
87
 
88
+ /**
89
+ * Create a client for querying Ponder apps.
90
+ *
91
+ * @param baseUrl - The URL of the Ponder app.
92
+ * @param schema - The schema of the Ponder app.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * import { createClient } from "@ponder/client";
97
+ * import * as schema from "../ponder.schema";
98
+ *
99
+ * const client = createClient("https://...", { schema });
100
+ * ```
101
+ */
23
102
  export const createClient = <schema extends Schema>(
24
- url: string,
103
+ baseUrl: string,
25
104
  { schema }: { schema: schema },
26
105
  ): Client<schema> => {
27
- const db = drizzle(
28
- async (sql, params, method, typings) => {
29
- const result = await fetch(`${url}/client`, {
106
+ let sse: EventSource | undefined;
107
+ let liveCount = 0;
108
+
109
+ const client: Client<schema> = {
110
+ db: drizzle(
111
+ async (sql, params, _, typings) => {
112
+ const builtQuery = { sql, params, typings };
113
+ const response = await fetch(getUrl(baseUrl, "db", builtQuery), {
114
+ method: "POST",
115
+ });
116
+
117
+ if (response.ok === false) {
118
+ const error = new Error(await response.text());
119
+ error.stack = undefined;
120
+ throw error;
121
+ }
122
+
123
+ const result = await response.json();
124
+
125
+ return {
126
+ ...result,
127
+ rows: result.rows.map((row: object) => Object.values(row)),
128
+ };
129
+ },
130
+ { schema, casing: "snake_case" },
131
+ ),
132
+ live: (queryFn, onData, onError) => {
133
+ // https://github.com/drizzle-team/drizzle-orm/blob/04c91434c7ac10aeb2923efd1d19a7ebf10ea9d4/drizzle-orm/src/pg-core/db.ts#L602-L621
134
+
135
+ const addEventListeners = () => {
136
+ sse!.addEventListener("message", (event) => {
137
+ const data = JSON.parse(event.data) as
138
+ | { status: "success"; result: unknown }
139
+ | { status: "error"; error: string };
140
+
141
+ if (data.status === "error") {
142
+ const error = new Error(data.error);
143
+ error.stack = undefined;
144
+ onError?.(error);
145
+ } else {
146
+ queryFn(client.db).then(onData).catch(onError);
147
+ }
148
+ });
149
+
150
+ sse!.addEventListener("error", () => {
151
+ onError?.(new Error("server disconnected"));
152
+ });
153
+ };
154
+
155
+ liveCount++;
156
+ if (sse === undefined) {
157
+ getEventSource().then((SSE) => {
158
+ sse = new SSE(getUrl(baseUrl, "live"));
159
+ addEventListeners();
160
+ });
161
+ } else {
162
+ addEventListeners();
163
+ }
164
+
165
+ return {
166
+ unsubscribe: () => {
167
+ if (--liveCount === 0) sse?.close();
168
+ },
169
+ };
170
+ },
171
+ getStatus: async () => {
172
+ const response = await fetch(getUrl(baseUrl, "status"), {
30
173
  method: "POST",
31
- body: JSON.stringify({ sql, params, method, typings }),
32
174
  });
33
175
 
34
- return await result.json();
176
+ return response.json();
35
177
  },
36
- { schema, casing: "snake_case" },
37
- );
178
+ };
38
179
 
39
- return { db };
180
+ return client;
40
181
  };
41
182
 
42
183
  export {
@@ -56,6 +197,8 @@ export {
56
197
  between,
57
198
  notBetween,
58
199
  like,
200
+ notLike,
201
+ ilike,
59
202
  notIlike,
60
203
  not,
61
204
  asc,
@@ -83,4 +226,17 @@ export {
83
226
  exceptAll,
84
227
  } from "drizzle-orm/pg-core";
85
228
 
86
- export { setDatabaseSchema } from "./setDatabaseSchema.js";
229
+ const Schema = Symbol.for("drizzle:Schema");
230
+
231
+ export const setDatabaseSchema = <T extends { [name: string]: unknown }>(
232
+ schema: T,
233
+ schemaName: string,
234
+ ): T => {
235
+ for (const table of Object.values(schema)) {
236
+ if (is(table, Table)) {
237
+ // @ts-ignore
238
+ table[Schema] = schemaName;
239
+ }
240
+ }
241
+ return schema;
242
+ };
@@ -1,16 +0,0 @@
1
- import { Table, is } from "drizzle-orm";
2
-
3
- const Schema = Symbol.for("drizzle:Schema");
4
-
5
- export const setDatabaseSchema = <T extends { [name: string]: unknown }>(
6
- schema: T,
7
- schemaName: string,
8
- ): T => {
9
- for (const table of Object.values(schema)) {
10
- if (is(table, Table)) {
11
- // @ts-ignore
12
- table[Schema] = schemaName;
13
- }
14
- }
15
- return schema;
16
- };