@porulle/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.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @porulle/sdk
2
+
3
+ Typed TypeScript client for any Porulle server. Generated from the OpenAPI spec — every endpoint, request body, and response is type-checked at compile time.
4
+
5
+ ## Two surfaces
6
+
7
+ ```ts
8
+ import { createClient } from "@porulle/sdk";
9
+ import { createCommerceHooks } from "@porulle/sdk/react";
10
+ ```
11
+
12
+ - **`createClient<paths>`** — a vanilla `openapi-fetch` client. Use anywhere (Node, Bun, browser, Cloudflare Workers).
13
+ - **`createCommerceHooks(client)`** — TanStack Query (React Query) hooks bound to that client.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { createClient } from "@porulle/sdk";
19
+ import type { paths } from "./generated/api-types"; // generate via openapi-typescript against your /api/doc
20
+
21
+ const client = createClient<paths>({
22
+ baseUrl: "https://your-store.com",
23
+ auth: { type: "api_key", key: process.env.PORULLE_API_KEY! },
24
+ });
25
+
26
+ const { data, error } = await client.GET("/api/catalog/entities", {
27
+ params: { query: { type: "product", limit: 10 } },
28
+ });
29
+ ```
30
+
31
+ React Query bindings:
32
+
33
+ ```tsx
34
+ import { createCommerceHooks } from "@porulle/sdk/react";
35
+
36
+ const commerce = createCommerceHooks(client);
37
+
38
+ function ProductList() {
39
+ const { data } = commerce.useQuery("get", "/api/catalog/entities", {
40
+ params: { query: { type: "product" } },
41
+ });
42
+ return <ul>{data?.data.map(p => <li key={p.id}>{p.slug}</li>)}</ul>;
43
+ }
44
+ ```
45
+
46
+ ## Auth credentials
47
+
48
+ | Type | Header sent |
49
+ |---|---|
50
+ | `{ type: "api_key", key }` | `x-api-key: <key>` |
51
+ | `{ type: "bearer", token }` | `Authorization: Bearer <token>` |
52
+ | (omitted) | request goes anonymous; the server's auth middleware decides what's allowed |
53
+
54
+ ## Generating types
55
+
56
+ The SDK is generic — bring your own `paths` type. Generate it from your server's OpenAPI doc:
57
+
58
+ ```bash
59
+ bunx openapi-typescript http://localhost:4000/api/doc -o src/generated/api-types.ts
60
+ ```
61
+
62
+ The shipped server exposes `/api/doc` (JSON) and `/api/reference` (Scalar UI in dev).
63
+
64
+ ## See also
65
+
66
+ - [Root README](../../README.md)
67
+ - `apps/store-example/` — full app using the SDK
package/dist/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SDK Codegen CLI — generates TypeScript types from your UC server's OpenAPI spec.
4
+ *
5
+ * Usage:
6
+ * bunx @porulle/sdk generate # from running server on :3000
7
+ * bunx @porulle/sdk generate --url http://localhost:4000/api/doc
8
+ * bunx @porulle/sdk generate --output src/types/api.ts
9
+ *
10
+ * This fetches /api/doc from your running server and runs openapi-typescript
11
+ * to produce a paths type file. Commit the output alongside your code.
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;GAUG"}
package/dist/cli.js ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SDK Codegen CLI — generates TypeScript types from your UC server's OpenAPI spec.
4
+ *
5
+ * Usage:
6
+ * bunx @porulle/sdk generate # from running server on :3000
7
+ * bunx @porulle/sdk generate --url http://localhost:4000/api/doc
8
+ * bunx @porulle/sdk generate --output src/types/api.ts
9
+ *
10
+ * This fetches /api/doc from your running server and runs openapi-typescript
11
+ * to produce a paths type file. Commit the output alongside your code.
12
+ */
13
+ import { writeFileSync, mkdirSync, unlinkSync } from "node:fs";
14
+ import { join, dirname } from "node:path";
15
+ import { execSync } from "node:child_process";
16
+ const args = process.argv.slice(2);
17
+ const command = args[0];
18
+ if (command !== "generate") {
19
+ console.log(`
20
+ @porulle/sdk — Type Generation CLI
21
+
22
+ Usage:
23
+ bunx @porulle/sdk generate [options]
24
+
25
+ Options:
26
+ --url <url> OpenAPI spec URL (default: http://localhost:3000/api/doc)
27
+ --output <path> Output file path (default: src/generated/api-types.ts)
28
+
29
+ Examples:
30
+ bunx @porulle/sdk generate
31
+ bunx @porulle/sdk generate --url http://localhost:4000/api/doc
32
+ bunx @porulle/sdk generate --output src/types/commerce.ts
33
+ `);
34
+ process.exit(command === undefined || command === "help" || command === "--help" ? 0 : 1);
35
+ }
36
+ const urlIdx = args.indexOf("--url");
37
+ const outputIdx = args.indexOf("--output");
38
+ const specUrl = urlIdx !== -1 && args[urlIdx + 1]
39
+ ? args[urlIdx + 1]
40
+ : process.env.API_URL ?? "http://localhost:3000/api/doc";
41
+ const outputFile = outputIdx !== -1 && args[outputIdx + 1]
42
+ ? args[outputIdx + 1]
43
+ : "src/generated/api-types.ts";
44
+ const outputDir = dirname(outputFile);
45
+ const tempSpec = join(outputDir, "_spec.json");
46
+ async function main() {
47
+ console.log(`Fetching OpenAPI spec from ${specUrl}...`);
48
+ let res;
49
+ try {
50
+ res = await fetch(specUrl);
51
+ }
52
+ catch (err) {
53
+ console.error(`\nCould not connect to ${specUrl}`);
54
+ console.error("");
55
+ console.error("Make sure your server is running:");
56
+ console.error(" bun run dev");
57
+ console.error("");
58
+ console.error("Or specify a custom URL:");
59
+ console.error(" bunx @porulle/sdk generate --url http://your-server/api/doc");
60
+ process.exit(1);
61
+ return; // unreachable, satisfies TS
62
+ }
63
+ if (!res.ok) {
64
+ console.error(`Server returned ${res.status} ${res.statusText}`);
65
+ console.error("Make sure the OpenAPI spec is enabled (GET /api/doc should return JSON).");
66
+ process.exit(1);
67
+ }
68
+ const spec = await res.json();
69
+ const pathCount = Object.keys(spec.paths ?? {}).length;
70
+ if (pathCount === 0) {
71
+ console.error("OpenAPI spec has 0 paths. Is the server configured correctly?");
72
+ process.exit(1);
73
+ }
74
+ mkdirSync(outputDir, { recursive: true });
75
+ writeFileSync(tempSpec, JSON.stringify(spec, null, 2));
76
+ console.log(`Spec extracted: ${pathCount} paths`);
77
+ console.log(`Generating types → ${outputFile}`);
78
+ try {
79
+ execSync(`npx openapi-typescript ${tempSpec} -o ${outputFile}`, {
80
+ stdio: "inherit",
81
+ });
82
+ }
83
+ catch {
84
+ console.error("openapi-typescript failed. Is it installed?");
85
+ console.error(" bun add -d openapi-typescript");
86
+ process.exit(1);
87
+ }
88
+ try {
89
+ unlinkSync(tempSpec);
90
+ }
91
+ catch { }
92
+ console.log(`\nDone — ${pathCount} paths typed in ${outputFile}`);
93
+ console.log("Commit this file alongside your route changes.");
94
+ }
95
+ main().catch((err) => {
96
+ console.error(err);
97
+ process.exit(1);
98
+ });
99
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { type AuthCredential } from "./middleware.js";
2
+ export interface SDKOptions {
3
+ /** Base URL of the UnifiedCommerce server (e.g., "http://localhost:4000"). */
4
+ baseUrl: string;
5
+ /** Authentication credential (API key or Bearer token). */
6
+ auth?: AuthCredential | undefined;
7
+ /** Additional headers sent with every request. */
8
+ headers?: Record<string, string> | undefined;
9
+ /** Custom fetch implementation (for testing or SSR). */
10
+ fetch?: typeof globalThis.fetch | undefined;
11
+ }
12
+ /**
13
+ * Creates a typed openapi-fetch client for your UnifiedCommerce API.
14
+ *
15
+ * Generic — you pass your own generated paths type:
16
+ *
17
+ * ```ts
18
+ * import { createClient } from "@porulle/sdk";
19
+ * import type { paths } from "./generated/api-types";
20
+ *
21
+ * const client = createClient<paths>({
22
+ * baseUrl: "http://localhost:3000",
23
+ * auth: { type: "api_key", key: "dev-key" },
24
+ * });
25
+ *
26
+ * const { data } = await client.GET("/api/catalog/entities");
27
+ * ```
28
+ */
29
+ export declare function createClient<TPaths extends {}>(options: SDKOptions): import("openapi-fetch").Client<TPaths, `${string}/${string}`>;
30
+ /**
31
+ * Creates a typed SDK with ergonomic domain namespaces.
32
+ *
33
+ * This is a convenience wrapper that creates an openapi-fetch client
34
+ * and adds sdk.catalog.list(), sdk.cart.addItem(), etc. on top.
35
+ *
36
+ * For full type coverage (including plugin routes), use createClient()
37
+ * with your own generated paths type instead.
38
+ *
39
+ * ```ts
40
+ * import { createSDK } from "@porulle/sdk";
41
+ * const sdk = createSDK({ baseUrl: "http://localhost:3000", auth: { ... } });
42
+ * const { data } = await sdk.catalog.list();
43
+ * ```
44
+ */
45
+ export declare function createSDK(options: SDKOptions): {
46
+ raw: import("openapi-fetch").Client<Record<string, unknown>, `${string}/${string}`>;
47
+ catalog: {
48
+ list(query?: Record<string, string>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
49
+ get(idOrSlug: string): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
50
+ create(body: Record<string, unknown>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
51
+ };
52
+ cart: {
53
+ create(body: Record<string, unknown>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
54
+ get(id: string): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
55
+ addItem(id: string, body: Record<string, unknown>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
56
+ };
57
+ checkout: {
58
+ create(body: Record<string, unknown>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
59
+ };
60
+ orders: {
61
+ list(query?: Record<string, string>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
62
+ get(idOrNumber: string): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
63
+ };
64
+ search: {
65
+ query(query: {
66
+ q: string;
67
+ [k: string]: string;
68
+ }): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
69
+ };
70
+ me: {
71
+ profile: {
72
+ get(): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
73
+ update(body: Record<string, unknown>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
74
+ };
75
+ orders: {
76
+ list(query?: Record<string, string>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
77
+ get(id: string): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
78
+ tracking(id: string): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
79
+ reorder(id: string): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
80
+ };
81
+ };
82
+ webhooks: {
83
+ list(query?: Record<string, string>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
84
+ get(id: string): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
85
+ create(body: Record<string, unknown>): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
86
+ delete(id: string): Promise<import("openapi-fetch").FetchResponse<unknown, never, `${string}/${string}`>>;
87
+ };
88
+ };
89
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtE,MAAM,WAAW,UAAU;IACzB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IAClC,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC7C,wDAAwD;IACxD,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,GAAG,SAAS,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,MAAM,SAAS,EAAE,EAAE,OAAO,EAAE,UAAU,iEAclE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,UAAU;;;qBAkB1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;sBACrB,MAAM;qBACP,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;qBAIvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAC5B,MAAM;oBACF,MAAM,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;qBAIpC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;qBAIvB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;wBACnB,MAAM;;;qBAIT;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;SAAE;;;;;yBAMhC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;yBAGvB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;oBAC3B,MAAM;yBACD,MAAM;wBACP,MAAM;;;;qBAKP,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;gBAC3B,MAAM;qBACD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;mBACzB,MAAM;;EAGtB"}
package/dist/client.js ADDED
@@ -0,0 +1,100 @@
1
+ import openapiCreateClient from "openapi-fetch";
2
+ import { authMiddleware } from "./middleware.js";
3
+ /**
4
+ * Creates a typed openapi-fetch client for your UnifiedCommerce API.
5
+ *
6
+ * Generic — you pass your own generated paths type:
7
+ *
8
+ * ```ts
9
+ * import { createClient } from "@porulle/sdk";
10
+ * import type { paths } from "./generated/api-types";
11
+ *
12
+ * const client = createClient<paths>({
13
+ * baseUrl: "http://localhost:3000",
14
+ * auth: { type: "api_key", key: "dev-key" },
15
+ * });
16
+ *
17
+ * const { data } = await client.GET("/api/catalog/entities");
18
+ * ```
19
+ */
20
+ export function createClient(options) {
21
+ const clientOpts = {
22
+ baseUrl: options.baseUrl,
23
+ };
24
+ if (options.headers)
25
+ clientOpts.headers = options.headers;
26
+ if (options.fetch)
27
+ clientOpts.fetch = options.fetch;
28
+ const client = openapiCreateClient(clientOpts);
29
+ if (options.auth) {
30
+ client.use(authMiddleware(options.auth));
31
+ }
32
+ return client;
33
+ }
34
+ /**
35
+ * Creates a typed SDK with ergonomic domain namespaces.
36
+ *
37
+ * This is a convenience wrapper that creates an openapi-fetch client
38
+ * and adds sdk.catalog.list(), sdk.cart.addItem(), etc. on top.
39
+ *
40
+ * For full type coverage (including plugin routes), use createClient()
41
+ * with your own generated paths type instead.
42
+ *
43
+ * ```ts
44
+ * import { createSDK } from "@porulle/sdk";
45
+ * const sdk = createSDK({ baseUrl: "http://localhost:3000", auth: { ... } });
46
+ * const { data } = await sdk.catalog.list();
47
+ * ```
48
+ */
49
+ export function createSDK(options) {
50
+ const client = openapiCreateClient({ baseUrl: options.baseUrl, ...(options.headers ? { headers: options.headers } : {}), ...(options.fetch ? { fetch: options.fetch } : {}) });
51
+ if (options.auth) {
52
+ client.use(authMiddleware(options.auth));
53
+ }
54
+ // Untyped convenience wrappers for core routes.
55
+ // These work without codegen but have no compile-time body/response validation.
56
+ // For full types, use createClient<paths>() with your generated types.
57
+ const raw = client;
58
+ return {
59
+ raw,
60
+ catalog: {
61
+ list(query) { return raw.GET("/api/catalog/entities", query ? { params: { query } } : undefined); },
62
+ get(idOrSlug) { return raw.GET("/api/catalog/entities/{idOrSlug}", { params: { path: { idOrSlug } } }); },
63
+ create(body) { return raw.POST("/api/catalog/entities", { body }); },
64
+ },
65
+ cart: {
66
+ create(body) { return raw.POST("/api/carts", { body }); },
67
+ get(id) { return raw.GET("/api/carts/{id}", { params: { path: { id } } }); },
68
+ addItem(id, body) { return raw.POST("/api/carts/{id}/items", { params: { path: { id } }, body }); },
69
+ },
70
+ checkout: {
71
+ create(body) { return raw.POST("/api/checkout", { body }); },
72
+ },
73
+ orders: {
74
+ list(query) { return raw.GET("/api/orders", query ? { params: { query } } : undefined); },
75
+ get(idOrNumber) { return raw.GET("/api/orders/{idOrNumber}", { params: { path: { idOrNumber } } }); },
76
+ },
77
+ search: {
78
+ query(query) { return raw.GET("/api/search", { params: { query } }); },
79
+ },
80
+ me: {
81
+ profile: {
82
+ get() { return raw.GET("/api/me/profile", undefined); },
83
+ update(body) { return raw.PATCH("/api/me/profile", { body }); },
84
+ },
85
+ orders: {
86
+ list(query) { return raw.GET("/api/me/orders", query ? { params: { query } } : undefined); },
87
+ get(id) { return raw.GET("/api/me/orders/{id}", { params: { path: { id } } }); },
88
+ tracking(id) { return raw.GET("/api/me/orders/{id}/tracking", { params: { path: { id } } }); },
89
+ reorder(id) { return raw.POST("/api/me/orders/{id}/reorder", { params: { path: { id } } }); },
90
+ },
91
+ },
92
+ webhooks: {
93
+ list(query) { return raw.GET("/api/webhooks", query ? { params: { query } } : undefined); },
94
+ get(id) { return raw.GET("/api/webhooks/{id}", { params: { path: { id } } }); },
95
+ create(body) { return raw.POST("/api/webhooks", { body }); },
96
+ delete(id) { return raw.DELETE("/api/webhooks/{id}", { params: { path: { id } } }); },
97
+ },
98
+ };
99
+ }
100
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { createSDK, createClient, type SDKOptions } from "./client.js";
2
+ export { authMiddleware, type AuthCredential, type ApiKeyAuth, type BearerAuth } from "./middleware.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createSDK, createClient } from "./client.js";
2
+ export { authMiddleware } from "./middleware.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,cAAc,EAAyD,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { Middleware } from "openapi-fetch";
2
+ export interface ApiKeyAuth {
3
+ type: "api_key";
4
+ key: string;
5
+ }
6
+ export interface BearerAuth {
7
+ type: "bearer";
8
+ token: string;
9
+ }
10
+ export type AuthCredential = ApiKeyAuth | BearerAuth;
11
+ /**
12
+ * openapi-fetch middleware that injects authentication headers.
13
+ *
14
+ * Supports API key (x-api-key header) and Bearer token (Authorization header).
15
+ */
16
+ export declare function authMiddleware(credential: AuthCredential): Middleware;
17
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,UAAU,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,cAAc,GAAG,UAAU,CAWrE"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * openapi-fetch middleware that injects authentication headers.
3
+ *
4
+ * Supports API key (x-api-key header) and Bearer token (Authorization header).
5
+ */
6
+ export function authMiddleware(credential) {
7
+ return {
8
+ onRequest({ request }) {
9
+ if (credential.type === "api_key") {
10
+ request.headers.set("x-api-key", credential.key);
11
+ }
12
+ else if (credential.type === "bearer") {
13
+ request.headers.set("Authorization", `Bearer ${credential.token}`);
14
+ }
15
+ return request;
16
+ },
17
+ };
18
+ }
19
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAcA;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,UAA0B;IACvD,OAAO;QACL,SAAS,CAAC,EAAE,OAAO,EAAE;YACnB,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAClC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;YACnD,CAAC;iBAAM,IAAI,UAAU,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACxC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * React Query integration for the UnifiedCommerce SDK.
3
+ *
4
+ * Wraps any openapi-fetch client in TanStack Query hooks.
5
+ * Generic — works with any generated paths type.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createClient } from "@porulle/sdk";
10
+ * import { createCommerceHooks } from "@porulle/sdk/react";
11
+ * import type { paths } from "./generated/api-types";
12
+ *
13
+ * const client = createClient<paths>({ baseUrl: "http://localhost:3000" });
14
+ * const commerce = createCommerceHooks(client);
15
+ *
16
+ * function ProductList() {
17
+ * const { data } = commerce.useQuery("get", "/api/catalog/entities", {
18
+ * params: { query: { type: "product" } },
19
+ * });
20
+ * }
21
+ * ```
22
+ */
23
+ import type createOpenapiClient from "openapi-fetch";
24
+ /**
25
+ * Creates TanStack Query hooks from any typed openapi-fetch client.
26
+ *
27
+ * @param client - A typed openapi-fetch client (from createClient<paths>())
28
+ * @returns useQuery, useMutation, useSuspenseQuery hooks typed against your paths
29
+ */
30
+ export declare function createCommerceHooks<TPaths extends {}>(client: ReturnType<typeof createOpenapiClient<TPaths>>): import("openapi-react-query").OpenapiQueryClient<TPaths, `${string}/${string}`>;
31
+ /** Type alias for the hooks object returned by createCommerceHooks. */
32
+ export type CommerceHooks<TPaths extends {} = {}> = ReturnType<typeof createCommerceHooks<TPaths>>;
33
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,mBAAmB,MAAM,eAAe,CAAC;AAErD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,SAAS,EAAE,EACnD,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC,mFAGvD;AAED,uEAAuE;AACvE,MAAM,MAAM,aAAa,CAAC,MAAM,SAAS,EAAE,GAAG,EAAE,IAAI,UAAU,CAAC,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC"}
package/dist/react.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * React Query integration for the UnifiedCommerce SDK.
3
+ *
4
+ * Wraps any openapi-fetch client in TanStack Query hooks.
5
+ * Generic — works with any generated paths type.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createClient } from "@porulle/sdk";
10
+ * import { createCommerceHooks } from "@porulle/sdk/react";
11
+ * import type { paths } from "./generated/api-types";
12
+ *
13
+ * const client = createClient<paths>({ baseUrl: "http://localhost:3000" });
14
+ * const commerce = createCommerceHooks(client);
15
+ *
16
+ * function ProductList() {
17
+ * const { data } = commerce.useQuery("get", "/api/catalog/entities", {
18
+ * params: { query: { type: "product" } },
19
+ * });
20
+ * }
21
+ * ```
22
+ */
23
+ import createQueryHooks from "openapi-react-query";
24
+ /**
25
+ * Creates TanStack Query hooks from any typed openapi-fetch client.
26
+ *
27
+ * @param client - A typed openapi-fetch client (from createClient<paths>())
28
+ * @returns useQuery, useMutation, useSuspenseQuery hooks typed against your paths
29
+ */
30
+ export function createCommerceHooks(client) {
31
+ return createQueryHooks(client);
32
+ }
33
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,gBAAgB,MAAM,qBAAqB,CAAC;AAGnD;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAsD;IAEtD,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@porulle/sdk",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "bun": "./src/index.ts",
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ },
12
+ "./react": {
13
+ "bun": "./src/react.ts",
14
+ "import": "./dist/react.js",
15
+ "types": "./dist/react.d.ts"
16
+ }
17
+ },
18
+ "bin": {
19
+ "unifiedcommerce-sdk": "./dist/cli.js"
20
+ },
21
+ "scripts": {
22
+ "build": "rm -rf dist tsconfig.build.tsbuildinfo && tsc -p tsconfig.build.json",
23
+ "check-types": "tsc --noEmit",
24
+ "lint": "eslint . --max-warnings 1000",
25
+ "test": "vitest run"
26
+ },
27
+ "dependencies": {
28
+ "openapi-fetch": "^0.17.0",
29
+ "openapi-typescript-helpers": "^0.1.0"
30
+ },
31
+ "devDependencies": {
32
+ "@repo/eslint-config": "*",
33
+ "@repo/typescript-config": "*",
34
+ "@types/node": "^24.5.2",
35
+ "eslint": "^9.39.1",
36
+ "openapi-react-query": "^0.5.4",
37
+ "@tanstack/react-query": "^5.94.5",
38
+ "openapi-typescript": "^7.13.0",
39
+ "typescript": "5.9.2",
40
+ "vitest": "^3.2.4"
41
+ },
42
+ "peerDependencies": {
43
+ "@tanstack/react-query": ">=5.0.0",
44
+ "openapi-react-query": ">=0.3.0",
45
+ "openapi-typescript": ">=7.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@tanstack/react-query": {
49
+ "optional": true
50
+ },
51
+ "openapi-react-query": {
52
+ "optional": true
53
+ },
54
+ "openapi-typescript": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ },
61
+ "files": [
62
+ "src",
63
+ "dist",
64
+ "README.md"
65
+ ],
66
+ "description": "Typed TypeScript client for any Porulle server. Generated from the OpenAPI spec — every endpoint, request body, and response is type-checked at compile time.",
67
+ "homepage": "https://porulle-docs.vercel.app",
68
+ "bugs": {
69
+ "url": "https://github.com/asyncdotengineering/porulle/issues"
70
+ },
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "git+https://github.com/asyncdotengineering/porulle.git",
74
+ "directory": "packages/sdk"
75
+ },
76
+ "author": "Porulle contributors"
77
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SDK Codegen CLI — generates TypeScript types from your UC server's OpenAPI spec.
5
+ *
6
+ * Usage:
7
+ * bunx @porulle/sdk generate # from running server on :3000
8
+ * bunx @porulle/sdk generate --url http://localhost:4000/api/doc
9
+ * bunx @porulle/sdk generate --output src/types/api.ts
10
+ *
11
+ * This fetches /api/doc from your running server and runs openapi-typescript
12
+ * to produce a paths type file. Commit the output alongside your code.
13
+ */
14
+
15
+ import { writeFileSync, mkdirSync, unlinkSync } from "node:fs";
16
+ import { join, dirname } from "node:path";
17
+ import { execSync } from "node:child_process";
18
+
19
+ const args = process.argv.slice(2);
20
+ const command = args[0];
21
+
22
+ if (command !== "generate") {
23
+ console.log(`
24
+ @porulle/sdk — Type Generation CLI
25
+
26
+ Usage:
27
+ bunx @porulle/sdk generate [options]
28
+
29
+ Options:
30
+ --url <url> OpenAPI spec URL (default: http://localhost:3000/api/doc)
31
+ --output <path> Output file path (default: src/generated/api-types.ts)
32
+
33
+ Examples:
34
+ bunx @porulle/sdk generate
35
+ bunx @porulle/sdk generate --url http://localhost:4000/api/doc
36
+ bunx @porulle/sdk generate --output src/types/commerce.ts
37
+ `);
38
+ process.exit(command === undefined || command === "help" || command === "--help" ? 0 : 1);
39
+ }
40
+
41
+ const urlIdx = args.indexOf("--url");
42
+ const outputIdx = args.indexOf("--output");
43
+
44
+ const specUrl = urlIdx !== -1 && args[urlIdx + 1]
45
+ ? args[urlIdx + 1]!
46
+ : process.env.API_URL ?? "http://localhost:3000/api/doc";
47
+
48
+ const outputFile = outputIdx !== -1 && args[outputIdx + 1]
49
+ ? args[outputIdx + 1]!
50
+ : "src/generated/api-types.ts";
51
+
52
+ const outputDir = dirname(outputFile);
53
+ const tempSpec = join(outputDir, "_spec.json");
54
+
55
+ async function main() {
56
+ console.log(`Fetching OpenAPI spec from ${specUrl}...`);
57
+
58
+ let res: Response;
59
+ try {
60
+ res = await fetch(specUrl);
61
+ } catch (err) {
62
+ console.error(`\nCould not connect to ${specUrl}`);
63
+ console.error("");
64
+ console.error("Make sure your server is running:");
65
+ console.error(" bun run dev");
66
+ console.error("");
67
+ console.error("Or specify a custom URL:");
68
+ console.error(" bunx @porulle/sdk generate --url http://your-server/api/doc");
69
+ process.exit(1);
70
+ return; // unreachable, satisfies TS
71
+ }
72
+
73
+ if (!res.ok) {
74
+ console.error(`Server returned ${res.status} ${res.statusText}`);
75
+ console.error("Make sure the OpenAPI spec is enabled (GET /api/doc should return JSON).");
76
+ process.exit(1);
77
+ }
78
+
79
+ const spec = await res.json() as { paths?: Record<string, unknown> };
80
+ const pathCount = Object.keys(spec.paths ?? {}).length;
81
+
82
+ if (pathCount === 0) {
83
+ console.error("OpenAPI spec has 0 paths. Is the server configured correctly?");
84
+ process.exit(1);
85
+ }
86
+
87
+ mkdirSync(outputDir, { recursive: true });
88
+ writeFileSync(tempSpec, JSON.stringify(spec, null, 2));
89
+ console.log(`Spec extracted: ${pathCount} paths`);
90
+
91
+ console.log(`Generating types → ${outputFile}`);
92
+ try {
93
+ execSync(`npx openapi-typescript ${tempSpec} -o ${outputFile}`, {
94
+ stdio: "inherit",
95
+ });
96
+ } catch {
97
+ console.error("openapi-typescript failed. Is it installed?");
98
+ console.error(" bun add -d openapi-typescript");
99
+ process.exit(1);
100
+ }
101
+
102
+ try { unlinkSync(tempSpec); } catch {}
103
+
104
+ console.log(`\nDone — ${pathCount} paths typed in ${outputFile}`);
105
+ console.log("Commit this file alongside your route changes.");
106
+ }
107
+
108
+ main().catch((err) => {
109
+ console.error(err);
110
+ process.exit(1);
111
+ });
package/src/client.ts ADDED
@@ -0,0 +1,126 @@
1
+ import openapiCreateClient from "openapi-fetch";
2
+ import type { PathsWithMethod, MediaType } from "openapi-typescript-helpers";
3
+ import { authMiddleware, type AuthCredential } from "./middleware.js";
4
+
5
+ export interface SDKOptions {
6
+ /** Base URL of the UnifiedCommerce server (e.g., "http://localhost:4000"). */
7
+ baseUrl: string;
8
+ /** Authentication credential (API key or Bearer token). */
9
+ auth?: AuthCredential | undefined;
10
+ /** Additional headers sent with every request. */
11
+ headers?: Record<string, string> | undefined;
12
+ /** Custom fetch implementation (for testing or SSR). */
13
+ fetch?: typeof globalThis.fetch | undefined;
14
+ }
15
+
16
+ /**
17
+ * Creates a typed openapi-fetch client for your UnifiedCommerce API.
18
+ *
19
+ * Generic — you pass your own generated paths type:
20
+ *
21
+ * ```ts
22
+ * import { createClient } from "@porulle/sdk";
23
+ * import type { paths } from "./generated/api-types";
24
+ *
25
+ * const client = createClient<paths>({
26
+ * baseUrl: "http://localhost:3000",
27
+ * auth: { type: "api_key", key: "dev-key" },
28
+ * });
29
+ *
30
+ * const { data } = await client.GET("/api/catalog/entities");
31
+ * ```
32
+ */
33
+ export function createClient<TPaths extends {}>(options: SDKOptions) {
34
+ const clientOpts: Parameters<typeof openapiCreateClient<TPaths>>[0] = {
35
+ baseUrl: options.baseUrl,
36
+ };
37
+ if (options.headers) clientOpts.headers = options.headers;
38
+ if (options.fetch) clientOpts.fetch = options.fetch;
39
+
40
+ const client = openapiCreateClient<TPaths>(clientOpts);
41
+
42
+ if (options.auth) {
43
+ client.use(authMiddleware(options.auth));
44
+ }
45
+
46
+ return client;
47
+ }
48
+
49
+ /**
50
+ * Creates a typed SDK with ergonomic domain namespaces.
51
+ *
52
+ * This is a convenience wrapper that creates an openapi-fetch client
53
+ * and adds sdk.catalog.list(), sdk.cart.addItem(), etc. on top.
54
+ *
55
+ * For full type coverage (including plugin routes), use createClient()
56
+ * with your own generated paths type instead.
57
+ *
58
+ * ```ts
59
+ * import { createSDK } from "@porulle/sdk";
60
+ * const sdk = createSDK({ baseUrl: "http://localhost:3000", auth: { ... } });
61
+ * const { data } = await sdk.catalog.list();
62
+ * ```
63
+ */
64
+ export function createSDK(options: SDKOptions) {
65
+ const client = openapiCreateClient<Record<string, never>>(
66
+ { baseUrl: options.baseUrl, ...(options.headers ? { headers: options.headers } : {}), ...(options.fetch ? { fetch: options.fetch } : {}) },
67
+ );
68
+
69
+ if (options.auth) {
70
+ client.use(authMiddleware(options.auth));
71
+ }
72
+
73
+ // Untyped convenience wrappers for core routes.
74
+ // These work without codegen but have no compile-time body/response validation.
75
+ // For full types, use createClient<paths>() with your generated types.
76
+ const raw = client as ReturnType<typeof openapiCreateClient<Record<string, unknown>>>;
77
+
78
+ return {
79
+ raw,
80
+
81
+ catalog: {
82
+ list(query?: Record<string, string>) { return raw.GET("/api/catalog/entities" as never, query ? { params: { query } } as never : undefined as never); },
83
+ get(idOrSlug: string) { return raw.GET("/api/catalog/entities/{idOrSlug}" as never, { params: { path: { idOrSlug } } } as never); },
84
+ create(body: Record<string, unknown>) { return raw.POST("/api/catalog/entities" as never, { body } as never); },
85
+ },
86
+
87
+ cart: {
88
+ create(body: Record<string, unknown>) { return raw.POST("/api/carts" as never, { body } as never); },
89
+ get(id: string) { return raw.GET("/api/carts/{id}" as never, { params: { path: { id } } } as never); },
90
+ addItem(id: string, body: Record<string, unknown>) { return raw.POST("/api/carts/{id}/items" as never, { params: { path: { id } }, body } as never); },
91
+ },
92
+
93
+ checkout: {
94
+ create(body: Record<string, unknown>) { return raw.POST("/api/checkout" as never, { body } as never); },
95
+ },
96
+
97
+ orders: {
98
+ list(query?: Record<string, string>) { return raw.GET("/api/orders" as never, query ? { params: { query } } as never : undefined as never); },
99
+ get(idOrNumber: string) { return raw.GET("/api/orders/{idOrNumber}" as never, { params: { path: { idOrNumber } } } as never); },
100
+ },
101
+
102
+ search: {
103
+ query(query: { q: string; [k: string]: string }) { return raw.GET("/api/search" as never, { params: { query } } as never); },
104
+ },
105
+
106
+ me: {
107
+ profile: {
108
+ get() { return raw.GET("/api/me/profile" as never, undefined as never); },
109
+ update(body: Record<string, unknown>) { return raw.PATCH("/api/me/profile" as never, { body } as never); },
110
+ },
111
+ orders: {
112
+ list(query?: Record<string, string>) { return raw.GET("/api/me/orders" as never, query ? { params: { query } } as never : undefined as never); },
113
+ get(id: string) { return raw.GET("/api/me/orders/{id}" as never, { params: { path: { id } } } as never); },
114
+ tracking(id: string) { return raw.GET("/api/me/orders/{id}/tracking" as never, { params: { path: { id } } } as never); },
115
+ reorder(id: string) { return raw.POST("/api/me/orders/{id}/reorder" as never, { params: { path: { id } } } as never); },
116
+ },
117
+ },
118
+
119
+ webhooks: {
120
+ list(query?: Record<string, string>) { return raw.GET("/api/webhooks" as never, query ? { params: { query } } as never : undefined as never); },
121
+ get(id: string) { return raw.GET("/api/webhooks/{id}" as never, { params: { path: { id } } } as never); },
122
+ create(body: Record<string, unknown>) { return raw.POST("/api/webhooks" as never, { body } as never); },
123
+ delete(id: string) { return raw.DELETE("/api/webhooks/{id}" as never, { params: { path: { id } } } as never); },
124
+ },
125
+ };
126
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { createSDK, createClient, type SDKOptions } from "./client.js";
2
+ export { authMiddleware, type AuthCredential, type ApiKeyAuth, type BearerAuth } from "./middleware.js";
@@ -0,0 +1,31 @@
1
+ import type { Middleware } from "openapi-fetch";
2
+
3
+ export interface ApiKeyAuth {
4
+ type: "api_key";
5
+ key: string;
6
+ }
7
+
8
+ export interface BearerAuth {
9
+ type: "bearer";
10
+ token: string;
11
+ }
12
+
13
+ export type AuthCredential = ApiKeyAuth | BearerAuth;
14
+
15
+ /**
16
+ * openapi-fetch middleware that injects authentication headers.
17
+ *
18
+ * Supports API key (x-api-key header) and Bearer token (Authorization header).
19
+ */
20
+ export function authMiddleware(credential: AuthCredential): Middleware {
21
+ return {
22
+ onRequest({ request }) {
23
+ if (credential.type === "api_key") {
24
+ request.headers.set("x-api-key", credential.key);
25
+ } else if (credential.type === "bearer") {
26
+ request.headers.set("Authorization", `Bearer ${credential.token}`);
27
+ }
28
+ return request;
29
+ },
30
+ };
31
+ }
package/src/react.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * React Query integration for the UnifiedCommerce SDK.
3
+ *
4
+ * Wraps any openapi-fetch client in TanStack Query hooks.
5
+ * Generic — works with any generated paths type.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createClient } from "@porulle/sdk";
10
+ * import { createCommerceHooks } from "@porulle/sdk/react";
11
+ * import type { paths } from "./generated/api-types";
12
+ *
13
+ * const client = createClient<paths>({ baseUrl: "http://localhost:3000" });
14
+ * const commerce = createCommerceHooks(client);
15
+ *
16
+ * function ProductList() {
17
+ * const { data } = commerce.useQuery("get", "/api/catalog/entities", {
18
+ * params: { query: { type: "product" } },
19
+ * });
20
+ * }
21
+ * ```
22
+ */
23
+
24
+ import createQueryHooks from "openapi-react-query";
25
+ import type createOpenapiClient from "openapi-fetch";
26
+
27
+ /**
28
+ * Creates TanStack Query hooks from any typed openapi-fetch client.
29
+ *
30
+ * @param client - A typed openapi-fetch client (from createClient<paths>())
31
+ * @returns useQuery, useMutation, useSuspenseQuery hooks typed against your paths
32
+ */
33
+ export function createCommerceHooks<TPaths extends {}>(
34
+ client: ReturnType<typeof createOpenapiClient<TPaths>>,
35
+ ) {
36
+ return createQueryHooks(client);
37
+ }
38
+
39
+ /** Type alias for the hooks object returned by createCommerceHooks. */
40
+ export type CommerceHooks<TPaths extends {} = {}> = ReturnType<typeof createCommerceHooks<TPaths>>;