@wavehouse/sdk 0.0.0-dev.h514be5522d89 → 0.0.0-dev.h70dbf5d8f5ed
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 +10 -2
- package/dist/cli/codegen.js +62 -18
- package/dist/index.cjs +65 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +143 -9
- package/dist/index.d.ts +143 -9
- package/dist/index.global.js +2 -2
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +65 -30
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/** User-provided database schema mapping table names to row types. */
|
|
2
2
|
type Database = Record<string, Record<string, unknown>>;
|
|
3
3
|
/**
|
|
4
|
-
* Discriminated union for all async SDK operations. Never throws
|
|
4
|
+
* Discriminated union for all async SDK operations. Never throws for anything
|
|
5
|
+
* the server returns — caller and environment errors (a non-absolute `baseURL`,
|
|
6
|
+
* a missing `EventSource`, a rejecting `auth` callback) do throw.
|
|
5
7
|
*
|
|
6
8
|
* Discriminated on `ok`: `if (result.ok)` narrows to the success arm (and tells
|
|
7
9
|
* the compiler `data` is present), while `error` is always available for
|
|
@@ -47,16 +49,102 @@ interface StreamSubscriber<T = Record<string, unknown>> {
|
|
|
47
49
|
error?: (err: WaveHouseError) => void;
|
|
48
50
|
}
|
|
49
51
|
interface ClientConfig<_DB extends Database = Database> {
|
|
50
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Base URL of the WaveHouse server (e.g. "http://localhost:8080"). May include
|
|
54
|
+
* a path prefix ("https://app.example.com/api/warehouse") for a WaveHouse
|
|
55
|
+
* behind a backend-for-frontend (BFF), app-server route, or path-routed
|
|
56
|
+
* ingress; request paths are appended to it. The proxy in front must strip
|
|
57
|
+
* the prefix before forwarding.
|
|
58
|
+
*
|
|
59
|
+
* Must be **absolute** — scheme and host included. The transports report a
|
|
60
|
+
* relative value such as "/api/warehouse" differently: a REST call rejects
|
|
61
|
+
* with a `TypeError` instead of returning a `Result`, while a stream reports
|
|
62
|
+
* `SSE_CONNECT_ERROR` to the subscriber's `error` callback. Build an absolute
|
|
63
|
+
* one: `` `${location.origin}/api/warehouse` ``.
|
|
64
|
+
*/
|
|
51
65
|
baseURL: string;
|
|
52
66
|
/** Auth token provider. Omit for public/unauthenticated access. */
|
|
53
67
|
auth?: () => Promise<string> | string;
|
|
54
68
|
/** Additional client options. */
|
|
55
69
|
options?: ClientOptions;
|
|
56
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* A `fetch`-compatible function.
|
|
73
|
+
*
|
|
74
|
+
* Spelled out rather than written `typeof fetch`, which resolves differently
|
|
75
|
+
* depending on whether the consumer's TypeScript `lib` includes DOM — the same
|
|
76
|
+
* signature either way, but stable across configurations.
|
|
77
|
+
*
|
|
78
|
+
* In practice the SDK only ever calls it with a string URL, and reads `.ok` and
|
|
79
|
+
* `.headers` always, `.text()` on success, and `.status`, `.statusText` plus
|
|
80
|
+
* `.json()` when the response is not `ok`. A rejection becomes a
|
|
81
|
+
* `NETWORK_ERROR` result and is retried with backoff — except a `DOMException`
|
|
82
|
+
* named `AbortError` (what the platform `fetch` and undici throw on abort),
|
|
83
|
+
* which becomes `ABORTED` and is not retried. Implementations that reject with
|
|
84
|
+
* some other abort error, such as `node-fetch`, are retried instead.
|
|
85
|
+
*
|
|
86
|
+
* Implementations shipping their own request/response declarations (undici,
|
|
87
|
+
* `node-fetch`) need casts on the URL, the init and the return value, since
|
|
88
|
+
* those types are separate from the ones behind your global `fetch`; the
|
|
89
|
+
* narrow runtime contract above is what makes them safe.
|
|
90
|
+
*/
|
|
91
|
+
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
57
92
|
interface ClientOptions {
|
|
58
93
|
/** Maximum retry attempts for failed requests. Default: 2. */
|
|
59
94
|
maxRetries?: number;
|
|
95
|
+
/**
|
|
96
|
+
* HTTP implementation used for every REST request (SSE streams excluded).
|
|
97
|
+
* Defaults to the global `fetch`.
|
|
98
|
+
*
|
|
99
|
+
* Provide one to route through a proxy, attach client certificates, add
|
|
100
|
+
* middleware (logging, tracing, circuit breaking), or bypass a transport bug
|
|
101
|
+
* in the runtime's bundled HTTP stack by routing through an implementation
|
|
102
|
+
* you install yourself — e.g. an undici new enough to be free of
|
|
103
|
+
* {@link https://github.com/nodejs/undici/issues/5600 | undici #5600}:
|
|
104
|
+
*
|
|
105
|
+
* ```ts
|
|
106
|
+
* import { Agent, fetch as undiciFetch } from "undici"; // npm install undici — 8.10.0+
|
|
107
|
+
* // Pass the dispatcher explicitly: undici's pool lives on a shared
|
|
108
|
+
* // globalThis symbol claimed by whichever copy loaded first, so an implied
|
|
109
|
+
* // dispatcher can still be the runtime's affected one.
|
|
110
|
+
* const dispatcher = new Agent();
|
|
111
|
+
* createClient({
|
|
112
|
+
* baseURL,
|
|
113
|
+
* options: {
|
|
114
|
+
* fetch: (url, init) =>
|
|
115
|
+
* undiciFetch(url as string, { ...init, dispatcher } as never) as unknown as Promise<Response>,
|
|
116
|
+
* },
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* Only the SSE transport is exempt: the live connection behind `.stream()`
|
|
121
|
+
* and `.liveQuery()` uses `EventSource`, which this does not replace.
|
|
122
|
+
* `.liveQuery()`'s initial backfill is an ordinary request and does go
|
|
123
|
+
* through your function.
|
|
124
|
+
*/
|
|
125
|
+
fetch?: FetchLike;
|
|
126
|
+
/**
|
|
127
|
+
* Headers added to every REST request (SSE streams excluded) — for a
|
|
128
|
+
* header-gated proxy in front of WaveHouse, such as a Cloudflare Access
|
|
129
|
+
* service token.
|
|
130
|
+
*
|
|
131
|
+
* Matched case-insensitively, as HTTP headers are. Applied underneath the
|
|
132
|
+
* SDK's own headers: `auth` still owns `Authorization`, and the
|
|
133
|
+
* `Content-Type` and `Accept` a given request needs are not overridable
|
|
134
|
+
* here — a global `Content-Type` that outranked the request's own is a
|
|
135
|
+
* documented way to break uploads.
|
|
136
|
+
*/
|
|
137
|
+
headers?: Record<string, string>;
|
|
138
|
+
/**
|
|
139
|
+
* Extra `RequestInit` fields merged into every REST request — `credentials`
|
|
140
|
+
* for a cookie-authenticated origin, `mode`, `cache`, `keepalive`, or a
|
|
141
|
+
* runtime-specific extension such as Next.js's `next: { tags }`.
|
|
142
|
+
*
|
|
143
|
+
* Fields the SDK controls (`method`, `headers`, `body`, `signal`) always
|
|
144
|
+
* win, so this cannot corrupt the request itself. Non-standard fields may
|
|
145
|
+
* need a cast, since `RequestInit` only declares the standard ones.
|
|
146
|
+
*/
|
|
147
|
+
fetchOptions?: RequestInit;
|
|
60
148
|
}
|
|
61
149
|
interface StructuredQuery {
|
|
62
150
|
/**
|
|
@@ -172,8 +260,22 @@ interface RolePermissions {
|
|
|
172
260
|
check?: Record<string, PolicyFilter>;
|
|
173
261
|
allowed_aggregations?: string[];
|
|
174
262
|
denied_aggregations?: string[];
|
|
263
|
+
/** Caps the query result LIMIT. 0 = no limit. */
|
|
175
264
|
max_rows?: number;
|
|
176
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Max query execution time, enforced server-side by ClickHouse. Set it as a
|
|
267
|
+
* duration string ("5s", "500ms") or a number of milliseconds; reads always
|
|
268
|
+
* return the number of milliseconds.
|
|
269
|
+
*/
|
|
270
|
+
max_execution_time?: number | string;
|
|
271
|
+
/** Max rows scanned from storage, enforced server-side by ClickHouse. 0 = no limit. */
|
|
272
|
+
max_rows_to_read?: number;
|
|
273
|
+
/**
|
|
274
|
+
* Max peak query memory, enforced server-side by ClickHouse. Set it as a size
|
|
275
|
+
* string ("4GiB", "512MiB") or a number of bytes; reads always return the
|
|
276
|
+
* number of bytes.
|
|
277
|
+
*/
|
|
278
|
+
max_memory_usage?: number | string;
|
|
177
279
|
}
|
|
178
280
|
interface PolicyFilter {
|
|
179
281
|
_eq?: string;
|
|
@@ -185,10 +287,32 @@ interface PolicyFilter {
|
|
|
185
287
|
interface ValidationResult {
|
|
186
288
|
valid: boolean;
|
|
187
289
|
}
|
|
188
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Options for a single call, passed to `.fetch()`.
|
|
292
|
+
*
|
|
293
|
+
* Note that `await`ing a builder directly (`await wh.from("t").select("*")`)
|
|
294
|
+
* takes no options — use the explicit `.fetch({ … })` form for those.
|
|
295
|
+
*/
|
|
296
|
+
interface RequestOptions {
|
|
189
297
|
signal?: AbortSignal;
|
|
190
298
|
limit?: number;
|
|
191
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Options for a single `PipeRef.fetch()` call.
|
|
302
|
+
*
|
|
303
|
+
* `limit` is declared `never` rather than simply left out. Omitting it would
|
|
304
|
+
* still admit a `RequestOptions` value that carries one — excess-property
|
|
305
|
+
* checking only rejects fresh object literals, so a variable would pass and the
|
|
306
|
+
* limit would be silently dropped, which is the whole defect this prevents.
|
|
307
|
+
*
|
|
308
|
+
* A pipe's row cap belongs in its SQL as a `{{limit}}` parameter, supplied via
|
|
309
|
+
* `wh.pipe(name, { limit })`.
|
|
310
|
+
*/
|
|
311
|
+
interface PipeRequestOptions {
|
|
312
|
+
signal?: AbortSignal;
|
|
313
|
+
/** Not supported on pipes — pass `limit` as a pipe parameter instead. */
|
|
314
|
+
limit?: never;
|
|
315
|
+
}
|
|
192
316
|
interface StreamOptions {
|
|
193
317
|
since?: string;
|
|
194
318
|
signal?: AbortSignal;
|
|
@@ -199,6 +323,9 @@ interface HttpContext {
|
|
|
199
323
|
auth?: () => Promise<string> | string;
|
|
200
324
|
options: {
|
|
201
325
|
maxRetries: number;
|
|
326
|
+
fetch?: FetchLike;
|
|
327
|
+
headers?: Record<string, string>;
|
|
328
|
+
fetchOptions?: RequestInit;
|
|
202
329
|
};
|
|
203
330
|
}
|
|
204
331
|
|
|
@@ -275,8 +402,15 @@ declare class PipeRef<Row = Record<string, unknown>> implements PromiseLike<Resu
|
|
|
275
402
|
private readonly _params?;
|
|
276
403
|
private readonly _createStream;
|
|
277
404
|
constructor(ctx: HttpContext, name: string, params: Record<string, unknown> | undefined, createStream: CreateStreamFn$2<Row>);
|
|
278
|
-
/**
|
|
279
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Execute the pipe and return results.
|
|
407
|
+
*
|
|
408
|
+
* Takes only `signal` — deliberately narrower than the `RequestOptions` the
|
|
409
|
+
* query builder accepts. The pipes endpoint binds the body as the pipe's
|
|
410
|
+
* parameters, so there is no row cap to forward; give the pipe a `{{limit}}`
|
|
411
|
+
* parameter in its SQL and pass it via `wh.pipe(name, { limit })`.
|
|
412
|
+
*/
|
|
413
|
+
fetch(opts?: PipeRequestOptions): Promise<Result<Row[]>>;
|
|
280
414
|
/** Subscribe to live events from the pipe's underlying query. */
|
|
281
415
|
stream(opts?: StreamOptions): StreamController<Row>;
|
|
282
416
|
then<TResult1 = Result<Row[]>, TResult2 = never>(onfulfilled?: ((value: Result<Row[]>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
@@ -423,7 +557,7 @@ declare class QueryBuilder<Row = Record<string, unknown>> implements PromiseLike
|
|
|
423
557
|
cacheTTL(seconds: number): QueryBuilder<Row>;
|
|
424
558
|
/** Default row limit when none is specified — deliberately tighter than the backend's DefaultMaxRows (10000) safety cap. */
|
|
425
559
|
static readonly DEFAULT_LIMIT = 1000;
|
|
426
|
-
fetch(opts?:
|
|
560
|
+
fetch(opts?: RequestOptions): Promise<Result<Row[]>>;
|
|
427
561
|
stream(opts?: StreamOptions): StreamController<Row>;
|
|
428
562
|
/**
|
|
429
563
|
* Start a live query: fetches historical data, then streams live updates.
|
|
@@ -459,7 +593,7 @@ declare class TableRef<Row = Record<string, unknown>> {
|
|
|
459
593
|
private readonly _createStream;
|
|
460
594
|
constructor(ctx: HttpContext, table: string, createStream: CreateStreamFn<Row>);
|
|
461
595
|
/** SELECT * shortcut — fetches rows with optional pagination. */
|
|
462
|
-
fetch(opts?:
|
|
596
|
+
fetch(opts?: RequestOptions): Promise<Result<Row[]>>;
|
|
463
597
|
/** Start building a typed query. Returns an immutable, PromiseLike QueryBuilder. */
|
|
464
598
|
select(...columns: string[]): QueryBuilder<Row>;
|
|
465
599
|
/**
|
|
@@ -549,4 +683,4 @@ declare class WaveHouseClient<DB extends Database = Database> {
|
|
|
549
683
|
/** Create a new WaveHouse client instance. */
|
|
550
684
|
declare function createClient<DB extends Database = Database>(config: ClientConfig<DB>): WaveHouseClient<DB>;
|
|
551
685
|
|
|
552
|
-
export { type Aggregation, type ClientConfig, type ClientOptions, type Column, DLQNamespace, type DLQStats, type Database, type
|
|
686
|
+
export { type Aggregation, type ClientConfig, type ClientOptions, type Column, DLQNamespace, type DLQStats, type Database, type FetchLike, type FilterOp, type InsertRecordResult, type InsertResult, LiveQuery, type NDJSONSource, type OrderClause, type ParamDef, type Pipe, PipeRef, type PipeRequestOptions, PipesNamespace, type Policy, type PolicyFilter, PolicyNamespace, QueryBuilder, type QueryFilter, type RequestOptions, type Result, type RolePermissions, SchemaNamespace, type Schemas, StreamController, type StreamEvent, type StreamOptions, type StreamStatus, type StreamSubscriber, type StructuredQuery, SysNamespace, type TablePolicy, TableRef, type TableSchema, type TimeRange, type ValidationResult, WaveHouseClient, type WaveHouseError, createClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/** User-provided database schema mapping table names to row types. */
|
|
2
2
|
type Database = Record<string, Record<string, unknown>>;
|
|
3
3
|
/**
|
|
4
|
-
* Discriminated union for all async SDK operations. Never throws
|
|
4
|
+
* Discriminated union for all async SDK operations. Never throws for anything
|
|
5
|
+
* the server returns — caller and environment errors (a non-absolute `baseURL`,
|
|
6
|
+
* a missing `EventSource`, a rejecting `auth` callback) do throw.
|
|
5
7
|
*
|
|
6
8
|
* Discriminated on `ok`: `if (result.ok)` narrows to the success arm (and tells
|
|
7
9
|
* the compiler `data` is present), while `error` is always available for
|
|
@@ -47,16 +49,102 @@ interface StreamSubscriber<T = Record<string, unknown>> {
|
|
|
47
49
|
error?: (err: WaveHouseError) => void;
|
|
48
50
|
}
|
|
49
51
|
interface ClientConfig<_DB extends Database = Database> {
|
|
50
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Base URL of the WaveHouse server (e.g. "http://localhost:8080"). May include
|
|
54
|
+
* a path prefix ("https://app.example.com/api/warehouse") for a WaveHouse
|
|
55
|
+
* behind a backend-for-frontend (BFF), app-server route, or path-routed
|
|
56
|
+
* ingress; request paths are appended to it. The proxy in front must strip
|
|
57
|
+
* the prefix before forwarding.
|
|
58
|
+
*
|
|
59
|
+
* Must be **absolute** — scheme and host included. The transports report a
|
|
60
|
+
* relative value such as "/api/warehouse" differently: a REST call rejects
|
|
61
|
+
* with a `TypeError` instead of returning a `Result`, while a stream reports
|
|
62
|
+
* `SSE_CONNECT_ERROR` to the subscriber's `error` callback. Build an absolute
|
|
63
|
+
* one: `` `${location.origin}/api/warehouse` ``.
|
|
64
|
+
*/
|
|
51
65
|
baseURL: string;
|
|
52
66
|
/** Auth token provider. Omit for public/unauthenticated access. */
|
|
53
67
|
auth?: () => Promise<string> | string;
|
|
54
68
|
/** Additional client options. */
|
|
55
69
|
options?: ClientOptions;
|
|
56
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* A `fetch`-compatible function.
|
|
73
|
+
*
|
|
74
|
+
* Spelled out rather than written `typeof fetch`, which resolves differently
|
|
75
|
+
* depending on whether the consumer's TypeScript `lib` includes DOM — the same
|
|
76
|
+
* signature either way, but stable across configurations.
|
|
77
|
+
*
|
|
78
|
+
* In practice the SDK only ever calls it with a string URL, and reads `.ok` and
|
|
79
|
+
* `.headers` always, `.text()` on success, and `.status`, `.statusText` plus
|
|
80
|
+
* `.json()` when the response is not `ok`. A rejection becomes a
|
|
81
|
+
* `NETWORK_ERROR` result and is retried with backoff — except a `DOMException`
|
|
82
|
+
* named `AbortError` (what the platform `fetch` and undici throw on abort),
|
|
83
|
+
* which becomes `ABORTED` and is not retried. Implementations that reject with
|
|
84
|
+
* some other abort error, such as `node-fetch`, are retried instead.
|
|
85
|
+
*
|
|
86
|
+
* Implementations shipping their own request/response declarations (undici,
|
|
87
|
+
* `node-fetch`) need casts on the URL, the init and the return value, since
|
|
88
|
+
* those types are separate from the ones behind your global `fetch`; the
|
|
89
|
+
* narrow runtime contract above is what makes them safe.
|
|
90
|
+
*/
|
|
91
|
+
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
57
92
|
interface ClientOptions {
|
|
58
93
|
/** Maximum retry attempts for failed requests. Default: 2. */
|
|
59
94
|
maxRetries?: number;
|
|
95
|
+
/**
|
|
96
|
+
* HTTP implementation used for every REST request (SSE streams excluded).
|
|
97
|
+
* Defaults to the global `fetch`.
|
|
98
|
+
*
|
|
99
|
+
* Provide one to route through a proxy, attach client certificates, add
|
|
100
|
+
* middleware (logging, tracing, circuit breaking), or bypass a transport bug
|
|
101
|
+
* in the runtime's bundled HTTP stack by routing through an implementation
|
|
102
|
+
* you install yourself — e.g. an undici new enough to be free of
|
|
103
|
+
* {@link https://github.com/nodejs/undici/issues/5600 | undici #5600}:
|
|
104
|
+
*
|
|
105
|
+
* ```ts
|
|
106
|
+
* import { Agent, fetch as undiciFetch } from "undici"; // npm install undici — 8.10.0+
|
|
107
|
+
* // Pass the dispatcher explicitly: undici's pool lives on a shared
|
|
108
|
+
* // globalThis symbol claimed by whichever copy loaded first, so an implied
|
|
109
|
+
* // dispatcher can still be the runtime's affected one.
|
|
110
|
+
* const dispatcher = new Agent();
|
|
111
|
+
* createClient({
|
|
112
|
+
* baseURL,
|
|
113
|
+
* options: {
|
|
114
|
+
* fetch: (url, init) =>
|
|
115
|
+
* undiciFetch(url as string, { ...init, dispatcher } as never) as unknown as Promise<Response>,
|
|
116
|
+
* },
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* Only the SSE transport is exempt: the live connection behind `.stream()`
|
|
121
|
+
* and `.liveQuery()` uses `EventSource`, which this does not replace.
|
|
122
|
+
* `.liveQuery()`'s initial backfill is an ordinary request and does go
|
|
123
|
+
* through your function.
|
|
124
|
+
*/
|
|
125
|
+
fetch?: FetchLike;
|
|
126
|
+
/**
|
|
127
|
+
* Headers added to every REST request (SSE streams excluded) — for a
|
|
128
|
+
* header-gated proxy in front of WaveHouse, such as a Cloudflare Access
|
|
129
|
+
* service token.
|
|
130
|
+
*
|
|
131
|
+
* Matched case-insensitively, as HTTP headers are. Applied underneath the
|
|
132
|
+
* SDK's own headers: `auth` still owns `Authorization`, and the
|
|
133
|
+
* `Content-Type` and `Accept` a given request needs are not overridable
|
|
134
|
+
* here — a global `Content-Type` that outranked the request's own is a
|
|
135
|
+
* documented way to break uploads.
|
|
136
|
+
*/
|
|
137
|
+
headers?: Record<string, string>;
|
|
138
|
+
/**
|
|
139
|
+
* Extra `RequestInit` fields merged into every REST request — `credentials`
|
|
140
|
+
* for a cookie-authenticated origin, `mode`, `cache`, `keepalive`, or a
|
|
141
|
+
* runtime-specific extension such as Next.js's `next: { tags }`.
|
|
142
|
+
*
|
|
143
|
+
* Fields the SDK controls (`method`, `headers`, `body`, `signal`) always
|
|
144
|
+
* win, so this cannot corrupt the request itself. Non-standard fields may
|
|
145
|
+
* need a cast, since `RequestInit` only declares the standard ones.
|
|
146
|
+
*/
|
|
147
|
+
fetchOptions?: RequestInit;
|
|
60
148
|
}
|
|
61
149
|
interface StructuredQuery {
|
|
62
150
|
/**
|
|
@@ -172,8 +260,22 @@ interface RolePermissions {
|
|
|
172
260
|
check?: Record<string, PolicyFilter>;
|
|
173
261
|
allowed_aggregations?: string[];
|
|
174
262
|
denied_aggregations?: string[];
|
|
263
|
+
/** Caps the query result LIMIT. 0 = no limit. */
|
|
175
264
|
max_rows?: number;
|
|
176
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Max query execution time, enforced server-side by ClickHouse. Set it as a
|
|
267
|
+
* duration string ("5s", "500ms") or a number of milliseconds; reads always
|
|
268
|
+
* return the number of milliseconds.
|
|
269
|
+
*/
|
|
270
|
+
max_execution_time?: number | string;
|
|
271
|
+
/** Max rows scanned from storage, enforced server-side by ClickHouse. 0 = no limit. */
|
|
272
|
+
max_rows_to_read?: number;
|
|
273
|
+
/**
|
|
274
|
+
* Max peak query memory, enforced server-side by ClickHouse. Set it as a size
|
|
275
|
+
* string ("4GiB", "512MiB") or a number of bytes; reads always return the
|
|
276
|
+
* number of bytes.
|
|
277
|
+
*/
|
|
278
|
+
max_memory_usage?: number | string;
|
|
177
279
|
}
|
|
178
280
|
interface PolicyFilter {
|
|
179
281
|
_eq?: string;
|
|
@@ -185,10 +287,32 @@ interface PolicyFilter {
|
|
|
185
287
|
interface ValidationResult {
|
|
186
288
|
valid: boolean;
|
|
187
289
|
}
|
|
188
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Options for a single call, passed to `.fetch()`.
|
|
292
|
+
*
|
|
293
|
+
* Note that `await`ing a builder directly (`await wh.from("t").select("*")`)
|
|
294
|
+
* takes no options — use the explicit `.fetch({ … })` form for those.
|
|
295
|
+
*/
|
|
296
|
+
interface RequestOptions {
|
|
189
297
|
signal?: AbortSignal;
|
|
190
298
|
limit?: number;
|
|
191
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Options for a single `PipeRef.fetch()` call.
|
|
302
|
+
*
|
|
303
|
+
* `limit` is declared `never` rather than simply left out. Omitting it would
|
|
304
|
+
* still admit a `RequestOptions` value that carries one — excess-property
|
|
305
|
+
* checking only rejects fresh object literals, so a variable would pass and the
|
|
306
|
+
* limit would be silently dropped, which is the whole defect this prevents.
|
|
307
|
+
*
|
|
308
|
+
* A pipe's row cap belongs in its SQL as a `{{limit}}` parameter, supplied via
|
|
309
|
+
* `wh.pipe(name, { limit })`.
|
|
310
|
+
*/
|
|
311
|
+
interface PipeRequestOptions {
|
|
312
|
+
signal?: AbortSignal;
|
|
313
|
+
/** Not supported on pipes — pass `limit` as a pipe parameter instead. */
|
|
314
|
+
limit?: never;
|
|
315
|
+
}
|
|
192
316
|
interface StreamOptions {
|
|
193
317
|
since?: string;
|
|
194
318
|
signal?: AbortSignal;
|
|
@@ -199,6 +323,9 @@ interface HttpContext {
|
|
|
199
323
|
auth?: () => Promise<string> | string;
|
|
200
324
|
options: {
|
|
201
325
|
maxRetries: number;
|
|
326
|
+
fetch?: FetchLike;
|
|
327
|
+
headers?: Record<string, string>;
|
|
328
|
+
fetchOptions?: RequestInit;
|
|
202
329
|
};
|
|
203
330
|
}
|
|
204
331
|
|
|
@@ -275,8 +402,15 @@ declare class PipeRef<Row = Record<string, unknown>> implements PromiseLike<Resu
|
|
|
275
402
|
private readonly _params?;
|
|
276
403
|
private readonly _createStream;
|
|
277
404
|
constructor(ctx: HttpContext, name: string, params: Record<string, unknown> | undefined, createStream: CreateStreamFn$2<Row>);
|
|
278
|
-
/**
|
|
279
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Execute the pipe and return results.
|
|
407
|
+
*
|
|
408
|
+
* Takes only `signal` — deliberately narrower than the `RequestOptions` the
|
|
409
|
+
* query builder accepts. The pipes endpoint binds the body as the pipe's
|
|
410
|
+
* parameters, so there is no row cap to forward; give the pipe a `{{limit}}`
|
|
411
|
+
* parameter in its SQL and pass it via `wh.pipe(name, { limit })`.
|
|
412
|
+
*/
|
|
413
|
+
fetch(opts?: PipeRequestOptions): Promise<Result<Row[]>>;
|
|
280
414
|
/** Subscribe to live events from the pipe's underlying query. */
|
|
281
415
|
stream(opts?: StreamOptions): StreamController<Row>;
|
|
282
416
|
then<TResult1 = Result<Row[]>, TResult2 = never>(onfulfilled?: ((value: Result<Row[]>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
@@ -423,7 +557,7 @@ declare class QueryBuilder<Row = Record<string, unknown>> implements PromiseLike
|
|
|
423
557
|
cacheTTL(seconds: number): QueryBuilder<Row>;
|
|
424
558
|
/** Default row limit when none is specified — deliberately tighter than the backend's DefaultMaxRows (10000) safety cap. */
|
|
425
559
|
static readonly DEFAULT_LIMIT = 1000;
|
|
426
|
-
fetch(opts?:
|
|
560
|
+
fetch(opts?: RequestOptions): Promise<Result<Row[]>>;
|
|
427
561
|
stream(opts?: StreamOptions): StreamController<Row>;
|
|
428
562
|
/**
|
|
429
563
|
* Start a live query: fetches historical data, then streams live updates.
|
|
@@ -459,7 +593,7 @@ declare class TableRef<Row = Record<string, unknown>> {
|
|
|
459
593
|
private readonly _createStream;
|
|
460
594
|
constructor(ctx: HttpContext, table: string, createStream: CreateStreamFn<Row>);
|
|
461
595
|
/** SELECT * shortcut — fetches rows with optional pagination. */
|
|
462
|
-
fetch(opts?:
|
|
596
|
+
fetch(opts?: RequestOptions): Promise<Result<Row[]>>;
|
|
463
597
|
/** Start building a typed query. Returns an immutable, PromiseLike QueryBuilder. */
|
|
464
598
|
select(...columns: string[]): QueryBuilder<Row>;
|
|
465
599
|
/**
|
|
@@ -549,4 +683,4 @@ declare class WaveHouseClient<DB extends Database = Database> {
|
|
|
549
683
|
/** Create a new WaveHouse client instance. */
|
|
550
684
|
declare function createClient<DB extends Database = Database>(config: ClientConfig<DB>): WaveHouseClient<DB>;
|
|
551
685
|
|
|
552
|
-
export { type Aggregation, type ClientConfig, type ClientOptions, type Column, DLQNamespace, type DLQStats, type Database, type
|
|
686
|
+
export { type Aggregation, type ClientConfig, type ClientOptions, type Column, DLQNamespace, type DLQStats, type Database, type FetchLike, type FilterOp, type InsertRecordResult, type InsertResult, LiveQuery, type NDJSONSource, type OrderClause, type ParamDef, type Pipe, PipeRef, type PipeRequestOptions, PipesNamespace, type Policy, type PolicyFilter, PolicyNamespace, QueryBuilder, type QueryFilter, type RequestOptions, type Result, type RolePermissions, SchemaNamespace, type Schemas, StreamController, type StreamEvent, type StreamOptions, type StreamStatus, type StreamSubscriber, type StructuredQuery, SysNamespace, type TablePolicy, TableRef, type TableSchema, type TimeRange, type ValidationResult, WaveHouseClient, type WaveHouseError, createClient };
|
package/dist/index.global.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var WaveHouse=(()=>{var O=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var I=(n,t)=>{for(var e in t)O(n,e,{get:t[e],enumerable:!0})},$=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of j(t))!q.call(n,s)&&s!==e&&O(n,s,{get:()=>t[s],enumerable:!(r=L(t,s))||r.enumerable});return n};var U=n=>$(O({},"__esModule",{value:!0}),n);var X={};I(X,{DLQNamespace:()=>h,LiveQuery:()=>R,PipeRef:()=>f,PipesNamespace:()=>g,PolicyNamespace:()=>_,QueryBuilder:()=>b,SchemaNamespace:()=>S,StreamController:()=>d,SysNamespace:()=>y,TableRef:()=>w,WaveHouseClient:()=>E,createClient:()=>F});async function D(n){let t;try{t=await n.json()}catch{t=void 0}let e=typeof t?.error=="string"?t.error:typeof t?.message=="string"?t.message:n.statusText,r=n.status===503||n.status>=500;return{status:n.status,code:`HTTP_${n.status}`,message:e,details:t,retryable:r}}function H(n){return{status:0,code:"NETWORK_ERROR",message:n instanceof Error?n.message:String(n),retryable:!0}}function l(n){return{ok:!0,data:n,error:null}}function T(n,t,e){return{ok:!0,data:n,error:null,hasMore:t,next:e}}function i(n){return{ok:!1,data:null,error:n}}async function a(n,t){let e=W(n.baseURL,t.path,t.params),r={"Content-Type":t.contentType??"application/json",Accept:"application/json"},s=t.rawBody!==void 0?t.rawBody:t.body!==void 0?JSON.stringify(t.body):void 0;if(n.auth){let c=await n.auth();c&&(r.Authorization=`Bearer ${c}`)}let o=null,u=n.options.maxRetries+1;for(let c=0;c<u;c++)try{let m=await fetch(e,{method:t.method,headers:r,body:s,signal:t.signal});if(m.ok){let p=await m.text();return{data:p?JSON.parse(p):void 0,error:null,headers:m.headers}}let v=await D(m);if(m.status===503){let p=m.headers.get("Retry-After");if(p&&c<u-1){let k=parseInt(p,10)*1e3||3e4;await A(k,t.signal),o=v;continue}}if(v.retryable&&c<u-1){await A(Q(c),t.signal),o=v;continue}return{data:null,error:v,headers:m.headers}}catch(m){if(m instanceof DOMException&&m.name==="AbortError")return{data:null,error:{status:0,code:"ABORTED",message:"Request aborted",retryable:!1},headers:new Headers};o=H(m),c<u-1&&await A(Q(c),t.signal)}return{data:null,error:o,headers:new Headers}}function W(n,t,e){let r=new URL(t,n.endsWith("/")?n:`${n}/`);if(e)for(let[s,o]of Object.entries(e))r.searchParams.set(s,o);return r.toString()}function Q(n){return Math.min(1e3*2**n,3e4)}function A(n,t){return new Promise((e,r)=>{if(t?.aborted){r(new DOMException("Aborted","AbortError"));return}let s=setTimeout(e,n);t?.addEventListener("abort",()=>{clearTimeout(s),r(new DOMException("Aborted","AbortError"))},{once:!0})})}var h=class{_ctx;_createStream;constructor(t,e){this._ctx=t,this._createStream=e}async list(t){let{data:e,error:r}=await a(this._ctx,{method:"GET",path:"/v1/dlq/stats",signal:t?.signal});return r?i(r):l(e)}async table(t,e){let{data:r,error:s}=await a(this._ctx,{method:"GET",path:"/v1/dlq/stats",params:{table:t},signal:e?.signal});return s?i(s):l(r)}stream(t){return this._createStream("dlq",t)}};var f=class{_ctx;_name;_params;_createStream;constructor(t,e,r,s){this._ctx=t,this._name=e,this._params=r,this._createStream=s}async fetch(t){let{data:e,error:r}=await a(this._ctx,{method:"POST",path:`/v1/pipes/${encodeURIComponent(this._name)}`,body:this._params??{},signal:t?.signal});return r?i(r):l(e)}stream(t){return this._createStream(this._name,t)}then(t,e){return this.fetch().then(t,e)}},g=class{_ctx;constructor(t){this._ctx=t}async list(t){let{data:e,error:r}=await a(this._ctx,{method:"GET",path:"/v1/admin/pipes",signal:t?.signal});return r?i(r):l(e)}async get(t,e){let{data:r,error:s}=await a(this._ctx,{method:"GET",path:`/v1/admin/pipes/${encodeURIComponent(t)}`,signal:e?.signal});return s?i(s):l(r)}async set(t,e,r){let{error:s}=await a(this._ctx,{method:"PUT",path:`/v1/admin/pipes/${encodeURIComponent(t)}`,body:e,signal:r?.signal});return s?i(s):l(void 0)}async delete(t,e){let{error:r}=await a(this._ctx,{method:"DELETE",path:`/v1/admin/pipes/${encodeURIComponent(t)}`,signal:e?.signal});return r?i(r):l(void 0)}};var _=class{_ctx;constructor(t){this._ctx=t}async get(t){let{data:e,error:r}=await a(this._ctx,{method:"GET",path:"/v1/admin/policy",signal:t?.signal});return r?i(r):l(e)}async set(t,e){let{error:r}=await a(this._ctx,{method:"PUT",path:"/v1/admin/policy",body:t,signal:e?.signal});return r?i(r):l(void 0)}async validate(t,e){let{data:r,error:s}=await a(this._ctx,{method:"POST",path:"/v1/admin/policy/validate",body:t,signal:e?.signal});return s?i(s):l(r)}};var S=class{_ctx;constructor(t){this._ctx=t}async list(t){let{data:e,error:r}=await a(this._ctx,{method:"GET",path:"/v1/schema",signal:t?.signal});if(r)return i(r);let s;if(Array.isArray(e)){s={};for(let o of e)o&&typeof o=="object"&&"name"in o&&(s[o.name]=o)}else s=e;return l(s)}async refresh(t){let{error:e}=await a(this._ctx,{method:"POST",path:"/v1/schema/refresh",signal:t?.signal});return e?i(e):l(void 0)}};async function N(n,t,e){let{data:r,error:s}=await a(n,{method:"POST",path:"/v1/admin/query",body:{sql:t},signal:e?.signal});return s?i(s):l(r)}var d=class{_transport;_subscribers=new Set;_status="connecting";_buffer=[];_waiters=[];_done=!1;constructor(t){this._transport=t,this._transport.onEvent=e=>{for(let s of this._subscribers)s.next(e);let r=this._waiters.shift();r?r.resolve({value:e,done:!1}):this._buffer.push(e)},this._transport.onStatus=e=>{if(e!==this._status){this._status=e;for(let r of this._subscribers)r.status?.(e);if(e==="closed"){this._done=!0;for(let r of this._waiters)r.resolve({value:void 0,done:!0});this._waiters=[]}}},this._transport.onError=e=>{for(let r of this._subscribers)r.error?.(e)},this._transport.connect()}get status(){return this._status}connected(t=5e3){return this._status==="live"?Promise.resolve():this._status==="closed"?Promise.reject(new Error("Stream is closed")):this._done?Promise.reject(new Error("Stream closed before connecting")):new Promise((e,r)=>{let s=!1,o=setTimeout(()=>{s||(s=!0,this._subscribers.delete(u),r(new Error(`Stream did not connect within ${t}ms`)))},t),u={next:()=>{},status:c=>{if(c==="live"){if(s)return;s=!0,clearTimeout(o),this._subscribers.delete(u),e()}else if(c==="closed"){if(s)return;s=!0,clearTimeout(o),this._subscribers.delete(u),r(new Error("Stream closed before connecting"))}}};this._subscribers.add(u)})}subscribe(t){return this._subscribers.add(t),t.status?.(this._status),()=>{this._subscribers.delete(t),this._subscribers.size===0&&this._waiters.length===0&&this.close()}}attachSignal(t){if(t.aborted){this.close();return}t.addEventListener("abort",()=>this.close(),{once:!0})}close(){if(this._transport.disconnect(),this._status!=="closed"){this._status="closed";for(let t of this._subscribers)t.status?.("closed")}this._done=!0;for(let t of this._waiters)t.resolve({value:void 0,done:!0});this._waiters=[]}[Symbol.asyncIterator](){let t=this;return{next(){return t._buffer.length>0?Promise.resolve({value:t._buffer.shift(),done:!1}):t._done?Promise.resolve({value:void 0,done:!0}):new Promise(e=>{t._waiters.push({resolve:e})})},return(){return t.close(),Promise.resolve({value:void 0,done:!0})},[Symbol.asyncIterator](){return this}}}};var x=0,J=5,P=class{_opts;_es=null;onEvent=null;onStatus=null;onError=null;constructor(t){this._opts=t}connect(){if(typeof EventSource>"u")throw new Error("[wavehouse] EventSource is not available in this environment. Please provide a global polyfill (e.g., `globalThis.EventSource = require('eventsource')`).");this._doConnect().catch(t=>{this.onError?.({status:0,code:"SSE_CONNECT_ERROR",message:t instanceof Error?t.message:String(t),retryable:!0})})}disconnect(){this._es&&(this._es.close(),this._es=null,x=Math.max(0,x-1)),this.onStatus?.("closed")}async _doConnect(){let t=new URL("/v1/stream",this._opts.baseURL);if(t.searchParams.set("table",this._opts.table),this._opts.since&&t.searchParams.set("since",this._opts.since),this._opts.auth){let e=await this._opts.auth();e&&t.searchParams.set("token",e)}x++,x>J&&console.warn(`[wavehouse] ${x} SSE connections open. Browsers limit HTTP/1.1 to 6 connections per domain.`),this._es=new EventSource(t.toString()),this._es.onopen=()=>{this.onStatus?.("live")},this._es.onmessage=e=>{try{let r=JSON.parse(e.data),s={table:r.table_name,timestamp:r.received_timestamp,data:r.data};this.onEvent?.(s)}catch{console.warn("[wavehouse] SSE received malformed message:",e.data)}},this._es.onerror=()=>{this._es?.readyState===EventSource.CONNECTING?this.onStatus?.("reconnecting"):this._es?.readyState===EventSource.CLOSED?this.onStatus?.("closed"):this._es?.readyState===EventSource.OPEN?this.onStatus?.("live"):this.onError?.({status:0,code:"SSE_ERROR",message:"SSE connection error",retryable:!0})}}};var y=class{_ctx;constructor(t){this._ctx=t}async health(t){let{error:e}=await a(this._ctx,{method:"GET",path:"/v1/health",signal:t?.signal});return e?i(e):l(void 0)}};var R=class{_stream;_subscriber;_buffer=[];_buffering=!0;_unsubStream=null;_closed=!1;constructor(t,e,r,s){this._stream=t,this._subscriber=r,this._unsubStream=t.subscribe({next:o=>{this._closed||(this._buffering?this._buffer.push(o):r.next(o))},status:o=>r.status?.(o),error:o=>r.error?.(o)}),this._runBackfill(e)}async _runBackfill(t){try{let e=await t();if(this._closed)return;if(this._subscriber.initial?.(e),e.error){this._buffering=!1;return}let r=e.data,s;r.length>0&&(s=r[r.length-1]?.received_timestamp),this._buffering=!1;for(let o of this._buffer){if(this._closed)break;s&&o.timestamp<=s||this._subscriber.next(o)}this._buffer=[]}catch{this._buffering=!1,this._buffer=[]}}close(){this._closed=!0,this._buffering=!1,this._buffer=[],this._unsubStream?.(),this._stream.close()}};var G={"=":"eq","!=":"neq",">":"gt",">=":"gte","<":"lt","<=":"lte",in:"in",like:"like",not_like:"not_like"},b=class n{_state;_ctx;_createStream;constructor(t,e,r){this._ctx=t,this._state=Object.freeze({...e}),this._createStream=r}select(...t){return this._clone({columns:[...this._state.columns,...t]})}selectAll(){return this._clone({selectAll:!0})}where(t,e,r){let s={column:t,op:G[e],value:r};return this._clone({filters:[...this._state.filters,s]})}count(t="*",e="count"){return this._addAgg("count",t,e)}sum(t,e=`sum_${t}`){return this._addAgg("sum",t,e)}avg(t,e=`avg_${t}`){return this._addAgg("avg",t,e)}min(t,e=`min_${t}`){return this._addAgg("min",t,e)}max(t,e=`max_${t}`){return this._addAgg("max",t,e)}countDistinct(t,e=`count_distinct_${t}`){return this._addAgg("countDistinct",t,e)}aggregate(t,e,r){return this._addAgg(t,e,r)}groupBy(...t){return this._clone({groupBy:[...this._state.groupBy,...t]})}orderBy(t,e="asc"){return this._clone({orderBy:[...this._state.orderBy,{column:t,dir:e}]})}limit(t){return this._clone({limit:t})}timeRange(t,e,r){return this._clone({timeRange:{column:t,since:e,until:r}})}cacheTTL(t){return this._clone({cacheTTL:t})}static DEFAULT_LIMIT=1e3;async fetch(t){let e=t?.limit??this._state.limit??n.DEFAULT_LIMIT,r=this._buildAST(e),{data:s,error:o}=await a(this._ctx,{method:"POST",path:`/v1/query?table=${encodeURIComponent(this._state.table)}`,body:r,signal:t?.signal});if(o)return i(o);let u=s,c=e!=null&&u.length>=e;return c&&this._state.orderBy.length>0?T(u,!0,()=>this._fetchNext(u,e,t)):T(u,c)}stream(t){let e=this._createStream(this._state.table,t),r=this._state.filters,s=this._state.columns;return r.length===0&&s.length===0?e:new B(e,r,s)}liveQuery(t,e){let r=this.stream(e),s=()=>this.fetch();return new R(r,s,t,this._state.filters)}then(t,e){return this.fetch().then(t,e)}_clone(t){return new n(this._ctx,{...this._state,...t},this._createStream)}_addAgg(t,e,r){let s={fn:t,column:e,alias:r};return this._clone({aggregations:[...this._state.aggregations,s]})}_buildAST(t){let e={},r=this._state.columns.length>0,s=this._state.aggregations.length>0;return this._state.selectAll?e.select_all=!0:r?e.columns=this._state.columns:s||(e.select_all=!0),s&&(e.aggregations=this._state.aggregations),this._state.filters.length>0&&(e.filters=this._state.filters),this._state.groupBy.length>0&&(e.group_by=this._state.groupBy),this._state.orderBy.length>0&&(e.order_by=this._state.orderBy),t!=null&&(e.limit=t),this._state.timeRange&&(e.time_range=this._state.timeRange),e}async _fetchNext(t,e,r){let s=this._state.orderBy[0];if(s==null)return T([],!1);let{column:o,dir:u}=s,m=t[t.length-1]?.[o];if(m===void 0)return T([],!1);let p={column:o,op:u==="desc"?"lt":"gt",value:m};return this._clone({filters:[...this._state.filters,p],orderBy:this._state.orderBy}).fetch(r)}},B=class extends d{constructor(t,e,r){let s={onEvent:null,onStatus:null,onError:null,connect(){t.subscribe({next:o=>{if(!M(o.data,e))return;let u=r.length>0?{...o,data:z(o.data,r)}:o;this.onEvent?.(u)},status:o=>this.onStatus?.(o),error:o=>this.onError?.(o)})},disconnect(){t.close()}};super(s)}};function M(n,t){for(let e of t){let r=n[e.column];if(!V(r,e.op,e.value))return!1}return!0}function V(n,t,e){switch(t){case"eq":return n===e;case"neq":return n!==e;case"gt":return C(n,e,(r,s)=>r>s);case"gte":return C(n,e,(r,s)=>r>=s);case"lt":return C(n,e,(r,s)=>r<s);case"lte":return C(n,e,(r,s)=>r<=s);case"in":return Array.isArray(e)&&e.includes(n);case"like":{if(typeof n!="string"||typeof e!="string")return!1;let s=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/%/g,".*").replace(/_/g,".");return new RegExp(`^${s}$`,"i").test(n)}case"not_like":{if(typeof n!="string"||typeof e!="string")return!1;let s=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/%/g,".*").replace(/_/g,".");return!new RegExp(`^${s}$`,"i").test(n)}default:return!0}}function C(n,t,e){return typeof n=="number"&&typeof t=="number"||typeof n=="string"&&typeof t=="string"?e(n,t):!1}function z(n,t){let e={};for(let r of t)r in n&&(e[r]=n[r]);return e}var K="application/x-ndjson";async function Y(n){return typeof n=="string"?n:n instanceof Uint8Array?new TextDecoder().decode(n):new Response(n).text()}var w=class{_ctx;_table;_createStream;constructor(t,e,r){this._ctx=t,this._table=e,this._createStream=r}async fetch(t){return this.select().limit(t?.limit??1e3).fetch(t)}select(...t){return new b(this._ctx,{table:this._table,columns:t,aggregations:[],filters:[],groupBy:[],orderBy:[]},this._createStream)}selectAll(){return this.select().selectAll()}async insert(t,e){if(Array.isArray(t)){if(t.length===0)return l({ok:!0,total:0,succeeded:0,failed:0,duplicates:0,results:[]});let u=t.map(c=>JSON.stringify(c)).join(`
|
|
2
|
-
`);return this._sendNDJSON(
|
|
1
|
+
"use strict";var WaveHouse=(()=>{var D=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var J=(n,t)=>{for(var e in t)D(n,e,{get:t[e],enumerable:!0})},G=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of $(t))!W.call(n,s)&&s!==e&&D(n,s,{get:()=>t[s],enumerable:!(r=U(t,s))||r.enumerable});return n};var M=n=>G(D({},"__esModule",{value:!0}),n);var rt={};J(rt,{DLQNamespace:()=>f,LiveQuery:()=>b,PipeRef:()=>g,PipesNamespace:()=>R,PolicyNamespace:()=>_,QueryBuilder:()=>w,SchemaNamespace:()=>S,StreamController:()=>d,SysNamespace:()=>y,TableRef:()=>v,WaveHouseClient:()=>P,createClient:()=>I});async function L(n){let t;try{t=await n.json()}catch{t=void 0}let e=typeof t?.error=="string"?t.error:typeof t?.message=="string"?t.message:n.statusText,r=n.status===503||n.status>=500;return{status:n.status,code:`HTTP_${n.status}`,message:e,details:t,retryable:r}}function q(n){return{status:0,code:"NETWORK_ERROR",message:n instanceof Error?n.message:String(n),retryable:!0}}function u(n){return{ok:!0,data:n,error:null}}function x(n,t,e){return{ok:!0,data:n,error:null,hasMore:t,next:e}}function a(n){return{ok:!1,data:null,error:n}}function O(n,t,e){let r=new URL(n);r.search="",r.hash="",r.pathname.endsWith("/")||(r.pathname+="/");let s=new URL(t.replace(/^\/+/,""),r);if(e)for(let[o,i]of Object.entries(e))s.searchParams.set(o,i);return s}function V(n,t){if(!t)return n;let e=new Set(Object.keys(n).map(o=>o.toLowerCase())),r={...n},s=new Map;for(let[o,i]of Object.entries(t)){let c=o.toLowerCase();if(e.has(c))continue;let m=s.get(c);m!==void 0&&delete r[m],r[o]=i,s.set(c,o)}return r}async function l(n,t){let e=O(n.baseURL,t.path,t.params).toString(),r={"Content-Type":t.contentType??"application/json",Accept:"application/json"},s=t.rawBody!==void 0?t.rawBody:t.body!==void 0?JSON.stringify(t.body):void 0;if(n.auth){let m=await n.auth();m&&(r.Authorization=`Bearer ${m}`)}let o=V(r,n.options.headers),i=null,c=n.options.maxRetries+1;for(let m=0;m<c;m++)try{let h={...n.options.fetchOptions,method:t.method,headers:o,body:s,signal:t.signal},C=n.options.fetch,p=C?await C(e,h):await fetch(e,h);if(p.ok){let T=await p.text();return{data:T?JSON.parse(T):void 0,error:null,headers:p.headers}}let k=await L(p);if(p.status===503){let T=p.headers.get("Retry-After");if(T&&m<c-1){let N=parseInt(T,10)*1e3||3e4;await H(N,t.signal),i=k;continue}}if(k.retryable&&m<c-1){await H(j(m),t.signal),i=k;continue}return{data:null,error:k,headers:p.headers}}catch(h){if(h instanceof DOMException&&h.name==="AbortError")return{data:null,error:{status:0,code:"ABORTED",message:"Request aborted",retryable:!1},headers:new Headers};i=q(h),m<c-1&&await H(j(m),t.signal)}return{data:null,error:i,headers:new Headers}}function j(n){return Math.min(1e3*2**n,3e4)}function H(n,t){return new Promise((e,r)=>{if(t?.aborted){r(new DOMException("Aborted","AbortError"));return}let s=setTimeout(e,n);t?.addEventListener("abort",()=>{clearTimeout(s),r(new DOMException("Aborted","AbortError"))},{once:!0})})}var f=class{_ctx;_createStream;constructor(t,e){this._ctx=t,this._createStream=e}async list(t){let{data:e,error:r}=await l(this._ctx,{method:"GET",path:"/v1/ops/dlq/stats",signal:t?.signal});return r?a(r):u(e)}async table(t,e){let{data:r,error:s}=await l(this._ctx,{method:"GET",path:"/v1/ops/dlq/stats",params:{table:t},signal:e?.signal});return s?a(s):u(r)}stream(t){return this._createStream("dlq",t)}};var g=class{_ctx;_name;_params;_createStream;constructor(t,e,r,s){this._ctx=t,this._name=e,this._params=r,this._createStream=s}async fetch(t){let{data:e,error:r}=await l(this._ctx,{method:"POST",path:`/v1/pipes/${encodeURIComponent(this._name)}`,body:this._params??{},signal:t?.signal});return r?a(r):u(e)}stream(t){return this._createStream(this._name,t)}then(t,e){return this.fetch().then(t,e)}},R=class{_ctx;constructor(t){this._ctx=t}async list(t){let{data:e,error:r}=await l(this._ctx,{method:"GET",path:"/v1/ops/pipes",signal:t?.signal});return r?a(r):u(e)}async get(t,e){let{data:r,error:s}=await l(this._ctx,{method:"GET",path:`/v1/ops/pipes/${encodeURIComponent(t)}`,signal:e?.signal});return s?a(s):u(r)}async set(t,e,r){let{error:s}=await l(this._ctx,{method:"PUT",path:`/v1/ops/pipes/${encodeURIComponent(t)}`,body:e,signal:r?.signal});return s?a(s):u(void 0)}async delete(t,e){let{error:r}=await l(this._ctx,{method:"DELETE",path:`/v1/ops/pipes/${encodeURIComponent(t)}`,signal:e?.signal});return r?a(r):u(void 0)}};var _=class{_ctx;constructor(t){this._ctx=t}async get(t){let{data:e,error:r}=await l(this._ctx,{method:"GET",path:"/v1/ops/policy",signal:t?.signal});return r?a(r):u(e)}async set(t,e){let{error:r}=await l(this._ctx,{method:"PUT",path:"/v1/ops/policy",body:t,signal:e?.signal});return r?a(r):u(void 0)}async validate(t,e){let{data:r,error:s}=await l(this._ctx,{method:"POST",path:"/v1/ops/policy/validate",body:t,signal:e?.signal});return s?a(s):u(r)}};var S=class{_ctx;constructor(t){this._ctx=t}async list(t){let{data:e,error:r}=await l(this._ctx,{method:"GET",path:"/v1/ops/schema",signal:t?.signal});if(r)return a(r);let s;if(Array.isArray(e)){s={};for(let o of e)o&&typeof o=="object"&&"name"in o&&(s[o.name]=o)}else s=e;return u(s)}async refresh(t){let{error:e}=await l(this._ctx,{method:"POST",path:"/v1/ops/schema/refresh",signal:t?.signal});return e?a(e):u(void 0)}};async function F(n,t,e){let{data:r,error:s}=await l(n,{method:"POST",path:"/v1/ops/query",body:{sql:t},signal:e?.signal});return s?a(s):u(r)}var d=class{_transport;_subscribers=new Set;_status="connecting";_buffer=[];_waiters=[];_done=!1;constructor(t){this._transport=t,this._transport.onEvent=e=>{for(let s of this._subscribers)s.next(e);let r=this._waiters.shift();r?r.resolve({value:e,done:!1}):this._buffer.push(e)},this._transport.onStatus=e=>{if(e!==this._status){this._status=e;for(let r of this._subscribers)r.status?.(e);if(e==="closed"){this._done=!0;for(let r of this._waiters)r.resolve({value:void 0,done:!0});this._waiters=[]}}},this._transport.onError=e=>{for(let r of this._subscribers)r.error?.(e)},this._transport.connect()}get status(){return this._status}connected(t=5e3){return this._status==="live"?Promise.resolve():this._status==="closed"?Promise.reject(new Error("Stream is closed")):this._done?Promise.reject(new Error("Stream closed before connecting")):new Promise((e,r)=>{let s=!1,o=setTimeout(()=>{s||(s=!0,this._subscribers.delete(i),r(new Error(`Stream did not connect within ${t}ms`)))},t),i={next:()=>{},status:c=>{if(c==="live"){if(s)return;s=!0,clearTimeout(o),this._subscribers.delete(i),e()}else if(c==="closed"){if(s)return;s=!0,clearTimeout(o),this._subscribers.delete(i),r(new Error("Stream closed before connecting"))}}};this._subscribers.add(i)})}subscribe(t){return this._subscribers.add(t),t.status?.(this._status),()=>{this._subscribers.delete(t),this._subscribers.size===0&&this._waiters.length===0&&this.close()}}attachSignal(t){if(t.aborted){this.close();return}t.addEventListener("abort",()=>this.close(),{once:!0})}close(){if(this._transport.disconnect(),this._status!=="closed"){this._status="closed";for(let t of this._subscribers)t.status?.("closed")}this._done=!0;for(let t of this._waiters)t.resolve({value:void 0,done:!0});this._waiters=[]}[Symbol.asyncIterator](){let t=this;return{next(){return t._buffer.length>0?Promise.resolve({value:t._buffer.shift(),done:!1}):t._done?Promise.resolve({value:void 0,done:!0}):new Promise(e=>{t._waiters.push({resolve:e})})},return(){return t.close(),Promise.resolve({value:void 0,done:!0})},[Symbol.asyncIterator](){return this}}}};var E=0,z=5,A=class{_opts;_es=null;onEvent=null;onStatus=null;onError=null;constructor(t){this._opts=t}connect(){if(typeof EventSource>"u")throw new Error("[wavehouse] EventSource is not available in this environment. Please provide a global polyfill (e.g., `globalThis.EventSource = require('eventsource')`).");this._doConnect().catch(t=>{this.onError?.({status:0,code:"SSE_CONNECT_ERROR",message:t instanceof Error?t.message:String(t),retryable:!0})})}disconnect(){this._es&&(this._es.close(),this._es=null,E=Math.max(0,E-1)),this.onStatus?.("closed")}async _doConnect(){let t=O(this._opts.baseURL,"/v1/stream");if(t.searchParams.set("table",this._opts.table),this._opts.since&&t.searchParams.set("since",this._opts.since),this._opts.auth){let e=await this._opts.auth();e&&t.searchParams.set("token",e)}E++,E>z&&console.warn(`[wavehouse] ${E} SSE connections open. Browsers limit HTTP/1.1 to 6 connections per domain.`),this._es=new EventSource(t.toString()),this._es.onopen=()=>{this.onStatus?.("live")},this._es.onmessage=e=>{try{let r=JSON.parse(e.data),s={table:r.table_name,timestamp:r.received_timestamp,data:r.data};this.onEvent?.(s)}catch{console.warn("[wavehouse] SSE received malformed message:",e.data)}},this._es.onerror=()=>{this._es?.readyState===EventSource.CONNECTING?this.onStatus?.("reconnecting"):this._es?.readyState===EventSource.CLOSED?this.onStatus?.("closed"):this._es?.readyState===EventSource.OPEN?this.onStatus?.("live"):this.onError?.({status:0,code:"SSE_ERROR",message:"SSE connection error",retryable:!0})}}};var y=class{_ctx;constructor(t){this._ctx=t}async health(t){let{error:e}=await l(this._ctx,{method:"GET",path:"/v1/health",signal:t?.signal});return e?a(e):u(void 0)}};var b=class{_stream;_subscriber;_buffer=[];_buffering=!0;_unsubStream=null;_closed=!1;constructor(t,e,r,s){this._stream=t,this._subscriber=r,this._unsubStream=t.subscribe({next:o=>{this._closed||(this._buffering?this._buffer.push(o):r.next(o))},status:o=>r.status?.(o),error:o=>r.error?.(o)}),this._runBackfill(e)}async _runBackfill(t){try{let e=await t();if(this._closed)return;if(this._subscriber.initial?.(e),e.error){this._buffering=!1;return}let r=e.data,s;r.length>0&&(s=r[r.length-1]?.received_timestamp),this._buffering=!1;for(let o of this._buffer){if(this._closed)break;s&&o.timestamp<=s||this._subscriber.next(o)}this._buffer=[]}catch{this._buffering=!1,this._buffer=[]}}close(){this._closed=!0,this._buffering=!1,this._buffer=[],this._unsubStream?.(),this._stream.close()}};var K={"=":"eq","!=":"neq",">":"gt",">=":"gte","<":"lt","<=":"lte",in:"in",like:"like",not_like:"not_like"},w=class n{_state;_ctx;_createStream;constructor(t,e,r){this._ctx=t,this._state=Object.freeze({...e}),this._createStream=r}select(...t){return this._clone({columns:[...this._state.columns,...t]})}selectAll(){return this._clone({selectAll:!0})}where(t,e,r){let s={column:t,op:K[e],value:r};return this._clone({filters:[...this._state.filters,s]})}count(t="*",e="count"){return this._addAgg("count",t,e)}sum(t,e=`sum_${t}`){return this._addAgg("sum",t,e)}avg(t,e=`avg_${t}`){return this._addAgg("avg",t,e)}min(t,e=`min_${t}`){return this._addAgg("min",t,e)}max(t,e=`max_${t}`){return this._addAgg("max",t,e)}countDistinct(t,e=`count_distinct_${t}`){return this._addAgg("countDistinct",t,e)}aggregate(t,e,r){return this._addAgg(t,e,r)}groupBy(...t){return this._clone({groupBy:[...this._state.groupBy,...t]})}orderBy(t,e="asc"){return this._clone({orderBy:[...this._state.orderBy,{column:t,dir:e}]})}limit(t){return this._clone({limit:t})}timeRange(t,e,r){return this._clone({timeRange:{column:t,since:e,until:r}})}cacheTTL(t){return this._clone({cacheTTL:t})}static DEFAULT_LIMIT=1e3;async fetch(t){let e=t?.limit??this._state.limit??n.DEFAULT_LIMIT,r=this._buildAST(e),{data:s,error:o}=await l(this._ctx,{method:"POST",path:`/v1/query?table=${encodeURIComponent(this._state.table)}`,body:r,signal:t?.signal});if(o)return a(o);let i=s,c=e!=null&&i.length>=e;return c&&this._state.orderBy.length>0?x(i,!0,()=>this._fetchNext(i,e,t)):x(i,c)}stream(t){let e=this._createStream(this._state.table,t),r=this._state.filters,s=this._state.columns;return r.length===0&&s.length===0?e:new Q(e,r,s)}liveQuery(t,e){let r=this.stream(e),s=()=>this.fetch();return new b(r,s,t,this._state.filters)}then(t,e){return this.fetch().then(t,e)}_clone(t){return new n(this._ctx,{...this._state,...t},this._createStream)}_addAgg(t,e,r){let s={fn:t,column:e,alias:r};return this._clone({aggregations:[...this._state.aggregations,s]})}_buildAST(t){let e={},r=this._state.columns.length>0,s=this._state.aggregations.length>0;return this._state.selectAll?e.select_all=!0:r?e.columns=this._state.columns:s||(e.select_all=!0),s&&(e.aggregations=this._state.aggregations),this._state.filters.length>0&&(e.filters=this._state.filters),this._state.groupBy.length>0&&(e.group_by=this._state.groupBy),this._state.orderBy.length>0&&(e.order_by=this._state.orderBy),t!=null&&(e.limit=t),this._state.timeRange&&(e.time_range=this._state.timeRange),e}async _fetchNext(t,e,r){let s=this._state.orderBy[0];if(s==null)return x([],!1);let{column:o,dir:i}=s,m=t[t.length-1]?.[o];if(m===void 0)return x([],!1);let C={column:o,op:i==="desc"?"lt":"gt",value:m};return this._clone({filters:[...this._state.filters,C],orderBy:this._state.orderBy}).fetch(r)}},Q=class extends d{constructor(t,e,r){let s={onEvent:null,onStatus:null,onError:null,connect(){t.subscribe({next:o=>{if(!Y(o.data,e))return;let i=r.length>0?{...o,data:Z(o.data,r)}:o;this.onEvent?.(i)},status:o=>this.onStatus?.(o),error:o=>this.onError?.(o)})},disconnect(){t.close()}};super(s)}};function Y(n,t){for(let e of t){let r=n[e.column];if(!X(r,e.op,e.value))return!1}return!0}function X(n,t,e){switch(t){case"eq":return n===e;case"neq":return n!==e;case"gt":return B(n,e,(r,s)=>r>s);case"gte":return B(n,e,(r,s)=>r>=s);case"lt":return B(n,e,(r,s)=>r<s);case"lte":return B(n,e,(r,s)=>r<=s);case"in":return Array.isArray(e)&&e.includes(n);case"like":{if(typeof n!="string"||typeof e!="string")return!1;let s=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/%/g,".*").replace(/_/g,".");return new RegExp(`^${s}$`,"i").test(n)}case"not_like":{if(typeof n!="string"||typeof e!="string")return!1;let s=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/%/g,".*").replace(/_/g,".");return!new RegExp(`^${s}$`,"i").test(n)}default:return!0}}function B(n,t,e){return typeof n=="number"&&typeof t=="number"||typeof n=="string"&&typeof t=="string"?e(n,t):!1}function Z(n,t){let e={};for(let r of t)r in n&&(e[r]=n[r]);return e}var tt="application/x-ndjson";async function et(n){return typeof n=="string"?n:n instanceof Uint8Array?new TextDecoder().decode(n):new Response(n).text()}var v=class{_ctx;_table;_createStream;constructor(t,e,r){this._ctx=t,this._table=e,this._createStream=r}async fetch(t){return this.select().limit(t?.limit??1e3).fetch(t)}select(...t){return new w(this._ctx,{table:this._table,columns:t,aggregations:[],filters:[],groupBy:[],orderBy:[]},this._createStream)}selectAll(){return this.select().selectAll()}async insert(t,e){if(Array.isArray(t)){if(t.length===0)return u({ok:!0,total:0,succeeded:0,failed:0,duplicates:0,results:[]});let i=t.map(c=>JSON.stringify(c)).join(`
|
|
2
|
+
`);return this._sendNDJSON(i,e)}let{data:r,error:s}=await l(this._ctx,{method:"POST",path:`/v1/ingest?table=${encodeURIComponent(this._table)}`,body:t,signal:e?.signal});if(s)return a(s);let o={ok:r?.ok??!0};return r?.duplicate!=null&&(o.duplicate=r.duplicate),u(o)}async insertNDJSON(t,e){let r=await et(t);return this._sendNDJSON(r,e)}async _sendNDJSON(t,e){let{data:r,error:s}=await l(this._ctx,{method:"POST",path:`/v1/ingest?table=${encodeURIComponent(this._table)}`,rawBody:t,contentType:tt,signal:e?.signal});if(s)return a(s);let o=r??{total:0,succeeded:0,failed:0,duplicates:0},i={ok:o.failed===0,total:o.total,succeeded:o.succeeded,failed:o.failed,duplicates:o.duplicates};return o.results&&o.results.length>0&&(i.results=o.results),u(i)}async schema(t){let{data:e,error:r}=await l(this._ctx,{method:"GET",path:`/v1/ops/schema?table=${encodeURIComponent(this._table)}`,signal:t?.signal});return r?a(r):u(e)}stream(t){return this._createStream(this._table,t)}};var P=class{_ctx;schema;policy;dlq;sys;pipes;constructor(t){this._ctx={baseURL:t.baseURL.replace(/\/+$/,""),auth:t.auth,options:{maxRetries:t.options?.maxRetries??2,fetch:t.options?.fetch,headers:t.options?.headers,fetchOptions:t.options?.fetchOptions}},this.schema=new S(this._ctx),this.policy=new _(this._ctx),this.dlq=new f(this._ctx,(e,r)=>this._createStream(e,r)),this.sys=new y(this._ctx),this.pipes=new R(this._ctx)}from(t){return new v(this._ctx,t,(e,r)=>this._createStream(e,r))}pipe(t,e){return new g(this._ctx,t,e,(r,s)=>this._createStream(r,s))}sql(t,e){if(Array.isArray(e))throw new Error("[WaveHouse SDK] client.sql(sql, params) was removed. The /v1/ops/query endpoint does not accept positional `?` params. Inline literals into the SQL, or use the structured query builder (wh.from(table)\u2026) for safe binding from user input.");return F(this._ctx,t,e)}_createStream(t,e){if(typeof EventSource>"u")throw new Error("[WaveHouse SDK] Native EventSource is not available in this environment. Please provide a global polyfill (e.g., `globalThis.EventSource = require('eventsource')`).");let r=new A({baseURL:this._ctx.baseURL,table:t,since:e?.since,auth:this._ctx.auth}),s=new d(r);return e?.signal&&s.attachSignal(e.signal),s}};function I(n){return new P(n)}return M(rt);})();
|
|
3
3
|
//# sourceMappingURL=index.global.js.map
|