@classytic/commerce-client 0.0.0-stage → 0.1.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/CHANGELOG.md +21 -0
- package/LICENSE +75 -0
- package/README.md +129 -2
- package/dist/category-tree.d.ts +20 -0
- package/dist/category-tree.js +39 -0
- package/dist/delivery-types.d.ts +48 -0
- package/dist/delivery-types.js +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +66 -0
- package/dist/management-types.d.ts +225 -0
- package/dist/management-types.js +1 -0
- package/dist/management.d.ts +94 -0
- package/dist/management.js +261 -0
- package/dist/types-C0cSi6Pk.d.ts +128 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/management.md +88 -0
- package/package.json +75 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-10-20
|
|
4
|
+
|
|
5
|
+
First publish.
|
|
6
|
+
|
|
7
|
+
Expose storefront tag/collection/sale/price/sort queries and public taxonomy/compare-at prices; type merchant taxonomy and compare-at writes.
|
|
8
|
+
|
|
9
|
+
Add typed category trees, hierarchy choices, parent/description/image fields and category image uploads. Category updates exclude immutable slugs.
|
|
10
|
+
|
|
11
|
+
API-key lifetime options expose optional non-expiry support; creation accepts explicit `expiresInDays: null`.
|
|
12
|
+
|
|
13
|
+
Delivery quote inputs include destination country; the storefront sends it when requesting prices.
|
|
14
|
+
|
|
15
|
+
Add a bounded store sales overview and tenant-scoped delivery settings. Public storefront clients can load delivery districts/areas and request server-priced delivery quotes without merchant credentials.
|
|
16
|
+
|
|
17
|
+
Typed store profiles, catalog reads, guest checkout with explicit retry keys, and private order tracking.
|
|
18
|
+
|
|
19
|
+
The isolated `/management` entry supports merchant catalog/media, order commands
|
|
20
|
+
and cash collection, store settings, Telegram connections and delivery history,
|
|
21
|
+
plus API-key options, creation, rotation and revocation.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Classytic Source-Available License (Community & Commercial), Version 1.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Classytic LLC. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is source-available, not open source. Please read this License
|
|
6
|
+
before using it.
|
|
7
|
+
|
|
8
|
+
1. DEFINITIONS
|
|
9
|
+
"Software" means the contents of this package and any accompanying source
|
|
10
|
+
code, object code, or documentation, in any form.
|
|
11
|
+
"Licensor" means Classytic LLC, a limited liability company organized in the
|
|
12
|
+
United States.
|
|
13
|
+
"You" means the individual or legal entity exercising rights under this
|
|
14
|
+
License.
|
|
15
|
+
"Evaluation Use" means use for evaluation, development, testing,
|
|
16
|
+
experimentation, or internal non-commercial purposes only.
|
|
17
|
+
"Production Use" means any use of the Software that is live, commercial,
|
|
18
|
+
revenue-generating, or customer-facing, or any use other than Evaluation Use.
|
|
19
|
+
"Commercial License" means a separate written agreement executed with
|
|
20
|
+
Licensor that grants Production Use rights.
|
|
21
|
+
|
|
22
|
+
2. OWNERSHIP
|
|
23
|
+
The Software is licensed, not sold. Licensor retains all right, title, and
|
|
24
|
+
interest in and to the Software, including all intellectual property rights.
|
|
25
|
+
All rights not expressly granted in this License are reserved by Licensor.
|
|
26
|
+
|
|
27
|
+
3. EVALUATION GRANT
|
|
28
|
+
Subject to Your compliance with this License, Licensor grants You a
|
|
29
|
+
worldwide, royalty-free, non-exclusive, non-transferable, revocable license
|
|
30
|
+
to download, install, run, and modify the Software solely for Evaluation Use.
|
|
31
|
+
|
|
32
|
+
4. PRODUCTION USE REQUIRES A COMMERCIAL LICENSE
|
|
33
|
+
Production Use of the Software is NOT permitted under this License and
|
|
34
|
+
requires a Commercial License from Licensor. A Commercial License may be
|
|
35
|
+
granted at Licensor's discretion, with or without a fee, and may confer
|
|
36
|
+
perpetual, irrevocable, and transferable Production Use rights as set out in
|
|
37
|
+
that agreement. Contact licensing@classytic.com to obtain one.
|
|
38
|
+
|
|
39
|
+
5. RESTRICTIONS
|
|
40
|
+
You may not:
|
|
41
|
+
(a) make any Production Use of the Software without a Commercial License;
|
|
42
|
+
(b) redistribute, resell, rent, sublicense, or otherwise make the Software
|
|
43
|
+
(in whole or in part, original or modified) available to third parties as
|
|
44
|
+
a stand-alone product, framework, library, or hosted/managed service that
|
|
45
|
+
competes with the Software or with Licensor;
|
|
46
|
+
(c) remove, obscure, or alter any copyright, license, or attribution notice.
|
|
47
|
+
|
|
48
|
+
6. FEEDBACK AND CONTRIBUTIONS
|
|
49
|
+
Any feedback, suggestions, or contributions You provide relating to the
|
|
50
|
+
Software may be used by Licensor for any purpose without obligation to You.
|
|
51
|
+
|
|
52
|
+
7. TERMINATION
|
|
53
|
+
This License and the Evaluation Grant terminate automatically if You breach
|
|
54
|
+
any term. Upon termination You must cease all use of the Software and destroy
|
|
55
|
+
all copies in Your possession. Rights under a Commercial License are governed
|
|
56
|
+
by that agreement's own terms and survive termination of this License to the
|
|
57
|
+
extent stated there.
|
|
58
|
+
|
|
59
|
+
8. DISCLAIMER OF WARRANTY
|
|
60
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
61
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
62
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
63
|
+
|
|
64
|
+
9. LIMITATION OF LIABILITY
|
|
65
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL LICENSOR BE LIABLE
|
|
66
|
+
FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
67
|
+
TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE
|
|
68
|
+
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
69
|
+
|
|
70
|
+
10. GOVERNING LAW
|
|
71
|
+
This License is governed by the laws of the State of Delaware, United States,
|
|
72
|
+
without regard to its conflict-of-laws principles. (Adjust to Your LLC's state
|
|
73
|
+
of formation.)
|
|
74
|
+
|
|
75
|
+
For commercial licensing, contact: licensing@classytic.com
|
package/README.md
CHANGED
|
@@ -1,3 +1,130 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @classytic/commerce-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@classytic/commerce-client` calls the shared Spine storefront resources.
|
|
4
|
+
It exposes profile, catalog, guest checkout and order tracking. The backend must
|
|
5
|
+
compose those resources, as Classytic Commerce does; the ERP's older checkout endpoints have
|
|
6
|
+
a different contract.
|
|
7
|
+
|
|
8
|
+
Install with `npm install @classytic/commerce-client`.
|
|
9
|
+
|
|
10
|
+
Use Arc Next 0.17 or later. This entry uses its server-safe client/API subpaths;
|
|
11
|
+
it imports no React hooks, Next.js APIs, merchant credentials or server kernels.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createStorefrontClient } from '@classytic/commerce-client';
|
|
15
|
+
import type { GuestCheckoutInput } from '@classytic/commerce-client/types';
|
|
16
|
+
|
|
17
|
+
const shop = createStorefrontClient({
|
|
18
|
+
apiUrl: 'http://localhost:4140/api/v1', // Full API mount, including its prefix.
|
|
19
|
+
store: 'store-alpha',
|
|
20
|
+
});
|
|
21
|
+
const profile = await shop.profile();
|
|
22
|
+
const page = await shop.products({ limit: 20, category: 'mugs' });
|
|
23
|
+
const next = page.next ? await shop.products({ after: page.next, limit: 20, category: 'mugs' }) : null;
|
|
24
|
+
const categories = await shop.categories();
|
|
25
|
+
// category=clothing includes products assigned to its descendants.
|
|
26
|
+
const product = await shop.product('blue-mug');
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Each instance captures its API mount and store. Dashboard `configureClient` and
|
|
30
|
+
`configureAuth` settings cannot change its routing or add credentials. Cookies
|
|
31
|
+
are omitted. Construct separate instances for separate stores or SSR requests.
|
|
32
|
+
|
|
33
|
+
## Checkout from a frontend cart
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const input: GuestCheckoutInput = {
|
|
37
|
+
lines: [{ productId: product.id, quantity: 2 }],
|
|
38
|
+
customer: { name: 'Buyer', phone: '01712345678' },
|
|
39
|
+
shippingAddress: { line1: 'Road 4', city: 'Dhaka', country: 'BD' },
|
|
40
|
+
};
|
|
41
|
+
// Create once for this attempt, outside any retry handler.
|
|
42
|
+
const idempotencyKey = crypto.randomUUID();
|
|
43
|
+
const placed = await shop.checkout(input, { idempotencyKey });
|
|
44
|
+
const status = await shop.orderStatus(placed);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Keep the input and key unchanged when retrying a lost response. Use a new key
|
|
48
|
+
when the buyer intentionally starts another order. The SDK does not generate a
|
|
49
|
+
fallback key. The server resolves prices, publication, variants, delivery charges
|
|
50
|
+
and totals. Multiple active variants require `variantSku` on each relevant line.
|
|
51
|
+
Do not send prices or lifecycle/payment state.
|
|
52
|
+
|
|
53
|
+
Money stays in integer minor units. Display it using the primitives money helpers;
|
|
54
|
+
do not assume every currency has two decimal places. This client does no pricing
|
|
55
|
+
arithmetic and stores no cart.
|
|
56
|
+
|
|
57
|
+
The checkout result includes an access token bound to that store and order. Keep
|
|
58
|
+
it privately for the buyer; the SDK puts it in `x-order-access`, never the URL.
|
|
59
|
+
It does not persist tokens or place them in logs. Status reads use `no-store` and
|
|
60
|
+
omit buyer contact/address. Shipment and delivery are fulfillment facts; delivery
|
|
61
|
+
alone does not establish COD collection.
|
|
62
|
+
|
|
63
|
+
## Requests and errors
|
|
64
|
+
|
|
65
|
+
Read methods accept an optional `{ signal }`; checkout takes it alongside its
|
|
66
|
+
required `idempotencyKey`. The client timeout defaults to 15 seconds and can be
|
|
67
|
+
configured with `timeoutMs`. A custom `fetch` transport is available for embedding
|
|
68
|
+
and tests; ordinary browser/Node consumers use the runtime's fetch.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { isArcApiError } from '@classytic/arc-next/client';
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
await shop.checkout(input, { idempotencyKey });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (isArcApiError(error)) {
|
|
77
|
+
// Inspect status/code/fieldErrors; preserve the attempt key for a retry.
|
|
78
|
+
console.error(error.code, error.fieldErrors);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Arc Next owns HTTP parsing, timeouts and the canonical error contract. Category
|
|
84
|
+
reads return the data array; product pages retain their cursor metadata.
|
|
85
|
+
The server remains the authority for validation;
|
|
86
|
+
unknown filters and extra checkout fields fail there.
|
|
87
|
+
|
|
88
|
+
Products use bounded cursor pages. Categories currently return at most 100 active
|
|
89
|
+
entries. Merchant catalog/order CRUD, integrations, key management and React hooks
|
|
90
|
+
are outside this public client.
|
|
91
|
+
|
|
92
|
+
`products({ q, category, limit, after })` searches product names literally,
|
|
93
|
+
case-insensitively, inside the selected store’s published catalog. Search terms
|
|
94
|
+
are limited to 100 characters. Pagination preserves the same search/category
|
|
95
|
+
filters; `next: null` marks the final page.
|
|
96
|
+
|
|
97
|
+
## Merchant dashboard and backend clients
|
|
98
|
+
|
|
99
|
+
Use the separate [`/management` entry](management.md) for authenticated products,
|
|
100
|
+
categories, media, order commands, store settings, Telegram connections,
|
|
101
|
+
notification delivery history and API keys. The root entry remains isolated for
|
|
102
|
+
public storefronts.
|
|
103
|
+
|
|
104
|
+
Delivery quotes accept `country` alongside `lines` and `areaId`. Send the destination
|
|
105
|
+
country when known so the preview applies the same country restriction as checkout.
|
|
106
|
+
|
|
107
|
+
`/category-tree` exports framework-independent `categoryNavigation` and
|
|
108
|
+
`categoryChoices` helpers. Public categories include `parent`, `parentPath`,
|
|
109
|
+
`description`, `image` and `displayOrder`; navigation omits children whose parents
|
|
110
|
+
are absent from the public category list.
|
|
111
|
+
|
|
112
|
+
## Storefront product filters
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const featured = await shop.products({ collection: 'featured', limit: 8 });
|
|
116
|
+
const summerSale = await shop.products({ tag: 'summer', onSale: true, limit: 8 });
|
|
117
|
+
const affordable = await shop.products({ category: 'clothing', minPrice: 0, maxPrice: 100000, sort: 'price-asc' });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Filters combine with AND. A tag or collection matches its exact stored name.
|
|
121
|
+
`category` includes descendants; `q` searches product names literally. Price bounds
|
|
122
|
+
are inclusive integer minor units on the product's base price; variant adjustments
|
|
123
|
+
are not part of that range. Sort values are `newest` (default), `price-asc` and
|
|
124
|
+
`price-desc`. Price sorting includes products with a numeric base price.
|
|
125
|
+
|
|
126
|
+
`next` is an opaque Mongokit cursor: pass it back as `after` with the same filters
|
|
127
|
+
and sort. Restart pagination when filters change. Public calls always enforce the
|
|
128
|
+
resolved store and published/priced visibility, and reject unknown query fields.
|
|
129
|
+
Merchant `products.getAll({ params })` continues to use Arc's normal allowed query
|
|
130
|
+
syntax, including tags, collections and base-price bracket filters.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/category-tree.d.ts
|
|
2
|
+
export interface CategoryBranch {
|
|
3
|
+
name: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
children?: readonly CategoryBranch[];
|
|
6
|
+
}
|
|
7
|
+
export interface CategoryChoice {
|
|
8
|
+
value: string;
|
|
9
|
+
label: string;
|
|
10
|
+
depth: number;
|
|
11
|
+
}
|
|
12
|
+
/** Flatten a category tree into breadcrumb choices, excluding a complete subtree when editing its parent. */
|
|
13
|
+
export declare function categoryChoices(nodes: readonly CategoryBranch[], excludeSlug?: string): CategoryChoice[];
|
|
14
|
+
/** Group the public flat projection into a navigation tree; children of missing parents stay unlisted. */
|
|
15
|
+
export declare function categoryNavigation(rows: readonly {
|
|
16
|
+
name: string;
|
|
17
|
+
slug: string;
|
|
18
|
+
parent?: string | null;
|
|
19
|
+
}[]): CategoryBranch[];
|
|
20
|
+
//#endregion
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/category-tree.ts
|
|
2
|
+
/** Flatten a category tree into breadcrumb choices, excluding a complete subtree when editing its parent. */
|
|
3
|
+
function categoryChoices(nodes, excludeSlug) {
|
|
4
|
+
function visit(items, path, seen) {
|
|
5
|
+
return items.flatMap((item) => {
|
|
6
|
+
if (item.slug === excludeSlug) return [];
|
|
7
|
+
if (seen.has(item.slug) || path.length > 20) throw new Error("Invalid category hierarchy.");
|
|
8
|
+
const branch = [...path, item.name];
|
|
9
|
+
return [{
|
|
10
|
+
value: item.slug,
|
|
11
|
+
label: branch.join(" / "),
|
|
12
|
+
depth: path.length
|
|
13
|
+
}, ...visit(item.children ?? [], branch, /* @__PURE__ */ new Set([...seen, item.slug]))];
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return visit(nodes, [], /* @__PURE__ */ new Set());
|
|
17
|
+
}
|
|
18
|
+
/** Group the public flat projection into a navigation tree; children of missing parents stay unlisted. */
|
|
19
|
+
function categoryNavigation(rows) {
|
|
20
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const row of rows) {
|
|
22
|
+
if (nodes.has(row.slug)) throw new Error("Duplicate category slug.");
|
|
23
|
+
nodes.set(row.slug, {
|
|
24
|
+
name: row.name,
|
|
25
|
+
slug: row.slug,
|
|
26
|
+
children: []
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const roots = [];
|
|
30
|
+
for (const row of rows) {
|
|
31
|
+
const node = nodes.get(row.slug);
|
|
32
|
+
if (!node) throw new Error("Category navigation is incomplete.");
|
|
33
|
+
if (row.parent) nodes.get(row.parent)?.children.push(node);
|
|
34
|
+
else roots.push(node);
|
|
35
|
+
}
|
|
36
|
+
return roots;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { categoryChoices, categoryNavigation };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/delivery-types.d.ts
|
|
2
|
+
export interface DeliveryRules {
|
|
3
|
+
mode: 'flat' | 'zones';
|
|
4
|
+
zones: Array<{
|
|
5
|
+
name: string;
|
|
6
|
+
chargeMinor: number;
|
|
7
|
+
districts: string[];
|
|
8
|
+
}>;
|
|
9
|
+
freeOverMinor: number | null;
|
|
10
|
+
}
|
|
11
|
+
export interface DeliverySettings {
|
|
12
|
+
deliveryChargeMinor: number;
|
|
13
|
+
deliveryRules: DeliveryRules;
|
|
14
|
+
version: number | null;
|
|
15
|
+
}
|
|
16
|
+
export interface DeliverySettingsUpdate {
|
|
17
|
+
deliveryChargeMinor: number;
|
|
18
|
+
deliveryRules: DeliveryRules;
|
|
19
|
+
expectedVersion: number | null;
|
|
20
|
+
}
|
|
21
|
+
export interface DeliveryDistrict {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
divisionId: string;
|
|
25
|
+
divisionName: string;
|
|
26
|
+
}
|
|
27
|
+
export interface DeliveryArea {
|
|
28
|
+
id: number;
|
|
29
|
+
name: string;
|
|
30
|
+
districtName: string;
|
|
31
|
+
}
|
|
32
|
+
export interface DeliveryQuoteInput {
|
|
33
|
+
lines: Array<{
|
|
34
|
+
productId: string;
|
|
35
|
+
variantSku?: string;
|
|
36
|
+
quantity: number;
|
|
37
|
+
}>;
|
|
38
|
+
areaId?: number;
|
|
39
|
+
country?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface DeliveryQuote {
|
|
42
|
+
subtotalMinor: number;
|
|
43
|
+
deliveryChargeMinor: number;
|
|
44
|
+
currency: string;
|
|
45
|
+
zone?: string;
|
|
46
|
+
freeDelivery?: boolean;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DeliveryArea, DeliveryDistrict, DeliveryQuote, DeliveryQuoteInput } from "./delivery-types.js";
|
|
2
|
+
import { a as StorefrontCategory, c as StorefrontProductPage, d as StorefrontRequestOptions, i as GuestOrderAccess, l as StorefrontProductQuery, n as GuestCheckoutOptions, o as StorefrontOrder, r as GuestCheckoutResult, s as StorefrontProduct, t as GuestCheckoutInput, u as StorefrontProfile } from "./types-C0cSi6Pk.js";
|
|
3
|
+
import { ArcClientConfig } from "@classytic/arc-next/client";
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
export interface StorefrontClientOptions {
|
|
6
|
+
/** Full API mount, for example https://api.example.com/api/v1. */
|
|
7
|
+
apiUrl: string;
|
|
8
|
+
store: string;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
fetch?: ArcClientConfig['fetch'];
|
|
11
|
+
}
|
|
12
|
+
/** Build an isolated guest client; never inherit dashboard credentials or global routing. */
|
|
13
|
+
export declare function createStorefrontClient(options: StorefrontClientOptions): {
|
|
14
|
+
profile: (request?: StorefrontRequestOptions) => Promise<StorefrontProfile>;
|
|
15
|
+
products: (query?: StorefrontProductQuery, request?: StorefrontRequestOptions) => Promise<StorefrontProductPage>;
|
|
16
|
+
product: (slug: string, request?: StorefrontRequestOptions) => Promise<StorefrontProduct>;
|
|
17
|
+
categories: (request?: StorefrontRequestOptions) => Promise<StorefrontCategory[]>;
|
|
18
|
+
deliveryDistricts: (request?: StorefrontRequestOptions) => Promise<{
|
|
19
|
+
requiresArea: boolean;
|
|
20
|
+
data: DeliveryDistrict[];
|
|
21
|
+
}>;
|
|
22
|
+
deliveryAreas: (district: string, request?: StorefrontRequestOptions) => Promise<{
|
|
23
|
+
data: DeliveryArea[];
|
|
24
|
+
}>;
|
|
25
|
+
quoteDelivery: (input: DeliveryQuoteInput, request?: StorefrontRequestOptions) => Promise<DeliveryQuote>;
|
|
26
|
+
checkout: (input: GuestCheckoutInput, request: GuestCheckoutOptions) => Promise<GuestCheckoutResult>;
|
|
27
|
+
orderStatus: (access: GuestOrderAccess, request?: StorefrontRequestOptions) => Promise<StorefrontOrder>;
|
|
28
|
+
};
|
|
29
|
+
export type StorefrontClient = ReturnType<typeof createStorefrontClient>;
|
|
30
|
+
//#endregion
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { BaseApi } from "@classytic/arc-next/api";
|
|
2
|
+
import { createClient } from "@classytic/arc-next/client";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
function segment(value) {
|
|
5
|
+
if (!value || value === "." || value === "..") throw new Error("A nonempty resource identifier is required.");
|
|
6
|
+
return encodeURIComponent(value);
|
|
7
|
+
}
|
|
8
|
+
/** Build an isolated guest client; never inherit dashboard credentials or global routing. */
|
|
9
|
+
function createStorefrontClient(options) {
|
|
10
|
+
const store = options.store;
|
|
11
|
+
const url = new URL(options.apiUrl);
|
|
12
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("apiUrl must be an HTTP(S) API mount without credentials, query or fragment.");
|
|
13
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(store)) throw new Error("A valid store slug is required.");
|
|
14
|
+
const config = {
|
|
15
|
+
client: createClient({
|
|
16
|
+
baseUrl: url.href.replace(/\/+$/, ""),
|
|
17
|
+
getToken: () => null,
|
|
18
|
+
getOrgId: () => null,
|
|
19
|
+
credentials: "omit",
|
|
20
|
+
autoIdempotency: false,
|
|
21
|
+
timeoutMs: options.timeoutMs ?? 15e3,
|
|
22
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
23
|
+
}),
|
|
24
|
+
basePath: "",
|
|
25
|
+
cache: "no-store"
|
|
26
|
+
};
|
|
27
|
+
const catalog = new BaseApi(`storefront/${segment(store)}`, config);
|
|
28
|
+
const profiles = new BaseApi("stores", config);
|
|
29
|
+
return {
|
|
30
|
+
profile: (request = {}) => profiles.request("GET", `${profiles.baseUrl}/${segment(store)}`, { options: request }),
|
|
31
|
+
products: (query = {}, request = {}) => catalog.request("GET", `${catalog.baseUrl}/products`, {
|
|
32
|
+
params: { ...query },
|
|
33
|
+
options: request
|
|
34
|
+
}),
|
|
35
|
+
product: (slug, request = {}) => catalog.request("GET", `${catalog.baseUrl}/products/${segment(slug)}`, { options: request }),
|
|
36
|
+
categories: async (request = {}) => {
|
|
37
|
+
return (await catalog.request("GET", `${catalog.baseUrl}/categories`, { options: request })).data;
|
|
38
|
+
},
|
|
39
|
+
deliveryDistricts: (request = {}) => catalog.request("GET", `${catalog.baseUrl}/delivery/districts`, { options: request }),
|
|
40
|
+
deliveryAreas: (district, request = {}) => catalog.request("GET", `${catalog.baseUrl}/delivery/areas`, {
|
|
41
|
+
params: { district },
|
|
42
|
+
options: request
|
|
43
|
+
}),
|
|
44
|
+
quoteDelivery: (input, request = {}) => catalog.request("POST", `${catalog.baseUrl}/orders/quote`, {
|
|
45
|
+
data: input,
|
|
46
|
+
options: request
|
|
47
|
+
}),
|
|
48
|
+
checkout: (input, request) => {
|
|
49
|
+
if (!request?.idempotencyKey?.trim()) throw new Error("idempotencyKey is required; preserve it for retries.");
|
|
50
|
+
return catalog.request("POST", `${catalog.baseUrl}/orders`, {
|
|
51
|
+
data: input,
|
|
52
|
+
options: {
|
|
53
|
+
signal: request.signal,
|
|
54
|
+
headerOptions: { "Idempotency-Key": request.idempotencyKey }
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
orderStatus: (access, request = {}) => catalog.request("GET", `${catalog.baseUrl}/orders/${segment(access.orderNumber)}/status`, { options: {
|
|
59
|
+
signal: request.signal,
|
|
60
|
+
cache: "no-store",
|
|
61
|
+
headerOptions: { "x-order-access": access.accessToken }
|
|
62
|
+
} })
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { createStorefrontClient };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { o as StorefrontOrder, s as StorefrontProduct } from "./types-C0cSi6Pk.js";
|
|
2
|
+
import { Address } from "@classytic/primitives/address";
|
|
3
|
+
import { Money } from "@classytic/primitives/money";
|
|
4
|
+
//#region src/management-types.d.ts
|
|
5
|
+
export interface ProductInput {
|
|
6
|
+
name: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
productType: 'physical';
|
|
9
|
+
status: 'draft' | 'active' | 'archived';
|
|
10
|
+
description?: string;
|
|
11
|
+
categorySlug?: string;
|
|
12
|
+
tags?: string[];
|
|
13
|
+
collections?: string[];
|
|
14
|
+
images?: StorefrontProduct['images'];
|
|
15
|
+
defaultMonetization: {
|
|
16
|
+
type: 'one_time';
|
|
17
|
+
pricing: {
|
|
18
|
+
basePrice: Money;
|
|
19
|
+
currency: string;
|
|
20
|
+
compareAtPrice?: Money;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
variationAttributes?: Array<{
|
|
24
|
+
code: string;
|
|
25
|
+
name: string;
|
|
26
|
+
values: Array<{
|
|
27
|
+
code: string;
|
|
28
|
+
label: string;
|
|
29
|
+
}>;
|
|
30
|
+
}>;
|
|
31
|
+
variants?: Array<{
|
|
32
|
+
sku: string;
|
|
33
|
+
attributes: Record<string, string>;
|
|
34
|
+
priceModifier: number;
|
|
35
|
+
isActive: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export interface MerchantProduct extends ProductInput {
|
|
39
|
+
_id: string;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
}
|
|
42
|
+
export interface CategoryInput {
|
|
43
|
+
name: string;
|
|
44
|
+
/** Omit on create to generate from the name. Slugs are immutable after creation. */
|
|
45
|
+
slug?: string;
|
|
46
|
+
parent?: string | null;
|
|
47
|
+
description?: string;
|
|
48
|
+
image?: {
|
|
49
|
+
url: string;
|
|
50
|
+
alt?: string;
|
|
51
|
+
} | null;
|
|
52
|
+
isActive?: boolean;
|
|
53
|
+
displayOrder?: number;
|
|
54
|
+
}
|
|
55
|
+
export type CategoryUpdate = Partial<Omit<CategoryInput, 'slug'>>;
|
|
56
|
+
export interface MerchantCategory extends CategoryInput {
|
|
57
|
+
_id: string;
|
|
58
|
+
slug: string;
|
|
59
|
+
parentPath?: string;
|
|
60
|
+
productCount?: number;
|
|
61
|
+
}
|
|
62
|
+
export interface MerchantCategoryTree extends MerchantCategory {
|
|
63
|
+
children: MerchantCategoryTree[];
|
|
64
|
+
totalProductCount: number;
|
|
65
|
+
}
|
|
66
|
+
export interface MerchantOrder extends StorefrontOrder {
|
|
67
|
+
_id: string;
|
|
68
|
+
customerSnapshot?: {
|
|
69
|
+
name: string;
|
|
70
|
+
phone?: string;
|
|
71
|
+
email?: string;
|
|
72
|
+
};
|
|
73
|
+
shippingAddress?: Address;
|
|
74
|
+
lines: Array<{
|
|
75
|
+
lineId: string;
|
|
76
|
+
quantity: number;
|
|
77
|
+
snapshot: {
|
|
78
|
+
name?: string;
|
|
79
|
+
sku?: string;
|
|
80
|
+
};
|
|
81
|
+
lineTotal?: Money;
|
|
82
|
+
}>;
|
|
83
|
+
paymentState?: {
|
|
84
|
+
chargeStatus: string;
|
|
85
|
+
totalCharged?: Money;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export type OrderCommand = {
|
|
89
|
+
action: 'confirm' | 'start_processing' | 'ship' | 'deliver';
|
|
90
|
+
} | {
|
|
91
|
+
action: 'cancel';
|
|
92
|
+
reason: string;
|
|
93
|
+
};
|
|
94
|
+
export interface StoreSettings {
|
|
95
|
+
version: number | null;
|
|
96
|
+
currency: string;
|
|
97
|
+
deliveryChargeMinor: number;
|
|
98
|
+
}
|
|
99
|
+
export interface MerchantMedia {
|
|
100
|
+
_id: string;
|
|
101
|
+
url: string;
|
|
102
|
+
filename: string;
|
|
103
|
+
mimeType?: string;
|
|
104
|
+
width?: number;
|
|
105
|
+
height?: number;
|
|
106
|
+
variants?: Array<{
|
|
107
|
+
name: string;
|
|
108
|
+
url: string;
|
|
109
|
+
width: number;
|
|
110
|
+
height: number;
|
|
111
|
+
mimeType: string;
|
|
112
|
+
size: number;
|
|
113
|
+
}>;
|
|
114
|
+
}
|
|
115
|
+
export interface IntegrationProvider {
|
|
116
|
+
service: string;
|
|
117
|
+
provider: string;
|
|
118
|
+
label: string;
|
|
119
|
+
oauth: boolean;
|
|
120
|
+
credentialFields: Array<{
|
|
121
|
+
name: string;
|
|
122
|
+
label: string;
|
|
123
|
+
type: 'text' | 'password' | 'number' | 'boolean' | 'select';
|
|
124
|
+
required: boolean;
|
|
125
|
+
secret?: boolean;
|
|
126
|
+
placeholder?: string;
|
|
127
|
+
help?: string;
|
|
128
|
+
options?: Array<{
|
|
129
|
+
label: string;
|
|
130
|
+
value: string;
|
|
131
|
+
}>;
|
|
132
|
+
}>;
|
|
133
|
+
}
|
|
134
|
+
export interface MerchantIntegration {
|
|
135
|
+
_id: string;
|
|
136
|
+
service: string;
|
|
137
|
+
provider: string;
|
|
138
|
+
label?: string;
|
|
139
|
+
enabled: boolean;
|
|
140
|
+
status: 'connected' | 'disabled' | 'error';
|
|
141
|
+
config: Record<string, unknown>;
|
|
142
|
+
secretsSet: Record<string, boolean>;
|
|
143
|
+
lastTestedAt?: string | null;
|
|
144
|
+
lastTestStatus?: 'ok' | 'failed' | 'skipped';
|
|
145
|
+
}
|
|
146
|
+
export interface NotificationDelivery {
|
|
147
|
+
_id: string;
|
|
148
|
+
timestamp: string;
|
|
149
|
+
event: string;
|
|
150
|
+
status: 'delivered' | 'partial' | 'failed' | 'skipped';
|
|
151
|
+
subject?: {
|
|
152
|
+
sourceModel: string;
|
|
153
|
+
sourceId: string;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export interface MerchantApiKey {
|
|
157
|
+
id: string;
|
|
158
|
+
kind: 'user' | 'service';
|
|
159
|
+
name: string;
|
|
160
|
+
prefix: string;
|
|
161
|
+
ownerId: string;
|
|
162
|
+
scopes: string[];
|
|
163
|
+
status: 'active' | 'expired' | 'revoked';
|
|
164
|
+
expiresAt: string | null;
|
|
165
|
+
lastUsedAt: string | null;
|
|
166
|
+
ceilingRole: string | null;
|
|
167
|
+
}
|
|
168
|
+
export interface ApiKeyOptions {
|
|
169
|
+
scopes: Array<{
|
|
170
|
+
scope: string;
|
|
171
|
+
requires: string[];
|
|
172
|
+
grantable: boolean;
|
|
173
|
+
}>;
|
|
174
|
+
ceilingRoles: Array<{
|
|
175
|
+
role: string;
|
|
176
|
+
scopes: string[];
|
|
177
|
+
}>;
|
|
178
|
+
can: {
|
|
179
|
+
own: boolean;
|
|
180
|
+
createService: boolean;
|
|
181
|
+
readBranch: boolean;
|
|
182
|
+
updateService: boolean;
|
|
183
|
+
deleteOthers: boolean;
|
|
184
|
+
};
|
|
185
|
+
lifetime: {
|
|
186
|
+
defaultDays: number;
|
|
187
|
+
maxDays: number;
|
|
188
|
+
allowNeverExpires?: boolean;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export interface ApiKeyInput {
|
|
192
|
+
kind: 'user' | 'service';
|
|
193
|
+
name: string;
|
|
194
|
+
scopes: string[];
|
|
195
|
+
/** Null explicitly requests a key that does not expire. */
|
|
196
|
+
expiresInDays: number | null;
|
|
197
|
+
ceilingRole?: string;
|
|
198
|
+
}
|
|
199
|
+
export interface IssuedApiKey {
|
|
200
|
+
key: string;
|
|
201
|
+
record: MerchantApiKey;
|
|
202
|
+
}
|
|
203
|
+
/** Inclusive start and exclusive end as ISO timestamps, at most 92 days apart. */
|
|
204
|
+
export interface AnalyticsWindow {
|
|
205
|
+
from: string;
|
|
206
|
+
to: string;
|
|
207
|
+
}
|
|
208
|
+
/** Order values include delivery charges and are independent of payment collection. */
|
|
209
|
+
export interface MerchantOverview {
|
|
210
|
+
summary: {
|
|
211
|
+
totalOrders: number;
|
|
212
|
+
totalOrderValue: number;
|
|
213
|
+
fulfilledOrders: number;
|
|
214
|
+
fulfilledOrderValue: number;
|
|
215
|
+
canceledOrders: number;
|
|
216
|
+
canceledOrderValue: number;
|
|
217
|
+
averageOrderValue: number;
|
|
218
|
+
};
|
|
219
|
+
statuses: Array<{
|
|
220
|
+
status: string;
|
|
221
|
+
count: number;
|
|
222
|
+
orderValue: number;
|
|
223
|
+
}>;
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { DeliveryDistrict, DeliverySettings, DeliverySettingsUpdate } from "./delivery-types.js";
|
|
2
|
+
import { AnalyticsWindow, ApiKeyInput, ApiKeyOptions, CategoryInput, IntegrationProvider, IssuedApiKey, MerchantApiKey, MerchantCategory, MerchantCategoryTree, MerchantIntegration, MerchantMedia, MerchantOrder, MerchantOverview, MerchantProduct, NotificationDelivery, OrderCommand, ProductInput, StoreSettings } from "./management-types.js";
|
|
3
|
+
import { BaseApi } from "@classytic/arc-next/api";
|
|
4
|
+
import { ArcClientConfig } from "@classytic/arc-next/client";
|
|
5
|
+
//#region src/management.d.ts
|
|
6
|
+
export interface MerchantClientOptions {
|
|
7
|
+
apiUrl: string;
|
|
8
|
+
token: string;
|
|
9
|
+
organizationId: string;
|
|
10
|
+
fetch?: ArcClientConfig['fetch'];
|
|
11
|
+
}
|
|
12
|
+
/** Capture one authenticated store; callers create a new instance when switching stores. */
|
|
13
|
+
export declare function createMerchantClient(options: MerchantClientOptions): {
|
|
14
|
+
integrations: {
|
|
15
|
+
catalog: (signal?: AbortSignal) => Promise<IntegrationProvider[]>;
|
|
16
|
+
list: (signal?: AbortSignal) => Promise<MerchantIntegration[]>;
|
|
17
|
+
connect: (data: {
|
|
18
|
+
service: string;
|
|
19
|
+
provider: string;
|
|
20
|
+
label: string;
|
|
21
|
+
credentials: Record<string, unknown>;
|
|
22
|
+
enable: boolean;
|
|
23
|
+
}) => Promise<MerchantIntegration>;
|
|
24
|
+
command: (id: string, action: 'test' | 'enable' | 'disable') => Promise<MerchantIntegration>;
|
|
25
|
+
remove: (id: string) => Promise<null>;
|
|
26
|
+
};
|
|
27
|
+
notifications: {
|
|
28
|
+
list: ({ token, organizationId, params, options }?: {
|
|
29
|
+
token?: string | null;
|
|
30
|
+
organizationId?: string | null;
|
|
31
|
+
params?: import("@classytic/arc-next/api").QueryParams;
|
|
32
|
+
options?: Omit<import("@classytic/arc-next/api").RequestOptions, "token" | "organizationId">;
|
|
33
|
+
}) => Promise<import("@classytic/repo-core/pagination").PaginatedResult<NotificationDelivery>>;
|
|
34
|
+
};
|
|
35
|
+
apiKeys: {
|
|
36
|
+
options: (signal?: AbortSignal) => Promise<ApiKeyOptions>;
|
|
37
|
+
list: (owner: 'me' | 'branch', signal?: AbortSignal) => Promise<MerchantApiKey[]>;
|
|
38
|
+
create: (data: ApiKeyInput) => Promise<IssuedApiKey>;
|
|
39
|
+
rotate: (id: string) => Promise<IssuedApiKey>;
|
|
40
|
+
revoke: (id: string) => Promise<MerchantApiKey>;
|
|
41
|
+
};
|
|
42
|
+
delivery: {
|
|
43
|
+
read: (signal?: AbortSignal) => Promise<DeliverySettings>;
|
|
44
|
+
update: (data: DeliverySettingsUpdate) => Promise<DeliverySettings>;
|
|
45
|
+
districts(signal?: AbortSignal): Promise<DeliveryDistrict[]>;
|
|
46
|
+
};
|
|
47
|
+
products: BaseApi<MerchantProduct, ProductInput, Partial<ProductInput>>;
|
|
48
|
+
categories: BaseApi<MerchantCategory, CategoryInput, Partial<Omit<CategoryInput, "slug">>> & {
|
|
49
|
+
tree: (options?: {
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
}) => Promise<MerchantCategoryTree[]>;
|
|
52
|
+
};
|
|
53
|
+
analytics: {
|
|
54
|
+
overview(window: AnalyticsWindow, options?: {
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
}): Promise<MerchantOverview>;
|
|
57
|
+
};
|
|
58
|
+
orders: {
|
|
59
|
+
collect(id: string, amount: number, idempotencyKey: string): Promise<{
|
|
60
|
+
outstanding: number;
|
|
61
|
+
settled: boolean;
|
|
62
|
+
transactionId?: string;
|
|
63
|
+
}>;
|
|
64
|
+
list: ({ token, organizationId, params, options }?: {
|
|
65
|
+
token?: string | null;
|
|
66
|
+
organizationId?: string | null;
|
|
67
|
+
params?: import("@classytic/arc-next/api").QueryParams;
|
|
68
|
+
options?: Omit<import("@classytic/arc-next/api").RequestOptions, "token" | "organizationId">;
|
|
69
|
+
}) => Promise<import("@classytic/repo-core/pagination").PaginatedResult<MerchantOrder>>;
|
|
70
|
+
detail: (orderNumber: string, options?: {
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
}) => Promise<MerchantOrder>;
|
|
73
|
+
command(id: string, command: OrderCommand, idempotencyKey: string): Promise<MerchantOrder>;
|
|
74
|
+
};
|
|
75
|
+
settings: {
|
|
76
|
+
get: () => Promise<StoreSettings>;
|
|
77
|
+
update: (data: {
|
|
78
|
+
expectedVersion: number | null;
|
|
79
|
+
deliveryChargeMinor: number;
|
|
80
|
+
}) => Promise<StoreSettings>;
|
|
81
|
+
};
|
|
82
|
+
media: {
|
|
83
|
+
list: ({ token, organizationId, params, options }?: {
|
|
84
|
+
token?: string | null;
|
|
85
|
+
organizationId?: string | null;
|
|
86
|
+
params?: import("@classytic/arc-next/api").QueryParams;
|
|
87
|
+
options?: Omit<import("@classytic/arc-next/api").RequestOptions, "token" | "organizationId">;
|
|
88
|
+
}) => Promise<import("@classytic/repo-core/pagination").PaginatedResult<MerchantMedia>>;
|
|
89
|
+
upload: (file: File, folder?: 'products' | 'categories') => Promise<MerchantMedia>;
|
|
90
|
+
uploadImage(file: File, folder?: 'products' | 'categories'): Promise<MerchantMedia>;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
export type MerchantClient = ReturnType<typeof createMerchantClient>;
|
|
94
|
+
//#endregion
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { BaseApi } from "@classytic/arc-next/api";
|
|
2
|
+
import { createClient } from "@classytic/arc-next/client";
|
|
3
|
+
//#region src/management-delivery.ts
|
|
4
|
+
function createMerchantDelivery(client) {
|
|
5
|
+
const api = new BaseApi("stores/delivery", {
|
|
6
|
+
client,
|
|
7
|
+
basePath: "",
|
|
8
|
+
cache: "no-store"
|
|
9
|
+
});
|
|
10
|
+
return { delivery: {
|
|
11
|
+
read: (signal) => api.request("GET", `${api.baseUrl}/current`, { options: { signal } }),
|
|
12
|
+
update: (data) => api.request("PATCH", `${api.baseUrl}/current`, { data }),
|
|
13
|
+
async districts(signal) {
|
|
14
|
+
return (await api.request("GET", `${api.baseUrl}/districts`, { options: { signal } })).data;
|
|
15
|
+
}
|
|
16
|
+
} };
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/management-access.ts
|
|
20
|
+
function key(value) {
|
|
21
|
+
if (!value || value === "." || value === "..") throw new Error("An identifier is required.");
|
|
22
|
+
return encodeURIComponent(value);
|
|
23
|
+
}
|
|
24
|
+
function createMerchantAccess(client) {
|
|
25
|
+
const config = {
|
|
26
|
+
client,
|
|
27
|
+
basePath: "",
|
|
28
|
+
cache: "no-store"
|
|
29
|
+
};
|
|
30
|
+
const integrations = new BaseApi("integrations", config);
|
|
31
|
+
const apiKeys = new BaseApi("auth/api-keys", config);
|
|
32
|
+
const deliveries = new BaseApi("notifications/delivery-log", config);
|
|
33
|
+
return {
|
|
34
|
+
integrations: {
|
|
35
|
+
catalog: (signal) => integrations.request("GET", `${integrations.baseUrl}/catalog`, { options: { signal } }),
|
|
36
|
+
list: (signal) => integrations.request("GET", integrations.baseUrl, { options: { signal } }),
|
|
37
|
+
connect: (data) => integrations.request("POST", integrations.baseUrl, { data }),
|
|
38
|
+
command: (id, action) => integrations.dispatchAction({
|
|
39
|
+
id: key(id),
|
|
40
|
+
action
|
|
41
|
+
}),
|
|
42
|
+
remove: (id) => integrations.request("DELETE", `${integrations.baseUrl}/${key(id)}`)
|
|
43
|
+
},
|
|
44
|
+
notifications: { list: deliveries.getAll.bind(deliveries) },
|
|
45
|
+
apiKeys: {
|
|
46
|
+
options: (signal) => apiKeys.request("GET", `${apiKeys.baseUrl}/options`, { options: { signal } }),
|
|
47
|
+
list: (owner, signal) => apiKeys.request("GET", apiKeys.baseUrl, {
|
|
48
|
+
params: { owner },
|
|
49
|
+
options: { signal }
|
|
50
|
+
}),
|
|
51
|
+
create: (data) => apiKeys.request("POST", apiKeys.baseUrl, { data }),
|
|
52
|
+
rotate: (id) => apiKeys.dispatchAction({
|
|
53
|
+
id: key(id),
|
|
54
|
+
action: "rotate"
|
|
55
|
+
}),
|
|
56
|
+
revoke: (id) => apiKeys.dispatchAction({
|
|
57
|
+
id: key(id),
|
|
58
|
+
action: "revoke"
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/management-media.ts
|
|
65
|
+
function createMerchantMedia(client, fetcher) {
|
|
66
|
+
const media = new BaseApi("media", {
|
|
67
|
+
client,
|
|
68
|
+
basePath: "",
|
|
69
|
+
cache: "no-store"
|
|
70
|
+
});
|
|
71
|
+
async function put(target, blob) {
|
|
72
|
+
const headers = new Headers(target.headers);
|
|
73
|
+
if (!headers.has("content-type")) headers.set("content-type", blob.type);
|
|
74
|
+
if (!(await (fetcher ?? fetch)(target.uploadUrl, {
|
|
75
|
+
method: "PUT",
|
|
76
|
+
credentials: "omit",
|
|
77
|
+
headers,
|
|
78
|
+
signal: AbortSignal.timeout(6e4),
|
|
79
|
+
body: blob
|
|
80
|
+
})).ok) throw new Error("Image upload failed. Please retry.");
|
|
81
|
+
}
|
|
82
|
+
async function upload(file, folder, processed, originalFilename) {
|
|
83
|
+
const derivatives = processed?.derivatives ?? [];
|
|
84
|
+
const started = await media.request("POST", `${media.baseUrl}/start-write`, { data: {
|
|
85
|
+
filename: file.name,
|
|
86
|
+
contentType: file.type,
|
|
87
|
+
size: file.size,
|
|
88
|
+
folder,
|
|
89
|
+
...derivatives.length ? { derivatives: derivatives.map((item) => ({
|
|
90
|
+
name: item.name,
|
|
91
|
+
contentType: item.blob.type,
|
|
92
|
+
size: item.bytes
|
|
93
|
+
})) } : {}
|
|
94
|
+
} });
|
|
95
|
+
if (started.kind === "dedup") return started.media;
|
|
96
|
+
if (started.kind !== "presigned") throw new Error("Choose a smaller product image for direct upload.");
|
|
97
|
+
const targets = derivatives.map((derivative) => {
|
|
98
|
+
const target = started.derivativeUploads?.find((item) => item.name === derivative.name)?.upload;
|
|
99
|
+
if (!target) throw new Error("The server did not provide every requested image size. Please retry.");
|
|
100
|
+
return {
|
|
101
|
+
derivative,
|
|
102
|
+
target
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
await put(started.upload, file);
|
|
106
|
+
await Promise.all(targets.map(({ derivative, target }) => put(target, derivative.blob)));
|
|
107
|
+
const hints = processed ? (await import("@classytic/media-transform/image/uploadHints")).toUploadHints(processed) : {};
|
|
108
|
+
return media.request("POST", `${media.baseUrl}/complete-write`, { data: {
|
|
109
|
+
key: started.upload.key,
|
|
110
|
+
filename: file.name,
|
|
111
|
+
mimeType: file.type,
|
|
112
|
+
size: file.size,
|
|
113
|
+
...originalFilename ? { originalFilename } : {},
|
|
114
|
+
...hints,
|
|
115
|
+
...targets.length ? { variants: targets.map(({ derivative, target }) => ({
|
|
116
|
+
name: derivative.name,
|
|
117
|
+
key: target.key,
|
|
118
|
+
mimeType: derivative.blob.type,
|
|
119
|
+
size: derivative.bytes,
|
|
120
|
+
width: derivative.width,
|
|
121
|
+
height: derivative.height
|
|
122
|
+
})) } : {}
|
|
123
|
+
} });
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
list: media.getAll.bind(media),
|
|
127
|
+
upload: (file, folder = "products") => upload(file, folder),
|
|
128
|
+
async uploadImage(file, folder = "products") {
|
|
129
|
+
const policy = await media.request("GET", `${media.baseUrl}/transform-policy`);
|
|
130
|
+
const { resolveTransformPlan } = await import("@classytic/media-transform/policy");
|
|
131
|
+
const plan = resolveTransformPlan({
|
|
132
|
+
folder,
|
|
133
|
+
mimeType: file.type,
|
|
134
|
+
bytes: file.size
|
|
135
|
+
}, policy);
|
|
136
|
+
if (!plan.transform) {
|
|
137
|
+
if (plan.outcome === "preserve") return upload(file, folder);
|
|
138
|
+
throw new Error("The image processing policy is unsupported. Refresh the dashboard before uploading.");
|
|
139
|
+
}
|
|
140
|
+
const [{ createBrowserImageAdapter }, { compressImageSet }, { storedFilename }] = await Promise.all([
|
|
141
|
+
import("@classytic/media-transform/image/BrowserImageAdapter"),
|
|
142
|
+
import("@classytic/media-transform/image/compressImageSet"),
|
|
143
|
+
import("@classytic/media-transform/image/uploadHints")
|
|
144
|
+
]);
|
|
145
|
+
const result = await compressImageSet(await createBrowserImageAdapter(), file, plan.options);
|
|
146
|
+
return upload(new File([result.blob], storedFilename(file.name, result.format), { type: result.blob.type }), folder, result, file.name);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/management.ts
|
|
152
|
+
function orderKey(value) {
|
|
153
|
+
if (!value || value === "." || value === "..") throw new Error("An order number is required.");
|
|
154
|
+
return encodeURIComponent(value);
|
|
155
|
+
}
|
|
156
|
+
/** Capture one authenticated store; callers create a new instance when switching stores. */
|
|
157
|
+
function createMerchantClient(options) {
|
|
158
|
+
const { token, organizationId, fetch: fetcher } = options;
|
|
159
|
+
const url = new URL(options.apiUrl);
|
|
160
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("A valid HTTP(S) API mount is required.");
|
|
161
|
+
if (!token.trim() || !organizationId.trim()) throw new Error("A bearer token and organizationId are required.");
|
|
162
|
+
const client = createClient({
|
|
163
|
+
baseUrl: url.href.replace(/\/+$/, ""),
|
|
164
|
+
getToken: () => token,
|
|
165
|
+
getOrgId: () => organizationId,
|
|
166
|
+
credentials: "omit",
|
|
167
|
+
autoIdempotency: false,
|
|
168
|
+
timeoutMs: 15e3,
|
|
169
|
+
...fetcher ? { fetch: fetcher } : {}
|
|
170
|
+
});
|
|
171
|
+
const config = {
|
|
172
|
+
client,
|
|
173
|
+
basePath: "",
|
|
174
|
+
cache: "no-store"
|
|
175
|
+
};
|
|
176
|
+
const products = new BaseApi("products", config);
|
|
177
|
+
const categories = new BaseApi("categories", config);
|
|
178
|
+
const orders = new BaseApi("orders", config);
|
|
179
|
+
const stores = new BaseApi("stores", config);
|
|
180
|
+
return {
|
|
181
|
+
...createMerchantAccess(client),
|
|
182
|
+
...createMerchantDelivery(client),
|
|
183
|
+
products,
|
|
184
|
+
categories: Object.assign(categories, { tree: (options = {}) => categories.request("GET", `${categories.baseUrl}/tree`, { options }) }),
|
|
185
|
+
analytics: { async overview(window, options = {}) {
|
|
186
|
+
const from = new Date(window.from);
|
|
187
|
+
const to = new Date(window.to);
|
|
188
|
+
if (!Number.isFinite(from.getTime()) || !Number.isFinite(to.getTime()) || to <= from || to.getTime() - from.getTime() > 79488e5) throw new Error("Choose a valid analytics window of at most 92 days.");
|
|
189
|
+
const statuses = (await orders.request("GET", `${orders.baseUrl}/aggregations/revenueByStatus`, {
|
|
190
|
+
params: {
|
|
191
|
+
"createdAt[gte]": from.toISOString(),
|
|
192
|
+
"createdAt[lt]": to.toISOString()
|
|
193
|
+
},
|
|
194
|
+
options
|
|
195
|
+
})).rows.map(({ status, count, revenue }) => ({
|
|
196
|
+
status,
|
|
197
|
+
count,
|
|
198
|
+
orderValue: revenue
|
|
199
|
+
}));
|
|
200
|
+
const summary = {
|
|
201
|
+
totalOrders: 0,
|
|
202
|
+
totalOrderValue: 0,
|
|
203
|
+
fulfilledOrders: 0,
|
|
204
|
+
fulfilledOrderValue: 0,
|
|
205
|
+
canceledOrders: 0,
|
|
206
|
+
canceledOrderValue: 0,
|
|
207
|
+
averageOrderValue: 0
|
|
208
|
+
};
|
|
209
|
+
for (const row of statuses) {
|
|
210
|
+
summary.totalOrders += row.count;
|
|
211
|
+
summary.totalOrderValue += row.orderValue;
|
|
212
|
+
if (row.status === "fulfilled" || row.status === "completed") {
|
|
213
|
+
summary.fulfilledOrders += row.count;
|
|
214
|
+
summary.fulfilledOrderValue += row.orderValue;
|
|
215
|
+
}
|
|
216
|
+
if (row.status === "canceled") {
|
|
217
|
+
summary.canceledOrders += row.count;
|
|
218
|
+
summary.canceledOrderValue += row.orderValue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
summary.averageOrderValue = summary.totalOrders ? Math.round(summary.totalOrderValue / summary.totalOrders) : 0;
|
|
222
|
+
return {
|
|
223
|
+
summary,
|
|
224
|
+
statuses
|
|
225
|
+
};
|
|
226
|
+
} },
|
|
227
|
+
orders: {
|
|
228
|
+
collect(id, amount, idempotencyKey) {
|
|
229
|
+
if (!idempotencyKey.trim()) throw new Error("Preserve an explicit idempotencyKey for collection retries.");
|
|
230
|
+
return orders.dispatchAction({
|
|
231
|
+
id: orderKey(id),
|
|
232
|
+
action: "collect_payment",
|
|
233
|
+
data: { amount },
|
|
234
|
+
options: { headerOptions: { "Idempotency-Key": idempotencyKey } }
|
|
235
|
+
});
|
|
236
|
+
},
|
|
237
|
+
list: orders.getAll.bind(orders),
|
|
238
|
+
detail: (orderNumber, options = {}) => orders.getById({
|
|
239
|
+
id: orderKey(orderNumber),
|
|
240
|
+
options
|
|
241
|
+
}),
|
|
242
|
+
command(id, command, idempotencyKey) {
|
|
243
|
+
if (!idempotencyKey.trim()) throw new Error("Preserve an explicit idempotencyKey for command retries.");
|
|
244
|
+
const { action, ...data } = command;
|
|
245
|
+
return orders.dispatchAction({
|
|
246
|
+
id: orderKey(id),
|
|
247
|
+
action,
|
|
248
|
+
data,
|
|
249
|
+
options: { headerOptions: { "Idempotency-Key": idempotencyKey } }
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
settings: {
|
|
254
|
+
get: () => stores.request("GET", `${stores.baseUrl}/settings/current`),
|
|
255
|
+
update: (data) => stores.request("PATCH", `${stores.baseUrl}/settings/current`, { data })
|
|
256
|
+
},
|
|
257
|
+
media: createMerchantMedia(client, fetcher)
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
export { createMerchantClient };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Address } from "@classytic/primitives/address";
|
|
2
|
+
import { Money } from "@classytic/primitives/money";
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
/** Public wire projections; merchant-only catalog and buyer fields are absent. */
|
|
5
|
+
interface StorefrontProduct {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
categorySlug?: string;
|
|
11
|
+
tags?: string[];
|
|
12
|
+
collections?: string[];
|
|
13
|
+
onSale?: boolean;
|
|
14
|
+
images?: Array<{
|
|
15
|
+
url: string;
|
|
16
|
+
alt?: string;
|
|
17
|
+
order?: number;
|
|
18
|
+
isFeatured?: boolean;
|
|
19
|
+
variants?: {
|
|
20
|
+
thumbnail?: string;
|
|
21
|
+
medium?: string;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
monetization?: {
|
|
25
|
+
type: 'one_time';
|
|
26
|
+
basePrice: Money;
|
|
27
|
+
compareAtPrice?: Money;
|
|
28
|
+
};
|
|
29
|
+
variants?: Array<{
|
|
30
|
+
sku: string;
|
|
31
|
+
attributes: Record<string, string>;
|
|
32
|
+
priceModifier: number;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
interface StorefrontCategory {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
slug: string;
|
|
39
|
+
parent?: string | null;
|
|
40
|
+
parentPath?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
image?: {
|
|
43
|
+
url: string;
|
|
44
|
+
alt?: string;
|
|
45
|
+
} | null;
|
|
46
|
+
displayOrder?: number;
|
|
47
|
+
}
|
|
48
|
+
interface StorefrontProductPage {
|
|
49
|
+
data: StorefrontProduct[];
|
|
50
|
+
next: string | null;
|
|
51
|
+
}
|
|
52
|
+
interface StorefrontProductQuery {
|
|
53
|
+
limit?: number;
|
|
54
|
+
/** Opaque cursor returned by the preceding page; keep filters and sort unchanged. */
|
|
55
|
+
after?: string;
|
|
56
|
+
category?: string;
|
|
57
|
+
q?: string;
|
|
58
|
+
tag?: string;
|
|
59
|
+
collection?: string;
|
|
60
|
+
onSale?: boolean;
|
|
61
|
+
/** Inclusive base-price bounds in the store currency's integer minor units. */
|
|
62
|
+
minPrice?: number;
|
|
63
|
+
maxPrice?: number;
|
|
64
|
+
sort?: 'newest' | 'price-asc' | 'price-desc';
|
|
65
|
+
}
|
|
66
|
+
interface StorefrontProfile {
|
|
67
|
+
organizationId: string;
|
|
68
|
+
name: string;
|
|
69
|
+
slug: string;
|
|
70
|
+
status: 'active';
|
|
71
|
+
currency: string;
|
|
72
|
+
deliveryChargeMinor: number;
|
|
73
|
+
}
|
|
74
|
+
interface GuestCheckoutInput {
|
|
75
|
+
lines: Array<{
|
|
76
|
+
productId: string;
|
|
77
|
+
variantSku?: string;
|
|
78
|
+
quantity: number;
|
|
79
|
+
}>;
|
|
80
|
+
customer: {
|
|
81
|
+
name: string;
|
|
82
|
+
phone: string;
|
|
83
|
+
email?: string;
|
|
84
|
+
};
|
|
85
|
+
shippingAddress: Address & {
|
|
86
|
+
areaId?: number;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
interface StorefrontOrder {
|
|
90
|
+
orderNumber: string;
|
|
91
|
+
status: string;
|
|
92
|
+
fulfillmentStatus?: 'unfulfilled' | 'partial' | 'fulfilled' | 'returned';
|
|
93
|
+
fulfillmentSummary?: {
|
|
94
|
+
total: number;
|
|
95
|
+
fulfilled: number;
|
|
96
|
+
shipped: number;
|
|
97
|
+
delivered: number;
|
|
98
|
+
returned: number;
|
|
99
|
+
};
|
|
100
|
+
paymentTerm?: 'prepaid' | 'collect_on_delivery';
|
|
101
|
+
currency: string;
|
|
102
|
+
totals: {
|
|
103
|
+
subtotal: Money;
|
|
104
|
+
discount: Money;
|
|
105
|
+
tax: Money;
|
|
106
|
+
shipping: Money;
|
|
107
|
+
grandTotal: Money;
|
|
108
|
+
taxBasis?: 'inclusive' | 'exclusive';
|
|
109
|
+
};
|
|
110
|
+
createdAt: string;
|
|
111
|
+
updatedAt: string;
|
|
112
|
+
}
|
|
113
|
+
type GuestCheckoutResult = StorefrontOrder & {
|
|
114
|
+
accessToken: string;
|
|
115
|
+
};
|
|
116
|
+
interface GuestOrderAccess {
|
|
117
|
+
orderNumber: string;
|
|
118
|
+
accessToken: string;
|
|
119
|
+
}
|
|
120
|
+
interface StorefrontRequestOptions {
|
|
121
|
+
signal?: AbortSignal;
|
|
122
|
+
}
|
|
123
|
+
interface GuestCheckoutOptions extends StorefrontRequestOptions {
|
|
124
|
+
/** Create once per checkout attempt; preserve across retries and lost responses. */
|
|
125
|
+
idempotencyKey: string;
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
export { StorefrontCategory as a, StorefrontProductPage as c, StorefrontRequestOptions as d, GuestOrderAccess as i, StorefrontProductQuery as l, GuestCheckoutOptions as n, StorefrontOrder as o, GuestCheckoutResult as r, StorefrontProduct as s, GuestCheckoutInput as t, StorefrontProfile as u };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as StorefrontCategory, c as StorefrontProductPage, d as StorefrontRequestOptions, i as GuestOrderAccess, l as StorefrontProductQuery, n as GuestCheckoutOptions, o as StorefrontOrder, r as GuestCheckoutResult, s as StorefrontProduct, t as GuestCheckoutInput, u as StorefrontProfile } from "./types-C0cSi6Pk.js";
|
|
2
|
+
export { GuestCheckoutInput, GuestCheckoutOptions, GuestCheckoutResult, GuestOrderAccess, StorefrontCategory, StorefrontOrder, StorefrontProduct, StorefrontProductPage, StorefrontProductQuery, StorefrontProfile, StorefrontRequestOptions };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/management.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Merchant client
|
|
2
|
+
|
|
3
|
+
Import `createMerchantClient` from `@classytic/commerce-client/management`.
|
|
4
|
+
Its contracts live in `/management/types`. The guest-only root entry imports
|
|
5
|
+
none of this authenticated surface.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createMerchantClient } from '@classytic/commerce-client/management';
|
|
9
|
+
|
|
10
|
+
const merchant = createMerchantClient({
|
|
11
|
+
apiUrl: 'https://api.example.com/api/v1',
|
|
12
|
+
token: merchantSessionToken,
|
|
13
|
+
organizationId: selectedStoreId,
|
|
14
|
+
});
|
|
15
|
+
const products = await merchant.products.getAll({ params: { page: 1, limit: 20 } });
|
|
16
|
+
const categories = await merchant.categories.tree();
|
|
17
|
+
const orders = await merchant.orders.list();
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Each instance captures one bearer credential and store. Create another instance
|
|
21
|
+
on session or store changes. Cookies and automatic command keys are disabled.
|
|
22
|
+
The server authorizes every operation; choosing a store ID grants no access.
|
|
23
|
+
|
|
24
|
+
Product/category APIs use Arc Next's typed CRUD methods. Order writes expose
|
|
25
|
+
only supported commands (`confirm`, `start_processing`, `ship`, `deliver`,
|
|
26
|
+
`cancel`) and cash collection. Generate a key once per command attempt and keep
|
|
27
|
+
it, its amount and its body unchanged across retries.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const key = crypto.randomUUID();
|
|
31
|
+
await merchant.orders.command(orderNumber, { action: 'confirm' }, key);
|
|
32
|
+
|
|
33
|
+
// After complete delivery and actual receipt of cash, with the collect permission:
|
|
34
|
+
const collectionKey = crypto.randomUUID();
|
|
35
|
+
await merchant.orders.collect(orderNumber, amountMinor, collectionKey);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`media.upload(file)` uses the shared start-write → storage PUT → complete-write
|
|
39
|
+
protocol. Merchant authorization goes to the API; the storage PUT receives only
|
|
40
|
+
its presigned headers and file bytes. A failed PUT does not confirm an upload.
|
|
41
|
+
Settings updates carry `expectedVersion`; refetch after a conflict before saving.
|
|
42
|
+
|
|
43
|
+
`integrations.catalog()` describes enabled providers and credential fields.
|
|
44
|
+
`integrations.connect()` stores/tests a connection for the authenticated store;
|
|
45
|
+
`command(id, 'test' | 'enable' | 'disable')` controls it and `remove(id)` disconnects
|
|
46
|
+
it. `notifications.list()` reads paginated delivery evidence. These routes may be
|
|
47
|
+
absent when the deployment has no notification credential encryption key.
|
|
48
|
+
|
|
49
|
+
`apiKeys.options()` returns live scope grants, ownership permissions and lifetime
|
|
50
|
+
bounds. Use them to construct `apiKeys.create(input)`. `apiKeys.list('me' | 'branch')`
|
|
51
|
+
returns safe records; `rotate(id)` returns a replacement secret and `revoke(id)`
|
|
52
|
+
disables one. Creation/rotation require a fresh bearer session. Store the returned
|
|
53
|
+
secret on your server: it is shown once, cannot be read later and should never be
|
|
54
|
+
placed in query caches or browser storage. These mutations have no automatic
|
|
55
|
+
retry; refresh the key list after an uncertain outcome before retrying.
|
|
56
|
+
|
|
57
|
+
`analytics.overview({ from, to }, { signal })` reads one store-scoped order
|
|
58
|
+
aggregation. Supply ISO timestamps for an inclusive start and exclusive end,
|
|
59
|
+
at most 92 days apart. Read the configured currency through `settings.get()`.
|
|
60
|
+
Amounts are integer minor units; the rounded average uses the same unit.
|
|
61
|
+
|
|
62
|
+
The summary reports order count/value, fulfilled count/value, canceled
|
|
63
|
+
count/value and average order value, alongside the individual status rows.
|
|
64
|
+
Total order value includes every status and delivery charges. Fulfilled combines
|
|
65
|
+
`fulfilled` and `completed`: shipment can fulfill an order before delivery.
|
|
66
|
+
Refunded remains a separate status. These metrics do not measure received cash
|
|
67
|
+
or prove delivery. Order-read permission is required, including `read:orders`
|
|
68
|
+
for API keys; organization IDs in query parameters cannot widen the store scope.
|
|
69
|
+
|
|
70
|
+
## Category hierarchy
|
|
71
|
+
|
|
72
|
+
`categories.tree({ signal })` returns nested categories with `children` and
|
|
73
|
+
`totalProductCount`. `parent` is the parent category's slug; `null` moves a category
|
|
74
|
+
to the top level. Slugs are permanent after creation. Omit a blank create slug to
|
|
75
|
+
generate it from the name; update names without resending a slug.
|
|
76
|
+
|
|
77
|
+
Import `categoryChoices` from `@classytic/commerce-client/category-tree` for
|
|
78
|
+
breadcrumb labels. Pass the edited slug as its second argument to exclude that
|
|
79
|
+
category and all its descendants from parent choices.
|
|
80
|
+
|
|
81
|
+
`media.uploadImage(file, 'categories')` optimizes a category image using the
|
|
82
|
+
category-folder policy. Send `image: null` to clear its reference; detached-media
|
|
83
|
+
cleanup follows the server's normal governance policy.
|
|
84
|
+
|
|
85
|
+
Product writes also accept `tags`, `collections` and
|
|
86
|
+
`defaultMonetization.pricing.compareAtPrice`. The catalog derives `onSale`; clients
|
|
87
|
+
never set it. Tags and collections use the existing catalog arrays, with no
|
|
88
|
+
additional collection-management service.
|
package/package.json
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/commerce-client",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed storefront and merchant clients for the Classytic Commerce API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./types": {
|
|
15
|
+
"types": "./dist/types.d.ts",
|
|
16
|
+
"default": "./dist/types.js"
|
|
17
|
+
},
|
|
18
|
+
"./management": {
|
|
19
|
+
"types": "./dist/management.d.ts",
|
|
20
|
+
"default": "./dist/management.js"
|
|
21
|
+
},
|
|
22
|
+
"./management/types": {
|
|
23
|
+
"types": "./dist/management-types.d.ts",
|
|
24
|
+
"default": "./dist/management-types.js"
|
|
25
|
+
},
|
|
26
|
+
"./delivery-types": {
|
|
27
|
+
"types": "./dist/delivery-types.d.ts",
|
|
28
|
+
"default": "./dist/delivery-types.js"
|
|
29
|
+
},
|
|
30
|
+
"./category-tree": {
|
|
31
|
+
"types": "./dist/category-tree.d.ts",
|
|
32
|
+
"default": "./dist/category-tree.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md",
|
|
38
|
+
"CHANGELOG.md",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
"management.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsdown",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
47
|
+
"push": "classytic-push",
|
|
48
|
+
"release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
|
|
49
|
+
"release": "npm run push -- main && npm run release:tag && npm publish"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@classytic/arc-next": "^0.18.0",
|
|
53
|
+
"@classytic/media-transform": "^0.3.0",
|
|
54
|
+
"@classytic/primitives": "^0.34.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@classytic/dev-tools": "^0.2.1",
|
|
58
|
+
"@types/node": "^26.6.5",
|
|
59
|
+
"esbuild": "^0.28.1",
|
|
60
|
+
"publint": "^0.3.25",
|
|
61
|
+
"tsdown": "^0.23.1",
|
|
62
|
+
"typescript": "^7.0.2",
|
|
63
|
+
"vitest": "^5.0.5"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/classytic/commerce-client.git"
|
|
72
|
+
},
|
|
73
|
+
"bugs": {
|
|
74
|
+
"url": "https://github.com/classytic/commerce-client/issues"
|
|
75
|
+
},
|
|
76
|
+
"homepage": "https://github.com/classytic/commerce-client#readme"
|
|
77
|
+
}
|