@ponder/client 0.14.13 → 0.15.1

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.js CHANGED
@@ -1,10 +1,18 @@
1
1
  // src/index.ts
2
2
  import {
3
+ Column,
4
+ SQL,
3
5
  Table,
4
- isTable
6
+ is,
7
+ isTable,
8
+ mapRelationalRow
5
9
  } from "drizzle-orm";
6
10
  import { isPgEnum } from "drizzle-orm/pg-core";
11
+ import { PgCountBuilder } from "drizzle-orm/pg-core/query-builders/count";
12
+ import { PgRelationalQuery } from "drizzle-orm/pg-core/query-builders/query";
13
+ import { PgRaw } from "drizzle-orm/pg-core/query-builders/raw";
7
14
  import { drizzle } from "drizzle-orm/pg-proxy";
15
+ import { TypedQueryBuilder } from "drizzle-orm/query-builders/query-builder";
8
16
  import { EventSource } from "eventsource";
9
17
  import superjson from "superjson";
10
18
  import {
@@ -41,7 +49,7 @@ import {
41
49
  max,
42
50
  min,
43
51
  relations,
44
- SQL
52
+ SQL as SQL2
45
53
  } from "drizzle-orm";
46
54
  import {
47
55
  alias,
@@ -68,14 +76,12 @@ var compileQuery = (query) => {
68
76
  return dialect.sqlToQuery(query.getSQL());
69
77
  };
70
78
  var createClient = (baseUrl, params = {}) => {
71
- let sse;
72
- let liveCount = 0;
73
79
  const client = {
74
80
  db: drizzle(
75
81
  async (sql2, params2, method, typings) => {
76
82
  const builtQuery = { sql: sql2, params: params2, typings };
77
83
  const response = await fetch(getUrl(baseUrl, "db", builtQuery), {
78
- method: "POST"
84
+ method: "GET"
79
85
  });
80
86
  if (response.ok === false) {
81
87
  const error = new Error(await response.text());
@@ -94,27 +100,93 @@ var createClient = (baseUrl, params = {}) => {
94
100
  { schema: params.schema, casing: "snake_case" }
95
101
  ),
96
102
  live: (queryFn, onData, onError) => {
97
- if (sse === void 0) {
98
- sse = new EventSource(getUrl(baseUrl, "live"));
103
+ const noopDatabase2 = drizzle(() => Promise.resolve({ rows: [] }), {
104
+ schema: params.schema,
105
+ casing: "snake_case"
106
+ });
107
+ const queryPromise = queryFn(noopDatabase2);
108
+ if ("getSQL" in queryPromise === false) {
109
+ throw new Error(
110
+ '"queryFn" must return SQL. You may have to remove `.execute()` from your query.'
111
+ );
99
112
  }
100
- const onDataListener = (_event) => {
101
- queryFn(client.db).then(onData).catch(onError);
113
+ const queryBuilder = queryPromise;
114
+ const query = compileQuery(queryBuilder);
115
+ const sse = new EventSource(getUrl(baseUrl, "live", query));
116
+ const onDataListener = async (event) => {
117
+ try {
118
+ const result = JSON.parse(event.data);
119
+ const drizzleShim = drizzle(
120
+ (_, __, method) => {
121
+ if (method === "all") {
122
+ return Promise.resolve({
123
+ ...result,
124
+ rows: result.rows.map((row) => Object.values(row))
125
+ });
126
+ }
127
+ return Promise.resolve(result);
128
+ },
129
+ { schema: params.schema }
130
+ );
131
+ let data;
132
+ if (queryBuilder instanceof TypedQueryBuilder) {
133
+ const fields = queryBuilder._.selectedFields;
134
+ const orderedFields = orderSelectedFields(fields);
135
+ data = await drizzleShim._.session.prepareQuery(
136
+ query,
137
+ // @ts-ignore
138
+ orderedFields,
139
+ void 0,
140
+ false
141
+ ).execute();
142
+ } else if (queryBuilder instanceof PgRelationalQuery) {
143
+ const selection = queryBuilder._toSQL().query.selection;
144
+ data = await drizzleShim._.session.prepareQuery(
145
+ query,
146
+ void 0,
147
+ void 0,
148
+ true,
149
+ (rawRows, mapColumnValue) => {
150
+ const rows = rawRows.map(
151
+ (row) => mapRelationalRow(
152
+ // @ts-ignore
153
+ queryBuilder.schema,
154
+ // @ts-ignore
155
+ queryBuilder.tableConfig,
156
+ // @ts-ignore
157
+ row,
158
+ selection,
159
+ mapColumnValue
160
+ )
161
+ );
162
+ if (queryBuilder.mode === "first") {
163
+ return rows[0];
164
+ }
165
+ return rows;
166
+ }
167
+ ).execute();
168
+ } else if (queryBuilder instanceof PgRaw) {
169
+ data = await drizzleShim._.session.prepareQuery(query, void 0, void 0, false).execute();
170
+ } else if (queryBuilder instanceof PgCountBuilder) {
171
+ data = await drizzleShim._.session.count(queryBuilder.getSQL());
172
+ } else {
173
+ throw new Error("Unsupported query builder");
174
+ }
175
+ onData(data);
176
+ } catch (error) {
177
+ onError?.(error);
178
+ }
102
179
  };
103
180
  const onErrorListener = (_event) => {
104
181
  onError?.(new Error("server disconnected"));
105
182
  };
106
- sse?.addEventListener("message", onDataListener);
107
- sse?.addEventListener("error", onErrorListener);
108
- liveCount = liveCount + 1;
183
+ sse.addEventListener("message", onDataListener);
184
+ sse.addEventListener("error", onErrorListener);
109
185
  return {
110
186
  unsubscribe: () => {
111
- sse?.removeEventListener("message", onDataListener);
112
- sse?.removeEventListener("error", onErrorListener);
113
- liveCount = liveCount - 1;
114
- if (liveCount === 0) {
115
- sse?.close();
116
- sse = void 0;
117
- }
187
+ sse.removeEventListener("message", onDataListener);
188
+ sse.removeEventListener("error", onErrorListener);
189
+ sse.close();
118
190
  }
119
191
  };
120
192
  },
@@ -134,8 +206,31 @@ var setDatabaseSchema = (schema, schemaName) => {
134
206
  }
135
207
  }
136
208
  };
209
+ function orderSelectedFields(fields, pathPrefix) {
210
+ return Object.entries(fields).reduce(
211
+ (result, [name, field]) => {
212
+ if (typeof name !== "string") {
213
+ return result;
214
+ }
215
+ const newPath = pathPrefix ? [...pathPrefix, name] : [name];
216
+ if (is(field, Column) || is(field, SQL) || is(field, SQL.Aliased)) {
217
+ result.push({ path: newPath, field });
218
+ } else if (is(field, Table)) {
219
+ result.push(
220
+ ...orderSelectedFields(field[Table.Symbol.Columns], newPath)
221
+ );
222
+ } else {
223
+ result.push(
224
+ ...orderSelectedFields(field, newPath)
225
+ );
226
+ }
227
+ return result;
228
+ },
229
+ []
230
+ );
231
+ }
137
232
  export {
138
- SQL,
233
+ SQL2 as SQL,
139
234
  alias,
140
235
  and,
141
236
  asc,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n type QueryWithTypings,\n type SQLWrapper,\n Table,\n isTable,\n} from \"drizzle-orm\";\nimport { type PgDialect, isPgEnum } 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\",\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 params: { 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, method, 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 if (method === \"all\") {\n return {\n ...result,\n rows: result.rows.map((row: object) => Object.values(row)),\n };\n }\n\n return result;\n },\n { schema: params.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(`${new URL(baseUrl).origin}/status`);\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 SQL,\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 const setDatabaseSchema = <T extends { [name: string]: unknown }>(\n schema: T,\n schemaName: string,\n) => {\n for (const table of Object.values(schema)) {\n if (isTable(table)) {\n // @ts-ignore\n table[Table.Symbol.Schema] = schemaName;\n } else if (isPgEnum(table)) {\n // @ts-ignore\n table.schema = schemaName;\n }\n }\n};\n"],"mappings":";AAAA;AAAA,EAGE;AAAA,EACA;AAAA,OACK;AACP,SAAyB,gBAAgB;AACzC,SAAgC,eAAe;AAC/C,SAAS,mBAAmB;AAC5B,OAAO,eAAe;AA6KtB;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,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA5JP,IAAM,SAAS,CACb,SACA,QACA,UACG;AACH,QAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,MAAI,WAAW,GAAG,IAAI,QAAQ,IAAI,MAAM;AACxC,MAAI,OAAO;AACT,QAAI,aAAa,IAAI,OAAO,UAAU,UAAU,KAAK,CAAC;AAAA,EACxD;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,UAAsB;AACjD,SAAO,QAAQ,WAAW,MAAM,OAAO,CAAC;AAC1C;AAgBO,IAAM,eAAe,CAC1B,SACA,SAA8B,CAAC,MACZ;AACnB,MAAI;AACJ,MAAI,YAAY;AAEhB,QAAM,SAAyB;AAAA,IAC7B,IAAI;AAAA,MACF,OAAOA,MAAKC,SAAQ,QAAQ,YAAY;AACtC,cAAM,aAAa,EAAE,KAAAD,MAAK,QAAAC,SAAQ,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,YAAI,WAAW,OAAO;AACpB,iBAAO;AAAA,YACL,GAAG;AAAA,YACH,MAAM,OAAO,KAAK,IAAI,CAAC,QAAgB,OAAO,OAAO,GAAG,CAAC;AAAA,UAC3D;AAAA,QACF;AAEA,eAAO;AAAA,MACT;AAAA,MACA,EAAE,QAAQ,OAAO,QAAQ,QAAQ,aAAa;AAAA,IAChD;AAAA,IACA,MAAM,CAAC,SAAS,QAAQ,YAAY;AAClC,UAAI,QAAQ,QAAW;AACrB,cAAM,IAAI,YAAY,OAAO,SAAS,MAAM,CAAC;AAAA,MAC/C;AAEA,YAAM,iBAAiB,CAAC,WAAyB;AAC/C,gBAAQ,OAAO,EAAE,EAAE,KAAK,MAAM,EAAE,MAAM,OAAO;AAAA,MAC/C;AAEA,YAAM,kBAAkB,CAAC,WAAyB;AAChD,kBAAU,IAAI,MAAM,qBAAqB,CAAC;AAAA,MAC5C;AAEA,WAAK,iBAAiB,WAAW,cAAc;AAC/C,WAAK,iBAAiB,SAAS,eAAe;AAC9C,kBAAY,YAAY;AAExB,aAAO;AAAA,QACL,aAAa,MAAM;AACjB,eAAK,oBAAoB,WAAW,cAAc;AAClD,eAAK,oBAAoB,SAAS,eAAe;AACjD,sBAAY,YAAY;AACxB,cAAI,cAAc,GAAG;AACnB,iBAAK,MAAM;AACX,kBAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW,YAAY;AACrB,YAAM,WAAW,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,EAAE,MAAM,SAAS;AAEhE,aAAO,SAAS,KAAK;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AACT;AAiDO,IAAM,oBAAoB,CAC/B,QACA,eACG;AACH,aAAW,SAAS,OAAO,OAAO,MAAM,GAAG;AACzC,QAAI,QAAQ,KAAK,GAAG;AAElB,YAAM,MAAM,OAAO,MAAM,IAAI;AAAA,IAC/B,WAAW,SAAS,KAAK,GAAG;AAE1B,YAAM,SAAS;AAAA,IACjB;AAAA,EACF;AACF;","names":["sql","params"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n type AnyColumn,\n Column,\n type QueryWithTypings,\n SQL,\n type SQLWrapper,\n type SelectedFieldsOrdered,\n Table,\n is,\n isTable,\n mapRelationalRow,\n} from \"drizzle-orm\";\nimport { type PgDialect, isPgEnum } from \"drizzle-orm/pg-core\";\nimport { PgCountBuilder } from \"drizzle-orm/pg-core/query-builders/count\";\nimport { PgRelationalQuery } from \"drizzle-orm/pg-core/query-builders/query\";\nimport { PgRaw } from \"drizzle-orm/pg-core/query-builders/raw\";\nimport { type PgRemoteDatabase, drizzle } from \"drizzle-orm/pg-proxy\";\nimport { TypedQueryBuilder } from \"drizzle-orm/query-builders/query-builder\";\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\",\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 params: { schema?: schema } = {},\n): Client<schema> => {\n const client: Client<schema> = {\n db: drizzle(\n async (sql, params, method, typings) => {\n const builtQuery = { sql, params, typings };\n const response = await fetch(getUrl(baseUrl, \"db\", builtQuery), {\n method: \"GET\",\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 if (method === \"all\") {\n return {\n ...result,\n rows: result.rows.map((row: object) => Object.values(row)),\n };\n }\n\n return result;\n },\n { schema: params.schema, casing: \"snake_case\" },\n ),\n live: (queryFn, onData, onError) => {\n const noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {\n schema: params.schema,\n casing: \"snake_case\",\n });\n\n const queryPromise = queryFn(noopDatabase);\n\n if (\"getSQL\" in queryPromise === false) {\n throw new Error(\n '\"queryFn\" must return SQL. You may have to remove `.execute()` from your query.',\n );\n }\n const queryBuilder = queryPromise as unknown as SQLWrapper;\n const query = compileQuery(queryBuilder);\n\n const sse = new EventSource(getUrl(baseUrl, \"live\", query));\n\n const onDataListener = async (event: MessageEvent) => {\n try {\n // @ts-ignore\n const result = JSON.parse(event.data);\n\n const drizzleShim = drizzle(\n (_, __, method) => {\n if (method === \"all\") {\n return Promise.resolve({\n ...result,\n rows: result.rows.map((row: object) => Object.values(row)),\n });\n }\n\n return Promise.resolve(result);\n },\n { schema: params.schema },\n );\n\n let data: unknown;\n\n if (queryBuilder instanceof TypedQueryBuilder) {\n const fields = queryBuilder._.selectedFields as Record<\n string,\n unknown\n >;\n const orderedFields = orderSelectedFields(fields);\n\n data = await drizzleShim._.session\n .prepareQuery(\n query,\n // @ts-ignore\n orderedFields,\n undefined,\n false,\n )\n .execute();\n } else if (queryBuilder instanceof PgRelationalQuery) {\n // @ts-ignore\n const selection = queryBuilder._toSQL().query.selection;\n data = await drizzleShim._.session\n .prepareQuery(\n query,\n undefined,\n undefined,\n true,\n (rawRows, mapColumnValue) => {\n const rows = rawRows.map((row) =>\n mapRelationalRow(\n // @ts-ignore\n queryBuilder.schema,\n // @ts-ignore\n queryBuilder.tableConfig,\n // @ts-ignore\n row,\n selection,\n mapColumnValue,\n ),\n );\n // @ts-ignore\n if (queryBuilder.mode === \"first\") {\n return rows[0];\n }\n return rows;\n },\n )\n .execute();\n } else if (queryBuilder instanceof PgRaw) {\n data = await drizzleShim._.session\n .prepareQuery(query, undefined, undefined, false)\n .execute();\n } else if (queryBuilder instanceof PgCountBuilder) {\n data = await drizzleShim._.session.count(queryBuilder.getSQL());\n } else {\n throw new Error(\"Unsupported query builder\");\n }\n\n // @ts-ignore\n onData(data);\n } catch (error) {\n onError?.(error as Error);\n }\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\n return {\n unsubscribe: () => {\n sse.removeEventListener(\"message\", onDataListener);\n sse.removeEventListener(\"error\", onErrorListener);\n sse.close();\n },\n };\n },\n getStatus: async () => {\n const response = await fetch(`${new URL(baseUrl).origin}/status`);\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 SQL,\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 const setDatabaseSchema = <T extends { [name: string]: unknown }>(\n schema: T,\n schemaName: string,\n) => {\n for (const table of Object.values(schema)) {\n if (isTable(table)) {\n // @ts-ignore\n table[Table.Symbol.Schema] = schemaName;\n } else if (isPgEnum(table)) {\n // @ts-ignore\n table.schema = schemaName;\n }\n }\n};\n\nfunction orderSelectedFields<TColumn extends AnyColumn>(\n fields: Record<string, unknown>,\n pathPrefix?: string[],\n): SelectedFieldsOrdered<TColumn> {\n return Object.entries(fields).reduce<SelectedFieldsOrdered<AnyColumn>>(\n (result, [name, field]) => {\n if (typeof name !== \"string\") {\n return result;\n }\n\n const newPath = pathPrefix ? [...pathPrefix, name] : [name];\n if (is(field, Column) || is(field, SQL) || is(field, SQL.Aliased)) {\n result.push({ path: newPath, field });\n } else if (is(field, Table)) {\n result.push(\n // @ts-ignore\n ...orderSelectedFields(field[Table.Symbol.Columns], newPath),\n );\n } else {\n result.push(\n ...orderSelectedFields(field as Record<string, unknown>, newPath),\n );\n }\n return result;\n },\n [],\n ) as SelectedFieldsOrdered<TColumn>;\n}\n"],"mappings":"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","names":["SQL","sql","params","noopDatabase"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponder/client",
3
- "version": "0.14.13",
3
+ "version": "0.15.1",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -1,11 +1,21 @@
1
1
  import {
2
+ type AnyColumn,
3
+ Column,
2
4
  type QueryWithTypings,
5
+ SQL,
3
6
  type SQLWrapper,
7
+ type SelectedFieldsOrdered,
4
8
  Table,
9
+ is,
5
10
  isTable,
11
+ mapRelationalRow,
6
12
  } from "drizzle-orm";
7
13
  import { type PgDialect, isPgEnum } from "drizzle-orm/pg-core";
14
+ import { PgCountBuilder } from "drizzle-orm/pg-core/query-builders/count";
15
+ import { PgRelationalQuery } from "drizzle-orm/pg-core/query-builders/query";
16
+ import { PgRaw } from "drizzle-orm/pg-core/query-builders/raw";
8
17
  import { type PgRemoteDatabase, drizzle } from "drizzle-orm/pg-proxy";
18
+ import { TypedQueryBuilder } from "drizzle-orm/query-builders/query-builder";
9
19
  import { EventSource } from "eventsource";
10
20
  import superjson from "superjson";
11
21
 
@@ -111,15 +121,12 @@ export const createClient = <schema extends Schema>(
111
121
  baseUrl: string,
112
122
  params: { schema?: schema } = {},
113
123
  ): Client<schema> => {
114
- let sse: EventSource | undefined;
115
- let liveCount = 0;
116
-
117
124
  const client: Client<schema> = {
118
125
  db: drizzle(
119
126
  async (sql, params, method, typings) => {
120
127
  const builtQuery = { sql, params, typings };
121
128
  const response = await fetch(getUrl(baseUrl, "db", builtQuery), {
122
- method: "POST",
129
+ method: "GET",
123
130
  });
124
131
 
125
132
  if (response.ok === false) {
@@ -142,31 +149,119 @@ export const createClient = <schema extends Schema>(
142
149
  { schema: params.schema, casing: "snake_case" },
143
150
  ),
144
151
  live: (queryFn, onData, onError) => {
145
- if (sse === undefined) {
146
- sse = new EventSource(getUrl(baseUrl, "live"));
152
+ const noopDatabase = drizzle(() => Promise.resolve({ rows: [] }), {
153
+ schema: params.schema,
154
+ casing: "snake_case",
155
+ });
156
+
157
+ const queryPromise = queryFn(noopDatabase);
158
+
159
+ if ("getSQL" in queryPromise === false) {
160
+ throw new Error(
161
+ '"queryFn" must return SQL. You may have to remove `.execute()` from your query.',
162
+ );
147
163
  }
164
+ const queryBuilder = queryPromise as unknown as SQLWrapper;
165
+ const query = compileQuery(queryBuilder);
166
+
167
+ const sse = new EventSource(getUrl(baseUrl, "live", query));
168
+
169
+ const onDataListener = async (event: MessageEvent) => {
170
+ try {
171
+ // @ts-ignore
172
+ const result = JSON.parse(event.data);
173
+
174
+ const drizzleShim = drizzle(
175
+ (_, __, method) => {
176
+ if (method === "all") {
177
+ return Promise.resolve({
178
+ ...result,
179
+ rows: result.rows.map((row: object) => Object.values(row)),
180
+ });
181
+ }
182
+
183
+ return Promise.resolve(result);
184
+ },
185
+ { schema: params.schema },
186
+ );
187
+
188
+ let data: unknown;
189
+
190
+ if (queryBuilder instanceof TypedQueryBuilder) {
191
+ const fields = queryBuilder._.selectedFields as Record<
192
+ string,
193
+ unknown
194
+ >;
195
+ const orderedFields = orderSelectedFields(fields);
148
196
 
149
- const onDataListener = (_event: MessageEvent) => {
150
- queryFn(client.db).then(onData).catch(onError);
197
+ data = await drizzleShim._.session
198
+ .prepareQuery(
199
+ query,
200
+ // @ts-ignore
201
+ orderedFields,
202
+ undefined,
203
+ false,
204
+ )
205
+ .execute();
206
+ } else if (queryBuilder instanceof PgRelationalQuery) {
207
+ // @ts-ignore
208
+ const selection = queryBuilder._toSQL().query.selection;
209
+ data = await drizzleShim._.session
210
+ .prepareQuery(
211
+ query,
212
+ undefined,
213
+ undefined,
214
+ true,
215
+ (rawRows, mapColumnValue) => {
216
+ const rows = rawRows.map((row) =>
217
+ mapRelationalRow(
218
+ // @ts-ignore
219
+ queryBuilder.schema,
220
+ // @ts-ignore
221
+ queryBuilder.tableConfig,
222
+ // @ts-ignore
223
+ row,
224
+ selection,
225
+ mapColumnValue,
226
+ ),
227
+ );
228
+ // @ts-ignore
229
+ if (queryBuilder.mode === "first") {
230
+ return rows[0];
231
+ }
232
+ return rows;
233
+ },
234
+ )
235
+ .execute();
236
+ } else if (queryBuilder instanceof PgRaw) {
237
+ data = await drizzleShim._.session
238
+ .prepareQuery(query, undefined, undefined, false)
239
+ .execute();
240
+ } else if (queryBuilder instanceof PgCountBuilder) {
241
+ data = await drizzleShim._.session.count(queryBuilder.getSQL());
242
+ } else {
243
+ throw new Error("Unsupported query builder");
244
+ }
245
+
246
+ // @ts-ignore
247
+ onData(data);
248
+ } catch (error) {
249
+ onError?.(error as Error);
250
+ }
151
251
  };
152
252
 
153
253
  const onErrorListener = (_event: MessageEvent) => {
154
254
  onError?.(new Error("server disconnected"));
155
255
  };
156
256
 
157
- sse?.addEventListener("message", onDataListener);
158
- sse?.addEventListener("error", onErrorListener);
159
- liveCount = liveCount + 1;
257
+ sse.addEventListener("message", onDataListener);
258
+ sse.addEventListener("error", onErrorListener);
160
259
 
161
260
  return {
162
261
  unsubscribe: () => {
163
- sse?.removeEventListener("message", onDataListener);
164
- sse?.removeEventListener("error", onErrorListener);
165
- liveCount = liveCount - 1;
166
- if (liveCount === 0) {
167
- sse?.close();
168
- sse = undefined;
169
- }
262
+ sse.removeEventListener("message", onDataListener);
263
+ sse.removeEventListener("error", onErrorListener);
264
+ sse.close();
170
265
  },
171
266
  };
172
267
  },
@@ -241,3 +336,32 @@ export const setDatabaseSchema = <T extends { [name: string]: unknown }>(
241
336
  }
242
337
  }
243
338
  };
339
+
340
+ function orderSelectedFields<TColumn extends AnyColumn>(
341
+ fields: Record<string, unknown>,
342
+ pathPrefix?: string[],
343
+ ): SelectedFieldsOrdered<TColumn> {
344
+ return Object.entries(fields).reduce<SelectedFieldsOrdered<AnyColumn>>(
345
+ (result, [name, field]) => {
346
+ if (typeof name !== "string") {
347
+ return result;
348
+ }
349
+
350
+ const newPath = pathPrefix ? [...pathPrefix, name] : [name];
351
+ if (is(field, Column) || is(field, SQL) || is(field, SQL.Aliased)) {
352
+ result.push({ path: newPath, field });
353
+ } else if (is(field, Table)) {
354
+ result.push(
355
+ // @ts-ignore
356
+ ...orderSelectedFields(field[Table.Symbol.Columns], newPath),
357
+ );
358
+ } else {
359
+ result.push(
360
+ ...orderSelectedFields(field as Record<string, unknown>, newPath),
361
+ );
362
+ }
363
+ return result;
364
+ },
365
+ [],
366
+ ) as SelectedFieldsOrdered<TColumn>;
367
+ }