@wavehouse/sdk 0.0.0-dev.0f8826c → 0.0.0-dev.h04a85f20bb96
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 +48 -7
- package/dist/cli/codegen.js +173 -0
- package/dist/index.cjs +152 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +236 -11
- package/dist/index.d.ts +236 -11
- package/dist/index.global.js +3 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +152 -45
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
package/dist/index.js
CHANGED
|
@@ -35,29 +35,64 @@ function err(error) {
|
|
|
35
35
|
return { ok: false, data: null, error };
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// src/url.ts
|
|
39
|
+
function resolveURL(base, path, params) {
|
|
40
|
+
const root = new URL(base);
|
|
41
|
+
root.search = "";
|
|
42
|
+
root.hash = "";
|
|
43
|
+
if (!root.pathname.endsWith("/")) root.pathname += "/";
|
|
44
|
+
const url = new URL(path.replace(/^\/+/, ""), root);
|
|
45
|
+
if (params) {
|
|
46
|
+
for (const [k, v] of Object.entries(params)) {
|
|
47
|
+
url.searchParams.set(k, v);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return url;
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
// src/http.ts
|
|
54
|
+
function mergeHeaders(base, extra) {
|
|
55
|
+
if (!extra) return base;
|
|
56
|
+
const sdkNames = new Set(Object.keys(base).map((k) => k.toLowerCase()));
|
|
57
|
+
const merged = { ...base };
|
|
58
|
+
const configured = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const [name, value] of Object.entries(extra)) {
|
|
60
|
+
const lower = name.toLowerCase();
|
|
61
|
+
if (sdkNames.has(lower)) continue;
|
|
62
|
+
const prior = configured.get(lower);
|
|
63
|
+
if (prior !== void 0) delete merged[prior];
|
|
64
|
+
merged[name] = value;
|
|
65
|
+
configured.set(lower, name);
|
|
66
|
+
}
|
|
67
|
+
return merged;
|
|
68
|
+
}
|
|
39
69
|
async function request(ctx, opts) {
|
|
40
|
-
const url =
|
|
70
|
+
const url = resolveURL(ctx.baseURL, opts.path, opts.params).toString();
|
|
41
71
|
const headers = {
|
|
42
|
-
"Content-Type": "application/json",
|
|
72
|
+
"Content-Type": opts.contentType ?? "application/json",
|
|
43
73
|
Accept: "application/json"
|
|
44
74
|
};
|
|
75
|
+
const requestBody = opts.rawBody !== void 0 ? opts.rawBody : opts.body !== void 0 ? JSON.stringify(opts.body) : void 0;
|
|
45
76
|
if (ctx.auth) {
|
|
46
77
|
const token = await ctx.auth();
|
|
47
78
|
if (token) {
|
|
48
79
|
headers.Authorization = `Bearer ${token}`;
|
|
49
80
|
}
|
|
50
81
|
}
|
|
82
|
+
const finalHeaders = mergeHeaders(headers, ctx.options.headers);
|
|
51
83
|
let lastError = null;
|
|
52
84
|
const maxAttempts = ctx.options.maxRetries + 1;
|
|
53
85
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
54
86
|
try {
|
|
55
|
-
const
|
|
87
|
+
const init = {
|
|
88
|
+
...ctx.options.fetchOptions,
|
|
56
89
|
method: opts.method,
|
|
57
|
-
headers,
|
|
58
|
-
body:
|
|
90
|
+
headers: finalHeaders,
|
|
91
|
+
body: requestBody,
|
|
59
92
|
signal: opts.signal
|
|
60
|
-
}
|
|
93
|
+
};
|
|
94
|
+
const doFetch = ctx.options.fetch;
|
|
95
|
+
const res = doFetch ? await doFetch(url, init) : await fetch(url, init);
|
|
61
96
|
if (res.ok) {
|
|
62
97
|
const text = await res.text();
|
|
63
98
|
const data = text ? JSON.parse(text) : void 0;
|
|
@@ -95,15 +130,6 @@ async function request(ctx, opts) {
|
|
|
95
130
|
}
|
|
96
131
|
return { data: null, error: lastError, headers: new Headers() };
|
|
97
132
|
}
|
|
98
|
-
function buildURL(base, path, params) {
|
|
99
|
-
const url = new URL(path, base.endsWith("/") ? base : `${base}/`);
|
|
100
|
-
if (params) {
|
|
101
|
-
for (const [k, v] of Object.entries(params)) {
|
|
102
|
-
url.searchParams.set(k, v);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return url.toString();
|
|
106
|
-
}
|
|
107
133
|
function backoff(attempt) {
|
|
108
134
|
return Math.min(1e3 * 2 ** attempt, 3e4);
|
|
109
135
|
}
|
|
@@ -172,7 +198,14 @@ var PipeRef = class {
|
|
|
172
198
|
this._params = params;
|
|
173
199
|
this._createStream = createStream;
|
|
174
200
|
}
|
|
175
|
-
/**
|
|
201
|
+
/**
|
|
202
|
+
* Execute the pipe and return results.
|
|
203
|
+
*
|
|
204
|
+
* Takes only `signal` — deliberately narrower than the `RequestOptions` the
|
|
205
|
+
* query builder accepts. The pipes endpoint binds the body as the pipe's
|
|
206
|
+
* parameters, so there is no row cap to forward; give the pipe a `{{limit}}`
|
|
207
|
+
* parameter in its SQL and pass it via `wh.pipe(name, { limit })`.
|
|
208
|
+
*/
|
|
176
209
|
async fetch(opts) {
|
|
177
210
|
const { data, error } = await request(this._ctx, {
|
|
178
211
|
method: "POST",
|
|
@@ -520,7 +553,7 @@ var SSETransport = class {
|
|
|
520
553
|
this.onStatus?.("closed");
|
|
521
554
|
}
|
|
522
555
|
async _doConnect() {
|
|
523
|
-
const url =
|
|
556
|
+
const url = resolveURL(this._opts.baseURL, "/v1/stream");
|
|
524
557
|
url.searchParams.set("table", this._opts.table);
|
|
525
558
|
if (this._opts.since) {
|
|
526
559
|
url.searchParams.set("since", this._opts.since);
|
|
@@ -690,6 +723,15 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
690
723
|
select(...columns) {
|
|
691
724
|
return this._clone({ columns: [...this._state.columns, ...columns] });
|
|
692
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Select every column the caller's role is allowed to read (the all-columns
|
|
728
|
+
* wildcard). Use this instead of `.select(...)` when you want a full-row read;
|
|
729
|
+
* a bare `.fetch()` with no `.select()` does this implicitly. Mutually
|
|
730
|
+
* exclusive with `.select(...)`.
|
|
731
|
+
*/
|
|
732
|
+
selectAll() {
|
|
733
|
+
return this._clone({ selectAll: true });
|
|
734
|
+
}
|
|
693
735
|
where(column, op, value) {
|
|
694
736
|
const filter = { column, op: OP_MAP[op], value };
|
|
695
737
|
return this._clone({ filters: [...this._state.filters, filter] });
|
|
@@ -731,7 +773,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
731
773
|
return this._clone({ cacheTTL: seconds });
|
|
732
774
|
}
|
|
733
775
|
// --- Execution ---
|
|
734
|
-
/** Default row limit when none is specified
|
|
776
|
+
/** Default row limit when none is specified — deliberately tighter than the backend's DefaultMaxRows (10000) safety cap. */
|
|
735
777
|
static DEFAULT_LIMIT = 1e3;
|
|
736
778
|
async fetch(opts) {
|
|
737
779
|
const effectiveLimit = opts?.limit ?? this._state.limit ?? _QueryBuilder.DEFAULT_LIMIT;
|
|
@@ -745,11 +787,11 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
745
787
|
if (error) return err(error);
|
|
746
788
|
const rows = data;
|
|
747
789
|
const hasMore = effectiveLimit != null && rows.length >= effectiveLimit;
|
|
748
|
-
if (hasMore) {
|
|
790
|
+
if (hasMore && this._state.orderBy.length > 0) {
|
|
749
791
|
const nextFn = () => this._fetchNext(rows, effectiveLimit, opts);
|
|
750
792
|
return okPage(rows, true, nextFn);
|
|
751
793
|
}
|
|
752
|
-
return okPage(rows,
|
|
794
|
+
return okPage(rows, hasMore);
|
|
753
795
|
}
|
|
754
796
|
stream(opts) {
|
|
755
797
|
const raw = this._createStream(this._state.table, opts);
|
|
@@ -788,31 +830,35 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
788
830
|
}
|
|
789
831
|
_buildAST(effectiveLimit) {
|
|
790
832
|
const ast = {};
|
|
791
|
-
|
|
792
|
-
|
|
833
|
+
const hasColumns = this._state.columns.length > 0;
|
|
834
|
+
const hasAggs = this._state.aggregations.length > 0;
|
|
835
|
+
if (this._state.selectAll) {
|
|
836
|
+
ast.select_all = true;
|
|
837
|
+
} else if (hasColumns) {
|
|
838
|
+
ast.columns = this._state.columns;
|
|
839
|
+
} else if (!hasAggs) {
|
|
840
|
+
ast.select_all = true;
|
|
841
|
+
}
|
|
842
|
+
if (hasAggs) ast.aggregations = this._state.aggregations;
|
|
793
843
|
if (this._state.filters.length > 0) ast.filters = this._state.filters;
|
|
794
844
|
if (this._state.groupBy.length > 0) ast.group_by = this._state.groupBy;
|
|
795
|
-
if (this._state.orderBy.length > 0)
|
|
796
|
-
ast.order_by = this._state.orderBy;
|
|
797
|
-
} else if (effectiveLimit != null && this._state.aggregations.length === 0) {
|
|
798
|
-
ast.order_by = [{ column: "received_timestamp", dir: "desc" }];
|
|
799
|
-
}
|
|
845
|
+
if (this._state.orderBy.length > 0) ast.order_by = this._state.orderBy;
|
|
800
846
|
if (effectiveLimit != null) ast.limit = effectiveLimit;
|
|
801
847
|
if (this._state.timeRange) ast.time_range = this._state.timeRange;
|
|
802
848
|
return ast;
|
|
803
849
|
}
|
|
804
850
|
async _fetchNext(prevRows, _limit, opts) {
|
|
805
|
-
const
|
|
806
|
-
|
|
851
|
+
const cursor = this._state.orderBy[0];
|
|
852
|
+
if (cursor == null) return okPage([], false);
|
|
853
|
+
const { column: orderCol, dir: orderDir } = cursor;
|
|
807
854
|
const lastRow = prevRows[prevRows.length - 1];
|
|
808
855
|
const lastValue = lastRow?.[orderCol];
|
|
809
856
|
if (lastValue === void 0) return okPage([], false);
|
|
810
857
|
const cursorOp = orderDir === "desc" ? "lt" : "gt";
|
|
811
858
|
const cursorFilter = { column: orderCol, op: cursorOp, value: lastValue };
|
|
812
|
-
const orderBy = this._state.orderBy.length > 0 ? this._state.orderBy : [{ column: orderCol, dir: orderDir }];
|
|
813
859
|
const nextBuilder = this._clone({
|
|
814
860
|
filters: [...this._state.filters, cursorFilter],
|
|
815
|
-
orderBy
|
|
861
|
+
orderBy: this._state.orderBy
|
|
816
862
|
});
|
|
817
863
|
return nextBuilder.fetch(opts);
|
|
818
864
|
}
|
|
@@ -903,6 +949,12 @@ function projectColumns(row, columns) {
|
|
|
903
949
|
}
|
|
904
950
|
|
|
905
951
|
// src/table.ts
|
|
952
|
+
var NDJSON_CONTENT_TYPE = "application/x-ndjson";
|
|
953
|
+
async function ndjsonSourceToString(source) {
|
|
954
|
+
if (typeof source === "string") return source;
|
|
955
|
+
if (source instanceof Uint8Array) return new TextDecoder().decode(source);
|
|
956
|
+
return new Response(source).text();
|
|
957
|
+
}
|
|
906
958
|
var TableRef = class {
|
|
907
959
|
_ctx;
|
|
908
960
|
_table;
|
|
@@ -931,22 +983,34 @@ var TableRef = class {
|
|
|
931
983
|
this._createStream
|
|
932
984
|
);
|
|
933
985
|
}
|
|
934
|
-
/**
|
|
986
|
+
/**
|
|
987
|
+
* Start a query that selects every column the caller's role is allowed to read
|
|
988
|
+
* (the all-columns wildcard). Use instead of `.select(...)` for an explicit
|
|
989
|
+
* full-row read; `.fetch()` does this implicitly.
|
|
990
|
+
*/
|
|
991
|
+
selectAll() {
|
|
992
|
+
return this.select().selectAll();
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Insert one or more rows into this table.
|
|
996
|
+
*
|
|
997
|
+
* A single object is sent as a JSON `POST /v1/ingest`. An **array** is
|
|
998
|
+
* serialized to NDJSON (one record per line) and sent as a single
|
|
999
|
+
* `application/x-ndjson` request: a bad record no longer fails or hides the
|
|
1000
|
+
* rest of the batch — per-record outcomes come back in the result
|
|
1001
|
+
* (`failed` / `results`), and `ok` is true only when every record succeeded.
|
|
1002
|
+
*
|
|
1003
|
+
* The array path sends one request regardless of size; bounded-concurrency
|
|
1004
|
+
* chunking of very large arrays is tracked separately (#196). For NDJSON you
|
|
1005
|
+
* already have (a file or stream), use {@link insertNDJSON}.
|
|
1006
|
+
*/
|
|
935
1007
|
async insert(data, opts) {
|
|
936
1008
|
if (Array.isArray(data)) {
|
|
937
|
-
|
|
938
|
-
(
|
|
939
|
-
method: "POST",
|
|
940
|
-
path: `/v1/ingest?table=${encodeURIComponent(this._table)}`,
|
|
941
|
-
body: row,
|
|
942
|
-
signal: opts?.signal
|
|
943
|
-
})
|
|
944
|
-
);
|
|
945
|
-
const results = await Promise.all(promises);
|
|
946
|
-
for (const res2 of results) {
|
|
947
|
-
if (res2.error) return err(res2.error);
|
|
1009
|
+
if (data.length === 0) {
|
|
1010
|
+
return ok({ ok: true, total: 0, succeeded: 0, failed: 0, duplicates: 0, results: [] });
|
|
948
1011
|
}
|
|
949
|
-
|
|
1012
|
+
const ndjson = data.map((row) => JSON.stringify(row)).join("\n");
|
|
1013
|
+
return this._sendNDJSON(ndjson, opts);
|
|
950
1014
|
}
|
|
951
1015
|
const { data: res, error } = await request(this._ctx, {
|
|
952
1016
|
method: "POST",
|
|
@@ -959,6 +1023,46 @@ var TableRef = class {
|
|
|
959
1023
|
if (res?.duplicate != null) result.duplicate = res.duplicate;
|
|
960
1024
|
return ok(result);
|
|
961
1025
|
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Insert pre-formatted NDJSON (newline-delimited JSON, one record per line)
|
|
1028
|
+
* from a string, raw bytes, a Blob/File, or a byte stream — e.g. a `.ndjson`
|
|
1029
|
+
* file or a stream produced by another system. For in-memory rows, prefer
|
|
1030
|
+
* {@link insert}.
|
|
1031
|
+
*
|
|
1032
|
+
* Non-string sources are read fully into memory before sending (the server
|
|
1033
|
+
* streams the parse). Returns the same per-record batch summary as an array
|
|
1034
|
+
* `insert`.
|
|
1035
|
+
*/
|
|
1036
|
+
async insertNDJSON(source, opts) {
|
|
1037
|
+
const ndjson = await ndjsonSourceToString(source);
|
|
1038
|
+
return this._sendNDJSON(ndjson, opts);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* @internal Send an NDJSON body and map the server's batch response onto an
|
|
1042
|
+
* {@link InsertResult}. A transport / whole-request failure surfaces as the
|
|
1043
|
+
* Result error arm; a processed batch (even with rejected records) surfaces
|
|
1044
|
+
* as the success arm with `ok = failed === 0`.
|
|
1045
|
+
*/
|
|
1046
|
+
async _sendNDJSON(ndjson, opts) {
|
|
1047
|
+
const { data, error } = await request(this._ctx, {
|
|
1048
|
+
method: "POST",
|
|
1049
|
+
path: `/v1/ingest?table=${encodeURIComponent(this._table)}`,
|
|
1050
|
+
rawBody: ndjson,
|
|
1051
|
+
contentType: NDJSON_CONTENT_TYPE,
|
|
1052
|
+
signal: opts?.signal
|
|
1053
|
+
});
|
|
1054
|
+
if (error) return err(error);
|
|
1055
|
+
const r = data ?? { total: 0, succeeded: 0, failed: 0, duplicates: 0 };
|
|
1056
|
+
const result = {
|
|
1057
|
+
ok: r.failed === 0,
|
|
1058
|
+
total: r.total,
|
|
1059
|
+
succeeded: r.succeeded,
|
|
1060
|
+
failed: r.failed,
|
|
1061
|
+
duplicates: r.duplicates
|
|
1062
|
+
};
|
|
1063
|
+
if (r.results && r.results.length > 0) result.results = r.results;
|
|
1064
|
+
return ok(result);
|
|
1065
|
+
}
|
|
962
1066
|
/** Fetch the schema for this table. */
|
|
963
1067
|
async schema(opts) {
|
|
964
1068
|
const { data, error } = await request(this._ctx, {
|
|
@@ -994,7 +1098,10 @@ var WaveHouseClient = class {
|
|
|
994
1098
|
baseURL: config.baseURL.replace(/\/+$/, ""),
|
|
995
1099
|
auth: config.auth,
|
|
996
1100
|
options: {
|
|
997
|
-
maxRetries: config.options?.maxRetries ?? 2
|
|
1101
|
+
maxRetries: config.options?.maxRetries ?? 2,
|
|
1102
|
+
fetch: config.options?.fetch,
|
|
1103
|
+
headers: config.options?.headers,
|
|
1104
|
+
fetchOptions: config.options?.fetchOptions
|
|
998
1105
|
}
|
|
999
1106
|
};
|
|
1000
1107
|
this.schema = new SchemaNamespace(this._ctx);
|