@shopi-lk/storefront-sdk 1.0.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.
@@ -0,0 +1,397 @@
1
+ interface ShopiConfig {
2
+ /** Your Storefront API key (must start with shopi_pk_) */
3
+ apiKey: string;
4
+ /** Override the API base URL (default: https://apicall.shopi.lk/v1) */
5
+ baseUrl?: string;
6
+ /** Request timeout in milliseconds (default: 10000) */
7
+ timeoutMs?: number;
8
+ }
9
+ interface Shop {
10
+ id: string;
11
+ shop_name: string;
12
+ subdomain: string;
13
+ custom_domain: string | null;
14
+ primary_domain: string | null;
15
+ business_type: string | null;
16
+ is_active: boolean;
17
+ active_theme: number | null;
18
+ }
19
+ interface Product {
20
+ id: string;
21
+ shop_id: string;
22
+ name: string;
23
+ slug: string;
24
+ description: string | null;
25
+ price: number;
26
+ compare_at_price: number | null;
27
+ category: string | null;
28
+ images: string[];
29
+ is_visible: boolean;
30
+ stock_quantity: number | null;
31
+ track_inventory: boolean;
32
+ sku: string | null;
33
+ weight: number | null;
34
+ variants: ProductVariant[] | null;
35
+ created_at: string;
36
+ updated_at: string;
37
+ }
38
+ interface ProductVariant {
39
+ name: string;
40
+ options: string[];
41
+ prices?: Record<string, number>;
42
+ }
43
+ interface ProductListParams {
44
+ category?: string;
45
+ search?: string;
46
+ limit?: number;
47
+ offset?: number;
48
+ sort_by?: "created_at" | "name" | "price" | "sales" | "updated_at";
49
+ sort_order?: "asc" | "desc";
50
+ }
51
+ interface ProductListResponse {
52
+ products: Product[];
53
+ total: number;
54
+ limit: number;
55
+ offset: number;
56
+ }
57
+ interface Category {
58
+ id: string;
59
+ name: string;
60
+ image_url: string | null;
61
+ }
62
+ interface Listing {
63
+ id: string;
64
+ shop_id?: string;
65
+ name: string;
66
+ slug: string | null;
67
+ description: string | null;
68
+ price: number | null;
69
+ price_label: string | null;
70
+ currency: string | null;
71
+ category: string | null;
72
+ condition: string | null;
73
+ location: string | null;
74
+ images: string[] | null;
75
+ video: string | null;
76
+ features: string[] | null;
77
+ specifications: Record<string, any> | null;
78
+ contact_info: Record<string, any> | null;
79
+ availability_status: string | null;
80
+ is_featured: boolean | null;
81
+ views_count: number | null;
82
+ inquiries_count: number | null;
83
+ sort_order: number | null;
84
+ created_at: string;
85
+ updated_at: string;
86
+ }
87
+ interface ListingListParams {
88
+ category?: string;
89
+ search?: string;
90
+ limit?: number;
91
+ offset?: number;
92
+ sort_by?: "created_at" | "name" | "price" | "views_count" | "updated_at" | "sort_order";
93
+ sort_order?: "asc" | "desc";
94
+ }
95
+ interface ListingListResponse {
96
+ listings: Listing[];
97
+ total: number;
98
+ limit: number;
99
+ offset: number;
100
+ }
101
+ interface RentalItem {
102
+ id: string;
103
+ shop_id?: string;
104
+ name: string;
105
+ slug: string | null;
106
+ description: string | null;
107
+ category: string | null;
108
+ condition: string | null;
109
+ location: string | null;
110
+ images: string[] | null;
111
+ video: string | null;
112
+ specifications: Record<string, any> | null;
113
+ currency: string | null;
114
+ hourly_rate: number | null;
115
+ daily_rate: number | null;
116
+ weekly_rate: number | null;
117
+ monthly_rate: number | null;
118
+ deposit_required: boolean | null;
119
+ deposit_amount: number | null;
120
+ min_rental_duration_hours: number | null;
121
+ max_rental_duration_days: number | null;
122
+ total_quantity: number | null;
123
+ available_quantity: number | null;
124
+ pickup_available: boolean | null;
125
+ delivery_available: boolean | null;
126
+ delivery_fee: number | null;
127
+ is_featured: boolean | null;
128
+ sort_order: number | null;
129
+ created_at: string;
130
+ updated_at: string;
131
+ }
132
+ interface RentalItemListParams {
133
+ category?: string;
134
+ search?: string;
135
+ limit?: number;
136
+ offset?: number;
137
+ sort_by?: "created_at" | "name" | "daily_rate" | "updated_at" | "sort_order";
138
+ sort_order?: "asc" | "desc";
139
+ }
140
+ interface RentalItemListResponse {
141
+ rental_items: RentalItem[];
142
+ total: number;
143
+ limit: number;
144
+ offset: number;
145
+ }
146
+ interface Service {
147
+ id: string;
148
+ shop_id?: string;
149
+ name: string;
150
+ slug: string | null;
151
+ description: string | null;
152
+ price: number;
153
+ currency: string | null;
154
+ category: string | null;
155
+ duration_minutes: number;
156
+ images: string[] | null;
157
+ deposit_required: boolean | null;
158
+ deposit_amount: number | null;
159
+ max_bookings_per_slot: number | null;
160
+ buffer_before_minutes: number | null;
161
+ buffer_after_minutes: number | null;
162
+ sort_order: number | null;
163
+ created_at: string;
164
+ updated_at: string;
165
+ }
166
+ interface ServiceListParams {
167
+ category?: string;
168
+ search?: string;
169
+ limit?: number;
170
+ offset?: number;
171
+ sort_by?: "created_at" | "name" | "price" | "sort_order" | "updated_at";
172
+ sort_order?: "asc" | "desc";
173
+ }
174
+ interface ServiceListResponse {
175
+ services: Service[];
176
+ total: number;
177
+ limit: number;
178
+ offset: number;
179
+ }
180
+ interface StoreSettings {
181
+ shop_name: string;
182
+ tagline: string | null;
183
+ logo_url: string | null;
184
+ phone_number: string | null;
185
+ email: string | null;
186
+ address: string | null;
187
+ footer_text: string | null;
188
+ meta_title: string | null;
189
+ meta_description: string | null;
190
+ keywords: string | null;
191
+ business_category: string | null;
192
+ business_hours: any;
193
+ location_city: string | null;
194
+ location_country: string | null;
195
+ social_links: Record<string, string> | null;
196
+ shipping_regions: any;
197
+ is_free_shipping: boolean | null;
198
+ free_shipping_enabled: boolean | null;
199
+ free_shipping_threshold: number | null;
200
+ shipping_currency: string | null;
201
+ shipping_config: any;
202
+ international_shipping: any;
203
+ }
204
+ interface PaymentMethod {
205
+ method_id: string;
206
+ method_name: string;
207
+ enabled: boolean;
208
+ }
209
+ interface BankDetail {
210
+ bank_name: string;
211
+ branch_name: string | null;
212
+ account_number: string;
213
+ account_holder_name: string;
214
+ }
215
+ interface Page {
216
+ id: string;
217
+ title: string;
218
+ slug: string;
219
+ content: string | null;
220
+ is_visible: boolean;
221
+ sort_order: number | null;
222
+ meta_title: string | null;
223
+ meta_description: string | null;
224
+ rendered_html: string | null;
225
+ rendered_css: string | null;
226
+ }
227
+ interface BlogPost {
228
+ id: string;
229
+ title: string;
230
+ slug: string;
231
+ content?: string;
232
+ excerpt: string | null;
233
+ featured_image: string | null;
234
+ author_name: string | null;
235
+ published_at: string | null;
236
+ tags: string[] | null;
237
+ meta_title: string | null;
238
+ meta_description: string | null;
239
+ rendered_html?: string | null;
240
+ rendered_css?: string | null;
241
+ }
242
+ interface BlogListParams {
243
+ limit?: number;
244
+ offset?: number;
245
+ }
246
+ interface Review {
247
+ id: string;
248
+ product_id: string;
249
+ rating: number;
250
+ review_text: string | null;
251
+ image_url: string | null;
252
+ is_verified_purchase: boolean;
253
+ created_at: string;
254
+ }
255
+ interface Discount {
256
+ id: string;
257
+ name: string;
258
+ discount_percentage: number;
259
+ applies_to: string;
260
+ product_id: string | null;
261
+ start_date: string;
262
+ end_date: string;
263
+ }
264
+ interface ThemeSettings {
265
+ theme_id: number;
266
+ settings: Record<string, any>;
267
+ is_published: boolean;
268
+ }
269
+ interface CartItem {
270
+ product_id: string;
271
+ name: string;
272
+ price: number;
273
+ quantity: number;
274
+ image?: string;
275
+ variant?: string;
276
+ }
277
+ interface CreateOrderParams {
278
+ customer_name: string;
279
+ customer_email: string;
280
+ customer_phone?: string;
281
+ items: CartItem[];
282
+ shipping_address?: Record<string, any>;
283
+ shipping_method?: string;
284
+ payment_method?: string;
285
+ notes?: string;
286
+ promo_code_id?: string;
287
+ discount_amount?: number;
288
+ }
289
+ interface Order {
290
+ id: string;
291
+ order_number: string;
292
+ total: number;
293
+ status: string;
294
+ created_at: string;
295
+ }
296
+ interface PromoValidateParams {
297
+ code: string;
298
+ cart_items?: CartItem[];
299
+ subtotal?: number;
300
+ }
301
+ interface PromoValidateResult {
302
+ valid: boolean;
303
+ error?: string;
304
+ promo_code?: {
305
+ id: string;
306
+ code: string;
307
+ discount_type: string;
308
+ discount_value: number;
309
+ applies_to: string;
310
+ };
311
+ discount_amount?: number;
312
+ message?: string;
313
+ }
314
+ interface SubmitReviewParams {
315
+ product_id: string;
316
+ rating: number;
317
+ review_text?: string;
318
+ customer_name?: string;
319
+ customer_email?: string;
320
+ image_url?: string;
321
+ }
322
+
323
+ interface RateLimitState {
324
+ /** Maximum requests allowed per window */
325
+ limit: number;
326
+ /** Requests remaining in the current window */
327
+ remaining: number;
328
+ /** Unix timestamp (seconds) when the window resets */
329
+ reset: number;
330
+ }
331
+ declare class Shopi {
332
+ private apiKey;
333
+ private baseUrl;
334
+ private timeoutMs;
335
+ /** Populated after every request — reflects the current rate-limit window. */
336
+ rateLimit: RateLimitState | null;
337
+ constructor(config: ShopiConfig);
338
+ private request;
339
+ getShop(): Promise<Shop>;
340
+ products: {
341
+ list: (params?: ProductListParams) => Promise<ProductListResponse>;
342
+ getBySlug: (slug: string) => Promise<Product>;
343
+ };
344
+ categories: {
345
+ list: () => Promise<Category[]>;
346
+ };
347
+ listings: {
348
+ list: (params?: ListingListParams) => Promise<ListingListResponse>;
349
+ getBySlug: (slug: string) => Promise<Listing>;
350
+ };
351
+ rentalItems: {
352
+ list: (params?: RentalItemListParams) => Promise<RentalItemListResponse>;
353
+ getBySlug: (slug: string) => Promise<RentalItem>;
354
+ };
355
+ services: {
356
+ list: (params?: ServiceListParams) => Promise<ServiceListResponse>;
357
+ getBySlug: (slug: string) => Promise<Service>;
358
+ };
359
+ getStoreSettings(): Promise<StoreSettings>;
360
+ getPaymentMethods(): Promise<{
361
+ payment_methods: PaymentMethod[];
362
+ bank_details: BankDetail[];
363
+ }>;
364
+ pages: {
365
+ list: () => Promise<Page[]>;
366
+ getBySlug: (slug: string) => Promise<Page>;
367
+ };
368
+ blog: {
369
+ list: (params?: BlogListParams) => Promise<{
370
+ posts: BlogPost[];
371
+ total: number;
372
+ }>;
373
+ getBySlug: (slug: string) => Promise<BlogPost>;
374
+ };
375
+ reviews: {
376
+ list: (productId: string, limit?: number) => Promise<Review[]>;
377
+ submit: (params: SubmitReviewParams) => Promise<{
378
+ id: string;
379
+ }>;
380
+ };
381
+ getDiscounts(): Promise<{
382
+ shop_wide: Discount[];
383
+ product_specific: Discount[];
384
+ }>;
385
+ getThemeSettings(): Promise<ThemeSettings | null>;
386
+ checkout: {
387
+ createOrder: (params: CreateOrderParams) => Promise<Order>;
388
+ validatePromo: (params: PromoValidateParams) => Promise<PromoValidateResult>;
389
+ };
390
+ }
391
+ declare class ShopiError extends Error {
392
+ status: number;
393
+ data: unknown;
394
+ constructor(message: string, status: number, data?: unknown);
395
+ }
396
+
397
+ export { type BankDetail, type BlogListParams, type BlogPost, type CartItem, type Category, type CreateOrderParams, type Discount, type Listing, type ListingListParams, type ListingListResponse, type Order, type Page, type PaymentMethod, type Product, type ProductListParams, type ProductListResponse, type ProductVariant, type PromoValidateParams, type PromoValidateResult, type RateLimitState, type RentalItem, type RentalItemListParams, type RentalItemListResponse, type Review, type Service, type ServiceListParams, type ServiceListResponse, type Shop, Shopi, type ShopiConfig, ShopiError, type StoreSettings, type SubmitReviewParams, type ThemeSettings };
package/dist/index.js ADDED
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Shopi: () => Shopi,
24
+ ShopiError: () => ShopiError
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var DEFAULT_BASE_URL = "https://apicall.shopi.lk/v1";
28
+ var DEFAULT_TIMEOUT_MS = 1e4;
29
+ var MAX_RETRIES = 2;
30
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
31
+ function sleep(ms) {
32
+ return new Promise((resolve) => setTimeout(resolve, ms));
33
+ }
34
+ var Shopi = class {
35
+ constructor(config) {
36
+ /** Populated after every request — reflects the current rate-limit window. */
37
+ this.rateLimit = null;
38
+ // ── Products ───────────────────────────────────────────────────────────
39
+ this.products = {
40
+ list: async (params) => {
41
+ return this.request("products", {
42
+ params
43
+ });
44
+ },
45
+ getBySlug: async (slug) => {
46
+ const res = await this.request(
47
+ `products/${encodeURIComponent(slug)}`
48
+ );
49
+ return res.product;
50
+ }
51
+ };
52
+ // ── Categories ─────────────────────────────────────────────────────────
53
+ this.categories = {
54
+ list: async () => {
55
+ const res = await this.request("categories");
56
+ return res.categories;
57
+ }
58
+ };
59
+ // ── Listings ──────────────────────────────────────────────────────────
60
+ this.listings = {
61
+ list: async (params) => {
62
+ return this.request("listings", {
63
+ params
64
+ });
65
+ },
66
+ getBySlug: async (slug) => {
67
+ const res = await this.request(
68
+ `listings/${encodeURIComponent(slug)}`
69
+ );
70
+ return res.listing;
71
+ }
72
+ };
73
+ // ── Rental Items ──────────────────────────────────────────────────────
74
+ this.rentalItems = {
75
+ list: async (params) => {
76
+ return this.request("rental-items", {
77
+ params
78
+ });
79
+ },
80
+ getBySlug: async (slug) => {
81
+ const res = await this.request(
82
+ `rental-items/${encodeURIComponent(slug)}`
83
+ );
84
+ return res.rental_item;
85
+ }
86
+ };
87
+ // ── Services ──────────────────────────────────────────────────────────
88
+ this.services = {
89
+ list: async (params) => {
90
+ return this.request("services", {
91
+ params
92
+ });
93
+ },
94
+ getBySlug: async (slug) => {
95
+ const res = await this.request(
96
+ `services/${encodeURIComponent(slug)}`
97
+ );
98
+ return res.service;
99
+ }
100
+ };
101
+ // ── Pages ──────────────────────────────────────────────────────────────
102
+ this.pages = {
103
+ list: async () => {
104
+ const res = await this.request("pages");
105
+ return res.pages;
106
+ },
107
+ getBySlug: async (slug) => {
108
+ const res = await this.request(
109
+ `pages/${encodeURIComponent(slug)}`
110
+ );
111
+ return res.page;
112
+ }
113
+ };
114
+ // ── Blog ───────────────────────────────────────────────────────────────
115
+ this.blog = {
116
+ list: async (params) => {
117
+ return this.request("blogs", {
118
+ params
119
+ });
120
+ },
121
+ getBySlug: async (slug) => {
122
+ const res = await this.request(
123
+ `blogs/${encodeURIComponent(slug)}`
124
+ );
125
+ return res.post;
126
+ }
127
+ };
128
+ // ── Reviews ────────────────────────────────────────────────────────────
129
+ this.reviews = {
130
+ list: async (productId, limit) => {
131
+ const res = await this.request(
132
+ `reviews/${encodeURIComponent(productId)}`,
133
+ { params: limit ? { limit } : void 0 }
134
+ );
135
+ return res.reviews;
136
+ },
137
+ submit: async (params) => {
138
+ const res = await this.request("reviews", {
139
+ method: "POST",
140
+ body: params
141
+ });
142
+ return res.review;
143
+ }
144
+ };
145
+ // ── Checkout / Orders ──────────────────────────────────────────────────
146
+ this.checkout = {
147
+ createOrder: async (params) => {
148
+ const res = await this.request("orders", {
149
+ method: "POST",
150
+ body: params
151
+ });
152
+ return res.order;
153
+ },
154
+ validatePromo: async (params) => {
155
+ return this.request("promo/validate", {
156
+ method: "POST",
157
+ body: params
158
+ });
159
+ }
160
+ };
161
+ if (!config.apiKey || typeof config.apiKey !== "string") {
162
+ throw new Error("apiKey is required and must be a string");
163
+ }
164
+ const trimmed = config.apiKey.trim();
165
+ if (!trimmed.startsWith("shopi_pk_")) {
166
+ throw new Error('apiKey must start with "shopi_pk_"');
167
+ }
168
+ this.apiKey = trimmed;
169
+ this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
170
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
171
+ }
172
+ async request(path, options = {}) {
173
+ const { method = "GET", body, params, _retryCount = 0 } = options;
174
+ let url = `${this.baseUrl}/${path}`;
175
+ if (params) {
176
+ const search = new URLSearchParams();
177
+ for (const [k, v] of Object.entries(params)) {
178
+ if (v !== void 0 && v !== null) search.set(k, String(v));
179
+ }
180
+ const qs = search.toString();
181
+ if (qs) url += `?${qs}`;
182
+ }
183
+ const controller = new AbortController();
184
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
185
+ let res;
186
+ try {
187
+ res = await fetch(url, {
188
+ method,
189
+ headers: {
190
+ "X-Shopi-Api-Key": this.apiKey,
191
+ ...body ? { "Content-Type": "application/json" } : {}
192
+ },
193
+ body: body ? JSON.stringify(body) : void 0,
194
+ signal: controller.signal
195
+ });
196
+ } catch (err) {
197
+ clearTimeout(timer);
198
+ if (err instanceof Error && err.name === "AbortError") {
199
+ throw new ShopiError(
200
+ `Request timed out after ${this.timeoutMs}ms`,
201
+ 408
202
+ );
203
+ }
204
+ throw new ShopiError(
205
+ err instanceof Error ? err.message : "Network error",
206
+ 0
207
+ );
208
+ } finally {
209
+ clearTimeout(timer);
210
+ }
211
+ if (RETRYABLE_STATUSES.has(res.status) && _retryCount < MAX_RETRIES) {
212
+ let delay = 2 ** _retryCount * 300;
213
+ if (res.status === 429) {
214
+ const retryAfter = res.headers.get("Retry-After");
215
+ if (retryAfter) delay = Math.min(parseInt(retryAfter, 10) * 1e3, 1e4);
216
+ }
217
+ await sleep(delay);
218
+ return this.request(path, { ...options, _retryCount: _retryCount + 1 });
219
+ }
220
+ const rlLimit = res.headers.get("X-RateLimit-Limit");
221
+ const rlRemaining = res.headers.get("X-RateLimit-Remaining");
222
+ const rlReset = res.headers.get("X-RateLimit-Reset");
223
+ if (rlLimit && rlRemaining && rlReset) {
224
+ this.rateLimit = {
225
+ limit: parseInt(rlLimit, 10),
226
+ remaining: parseInt(rlRemaining, 10),
227
+ reset: parseInt(rlReset, 10)
228
+ };
229
+ }
230
+ let data;
231
+ try {
232
+ data = await res.json();
233
+ } catch {
234
+ throw new ShopiError(
235
+ `Unexpected server response (status ${res.status})`,
236
+ res.status
237
+ );
238
+ }
239
+ if (!res.ok) {
240
+ const errData = data;
241
+ throw new ShopiError(
242
+ typeof errData?.error === "string" ? errData.error : "Request failed",
243
+ res.status,
244
+ data
245
+ );
246
+ }
247
+ return data;
248
+ }
249
+ // ── Shop ───────────────────────────────────────────────────────────────
250
+ async getShop() {
251
+ const res = await this.request("shop");
252
+ return res.shop;
253
+ }
254
+ // ── Store Settings ─────────────────────────────────────────────────────
255
+ async getStoreSettings() {
256
+ const res = await this.request(
257
+ "store-settings"
258
+ );
259
+ return res.settings;
260
+ }
261
+ // ── Payment Methods ────────────────────────────────────────────────────
262
+ async getPaymentMethods() {
263
+ return this.request("payment-methods");
264
+ }
265
+ // ── Discounts ──────────────────────────────────────────────────────────
266
+ async getDiscounts() {
267
+ const res = await this.request("discounts");
268
+ return {
269
+ shop_wide: res.shop_wide_discounts,
270
+ product_specific: res.product_discounts
271
+ };
272
+ }
273
+ // ── Theme Settings ─────────────────────────────────────────────────────
274
+ async getThemeSettings() {
275
+ const res = await this.request(
276
+ "theme-settings"
277
+ );
278
+ return res.theme;
279
+ }
280
+ };
281
+ var ShopiError = class extends Error {
282
+ constructor(message, status, data) {
283
+ super(message);
284
+ this.name = "ShopiError";
285
+ this.status = status;
286
+ this.data = data;
287
+ }
288
+ };
289
+ // Annotate the CommonJS export names for ESM import in node:
290
+ 0 && (module.exports = {
291
+ Shopi,
292
+ ShopiError
293
+ });