@caldik/client 0.0.7 → 0.0.9

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 CHANGED
@@ -21,4 +21,32 @@ const caldik = new CaldikClient({
21
21
  storeId: "STORE_...",
22
22
  apiKey: process.env.CALDIK_PRIVATE_API_KEY,
23
23
  })
24
- ```
24
+ ```
25
+
26
+ ## Cart and discounts
27
+
28
+
29
+ ```ts
30
+ const cart = caldik.cart.add({
31
+ productId: "PRODUCT_...",
32
+ productVariantId: "PRODUCT_VARIANT_...",
33
+ quantity: 1,
34
+ })
35
+
36
+ caldik.cart.updateQuantity(cart.items[0].id, 3)
37
+ caldik.cart.remove(cart.items[0].id)
38
+ ```
39
+
40
+ ```ts
41
+ await caldik.cart.applyDiscount("SAVE10")
42
+
43
+ const result = await caldik.cart.checkout({
44
+ paymentMethod: "stripe",
45
+ successUrl: "https://shop.example/success",
46
+ cancelUrl: "https://shop.example/cart",
47
+ })
48
+ ```
49
+
50
+ Use `caldik.discounts.check({ code, items })` when a cart should be managed by
51
+ the application instead of the SDK.
52
+
package/dist/index.d.mts CHANGED
@@ -148,6 +148,7 @@ interface CreateOrderInput {
148
148
  email?: string;
149
149
  paymentMethod: CheckoutPaymentProviderId;
150
150
  items: CommerceItem[];
151
+ discountCode?: string;
151
152
  successUrl?: string;
152
153
  cancelUrl?: string;
153
154
  }
@@ -158,7 +159,13 @@ interface StorefrontOrder {
158
159
  customerId: string | null;
159
160
  paymentStatus: string;
160
161
  fulfillmentStatus: string;
162
+ subtotal: string;
161
163
  amount: string;
164
+ discount: {
165
+ code: string;
166
+ percentage: string;
167
+ amount: string;
168
+ } | null;
162
169
  currency: string;
163
170
  }
164
171
  interface CheckoutPayment {
@@ -202,18 +209,6 @@ interface SubscriptionResponse {
202
209
  checkoutSessionId: string;
203
210
  };
204
211
  }
