@wavehouse/sdk 0.0.0-dev.hb0c3a577b2f8 → 0.0.1-dev.20260819163639.h62b2f85f436a

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @wavehouse/sdk
2
2
 
3
- Zero-dependency TypeScript client for [WaveHouse](https://github.com/Wave-RF/WaveHouse) — schema-aware real-time API gateway for ClickHouse.
3
+ TypeScript client for [WaveHouse](https://github.com/Wave-RF/WaveHouse) — schema-aware real-time API gateway for ClickHouse. One runtime dependency (`eventsource-parser`, ~1.4 KB gzipped, itself dependency-free).
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,6 +8,8 @@ Zero-dependency TypeScript client for [WaveHouse](https://github.com/Wave-RF/Wav
8
8
  npm install @wavehouse/sdk
9
9
  ```
10
10
 
11
+ Requires Node 22 or newer — the only line this SDK is tested against; Node 18 and 20 are past upstream end-of-life. Neither browsers nor Node need a polyfill: streaming runs on `fetch`, like the rest of the SDK — see [Runtime support](https://wavehouse.dev/sdk/#runtime-support).
12
+
11
13
  This works in any framework that uses a bundler — React, Vue, Svelte, Angular, Astro, SolidJS, or plain Vite — with `import { createClient } from '@wavehouse/sdk'`.
12
14
 
13
15
  The package is published with [npm provenance](https://docs.npmjs.com/generating-provenance-statements) — npm shows a verified provenance badge on the [package page](https://www.npmjs.com/package/@wavehouse/sdk), and `npm audit signatures` checks the attestation against this repository's signed build.
@@ -42,7 +44,7 @@ Pin a version for production (`https://esm.sh/@wavehouse/sdk@0.1.0`). jsDelivr (
42
44
 
43
45
  **Versioning.** A bare CDN URL serves the latest published **release**; pin for production (`@wavehouse/sdk@0.1.0`) or float on a range (`@0` for the newest 0.x, `@0.1` for 0.1.x). Builds from `main` are published under the `dev` tag — `@wavehouse/sdk@dev` — for trying unreleased changes.
44
46
 
45
- Streaming (`.stream()`) uses the browser's native `EventSource`, so it works in both forms with no polyfill.
47
+ Streaming (`.stream()`) runs on `fetch`, so it works in both forms with no polyfill.
46
48
 
47
49
  ## Quick Start
48
50
 
@@ -55,7 +57,7 @@ const wh = createClient({
55
57
  });
56
58
  ```
57
59
 
58
- `baseURL` may include a path prefix (`https://app.example.com/api/warehouse`) when WaveHouse is served under one — see [Serving under a path prefix](https://wavehouse.dev/sdk#serving-under-a-path-prefix).
60
+ `baseURL` may include a path prefix (`https://app.example.com/api/wavehouse`) when WaveHouse is served under one — see [Serving under a path prefix](https://wavehouse.dev/sdk#serving-under-a-path-prefix).
59
61
 
60
62
  ### Query Data
61
63
 
@@ -129,7 +131,7 @@ const { data } = await wh.from('clicks').select('page').limit(10);
129
131
 
130
132
  Async request methods return a `Result<T>` object — destructure it as `{ data, error }` — and never throw for anything the server returns. `.stream()` and `.liveQuery()` return controllers instead, reporting failures through the subscriber's `error` callback.
131
133
 
132
- The SDK does throw on caller and environment errors: a non-absolute `baseURL` (REST calls reject with a `TypeError`; streams report `SSE_CONNECT_ERROR`, and despite that error's `retryable: true` the SDK never re-dials the stream itself), `.stream()` / `.liveQuery()` in a runtime with no `EventSource`, and an `auth` callback that rejects — a token-refresh failure propagates out of the REST call.
134
+ The SDK does throw on caller and environment errors: a non-absolute `baseURL` (REST calls reject with a `TypeError`; streams report a terminal `SSE_CONNECT_ERROR`), `.stream()` / `.liveQuery()` in a runtime with no global `fetch` and no `options.fetch`, and an `auth` callback that rejects — a token-refresh failure propagates out of the REST call, and on a stream is reported as a retryable `SSE_AUTH_ERROR`. A dropped stream *is* re-dialed by the SDK, on a jittered backoff; only non-retryable failures end it. One more exception can escape an SDK call, though it is yours rather than ours: a subscriber's `status` handler throwing on the first, synchronous `.subscribe()` / `.liveQuery()` call — see [Error Handling](https://wavehouse.dev/sdk/reference#error-handling).
133
135
 
134
136
  ```ts
135
137
  const { data, error } = await wh.from('clicks').fetch();
@@ -148,7 +150,7 @@ Generate TypeScript types from your live WaveHouse schema. The package ships a `
148
150
  npx wavehouse-codegen --url http://localhost:8080 --out ./src/db.d.ts
149
151
  ```
150
152
 
151
- This introspects `/v1/schema`, maps ClickHouse column types to TypeScript, and outputs a `Database` interface you can pass to `createClient<Database>()`. `/v1/schema` is **admin-only** — pass an admin-role token with `--auth <jwt>` (or `-a`) against any non-dev policy.
153
+ This introspects `/v1/ops/schema`, maps ClickHouse column types to TypeScript, and outputs a `Database` interface you can pass to `createClient<Database>()`. `/v1/ops/schema` is **admin-only** — pass an admin-role token with `--auth <jwt>` (or `-a`) against any non-dev policy.
152
154
 
153
155
  ## Development & Testing
154
156
 
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/cli/codegen.ts
4
+ import { realpathSync } from "fs";
5
+ import { pathToFileURL } from "url";
6
+
3
7
  // src/url.ts
4
8
  function resolveURL(base, path, params) {
5
9
  const root = new URL(base);
@@ -107,15 +111,29 @@ function findTopLevelComma(s) {
107
111
  }
108
112
  return -1;
109
113
  }
114
+ function isColumn(value) {
115
+ if (typeof value !== "object" || value === null) return false;
116
+ const col = value;
117
+ return typeof col.name === "string" && typeof col.type === "string";
118
+ }
119
+ function isTableSchema(value) {
120
+ if (typeof value !== "object" || value === null) return false;
121
+ const table = value;
122
+ return typeof table.name === "string" && Array.isArray(table.columns) && table.columns.every(isColumn);
123
+ }
110
124
  async function fetchSchemas(url, auth) {
111
125
  const headers = {};
112
126
  if (auth) headers.Authorization = `Bearer ${auth}`;
113
- const res = await fetch(resolveURL(url, "/v1/schema").toString(), { headers });
127
+ const res = await fetch(resolveURL(url, "/v1/ops/schema").toString(), { headers });
114
128
  if (!res.ok) {
115
129
  const text = await res.text();
116
130
  throw new Error(`Schema fetch failed (${res.status}): ${text}`);
117
131
  }
118
- return await res.json();
132
+ const body = await res.json();
133
+ if (!Array.isArray(body) || !body.every(isTableSchema)) {
134
+ throw new Error("Unexpected /v1/ops/schema response: expected a JSON array of table schemas");
135
+ }
136
+ return body;
119
137
  }
120
138
  function generateTypes(schemas) {
121
139
  const lines = [
@@ -125,19 +143,16 @@ function generateTypes(schemas) {
125
143
  "",
126
144
  "export interface Database {"
127
145
  ];
128
- const tableNames = Object.keys(schemas).sort();
129
- for (const tableName of tableNames) {
130
- const _schema = schemas[tableName];
131
- const rowType = `${pascalCase(tableName)}Row`;
132
- lines.push(` ${tableName}: ${rowType};`);
146
+ const tables = [...schemas].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
147
+ for (const table of tables) {
148
+ lines.push(` ${table.name}: ${pascalCase(table.name)}Row;`);
133
149
  }
134
150
  lines.push("}");
135
151
  lines.push("");
136
- for (const tableName of tableNames) {
137
- const schema = schemas[tableName];
138
- const rowType = `${pascalCase(tableName)}Row`;
152
+ for (const table of tables) {
153
+ const rowType = `${pascalCase(table.name)}Row`;
139
154
  lines.push(`export interface ${rowType} {`);
140
- for (const col of schema.columns) {
155
+ for (const col of table.columns) {
141
156
  const tsType = chTypeToTS(col.type);
142
157
  const optional = col.has_default ? "?" : "";
143
158
  lines.push(` ${col.name}${optional}: ${tsType};`);
@@ -154,12 +169,11 @@ async function main() {
154
169
  const args = parseArgs(process.argv);
155
170
  console.log(`Fetching schema from ${args.url}...`);
156
171
  const schemas = await fetchSchemas(args.url, args.auth);
157
- const tableCount = Object.keys(schemas).length;
158
- if (tableCount === 0) {
172
+ if (schemas.length === 0) {
159
173
  console.warn("No tables found. Is WaveHouse running with tables in ClickHouse?");
160
174
  process.exit(1);
161
175
  }
162
- console.log(`Found ${tableCount} table(s): ${Object.keys(schemas).join(", ")}`);
176
+ console.log(`Found ${schemas.length} table(s): ${schemas.map((t) => t.name).join(", ")}`);
163
177
  const output = generateTypes(schemas);
164
178
  const { writeFile } = await import("fs/promises");
165
179
  const { resolve } = await import("path");
@@ -167,7 +181,22 @@ async function main() {
167
181
  await writeFile(outPath, output, "utf-8");
168
182
  console.log(`\u2713 Types written to ${outPath}`);
169
183
  }
170
- main().catch((err) => {
171
- console.error("Codegen failed:", err.message);
172
- process.exit(1);
173
- });
184
+ function isDirectInvocation() {
185
+ if (!process.argv[1]) return false;
186
+ try {
187
+ return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
188
+ } catch {
189
+ return false;
190
+ }
191
+ }
192
+ if (isDirectInvocation()) {
193
+ main().catch((err) => {
194
+ console.error("Codegen failed:", err.message);
195
+ process.exit(1);
196
+ });
197
+ }
198
+ export {
199
+ chTypeToTS,
200
+ fetchSchemas,
201
+ generateTypes
202
+ };