@dashai/sdk 0.7.0
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/LICENSE +21 -0
- package/README.md +377 -0
- package/dist/auth/client.cjs +185 -0
- package/dist/auth/client.cjs.map +1 -0
- package/dist/auth/client.d.cts +137 -0
- package/dist/auth/client.d.ts +137 -0
- package/dist/auth/client.js +175 -0
- package/dist/auth/client.js.map +1 -0
- package/dist/auth/middleware.cjs +352 -0
- package/dist/auth/middleware.cjs.map +1 -0
- package/dist/auth/middleware.d.cts +90 -0
- package/dist/auth/middleware.d.ts +90 -0
- package/dist/auth/middleware.js +343 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/server.cjs +445 -0
- package/dist/auth/server.cjs.map +1 -0
- package/dist/auth/server.d.cts +170 -0
- package/dist/auth/server.d.ts +170 -0
- package/dist/auth/server.js +432 -0
- package/dist/auth/server.js.map +1 -0
- package/dist/auth/types.cjs +31 -0
- package/dist/auth/types.cjs.map +1 -0
- package/dist/auth/types.d.cts +163 -0
- package/dist/auth/types.d.ts +163 -0
- package/dist/auth/types.js +29 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/deps/index.cjs +117 -0
- package/dist/deps/index.cjs.map +1 -0
- package/dist/deps/index.d.cts +93 -0
- package/dist/deps/index.d.ts +93 -0
- package/dist/deps/index.js +112 -0
- package/dist/deps/index.js.map +1 -0
- package/dist/errors-BV75u7b9.d.cts +207 -0
- package/dist/errors-BV75u7b9.d.ts +207 -0
- package/dist/index.cjs +721 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +171 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.js +703 -0
- package/dist/index.js.map +1 -0
- package/dist/query/index.cjs +621 -0
- package/dist/query/index.cjs.map +1 -0
- package/dist/query/index.d.cts +800 -0
- package/dist/query/index.d.ts +800 -0
- package/dist/query/index.js +617 -0
- package/dist/query/index.js.map +1 -0
- package/dist/react/index.cjs +199 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +117 -0
- package/dist/react/index.d.ts +117 -0
- package/dist/react/index.js +195 -0
- package/dist/react/index.js.map +1 -0
- package/dist/types-BLNQ1S1C.d.cts +396 -0
- package/dist/types-BLNQ1S1C.d.ts +396 -0
- package/package.json +109 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
// src/data/errors.ts
|
|
2
|
+
var DashwiseErrorCode = {
|
|
3
|
+
// Auth / authz
|
|
4
|
+
UNAUTHENTICATED: "UNAUTHENTICATED",
|
|
5
|
+
FORBIDDEN: "FORBIDDEN",
|
|
6
|
+
TOKEN_EXPIRED: "TOKEN_EXPIRED",
|
|
7
|
+
// Contract enforcement
|
|
8
|
+
CONTRACT_VIOLATION: "CONTRACT_VIOLATION",
|
|
9
|
+
FIELD_NOT_READABLE: "FIELD_NOT_READABLE",
|
|
10
|
+
FILTER_OP_UNKNOWN: "FILTER_OP_UNKNOWN",
|
|
11
|
+
OPERATION_NOT_ALLOWED: "OPERATION_NOT_ALLOWED",
|
|
12
|
+
// Cross-module dependencies (Phase 1 of the SDK query plane —
|
|
13
|
+
// see dashwise-devops-docs/plans/modules-sdk-query-v1.md).
|
|
14
|
+
// Surfaced when reading borrowed tables via `client.deps(slug)`.
|
|
15
|
+
DEPENDENCY_NOT_DECLARED: "DEPENDENCY_NOT_DECLARED",
|
|
16
|
+
TABLE_NOT_IN_CONTRACT: "TABLE_NOT_IN_CONTRACT",
|
|
17
|
+
PROVIDER_TABLE_MISSING: "PROVIDER_TABLE_MISSING",
|
|
18
|
+
OPERATION_NOT_ALLOWED_BY_PROVIDER: "OPERATION_NOT_ALLOWED_BY_PROVIDER",
|
|
19
|
+
// Query-plane joins (Phase 3 — see
|
|
20
|
+
// dashwise-devops-docs/plans/modules-sdk-query-v1-p3-execution.md).
|
|
21
|
+
// Surfaced when `.join('<slug>')` / `.leftJoin('<slug>')` can't
|
|
22
|
+
// resolve the target. Pre-declared in slice 3.1 so slice 3.2's
|
|
23
|
+
// backend translator can emit them through fromBackendEnvelope
|
|
24
|
+
// without an SDK roundtrip.
|
|
25
|
+
LINK_FIELD_NOT_FOUND: "LINK_FIELD_NOT_FOUND",
|
|
26
|
+
NOT_A_LINK_FIELD: "NOT_A_LINK_FIELD",
|
|
27
|
+
JOIN_NOT_LINKED: "JOIN_NOT_LINKED",
|
|
28
|
+
// Cross-module actions (Phase 7 slice 7.3a — see
|
|
29
|
+
// dashwise-devops-docs/plans/modules-sdk-query-v1-p7-cross-module-writes.md).
|
|
30
|
+
// Surfaced when `qb.deps.<dep>.actions.<name>(input)` (generated
|
|
31
|
+
// factory) dispatches against the new backend endpoint
|
|
32
|
+
// `POST :installationId/deps/:providerSlug/actions/:actionName`.
|
|
33
|
+
// Slice 7.2b ships the backend emitter; slice 7.3a (this slice)
|
|
34
|
+
// reserves the codes + maps them to DepActionError in the SDK.
|
|
35
|
+
ACTION_NOT_EXPOSED: "ACTION_NOT_EXPOSED",
|
|
36
|
+
ACTION_NOT_DECLARED: "ACTION_NOT_DECLARED",
|
|
37
|
+
ACTION_INPUT_INVALID: "ACTION_INPUT_INVALID",
|
|
38
|
+
ACTION_OUTPUT_INVALID: "ACTION_OUTPUT_INVALID",
|
|
39
|
+
ACTION_RATE_LIMITED: "ACTION_RATE_LIMITED",
|
|
40
|
+
ACTION_EXECUTION_FAILED: "ACTION_EXECUTION_FAILED",
|
|
41
|
+
// Data plane
|
|
42
|
+
ROW_NOT_FOUND: "ROW_NOT_FOUND",
|
|
43
|
+
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
44
|
+
// Server
|
|
45
|
+
INTERNAL_ERROR: "INTERNAL_ERROR"
|
|
46
|
+
};
|
|
47
|
+
var DashwiseError = class extends Error {
|
|
48
|
+
code;
|
|
49
|
+
status;
|
|
50
|
+
retriable;
|
|
51
|
+
context;
|
|
52
|
+
/**
|
|
53
|
+
* Backend-issued trace anchor (Phase 6 slice 6.2, 2026-05-19).
|
|
54
|
+
* Mirrors the `x-request-id` response header. Populated whenever
|
|
55
|
+
* the backend's `RuntimeQueryController` (or any future endpoint
|
|
56
|
+
* that echoes the header) is the origin of this error. `null` when
|
|
57
|
+
* the failure is client-side (NetworkError / TimeoutError — the
|
|
58
|
+
* request never reached the server, so there's no server-issued
|
|
59
|
+
* ID to surface).
|
|
60
|
+
*
|
|
61
|
+
* Use this as a debugging trace anchor when filing a bug against a
|
|
62
|
+
* specific failed query — the matching row in `module_query_log`
|
|
63
|
+
* carries the full backend context (ast_hash, error_code,
|
|
64
|
+
* duration_ms, etc.).
|
|
65
|
+
*/
|
|
66
|
+
requestId;
|
|
67
|
+
constructor(code, message, options = {}) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = this.constructor.name;
|
|
70
|
+
this.code = code;
|
|
71
|
+
this.status = options.status ?? 0;
|
|
72
|
+
this.retriable = options.retriable ?? false;
|
|
73
|
+
this.context = options.context;
|
|
74
|
+
this.requestId = options.requestId ?? null;
|
|
75
|
+
if (options.cause !== void 0) {
|
|
76
|
+
this.cause = options.cause;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var UnauthenticatedError = class extends DashwiseError {
|
|
81
|
+
constructor(message = "Authentication required", requestId = null) {
|
|
82
|
+
super(DashwiseErrorCode.UNAUTHENTICATED, message, { status: 401, requestId });
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var ForbiddenError = class extends DashwiseError {
|
|
86
|
+
constructor(message, context, requestId = null) {
|
|
87
|
+
super(DashwiseErrorCode.FORBIDDEN, message, { status: 403, context, requestId });
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var RowNotFoundError = class extends DashwiseError {
|
|
91
|
+
constructor(message, context, requestId = null) {
|
|
92
|
+
super(DashwiseErrorCode.ROW_NOT_FOUND, message, { status: 404, context, requestId });
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var ContractViolationError = class extends DashwiseError {
|
|
96
|
+
constructor(message, context, requestId = null) {
|
|
97
|
+
super(DashwiseErrorCode.CONTRACT_VIOLATION, message, {
|
|
98
|
+
status: 403,
|
|
99
|
+
context,
|
|
100
|
+
requestId
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var ValidationError = class extends DashwiseError {
|
|
105
|
+
constructor(message, context, requestId = null) {
|
|
106
|
+
super(DashwiseErrorCode.VALIDATION_FAILED, message, {
|
|
107
|
+
status: 400,
|
|
108
|
+
context,
|
|
109
|
+
requestId
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var DependencyContractError = class extends DashwiseError {
|
|
114
|
+
constructor(code, message, context, requestId = null) {
|
|
115
|
+
super(code, message, { status: 404, context, requestId });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var ProviderTableMissingError = class extends DashwiseError {
|
|
119
|
+
constructor(message, context, requestId = null) {
|
|
120
|
+
super(DashwiseErrorCode.PROVIDER_TABLE_MISSING, message, {
|
|
121
|
+
status: 404,
|
|
122
|
+
context,
|
|
123
|
+
requestId
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var OperationNotAllowedByProviderError = class extends DashwiseError {
|
|
128
|
+
constructor(message, context, requestId = null) {
|
|
129
|
+
super(DashwiseErrorCode.OPERATION_NOT_ALLOWED_BY_PROVIDER, message, {
|
|
130
|
+
status: 403,
|
|
131
|
+
context,
|
|
132
|
+
requestId
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
var JoinError = class extends DashwiseError {
|
|
137
|
+
constructor(code, message, context, requestId = null) {
|
|
138
|
+
super(code, message, { status: 400, context, requestId });
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var DepActionError = class extends DashwiseError {
|
|
142
|
+
constructor(code, message, status, context, requestId = null) {
|
|
143
|
+
super(code, message, {
|
|
144
|
+
status,
|
|
145
|
+
retriable: code === DashwiseErrorCode.ACTION_RATE_LIMITED,
|
|
146
|
+
context,
|
|
147
|
+
requestId
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function fromBackendEnvelope(status, envelope, requestId = null) {
|
|
152
|
+
const code = envelope?.code ?? codeFromStatus(status);
|
|
153
|
+
const message = envelope?.message ?? `HTTP ${status}`;
|
|
154
|
+
const context = envelope?.context;
|
|
155
|
+
switch (code) {
|
|
156
|
+
case DashwiseErrorCode.UNAUTHENTICATED:
|
|
157
|
+
case DashwiseErrorCode.TOKEN_EXPIRED:
|
|
158
|
+
return new UnauthenticatedError(message, requestId);
|
|
159
|
+
case DashwiseErrorCode.FORBIDDEN:
|
|
160
|
+
return new ForbiddenError(message, context, requestId);
|
|
161
|
+
case DashwiseErrorCode.ROW_NOT_FOUND:
|
|
162
|
+
return new RowNotFoundError(message, context, requestId);
|
|
163
|
+
case DashwiseErrorCode.CONTRACT_VIOLATION:
|
|
164
|
+
case DashwiseErrorCode.FIELD_NOT_READABLE:
|
|
165
|
+
case DashwiseErrorCode.FILTER_OP_UNKNOWN:
|
|
166
|
+
case DashwiseErrorCode.OPERATION_NOT_ALLOWED:
|
|
167
|
+
return new ContractViolationError(message, context, requestId);
|
|
168
|
+
case DashwiseErrorCode.VALIDATION_FAILED:
|
|
169
|
+
return new ValidationError(message, context, requestId);
|
|
170
|
+
case DashwiseErrorCode.DEPENDENCY_NOT_DECLARED:
|
|
171
|
+
case DashwiseErrorCode.TABLE_NOT_IN_CONTRACT:
|
|
172
|
+
return new DependencyContractError(code, message, context, requestId);
|
|
173
|
+
case DashwiseErrorCode.PROVIDER_TABLE_MISSING:
|
|
174
|
+
return new ProviderTableMissingError(message, context, requestId);
|
|
175
|
+
case DashwiseErrorCode.OPERATION_NOT_ALLOWED_BY_PROVIDER:
|
|
176
|
+
return new OperationNotAllowedByProviderError(message, context, requestId);
|
|
177
|
+
case DashwiseErrorCode.LINK_FIELD_NOT_FOUND:
|
|
178
|
+
case DashwiseErrorCode.NOT_A_LINK_FIELD:
|
|
179
|
+
case DashwiseErrorCode.JOIN_NOT_LINKED:
|
|
180
|
+
return new JoinError(code, message, context, requestId);
|
|
181
|
+
case DashwiseErrorCode.ACTION_NOT_EXPOSED:
|
|
182
|
+
case DashwiseErrorCode.ACTION_NOT_DECLARED:
|
|
183
|
+
return new DepActionError(code, message, 403, context, requestId);
|
|
184
|
+
case DashwiseErrorCode.ACTION_INPUT_INVALID:
|
|
185
|
+
return new DepActionError(code, message, 400, context, requestId);
|
|
186
|
+
case DashwiseErrorCode.ACTION_OUTPUT_INVALID:
|
|
187
|
+
return new DepActionError(code, message, 500, context, requestId);
|
|
188
|
+
case DashwiseErrorCode.ACTION_RATE_LIMITED:
|
|
189
|
+
return new DepActionError(code, message, 429, context, requestId);
|
|
190
|
+
case DashwiseErrorCode.ACTION_EXECUTION_FAILED:
|
|
191
|
+
return new DepActionError(code, message, 200, context, requestId);
|
|
192
|
+
default:
|
|
193
|
+
return new DashwiseError(code, message, {
|
|
194
|
+
status,
|
|
195
|
+
retriable: envelope?.retriable ?? false,
|
|
196
|
+
context,
|
|
197
|
+
requestId
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function codeFromStatus(status) {
|
|
202
|
+
if (status === 401) return DashwiseErrorCode.UNAUTHENTICATED;
|
|
203
|
+
if (status === 403) return DashwiseErrorCode.FORBIDDEN;
|
|
204
|
+
if (status === 404) return DashwiseErrorCode.ROW_NOT_FOUND;
|
|
205
|
+
if (status >= 500) return DashwiseErrorCode.INTERNAL_ERROR;
|
|
206
|
+
return "HTTP_ERROR";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/query/ast.ts
|
|
210
|
+
function isQueryAst(v) {
|
|
211
|
+
if (!v || typeof v !== "object") return false;
|
|
212
|
+
const a = v;
|
|
213
|
+
if (a.v !== 1) return false;
|
|
214
|
+
if (!a.from || typeof a.from !== "object") return false;
|
|
215
|
+
const f = a.from;
|
|
216
|
+
if (typeof f.kind !== "string") return false;
|
|
217
|
+
if (f.kind === "owned" || f.kind === "view") {
|
|
218
|
+
return typeof f.slug === "string";
|
|
219
|
+
}
|
|
220
|
+
if (f.kind === "dep") {
|
|
221
|
+
return typeof f.module === "string" && typeof f.slug === "string";
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
var CURRENT_QUERY_AST_VERSION = 1;
|
|
226
|
+
|
|
227
|
+
// src/query/builder.ts
|
|
228
|
+
var TABLE_SLUG_REGEX = /^[a-z][a-z0-9_]*[a-z0-9]$/;
|
|
229
|
+
var MODULE_SLUG_REGEX = /^[a-z][a-z0-9-]*[a-z0-9]$/;
|
|
230
|
+
function assertValidTableSlug(slug) {
|
|
231
|
+
if (typeof slug !== "string" || !TABLE_SLUG_REGEX.test(slug)) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`@dashai/sdk/query: invalid table slug "${slug}". Slugs must match /^[a-z][a-z0-9_]*[a-z0-9]$/ (snake_case).`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function assertValidModuleSlug(slug) {
|
|
238
|
+
if (typeof slug !== "string" || !MODULE_SLUG_REGEX.test(slug)) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`@dashai/sdk/query: invalid module slug "${slug}". Module slugs must match /^[a-z][a-z0-9-]*[a-z0-9]$/ (kebab-case).`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function makeQb(client) {
|
|
245
|
+
return {
|
|
246
|
+
// Single runtime function backs all three overloads — TypeScript
|
|
247
|
+
// narrows at the call site, but at runtime we discriminate on the
|
|
248
|
+
// input shape:
|
|
249
|
+
// string → own-table slug
|
|
250
|
+
// { kind: 'dep', ... } → cross-module dep ref (slice 4.1b)
|
|
251
|
+
// { kind: 'view', slug } → view ref (slice 5.4b)
|
|
252
|
+
from(target) {
|
|
253
|
+
let ref;
|
|
254
|
+
if (typeof target === "string") {
|
|
255
|
+
assertValidTableSlug(target);
|
|
256
|
+
ref = { kind: "owned", slug: target };
|
|
257
|
+
} else if (target && typeof target === "object" && target.kind === "dep") {
|
|
258
|
+
assertValidModuleSlug(target.module);
|
|
259
|
+
assertValidTableSlug(target.slug);
|
|
260
|
+
ref = { kind: "dep", module: target.module, slug: target.slug };
|
|
261
|
+
} else if (target && typeof target === "object" && target.kind === "view") {
|
|
262
|
+
assertValidTableSlug(target.slug);
|
|
263
|
+
ref = { kind: "view", slug: target.slug };
|
|
264
|
+
} else {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`@dashai/sdk/query: qb.from() expects a string table slug, { kind: 'dep', module, slug }, or { kind: 'view', slug }; got ${JSON.stringify(target)}.`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
const ast = {
|
|
270
|
+
v: CURRENT_QUERY_AST_VERSION,
|
|
271
|
+
from: ref
|
|
272
|
+
};
|
|
273
|
+
return makeQuery(client, ast);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
async function postExplain(client, ast) {
|
|
278
|
+
return client.request("POST", "/query/explain", { ast });
|
|
279
|
+
}
|
|
280
|
+
async function* streamRowsViaSSE(client, ast, signal) {
|
|
281
|
+
const res = await client.streamRaw("POST", "/query/stream", { ast }, signal);
|
|
282
|
+
const requestId = res.headers.get("x-request-id");
|
|
283
|
+
if (!res.ok) {
|
|
284
|
+
const envelope = await readErrorEnvelope(res);
|
|
285
|
+
throw fromBackendEnvelope(res.status, envelope, requestId);
|
|
286
|
+
}
|
|
287
|
+
const reader = res.body?.getReader();
|
|
288
|
+
if (!reader) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
"@dashai/sdk/query: streaming response has no readable body \u2014 backend bug or misconfigured fetch impl."
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
const decoder = new TextDecoder();
|
|
294
|
+
let buffer = "";
|
|
295
|
+
try {
|
|
296
|
+
while (true) {
|
|
297
|
+
const { done, value } = await reader.read();
|
|
298
|
+
if (done) {
|
|
299
|
+
if (buffer.trim().length > 0) {
|
|
300
|
+
const last = parseSseBlock(buffer);
|
|
301
|
+
if (last && last.event === "message") {
|
|
302
|
+
yield last.data;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
buffer += decoder.decode(value, { stream: true });
|
|
308
|
+
const parts = buffer.split("\n\n");
|
|
309
|
+
buffer = parts.pop() ?? "";
|
|
310
|
+
for (const block of parts) {
|
|
311
|
+
if (!block.trim()) continue;
|
|
312
|
+
const parsed = parseSseBlock(block);
|
|
313
|
+
if (!parsed) continue;
|
|
314
|
+
if (parsed.event === "message") {
|
|
315
|
+
yield parsed.data;
|
|
316
|
+
} else if (parsed.event === "complete") {
|
|
317
|
+
return;
|
|
318
|
+
} else if (parsed.event === "error") {
|
|
319
|
+
const err = parsed.data;
|
|
320
|
+
throw fromBackendEnvelope(
|
|
321
|
+
500,
|
|
322
|
+
{ code: err.code, message: err.message },
|
|
323
|
+
requestId
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} finally {
|
|
329
|
+
try {
|
|
330
|
+
reader.releaseLock();
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function parseSseBlock(block) {
|
|
336
|
+
const trimmed = block.trim();
|
|
337
|
+
if (trimmed.length === 0) return null;
|
|
338
|
+
let event = "message";
|
|
339
|
+
const dataLines = [];
|
|
340
|
+
for (const line of trimmed.split("\n")) {
|
|
341
|
+
if (line.startsWith("event:")) {
|
|
342
|
+
event = line.slice(6).trim();
|
|
343
|
+
} else if (line.startsWith("data:")) {
|
|
344
|
+
const v = line.slice(5);
|
|
345
|
+
dataLines.push(v.startsWith(" ") ? v.slice(1) : v);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (dataLines.length === 0) return null;
|
|
349
|
+
const joined = dataLines.join("\n");
|
|
350
|
+
let data;
|
|
351
|
+
try {
|
|
352
|
+
data = JSON.parse(joined);
|
|
353
|
+
} catch {
|
|
354
|
+
data = joined;
|
|
355
|
+
}
|
|
356
|
+
return { event, data };
|
|
357
|
+
}
|
|
358
|
+
async function readErrorEnvelope(res) {
|
|
359
|
+
try {
|
|
360
|
+
const parsed = await res.json();
|
|
361
|
+
if (parsed && typeof parsed === "object") {
|
|
362
|
+
return parsed;
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
} catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function makeQuery(client, ast) {
|
|
370
|
+
const fork = (patch) => makeQuery(client, { ...ast, ...patch });
|
|
371
|
+
return {
|
|
372
|
+
toAst() {
|
|
373
|
+
return JSON.parse(JSON.stringify(ast));
|
|
374
|
+
},
|
|
375
|
+
where(clause) {
|
|
376
|
+
return fork({ where: clause });
|
|
377
|
+
},
|
|
378
|
+
orderBy(clauses) {
|
|
379
|
+
return fork({ orderBy: [...clauses] });
|
|
380
|
+
},
|
|
381
|
+
limit(n) {
|
|
382
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`@dashai/sdk/query: .limit(${n}) must be a positive integer.`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return fork({ limit: n });
|
|
388
|
+
},
|
|
389
|
+
offset(n) {
|
|
390
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`@dashai/sdk/query: .offset(${n}) must be a non-negative integer.`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
return fork({ offset: n });
|
|
396
|
+
},
|
|
397
|
+
select(fields) {
|
|
398
|
+
return fork({ select: [...fields] });
|
|
399
|
+
},
|
|
400
|
+
async count() {
|
|
401
|
+
const minimalAst = {
|
|
402
|
+
...ast,
|
|
403
|
+
limit: ast.limit ?? 1
|
|
404
|
+
};
|
|
405
|
+
const result = await client.request(
|
|
406
|
+
"POST",
|
|
407
|
+
"/query",
|
|
408
|
+
{ ast: minimalAst }
|
|
409
|
+
);
|
|
410
|
+
if (Array.isArray(result)) {
|
|
411
|
+
return result.length;
|
|
412
|
+
}
|
|
413
|
+
return result.total;
|
|
414
|
+
},
|
|
415
|
+
async execute() {
|
|
416
|
+
const result = await client.request(
|
|
417
|
+
"POST",
|
|
418
|
+
"/query",
|
|
419
|
+
{ ast }
|
|
420
|
+
);
|
|
421
|
+
if (Array.isArray(result)) {
|
|
422
|
+
return {
|
|
423
|
+
rows: result,
|
|
424
|
+
total: result.length,
|
|
425
|
+
limit: ast.limit ?? result.length,
|
|
426
|
+
offset: ast.offset ?? 0
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return result;
|
|
430
|
+
},
|
|
431
|
+
async explain() {
|
|
432
|
+
return postExplain(client, ast);
|
|
433
|
+
},
|
|
434
|
+
stream(opts) {
|
|
435
|
+
return streamRowsViaSSE(client, ast, opts?.signal);
|
|
436
|
+
},
|
|
437
|
+
groupBy(fields) {
|
|
438
|
+
return makeAggQuery(client, { ...ast, groupBy: [...fields] });
|
|
439
|
+
},
|
|
440
|
+
agg(expressions) {
|
|
441
|
+
return makeAggQuery(client, { ...ast, agg: expressions });
|
|
442
|
+
},
|
|
443
|
+
// ── Join entries (Phase 3 slice 3.3) ──────────────────────────
|
|
444
|
+
//
|
|
445
|
+
// .join() / .leftJoin() transition to JoinedQuery whose surface
|
|
446
|
+
// omits write methods + the aggregation suite. The SDK delegates
|
|
447
|
+
// shape validation to the backend's AST validator + the
|
|
448
|
+
// join-translator; today's slice 3.2b-1 cap means combinations
|
|
449
|
+
// with where/orderBy/limit/offset/agg backend-reject with clear
|
|
450
|
+
// "lands in slice X" errors.
|
|
451
|
+
join(target, opts) {
|
|
452
|
+
return makeJoinedQuery(client, {
|
|
453
|
+
...ast,
|
|
454
|
+
joins: [...ast.joins ?? [], buildJoinNode(target, opts, "inner")]
|
|
455
|
+
});
|
|
456
|
+
},
|
|
457
|
+
leftJoin(target, opts) {
|
|
458
|
+
return makeJoinedQuery(client, {
|
|
459
|
+
...ast,
|
|
460
|
+
joins: [...ast.joins ?? [], buildJoinNode(target, opts, "left")]
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function makeJoinedQuery(client, ast) {
|
|
466
|
+
const fork = (patch) => makeJoinedQuery(client, { ...ast, ...patch });
|
|
467
|
+
return {
|
|
468
|
+
toAst() {
|
|
469
|
+
return JSON.parse(JSON.stringify(ast));
|
|
470
|
+
},
|
|
471
|
+
where(clause) {
|
|
472
|
+
return fork({ where: clause });
|
|
473
|
+
},
|
|
474
|
+
orderBy(clauses) {
|
|
475
|
+
return fork({ orderBy: [...clauses] });
|
|
476
|
+
},
|
|
477
|
+
limit(n) {
|
|
478
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`@dashai/sdk/query: .limit(${n}) must be a positive integer.`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
return fork({ limit: n });
|
|
484
|
+
},
|
|
485
|
+
offset(n) {
|
|
486
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
487
|
+
throw new Error(
|
|
488
|
+
`@dashai/sdk/query: .offset(${n}) must be a non-negative integer.`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
return fork({ offset: n });
|
|
492
|
+
},
|
|
493
|
+
join(target, opts) {
|
|
494
|
+
return makeJoinedQuery(client, {
|
|
495
|
+
...ast,
|
|
496
|
+
joins: [...ast.joins ?? [], buildJoinNode(target, opts, "inner")]
|
|
497
|
+
});
|
|
498
|
+
},
|
|
499
|
+
leftJoin(target, opts) {
|
|
500
|
+
return makeJoinedQuery(client, {
|
|
501
|
+
...ast,
|
|
502
|
+
joins: [...ast.joins ?? [], buildJoinNode(target, opts, "left")]
|
|
503
|
+
});
|
|
504
|
+
},
|
|
505
|
+
flatten() {
|
|
506
|
+
return makeJoinedQuery(client, {
|
|
507
|
+
...ast,
|
|
508
|
+
flatten: true
|
|
509
|
+
});
|
|
510
|
+
},
|
|
511
|
+
async execute() {
|
|
512
|
+
const result = await client.request(
|
|
513
|
+
"POST",
|
|
514
|
+
"/query",
|
|
515
|
+
{ ast }
|
|
516
|
+
);
|
|
517
|
+
if (Array.isArray(result)) {
|
|
518
|
+
return {
|
|
519
|
+
rows: result,
|
|
520
|
+
total: result.length,
|
|
521
|
+
limit: ast.limit ?? result.length,
|
|
522
|
+
offset: ast.offset ?? 0
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return result;
|
|
526
|
+
},
|
|
527
|
+
async explain() {
|
|
528
|
+
return postExplain(client, ast);
|
|
529
|
+
},
|
|
530
|
+
stream(opts) {
|
|
531
|
+
return streamRowsViaSSE(client, ast, opts?.signal);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function buildJoinNode(target, opts, defaultKind) {
|
|
536
|
+
let to;
|
|
537
|
+
let displaySlug;
|
|
538
|
+
if (typeof target === "string") {
|
|
539
|
+
assertValidTableSlug(target);
|
|
540
|
+
to = { kind: "owned", slug: target };
|
|
541
|
+
displaySlug = target;
|
|
542
|
+
} else if (target && typeof target === "object" && target.kind === "dep") {
|
|
543
|
+
assertValidModuleSlug(target.module);
|
|
544
|
+
assertValidTableSlug(target.slug);
|
|
545
|
+
to = { kind: "dep", module: target.module, slug: target.slug };
|
|
546
|
+
displaySlug = `${target.module}:${target.slug}`;
|
|
547
|
+
} else {
|
|
548
|
+
throw new Error(
|
|
549
|
+
`@dashai/sdk/query: .join() target must be a table slug string or { kind: 'dep', module, slug }; got ${JSON.stringify(target)}.`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
if (!opts.on || Object.keys(opts.on).length === 0) {
|
|
553
|
+
throw new Error(
|
|
554
|
+
`@dashai/sdk/query: .join('${displaySlug}') requires an \`on\` clause with at least one field pair. Link-field-derived \`on\` inference lands in a follow-up slice.`
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
kind: opts.kind ?? defaultKind,
|
|
559
|
+
to,
|
|
560
|
+
on: opts.on,
|
|
561
|
+
...opts.as !== void 0 ? { as: opts.as } : {}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function makeAggQuery(client, ast) {
|
|
565
|
+
const fork = (patch) => makeAggQuery(client, { ...ast, ...patch });
|
|
566
|
+
return {
|
|
567
|
+
toAst() {
|
|
568
|
+
return JSON.parse(JSON.stringify(ast));
|
|
569
|
+
},
|
|
570
|
+
where(clause) {
|
|
571
|
+
return fork({ where: clause });
|
|
572
|
+
},
|
|
573
|
+
groupBy(fields) {
|
|
574
|
+
return fork({ groupBy: [...fields] });
|
|
575
|
+
},
|
|
576
|
+
agg(expressions) {
|
|
577
|
+
return fork({ agg: expressions });
|
|
578
|
+
},
|
|
579
|
+
limit(n) {
|
|
580
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
581
|
+
throw new Error(
|
|
582
|
+
`@dashai/sdk/query: .limit(${n}) must be a positive integer.`
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
return fork({ limit: n });
|
|
586
|
+
},
|
|
587
|
+
offset(n) {
|
|
588
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
`@dashai/sdk/query: .offset(${n}) must be a non-negative integer.`
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
return fork({ offset: n });
|
|
594
|
+
},
|
|
595
|
+
async execute() {
|
|
596
|
+
const result = await client.request(
|
|
597
|
+
"POST",
|
|
598
|
+
"/query",
|
|
599
|
+
{ ast }
|
|
600
|
+
);
|
|
601
|
+
if (Array.isArray(result)) {
|
|
602
|
+
return {
|
|
603
|
+
rows: result,
|
|
604
|
+
total: result.length
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
return result;
|
|
608
|
+
},
|
|
609
|
+
async explain() {
|
|
610
|
+
return postExplain(client, ast);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export { CURRENT_QUERY_AST_VERSION, isQueryAst, makeQb };
|
|
616
|
+
//# sourceMappingURL=index.js.map
|
|
617
|
+
//# sourceMappingURL=index.js.map
|