@fayz-ai/shop 0.2.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 +36 -0
- package/dist/catalog.cjs +176 -0
- package/dist/catalog.cjs.map +1 -0
- package/dist/catalog.d.ts +4 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +170 -0
- package/dist/catalog.js.map +1 -0
- package/dist/commerce.d.ts +28 -0
- package/dist/commerce.d.ts.map +1 -0
- package/dist/index.cjs +1047 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1027 -0
- package/dist/index.js.map +1 -0
- package/dist/mock-catalog.d.ts +42 -0
- package/dist/mock-catalog.d.ts.map +1 -0
- package/dist/mock-provider.d.ts +48 -0
- package/dist/mock-provider.d.ts.map +1 -0
- package/dist/mock.cjs +430 -0
- package/dist/mock.cjs.map +1 -0
- package/dist/mock.d.ts +3 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +427 -0
- package/dist/mock.js.map +1 -0
- package/dist/provider.cjs +4 -0
- package/dist/provider.cjs.map +1 -0
- package/dist/provider.d.ts +42 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +3 -0
- package/dist/provider.js.map +1 -0
- package/dist/runtime.cjs +447 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +443 -0
- package/dist/runtime.js.map +1 -0
- package/dist/supabase-provider.d.ts +33 -0
- package/dist/supabase-provider.d.ts.map +1 -0
- package/dist/tenant.d.ts +4 -0
- package/dist/tenant.d.ts.map +1 -0
- package/dist/types.cjs +4 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.ts +345 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +99 -0
- package/src/catalog.ts +3 -0
- package/src/commerce.ts +120 -0
- package/src/index.ts +34 -0
- package/src/mock-catalog.ts +258 -0
- package/src/mock-provider.ts +372 -0
- package/src/mock.ts +2 -0
- package/src/provider.ts +59 -0
- package/src/runtime.ts +20 -0
- package/src/supabase-provider.ts +499 -0
- package/src/tenant.ts +21 -0
- package/src/types.ts +401 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
export type ProductStatus = 'draft' | 'active' | 'archived';
|
|
2
|
+
export interface Product {
|
|
3
|
+
id: string;
|
|
4
|
+
tenantId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
slug: string;
|
|
7
|
+
description: string | null;
|
|
8
|
+
price: number;
|
|
9
|
+
compareAtPrice: number | null;
|
|
10
|
+
currency: string;
|
|
11
|
+
status: ProductStatus;
|
|
12
|
+
inventoryCount: number;
|
|
13
|
+
sku: string | null;
|
|
14
|
+
sortOrder: number;
|
|
15
|
+
metadata: Record<string, unknown>;
|
|
16
|
+
images: ProductImage[];
|
|
17
|
+
categoryId: string | null;
|
|
18
|
+
categoryName: string | null;
|
|
19
|
+
isPhysical: boolean;
|
|
20
|
+
weight: number | null;
|
|
21
|
+
weightUnit: string;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ProductImage {
|
|
26
|
+
id: string;
|
|
27
|
+
productId: string;
|
|
28
|
+
url: string;
|
|
29
|
+
altText: string | null;
|
|
30
|
+
sortOrder: number;
|
|
31
|
+
isPrimary: boolean;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
}
|
|
34
|
+
export interface CreateProductInput {
|
|
35
|
+
name: string;
|
|
36
|
+
slug?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
price: number;
|
|
39
|
+
compareAtPrice?: number;
|
|
40
|
+
currency?: string;
|
|
41
|
+
status?: ProductStatus;
|
|
42
|
+
inventoryCount?: number;
|
|
43
|
+
sku?: string;
|
|
44
|
+
sortOrder?: number;
|
|
45
|
+
metadata?: Record<string, unknown>;
|
|
46
|
+
categoryId?: string;
|
|
47
|
+
isPhysical?: boolean;
|
|
48
|
+
weight?: number;
|
|
49
|
+
weightUnit?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface UpdateProductInput {
|
|
52
|
+
name?: string;
|
|
53
|
+
slug?: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
price?: number;
|
|
56
|
+
compareAtPrice?: number | null;
|
|
57
|
+
currency?: string;
|
|
58
|
+
status?: ProductStatus;
|
|
59
|
+
inventoryCount?: number;
|
|
60
|
+
sku?: string | null;
|
|
61
|
+
sortOrder?: number;
|
|
62
|
+
metadata?: Record<string, unknown>;
|
|
63
|
+
categoryId?: string | null;
|
|
64
|
+
isPhysical?: boolean;
|
|
65
|
+
weight?: number | null;
|
|
66
|
+
weightUnit?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface ListProductsOptions {
|
|
69
|
+
status?: ProductStatus;
|
|
70
|
+
categoryId?: string;
|
|
71
|
+
search?: string;
|
|
72
|
+
/** Exact-match slug lookup (storefront product-detail routing) */
|
|
73
|
+
slug?: string;
|
|
74
|
+
limit?: number;
|
|
75
|
+
offset?: number;
|
|
76
|
+
orderBy?: 'name' | 'price' | 'created_at' | 'sort_order';
|
|
77
|
+
order?: 'asc' | 'desc';
|
|
78
|
+
}
|
|
79
|
+
export interface Category {
|
|
80
|
+
id: string;
|
|
81
|
+
tenantId: string;
|
|
82
|
+
name: string;
|
|
83
|
+
slug: string;
|
|
84
|
+
description: string | null;
|
|
85
|
+
parentId: string | null;
|
|
86
|
+
sortOrder: number;
|
|
87
|
+
/** Showcase image (storefront category tiles). Not yet a shop_categories column. */
|
|
88
|
+
imageUrl?: string | null;
|
|
89
|
+
children?: Category[];
|
|
90
|
+
createdAt: string;
|
|
91
|
+
}
|
|
92
|
+
export interface CreateCategoryInput {
|
|
93
|
+
name: string;
|
|
94
|
+
slug?: string;
|
|
95
|
+
description?: string;
|
|
96
|
+
parentId?: string;
|
|
97
|
+
sortOrder?: number;
|
|
98
|
+
}
|
|
99
|
+
export interface UpdateCategoryInput {
|
|
100
|
+
name?: string;
|
|
101
|
+
slug?: string;
|
|
102
|
+
description?: string | null;
|
|
103
|
+
parentId?: string | null;
|
|
104
|
+
sortOrder?: number;
|
|
105
|
+
}
|
|
106
|
+
export type OrderStatus = 'open' | 'archived' | 'cancelled';
|
|
107
|
+
export type FinancialStatus = 'pending' | 'paid' | 'partially_paid' | 'refunded' | 'partially_refunded' | 'voided';
|
|
108
|
+
export type FulfillmentStatus = 'unfulfilled' | 'partially_fulfilled' | 'fulfilled';
|
|
109
|
+
export interface Order {
|
|
110
|
+
id: string;
|
|
111
|
+
tenantId: string;
|
|
112
|
+
orderNumber: number;
|
|
113
|
+
status: OrderStatus;
|
|
114
|
+
financialStatus: FinancialStatus;
|
|
115
|
+
fulfillmentStatus: FulfillmentStatus;
|
|
116
|
+
currency: string;
|
|
117
|
+
subtotal: number;
|
|
118
|
+
taxTotal: number;
|
|
119
|
+
discountTotal: number;
|
|
120
|
+
shippingTotal: number;
|
|
121
|
+
total: number;
|
|
122
|
+
customerId: string | null;
|
|
123
|
+
customerName: string | null;
|
|
124
|
+
customerEmail: string | null;
|
|
125
|
+
discountCode: string | null;
|
|
126
|
+
notes: string | null;
|
|
127
|
+
items: OrderItem[];
|
|
128
|
+
createdAt: string;
|
|
129
|
+
updatedAt: string;
|
|
130
|
+
}
|
|
131
|
+
export interface OrderItem {
|
|
132
|
+
id: string;
|
|
133
|
+
orderId: string;
|
|
134
|
+
productId: string | null;
|
|
135
|
+
name: string;
|
|
136
|
+
sku: string | null;
|
|
137
|
+
quantity: number;
|
|
138
|
+
unitPrice: number;
|
|
139
|
+
total: number;
|
|
140
|
+
imageUrl: string | null;
|
|
141
|
+
}
|
|
142
|
+
export interface CreateOrderInput {
|
|
143
|
+
customerId?: string;
|
|
144
|
+
customerName?: string;
|
|
145
|
+
customerEmail?: string;
|
|
146
|
+
currency?: string;
|
|
147
|
+
notes?: string;
|
|
148
|
+
discountCode?: string;
|
|
149
|
+
discountTotal?: number;
|
|
150
|
+
shippingTotal?: number;
|
|
151
|
+
items: Array<{
|
|
152
|
+
productId?: string;
|
|
153
|
+
name: string;
|
|
154
|
+
sku?: string;
|
|
155
|
+
quantity: number;
|
|
156
|
+
unitPrice: number;
|
|
157
|
+
imageUrl?: string;
|
|
158
|
+
}>;
|
|
159
|
+
}
|
|
160
|
+
export interface UpdateOrderInput {
|
|
161
|
+
status?: OrderStatus;
|
|
162
|
+
financialStatus?: FinancialStatus;
|
|
163
|
+
fulfillmentStatus?: FulfillmentStatus;
|
|
164
|
+
notes?: string;
|
|
165
|
+
}
|
|
166
|
+
export interface ListOrdersOptions {
|
|
167
|
+
status?: OrderStatus;
|
|
168
|
+
financialStatus?: FinancialStatus;
|
|
169
|
+
fulfillmentStatus?: FulfillmentStatus;
|
|
170
|
+
customerId?: string;
|
|
171
|
+
customerEmail?: string;
|
|
172
|
+
search?: string;
|
|
173
|
+
limit?: number;
|
|
174
|
+
offset?: number;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* A single line in a trusted order-placement request. Carries NO price — the
|
|
178
|
+
* server re-reads the product price from the catalog so the browser can never
|
|
179
|
+
* set what it pays.
|
|
180
|
+
*/
|
|
181
|
+
export interface PlaceOrderLine {
|
|
182
|
+
productId: string;
|
|
183
|
+
quantity: number;
|
|
184
|
+
/** Variant/options label appended to the stored item name (display only). */
|
|
185
|
+
optionsLabel?: string;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Input for {@link ShopProvider.placeOrder} — the only trusted path to create a
|
|
189
|
+
* storefront order. Prices, discount math, and inventory are resolved
|
|
190
|
+
* server-side; the client supplies product ids, quantities, and intent only.
|
|
191
|
+
*/
|
|
192
|
+
export interface PlaceOrderInput {
|
|
193
|
+
/** Tenant/store id. The Supabase provider falls back to the resolved shop tenant. */
|
|
194
|
+
tenantId?: string;
|
|
195
|
+
/** Existing customer id (preferred when the session already resolved one). */
|
|
196
|
+
customerId?: string;
|
|
197
|
+
/** Customer identity for find-or-create when {@link PlaceOrderInput.customerId} is absent. */
|
|
198
|
+
customer?: {
|
|
199
|
+
name?: string;
|
|
200
|
+
email?: string;
|
|
201
|
+
};
|
|
202
|
+
currency?: string;
|
|
203
|
+
notes?: string;
|
|
204
|
+
/** Discount code to validate + apply server-side (never trust a client total). */
|
|
205
|
+
discountCode?: string;
|
|
206
|
+
/** Shipping cost from the store's shipping policy. Clamped to >= 0 server-side. */
|
|
207
|
+
shippingTotal?: number;
|
|
208
|
+
items: PlaceOrderLine[];
|
|
209
|
+
}
|
|
210
|
+
export interface ShopCustomer {
|
|
211
|
+
id: string;
|
|
212
|
+
tenantId: string;
|
|
213
|
+
firstName: string;
|
|
214
|
+
lastName: string;
|
|
215
|
+
email: string | null;
|
|
216
|
+
phone: string | null;
|
|
217
|
+
notes: string | null;
|
|
218
|
+
ordersCount: number;
|
|
219
|
+
totalSpent: number;
|
|
220
|
+
createdAt: string;
|
|
221
|
+
updatedAt: string;
|
|
222
|
+
}
|
|
223
|
+
export interface CreateCustomerInput {
|
|
224
|
+
firstName: string;
|
|
225
|
+
lastName?: string;
|
|
226
|
+
email?: string;
|
|
227
|
+
phone?: string;
|
|
228
|
+
notes?: string;
|
|
229
|
+
}
|
|
230
|
+
export interface UpdateCustomerInput {
|
|
231
|
+
firstName?: string;
|
|
232
|
+
lastName?: string;
|
|
233
|
+
email?: string | null;
|
|
234
|
+
phone?: string | null;
|
|
235
|
+
notes?: string | null;
|
|
236
|
+
}
|
|
237
|
+
export interface ListCustomersOptions {
|
|
238
|
+
search?: string;
|
|
239
|
+
limit?: number;
|
|
240
|
+
offset?: number;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Input for {@link ShopProvider.resolveCustomer} — find-or-create a customer by
|
|
244
|
+
* email and (on Supabase) link it to the authenticated user (auth.uid) so
|
|
245
|
+
* "my orders" can be enforced by RLS. Runs server-side; the browser never
|
|
246
|
+
* supplies the auth id.
|
|
247
|
+
*/
|
|
248
|
+
export interface ResolveCustomerInput {
|
|
249
|
+
email: string;
|
|
250
|
+
name?: string;
|
|
251
|
+
/** Tenant/store id. The Supabase provider falls back to the resolved shop tenant. */
|
|
252
|
+
tenantId?: string;
|
|
253
|
+
}
|
|
254
|
+
export type DiscountType = 'percentage' | 'fixed_amount' | 'free_shipping' | 'buy_x_get_y';
|
|
255
|
+
export type DiscountMethod = 'code' | 'automatic';
|
|
256
|
+
export type DiscountStatus = 'active' | 'expired' | 'scheduled' | 'draft';
|
|
257
|
+
export interface Discount {
|
|
258
|
+
id: string;
|
|
259
|
+
tenantId: string;
|
|
260
|
+
title: string;
|
|
261
|
+
code: string | null;
|
|
262
|
+
type: DiscountType;
|
|
263
|
+
method: DiscountMethod;
|
|
264
|
+
value: number;
|
|
265
|
+
usageLimit: number | null;
|
|
266
|
+
oncePerCustomer: boolean;
|
|
267
|
+
startsAt: string;
|
|
268
|
+
endsAt: string | null;
|
|
269
|
+
status: DiscountStatus;
|
|
270
|
+
timesUsed: number;
|
|
271
|
+
createdAt: string;
|
|
272
|
+
updatedAt: string;
|
|
273
|
+
}
|
|
274
|
+
export interface CreateDiscountInput {
|
|
275
|
+
title: string;
|
|
276
|
+
code?: string;
|
|
277
|
+
type: DiscountType;
|
|
278
|
+
method?: DiscountMethod;
|
|
279
|
+
value: number;
|
|
280
|
+
usageLimit?: number;
|
|
281
|
+
oncePerCustomer?: boolean;
|
|
282
|
+
startsAt?: string;
|
|
283
|
+
endsAt?: string;
|
|
284
|
+
status?: DiscountStatus;
|
|
285
|
+
}
|
|
286
|
+
export interface UpdateDiscountInput {
|
|
287
|
+
title?: string;
|
|
288
|
+
code?: string | null;
|
|
289
|
+
type?: DiscountType;
|
|
290
|
+
method?: DiscountMethod;
|
|
291
|
+
value?: number;
|
|
292
|
+
usageLimit?: number | null;
|
|
293
|
+
oncePerCustomer?: boolean;
|
|
294
|
+
startsAt?: string;
|
|
295
|
+
endsAt?: string | null;
|
|
296
|
+
status?: DiscountStatus;
|
|
297
|
+
}
|
|
298
|
+
export interface ListDiscountsOptions {
|
|
299
|
+
status?: DiscountStatus;
|
|
300
|
+
type?: DiscountType;
|
|
301
|
+
search?: string;
|
|
302
|
+
limit?: number;
|
|
303
|
+
offset?: number;
|
|
304
|
+
}
|
|
305
|
+
export interface DiscountValidation {
|
|
306
|
+
valid: boolean;
|
|
307
|
+
code: string | null;
|
|
308
|
+
type: DiscountType | null;
|
|
309
|
+
/** Percentage points for 'percentage'; absolute amount for 'fixed_amount'. */
|
|
310
|
+
value: number;
|
|
311
|
+
/** Machine-readable reason when invalid: not_found | expired | not_started | usage_limit | unsupported | empty */
|
|
312
|
+
reason?: string;
|
|
313
|
+
}
|
|
314
|
+
export interface InventoryLine {
|
|
315
|
+
productId: string;
|
|
316
|
+
quantity: number;
|
|
317
|
+
}
|
|
318
|
+
export interface InventoryIssue {
|
|
319
|
+
productId: string;
|
|
320
|
+
name: string;
|
|
321
|
+
requested: number;
|
|
322
|
+
available: number;
|
|
323
|
+
}
|
|
324
|
+
export interface InventoryCheck {
|
|
325
|
+
ok: boolean;
|
|
326
|
+
issues: InventoryIssue[];
|
|
327
|
+
}
|
|
328
|
+
export interface CartTotalLine {
|
|
329
|
+
productId: string;
|
|
330
|
+
name: string;
|
|
331
|
+
sku: string | null;
|
|
332
|
+
unitPrice: number;
|
|
333
|
+
quantity: number;
|
|
334
|
+
lineTotal: number;
|
|
335
|
+
}
|
|
336
|
+
export interface CartTotals {
|
|
337
|
+
lines: CartTotalLine[];
|
|
338
|
+
subtotal: number;
|
|
339
|
+
discountTotal: number;
|
|
340
|
+
discountCode: string | null;
|
|
341
|
+
taxTotal: number;
|
|
342
|
+
shippingTotal: number;
|
|
343
|
+
total: number;
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"types.js"}
|
package/package.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fayz-ai/shop",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Fayz SDK ecommerce layer — products, orders, customers, discounts, shipping",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"source": "./src/index.ts",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./client": {
|
|
17
|
+
"source": "./src/index.ts",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
},
|
|
22
|
+
"./admin": {
|
|
23
|
+
"source": "./src/index.ts",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"require": "./dist/index.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./runtime": {
|
|
29
|
+
"source": "./src/runtime.ts",
|
|
30
|
+
"types": "./dist/runtime.d.ts",
|
|
31
|
+
"import": "./dist/runtime.js",
|
|
32
|
+
"require": "./dist/runtime.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./provider": {
|
|
35
|
+
"source": "./src/provider.ts",
|
|
36
|
+
"types": "./dist/provider.d.ts",
|
|
37
|
+
"import": "./dist/provider.js",
|
|
38
|
+
"require": "./dist/provider.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./types": {
|
|
41
|
+
"source": "./src/types.ts",
|
|
42
|
+
"types": "./dist/types.d.ts",
|
|
43
|
+
"import": "./dist/types.js",
|
|
44
|
+
"require": "./dist/types.cjs"
|
|
45
|
+
},
|
|
46
|
+
"./catalog": {
|
|
47
|
+
"source": "./src/catalog.ts",
|
|
48
|
+
"types": "./dist/catalog.d.ts",
|
|
49
|
+
"import": "./dist/catalog.js",
|
|
50
|
+
"require": "./dist/catalog.cjs"
|
|
51
|
+
},
|
|
52
|
+
"./mock": {
|
|
53
|
+
"source": "./src/mock.ts",
|
|
54
|
+
"types": "./dist/mock.d.ts",
|
|
55
|
+
"import": "./dist/mock.js",
|
|
56
|
+
"require": "./dist/mock.cjs"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"dist",
|
|
61
|
+
"src"
|
|
62
|
+
],
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"lucide-react": "^0.400.0",
|
|
65
|
+
"@supabase/supabase-js": "^2.45.0",
|
|
66
|
+
"zustand": "^4.5.0",
|
|
67
|
+
"@fayz-ai/ui": "^0.2.0",
|
|
68
|
+
"@fayz-ai/auth": "^0.2.0",
|
|
69
|
+
"@fayz-ai/core": "^0.2.0"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
73
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@types/react": "^18.3.0",
|
|
77
|
+
"@types/react-dom": "^18.3.0",
|
|
78
|
+
"react": "^18.3.0",
|
|
79
|
+
"react-dom": "^18.3.0",
|
|
80
|
+
"tsup": "^8.2.0",
|
|
81
|
+
"typescript": "^5.5.0"
|
|
82
|
+
},
|
|
83
|
+
"keywords": [
|
|
84
|
+
"fayz",
|
|
85
|
+
"ecommerce",
|
|
86
|
+
"shop",
|
|
87
|
+
"supabase"
|
|
88
|
+
],
|
|
89
|
+
"license": "MIT",
|
|
90
|
+
"publishConfig": {
|
|
91
|
+
"access": "public"
|
|
92
|
+
},
|
|
93
|
+
"scripts": {
|
|
94
|
+
"build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap --noEmit false",
|
|
95
|
+
"dev": "tsup --watch",
|
|
96
|
+
"typecheck": "tsc --noEmit",
|
|
97
|
+
"clean": "rm -rf dist"
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/catalog.ts
ADDED
package/src/commerce.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { getShopProvider } from './runtime'
|
|
2
|
+
import type { ShopProvider } from './provider'
|
|
3
|
+
import type {
|
|
4
|
+
CartTotals, DiscountValidation, InventoryCheck, InventoryIssue, InventoryLine, Order,
|
|
5
|
+
} from './types'
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Commerce primitive APIs (FAY-1193)
|
|
9
|
+
//
|
|
10
|
+
// Provider-agnostic helpers that custom app workflows call instead of re-deriving
|
|
11
|
+
// discount/inventory/total logic. They take an explicit ShopProvider (default:
|
|
12
|
+
// the configured runtime provider) so they work the same against mock or Supabase.
|
|
13
|
+
//
|
|
14
|
+
// NOTE: when run in the browser these compute the *advisory* answer (UI preview).
|
|
15
|
+
// The authoritative values are recomputed server-side in the shop_place_order RPC
|
|
16
|
+
// — the browser can never set what it pays.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
function round2(n: number): number {
|
|
20
|
+
return Math.round(n * 100) / 100
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Validate a discount code against the provider: existence, status, date window, usage limit, supported type. */
|
|
24
|
+
export async function validateDiscount(
|
|
25
|
+
input: { code: string; subtotal?: number; customerId?: string },
|
|
26
|
+
provider: ShopProvider = getShopProvider(),
|
|
27
|
+
): Promise<DiscountValidation> {
|
|
28
|
+
const code = input.code.trim()
|
|
29
|
+
if (!code) return { valid: false, code: null, type: null, value: 0, reason: 'empty' }
|
|
30
|
+
|
|
31
|
+
const list = await provider.listDiscounts({ status: 'active' })
|
|
32
|
+
const discount = list.find((d) => (d.code ?? '').toUpperCase() === code.toUpperCase())
|
|
33
|
+
if (!discount) return { valid: false, code: null, type: null, value: 0, reason: 'not_found' }
|
|
34
|
+
|
|
35
|
+
const now = new Date().toISOString()
|
|
36
|
+
if (discount.startsAt > now) return { valid: false, code: discount.code, type: discount.type, value: 0, reason: 'not_started' }
|
|
37
|
+
if (discount.endsAt && discount.endsAt < now) return { valid: false, code: discount.code, type: discount.type, value: 0, reason: 'expired' }
|
|
38
|
+
if (discount.usageLimit != null && discount.timesUsed >= discount.usageLimit) {
|
|
39
|
+
return { valid: false, code: discount.code, type: discount.type, value: 0, reason: 'usage_limit' }
|
|
40
|
+
}
|
|
41
|
+
if (discount.type !== 'percentage' && discount.type !== 'fixed_amount') {
|
|
42
|
+
return { valid: false, code: discount.code, type: discount.type, value: 0, reason: 'unsupported' }
|
|
43
|
+
}
|
|
44
|
+
return { valid: true, code: discount.code, type: discount.type, value: discount.value }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Apply a validated discount to a subtotal, clamped to [0, subtotal]. Pure. */
|
|
48
|
+
export function applyDiscount(validation: DiscountValidation, subtotal: number): { discountTotal: number; code: string | null } {
|
|
49
|
+
if (!validation.valid) return { discountTotal: 0, code: null }
|
|
50
|
+
if (validation.type === 'percentage') {
|
|
51
|
+
return { discountTotal: round2(Math.min(subtotal, (subtotal * validation.value) / 100)), code: validation.code }
|
|
52
|
+
}
|
|
53
|
+
if (validation.type === 'fixed_amount') {
|
|
54
|
+
return { discountTotal: round2(Math.min(subtotal, validation.value)), code: validation.code }
|
|
55
|
+
}
|
|
56
|
+
return { discountTotal: 0, code: validation.code }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Report out-of-stock / unavailable lines against current catalog inventory. No mutation. */
|
|
60
|
+
export async function checkInventory(
|
|
61
|
+
lines: InventoryLine[],
|
|
62
|
+
provider: ShopProvider = getShopProvider(),
|
|
63
|
+
): Promise<InventoryCheck> {
|
|
64
|
+
const issues: InventoryIssue[] = []
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
const product = await provider.getProduct(line.productId)
|
|
67
|
+
if (!product || product.status !== 'active') {
|
|
68
|
+
issues.push({ productId: line.productId, name: product?.name ?? line.productId, requested: line.quantity, available: 0 })
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
if (product.inventoryCount < line.quantity) {
|
|
72
|
+
issues.push({ productId: line.productId, name: product.name, requested: line.quantity, available: product.inventoryCount })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { ok: issues.length === 0, issues }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Recompute cart totals from authoritative catalog prices (ignores any client
|
|
80
|
+
* unitPrice), applying a validated discount and shipping. Tax is 0 in v1 (hook).
|
|
81
|
+
*/
|
|
82
|
+
export async function computeCartTotals(
|
|
83
|
+
input: { lines: InventoryLine[]; discountCode?: string; customerId?: string; shipping?: number },
|
|
84
|
+
provider: ShopProvider = getShopProvider(),
|
|
85
|
+
): Promise<CartTotals> {
|
|
86
|
+
const lines: CartTotals['lines'] = []
|
|
87
|
+
let subtotal = 0
|
|
88
|
+
for (const line of input.lines) {
|
|
89
|
+
const product = await provider.getProduct(line.productId)
|
|
90
|
+
if (!product) continue
|
|
91
|
+
const lineTotal = round2(product.price * line.quantity)
|
|
92
|
+
subtotal += lineTotal
|
|
93
|
+
lines.push({ productId: product.id, name: product.name, sku: product.sku, unitPrice: product.price, quantity: line.quantity, lineTotal })
|
|
94
|
+
}
|
|
95
|
+
subtotal = round2(subtotal)
|
|
96
|
+
|
|
97
|
+
let discountTotal = 0
|
|
98
|
+
let discountCode: string | null = null
|
|
99
|
+
if (input.discountCode) {
|
|
100
|
+
const applied = applyDiscount(
|
|
101
|
+
await validateDiscount({ code: input.discountCode, subtotal, customerId: input.customerId }, provider),
|
|
102
|
+
subtotal,
|
|
103
|
+
)
|
|
104
|
+
discountTotal = applied.discountTotal
|
|
105
|
+
discountCode = applied.code
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const shippingTotal = round2(Math.max(input.shipping ?? 0, 0))
|
|
109
|
+
const taxTotal = 0
|
|
110
|
+
const total = round2(Math.max(subtotal - discountTotal + shippingTotal + taxTotal, 0))
|
|
111
|
+
return { lines, subtotal, discountTotal, discountCode, taxTotal, shippingTotal, total }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A customer's orders (scoped by id; RLS-enforced to the owner on Supabase). */
|
|
115
|
+
export async function getCustomerOrders(
|
|
116
|
+
customerId: string,
|
|
117
|
+
provider: ShopProvider = getShopProvider(),
|
|
118
|
+
): Promise<Order[]> {
|
|
119
|
+
return provider.listOrders({ customerId, limit: 100 })
|
|
120
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
Product, ProductImage, Category,
|
|
3
|
+
Order, OrderItem, OrderStatus, FinancialStatus, FulfillmentStatus,
|
|
4
|
+
ShopCustomer, Discount, DiscountType, DiscountMethod, DiscountStatus,
|
|
5
|
+
ProductStatus,
|
|
6
|
+
CreateProductInput, UpdateProductInput, ListProductsOptions,
|
|
7
|
+
CreateCategoryInput, UpdateCategoryInput,
|
|
8
|
+
CreateOrderInput, UpdateOrderInput, ListOrdersOptions,
|
|
9
|
+
CreateCustomerInput, UpdateCustomerInput, ListCustomersOptions, ResolveCustomerInput,
|
|
10
|
+
CreateDiscountInput, UpdateDiscountInput, ListDiscountsOptions,
|
|
11
|
+
PlaceOrderInput, PlaceOrderLine,
|
|
12
|
+
DiscountValidation, InventoryLine, InventoryIssue, InventoryCheck, CartTotalLine, CartTotals,
|
|
13
|
+
} from './types'
|
|
14
|
+
|
|
15
|
+
export type { ShopProvider } from './provider'
|
|
16
|
+
export {
|
|
17
|
+
validateDiscount, applyDiscount, checkInventory, computeCartTotals, getCustomerOrders,
|
|
18
|
+
} from './commerce'
|
|
19
|
+
export { SupabaseShopProvider, createSupabaseShopProvider } from './supabase-provider'
|
|
20
|
+
export { MockShopProvider, createMockShopProvider } from './mock-provider'
|
|
21
|
+
export type { MockShopSeed } from './mock-provider'
|
|
22
|
+
export { setShopTenantResolver, getShopTenantId } from './tenant'
|
|
23
|
+
export type { TenantResolver } from './tenant'
|
|
24
|
+
export { getShopProvider, setShopProvider, resetShopProvider } from './runtime'
|
|
25
|
+
|
|
26
|
+
// Deterministic mock catalog (also useful for seeding real databases and tests)
|
|
27
|
+
export {
|
|
28
|
+
MOCK_PRODUCTS,
|
|
29
|
+
MOCK_CATEGORIES,
|
|
30
|
+
MOCK_DISCOUNTS,
|
|
31
|
+
svgPlaceholder,
|
|
32
|
+
buildMockCatalog,
|
|
33
|
+
} from './catalog'
|
|
34
|
+
export type { CatalogInput, CatalogProductInput, CatalogCategoryInput } from './catalog'
|