@managesales/storefront 1.0.1 → 1.0.2
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/CHANGELOG.md +26 -0
- package/README.md +152 -101
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@managesales/storefront`. This project adheres to
|
|
4
|
+
[Semantic Versioning](https://semver.org).
|
|
5
|
+
|
|
6
|
+
## 1.0.2 — 2026-07-11
|
|
7
|
+
|
|
8
|
+
### Docs
|
|
9
|
+
- Rewrote the README as a full developer landing page: human-readable title and tagline, feature
|
|
10
|
+
overview, all-package-manager install, a copy-paste quick start, a complete **API organization**
|
|
11
|
+
reference, framework examples (React / Next.js / Node.js), server-side auth (SSR) guide, error
|
|
12
|
+
handling, TypeScript guide, a **Versioning** policy, and this changelog.
|
|
13
|
+
|
|
14
|
+
## 1.0.1 — 2026-07-11
|
|
15
|
+
|
|
16
|
+
### Docs
|
|
17
|
+
- Expanded the README with per-resource examples and browser/Node runtime support notes.
|
|
18
|
+
|
|
19
|
+
## 1.0.0 — 2026-07-11
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Initial public release. Resource-based Storefront SDK with namespaces for products, collections,
|
|
23
|
+
checkout, cart recovery, auth, customer, blog, bookings, and forms.
|
|
24
|
+
- Built-in customer auth with automatic access-token refresh on `401`.
|
|
25
|
+
- `StorefrontError` class exposing `status`, `code`, `message`, and `details`.
|
|
26
|
+
- Dual ESM + CommonJS builds with bundled TypeScript declarations. Zero runtime dependencies.
|
package/README.md
CHANGED
|
@@ -1,84 +1,110 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ManageSales Storefront SDK
|
|
2
|
+
|
|
3
|
+
**Build custom storefronts on top of the ManageSales Commerce Platform.**
|
|
2
4
|
|
|
3
5
|
[](https://www.npmjs.com/package/@managesales/storefront)
|
|
4
6
|
[](./LICENSE)
|
|
5
7
|
[](#typescript-support)
|
|
6
8
|
[](#browser--node-support)
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
manage customer accounts with a typed, ergonomic client instead of hand-rolled `fetch` calls.
|
|
10
|
+
`@managesales/storefront` is a typed, resource-based client for the ManageSales Storefront API —
|
|
11
|
+
like Shopify's Storefront client, for ManageSales. Everything you need to ship a storefront:
|
|
11
12
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
13
|
+
- 🛍️ **Products** — list, search, and fetch details
|
|
14
|
+
- 🗂️ **Collections** — browse categories and their products
|
|
15
|
+
- 👤 **Customers** — registration, login, profile, order history
|
|
16
|
+
- 🛒 **Checkout** — line items, coupons, complete purchases (+ abandoned-cart recovery)
|
|
17
|
+
- 🔎 **Search** — full-text product search
|
|
18
|
+
- 🟦 **TypeScript** — first-class types, no `@types` needed
|
|
19
|
+
- ⚡ **SSR-ready** — inject a custom `fetch`; server-side auth pattern built in
|
|
20
|
+
- 📦 **Zero dependencies** — one small file, dual ESM + CommonJS
|
|
17
21
|
|
|
18
22
|
---
|
|
19
23
|
|
|
20
24
|
## Table of contents
|
|
21
25
|
|
|
22
|
-
- [What is this?](#what-is-this)
|
|
23
26
|
- [Installation](#installation)
|
|
24
|
-
- [Quick start](#quick-start
|
|
27
|
+
- [Quick start](#quick-start)
|
|
28
|
+
- [API organization](#api-organization)
|
|
25
29
|
- [Create a client](#create-a-client)
|
|
26
|
-
- [Products](#products)
|
|
30
|
+
- [Products & search](#products--search)
|
|
27
31
|
- [Collections](#collections)
|
|
28
32
|
- [Cart & checkout](#cart--checkout)
|
|
29
33
|
- [Authentication](#authentication)
|
|
30
34
|
- [Customer account](#customer-account)
|
|
31
35
|
- [Error handling](#error-handling)
|
|
32
|
-
- [More resources](#more-resources)
|
|
33
36
|
- [TypeScript support](#typescript-support)
|
|
37
|
+
- [Framework examples](#framework-examples)
|
|
34
38
|
- [Browser & Node support](#browser--node-support)
|
|
39
|
+
- [Versioning](#versioning)
|
|
40
|
+
- [Changelog](#changelog)
|
|
35
41
|
- [No-npm alternative](#no-npm-alternative)
|
|
36
42
|
- [License](#license)
|
|
37
43
|
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## What is this?
|
|
41
|
-
|
|
42
|
-
`@managesales/storefront` is the official client SDK for the **ManageSales Storefront API** — the
|
|
43
|
-
public, customer-facing API behind a ManageSales store. Use it to build a custom storefront
|
|
44
|
-
(product pages, search, cart, checkout, customer login) in any JavaScript/TypeScript framework,
|
|
45
|
-
while the SDK handles request headers, workspace scoping, error parsing, pagination, and token
|
|
46
|
-
refresh for you.
|
|
47
|
-
|
|
48
|
-
It talks to the same API you can also reach directly over HTTP (`/storefront-api/*`) — the SDK is
|
|
49
|
-
just the typed, batteries-included way to do it.
|
|
50
|
-
|
|
51
44
|
## Installation
|
|
52
45
|
|
|
53
46
|
```bash
|
|
54
47
|
npm install @managesales/storefront
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
pnpm add @managesales/storefront
|
|
49
|
+
yarn add @managesales/storefront
|
|
50
|
+
bun add @managesales/storefront
|
|
58
51
|
```
|
|
59
52
|
|
|
60
|
-
## Quick start
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
Copy-paste, add your workspace ID, and you're fetching live catalog data — no auth required:
|
|
61
56
|
|
|
62
57
|
```ts
|
|
63
58
|
import { createStorefrontClient } from "@managesales/storefront";
|
|
64
59
|
|
|
65
60
|
const client = createStorefrontClient({
|
|
66
|
-
workspaceId: "ws_123", // your store
|
|
61
|
+
workspaceId: "ws_123", // your store — Settings → API
|
|
67
62
|
});
|
|
68
63
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.log(`${total} products`, products);
|
|
64
|
+
const { data: products } = await client.products.list({ limit: 12 });
|
|
65
|
+
console.log(products);
|
|
72
66
|
```
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
customers log in.
|
|
68
|
+
## API organization
|
|
76
69
|
|
|
77
|
-
|
|
70
|
+
Every resource is a namespace on the client. Public resources need no auth; customer resources
|
|
71
|
+
require a logged-in session.
|
|
78
72
|
|
|
79
73
|
```ts
|
|
80
|
-
|
|
74
|
+
// Catalog (public)
|
|
75
|
+
client.products.list(params?) // paginated list
|
|
76
|
+
client.products.get(idOrSlug) // product details
|
|
77
|
+
client.products.search(query, params?) // full-text search
|
|
78
|
+
client.collections.list(params?)
|
|
79
|
+
client.collections.get(idOrSlug) // collection + its products
|
|
80
|
+
|
|
81
|
+
// Cart & checkout (public)
|
|
82
|
+
client.checkout.create(lineItems) // start a checkout from line items
|
|
83
|
+
client.checkout.get(checkoutId)
|
|
84
|
+
client.checkout.applyCoupon(checkoutId, code)
|
|
85
|
+
client.checkout.complete(checkoutId, payment?)
|
|
86
|
+
client.cart.recover(token) // rebuild a checkout from a recovery link
|
|
87
|
+
|
|
88
|
+
// Auth & customer (customer session)
|
|
89
|
+
client.auth.register({ email, password, name })
|
|
90
|
+
client.auth.login(email, password)
|
|
91
|
+
client.auth.loginWithGoogle(idToken)
|
|
92
|
+
client.auth.logout()
|
|
93
|
+
client.auth.isAuthenticated()
|
|
94
|
+
client.auth.setTokens(access, refresh) // restore a session (e.g. from cookies)
|
|
95
|
+
client.customer.me()
|
|
96
|
+
client.customer.orders(params?) // order history
|
|
97
|
+
client.customer.addresses()
|
|
98
|
+
|
|
99
|
+
// Content (public)
|
|
100
|
+
client.blog.list(params?) / client.blog.get(slug)
|
|
101
|
+
client.bookings.services() / client.bookings.availability(params) / client.bookings.book(data)
|
|
102
|
+
client.forms.get(idOrSlug) / client.forms.submit(formId, data) / client.forms.uploadFile(formId, file)
|
|
103
|
+
```
|
|
81
104
|
|
|
105
|
+
## Create a client
|
|
106
|
+
|
|
107
|
+
```ts
|
|
82
108
|
const client = createStorefrontClient({
|
|
83
109
|
workspaceId: "ws_123",
|
|
84
110
|
});
|
|
@@ -87,12 +113,11 @@ const client = createStorefrontClient({
|
|
|
87
113
|
| Option | Type | Required | Description |
|
|
88
114
|
|--------|------|----------|-------------|
|
|
89
115
|
| `workspaceId` | `string` | ✅ | Identifies your store. Found in **Settings → API**. |
|
|
90
|
-
| `apiUrl` | `string` | — | Backend base URL **including** the `/api/v1` prefix. Defaults to ManageSales cloud; override for self-hosted/staging/local. |
|
|
91
|
-
| `customFetch` | `typeof fetch` | — | Inject a `fetch`
|
|
116
|
+
| `apiUrl` | `string` | — | Backend base URL **including** the `/api/v1` prefix. Defaults to ManageSales cloud; override for self-hosted / staging / local. |
|
|
117
|
+
| `customFetch` | `typeof fetch` | — | Inject a `fetch` for SSR / edge runtimes or Node < 18. |
|
|
92
118
|
| `locale` | `string` | — | Default locale for localized content (e.g. `"en"`). |
|
|
93
119
|
|
|
94
120
|
```ts
|
|
95
|
-
// Point at your own backend and inject a custom fetch:
|
|
96
121
|
const client = createStorefrontClient({
|
|
97
122
|
workspaceId: "ws_123",
|
|
98
123
|
apiUrl: "https://api.mystore.com/api/v1",
|
|
@@ -101,7 +126,7 @@ const client = createStorefrontClient({
|
|
|
101
126
|
});
|
|
102
127
|
```
|
|
103
128
|
|
|
104
|
-
## Products
|
|
129
|
+
## Products & search
|
|
105
130
|
|
|
106
131
|
```ts
|
|
107
132
|
// List with pagination, search, and sorting
|
|
@@ -124,32 +149,30 @@ const { data: results } = await client.products.search("organic cotton");
|
|
|
124
149
|
## Collections
|
|
125
150
|
|
|
126
151
|
```ts
|
|
127
|
-
// List collections
|
|
128
152
|
const { data: collections } = await client.collections.list();
|
|
129
153
|
|
|
130
|
-
// Get a collection with its products
|
|
131
154
|
const summer = await client.collections.get("summer-sale");
|
|
132
|
-
console.log(summer.name, summer.products.length);
|
|
155
|
+
console.log(summer.name, summer.products.length); // collection + its products
|
|
133
156
|
```
|
|
134
157
|
|
|
135
158
|
## Cart & checkout
|
|
136
159
|
|
|
137
|
-
|
|
138
|
-
|
|
160
|
+
Assemble line items in your UI, create a checkout session, optionally apply a coupon, then complete
|
|
161
|
+
it:
|
|
139
162
|
|
|
140
163
|
```ts
|
|
141
|
-
// 1. Create a checkout from
|
|
164
|
+
// 1. Create a checkout from line items
|
|
142
165
|
const session = await client.checkout.create([
|
|
143
166
|
{ productId: "prod_123", variantId: "var_456", quantity: 2 },
|
|
144
167
|
{ productId: "prod_789", quantity: 1 },
|
|
145
168
|
]);
|
|
146
169
|
|
|
147
|
-
// 2. Re-fetch a session (e.g. after a
|
|
170
|
+
// 2. Re-fetch a session (e.g. after a reload)
|
|
148
171
|
const current = await client.checkout.get(session.id);
|
|
149
172
|
|
|
150
173
|
// 3. Apply a coupon
|
|
151
174
|
const discounted = await client.checkout.applyCoupon(session.id, "SAVE10");
|
|
152
|
-
console.log(
|
|
175
|
+
console.log(discounted.discount, discounted.total);
|
|
153
176
|
|
|
154
177
|
// 4. Complete the purchase
|
|
155
178
|
const order = await client.checkout.complete(session.id, {
|
|
@@ -162,44 +185,31 @@ console.log("Order placed:", order.orderNumber);
|
|
|
162
185
|
**Abandoned-cart recovery** — turn a recovery link back into a checkout session:
|
|
163
186
|
|
|
164
187
|
```ts
|
|
165
|
-
// URL: https://yourstore.com/recover?token=abc123
|
|
166
188
|
const token = new URLSearchParams(location.search).get("token")!;
|
|
167
189
|
const recovered = await client.cart.recover(token);
|
|
168
190
|
```
|
|
169
191
|
|
|
170
192
|
## Authentication
|
|
171
193
|
|
|
172
|
-
Most
|
|
173
|
-
|
|
194
|
+
Most pages need **no auth**. When a customer logs in, the SDK stores the access + refresh tokens and
|
|
195
|
+
**auto-refreshes on `401`** — you never handle tokens by hand.
|
|
174
196
|
|
|
175
197
|
```ts
|
|
176
|
-
|
|
177
|
-
const session = await client.auth.register({
|
|
178
|
-
email: "jane@example.com",
|
|
179
|
-
password: "SecurePass123!",
|
|
180
|
-
name: "Jane Doe",
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// Login
|
|
198
|
+
await client.auth.register({ email: "jane@example.com", password: "SecurePass123!", name: "Jane Doe" });
|
|
184
199
|
await client.auth.login("jane@example.com", "SecurePass123!");
|
|
200
|
+
await client.auth.loginWithGoogle(googleIdToken); // pass a Google ID token
|
|
185
201
|
|
|
186
|
-
|
|
187
|
-
await client.auth.loginWithGoogle(googleIdToken);
|
|
188
|
-
|
|
189
|
-
// Check state / log out
|
|
190
|
-
if (client.auth.isAuthenticated()) {
|
|
191
|
-
/* show account menu */
|
|
192
|
-
}
|
|
202
|
+
if (client.auth.isAuthenticated()) { /* show account menu */ }
|
|
193
203
|
await client.auth.logout();
|
|
194
204
|
```
|
|
195
205
|
|
|
196
206
|
### Server-side (recommended)
|
|
197
207
|
|
|
198
|
-
For production, run the SDK on your server
|
|
199
|
-
|
|
208
|
+
For production, run the SDK on your server with httpOnly cookies so tokens never reach the browser.
|
|
209
|
+
Restore a session with `setTokens`:
|
|
200
210
|
|
|
201
211
|
```ts
|
|
202
|
-
// app/api/storefront/[...path]/route.ts
|
|
212
|
+
// app/api/storefront/[...path]/route.ts (Next.js)
|
|
203
213
|
import { createStorefrontClient } from "@managesales/storefront";
|
|
204
214
|
import { cookies } from "next/headers";
|
|
205
215
|
|
|
@@ -217,18 +227,17 @@ export async function GET() {
|
|
|
217
227
|
|
|
218
228
|
## Customer account
|
|
219
229
|
|
|
220
|
-
Requires a logged-in session
|
|
230
|
+
Requires a logged-in session.
|
|
221
231
|
|
|
222
232
|
```ts
|
|
223
|
-
const me = await client.customer.me();
|
|
233
|
+
const me = await client.customer.me(); // profile
|
|
224
234
|
const { data: orders } = await client.customer.orders({ page: 1 }); // order history
|
|
225
|
-
const addresses = await client.customer.addresses();
|
|
235
|
+
const addresses = await client.customer.addresses(); // saved addresses
|
|
226
236
|
```
|
|
227
237
|
|
|
228
238
|
## Error handling
|
|
229
239
|
|
|
230
|
-
Every non-2xx response throws a `StorefrontError` with structured fields
|
|
231
|
-
precisely:
|
|
240
|
+
Every non-2xx response throws a `StorefrontError` with structured fields:
|
|
232
241
|
|
|
233
242
|
```ts
|
|
234
243
|
import { StorefrontError } from "@managesales/storefront";
|
|
@@ -247,30 +256,13 @@ try {
|
|
|
247
256
|
}
|
|
248
257
|
```
|
|
249
258
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
```ts
|
|
255
|
-
const { data: posts } = await client.blog.list({ limit: 10 });
|
|
256
|
-
const post = await client.blog.get("how-to-style-leather");
|
|
257
|
-
|
|
258
|
-
const services = await client.bookings.services();
|
|
259
|
-
const slots = await client.bookings.availability({ serviceId: "svc_1", date: "2026-08-15" });
|
|
260
|
-
const booking = await client.bookings.book({
|
|
261
|
-
serviceId: "svc_1",
|
|
262
|
-
date: "2026-08-15",
|
|
263
|
-
startTime: "10:00",
|
|
264
|
-
customer: { name: "Jane Doe", email: "jane@example.com" },
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const form = await client.forms.get("contact-us");
|
|
268
|
-
await client.forms.submit(form.id, { name: "Jane", email: "jane@example.com", message: "Hi!" });
|
|
269
|
-
```
|
|
259
|
+
> **Resilience:** the client automatically retries a request **once** with a refreshed token when a
|
|
260
|
+
> `401` indicates an expired access token. It does not retry on network/5xx errors — wrap calls in
|
|
261
|
+
> your own retry policy if you need that.
|
|
270
262
|
|
|
271
263
|
## TypeScript support
|
|
272
264
|
|
|
273
|
-
Written in TypeScript and shipped with declaration files — no `@types/*` package
|
|
265
|
+
Written in TypeScript and shipped with declaration files — no `@types/*` package. Params and
|
|
274
266
|
responses are fully typed, including generics like `PaginatedResponse<Product>`:
|
|
275
267
|
|
|
276
268
|
```ts
|
|
@@ -288,18 +280,77 @@ const client = createStorefrontClient(config);
|
|
|
288
280
|
const page: PaginatedResponse<Product> = await client.products.list();
|
|
289
281
|
```
|
|
290
282
|
|
|
283
|
+
## Framework examples
|
|
284
|
+
|
|
285
|
+
**React**
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { useEffect, useState } from "react";
|
|
289
|
+
import { createStorefrontClient, type Product } from "@managesales/storefront";
|
|
290
|
+
|
|
291
|
+
const client = createStorefrontClient({ workspaceId: "ws_123" });
|
|
292
|
+
|
|
293
|
+
export function ProductGrid() {
|
|
294
|
+
const [products, setProducts] = useState<Product[]>([]);
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
client.products.list({ limit: 12 }).then((res) => setProducts(res.data));
|
|
297
|
+
}, []);
|
|
298
|
+
return <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Next.js (Server Component)**
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
// app/page.tsx
|
|
306
|
+
import { createStorefrontClient } from "@managesales/storefront";
|
|
307
|
+
|
|
308
|
+
const client = createStorefrontClient({ workspaceId: "ws_123" });
|
|
309
|
+
|
|
310
|
+
export default async function Home() {
|
|
311
|
+
const { data: products } = await client.products.list({ limit: 12 });
|
|
312
|
+
return <ProductGrid products={products} />;
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Node.js**
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
import { createStorefrontClient } from "@managesales/storefront";
|
|
320
|
+
|
|
321
|
+
const client = createStorefrontClient({ workspaceId: "ws_123" });
|
|
322
|
+
|
|
323
|
+
const { data } = await client.products.list({ page: 1, limit: 20 });
|
|
324
|
+
console.log(data);
|
|
325
|
+
```
|
|
326
|
+
|
|
291
327
|
## Browser & Node support
|
|
292
328
|
|
|
293
329
|
- **Zero dependencies** — uses the platform's global `fetch`.
|
|
294
330
|
- **Node.js ≥ 18** (global `fetch`). On older Node, pass `customFetch` (e.g. `undici`).
|
|
295
331
|
- **Browsers** — all modern evergreen browsers.
|
|
296
|
-
- **Deno, Bun,
|
|
297
|
-
|
|
332
|
+
- **Deno, Bun, edge runtimes** (Cloudflare Workers, Vercel Edge) — supported; inject `customFetch`
|
|
333
|
+
where a global isn't available.
|
|
298
334
|
- **Module formats** — ships both **ESM** (`import`) and **CommonJS** (`require`).
|
|
299
335
|
|
|
336
|
+
## Versioning
|
|
337
|
+
|
|
338
|
+
Follows [Semantic Versioning](https://semver.org):
|
|
339
|
+
|
|
340
|
+
- **patch** (`x.y.Z`) — docs and bug fixes, no API change
|
|
341
|
+
- **minor** (`x.Y.z`) — additive, backward-compatible API
|
|
342
|
+
- **major** (`X.y.z`) — breaking changes to the client surface
|
|
343
|
+
|
|
344
|
+
The SDK version tracks the ManageSales Storefront API contract it targets. See the
|
|
345
|
+
[Changelog](#changelog).
|
|
346
|
+
|
|
347
|
+
## Changelog
|
|
348
|
+
|
|
349
|
+
See [CHANGELOG.md](./CHANGELOG.md) for release notes.
|
|
350
|
+
|
|
300
351
|
## No-npm alternative
|
|
301
352
|
|
|
302
|
-
Prefer
|
|
353
|
+
Prefer no dependency? The identical client is served as a single file — download it from
|
|
303
354
|
**Developers → SDKs** in the app, or curl it:
|
|
304
355
|
|
|
305
356
|
```bash
|
|
@@ -308,7 +359,7 @@ curl -o managesales-storefront.ts \
|
|
|
308
359
|
```
|
|
309
360
|
|
|
310
361
|
Then `import { createStorefrontClient } from "./managesales-storefront"`. The npm package, the curl
|
|
311
|
-
endpoint, and the in-app download are
|
|
362
|
+
endpoint, and the in-app download are generated from one source, so they're always identical.
|
|
312
363
|
|
|
313
364
|
## License
|
|
314
365
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@managesales/storefront",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Resource-based TypeScript client for building storefronts on the ManageSales Storefront API. Zero runtime dependencies, built-in auth and token refresh.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
18
|
"README.md",
|
|
19
|
+
"CHANGELOG.md",
|
|
19
20
|
"LICENSE"
|
|
20
21
|
],
|
|
21
22
|
"sideEffects": false,
|