@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.
package/dist/index.mjs ADDED
@@ -0,0 +1,267 @@
1
+ // src/index.ts
2
+ var DEFAULT_BASE_URL = "https://apicall.shopi.lk/v1";
3
+ var DEFAULT_TIMEOUT_MS = 1e4;
4
+ var MAX_RETRIES = 2;
5
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
6
+ function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ var Shopi = class {
10
+ constructor(config) {
11
+ /** Populated after every request — reflects the current rate-limit window. */
12
+ this.rateLimit = null;
13
+ // ── Products ───────────────────────────────────────────────────────────
14
+ this.products = {
15
+ list: async (params) => {
16
+ return this.request("products", {
17
+ params
18
+ });
19
+ },
20
+ getBySlug: async (slug) => {
21
+ const res = await this.request(
22
+ `products/${encodeURIComponent(slug)}`
23
+ );
24
+ return res.product;
25
+ }
26
+ };
27
+ // ── Categories ─────────────────────────────────────────────────────────
28
+ this.categories = {
29
+ list: async () => {
30
+ const res = await this.request("categories");
31
+ return res.categories;
32
+ }
33
+ };
34
+ // ── Listings ──────────────────────────────────────────────────────────
35
+ this.listings = {
36
+ list: async (params) => {
37
+ return this.request("listings", {
38
+ params
39
+ });
40
+ },
41
+ getBySlug: async (slug) => {
42
+ const res = await this.request(
43
+ `listings/${encodeURIComponent(slug)}`
44
+ );
45
+ return res.listing;
46
+ }
47
+ };
48
+ // ── Rental Items ──────────────────────────────────────────────────────
49
+ this.rentalItems = {
50
+ list: async (params) => {
51
+ return this.request("rental-items", {
52
+ params
53
+ });
54
+ },
55
+ getBySlug: async (slug) => {
56
+ const res = await this.request(
57
+ `rental-items/${encodeURIComponent(slug)}`
58
+ );
59
+ return res.rental_item;
60
+ }
61
+ };
62
+ // ── Services ──────────────────────────────────────────────────────────
63
+ this.services = {
64
+ list: async (params) => {
65
+ return this.request("services", {
66
+ params
67
+ });
68
+ },
69
+ getBySlug: async (slug) => {
70
+ const res = await this.request(
71
+ `services/${encodeURIComponent(slug)}`
72
+ );
73
+ return res.service;
74
+ }
75
+ };
76
+ // ── Pages ──────────────────────────────────────────────────────────────
77
+ this.pages = {
78
+ list: async () => {
79
+ const res = await this.request("pages");
80
+ return res.pages;
81
+ },
82
+ getBySlug: async (slug) => {
83
+ const res = await this.request(
84
+ `pages/${encodeURIComponent(slug)}`
85
+ );
86
+ return res.page;
87
+ }
88
+ };
89
+ // ── Blog ───────────────────────────────────────────────────────────────
90
+ this.blog = {
91
+ list: async (params) => {
92
+ return this.request("blogs", {
93
+ params
94
+ });
95
+ },
96
+ getBySlug: async (slug) => {
97
+ const res = await this.request(
98
+ `blogs/${encodeURIComponent(slug)}`
99
+ );
100
+ return res.post;
101
+ }
102
+ };
103
+ // ── Reviews ────────────────────────────────────────────────────────────
104
+ this.reviews = {
105
+ list: async (productId, limit) => {
106
+ const res = await this.request(
107
+ `reviews/${encodeURIComponent(productId)}`,
108
+ { params: limit ? { limit } : void 0 }
109
+ );
110
+ return res.reviews;
111
+ },
112
+ submit: async (params) => {
113
+ const res = await this.request("reviews", {
114
+ method: "POST",
115
+ body: params
116
+ });
117
+ return res.review;
118
+ }
119
+ };
120
+ // ── Checkout / Orders ──────────────────────────────────────────────────
121
+ this.checkout = {
122
+ createOrder: async (params) => {
123
+ const res = await this.request("orders", {
124
+ method: "POST",
125
+ body: params
126
+ });
127
+ return res.order;
128
+ },
129
+ validatePromo: async (params) => {
130
+ return this.request("promo/validate", {
131
+ method: "POST",
132
+ body: params
133
+ });
134
+ }
135
+ };
136
+ if (!config.apiKey || typeof config.apiKey !== "string") {
137
+ throw new Error("apiKey is required and must be a string");
138
+ }
139
+ const trimmed = config.apiKey.trim();
140
+ if (!trimmed.startsWith("shopi_pk_")) {
141
+ throw new Error('apiKey must start with "shopi_pk_"');
142
+ }
143
+ this.apiKey = trimmed;
144
+ this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
145
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
146
+ }
147
+ async request(path, options = {}) {
148
+ const { method = "GET", body, params, _retryCount = 0 } = options;
149
+ let url = `${this.baseUrl}/${path}`;
150
+ if (params) {
151
+ const search = new URLSearchParams();
152
+ for (const [k, v] of Object.entries(params)) {
153
+ if (v !== void 0 && v !== null) search.set(k, String(v));
154
+ }
155
+ const qs = search.toString();
156
+ if (qs) url += `?${qs}`;
157
+ }
158
+ const controller = new AbortController();
159
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
160
+ let res;
161
+ try {
162
+ res = await fetch(url, {
163
+ method,
164
+ headers: {
165
+ "X-Shopi-Api-Key": this.apiKey,
166
+ ...body ? { "Content-Type": "application/json" } : {}
167
+ },
168
+ body: body ? JSON.stringify(body) : void 0,
169
+ signal: controller.signal
170
+ });
171
+ } catch (err) {
172
+ clearTimeout(timer);
173
+ if (err instanceof Error && err.name === "AbortError") {
174
+ throw new ShopiError(
175
+ `Request timed out after ${this.timeoutMs}ms`,
176
+ 408
177
+ );
178
+ }
179
+ throw new ShopiError(
180
+ err instanceof Error ? err.message : "Network error",
181
+ 0
182
+ );
183
+ } finally {
184
+ clearTimeout(timer);
185
+ }
186
+ if (RETRYABLE_STATUSES.has(res.status) && _retryCount < MAX_RETRIES) {
187
+ let delay = 2 ** _retryCount * 300;
188
+ if (res.status === 429) {
189
+ const retryAfter = res.headers.get("Retry-After");
190
+ if (retryAfter) delay = Math.min(parseInt(retryAfter, 10) * 1e3, 1e4);
191
+ }
192
+ await sleep(delay);
193
+ return this.request(path, { ...options, _retryCount: _retryCount + 1 });
194
+ }
195
+ const rlLimit = res.headers.get("X-RateLimit-Limit");
196
+ const rlRemaining = res.headers.get("X-RateLimit-Remaining");
197
+ const rlReset = res.headers.get("X-RateLimit-Reset");
198
+ if (rlLimit && rlRemaining && rlReset) {
199
+ this.rateLimit = {
200
+ limit: parseInt(rlLimit, 10),
201
+ remaining: parseInt(rlRemaining, 10),
202
+ reset: parseInt(rlReset, 10)
203
+ };
204
+ }
205
+ let data;
206
+ try {
207
+ data = await res.json();
208
+ } catch {
209
+ throw new ShopiError(
210
+ `Unexpected server response (status ${res.status})`,
211
+ res.status
212
+ );
213
+ }
214
+ if (!res.ok) {
215
+ const errData = data;
216
+ throw new ShopiError(
217
+ typeof errData?.error === "string" ? errData.error : "Request failed",
218
+ res.status,
219
+ data
220
+ );
221
+ }
222
+ return data;
223
+ }
224
+ // ── Shop ───────────────────────────────────────────────────────────────
225
+ async getShop() {
226
+ const res = await this.request("shop");
227
+ return res.shop;
228
+ }
229
+ // ── Store Settings ─────────────────────────────────────────────────────
230
+ async getStoreSettings() {
231
+ const res = await this.request(
232
+ "store-settings"
233
+ );
234
+ return res.settings;
235
+ }
236
+ // ── Payment Methods ────────────────────────────────────────────────────
237
+ async getPaymentMethods() {
238
+ return this.request("payment-methods");
239
+ }
240
+ // ── Discounts ──────────────────────────────────────────────────────────
241
+ async getDiscounts() {
242
+ const res = await this.request("discounts");
243
+ return {
244
+ shop_wide: res.shop_wide_discounts,
245
+ product_specific: res.product_discounts
246
+ };
247
+ }
248
+ // ── Theme Settings ─────────────────────────────────────────────────────
249
+ async getThemeSettings() {
250
+ const res = await this.request(
251
+ "theme-settings"
252
+ );
253
+ return res.theme;
254
+ }
255
+ };
256
+ var ShopiError = class extends Error {
257
+ constructor(message, status, data) {
258
+ super(message);
259
+ this.name = "ShopiError";
260
+ this.status = status;
261
+ this.data = data;
262
+ }
263
+ };
264
+ export {
265
+ Shopi,
266
+ ShopiError
267
+ };
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@shopi-lk/storefront-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Official Shopi.lk Storefront SDK — access products, cart, checkout, and theme settings for custom storefronts",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": ["dist"],
16
+ "scripts": {
17
+ "build": "tsup src/index.ts --format cjs,esm --dts",
18
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
19
+ },
20
+ "keywords": ["shopi", "storefront", "ecommerce", "sdk", "headless-commerce"],
21
+ "license": "MIT",
22
+ "devDependencies": {
23
+ "tsup": "^8.0.0",
24
+ "typescript": "^5.3.0"
25
+ }
26
+ }