@sixtyninecommerce/admin 0.11.0 → 0.13.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/README.md +1509 -28
- package/dist/auth/index.d.ts +7 -1
- package/dist/auth/index.js +8 -1
- package/dist/catalog/documents.d.ts +142 -0
- package/dist/catalog/documents.js +114 -0
- package/dist/catalog/index.d.ts +48 -7
- package/dist/catalog/index.js +6 -1
- package/dist/catalog/metadata.d.ts +244 -0
- package/dist/catalog/metadata.js +220 -0
- package/dist/catalog/properties.d.ts +38 -0
- package/dist/catalog/properties.js +52 -1
- package/dist/catalog/recommendations.d.ts +33 -6
- package/dist/catalog/recommendations.js +94 -2
- package/dist/catalog/reviews.d.ts +17 -4
- package/dist/catalog/reviews.js +7 -0
- package/dist/catalog/variants.d.ts +60 -0
- package/dist/catalog/variants.js +39 -0
- package/dist/catalog/visibility.d.ts +18 -2
- package/dist/client.d.ts +29 -3
- package/dist/client.js +19 -8
- package/dist/content/assets.d.ts +3 -4
- package/dist/content/forms.d.ts +332 -1
- package/dist/content/forms.js +140 -4
- package/dist/content/items.d.ts +31 -8
- package/dist/content/items.js +1 -1
- package/dist/content/menus.d.ts +254 -0
- package/dist/content/menus.js +70 -0
- package/dist/content/strings.d.ts +137 -0
- package/dist/content/strings.js +82 -0
- package/dist/customers/delivery-terms.d.ts +44 -0
- package/dist/customers/delivery-terms.js +32 -0
- package/dist/customers/groups.d.ts +16 -5
- package/dist/customers/groups.js +6 -2
- package/dist/customers/index.d.ts +15 -0
- package/dist/customers/index.js +4 -0
- package/dist/documents/emails.d.ts +154 -0
- package/dist/documents/emails.js +220 -0
- package/dist/documents/index.d.ts +13 -0
- package/dist/documents/index.js +2 -0
- package/dist/fulfillments/create.d.ts +1 -1
- package/dist/fulfillments/create.js +5 -2
- package/dist/fulfillments/index.d.ts +119 -1
- package/dist/fulfillments/index.js +124 -4
- package/dist/fulfillments/warehouse.d.ts +29 -17
- package/dist/fulfillments/warehouse.js +32 -1
- package/dist/index.d.ts +43 -23
- package/dist/index.js +11 -5
- package/dist/integrations/exact-online-types.d.ts +542 -0
- package/dist/integrations/exact-online.d.ts +541 -5
- package/dist/integrations/exact-online.js +714 -1
- package/dist/integrations/index.d.ts +23 -8
- package/dist/integrations/index.js +9 -4
- package/dist/integrations/pipeline-discoveries.d.ts +65 -0
- package/dist/integrations/pipeline-discoveries.js +65 -0
- package/dist/integrations/pipeline-runs.d.ts +119 -0
- package/dist/integrations/pipeline-runs.js +87 -0
- package/dist/integrations/pipelines.d.ts +73 -2
- package/dist/integrations/pipelines.js +36 -1
- package/dist/integrations/price-import.d.ts +223 -0
- package/dist/integrations/price-import.js +124 -0
- package/dist/integrations/types.d.ts +221 -3
- package/dist/integrations/types.js +4 -3
- package/dist/meta/index.d.ts +10 -0
- package/dist/metafields/index.d.ts +8 -1
- package/dist/metafields/index.js +10 -0
- package/dist/orders/cancel-with-money.d.ts +10 -0
- package/dist/orders/cancel-with-money.js +2 -0
- package/dist/orders/commit-edit.d.ts +16 -0
- package/dist/orders/commit-edit.js +14 -1
- package/dist/orders/deposit-policy.d.ts +3 -1
- package/dist/orders/index.d.ts +22 -11
- package/dist/orders/index.js +5 -2
- package/dist/orders/list.d.ts +26 -5
- package/dist/orders/list.js +123 -13
- package/dist/orders/manual-discounts.d.ts +13 -5
- package/dist/orders/money-view.d.ts +149 -0
- package/dist/orders/money-view.js +99 -0
- package/dist/orders/plan-edit.d.ts +34 -2
- package/dist/orders/plan-edit.js +110 -12
- package/dist/orders/surcharges.d.ts +12 -0
- package/dist/orders/surcharges.js +34 -9
- package/dist/payments/account-credit.d.ts +17 -1
- package/dist/payments/account-credit.js +25 -2
- package/dist/payments/credit-noted-fields.d.ts +8 -1
- package/dist/payments/credit-noted-fields.js +34 -2
- package/dist/payments/customer-consent.d.ts +30 -0
- package/dist/payments/customer-consent.js +49 -0
- package/dist/payments/index.d.ts +89 -14
- package/dist/payments/index.js +22 -2
- package/dist/payments/requests.d.ts +6 -3
- package/dist/payments/requests.js +1 -0
- package/dist/pickups/index.d.ts +5 -3
- package/dist/pickups/index.js +5 -2
- package/dist/platform/email-gateways.d.ts +165 -0
- package/dist/platform/email-gateways.js +78 -0
- package/dist/platform/tax.d.ts +11 -3
- package/dist/platform/tax.js +30 -0
- package/dist/pos/exchanges.d.ts +6 -1
- package/dist/pos/index.d.ts +229 -10
- package/dist/pos/index.js +113 -4
- package/dist/pos/offline-sales.d.ts +44 -3
- package/dist/pos/offline-sales.js +57 -3
- package/dist/pos/returns.d.ts +28 -1
- package/dist/pos/returns.js +36 -6
- package/dist/pricing/price-lists.d.ts +3 -1
- package/dist/purchasing/alerts.d.ts +19 -4
- package/dist/purchasing/alerts.js +29 -3
- package/dist/purchasing/backorders.d.ts +61 -0
- package/dist/purchasing/backorders.js +92 -0
- package/dist/purchasing/index.d.ts +21 -3
- package/dist/purchasing/index.js +8 -0
- package/dist/purchasing/links.d.ts +263 -0
- package/dist/purchasing/links.js +192 -0
- package/dist/purchasing/purchase-orders.d.ts +4 -1
- package/dist/purchasing/receipts.d.ts +396 -0
- package/dist/purchasing/receipts.js +287 -0
- package/dist/purchasing/returns.d.ts +273 -0
- package/dist/purchasing/returns.js +229 -0
- package/dist/quotes/index.d.ts +20 -3
- package/dist/returns/index.d.ts +62 -1
- package/dist/returns/index.js +61 -0
- package/dist/seo/config.d.ts +128 -0
- package/dist/seo/config.js +95 -0
- package/dist/seo/redirects.d.ts +125 -0
- package/dist/seo/redirects.js +60 -1
- package/dist/seo/settings.d.ts +147 -0
- package/dist/seo/settings.js +209 -0
- package/dist/shipping/index.d.ts +6 -0
- package/dist/shipping/index.js +2 -0
- package/dist/shipping/methods.d.ts +8 -1
- package/dist/shipping/myparcel.d.ts +415 -1
- package/dist/shipping/myparcel.js +291 -3
- package/dist/shipping/rate-tables.d.ts +149 -4
- package/dist/shipping/rate-tables.js +229 -23
- package/dist/shipping/ship-to-countries.d.ts +75 -0
- package/dist/shipping/ship-to-countries.js +47 -0
- package/dist/stock/by-location.d.ts +86 -0
- package/dist/stock/by-location.js +94 -0
- package/dist/stock/index.d.ts +8 -1
- package/dist/stock/index.js +4 -0
- package/dist/stock/settings.d.ts +106 -0
- package/dist/stock/settings.js +103 -0
- package/dist/stock/transfers.d.ts +171 -10
- package/dist/stock/transfers.js +82 -4
- package/dist/stored-value/index.d.ts +98 -7
- package/dist/stored-value/index.js +71 -4
- package/dist/tax/display-policy.d.ts +105 -0
- package/dist/tax/display-policy.js +84 -0
- package/dist/tax/index.d.ts +3 -0
- package/dist/tax/index.js +2 -0
- package/dist/tenant/email-templates.d.ts +222 -1
- package/dist/tenant/email-templates.js +227 -10
- package/package.json +2 -2
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call, ListOptions } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The item types the CMS accepts on a new menu item (`CreateMenuItemDto.item_type`), pinned
|
|
5
|
+
* against the CMS's published `openapi.json` by `content-coverage.spec.ts`. A read types
|
|
6
|
+
* `itemType` as a plain string on purpose: a type the CMS adds later must not turn a read into a
|
|
7
|
+
* type error.
|
|
8
|
+
*/
|
|
9
|
+
export type MenuItemType = 'internal' | 'external' | 'collection_item' | 'collection_page' | 'heading' | 'product' | 'product_collection';
|
|
10
|
+
/** One language's label of a menu item. */
|
|
11
|
+
export interface MenuItemTranslation {
|
|
12
|
+
id: string;
|
|
13
|
+
menuItemId: string;
|
|
14
|
+
/** e.g. `en`, `nl`, `de`. */
|
|
15
|
+
languageCode: string;
|
|
16
|
+
label: string;
|
|
17
|
+
/** The link's title attribute. */
|
|
18
|
+
title: string | null;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
[extra: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One entry of a menu, as the CMS stores it.
|
|
25
|
+
*
|
|
26
|
+
* `metadata` is stored VERBATIM (its keys are the writer's own): this SDK never case-converts
|
|
27
|
+
* it, in either direction, so a fetched `metadata` can be written back unchanged.
|
|
28
|
+
*/
|
|
29
|
+
export interface MenuItem {
|
|
30
|
+
id: string;
|
|
31
|
+
menuId: string;
|
|
32
|
+
/** The item this one is nested under, or null for a top-level item. */
|
|
33
|
+
parentId: string | null;
|
|
34
|
+
/** One of {@link MenuItemType}, typed open so a newer CMS's type still reads. */
|
|
35
|
+
itemType: string;
|
|
36
|
+
/** The link of an `internal` or `external` item. */
|
|
37
|
+
url: string | null;
|
|
38
|
+
/** The linked entity: a CMS item or content type id, or a product / collection id. */
|
|
39
|
+
targetId: string | null;
|
|
40
|
+
/** Order among the item's siblings. */
|
|
41
|
+
position: number;
|
|
42
|
+
openInNewTab: boolean;
|
|
43
|
+
metadata: Record<string, unknown> | null;
|
|
44
|
+
/**
|
|
45
|
+
* The item's labels, one per language. Every item of `menus.get`'s flat `items` list has them,
|
|
46
|
+
* and so do the answers of `items.create` and `items.update`; an item's `children` do not (nor
|
|
47
|
+
* does `items.create`'s answer from a CMS before 2026-10-14).
|
|
48
|
+
*/
|
|
49
|
+
translations?: MenuItemTranslation[];
|
|
50
|
+
/** The item's direct children (on `menus.get` only), without their translations. */
|
|
51
|
+
children?: MenuItem[];
|
|
52
|
+
/**
|
|
53
|
+
* The storefront path a `collection_item` or `collection_page` item links to, when its target
|
|
54
|
+
* resolves in this store (on `menus.get` only); absent otherwise.
|
|
55
|
+
*/
|
|
56
|
+
resolvedPath?: string;
|
|
57
|
+
createdAt: string;
|
|
58
|
+
updatedAt: string;
|
|
59
|
+
[extra: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
/** A navigation menu (a header, a footer column), as the CMS stores it. */
|
|
62
|
+
export interface Menu {
|
|
63
|
+
id: string;
|
|
64
|
+
channelId: string;
|
|
65
|
+
name: string;
|
|
66
|
+
/** Unique within the store: what a storefront reads the menu by. */
|
|
67
|
+
slug: string;
|
|
68
|
+
description: string | null;
|
|
69
|
+
/** Free-form presentation data for the menu as a whole, stored verbatim. */
|
|
70
|
+
metadata: Record<string, unknown> | null;
|
|
71
|
+
/** Live items of the menu, nested ones included. On `menus.list` only. */
|
|
72
|
+
itemCount?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Every live item of the menu, nested ones included, as a FLAT list: build the tree from each
|
|
75
|
+
* item's `parentId`. On `menus.get` and `menus.getBySlug` only.
|
|
76
|
+
*/
|
|
77
|
+
items?: MenuItem[];
|
|
78
|
+
createdAt: string;
|
|
79
|
+
updatedAt: string;
|
|
80
|
+
[extra: string]: unknown;
|
|
81
|
+
}
|
|
82
|
+
export interface MenuListOptions extends ListOptions {
|
|
83
|
+
/** Menus whose name contains this, ignoring case. */
|
|
84
|
+
search?: string;
|
|
85
|
+
/**
|
|
86
|
+
* `name`, `created_at` or `updated_at`, with a leading `-` for descending. Anything else (and
|
|
87
|
+
* no sort) is newest first.
|
|
88
|
+
*/
|
|
89
|
+
sort?: string;
|
|
90
|
+
}
|
|
91
|
+
/** Exactly what `CreateMenuDto` whitelists. */
|
|
92
|
+
export interface CreateMenuInput {
|
|
93
|
+
name: string;
|
|
94
|
+
/** Lowercase letters, digits and hyphens. */
|
|
95
|
+
slug: string;
|
|
96
|
+
description?: string | null;
|
|
97
|
+
/** Stored verbatim. */
|
|
98
|
+
metadata?: Record<string, unknown> | null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* What `UpdateMenuDto` whitelists. An absent key leaves the stored value alone; `null` clears
|
|
102
|
+
* `description` and `metadata`. `metadata` is replaced whole, never merged.
|
|
103
|
+
*/
|
|
104
|
+
export interface UpdateMenuInput {
|
|
105
|
+
name?: string;
|
|
106
|
+
slug?: string;
|
|
107
|
+
description?: string | null;
|
|
108
|
+
metadata?: Record<string, unknown> | null;
|
|
109
|
+
}
|
|
110
|
+
/** One language's label, as `MenuItemTranslationDto` takes it. */
|
|
111
|
+
export interface MenuItemTranslationInput {
|
|
112
|
+
languageCode: string;
|
|
113
|
+
label: string;
|
|
114
|
+
/** `null` clears the title; an absent key leaves it alone. */
|
|
115
|
+
title?: string | null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Exactly what `CreateMenuItemDto` whitelists — not `Partial<MenuItem>`: the CMS runs
|
|
119
|
+
* `forbidNonWhitelisted`, so handing a fetched item back (with its `id` and timestamps) is a 400.
|
|
120
|
+
*/
|
|
121
|
+
export interface CreateMenuItemInput {
|
|
122
|
+
itemType: MenuItemType;
|
|
123
|
+
url?: string | null;
|
|
124
|
+
targetId?: string | null;
|
|
125
|
+
/** Nests the item under another item of the SAME menu. */
|
|
126
|
+
parentId?: string | null;
|
|
127
|
+
/** Defaults to after the last of its siblings. */
|
|
128
|
+
position?: number;
|
|
129
|
+
openInNewTab?: boolean;
|
|
130
|
+
/** Stored verbatim. */
|
|
131
|
+
metadata?: Record<string, unknown> | null;
|
|
132
|
+
/** Each language given is written; the others are left alone. */
|
|
133
|
+
translations?: MenuItemTranslationInput[];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The members `items.update` acts on. An absent key leaves the stored value alone; `null` clears
|
|
137
|
+
* `url`, `targetId`, `metadata`, and `parentId` (back to the top level). A changed `position`
|
|
138
|
+
* moves the item among its siblings and shifts the ones in between.
|
|
139
|
+
*
|
|
140
|
+
* A move to another parent is NOT renumbered correctly. With a new `position`, the shift above
|
|
141
|
+
* runs on the NEW parent's children between the item's old and new numbers, as if both numbers
|
|
142
|
+
* belonged to one list, so two of those children can end up sharing a number; with no new
|
|
143
|
+
* `position` (or the same number) nothing shifts and the item can share a number with one of
|
|
144
|
+
* them. Either way the old parent's children keep the gap the item left. After such a move, read
|
|
145
|
+
* the menu again and set each sibling's `position`.
|
|
146
|
+
*/
|
|
147
|
+
export interface UpdateMenuItemInput {
|
|
148
|
+
itemType?: MenuItemType;
|
|
149
|
+
url?: string | null;
|
|
150
|
+
targetId?: string | null;
|
|
151
|
+
parentId?: string | null;
|
|
152
|
+
position?: number;
|
|
153
|
+
openInNewTab?: boolean;
|
|
154
|
+
metadata?: Record<string, unknown> | null;
|
|
155
|
+
translations?: MenuItemTranslationInput[];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* A menu's items. Every method takes the menu's id first; an item of another menu, or of another
|
|
159
|
+
* store's menu, is `NotFoundError` (404). Refusals as on {@link ContentMenusModule}; a `parentId`
|
|
160
|
+
* that is not a live item of the same menu (or would put the item under itself or one of its own
|
|
161
|
+
* children) is `ValidationError` with code `MENU_ITEM_PARENT_INVALID`.
|
|
162
|
+
*/
|
|
163
|
+
export interface ContentMenuItemsModule {
|
|
164
|
+
/**
|
|
165
|
+
* Adds an item. Answers the item as stored, with its translations (a CMS before 2026-10-14
|
|
166
|
+
* answers it without them, and with `parentId: null` for a nested item).
|
|
167
|
+
*
|
|
168
|
+
* @effect write
|
|
169
|
+
* @permission CreateCmsMenu
|
|
170
|
+
*/
|
|
171
|
+
create(menuId: string, input: CreateMenuItemInput, options?: Call): Promise<MenuItem>;
|
|
172
|
+
/**
|
|
173
|
+
* Writes what the input names in one transaction. Answers the item with its translations. A
|
|
174
|
+
* move to another parent leaves both parents' numbering to repair (see
|
|
175
|
+
* {@link UpdateMenuItemInput}).
|
|
176
|
+
*
|
|
177
|
+
* @effect write
|
|
178
|
+
* @permission UpdateCmsMenu
|
|
179
|
+
*/
|
|
180
|
+
update(menuId: string, itemId: string, input: UpdateMenuItemInput, options?: Call): Promise<MenuItem>;
|
|
181
|
+
/**
|
|
182
|
+
* Removes the item from the menu: storefronts stop rendering it, and nothing in the SDK brings
|
|
183
|
+
* it back. Its children are NOT removed: they stay in the menu's `items`, their `parentId`
|
|
184
|
+
* still naming the removed item, so move or delete them first.
|
|
185
|
+
*
|
|
186
|
+
* @effect destructive
|
|
187
|
+
* @permission DeleteCmsMenu
|
|
188
|
+
*/
|
|
189
|
+
delete(menuId: string, itemId: string, options?: Call): Promise<void>;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The store's navigation menus, read and written through the CMS.
|
|
193
|
+
*
|
|
194
|
+
* Reads need no permission: storefronts read the same routes, and every caller gets the same
|
|
195
|
+
* answer. Writes need the permission each method names (`@permission`).
|
|
196
|
+
*
|
|
197
|
+
* Refusals are the CMS's problem+json, mapped by status:
|
|
198
|
+
* - `NotFoundError` (404, `NOT_FOUND`): no such menu or item in this store.
|
|
199
|
+
* - `ConflictError` (409, `CONFLICT`): the slug is taken in this store.
|
|
200
|
+
* - `ValidationError` (400): `VALIDATION_FAILED` with one `details[]` entry per refused input
|
|
201
|
+
* member (a slug that is not lowercase-and-hyphens, an unknown `itemType`, an id that is not a
|
|
202
|
+
* UUID), or a code of its own (`MENU_ITEM_PARENT_INVALID`).
|
|
203
|
+
* - `PermissionDeniedError` (403): the credential lacks the write's permission.
|
|
204
|
+
*
|
|
205
|
+
* Every write records the platform event a storefront revalidates on (`content.site_content_changed`
|
|
206
|
+
* with kind `menu`); nothing here emits one. No write is deduplicated: an `idempotencyKey` is
|
|
207
|
+
* sent and acts on nothing, so a repeated `items.create` is a second item.
|
|
208
|
+
*/
|
|
209
|
+
export interface ContentMenusModule {
|
|
210
|
+
/**
|
|
211
|
+
* The store's menus, a page at a time, newest first unless `sort` says otherwise; each with
|
|
212
|
+
* its `itemCount`, none with its items. Pages are offset-based (`Page.total` counts the
|
|
213
|
+
* matches), so `after: offsetCursor((n - 1) * size)` opens page `n`.
|
|
214
|
+
*
|
|
215
|
+
* @effect read
|
|
216
|
+
*/
|
|
217
|
+
list(options?: MenuListOptions): Promise<Page<Menu>>;
|
|
218
|
+
/**
|
|
219
|
+
* One menu with every live item (`items`, a flat list).
|
|
220
|
+
*
|
|
221
|
+
* @effect read
|
|
222
|
+
*/
|
|
223
|
+
get(id: string, options?: Call): Promise<Menu>;
|
|
224
|
+
/**
|
|
225
|
+
* One menu by its slug, with every live item: what a storefront renders.
|
|
226
|
+
*
|
|
227
|
+
* @effect read
|
|
228
|
+
*/
|
|
229
|
+
getBySlug(slug: string, options?: Call): Promise<Menu>;
|
|
230
|
+
/**
|
|
231
|
+
* Adds an empty menu. Answers it without items.
|
|
232
|
+
*
|
|
233
|
+
* @effect write
|
|
234
|
+
* @permission CreateCmsMenu
|
|
235
|
+
*/
|
|
236
|
+
create(input: CreateMenuInput, options?: Call): Promise<Menu>;
|
|
237
|
+
/**
|
|
238
|
+
* Writes what the input names. A new `slug` changes what storefronts read the menu by.
|
|
239
|
+
*
|
|
240
|
+
* @effect write
|
|
241
|
+
* @permission UpdateCmsMenu
|
|
242
|
+
*/
|
|
243
|
+
update(id: string, input: UpdateMenuInput, options?: Call): Promise<Menu>;
|
|
244
|
+
/**
|
|
245
|
+
* Removes the menu with its items: storefronts reading it by slug get `NotFoundError`, its
|
|
246
|
+
* slug is free again, and nothing in the SDK brings it back.
|
|
247
|
+
*
|
|
248
|
+
* @effect destructive
|
|
249
|
+
* @permission DeleteCmsMenu
|
|
250
|
+
*/
|
|
251
|
+
delete(id: string, options?: Call): Promise<void>;
|
|
252
|
+
items: ContentMenuItemsModule;
|
|
253
|
+
}
|
|
254
|
+
export declare function createContentMenusModule(transport: Transport): ContentMenusModule;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createPage, offsetCursor, PLATFORM_PASSTHROUGH_KEYS, readOffsetCursor, } from '@sixtyninecommerce/core';
|
|
2
|
+
const MENUS = '/menus';
|
|
3
|
+
const DEFAULT_PAGE_SIZE = 50;
|
|
4
|
+
function menuPath(id) {
|
|
5
|
+
return `${MENUS}/${encodeURIComponent(id)}`;
|
|
6
|
+
}
|
|
7
|
+
function itemPath(menuId, itemId) {
|
|
8
|
+
return `${menuPath(menuId)}/items/${encodeURIComponent(itemId)}`;
|
|
9
|
+
}
|
|
10
|
+
export function createContentMenusModule(transport) {
|
|
11
|
+
async function listPage(offset, limit, options) {
|
|
12
|
+
const body = await transport.rest('cms', {
|
|
13
|
+
method: 'GET',
|
|
14
|
+
path: MENUS,
|
|
15
|
+
// `offset` is always sent: it keeps the CMS out of its keyset mode, which cannot resume
|
|
16
|
+
// a `sort` (400 `CURSOR_ORDERING_CONFLICT`) and cannot open page n. Without `limit` an
|
|
17
|
+
// offset read answers the WHOLE list.
|
|
18
|
+
query: { limit, offset, search: options?.search, sort: options?.sort },
|
|
19
|
+
}, { ...options, preserveKeys: PLATFORM_PASSTHROUGH_KEYS });
|
|
20
|
+
const items = body.data ?? [];
|
|
21
|
+
// An offset read echoes neither the offset nor a cursor, so the page is built from what
|
|
22
|
+
// THIS call asked for.
|
|
23
|
+
const total = body.pagination?.total ?? items.length;
|
|
24
|
+
const advanced = offset + (items.length > 0 ? items.length : limit);
|
|
25
|
+
const hasNext = advanced < total;
|
|
26
|
+
return createPage(items, { nextCursor: hasNext ? offsetCursor(advanced) : null, hasNext, total }, (cursor) => listPage(readOffsetCursor(cursor), limit, options));
|
|
27
|
+
}
|
|
28
|
+
async function readMenu(path, options) {
|
|
29
|
+
const body = await transport.rest('cms', { method: 'GET', path }, { ...options, preserveKeys: PLATFORM_PASSTHROUGH_KEYS });
|
|
30
|
+
return body.data;
|
|
31
|
+
}
|
|
32
|
+
const items = {
|
|
33
|
+
async create(menuId, input, options) {
|
|
34
|
+
const body = await transport.rest('cms', { method: 'POST', path: `${menuPath(menuId)}/items`, body: input }, { ...options, preserveKeys: PLATFORM_PASSTHROUGH_KEYS });
|
|
35
|
+
return body.data;
|
|
36
|
+
},
|
|
37
|
+
async update(menuId, itemId, input, options) {
|
|
38
|
+
const body = await transport.rest('cms', { method: 'PATCH', path: itemPath(menuId, itemId), body: input }, { ...options, preserveKeys: PLATFORM_PASSTHROUGH_KEYS });
|
|
39
|
+
return body.data;
|
|
40
|
+
},
|
|
41
|
+
async delete(menuId, itemId, options) {
|
|
42
|
+
await transport.rest('cms', { method: 'DELETE', path: itemPath(menuId, itemId), expect: 'void' }, options);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
list(options) {
|
|
47
|
+
const limit = options?.limit ?? DEFAULT_PAGE_SIZE;
|
|
48
|
+
const offset = options?.after === undefined ? 0 : readOffsetCursor(options.after);
|
|
49
|
+
return listPage(offset, limit, options);
|
|
50
|
+
},
|
|
51
|
+
get(id, options) {
|
|
52
|
+
return readMenu(menuPath(id), options);
|
|
53
|
+
},
|
|
54
|
+
getBySlug(slug, options) {
|
|
55
|
+
return readMenu(`${MENUS}/by-slug/${encodeURIComponent(slug)}`, options);
|
|
56
|
+
},
|
|
57
|
+
async create(input, options) {
|
|
58
|
+
const body = await transport.rest('cms', { method: 'POST', path: MENUS, body: input }, { ...options, preserveKeys: PLATFORM_PASSTHROUGH_KEYS });
|
|
59
|
+
return body.data;
|
|
60
|
+
},
|
|
61
|
+
async update(id, input, options) {
|
|
62
|
+
const body = await transport.rest('cms', { method: 'PATCH', path: menuPath(id), body: input }, { ...options, preserveKeys: PLATFORM_PASSTHROUGH_KEYS });
|
|
63
|
+
return body.data;
|
|
64
|
+
},
|
|
65
|
+
async delete(id, options) {
|
|
66
|
+
await transport.rest('cms', { method: 'DELETE', path: menuPath(id), expect: 'void' }, options);
|
|
67
|
+
},
|
|
68
|
+
items,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { type Page, type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call, ListOptions } from '../types.js';
|
|
3
|
+
/** One language's value of a content string. */
|
|
4
|
+
export interface ContentStringValue {
|
|
5
|
+
id: string;
|
|
6
|
+
contentStringId: string;
|
|
7
|
+
/** e.g. `en`, `nl`, `de`. */
|
|
8
|
+
languageCode: string;
|
|
9
|
+
value: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
[extra: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A content string: one translatable piece of storefront copy (a button label, a footer line),
|
|
16
|
+
* read by its `key`.
|
|
17
|
+
*/
|
|
18
|
+
export interface ContentString {
|
|
19
|
+
id: string;
|
|
20
|
+
channelId: string;
|
|
21
|
+
/** Unique within the store; dot-separated by convention (`footer.copyright`). */
|
|
22
|
+
key: string;
|
|
23
|
+
/** A note for translators. */
|
|
24
|
+
description: string | null;
|
|
25
|
+
/** One value per language, in no guaranteed order. */
|
|
26
|
+
values: ContentStringValue[];
|
|
27
|
+
createdAt: string;
|
|
28
|
+
updatedAt: string;
|
|
29
|
+
[extra: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
export interface ContentStringListOptions extends ListOptions {
|
|
32
|
+
/**
|
|
33
|
+
* A full-text search over keys, descriptions and values, answered in key order; its
|
|
34
|
+
* `Page.total` is the search engine's estimate. Without one the list is every string of the
|
|
35
|
+
* store, in key order.
|
|
36
|
+
*/
|
|
37
|
+
search?: string;
|
|
38
|
+
}
|
|
39
|
+
/** One language's value, as `ContentStringValueDto` takes it. */
|
|
40
|
+
export interface ContentStringValueInput {
|
|
41
|
+
languageCode: string;
|
|
42
|
+
value: string;
|
|
43
|
+
}
|
|
44
|
+
/** Exactly what `CreateContentStringDto` whitelists. */
|
|
45
|
+
export interface CreateContentStringInput {
|
|
46
|
+
key: string;
|
|
47
|
+
description?: string | null;
|
|
48
|
+
values?: ContentStringValueInput[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* What `UpdateContentStringDto` whitelists. An absent key leaves the stored value alone; `null`
|
|
52
|
+
* clears `description`. `values` writes each language it names and leaves every other language's
|
|
53
|
+
* value as stored: there is no way to remove one language's value here.
|
|
54
|
+
*/
|
|
55
|
+
export interface UpdateContentStringInput {
|
|
56
|
+
key?: string;
|
|
57
|
+
description?: string | null;
|
|
58
|
+
values?: ContentStringValueInput[];
|
|
59
|
+
}
|
|
60
|
+
/** One string of a bulk import: its key and the values to write. */
|
|
61
|
+
export interface ContentStringImportItem {
|
|
62
|
+
key: string;
|
|
63
|
+
values: ContentStringValueInput[];
|
|
64
|
+
}
|
|
65
|
+
/** Every string of the store: `key → language code → value`. */
|
|
66
|
+
export type ContentStringsExport = Record<string, Record<string, string>>;
|
|
67
|
+
/**
|
|
68
|
+
* The store's content strings, read and written through the CMS.
|
|
69
|
+
*
|
|
70
|
+
* Reads need no permission: storefronts read the same routes, and every caller gets the same
|
|
71
|
+
* answer. Writes need the permission each method names (`@permission`).
|
|
72
|
+
*
|
|
73
|
+
* Refusals are the CMS's problem+json, mapped by status:
|
|
74
|
+
* - `NotFoundError` (404, `NOT_FOUND`): no such string in this store.
|
|
75
|
+
* - `ConflictError` (409, `CONFLICT`): the key is taken in this store.
|
|
76
|
+
* - `ValidationError` (400): `VALIDATION_FAILED` with one `details[]` entry per refused input
|
|
77
|
+
* member (a key over 500 characters, a value that is not a string, an id that is not a UUID).
|
|
78
|
+
* - `PermissionDeniedError` (403): the credential lacks the write's permission.
|
|
79
|
+
*
|
|
80
|
+
* No write here is deduplicated: an `idempotencyKey` is sent and acts on nothing, so a repeated
|
|
81
|
+
* `create` is refused `CONFLICT` on the same key. `import` is safe to repeat: it upserts.
|
|
82
|
+
*/
|
|
83
|
+
export interface ContentStringsModule {
|
|
84
|
+
/**
|
|
85
|
+
* The store's strings with their values, a page at a time, in key order. Pages are
|
|
86
|
+
* offset-based (`Page.total` counts the matches), so `after: offsetCursor((n - 1) * size)`
|
|
87
|
+
* opens page `n`.
|
|
88
|
+
*
|
|
89
|
+
* @effect read
|
|
90
|
+
*/
|
|
91
|
+
list(options?: ContentStringListOptions): Promise<Page<ContentString>>;
|
|
92
|
+
/**
|
|
93
|
+
* One string with its values.
|
|
94
|
+
*
|
|
95
|
+
* @effect read
|
|
96
|
+
*/
|
|
97
|
+
get(id: string, options?: Call): Promise<ContentString>;
|
|
98
|
+
/**
|
|
99
|
+
* @effect write
|
|
100
|
+
* @permission CreateCmsContent
|
|
101
|
+
*/
|
|
102
|
+
create(input: CreateContentStringInput, options?: Call): Promise<ContentString>;
|
|
103
|
+
/**
|
|
104
|
+
* Writes what the input names in one transaction. Answers the string with all its values.
|
|
105
|
+
*
|
|
106
|
+
* @effect write
|
|
107
|
+
* @permission UpdateCmsContent
|
|
108
|
+
*/
|
|
109
|
+
update(id: string, input: UpdateContentStringInput, options?: Call): Promise<ContentString>;
|
|
110
|
+
/**
|
|
111
|
+
* Removes the string with its values: a storefront reading its key falls back to its own
|
|
112
|
+
* default, the key is free again, and nothing in the SDK brings it back.
|
|
113
|
+
*
|
|
114
|
+
* @effect destructive
|
|
115
|
+
* @permission DeleteCmsContent
|
|
116
|
+
*/
|
|
117
|
+
delete(id: string, options?: Call): Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* Writes many strings in one transaction: a key the store lacks is created, and each value
|
|
120
|
+
* named is written over the stored one; values of languages not named are left alone.
|
|
121
|
+
* Answers how many strings it wrote. All or nothing: one refused item writes none.
|
|
122
|
+
*
|
|
123
|
+
* @effect write
|
|
124
|
+
* @permission CreateCmsContent
|
|
125
|
+
*/
|
|
126
|
+
import(items: readonly ContentStringImportItem[], options?: Call): Promise<{
|
|
127
|
+
imported: number;
|
|
128
|
+
}>;
|
|
129
|
+
/**
|
|
130
|
+
* Every string of the store as one map, `key → language code → value`, keys exactly as
|
|
131
|
+
* stored.
|
|
132
|
+
*
|
|
133
|
+
* @effect read
|
|
134
|
+
*/
|
|
135
|
+
export(options?: Call): Promise<ContentStringsExport>;
|
|
136
|
+
}
|
|
137
|
+
export declare function createContentStringsModule(transport: Transport): ContentStringsModule;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createPage, offsetCursor, readOffsetCursor, ValidationError, } from '@sixtyninecommerce/core';
|
|
2
|
+
const STRINGS = '/content-strings';
|
|
3
|
+
const DEFAULT_PAGE_SIZE = 100;
|
|
4
|
+
function stringPath(id) {
|
|
5
|
+
return `${STRINGS}/${encodeURIComponent(id)}`;
|
|
6
|
+
}
|
|
7
|
+
/** A plain object whose every value passes `entryIs` (a string, by default). */
|
|
8
|
+
function isStringMap(value, entryIs = (entry) => typeof entry === 'string') {
|
|
9
|
+
return (typeof value === 'object' &&
|
|
10
|
+
value !== null &&
|
|
11
|
+
!Array.isArray(value) &&
|
|
12
|
+
Object.values(value).every((entry) => entryIs(entry)));
|
|
13
|
+
}
|
|
14
|
+
export function createContentStringsModule(transport) {
|
|
15
|
+
async function listPage(offset, limit, options) {
|
|
16
|
+
const body = await transport.rest('cms', {
|
|
17
|
+
method: 'GET',
|
|
18
|
+
path: STRINGS,
|
|
19
|
+
// Never `after`: that switches the CMS to its keyset mode, which orders newest first
|
|
20
|
+
// instead of by key and refuses a search. Without `limit` an offset read answers the
|
|
21
|
+
// WHOLE list.
|
|
22
|
+
query: { limit, offset, search: options?.search },
|
|
23
|
+
}, options);
|
|
24
|
+
const items = body.data ?? [];
|
|
25
|
+
// An offset read echoes neither the offset nor a cursor, so the page is built from what
|
|
26
|
+
// THIS call asked for.
|
|
27
|
+
const total = body.pagination?.total ?? items.length;
|
|
28
|
+
const advanced = offset + (items.length > 0 ? items.length : limit);
|
|
29
|
+
const hasNext = advanced < total;
|
|
30
|
+
return createPage(items, { nextCursor: hasNext ? offsetCursor(advanced) : null, hasNext, total }, (cursor) => listPage(readOffsetCursor(cursor), limit, options));
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
list(options) {
|
|
34
|
+
const limit = options?.limit ?? DEFAULT_PAGE_SIZE;
|
|
35
|
+
const offset = options?.after === undefined ? 0 : readOffsetCursor(options.after);
|
|
36
|
+
return listPage(offset, limit, options);
|
|
37
|
+
},
|
|
38
|
+
async get(id, options) {
|
|
39
|
+
const body = await transport.rest('cms', { method: 'GET', path: stringPath(id) }, options);
|
|
40
|
+
return body.data;
|
|
41
|
+
},
|
|
42
|
+
async create(input, options) {
|
|
43
|
+
const body = await transport.rest('cms', { method: 'POST', path: STRINGS, body: input }, options);
|
|
44
|
+
return body.data;
|
|
45
|
+
},
|
|
46
|
+
async update(id, input, options) {
|
|
47
|
+
const body = await transport.rest('cms', { method: 'PATCH', path: stringPath(id), body: input }, options);
|
|
48
|
+
return body.data;
|
|
49
|
+
},
|
|
50
|
+
async delete(id, options) {
|
|
51
|
+
await transport.rest('cms', { method: 'DELETE', path: stringPath(id), expect: 'void' }, options);
|
|
52
|
+
},
|
|
53
|
+
async import(items, options) {
|
|
54
|
+
const body = await transport.rest('cms', { method: 'POST', path: `${STRINGS}/import`, body: { items } }, options);
|
|
55
|
+
return body.data;
|
|
56
|
+
},
|
|
57
|
+
async export(options) {
|
|
58
|
+
// The answer is a bare map keyed by the merchant's own keys (`footer.copyright_notice`),
|
|
59
|
+
// not an envelope: every key is data, and the JSON reader would camelise all of them
|
|
60
|
+
// (`footer.copyrightNotice`), which `preserveKeys` cannot stop at the top level. So the
|
|
61
|
+
// body is taken as bytes and parsed here, untouched.
|
|
62
|
+
const answer = await transport.rest('cms', { method: 'GET', path: `${STRINGS}/export`, expect: 'blob' }, options);
|
|
63
|
+
const text = await answer.blob.text();
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(text);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
parsed = undefined;
|
|
70
|
+
}
|
|
71
|
+
if (!isStringMap(parsed, (entry) => isStringMap(entry))) {
|
|
72
|
+
throw new ValidationError({
|
|
73
|
+
code: 'MALFORMED_EXPORT_RESPONSE',
|
|
74
|
+
message: 'The content-string export was not a map of key to language to value.',
|
|
75
|
+
status: 400,
|
|
76
|
+
service: 'cms',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return parsed;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A customer's resolved delivery terms (`admin.customers.deliveryTerms`, W10-57): the shipping
|
|
3
|
+
* method, Incoterm, lead time and delivery advice that govern the customer's orders, whichever
|
|
4
|
+
* source decides them (an ERP such as Exact Online, or nothing).
|
|
5
|
+
*/
|
|
6
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
7
|
+
import type { OrderDeliveryAdvice, OrderDeliveryTermsStatus } from '../orders/get.js';
|
|
8
|
+
import type { Call } from '../types.js';
|
|
9
|
+
/** One customer's delivery terms, as an order placed now would take them. */
|
|
10
|
+
export interface CustomerDeliveryTerms {
|
|
11
|
+
customerId: string | null;
|
|
12
|
+
/** Where the terms come from (`exact-online`, …). */
|
|
13
|
+
source: string;
|
|
14
|
+
/** An ERP governs the terms: staff cannot change them here. */
|
|
15
|
+
managedExternally: boolean;
|
|
16
|
+
status: OrderDeliveryTermsStatus;
|
|
17
|
+
shippingMethodStatus: OrderDeliveryTermsStatus;
|
|
18
|
+
policyStatus: OrderDeliveryTermsStatus;
|
|
19
|
+
/** The terms in a sentence. */
|
|
20
|
+
summary: string;
|
|
21
|
+
/** The store's shipping method the customer's orders default to. */
|
|
22
|
+
preferredShippingMethodId: string | null;
|
|
23
|
+
preferredShippingMethodCode: string | null;
|
|
24
|
+
preferredShippingMethodName: string | null;
|
|
25
|
+
/** The preferred method is offered to the customer right now. */
|
|
26
|
+
preferredShippingMethodAvailable: boolean;
|
|
27
|
+
/** The store's shipping methods the customer may choose. */
|
|
28
|
+
eligibleShippingMethodIds: string[];
|
|
29
|
+
incotermStatus: OrderDeliveryTermsStatus;
|
|
30
|
+
incotermCode: string | null;
|
|
31
|
+
incotermVersion: number | null;
|
|
32
|
+
/** The Incoterm's named place. */
|
|
33
|
+
incotermAddress: string | null;
|
|
34
|
+
incotermDescription: string | null;
|
|
35
|
+
/** Days between the order and its dispatch. */
|
|
36
|
+
shippingLeadDays: number | null;
|
|
37
|
+
deliveryAdvice: OrderDeliveryAdvice;
|
|
38
|
+
stale: boolean;
|
|
39
|
+
/** ISO-8601, UTC: when the source last changed them. */
|
|
40
|
+
syncedAt: string | null;
|
|
41
|
+
warning: string | null;
|
|
42
|
+
}
|
|
43
|
+
export declare const CUSTOMER_DELIVERY_TERMS_QUERY = "\n query CustomerDeliveryTerms($customerId: ID!) {\n customerDeliveryTerms(customerId: $customerId) {\n customerId\n source\n managedExternally\n status\n shippingMethodStatus\n policyStatus\n summary\n preferredShippingMethodId\n preferredShippingMethodCode\n preferredShippingMethodName\n preferredShippingMethodAvailable\n eligibleShippingMethodIds\n incotermStatus\n incotermCode\n incotermVersion\n incotermAddress\n incotermDescription\n shippingLeadDays\n deliveryAdvice\n stale\n syncedAt\n warning\n }\n }\n";
|
|
44
|
+
export declare function readCustomerDeliveryTerms(transport: Transport, customerId: string, options: Call | undefined): Promise<CustomerDeliveryTerms | null>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fieldOf } from '../catalog/types.js';
|
|
2
|
+
export const CUSTOMER_DELIVERY_TERMS_QUERY = /* GraphQL */ `
|
|
3
|
+
query CustomerDeliveryTerms($customerId: ID!) {
|
|
4
|
+
customerDeliveryTerms(customerId: $customerId) {
|
|
5
|
+
customerId
|
|
6
|
+
source
|
|
7
|
+
managedExternally
|
|
8
|
+
status
|
|
9
|
+
shippingMethodStatus
|
|
10
|
+
policyStatus
|
|
11
|
+
summary
|
|
12
|
+
preferredShippingMethodId
|
|
13
|
+
preferredShippingMethodCode
|
|
14
|
+
preferredShippingMethodName
|
|
15
|
+
preferredShippingMethodAvailable
|
|
16
|
+
eligibleShippingMethodIds
|
|
17
|
+
incotermStatus
|
|
18
|
+
incotermCode
|
|
19
|
+
incotermVersion
|
|
20
|
+
incotermAddress
|
|
21
|
+
incotermDescription
|
|
22
|
+
shippingLeadDays
|
|
23
|
+
deliveryAdvice
|
|
24
|
+
stale
|
|
25
|
+
syncedAt
|
|
26
|
+
warning
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
export async function readCustomerDeliveryTerms(transport, customerId, options) {
|
|
31
|
+
return ((await fieldOf(transport, CUSTOMER_DELIVERY_TERMS_QUERY, 'customerDeliveryTerms', { customerId }, options)) ?? null);
|
|
32
|
+
}
|