@unifiedcommerce/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/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@unifiedcommerce/sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "default": "./dist/index.js"
9
+ },
10
+ "./react": {
11
+ "types": "./dist/react.d.ts",
12
+ "default": "./dist/react.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.build.json",
17
+ "check-types": "tsc --noEmit",
18
+ "lint": "eslint . --max-warnings 1000",
19
+ "test": "vitest run",
20
+ "sdk:generate": "bun scripts/generate-types.ts",
21
+ "sdk:generate:local": "bun scripts/generate-types.ts --local",
22
+ "sdk:generate:runvae": "bun scripts/generate-types.ts --app ../../apps/runvae",
23
+ "sdk:generate:store-example": "bun scripts/generate-types.ts --app ../../apps/store-example"
24
+ },
25
+ "dependencies": {
26
+ "openapi-fetch": "^0.17.0"
27
+ },
28
+ "devDependencies": {
29
+ "@electric-sql/pglite": "*",
30
+ "@repo/eslint-config": "*",
31
+ "@repo/typescript-config": "*",
32
+ "@types/node": "^24.5.2",
33
+ "@unifiedcommerce/core": "*",
34
+ "@unifiedcommerce/plugin-appointments": "*",
35
+ "@unifiedcommerce/plugin-marketplace": "*",
36
+ "@unifiedcommerce/plugin-pos": "*",
37
+ "drizzle-orm": "^0.45.1",
38
+ "eslint": "^9.39.1",
39
+ "openapi-typescript": "^7.13.0",
40
+ "typescript": "5.9.2",
41
+ "vitest": "^3.2.4"
42
+ },
43
+ "peerDependencies": {
44
+ "@tanstack/react-query": ">=5.0.0",
45
+ "openapi-react-query": ">=0.3.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@tanstack/react-query": {
49
+ "optional": true
50
+ },
51
+ "openapi-react-query": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "files": [
59
+ "dist",
60
+ "src",
61
+ "README.md"
62
+ ]
63
+ }
package/src/client.ts ADDED
@@ -0,0 +1,305 @@
1
+ import createClient from "openapi-fetch";
2
+ import type { paths } from "./generated/api-types";
3
+ import { authMiddleware, type AuthCredential } from "./middleware";
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
+ // Strips undefined values from params to satisfy exactOptionalPropertyTypes
17
+ function params<T extends Record<string, unknown>>(p: T): T {
18
+ const result: Record<string, unknown> = {};
19
+ for (const [k, v] of Object.entries(p)) {
20
+ if (v !== undefined) result[k] = v;
21
+ }
22
+ return result as T;
23
+ }
24
+
25
+ /**
26
+ * Creates a typed UnifiedCommerce SDK client.
27
+ *
28
+ * Layer 1 (generated types from OpenAPI spec) provides compile-time
29
+ * validation of request bodies, query parameters, and response shapes.
30
+ *
31
+ * Layer 2 (this wrapper) provides ergonomic domain namespaces:
32
+ * sdk.catalog.list(), sdk.cart.addItem(), sdk.orders.get(), etc.
33
+ *
34
+ * For routes not covered by the wrapper (plugin routes, custom routes),
35
+ * use sdk.raw which is the full openapi-fetch client with all paths typed.
36
+ */
37
+ export function createSDK(options: SDKOptions) {
38
+ const clientOpts: Parameters<typeof createClient<paths>>[0] = {
39
+ baseUrl: options.baseUrl,
40
+ };
41
+ if (options.headers) clientOpts.headers = options.headers;
42
+ if (options.fetch) clientOpts.fetch = options.fetch;
43
+
44
+ const client = createClient<paths>(clientOpts);
45
+
46
+ if (options.auth) {
47
+ client.use(authMiddleware(options.auth));
48
+ }
49
+
50
+ return {
51
+ /** Raw openapi-fetch client for paths not covered by the wrapper. */
52
+ raw: client,
53
+
54
+ // ─── Catalog ──────────────────────────────────────────────────────────
55
+
56
+ catalog: {
57
+ list(query?: { type?: string; status?: string; category?: string; brand?: string; include?: string; sort?: string; page?: string; limit?: string }) {
58
+ return client.GET("/api/catalog/entities", query ? { params: { query } } : {});
59
+ },
60
+ get(idOrSlug: string, query?: { include?: string }) {
61
+ return client.GET("/api/catalog/entities/{idOrSlug}", { params: params({ path: { idOrSlug }, ...(query ? { query } : {}) }) });
62
+ },
63
+ create(body: paths["/api/catalog/entities"]["post"]["requestBody"]["content"]["application/json"]) {
64
+ return client.POST("/api/catalog/entities", { body });
65
+ },
66
+ update(id: string, body: paths["/api/catalog/entities/{id}"]["patch"]["requestBody"]["content"]["application/json"]) {
67
+ return client.PATCH("/api/catalog/entities/{id}", { params: { path: { id } }, body });
68
+ },
69
+ remove(id: string) {
70
+ return client.DELETE("/api/catalog/entities/{id}", { params: { path: { id } } });
71
+ },
72
+ publish(id: string) {
73
+ return client.POST("/api/catalog/entities/{id}/publish", { params: { path: { id } } });
74
+ },
75
+ archive(id: string) {
76
+ return client.POST("/api/catalog/entities/{id}/archive", { params: { path: { id } } });
77
+ },
78
+ discontinue(id: string) {
79
+ return client.POST("/api/catalog/entities/{id}/discontinue", { params: { path: { id } } });
80
+ },
81
+ setAttributes(id: string, locale: string, body: paths["/api/catalog/entities/{id}/attributes/{locale}"]["put"]["requestBody"]["content"]["application/json"]) {
82
+ return client.PUT("/api/catalog/entities/{id}/attributes/{locale}", { params: { path: { id, locale } }, body });
83
+ },
84
+ getAttributes(id: string, locale: string) {
85
+ return client.GET("/api/catalog/entities/{id}/attributes/{locale}", { params: { path: { id, locale } } });
86
+ },
87
+ addToCategory(id: string, categoryId: string) {
88
+ return client.POST("/api/catalog/entities/{id}/categories/{categoryId}", { params: { path: { id, categoryId } } });
89
+ },
90
+ removeFromCategory(id: string, categoryId: string) {
91
+ return client.DELETE("/api/catalog/entities/{id}/categories/{categoryId}", { params: { path: { id, categoryId } } });
92
+ },
93
+ listCategories() {
94
+ return client.GET("/api/catalog/categories");
95
+ },
96
+ createCategory(body: paths["/api/catalog/categories"]["post"]["requestBody"]["content"]["application/json"]) {
97
+ return client.POST("/api/catalog/categories", { body });
98
+ },
99
+ updateCategory(categoryId: string, body: paths["/api/catalog/categories/{categoryId}"]["patch"]["requestBody"]["content"]["application/json"]) {
100
+ return client.PATCH("/api/catalog/categories/{categoryId}", { params: { path: { categoryId } }, body });
101
+ },
102
+ deleteCategory(categoryId: string) {
103
+ return client.DELETE("/api/catalog/categories/{categoryId}", { params: { path: { categoryId } } });
104
+ },
105
+ listBrands() {
106
+ return client.GET("/api/catalog/brands");
107
+ },
108
+ createBrand(body: paths["/api/catalog/brands"]["post"]["requestBody"]["content"]["application/json"]) {
109
+ return client.POST("/api/catalog/brands", { body });
110
+ },
111
+ updateBrand(brandId: string, body: paths["/api/catalog/brands/{brandId}"]["patch"]["requestBody"]["content"]["application/json"]) {
112
+ return client.PATCH("/api/catalog/brands/{brandId}", { params: { path: { brandId } }, body });
113
+ },
114
+ deleteBrand(brandId: string) {
115
+ return client.DELETE("/api/catalog/brands/{brandId}", { params: { path: { brandId } } });
116
+ },
117
+ addToBrand(id: string, brandId: string) {
118
+ return client.POST("/api/catalog/entities/{id}/brands/{brandId}", { params: { path: { id, brandId } } });
119
+ },
120
+ removeFromBrand(id: string, brandId: string) {
121
+ return client.DELETE("/api/catalog/entities/{id}/brands/{brandId}", { params: { path: { id, brandId } } });
122
+ },
123
+ createOptionType(id: string, body: paths["/api/catalog/entities/{id}/options"]["post"]["requestBody"]["content"]["application/json"]) {
124
+ return client.POST("/api/catalog/entities/{id}/options", { params: { path: { id } }, body });
125
+ },
126
+ createOptionValue(optionTypeId: string, body: paths["/api/catalog/options/{optionTypeId}/values"]["post"]["requestBody"]["content"]["application/json"]) {
127
+ return client.POST("/api/catalog/options/{optionTypeId}/values", { params: { path: { optionTypeId } }, body });
128
+ },
129
+ createVariant(id: string, body: paths["/api/catalog/entities/{id}/variants"]["post"]["requestBody"]["content"]["application/json"]) {
130
+ return client.POST("/api/catalog/entities/{id}/variants", { params: { path: { id } }, body });
131
+ },
132
+ generateVariants(id: string, body: paths["/api/catalog/entities/{id}/variants/generate"]["post"]["requestBody"]["content"]["application/json"]) {
133
+ return client.POST("/api/catalog/entities/{id}/variants/generate", { params: { path: { id } }, body });
134
+ },
135
+ },
136
+
137
+ // ─── Inventory ────────────────────────────────────────────────────────
138
+
139
+ inventory: {
140
+ check(query: { entityIds: string }) {
141
+ return client.GET("/api/inventory/check", { params: { query } });
142
+ },
143
+ adjust(body: paths["/api/inventory/adjust"]["post"]["requestBody"]["content"]["application/json"]) {
144
+ return client.POST("/api/inventory/adjust", { body });
145
+ },
146
+ reserve(body: paths["/api/inventory/reserve"]["post"]["requestBody"]["content"]["application/json"]) {
147
+ return client.POST("/api/inventory/reserve", { body });
148
+ },
149
+ release(body: paths["/api/inventory/release"]["post"]["requestBody"]["content"]["application/json"]) {
150
+ return client.POST("/api/inventory/release", { body });
151
+ },
152
+ createWarehouse(body: paths["/api/inventory/warehouses"]["post"]["requestBody"]["content"]["application/json"]) {
153
+ return client.POST("/api/inventory/warehouses", { body });
154
+ },
155
+ listWarehouses() {
156
+ return client.GET("/api/inventory/warehouses");
157
+ },
158
+ },
159
+
160
+ // ─── Cart ─────────────────────────────────────────────────────────────
161
+
162
+ cart: {
163
+ create(body: paths["/api/carts"]["post"]["requestBody"]["content"]["application/json"]) {
164
+ return client.POST("/api/carts", { body });
165
+ },
166
+ get(id: string) {
167
+ return client.GET("/api/carts/{id}", { params: { path: { id } } });
168
+ },
169
+ addItem(id: string, body: paths["/api/carts/{id}/items"]["post"]["requestBody"]["content"]["application/json"]) {
170
+ return client.POST("/api/carts/{id}/items", { params: { path: { id } }, body });
171
+ },
172
+ updateItem(id: string, itemId: string, body: paths["/api/carts/{id}/items/{itemId}"]["patch"]["requestBody"]["content"]["application/json"]) {
173
+ return client.PATCH("/api/carts/{id}/items/{itemId}", { params: { path: { id, itemId } }, body });
174
+ },
175
+ removeItem(id: string, itemId: string) {
176
+ return client.DELETE("/api/carts/{id}/items/{itemId}", { params: { path: { id, itemId } } });
177
+ },
178
+ },
179
+
180
+ // ─── Checkout ─────────────────────────────────────────────────────────
181
+
182
+ checkout: {
183
+ create(body: paths["/api/checkout"]["post"]["requestBody"]["content"]["application/json"]) {
184
+ return client.POST("/api/checkout", { body });
185
+ },
186
+ },
187
+
188
+ // ─── Orders ───────────────────────────────────────────────────────────
189
+
190
+ orders: {
191
+ list(query?: { page?: string; limit?: string; status?: string }) {
192
+ return client.GET("/api/orders", query ? { params: { query } } : {});
193
+ },
194
+ get(idOrNumber: string) {
195
+ return client.GET("/api/orders/{idOrNumber}", { params: { path: { idOrNumber } } });
196
+ },
197
+ changeStatus(id: string, body: paths["/api/orders/{id}/status"]["patch"]["requestBody"]["content"]["application/json"]) {
198
+ return client.PATCH("/api/orders/{id}/status", { params: { path: { id } }, body });
199
+ },
200
+ listFulfillments(id: string) {
201
+ return client.GET("/api/orders/{id}/fulfillments", { params: { path: { id } } });
202
+ },
203
+ },
204
+
205
+ // ─── Pricing ──────────────────────────────────────────────────────────
206
+
207
+ pricing: {
208
+ setBasePrice(body: paths["/api/pricing/prices"]["post"]["requestBody"]["content"]["application/json"]) {
209
+ return client.POST("/api/pricing/prices", { body });
210
+ },
211
+ listPrices(query?: { entityId?: string; variantId?: string; currency?: string }) {
212
+ return client.GET("/api/pricing/prices", query ? { params: { query } } : {});
213
+ },
214
+ createModifier(body: paths["/api/pricing/modifiers"]["post"]["requestBody"]["content"]["application/json"]) {
215
+ return client.POST("/api/pricing/modifiers", { body });
216
+ },
217
+ },
218
+
219
+ // ─── Promotions ───────────────────────────────────────────────────────
220
+
221
+ promotions: {
222
+ create(body: paths["/api/promotions"]["post"]["requestBody"]["content"]["application/json"]) {
223
+ return client.POST("/api/promotions", { body });
224
+ },
225
+ list() {
226
+ return client.GET("/api/promotions");
227
+ },
228
+ validate(body: paths["/api/promotions/validate"]["post"]["requestBody"]["content"]["application/json"]) {
229
+ return client.POST("/api/promotions/validate", { body });
230
+ },
231
+ deactivate(id: string) {
232
+ return client.POST("/api/promotions/{id}/deactivate", { params: { path: { id } } });
233
+ },
234
+ },
235
+
236
+ // ─── Search ───────────────────────────────────────────────────────────
237
+
238
+ search: {
239
+ query(query: { q: string; type?: string; page?: string; limit?: string }) {
240
+ return client.GET("/api/search", { params: { query } });
241
+ },
242
+ suggest(query: { prefix: string; type?: string; limit?: string }) {
243
+ return client.GET("/api/search/suggest", { params: { query } });
244
+ },
245
+ },
246
+
247
+ // ─── Webhooks ─────────────────────────────────────────────────────────
248
+
249
+ webhooks: {
250
+ create(body: paths["/api/webhooks"]["post"]["requestBody"]["content"]["application/json"]) {
251
+ return client.POST("/api/webhooks", { body });
252
+ },
253
+ list() {
254
+ return client.GET("/api/webhooks");
255
+ },
256
+ remove(id: string) {
257
+ return client.DELETE("/api/webhooks/{id}", { params: { path: { id } } });
258
+ },
259
+ },
260
+
261
+ // ─── Customer Self-Service (me) ───────────────────────────────────────
262
+
263
+ me: {
264
+ profile: {
265
+ get() { return client.GET("/api/me/profile"); },
266
+ update(body: paths["/api/me/profile"]["patch"]["requestBody"]["content"]["application/json"]) {
267
+ return client.PATCH("/api/me/profile", { body });
268
+ },
269
+ },
270
+ addresses: {
271
+ list() { return client.GET("/api/me/addresses"); },
272
+ create(body: paths["/api/me/addresses"]["post"]["requestBody"]["content"]["application/json"]) {
273
+ return client.POST("/api/me/addresses", { body });
274
+ },
275
+ remove(id: string) {
276
+ return client.DELETE("/api/me/addresses/{id}", { params: { path: { id } } });
277
+ },
278
+ },
279
+ orders: {
280
+ list(query?: { page?: string; limit?: string }) {
281
+ return client.GET("/api/me/orders", query ? { params: { query } } : {});
282
+ },
283
+ // idOrNumber: accepts UUID or order number (e.g., "ORD-001")
284
+ get(idOrNumber: string) {
285
+ return client.GET("/api/me/orders/{idOrNumber}", { params: { path: { idOrNumber } } });
286
+ },
287
+ // idOrNumber: accepts UUID or order number
288
+ tracking(idOrNumber: string) {
289
+ return client.GET("/api/me/orders/{idOrNumber}/tracking", { params: { path: { idOrNumber } } });
290
+ },
291
+ // orderId: UUID only (downloads are keyed by internal ID, not order number)
292
+ downloads(orderId: string) {
293
+ return client.GET("/api/me/orders/{orderId}/downloads", { params: { path: { orderId } } });
294
+ },
295
+ // orderId: UUID only
296
+ reorder(orderId: string) {
297
+ return client.POST("/api/me/orders/{orderId}/reorder", { params: { path: { orderId } } });
298
+ },
299
+ },
300
+ courses: {
301
+ list() { return client.GET("/api/me/courses"); },
302
+ },
303
+ },
304
+ };
305
+ }