@duckviz/sdk 0.1.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.
@@ -0,0 +1,401 @@
1
+ interface DuckvizEndpointFields {
2
+ /** Personal access token (`dvz_live_...`). Never commit to source control. */
3
+ token: string;
4
+ /** Override base URL (defaults to the DuckViz Cloud production host). */
5
+ baseUrl?: string;
6
+ /** Injectable fetch — used in tests to mock the transport. */
7
+ fetch?: typeof fetch;
8
+ /** Max retry attempts on 5xx / 429 / network error. Default: 2. */
9
+ maxRetries?: number;
10
+ /** Per-attempt timeout in ms. Default: 60_000. */
11
+ timeout?: number;
12
+ }
13
+ interface InvokeOptions {
14
+ signal?: AbortSignal;
15
+ }
16
+ interface SSEMessage<T = unknown> {
17
+ event: string;
18
+ data: T;
19
+ }
20
+ /**
21
+ * Base class for every DuckViz endpoint wrapper.
22
+ *
23
+ * Why a class tree (mirrors LangChain's `BaseChatModel`): each endpoint has
24
+ * identical auth, retry, abort, and error-mapping semantics — only the URL,
25
+ * request body, and return shape differ. Subclasses override `invoke` (and
26
+ * optionally `stream`) and call `postJson` / `postSSE` under the hood.
27
+ *
28
+ * Retry policy: exponential backoff for 5xx / 429 / network errors. 4xx
29
+ * (401/402/400) are terminal. `Retry-After` headers are honored.
30
+ */
31
+ declare abstract class DuckvizEndpoint<TInput, TOutput> {
32
+ protected readonly token: string;
33
+ protected readonly baseUrl: string;
34
+ protected readonly fetchImpl: typeof fetch;
35
+ protected readonly maxRetries: number;
36
+ protected readonly timeout: number;
37
+ constructor(fields: DuckvizEndpointFields);
38
+ abstract invoke(input: TInput, options?: InvokeOptions): Promise<TOutput>;
39
+ protected buildHeaders(): Headers;
40
+ /**
41
+ * POST a JSON body and parse a JSON response. Retries transient failures.
42
+ * Throws a typed `DuckvizError` subclass on non-2xx responses.
43
+ */
44
+ protected postJson<TResponse>(path: string, body: unknown, options?: InvokeOptions): Promise<TResponse>;
45
+ /**
46
+ * GET a JSON response. Query params are passed as a plain object and
47
+ * serialized into the URL — values coerce to strings, arrays repeat the
48
+ * key, `undefined` / `null` are skipped.
49
+ */
50
+ protected getJson<TResponse>(path: string, params?: Record<string, string | number | boolean | undefined | null>, options?: InvokeOptions): Promise<TResponse>;
51
+ /**
52
+ * POST a JSON body and stream back an SSE response, one parsed message at
53
+ * a time. The raw DuckViz event wire format is
54
+ * data: {"event":"...","data":...}\n\n
55
+ * — this helper yields the parsed `{event, data}` objects directly.
56
+ */
57
+ protected postSSE<TData = unknown>(path: string, body: unknown, options?: InvokeOptions): AsyncIterable<SSEMessage<TData>>;
58
+ private requestWithRetry;
59
+ }
60
+
61
+ /**
62
+ * Typed error hierarchy for the DuckViz SDK.
63
+ *
64
+ * Callers can `catch` either the base `DuckvizError` or a specific subclass.
65
+ * Every error carries the upstream HTTP status (when applicable) and the
66
+ * original message from the server, so logging and retry logic stays simple.
67
+ */
68
+ declare class DuckvizError extends Error {
69
+ readonly status?: number | undefined;
70
+ constructor(message: string, status?: number | undefined);
71
+ }
72
+ /** 401 — token missing, malformed, or revoked. Not retryable. */
73
+ declare class DuckvizAuthError extends DuckvizError {
74
+ constructor(message?: string);
75
+ }
76
+ /** 402 — token owner is out of credits. Not retryable; UI should prompt top-up. */
77
+ declare class DuckvizQuotaExceededError extends DuckvizError {
78
+ readonly required?: number | undefined;
79
+ constructor(message?: string, required?: number | undefined);
80
+ }
81
+ /**
82
+ * 429 — too many requests. `retryAfterMs` is derived from the `Retry-After`
83
+ * response header when present, otherwise undefined. The base class's retry
84
+ * loop honors this automatically.
85
+ */
86
+ declare class DuckvizRateLimitError extends DuckvizError {
87
+ readonly retryAfterMs?: number | undefined;
88
+ constructor(message?: string, retryAfterMs?: number | undefined);
89
+ }
90
+ /** 5xx — upstream crashed. Retried automatically by the base class. */
91
+ declare class DuckvizServerError extends DuckvizError {
92
+ constructor(message: string, status: number);
93
+ }
94
+ /** fetch() itself threw (DNS, TCP, offline, abort). Retried automatically. */
95
+ declare class DuckvizNetworkError extends DuckvizError {
96
+ readonly cause?: unknown | undefined;
97
+ constructor(message: string, cause?: unknown | undefined);
98
+ }
99
+
100
+ /**
101
+ * Public SDK types — mirrored from the app's internal shapes but declared
102
+ * independently so SDK consumers don't need to install `@duckviz/db` or
103
+ * `@duckviz/explorer` as peer deps.
104
+ *
105
+ * Keep in sync with:
106
+ * - apps/app/app/api/widget-flow/fast-recommend/route.ts
107
+ * - apps/app/app/api/detect-log-format/route.ts
108
+ * - apps/app/app/api/generate-report-sections/route.ts
109
+ * - apps/app/app/api/modify-report-section/route.ts
110
+ */
111
+ interface SchemaColumn {
112
+ column_name: string;
113
+ column_type: string;
114
+ }
115
+ interface TableInfo {
116
+ tableName: string;
117
+ schema: SchemaColumn[];
118
+ }
119
+ interface RecommendedWidget {
120
+ type: string;
121
+ title: string;
122
+ description: string;
123
+ duckdbQuery: string;
124
+ config?: Record<string, unknown>;
125
+ domain?: string;
126
+ }
127
+ interface VizSuggestion {
128
+ type: string;
129
+ title: string;
130
+ rationale?: string;
131
+ }
132
+ interface WidgetFlowInput {
133
+ tables: TableInfo[];
134
+ dateSamples?: Record<string, string[]>;
135
+ formatName?: string;
136
+ }
137
+ /**
138
+ * Events emitted by `DuckvizWidgetFlow.stream()`. The exact sequence is:
139
+ * thinking → (domain | viz_suggestion | widget)* → done
140
+ * plus an `error` terminal event on failure.
141
+ */
142
+ type WidgetFlowEvent = {
143
+ event: "thinking";
144
+ data: {
145
+ message: string;
146
+ };
147
+ } | {
148
+ event: "domain";
149
+ data: {
150
+ domain: string;
151
+ source?: string;
152
+ };
153
+ } | {
154
+ event: "viz_suggestion";
155
+ data: VizSuggestion;
156
+ } | {
157
+ event: "widget";
158
+ data: RecommendedWidget;
159
+ } | {
160
+ event: "done";
161
+ data: null;
162
+ } | {
163
+ event: "error";
164
+ data: {
165
+ message: string;
166
+ };
167
+ };
168
+ interface LogFormatDetectInput {
169
+ lines: string[];
170
+ existingFormats?: unknown[];
171
+ }
172
+ interface DetectedLogFormat {
173
+ logType: string;
174
+ formatName: string;
175
+ explanation: string;
176
+ [key: string]: unknown;
177
+ }
178
+ interface ReportWidgetRef {
179
+ title: string;
180
+ type: string;
181
+ description: string;
182
+ duckdbQuery: string;
183
+ }
184
+ interface ReportSectionTemplate {
185
+ key: string;
186
+ title: string;
187
+ promptHint: string;
188
+ position: "pre" | "post";
189
+ }
190
+ interface ReportsInput {
191
+ sections: ReportSectionTemplate[];
192
+ systemPromptHint: string;
193
+ reportTitle: string;
194
+ widgets: ReportWidgetRef[];
195
+ }
196
+ /**
197
+ * Event stream from `DuckvizReports.stream()`. `section-complete` carries the
198
+ * generated block list for a single section; callers can swap placeholders in
199
+ * their report editor as each arrives.
200
+ */
201
+ type ReportsEvent = {
202
+ event: "section-start";
203
+ data: {
204
+ sectionKey: string;
205
+ sectionTitle: string;
206
+ };
207
+ } | {
208
+ event: "section-complete";
209
+ data: {
210
+ sectionKey: string;
211
+ sectionTitle: string;
212
+ blocks: unknown[];
213
+ queries: Record<string, string>;
214
+ };
215
+ } | {
216
+ event: "section-error";
217
+ data: {
218
+ sectionKey: string;
219
+ message: string;
220
+ };
221
+ } | {
222
+ event: "done";
223
+ data: null;
224
+ } | {
225
+ event: "error";
226
+ data: {
227
+ message: string;
228
+ };
229
+ };
230
+ interface ReportSection {
231
+ type: string;
232
+ [key: string]: unknown;
233
+ }
234
+ interface StructuredReport {
235
+ title: string;
236
+ sections: ReportSection[];
237
+ variables?: string[];
238
+ }
239
+ interface ModifyReportSectionInput {
240
+ content: StructuredReport;
241
+ prompt: string;
242
+ }
243
+
244
+ /**
245
+ * Widget-flow endpoint wrapper.
246
+ *
247
+ * Two modes:
248
+ * - `invoke(input)` → collects the fast-recommend SSE stream into a plain
249
+ * array of widgets. Good for batch / non-streaming callers.
250
+ * - `stream(input)` → yields the raw SSE events (thinking / domain /
251
+ * viz_suggestion / widget / done) as they arrive. Good for UIs that want
252
+ * to render progress.
253
+ *
254
+ * Both modes hit the same backend path (`/api/widget-flow/fast-recommend`) —
255
+ * there's no separate non-streaming endpoint, because the backend already
256
+ * short-circuits on cache hits without doing any LLM work.
257
+ */
258
+ declare class DuckvizWidgetFlow extends DuckvizEndpoint<WidgetFlowInput, RecommendedWidget[]> {
259
+ private static readonly PATH;
260
+ invoke(input: WidgetFlowInput, options?: InvokeOptions): Promise<RecommendedWidget[]>;
261
+ stream(input: WidgetFlowInput, options?: InvokeOptions): AsyncIterable<WidgetFlowEvent>;
262
+ }
263
+
264
+ /**
265
+ * LLM-driven log format detection. Given a handful of sample lines, returns
266
+ * `{ logType, formatName, explanation }`. No streaming — this is a single
267
+ * classify call on the backend.
268
+ */
269
+ declare class DuckvizLogFormatDetector extends DuckvizEndpoint<LogFormatDetectInput, DetectedLogFormat> {
270
+ private static readonly PATH;
271
+ invoke(input: LogFormatDetectInput, options?: InvokeOptions): Promise<DetectedLogFormat>;
272
+ }
273
+
274
+ /**
275
+ * Report generation + modification.
276
+ *
277
+ * `stream()` is the primary interface — the backend generates sections in
278
+ * parallel and emits `section-complete` events as each finishes, so callers
279
+ * can progressively fill a placeholder scaffold.
280
+ *
281
+ * `invoke()` aggregates the stream into a single `StructuredReport`, for
282
+ * non-UI callers that just want the final object.
283
+ *
284
+ * `modifySection()` hits a separate non-streaming endpoint that rewrites the
285
+ * entire report content in response to a freeform prompt.
286
+ */
287
+ declare class DuckvizReports extends DuckvizEndpoint<ReportsInput, StructuredReport> {
288
+ private static readonly GENERATE_PATH;
289
+ private static readonly MODIFY_PATH;
290
+ stream(input: ReportsInput, options?: InvokeOptions): AsyncIterable<ReportsEvent>;
291
+ invoke(input: ReportsInput, options?: InvokeOptions): Promise<StructuredReport>;
292
+ /** Rewrite an existing structured report via a prompt. */
293
+ modifySection(input: ModifyReportSectionInput, options?: InvokeOptions): Promise<StructuredReport>;
294
+ }
295
+
296
+ interface CreditBalance {
297
+ balance: number;
298
+ plan: string;
299
+ }
300
+ interface UsageEvent {
301
+ id: string;
302
+ ts: string;
303
+ endpoint: string;
304
+ credits: number;
305
+ balanceAfter: number;
306
+ }
307
+ interface UsageListParams {
308
+ from?: string;
309
+ to?: string;
310
+ limit?: number;
311
+ }
312
+ /**
313
+ * Read-only credits client. Unlike the streaming endpoint classes there's
314
+ * no natural single "input/output" — `balance()` is a no-arg call — so we
315
+ * extend the base class with a degenerate `invoke()` that aliases to
316
+ * `balance()` to satisfy the abstract contract. Most callers will just use
317
+ * `balance()` directly.
318
+ */
319
+ declare class DuckvizCredits extends DuckvizEndpoint<void, CreditBalance> {
320
+ private static readonly PATH;
321
+ invoke(_input?: void, options?: InvokeOptions): Promise<CreditBalance>;
322
+ balance(options?: InvokeOptions): Promise<CreditBalance>;
323
+ }
324
+ /**
325
+ * Read-only usage history client. `list()` returns billed calls in reverse
326
+ * chronological order, filtered by optional date range.
327
+ */
328
+ declare class DuckvizUsage extends DuckvizEndpoint<UsageListParams, {
329
+ events: UsageEvent[];
330
+ }> {
331
+ private static readonly PATH;
332
+ invoke(input?: UsageListParams, options?: InvokeOptions): Promise<{
333
+ events: UsageEvent[];
334
+ }>;
335
+ list(params?: UsageListParams, options?: InvokeOptions): Promise<{
336
+ events: UsageEvent[];
337
+ }>;
338
+ }
339
+
340
+ interface ThemeConfig {
341
+ mode?: "light" | "dark";
342
+ variables?: Partial<ThemeVariables>;
343
+ }
344
+ interface ThemeVariables {
345
+ "--app-background-default": string;
346
+ "--app-background-secondary": string;
347
+ "--app-background-tertiary": string;
348
+ "--app-surface-subtle": string;
349
+ "--app-surface-default": string;
350
+ "--app-surface-secondary": string;
351
+ "--app-surface-tertiary": string;
352
+ "--app-text-default": string;
353
+ "--app-text-secondary": string;
354
+ "--app-text-subtle": string;
355
+ "--app-border-default": string;
356
+ "--app-border-secondary": string;
357
+ "--app-border-subtle": string;
358
+ "--app-primary-default": string;
359
+ "--app-primary-secondary": string;
360
+ "--app-color-1": string;
361
+ "--app-color-2": string;
362
+ "--app-color-3": string;
363
+ "--app-color-4": string;
364
+ "--app-color-5": string;
365
+ "--app-color-6": string;
366
+ "--app-color-7": string;
367
+ "--app-color-8": string;
368
+ }
369
+ interface WidgetConfig {
370
+ id: string;
371
+ type: string;
372
+ title: string;
373
+ description?: string;
374
+ dataKey: string;
375
+ config?: Record<string, unknown>;
376
+ layout?: {
377
+ x: number;
378
+ y: number;
379
+ w: number;
380
+ h: number;
381
+ };
382
+ }
383
+ interface DashboardConfig {
384
+ name?: string;
385
+ description?: string;
386
+ dataset?: string;
387
+ widgets: WidgetConfig[];
388
+ grid?: {
389
+ cols?: number;
390
+ rowHeight?: number;
391
+ margin?: [number, number];
392
+ };
393
+ theme?: ThemeConfig;
394
+ }
395
+ declare class DashboardConfigError extends Error {
396
+ path: string;
397
+ constructor(message: string, path: string);
398
+ }
399
+ declare function validateDashboardConfig(input: unknown): DashboardConfig;
400
+
401
+ export { type CreditBalance, type DashboardConfig, DashboardConfigError, type DetectedLogFormat, DuckvizAuthError, DuckvizCredits, DuckvizError, DuckvizLogFormatDetector, DuckvizNetworkError, type DuckvizEndpointFields as DuckvizOptions, DuckvizQuotaExceededError, DuckvizRateLimitError, DuckvizReports, DuckvizServerError, DuckvizUsage, DuckvizWidgetFlow, type InvokeOptions, type LogFormatDetectInput, type RecommendedWidget, type ReportSection, type ReportSectionTemplate, type ReportWidgetRef, type ReportsEvent, type ReportsInput, type SchemaColumn, type StructuredReport, type UsageEvent, type UsageListParams, type VizSuggestion, type WidgetConfig, type WidgetFlowEvent, type WidgetFlowInput, validateDashboardConfig };
package/dist/index.js ADDED
@@ -0,0 +1,195 @@
1
+ import { DuckvizEndpoint } from './chunk-WYWQAALZ.js';
2
+ export { DuckvizAuthError, DuckvizError, DuckvizNetworkError, DuckvizQuotaExceededError, DuckvizRateLimitError, DuckvizServerError } from './chunk-WYWQAALZ.js';
3
+
4
+ // src/widget-flow.ts
5
+ var DuckvizWidgetFlow = class _DuckvizWidgetFlow extends DuckvizEndpoint {
6
+ static PATH = "/api/widget-flow/fast-recommend";
7
+ async invoke(input, options) {
8
+ const widgets = [];
9
+ for await (const evt of this.stream(input, options)) {
10
+ if (evt.event === "widget") {
11
+ widgets.push(evt.data);
12
+ } else if (evt.event === "error") {
13
+ throw new Error(evt.data.message);
14
+ }
15
+ }
16
+ return widgets;
17
+ }
18
+ async *stream(input, options) {
19
+ for await (const msg of this.postSSE(
20
+ _DuckvizWidgetFlow.PATH,
21
+ input,
22
+ options
23
+ )) {
24
+ yield msg;
25
+ }
26
+ }
27
+ };
28
+
29
+ // src/log-format-detector.ts
30
+ var DuckvizLogFormatDetector = class _DuckvizLogFormatDetector extends DuckvizEndpoint {
31
+ static PATH = "/api/detect-log-format";
32
+ async invoke(input, options) {
33
+ return this.postJson(
34
+ _DuckvizLogFormatDetector.PATH,
35
+ input,
36
+ options
37
+ );
38
+ }
39
+ };
40
+
41
+ // src/reports.ts
42
+ var DuckvizReports = class _DuckvizReports extends DuckvizEndpoint {
43
+ static GENERATE_PATH = "/api/generate-report-sections";
44
+ static MODIFY_PATH = "/api/modify-report-section";
45
+ async *stream(input, options) {
46
+ for await (const msg of this.postSSE(
47
+ _DuckvizReports.GENERATE_PATH,
48
+ input,
49
+ options
50
+ )) {
51
+ yield msg;
52
+ }
53
+ }
54
+ async invoke(input, options) {
55
+ const sectionBlocks = [];
56
+ for await (const evt of this.stream(input, options)) {
57
+ if (evt.event === "section-complete") {
58
+ sectionBlocks.push(...evt.data.blocks);
59
+ } else if (evt.event === "error") {
60
+ throw new Error(evt.data.message);
61
+ }
62
+ }
63
+ return {
64
+ title: input.reportTitle,
65
+ sections: sectionBlocks
66
+ };
67
+ }
68
+ /** Rewrite an existing structured report via a prompt. */
69
+ async modifySection(input, options) {
70
+ const result = await this.postJson(
71
+ _DuckvizReports.MODIFY_PATH,
72
+ input,
73
+ options
74
+ );
75
+ return result.content;
76
+ }
77
+ };
78
+
79
+ // src/credits.ts
80
+ var DuckvizCredits = class _DuckvizCredits extends DuckvizEndpoint {
81
+ static PATH = "/api/credits/balance";
82
+ async invoke(_input, options) {
83
+ return this.balance(options);
84
+ }
85
+ async balance(options) {
86
+ return this.getJson(_DuckvizCredits.PATH, void 0, options);
87
+ }
88
+ };
89
+ var DuckvizUsage = class _DuckvizUsage extends DuckvizEndpoint {
90
+ static PATH = "/api/usage";
91
+ async invoke(input = {}, options) {
92
+ return this.list(input, options);
93
+ }
94
+ async list(params = {}, options) {
95
+ return this.getJson(
96
+ _DuckvizUsage.PATH,
97
+ {
98
+ from: params.from,
99
+ to: params.to,
100
+ limit: params.limit
101
+ },
102
+ options
103
+ );
104
+ }
105
+ };
106
+
107
+ // src/dashboard-config.ts
108
+ var DashboardConfigError = class extends Error {
109
+ constructor(message, path) {
110
+ super(`DashboardConfig.${path}: ${message}`);
111
+ this.path = path;
112
+ this.name = "DashboardConfigError";
113
+ }
114
+ };
115
+ function assertString(val, path) {
116
+ if (typeof val !== "string" || val.length === 0) {
117
+ throw new DashboardConfigError("must be a non-empty string", path);
118
+ }
119
+ }
120
+ function assertOptionalString(val, path) {
121
+ if (val !== void 0 && typeof val !== "string") {
122
+ throw new DashboardConfigError("must be a string", path);
123
+ }
124
+ }
125
+ function validateWidget(w, idx) {
126
+ if (w === null || typeof w !== "object") {
127
+ throw new DashboardConfigError("must be an object", `widgets[${idx}]`);
128
+ }
129
+ const obj = w;
130
+ assertString(obj.id, `widgets[${idx}].id`);
131
+ assertString(obj.type, `widgets[${idx}].type`);
132
+ assertString(obj.title, `widgets[${idx}].title`);
133
+ assertOptionalString(obj.description, `widgets[${idx}].description`);
134
+ assertString(obj.dataKey, `widgets[${idx}].dataKey`);
135
+ if (obj.config !== void 0 && (typeof obj.config !== "object" || obj.config === null)) {
136
+ throw new DashboardConfigError(
137
+ "must be an object",
138
+ `widgets[${idx}].config`
139
+ );
140
+ }
141
+ if (obj.layout !== void 0) {
142
+ if (typeof obj.layout !== "object" || obj.layout === null) {
143
+ throw new DashboardConfigError(
144
+ "must be an object",
145
+ `widgets[${idx}].layout`
146
+ );
147
+ }
148
+ const l = obj.layout;
149
+ for (const k of ["x", "y", "w", "h"]) {
150
+ if (typeof l[k] !== "number") {
151
+ throw new DashboardConfigError(
152
+ `must be a number`,
153
+ `widgets[${idx}].layout.${k}`
154
+ );
155
+ }
156
+ }
157
+ }
158
+ return obj;
159
+ }
160
+ function validateDashboardConfig(input) {
161
+ if (input === null || typeof input !== "object") {
162
+ throw new DashboardConfigError("must be an object", "(root)");
163
+ }
164
+ const obj = input;
165
+ assertOptionalString(obj.name, "name");
166
+ assertOptionalString(obj.description, "description");
167
+ assertOptionalString(obj.dataset, "dataset");
168
+ if (!Array.isArray(obj.widgets)) {
169
+ throw new DashboardConfigError("must be an array", "widgets");
170
+ }
171
+ const widgets = obj.widgets.map((w, i) => validateWidget(w, i));
172
+ if (obj.grid !== void 0) {
173
+ if (typeof obj.grid !== "object" || obj.grid === null) {
174
+ throw new DashboardConfigError("must be an object", "grid");
175
+ }
176
+ const g = obj.grid;
177
+ if (g.cols !== void 0 && typeof g.cols !== "number") {
178
+ throw new DashboardConfigError("must be a number", "grid.cols");
179
+ }
180
+ if (g.rowHeight !== void 0 && typeof g.rowHeight !== "number") {
181
+ throw new DashboardConfigError("must be a number", "grid.rowHeight");
182
+ }
183
+ if (g.margin !== void 0) {
184
+ if (!Array.isArray(g.margin) || g.margin.length !== 2 || typeof g.margin[0] !== "number" || typeof g.margin[1] !== "number") {
185
+ throw new DashboardConfigError(
186
+ "must be a [number, number] tuple",
187
+ "grid.margin"
188
+ );
189
+ }
190
+ }
191
+ }
192
+ return { ...obj, widgets };
193
+ }
194
+
195
+ export { DashboardConfigError, DuckvizCredits, DuckvizLogFormatDetector, DuckvizReports, DuckvizUsage, DuckvizWidgetFlow, validateDashboardConfig };
package/dist/next.cjs ADDED
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ var chunkU3PJRXFJ_cjs = require('./chunk-U3PJRXFJ.cjs');
4
+
5
+ // src/next.ts
6
+ function createDuckvizHandlers(options) {
7
+ if (!options.token) {
8
+ throw new Error("createDuckvizHandlers: `token` is required");
9
+ }
10
+ const baseUrl = (options.baseUrl ?? chunkU3PJRXFJ_cjs.DEFAULT_BASE_URL).replace(/\/$/, "");
11
+ const fetchImpl = options.fetch ?? globalThis.fetch;
12
+ async function proxy(request, ctx) {
13
+ const params = await Promise.resolve(ctx.params);
14
+ const route = Array.isArray(params.route) ? params.route : [];
15
+ if (route.length === 0) {
16
+ return json({ error: "Missing route path" }, 400);
17
+ }
18
+ const incomingUrl = new URL(request.url);
19
+ const qs = incomingUrl.search;
20
+ const path = `/api/${route.join("/")}${qs}`;
21
+ if (options.onRequest) {
22
+ const result = await options.onRequest({ path, request });
23
+ if (result === false) {
24
+ return json({ error: "Request rejected" }, 403);
25
+ }
26
+ }
27
+ const method = request.method;
28
+ const hasBody = method !== "GET" && method !== "HEAD";
29
+ let upstream;
30
+ try {
31
+ const init = {
32
+ method,
33
+ headers: {
34
+ Authorization: `Bearer ${options.token}`,
35
+ ...hasBody && {
36
+ "Content-Type": request.headers.get("content-type") ?? "application/json"
37
+ }
38
+ },
39
+ signal: request.signal
40
+ };
41
+ if (hasBody) {
42
+ init.body = request.body;
43
+ init.duplex = "half";
44
+ }
45
+ upstream = await fetchImpl(`${baseUrl}${path}`, init);
46
+ } catch (err) {
47
+ return json(
48
+ {
49
+ error: err instanceof Error ? `Upstream fetch failed: ${err.message}` : "Upstream fetch failed"
50
+ },
51
+ 502
52
+ );
53
+ }
54
+ const outHeaders = new Headers();
55
+ const ct = upstream.headers.get("content-type");
56
+ if (ct) outHeaders.set("Content-Type", ct);
57
+ const cc = upstream.headers.get("cache-control");
58
+ if (cc) outHeaders.set("Cache-Control", cc);
59
+ const ra = upstream.headers.get("retry-after");
60
+ if (ra) outHeaders.set("Retry-After", ra);
61
+ if (ct?.includes("text/event-stream")) {
62
+ outHeaders.set("Cache-Control", "no-cache, no-transform");
63
+ outHeaders.set("Connection", "keep-alive");
64
+ outHeaders.set("X-Accel-Buffering", "no");
65
+ }
66
+ return new Response(upstream.body, {
67
+ status: upstream.status,
68
+ headers: outHeaders
69
+ });
70
+ }
71
+ return { POST: proxy, GET: proxy };
72
+ }
73
+ function json(body, status) {
74
+ return new Response(JSON.stringify(body), {
75
+ status,
76
+ headers: { "Content-Type": "application/json" }
77
+ });
78
+ }
79
+
80
+ exports.createDuckvizHandlers = createDuckvizHandlers;