@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,51 @@
1
+ /**
2
+ * @duckviz/sdk/next — thin Next.js App Router handler that proxies requests
3
+ * to DuckViz Cloud with the server's token attached.
4
+ *
5
+ * Usage:
6
+ * // app/api/duckviz/[...route]/route.ts
7
+ * import { createDuckvizHandlers } from "@duckviz/sdk/next";
8
+ * export const { POST } = createDuckvizHandlers({
9
+ * token: process.env.DUCKVIZ_TOKEN!,
10
+ * });
11
+ *
12
+ * What it does:
13
+ * - Forwards the request body 1:1 to `${baseUrl}/api/<catchall>`.
14
+ * - Attaches `Authorization: Bearer <token>` server-side. Browser never
15
+ * sees the token.
16
+ * - Pass-through SSE without buffering: returns `new Response(upstream.body, …)`
17
+ * so events stream straight through to the browser.
18
+ * - Forwards AbortSignal both ways, so a browser disconnect tears down the
19
+ * upstream request.
20
+ *
21
+ * Hard rules (see consumer platform plan):
22
+ * - Never mutate the request body.
23
+ * - Never buffer SSE responses.
24
+ * - Never log the token.
25
+ */
26
+ interface DuckvizHandlerOptions {
27
+ /** Server-only DuckViz personal access token (`dvz_live_...`). */
28
+ token: string;
29
+ /** Override DuckViz Cloud base URL. */
30
+ baseUrl?: string;
31
+ /** Optional hook called before each upstream request. Return false to reject. */
32
+ onRequest?: (ctx: {
33
+ path: string;
34
+ request: Request;
35
+ }) => void | boolean | Promise<void | boolean>;
36
+ /** Injectable fetch — used in tests. */
37
+ fetch?: typeof fetch;
38
+ }
39
+ type NextRouteContext = {
40
+ params: Promise<{
41
+ route?: string[];
42
+ }> | {
43
+ route?: string[];
44
+ };
45
+ };
46
+ declare function createDuckvizHandlers(options: DuckvizHandlerOptions): {
47
+ POST: (request: Request, ctx: NextRouteContext) => Promise<Response>;
48
+ GET: (request: Request, ctx: NextRouteContext) => Promise<Response>;
49
+ };
50
+
51
+ export { type DuckvizHandlerOptions, createDuckvizHandlers };
package/dist/next.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @duckviz/sdk/next — thin Next.js App Router handler that proxies requests
3
+ * to DuckViz Cloud with the server's token attached.
4
+ *
5
+ * Usage:
6
+ * // app/api/duckviz/[...route]/route.ts
7
+ * import { createDuckvizHandlers } from "@duckviz/sdk/next";
8
+ * export const { POST } = createDuckvizHandlers({
9
+ * token: process.env.DUCKVIZ_TOKEN!,
10
+ * });
11
+ *
12
+ * What it does:
13
+ * - Forwards the request body 1:1 to `${baseUrl}/api/<catchall>`.
14
+ * - Attaches `Authorization: Bearer <token>` server-side. Browser never
15
+ * sees the token.
16
+ * - Pass-through SSE without buffering: returns `new Response(upstream.body, …)`
17
+ * so events stream straight through to the browser.
18
+ * - Forwards AbortSignal both ways, so a browser disconnect tears down the
19
+ * upstream request.
20
+ *
21
+ * Hard rules (see consumer platform plan):
22
+ * - Never mutate the request body.
23
+ * - Never buffer SSE responses.
24
+ * - Never log the token.
25
+ */
26
+ interface DuckvizHandlerOptions {
27
+ /** Server-only DuckViz personal access token (`dvz_live_...`). */
28
+ token: string;
29
+ /** Override DuckViz Cloud base URL. */
30
+ baseUrl?: string;
31
+ /** Optional hook called before each upstream request. Return false to reject. */
32
+ onRequest?: (ctx: {
33
+ path: string;
34
+ request: Request;
35
+ }) => void | boolean | Promise<void | boolean>;
36
+ /** Injectable fetch — used in tests. */
37
+ fetch?: typeof fetch;
38
+ }
39
+ type NextRouteContext = {
40
+ params: Promise<{
41
+ route?: string[];
42
+ }> | {
43
+ route?: string[];
44
+ };
45
+ };
46
+ declare function createDuckvizHandlers(options: DuckvizHandlerOptions): {
47
+ POST: (request: Request, ctx: NextRouteContext) => Promise<Response>;
48
+ GET: (request: Request, ctx: NextRouteContext) => Promise<Response>;
49
+ };
50
+
51
+ export { type DuckvizHandlerOptions, createDuckvizHandlers };
package/dist/next.js ADDED
@@ -0,0 +1,78 @@
1
+ import { DEFAULT_BASE_URL } from './chunk-WYWQAALZ.js';
2
+
3
+ // src/next.ts
4
+ function createDuckvizHandlers(options) {
5
+ if (!options.token) {
6
+ throw new Error("createDuckvizHandlers: `token` is required");
7
+ }
8
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
9
+ const fetchImpl = options.fetch ?? globalThis.fetch;
10
+ async function proxy(request, ctx) {
11
+ const params = await Promise.resolve(ctx.params);
12
+ const route = Array.isArray(params.route) ? params.route : [];
13
+ if (route.length === 0) {
14
+ return json({ error: "Missing route path" }, 400);
15
+ }
16
+ const incomingUrl = new URL(request.url);
17
+ const qs = incomingUrl.search;
18
+ const path = `/api/${route.join("/")}${qs}`;
19
+ if (options.onRequest) {
20
+ const result = await options.onRequest({ path, request });
21
+ if (result === false) {
22
+ return json({ error: "Request rejected" }, 403);
23
+ }
24
+ }
25
+ const method = request.method;
26
+ const hasBody = method !== "GET" && method !== "HEAD";
27
+ let upstream;
28
+ try {
29
+ const init = {
30
+ method,
31
+ headers: {
32
+ Authorization: `Bearer ${options.token}`,
33
+ ...hasBody && {
34
+ "Content-Type": request.headers.get("content-type") ?? "application/json"
35
+ }
36
+ },
37
+ signal: request.signal
38
+ };
39
+ if (hasBody) {
40
+ init.body = request.body;
41
+ init.duplex = "half";
42
+ }
43
+ upstream = await fetchImpl(`${baseUrl}${path}`, init);
44
+ } catch (err) {
45
+ return json(
46
+ {
47
+ error: err instanceof Error ? `Upstream fetch failed: ${err.message}` : "Upstream fetch failed"
48
+ },
49
+ 502
50
+ );
51
+ }
52
+ const outHeaders = new Headers();
53
+ const ct = upstream.headers.get("content-type");
54
+ if (ct) outHeaders.set("Content-Type", ct);
55
+ const cc = upstream.headers.get("cache-control");
56
+ if (cc) outHeaders.set("Cache-Control", cc);
57
+ const ra = upstream.headers.get("retry-after");
58
+ if (ra) outHeaders.set("Retry-After", ra);
59
+ if (ct?.includes("text/event-stream")) {
60
+ outHeaders.set("Cache-Control", "no-cache, no-transform");
61
+ outHeaders.set("Connection", "keep-alive");
62
+ outHeaders.set("X-Accel-Buffering", "no");
63
+ }
64
+ return new Response(upstream.body, {
65
+ status: upstream.status,
66
+ headers: outHeaders
67
+ });
68
+ }
69
+ return { POST: proxy, GET: proxy };
70
+ }
71
+ function json(body, status) {
72
+ return new Response(JSON.stringify(body), {
73
+ status,
74
+ headers: { "Content-Type": "application/json" }
75
+ });
76
+ }
77
+
78
+ export { createDuckvizHandlers };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@duckviz/sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "DuckViz SDK — LangChain-style composable classes for DuckViz AI endpoints. Node-only; never import from browser code.",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "browser": false,
10
+ "engines": {
11
+ "node": ">=18"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.cjs"
18
+ },
19
+ "./next": {
20
+ "types": "./dist/next.d.ts",
21
+ "import": "./dist/next.js",
22
+ "require": "./dist/next.cjs"
23
+ },
24
+ "./express": {
25
+ "types": "./dist/express.d.ts",
26
+ "import": "./dist/express.js",
27
+ "require": "./dist/express.cjs"
28
+ }
29
+ },
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "registry": "https://registry.npmjs.org/"
33
+ },
34
+ "files": ["dist", "README.md"],
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "check-types": "tsc --noEmit",
39
+ "test": "bun test",
40
+ "lint": "echo 'lint ok'"
41
+ },
42
+ "dependencies": {},
43
+ "devDependencies": {
44
+ "@duckviz/typescript-config": "workspace:*",
45
+ "@types/node": "^22.0.0",
46
+ "tsup": "^8.4.0",
47
+ "typescript": "5.9.2"
48
+ }
49
+ }