commerce-kit 0.6.1-experimental.8 → 0.7.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,234 @@
1
+ # commerce-kit
2
+
3
+ TypeScript SDK for building e-commerce apps. Works with Stripe and YNS APIs through a clean, unified interface. Built for Next.js, but plays nice with whatever you're using.
4
+
5
+ Built by [Your Next Store](https://yournextstore.com).
6
+
7
+ ## Features
8
+
9
+ - **Multi-provider**: Switch between Stripe and YNS without changing code
10
+ - **Class-based**: Industry standard pattern like Stripe, OpenAI, AWS SDK
11
+ - **Zero config**: Works out of the box with environment variables
12
+ - **Type-safe**: Full TypeScript with provider-specific types
13
+ - **GraphQL support**: Field selection for YNS, REST for Stripe
14
+ - **Multi-instance**: Perfect for testing and multi-tenant apps
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install commerce-kit
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ **Zero config** - works automatically with environment variables:
25
+
26
+ ```tsx
27
+ // Set environment variables:
28
+ // STRIPE_SECRET_KEY=sk_test_...
29
+ // or YNS_ENDPOINT=https://api.yournextstore.com and YNS_TOKEN=token_...
30
+
31
+ import commerce from "commerce-kit";
32
+ import { formatMoney } from "commerce-kit/currencies";
33
+
34
+ export async function ProductList() {
35
+ const products = await commerce.product.browse({ first: 6 });
36
+
37
+ return (
38
+ <div>
39
+ {products.data.map((product) => (
40
+ <div key={product.id}>
41
+ <h2>{product.name}</h2>
42
+ <p>{formatMoney({ amount: product.price, currency: product.currency })}</p>
43
+ </div>
44
+ ))}
45
+ </div>
46
+ );
47
+ }
48
+ ```
49
+
50
+ **Explicit configuration**:
51
+
52
+ ```tsx
53
+ import { Commerce } from "commerce-kit";
54
+
55
+ // Create your own instance with explicit config
56
+ const commerce = new Commerce({
57
+ provider: "stripe",
58
+ stripe: { secretKey: "sk_test_..." }
59
+ });
60
+
61
+ const products = await commerce.product.browse({ first: 6 });
62
+ ```
63
+
64
+ ## Usage Patterns
65
+
66
+ ### 1. Default Instance (Recommended)
67
+
68
+ ```tsx
69
+ import commerce from "commerce-kit";
70
+
71
+ // Auto-detects from STRIPE_SECRET_KEY or YNS_ENDPOINT/YNS_TOKEN
72
+ const products = await commerce.product.browse({ first: 10 });
73
+ const cart = await commerce.cart.add({ variantId: "var_123", quantity: 1 });
74
+ ```
75
+
76
+ ### 2. Commerce Class
77
+
78
+ ```tsx
79
+ import { Commerce } from "commerce-kit";
80
+
81
+ // Zero-config constructor
82
+ const commerce = new Commerce();
83
+
84
+ // Or explicit configuration
85
+ const commerce = new Commerce({
86
+ provider: "stripe",
87
+ stripe: { secretKey: "sk_test_..." }
88
+ });
89
+
90
+ const products = await commerce.product.browse({ first: 10 });
91
+ ```
92
+
93
+ ### 3. Provider-Specific Classes
94
+
95
+ ```tsx
96
+ import { StripeCommerce, YNSCommerce } from "commerce-kit";
97
+
98
+ // Stripe-specific client
99
+ const stripe = new StripeCommerce({
100
+ secretKey: "sk_test_..."
101
+ });
102
+
103
+ // YNS-specific client with GraphQL support
104
+ const yns = new YNSCommerce({
105
+ endpoint: "https://api.yournextstore.com",
106
+ token: "token_..."
107
+ });
108
+
109
+ const stripeProducts = await stripe.product.browse({ first: 10 });
110
+ const ynsProducts = await yns.product.browse({
111
+ first: 10,
112
+ fields: ["id", "name", "price"] // GraphQL field selection
113
+ });
114
+ ```
115
+
116
+ ## API Reference
117
+
118
+ ### Products
119
+
120
+ ```tsx
121
+ import { Commerce } from "commerce-kit";
122
+ const commerce = new Commerce();
123
+
124
+ // Browse with filters
125
+ const products = await commerce.product.browse({
126
+ first: 10,
127
+ category: "electronics",
128
+ fields: ["id", "name", "price"] // GraphQL field selection (YNS only)
129
+ });
130
+
131
+ // Get single product
132
+ const product = await commerce.product.get({ slug: "awesome-laptop" });
133
+
134
+ // Search (YNS only)
135
+ const results = await commerce.product.search({ query: "macbook" });
136
+ ```
137
+
138
+ ### Cart
139
+
140
+ ```tsx
141
+ import { Commerce } from "commerce-kit";
142
+ const commerce = new Commerce();
143
+
144
+ // Add to cart
145
+ const result = await commerce.cart.add({
146
+ variantId: "variant_123",
147
+ quantity: 2
148
+ });
149
+
150
+ // Update quantity
151
+ await commerce.cart.update({
152
+ cartId: result.cartId,
153
+ variantId: "variant_123",
154
+ quantity: 3
155
+ });
156
+
157
+ // Get cart
158
+ const cartData = await commerce.cart.get({ cartId: result.cartId });
159
+ ```
160
+
161
+ ### Orders (YNS only)
162
+
163
+ ```tsx
164
+ import { YNSCommerce } from "commerce-kit";
165
+
166
+ const yns = new YNSCommerce({
167
+ endpoint: process.env.YNS_ENDPOINT,
168
+ token: process.env.YNS_TOKEN
169
+ });
170
+
171
+ // List orders
172
+ const orders = await yns.order.list({ first: 10 });
173
+
174
+ // Get single order
175
+ const order = await yns.order.get({ id: "order_123" });
176
+ ```
177
+
178
+ ## Environment Variables
179
+
180
+ Set these environment variables for automatic configuration:
181
+
182
+ ```bash
183
+ # For Stripe
184
+ STRIPE_SECRET_KEY=sk_test_...
185
+ STRIPE_TAG_PREFIX=my-store # optional
186
+
187
+ # For YNS
188
+ YNS_ENDPOINT=https://api.yournextstore.com
189
+ YNS_TOKEN=token_...
190
+
191
+ # Optional: explicitly choose provider
192
+ COMMERCE_PROVIDER=stripe # or "yns"
193
+ ```
194
+
195
+ ## Multi-Tenant & Testing
196
+
197
+ Perfect for multi-tenant applications and testing:
198
+
199
+ ```tsx
200
+ // Multi-tenant
201
+ class TenantService {
202
+ getCommerce(tenantId: string) {
203
+ const config = this.getTenantConfig(tenantId);
204
+ return new Commerce(config);
205
+ }
206
+ }
207
+
208
+ // Testing
209
+ describe("Product Service", () => {
210
+ it("should fetch products", async () => {
211
+ const commerce = new Commerce({
212
+ provider: "stripe",
213
+ stripe: { secretKey: "sk_test_mock" }
214
+ });
215
+
216
+ const products = await commerce.product.browse();
217
+ expect(products).toBeDefined();
218
+ });
219
+ });
220
+ ```
221
+
222
+ ## Why Class-Based?
223
+
224
+ Following industry standards from Stripe, OpenAI, and AWS SDK:
225
+
226
+ - ✅ **Explicit initialization** - Clear where configuration happens
227
+ - ✅ **Multiple instances** - Multi-tenant and testing support
228
+ - ✅ **Type safety** - Better TypeScript integration
229
+ - ✅ **No global state** - Each instance is isolated
230
+ - ✅ **Familiar pattern** - Same as `new Stripe()`, `new OpenAI()`
231
+
232
+ ## License
233
+
234
+ AGPL-3.0 – see LICENSE.md
@@ -0,0 +1,11 @@
1
+ type Money = {
2
+ amount: number;
3
+ currency: string;
4
+ };
5
+ declare const getStripeAmountFromDecimal: ({ amount: major, currency }: Money) => number;
6
+ declare const getDecimalFromStripeAmount: ({ amount: minor, currency }: Money) => number;
7
+ declare const formatMoney: ({ amount: minor, currency, locale }: Money & {
8
+ locale?: string;
9
+ }) => string;
10
+
11
+ export { formatMoney, getDecimalFromStripeAmount, getStripeAmountFromDecimal };
@@ -0,0 +1 @@
1
+ function o(e,n){if(!e)throw new Error(n)}var s=e=>{o(Number.isInteger(e),"Value must be an integer")};var u=e=>(o(e.length===3,"currency needs to be a 3-letter code"),a[e.toUpperCase()]??2),m=({amount:e,currency:n})=>{let t=10**u(n);return Number.parseInt((e*t).toFixed(0),10)},i=({amount:e,currency:n})=>{s(e);let r=u(n),t=10**r;return Number.parseFloat((e/t).toFixed(r))},p=({amount:e,currency:n,locale:r="en-US"})=>{let t=i({amount:e,currency:n});return new Intl.NumberFormat(r,{style:"currency",currency:n}).format(t)},a={BIF:0,CLP:0,DJF:0,GNF:0,JPY:0,KMF:0,KRW:0,MGA:0,PYG:0,RWF:0,UGX:0,VND:0,VUV:0,XAF:0,XOF:0,XPF:0,BHD:3,JOD:3,KWD:3,OMR:3,TND:3};export{p as formatMoney,i as getDecimalFromStripeAmount,m as getStripeAmountFromDecimal};
package/dist/index.d.ts CHANGED
@@ -1,26 +1,6 @@
1
- import { APIProductsBrowseQueryParams, APIProductsBrowseResult, APIProductGetByIdParams, APIProductGetByIdResult, APIOrdersBrowseQueryParams, APIOrdersBrowseResult, APIOrderGetByIdParams, APIOrderGetByIdResult, APICartCreateBody, APICartCreateResult, APICartGetResult } from './api-types.js';
2
- export { APICartAddBody, APICartAddResult, APICartRemoveItemQueryParams, APICartRemoveItemResult, JSONContent } from './api-types.js';
3
-
4
- interface YnsProviderConfig {
5
- version: "v1";
6
- endpoint: string;
7
- token: string;
8
- }
9
- declare class YnsProvider {
10
- #private;
11
- constructor(config: YnsProviderConfig);
12
- productBrowse(params: APIProductsBrowseQueryParams): Promise<APIProductsBrowseResult>;
13
- productGet(params: APIProductGetByIdParams): Promise<APIProductGetByIdResult | null>;
14
- orderBrowse(params: APIOrdersBrowseQueryParams): Promise<APIOrdersBrowseResult>;
15
- orderGet(params: APIOrderGetByIdParams): Promise<APIOrderGetByIdResult | null>;
16
- cartUpsert(body: APICartCreateBody): Promise<APICartCreateResult>;
17
- cartRemoveItem(params: {
18
- cartId: string;
19
- variantId: string;
20
- }): Promise<null>;
21
- cartGet(params: {
22
- cartId: string;
23
- }): Promise<APICartGetResult | null>;
24
- }
25
-
26
- export { APICartCreateBody, APICartCreateResult, APICartGetResult, APIOrderGetByIdParams, APIOrderGetByIdResult, APIOrdersBrowseQueryParams, APIOrdersBrowseResult, APIProductGetByIdParams, APIProductGetByIdResult, APIProductsBrowseQueryParams, APIProductsBrowseResult, YnsProvider };
1
+ export { formatMoney } from './currencies.js';
2
+ export { MappedCart, MappedProduct, MappedShippingRate } from './internal.js';
3
+ export { B as BaseProvider, C as Cart, a as CartAddParams, b as CartClearParams, c as CartGetParams, d as CartItem, e as CartRemoveParams, f as CartUpdateParams, g as Customer, O as Order, h as OrderGetParams, i as OrderListParams, j as OrderListResult, P as Product, k as ProductBrowseParams, l as ProductBrowseResult, m as ProductGetParams, n as ProductInfo, o as ProductSearchParams, p as ProductSearchResult, S as StripeProviderConfig, Y as YnsProduct, q as YnsProviderConfig } from './provider-CeP9uHnB.js';
4
+ export { Commerce } from './yns.js';
5
+ import 'stripe';
6
+ import 'zod';
package/dist/index.js CHANGED
@@ -1,2 +1 @@
1
- import"./chunk-PNKB6P4V.js";var n={DEBUG:0,LOG:1,WARN:2,ERROR:3},p=process.env.NEXT_PUBLIC_LOG_LEVEL||"LOG",i=n[p],d="\x1B[0m",R="\x1B[34m",y="\x1B[32m",B="\x1B[33m",w="\x1B[31m",E="\u23F1\uFE0F",h="\u{1F41B}",A="\u2714\uFE0F",L="\u26A0\uFE0F",$="\u274C",f=`${E} `,G=`${R}${h}${d} `,O=`${y}${A}${d} `,v=`${B}${L}${d} `,C=`${w}${$}${d} `,l=a=>{let e=a?`[${a}] `:"";return{getLogger(t){return l([a,t].filter(Boolean).join(" > "))},time(t){i>n.DEBUG||console.time([f,e,t].filter(Boolean).join(" "))},timeEnd(t){i>n.DEBUG||console.timeEnd([f,e,t].filter(Boolean).join(" "))},debug(...t){i>n.DEBUG||console.log(...[G,e,...t].filter(Boolean))},log(...t){i>n.LOG||console.log(...[O,e,...t].filter(Boolean))},dir(t,r){i>n.LOG||console.dir(t,r)},warn(...t){i>n.WARN||console.warn(...[v,e,...t].filter(Boolean))},error(...t){i>n.ERROR||console.error(...[C,e,...t].filter(Boolean))}}},T=l();var I=class{#t;#r=l("YnsProvider");constructor(e){this.#t=e,this.#r.debug("YnsProvider initialized")}async#e(e,t="GET",r){let c=this.#r.getLogger("#restRequest"),s=`${this.#t.endpoint}/api/${this.#t.version}${e}`;c.debug(`Making ${t} request to YNS API: ${s}`);let o=await fetch(s,{method:t,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.#t.token}`},body:r?JSON.stringify(r):void 0});if(!o.ok){let m=o.headers.get("content-type"),u=`YNS REST request failed: ${o.status} ${o.statusText}`;if(m?.includes("application/json"))try{let g=await o.json();u=g.error||g.message||u}catch{}throw c.error(`YNS API request failed: ${o.status} ${o.statusText}`,u),new Error(u)}let P=o.headers.get("content-type");if(!P?.includes("application/json"))throw new Error(`YNS API returned ${P} instead of JSON for ${e}`);return o.json()}async productBrowse(e){let t=this.#r.getLogger("productBrowse");t.debug("Browsing products with params:",e);let r=new URLSearchParams;e.limit&&r.append("limit",e.limit.toString()),e.offset&&r.append("offset",e.offset.toString()),e.category&&r.append("category",e.category),e.query&&r.append("query",e.query),e.active!==void 0&&r.append("active",e.active.toString()),e.orderBy&&r.append("orderBy",e.orderBy),e.orderDirection&&r.append("orderDirection",e.orderDirection);let s=`/products${r.size?`?${r}`:""}`;t.debug("Constructed pathname:",s);let o=await this.#e(s);return t.debug("Received product browse result:",{meta:o.meta}),o}async productGet(e){let t=`/products/${e.idOrSlug}`,r=await this.#e(t);return r||null}async orderBrowse(e){let t=this.#r.getLogger("orderBrowse");t.debug("Browsing orders with params:",e);let r=new URLSearchParams;e.limit&&r.append("limit",e.limit.toString()),e.offset&&r.append("offset",e.offset.toString());let s=`/orders${r.size?`?${r}`:""}`;t.debug("Constructed pathname:",s);let o=await this.#e(s);return t.debug("Received orders browse result:",{meta:o.meta}),o}async orderGet(e){let t=`/orders/${e.id}`,r=await this.#e(t);return r||null}async cartUpsert(e){return await this.#e("/carts","POST",e)}async cartRemoveItem(e){let t=`/carts/${e.cartId}/line-items/${e.variantId}`,r=await this.#e(t,"DELETE");return null}async cartGet(e){let t=`/carts/${e.cartId}`;return await this.#e(t)}};export{I as YnsProvider};
2
- //# sourceMappingURL=index.js.map
1
+ var v=Object.defineProperty;var f=(o,r)=>()=>(o&&(r=o(o=0)),r);var g=(o,r)=>{for(var t in r)v(o,t,{get:r[t],enumerable:!0})};var P={};g(P,{YnsProvider:()=>d,createYnsProvider:()=>I});function I(o){return new d(o)}var d,l=f(()=>{"use strict";d=class{config;constructor(r){this.config=r}async graphqlRequest(r,t){let e=await fetch(`${this.config.endpoint}/api/graphql`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.token}`},body:JSON.stringify({query:r,variables:t})});if(!e.ok)throw new Error(`YNS GraphQL request failed: ${e.status} ${e.statusText}`);let i=await e.json();if(i.errors)throw new Error(`YNS GraphQL errors: ${JSON.stringify(i.errors)}`);return i.data}async restRequest(r,t="GET",e){let i=await fetch(`${this.config.endpoint}/api/v1${r}`,{method:t,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.token}`},body:e?JSON.stringify(e):void 0});if(!i.ok){let s=i.headers.get("content-type"),n=`YNS REST request failed: ${i.status} ${i.statusText}`;if(s?.includes("application/json"))try{let p=await i.json();n=p.error||p.message||n}catch{}throw new Error(n)}let a=i.headers.get("content-type");if(!a?.includes("application/json"))throw new Error(`YNS API returned ${a} instead of JSON for ${r}`);return i.json()}mapYnsProduct(r){return{id:r.id,name:r.name,slug:r.slug,summary:r.summary,images:r.images||[],active:r.active,price:r.variants?.[0]?.price?Number.parseFloat(r.variants[0].price):0,currency:"USD",stock:r.variants?.[0]?.stock,category:r.category,variants:r.variants?.map(t=>({id:t.id,price:Number.parseFloat(t.price),stock:t.stock,attributes:t.attributes}))||[]}}mapCart(r){let t=this.mapCartItems(r.lineItems);return{id:r.id,customerId:r.customerId,storeId:r.storeId,customer:this.mapCustomer(r.customer),items:t,total:this.calculateTotal(t),currency:"USD",createdAt:r.createdAt,updatedAt:r.updatedAt}}mapCartItems(r){return r?.map(t=>this.mapCartItem(t))||[]}mapCartItem(r){let t=r.productVariant;return{id:r.id,productId:t?.product?.id||t?.id||"",variantId:t?.id,quantity:r.quantity,price:Number.parseFloat(t?.price||"0"),stock:t?.stock,product:this.mapProduct(t?.product)}}mapCustomer(r){if(r)return{id:r.id,email:r.email}}mapProduct(r){if(r)return{id:r.id,name:r.name,images:r.images||[]}}calculateTotal(r){return r.reduce((t,e)=>t+e.price*e.quantity,0)}async productBrowse(r){if(r.graphql){let t={offset:r.offset||0,limit:r.first||10,category:r.category,query:r.query,active:r.active,orderBy:r.orderBy,orderDirection:r.orderDirection},e=await this.graphqlRequest(r.graphql,t);return{data:e.products.data.map(i=>this.mapYnsProduct(i)),meta:e.products.meta}}else{let t=new URLSearchParams;r.first&&t.append("limit",r.first.toString()),r.offset&&t.append("offset",r.offset.toString()),r.category&&t.append("category",r.category),r.query&&t.append("q",r.query),r.active!==void 0&&t.append("active",r.active.toString()),r.orderBy&&t.append("orderBy",r.orderBy),r.orderDirection&&t.append("orderDirection",r.orderDirection);let e=`/products${t.toString()?`?${t.toString()}`:""}`,i=await this.restRequest(e);return{data:i.data.map(a=>this.mapYnsProduct(a)),meta:i.meta}}}async productGet(r){if(!r.slug&&!r.id)throw new Error("Either slug or id is required for productGet");if(r.graphql){let t={slug:r.slug,id:r.id},e=await this.graphqlRequest(r.graphql,t);return e.product?this.mapYnsProduct(e.product):null}else{let t=`/products/${r.id||r.slug}`,e=await this.restRequest(t);return e?this.mapYnsProduct(e):null}}async cartAdd(r){let t=r.quantity;if(r.cartId)try{let a=await this.cartGet({cartId:r.cartId});if(a){let s=a.items.find(n=>n.variantId===r.variantId);s&&(t=s.quantity+r.quantity)}}catch{}let e={variantId:r.variantId,cartId:r.cartId,quantity:t,subscriptionId:r.subscriptionId},i=await this.restRequest("/cart","POST",e);return this.mapCart(i)}async cartUpdate(r){let t={variantId:r.variantId,cartId:r.cartId,quantity:r.quantity},e=await this.restRequest("/cart","POST",t);return this.mapCart(e)}async cartRemove(r){let t=`/cart/${r.cartId}/items/${r.variantId}`,e=await this.restRequest(t,"DELETE");return this.mapCart(e)}async cartClear(r){let t=await this.restRequest(`/cart/${r.cartId}`,"DELETE");return this.mapCart(t)}async cartGet(r){let t=await this.restRequest(`/cart/${r.cartId}`);return t?this.mapCart(t):null}}});function c(o,r){if(!o)throw new Error(r)}var m=o=>{c(Number.isInteger(o),"Value must be an integer")};var y=o=>(c(o.length===3,"currency needs to be a 3-letter code"),w[o.toUpperCase()]??2);var h=({amount:o,currency:r})=>{m(o);let t=y(r),e=10**t;return Number.parseFloat((o/e).toFixed(t))},C=({amount:o,currency:r,locale:t="en-US"})=>{let e=h({amount:o,currency:r});return new Intl.NumberFormat(t,{style:"currency",currency:r}).format(e)},w={BIF:0,CLP:0,DJF:0,GNF:0,JPY:0,KMF:0,KRW:0,MGA:0,PYG:0,RWF:0,UGX:0,VND:0,VUV:0,XAF:0,XOF:0,XPF:0,BHD:3,JOD:3,KWD:3,OMR:3,TND:3};var u=class{config;provider;providerPromise;constructor(r){if(this.config=r||this.detectFromEnv(),!this.config.endpoint||!this.config.token)throw new Error("YNS configuration required. Provide endpoint and token in constructor or set YNS_ENDPOINT and YNS_TOKEN environment variables.")}detectFromEnv(){return{endpoint:process.env.YNS_ENDPOINT||"",token:process.env.YNS_TOKEN||""}}async getProvider(){return this.provider?this.provider:(this.providerPromise||(this.providerPromise=this.loadProvider()),this.provider=await this.providerPromise,this.provider)}async loadProvider(){try{let{createYnsProvider:r}=await Promise.resolve().then(()=>(l(),P));return r(this.config)}catch(r){throw new Error(`Failed to initialize YNS provider: ${r instanceof Error?r.message:"Unknown error"}`)}}get product(){return{browse:async(r={})=>(await this.getProvider()).productBrowse(r),get:async r=>(await this.getProvider()).productGet(r),search:async r=>{let t=await this.getProvider();if(!t.productSearch)throw new Error("Product search is not supported by YNS provider");return t.productSearch(r)}}}get cart(){return{add:async r=>(await this.getProvider()).cartAdd(r),update:async r=>(await this.getProvider()).cartUpdate(r),remove:async r=>(await this.getProvider()).cartRemove(r),clear:async r=>(await this.getProvider()).cartClear(r),get:async r=>(await this.getProvider()).cartGet(r)}}get order(){return{get:async r=>{let t=await this.getProvider();if(!t.orderGet)throw new Error("Order retrieval is not supported by YNS provider");return t.orderGet(r)},list:async(r={})=>{let t=await this.getProvider();if(!t.orderList)throw new Error("Order listing is not supported by YNS provider");return t.orderList(r)}}}};export{u as Commerce,C as formatMoney};