205
- interface StoreWebhookEventDefinition {
206
- name: StoreWebhookEventName;
207
- resource: string;
208
- action: string;
209
- description: string;
210
- }
211
- interface StoreWebhookEventsResponse {
212
- success: boolean;
213
- apiVersion: "v1";
214
- catalogVersion: string;
215
- events: StoreWebhookEventDefinition[];
216
- }
217
212
  interface StoreWebhookEvent<TObject extends Record<string, unknown> = Record<string, unknown>> {
218
213
  id: string;
219
214
  type: StoreWebhookEventName;
@@ -257,6 +252,7 @@ declare class CaldikHttpClient {
257
252
  private readonly apiUrl;
258
253
  private readonly storeId?;
259
254
  constructor(options?: HttpClientOptions);
255
+ storeContextId(): string | null;
260
256
  private baseUrl;
261
257
  private request;
262
258
  get<T>(path: string, params?: Record<string, string | number>, options?: RequestOptions): Promise<T>;
@@ -316,12 +312,66 @@ declare class SubscriptionsApi {
316
312
  create(input: CreateSubscriptionInput): Promise<SubscriptionResponse>;
317
313
  }
318
314
  //#endregion
319
- //#region src/webhooks.d.ts
320
- declare class WebhooksApi {
315
+ //#region src/discounts.d.ts
316
+ interface CheckDiscountInput {
317
+ code: string;
318
+ items: CommerceItem[];
319
+ }
320
+ interface AppliedDiscount {
321
+ code: string;
322
+ percentage: string;
323
+ }
324
+ interface DiscountQuoteResponse {
325
+ success: boolean;
326
+ discount: AppliedDiscount;
327
+ subtotal: string;
328
+ discountAmount: string;
329
+ total: string;
330
+ currency: string;
331
+ }
332
+ declare class DiscountsApi {
333
+ private client;
334
+ constructor(client: CaldikHttpClient);
335
+ check(input: CheckDiscountInput): Promise<DiscountQuoteResponse>;
336
+ }
337
+ //#endregion
338
+ //#region src/cart.d.ts
339
+ interface CartItem {
340
+ id: string;
341
+ productId: string;
342
+ productVariantId?: string;
343
+ quantity: number;
344
+ }
345
+ interface CartDiscount {
346
+ code: string;
347
+ percentage: string;
348
+ subtotal: string;
349
+ discountAmount: string;
350
+ total: string;
351
+ currency: string;
352
+ }
353
+ interface CartSnapshot {
354
+ items: CartItem[];
355
+ discount: CartDiscount | null;
356
+ }
357
+ type CartCheckoutInput = Omit<CreateOrderInput, "items" | "discountCode">;
358
+ declare class CartApi {
321
359
  private client;
360
+ private memory;
322
361
  constructor(client: CaldikHttpClient);
323
- /** Lists the canonical events accepted by store webhook subscriptions. */
324
- events(): Promise<StoreWebhookEventsResponse>;
362
+ private storageKey;
363
+ private read;
364
+ private write;
365
+ get(): CartSnapshot;
366
+ add(item: ProductCommerceItem): CartSnapshot;
367
+ updateQuantity(itemId: string, nextQuantity: number): CartSnapshot;
368
+ remove(itemId: string): CartSnapshot;
369
+ clear(): CartSnapshot;
370
+ private commerceItems;
371
+ checkDiscount(code: string): Promise<DiscountQuoteResponse>;
372
+ applyDiscount(code: string): Promise<CartSnapshot>;
373
+ removeDiscount(): CartSnapshot;
374
+ checkout(input: CartCheckoutInput): Promise<OrderResponse>;
325
375
  }
326
376
  //#endregion
327
377
  //#region src/index.d.ts
@@ -340,8 +390,9 @@ declare class CaldikClient {
340
390
  readonly checkout: CheckoutApi;
341
391
  readonly customer: CustomerApi;
342
392
  readonly subscriptions: SubscriptionsApi;
343
- readonly webhooks: WebhooksApi;
393
+ readonly discounts: DiscountsApi;
394
+ readonly cart: CartApi;
344
395
  constructor(options?: CaldikClientOptions);
345
396
  }
346
397
  //#endregion
347
- export { CaldikClient, CaldikClientOptions, CategoriesResponse, Category, CategoryDetails, CategoryMetadata, CategoryProduct, CategoryResponse, CheckoutPayment, CheckoutPaymentProviderId, CommerceItem, CreateOrderInput, CreateSubscriptionInput, CustomerLoginResponse, CustomerLoginVerifyResponse, ManualCommerceItem, OrderResponse, PaymentProviderId, Product, ProductCommerceItem, ProductCustomField, ProductCustomFieldInputType, ProductCustomFieldOption, ProductMetadata, ProductResponse, ProductTag, ProductVariant, ProductsResponse, Review, ReviewsResponse, Store, StoreResponse, StoreWebhookEvent, StoreWebhookEventDefinition, StoreWebhookEventName, StoreWebhookEventsResponse, StorefrontMetadata, StorefrontOrder, SubscriptionPaymentProviderId, SubscriptionResponse, checkoutPaymentProviderIds, paymentProviderIds, storeWebhookEventNames, subscriptionPaymentProviderIds };
398
+ export { AppliedDiscount, CaldikClient, CaldikClientOptions, CartApi, CartCheckoutInput, CartDiscount, CartItem, CartSnapshot, CategoriesResponse, Category, CategoryDetails, CategoryMetadata, CategoryProduct, CategoryResponse, CheckDiscountInput, CheckoutPayment, CheckoutPaymentProviderId, CommerceItem, CreateOrderInput, CreateSubscriptionInput, CustomerLoginResponse, CustomerLoginVerifyResponse, DiscountQuoteResponse, DiscountsApi, ManualCommerceItem, OrderResponse, PaymentProviderId, Product, ProductCommerceItem, ProductCustomField, ProductCustomFieldInputType, ProductCustomFieldOption, ProductMetadata, ProductResponse, ProductTag, ProductVariant, ProductsResponse, Review, ReviewsResponse, Store, StoreResponse, StoreWebhookEvent, StoreWebhookEventName, StorefrontMetadata, StorefrontOrder, SubscriptionPaymentProviderId, SubscriptionResponse, checkoutPaymentProviderIds, paymentProviderIds, storeWebhookEventNames, subscriptionPaymentProviderIds };
package/dist/index.mjs CHANGED
@@ -1,5 +1,27 @@
1
+ //#region src/internal/environment.ts
2
+ function firstValue(...values) {
3
+ for (const value of values) {
4
+ const normalized = value?.trim();
5
+ if (normalized) return normalized;
6
+ }
7
+ }
8
+ function importMetaEnvironment() {
9
+ return import.meta.env;
10
+ }
11
+ function browserBuildStoreId() {
12
+ const environment = importMetaEnvironment();
13
+ return firstValue(typeof process === "undefined" ? void 0 : process.env.NEXT_PUBLIC_CALDIK_STORE_ID, environment?.VITE_CALDIK_STORE_ID, environment?.PUBLIC_CALDIK_STORE_ID);
14
+ }
15
+ function environmentApiUrl(browser) {
16
+ const environment = importMetaEnvironment();
17
+ if (browser) return firstValue(typeof process === "undefined" ? void 0 : process.env.NEXT_PUBLIC_CALDIK_API_URL, environment?.VITE_CALDIK_API_URL, environment?.PUBLIC_CALDIK_API_URL);
18
+ return firstValue(typeof process === "undefined" ? void 0 : process.env.CALDIK_API_URL);
19
+ }
20
+ //#endregion
1
21
  //#region src/config.ts
2
- const defaultApiUrl = typeof API_URL !== "undefined" ? API_URL : "https://api.caldik.com";
22
+ function defaultApiUrl(browser) {
23
+ return environmentApiUrl(browser) || (typeof API_URL !== "undefined" ? API_URL : "https://api.caldik.com");
24
+ }
3
25
  //#endregion
4
26
  //#region src/internal/client.ts
5
27
  const STORE_ID_PATTERN = /^STORE_[a-f0-9]{32}$/i;
@@ -12,8 +34,15 @@ function boundRuntimeStoreId() {
12
34
  return storeId && STORE_ID_PATTERN.test(storeId) ? storeId : null;
13
35
  }
14
36
  function browserRuntimeStoreId() {
15
- const storeId = document.documentElement?.getAttribute(STOREFRONT_STORE_ID_ATTRIBUTE)?.trim();
16
- return storeId && STORE_ID_PATTERN.test(storeId) ? storeId : null;
37
+ const documentStoreId = document.documentElement?.getAttribute(STOREFRONT_STORE_ID_ATTRIBUTE)?.trim();
38
+ if (documentStoreId && STORE_ID_PATTERN.test(documentStoreId)) return documentStoreId;
39
+ const buildStoreId = browserBuildStoreId();
40
+ return buildStoreId && STORE_ID_PATTERN.test(buildStoreId) ? buildStoreId : null;
41
+ }
42
+ function isLocalBrowserRuntime() {
43
+ if (!isBrowserRuntime() || typeof location === "undefined") return false;
44
+ const hostname = location.hostname.toLowerCase();
45
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
17
46
  }
18
47
  var CaldikHttpClient = class {
19
48
  apiKey;
@@ -21,13 +50,16 @@ var CaldikHttpClient = class {
21
50
  storeId;
22
51
  constructor(options = {}) {
23
52
  this.apiKey = options.apiKey?.trim() || void 0;
24
- this.apiUrl = options.apiUrl || defaultApiUrl;
53
+ this.apiUrl = options.apiUrl || defaultApiUrl(isBrowserRuntime());
25
54
  const configuredStoreId = options.storeId?.trim() || void 0;
26
55
  if (this.apiKey && !this.apiKey.startsWith("sk_")) throw new Error("Caldik external integrations require a private sk_ API key");
27
56
  if (configuredStoreId && !STORE_ID_PATTERN.test(configuredStoreId)) throw new Error("Caldik storeId is invalid");
28
57
  if (isBrowserRuntime() && this.apiKey) throw new Error("Caldik private API keys cannot be used in browser code");
29
58
  this.storeId = configuredStoreId || (isBrowserRuntime() ? browserRuntimeStoreId() || void 0 : void 0);
30
59
  }
60
+ storeContextId() {
61
+ return this.storeId || boundRuntimeStoreId();
62
+ }
31
63
  baseUrl() {
32
64
  if (isBrowserRuntime()) {
33
65
  const storeId = this.storeId;
@@ -47,7 +79,7 @@ var CaldikHttpClient = class {
47
79
  const headers = {};
48
80
  if (!isBrowserRuntime()) headers["User-Agent"] = "CaldikClient/0.0.2";
49
81
  if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
50
- else if (options?.access === "public" && !isBrowserRuntime()) headers["X-Caldik-Public-Request"] = "1";
82
+ else if (options?.access === "public" && (!isBrowserRuntime() || isLocalBrowserRuntime())) headers["X-Caldik-Public-Request"] = "1";
51
83
  if (input !== void 0) headers["Content-Type"] = "application/json";
52
84
  if (options?.edgeCache) headers["X-Cache-Policy"] = "public";
53
85
  const response = await fetch(url, {
@@ -161,15 +193,176 @@ var SubscriptionsApi = class {
161
193
  }
162
194
  };
163
195
  //#endregion
164
- //#region src/webhooks.ts
165
- var WebhooksApi = class {
196
+ //#region src/discounts.ts
197
+ var DiscountsApi = class {
166
198
  client;
167
199
  constructor(client) {
168
200
  this.client = client;
169
201
  }
170
- /** Lists the canonical events accepted by store webhook subscriptions. */
171
- events() {
172
- return this.client.get("/webhooks/events");
202
+ check(input) {
203
+ return this.client.post("/discounts/check", input, { access: "public" });
204
+ }
205
+ };
206
+ //#endregion
207
+ //#region src/cart.ts
208
+ const CART_VERSION = 1;
209
+ const MAX_QUANTITY = 999999;
210
+ function cartItemId(item) {
211
+ return item.productVariantId ? `${item.productId}:${item.productVariantId}` : item.productId;
212
+ }
213
+ function quantity(value, allowZero = false) {
214
+ const normalized = value ?? 1;
215
+ const minimum = allowZero ? 0 : 1;
216
+ if (!Number.isInteger(normalized) || normalized < minimum || normalized > MAX_QUANTITY) throw new Error(`Cart quantity must be an integer between ${minimum} and ${MAX_QUANTITY}`);
217
+ return normalized;
218
+ }
219
+ function cloneSnapshot(state) {
220
+ return {
221
+ items: state.items.map((item) => ({ ...item })),
222
+ discount: state.discount ? { ...state.discount } : null
223
+ };
224
+ }
225
+ function isCartItem(value) {
226
+ if (!value || typeof value !== "object") return false;
227
+ const item = value;
228
+ return typeof item.id === "string" && typeof item.productId === "string" && (item.productVariantId === void 0 || typeof item.productVariantId === "string") && typeof item.quantity === "number" && Number.isInteger(item.quantity) && item.quantity > 0 && item.quantity <= MAX_QUANTITY;
229
+ }
230
+ function isCartDiscount(value) {
231
+ if (!value || typeof value !== "object") return false;
232
+ const discount = value;
233
+ return typeof discount.code === "string" && typeof discount.percentage === "string" && typeof discount.subtotal === "string" && typeof discount.discountAmount === "string" && typeof discount.total === "string" && typeof discount.currency === "string";
234
+ }
235
+ var CartApi = class {
236
+ client;
237
+ memory = {
238
+ version: CART_VERSION,
239
+ items: [],
240
+ discount: null
241
+ };
242
+ constructor(client) {
243
+ this.client = client;
244
+ }
245
+ storageKey() {
246
+ const storeId = this.client.storeContextId();
247
+ return storeId ? `caldik:cart:${storeId}` : null;
248
+ }
249
+ read() {
250
+ const key = this.storageKey();
251
+ if (key && typeof localStorage !== "undefined") try {
252
+ const value = localStorage.getItem(key);
253
+ if (value) {
254
+ const parsed = JSON.parse(value);
255
+ if (parsed.version === CART_VERSION && Array.isArray(parsed.items) && parsed.items.every(isCartItem)) this.memory = {
256
+ version: CART_VERSION,
257
+ items: parsed.items,
258
+ discount: isCartDiscount(parsed.discount) ? parsed.discount : null
259
+ };
260
+ }
261
+ } catch {}
262
+ return this.memory;
263
+ }
264
+ write(state) {
265
+ this.memory = {
266
+ version: CART_VERSION,
267
+ ...cloneSnapshot(state)
268
+ };
269
+ const key = this.storageKey();
270
+ if (key && typeof localStorage !== "undefined") try {
271
+ localStorage.setItem(key, JSON.stringify(this.memory));
272
+ } catch {}
273
+ return this.get();
274
+ }
275
+ get() {
276
+ return cloneSnapshot(this.read());
277
+ }
278
+ add(item) {
279
+ const productId = item.productId.trim();
280
+ const productVariantId = item.productVariantId?.trim() || "";
281
+ if (!productId) throw new Error("Cart productId is required");
282
+ const state = this.get();
283
+ const id = cartItemId({
284
+ productId,
285
+ productVariantId
286
+ });
287
+ const existing = state.items.find((entry) => entry.id === id);
288
+ const addedQuantity = quantity(item.quantity);
289
+ if (existing) existing.quantity = quantity(existing.quantity + addedQuantity);
290
+ else state.items.push({
291
+ id,
292
+ productId,
293
+ ...productVariantId ? { productVariantId } : {},
294
+ quantity: addedQuantity
295
+ });
296
+ state.discount = null;
297
+ return this.write(state);
298
+ }
299
+ updateQuantity(itemId, nextQuantity) {
300
+ const state = this.get();
301
+ const normalized = quantity(nextQuantity, true);
302
+ const index = state.items.findIndex((item) => item.id === itemId);
303
+ if (index === -1) throw new Error("Cart item not found");
304
+ if (normalized === 0) state.items.splice(index, 1);
305
+ else state.items[index].quantity = normalized;
306
+ state.discount = null;
307
+ return this.write(state);
308
+ }
309
+ remove(itemId) {
310
+ const state = this.get();
311
+ const items = state.items.filter((item) => item.id !== itemId);
312
+ if (items.length === state.items.length) throw new Error("Cart item not found");
313
+ return this.write({
314
+ items,
315
+ discount: null
316
+ });
317
+ }
318
+ clear() {
319
+ return this.write({
320
+ items: [],
321
+ discount: null
322
+ });
323
+ }
324
+ commerceItems() {
325
+ return this.get().items.map(({ productId, productVariantId, quantity }) => ({
326
+ productId,
327
+ ...productVariantId ? { productVariantId } : {},
328
+ quantity
329
+ }));
330
+ }
331
+ async checkDiscount(code) {
332
+ const items = this.commerceItems();
333
+ if (items.length === 0) throw new Error("Cannot check a discount on an empty cart");
334
+ return this.client.post("/discounts/check", {
335
+ code,
336
+ items
337
+ }, { access: "public" });
338
+ }
339
+ async applyDiscount(code) {
340
+ const quote = await this.checkDiscount(code);
341
+ const state = this.get();
342
+ state.discount = {
343
+ ...quote.discount,
344
+ subtotal: quote.subtotal,
345
+ discountAmount: quote.discountAmount,
346
+ total: quote.total,
347
+ currency: quote.currency
348
+ };
349
+ return this.write(state);
350
+ }
351
+ removeDiscount() {
352
+ const state = this.get();
353
+ state.discount = null;
354
+ return this.write(state);
355
+ }
356
+ async checkout(input) {
357
+ const state = this.get();
358
+ if (state.items.length === 0) throw new Error("Cannot checkout an empty cart");
359
+ const response = await this.client.post("/checkouts", {
360
+ ...input,
361
+ items: this.commerceItems(),
362
+ ...state.discount ? { discountCode: state.discount.code } : {}
363
+ }, { access: "public" });
364
+ this.clear();
365
+ return response;
173
366
  }
174
367
  };
175
368
  //#endregion
@@ -224,7 +417,8 @@ var CaldikClient = class {
224
417
  checkout;
225
418
  customer;
226
419
  subscriptions;
227
- webhooks;
420
+ discounts;
421
+ cart;
228
422
  constructor(options = {}) {
229
423
  const client = new CaldikHttpClient({
230
424
  apiKey: options.apiKey,
@@ -238,8 +432,9 @@ var CaldikClient = class {
238
432
  this.checkout = new CheckoutApi(client);
239
433
  this.customer = new CustomerApi(client);
240
434
  this.subscriptions = new SubscriptionsApi(client);
241
- this.webhooks = new WebhooksApi(client);
435
+ this.discounts = new DiscountsApi(client);
436
+ this.cart = new CartApi(client);
242
437
  }
243
438
  };
244
439
  //#endregion
245
- export { CaldikClient, checkoutPaymentProviderIds, paymentProviderIds, storeWebhookEventNames, subscriptionPaymentProviderIds };
440
+ export { CaldikClient, CartApi, DiscountsApi, checkoutPaymentProviderIds, paymentProviderIds, storeWebhookEventNames, subscriptionPaymentProviderIds };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caldik/client",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.mts",