@sixtyninecommerce/admin 0.11.0 → 0.12.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.
Files changed (147) hide show
  1. package/README.md +1501 -23
  2. package/dist/auth/index.d.ts +7 -1
  3. package/dist/auth/index.js +8 -1
  4. package/dist/catalog/documents.d.ts +142 -0
  5. package/dist/catalog/documents.js +114 -0
  6. package/dist/catalog/index.d.ts +48 -7
  7. package/dist/catalog/index.js +6 -1
  8. package/dist/catalog/metadata.d.ts +244 -0
  9. package/dist/catalog/metadata.js +220 -0
  10. package/dist/catalog/properties.d.ts +38 -0
  11. package/dist/catalog/properties.js +52 -1
  12. package/dist/catalog/recommendations.d.ts +33 -6
  13. package/dist/catalog/recommendations.js +94 -2
  14. package/dist/catalog/reviews.d.ts +17 -4
  15. package/dist/catalog/reviews.js +7 -0
  16. package/dist/catalog/variants.d.ts +60 -0
  17. package/dist/catalog/variants.js +39 -0
  18. package/dist/catalog/visibility.d.ts +18 -2
  19. package/dist/client.d.ts +29 -3
  20. package/dist/client.js +19 -8
  21. package/dist/content/assets.d.ts +3 -4
  22. package/dist/content/forms.d.ts +332 -1
  23. package/dist/content/forms.js +140 -4
  24. package/dist/content/items.d.ts +31 -8
  25. package/dist/content/items.js +1 -1
  26. package/dist/content/menus.d.ts +254 -0
  27. package/dist/content/menus.js +70 -0
  28. package/dist/content/strings.d.ts +137 -0
  29. package/dist/content/strings.js +82 -0
  30. package/dist/customers/delivery-terms.d.ts +44 -0
  31. package/dist/customers/delivery-terms.js +32 -0
  32. package/dist/customers/index.d.ts +15 -0
  33. package/dist/customers/index.js +4 -0
  34. package/dist/documents/emails.d.ts +150 -0
  35. package/dist/documents/emails.js +220 -0
  36. package/dist/documents/index.d.ts +13 -0
  37. package/dist/documents/index.js +2 -0
  38. package/dist/fulfillments/index.d.ts +119 -1
  39. package/dist/fulfillments/index.js +124 -4
  40. package/dist/index.d.ts +43 -23
  41. package/dist/index.js +11 -5
  42. package/dist/integrations/exact-online-types.d.ts +542 -0
  43. package/dist/integrations/exact-online.d.ts +541 -5
  44. package/dist/integrations/exact-online.js +714 -1
  45. package/dist/integrations/index.d.ts +23 -8
  46. package/dist/integrations/index.js +9 -4
  47. package/dist/integrations/pipeline-discoveries.d.ts +65 -0
  48. package/dist/integrations/pipeline-discoveries.js +65 -0
  49. package/dist/integrations/pipeline-runs.d.ts +119 -0
  50. package/dist/integrations/pipeline-runs.js +87 -0
  51. package/dist/integrations/pipelines.d.ts +73 -2
  52. package/dist/integrations/pipelines.js +36 -1
  53. package/dist/integrations/price-import.d.ts +223 -0
  54. package/dist/integrations/price-import.js +124 -0
  55. package/dist/integrations/types.d.ts +221 -3
  56. package/dist/integrations/types.js +4 -3
  57. package/dist/meta/index.d.ts +10 -0
  58. package/dist/metafields/index.d.ts +8 -1
  59. package/dist/metafields/index.js +10 -0
  60. package/dist/orders/cancel-with-money.d.ts +10 -0
  61. package/dist/orders/cancel-with-money.js +2 -0
  62. package/dist/orders/commit-edit.d.ts +16 -0
  63. package/dist/orders/commit-edit.js +14 -1
  64. package/dist/orders/deposit-policy.d.ts +3 -1
  65. package/dist/orders/index.d.ts +22 -11
  66. package/dist/orders/index.js +5 -2
  67. package/dist/orders/list.d.ts +26 -5
  68. package/dist/orders/list.js +123 -13
  69. package/dist/orders/manual-discounts.d.ts +13 -5
  70. package/dist/orders/money-view.d.ts +149 -0
  71. package/dist/orders/money-view.js +99 -0
  72. package/dist/orders/plan-edit.d.ts +34 -2
  73. package/dist/orders/plan-edit.js +110 -12
  74. package/dist/orders/surcharges.d.ts +12 -0
  75. package/dist/orders/surcharges.js +34 -9
  76. package/dist/payments/account-credit.d.ts +17 -1
  77. package/dist/payments/account-credit.js +25 -2
  78. package/dist/payments/credit-noted-fields.d.ts +8 -1
  79. package/dist/payments/credit-noted-fields.js +34 -2
  80. package/dist/payments/customer-consent.d.ts +30 -0
  81. package/dist/payments/customer-consent.js +49 -0
  82. package/dist/payments/index.d.ts +89 -14
  83. package/dist/payments/index.js +22 -2
  84. package/dist/payments/requests.d.ts +6 -3
  85. package/dist/payments/requests.js +1 -0
  86. package/dist/pickups/index.d.ts +5 -3
  87. package/dist/pickups/index.js +5 -2
  88. package/dist/platform/email-gateways.d.ts +165 -0
  89. package/dist/platform/email-gateways.js +78 -0
  90. package/dist/platform/tax.d.ts +11 -3
  91. package/dist/platform/tax.js +30 -0
  92. package/dist/pos/exchanges.d.ts +6 -1
  93. package/dist/pos/index.d.ts +229 -10
  94. package/dist/pos/index.js +113 -4
  95. package/dist/pos/offline-sales.d.ts +44 -3
  96. package/dist/pos/offline-sales.js +57 -3
  97. package/dist/pos/returns.d.ts +28 -1
  98. package/dist/pos/returns.js +36 -6
  99. package/dist/pricing/price-lists.d.ts +3 -1
  100. package/dist/purchasing/alerts.d.ts +19 -4
  101. package/dist/purchasing/alerts.js +29 -3
  102. package/dist/purchasing/backorders.d.ts +61 -0
  103. package/dist/purchasing/backorders.js +92 -0
  104. package/dist/purchasing/index.d.ts +21 -3
  105. package/dist/purchasing/index.js +8 -0
  106. package/dist/purchasing/links.d.ts +263 -0
  107. package/dist/purchasing/links.js +192 -0
  108. package/dist/purchasing/purchase-orders.d.ts +4 -1
  109. package/dist/purchasing/receipts.d.ts +396 -0
  110. package/dist/purchasing/receipts.js +287 -0
  111. package/dist/purchasing/returns.d.ts +273 -0
  112. package/dist/purchasing/returns.js +229 -0
  113. package/dist/quotes/index.d.ts +20 -3
  114. package/dist/returns/index.d.ts +62 -1
  115. package/dist/returns/index.js +61 -0
  116. package/dist/seo/config.d.ts +128 -0
  117. package/dist/seo/config.js +95 -0
  118. package/dist/seo/redirects.d.ts +125 -0
  119. package/dist/seo/redirects.js +60 -1
  120. package/dist/seo/settings.d.ts +147 -0
  121. package/dist/seo/settings.js +209 -0
  122. package/dist/shipping/index.d.ts +6 -0
  123. package/dist/shipping/index.js +2 -0
  124. package/dist/shipping/methods.d.ts +8 -1
  125. package/dist/shipping/myparcel.d.ts +415 -1
  126. package/dist/shipping/myparcel.js +291 -3
  127. package/dist/shipping/rate-tables.d.ts +149 -4
  128. package/dist/shipping/rate-tables.js +229 -23
  129. package/dist/shipping/ship-to-countries.d.ts +75 -0
  130. package/dist/shipping/ship-to-countries.js +47 -0
  131. package/dist/stock/by-location.d.ts +86 -0
  132. package/dist/stock/by-location.js +94 -0
  133. package/dist/stock/index.d.ts +8 -1
  134. package/dist/stock/index.js +4 -0
  135. package/dist/stock/settings.d.ts +106 -0
  136. package/dist/stock/settings.js +103 -0
  137. package/dist/stock/transfers.d.ts +171 -10
  138. package/dist/stock/transfers.js +82 -4
  139. package/dist/stored-value/index.d.ts +98 -7
  140. package/dist/stored-value/index.js +71 -4
  141. package/dist/tax/display-policy.d.ts +105 -0
  142. package/dist/tax/display-policy.js +84 -0
  143. package/dist/tax/index.d.ts +3 -0
  144. package/dist/tax/index.js +2 -0
  145. package/dist/tenant/email-templates.d.ts +222 -1
  146. package/dist/tenant/email-templates.js +227 -10
  147. 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
+ }
@@ -6,6 +6,7 @@ import type { Call, ListOptions } from '../types.js';
6
6
  import { type CustomerAgeVerification, type CustomerDateOfBirthModule } from './age-verification.js';
7
7
  import { type CustomerCreditModule } from './credit.js';
8
8
  import { type CustomerDeliveryPreferencesModule } from './delivery-preferences.js';
9
+ import { type CustomerDeliveryTerms } from './delivery-terms.js';
9
10
  import { type CreateCustomerGroupInput, type CustomerB2BGroupsModule, type CustomerGroup, type CustomerGroupListItem, type CustomerGroupListOptions, type CustomerGroupMembersOptions, type CustomerGroupRef, type CustomerGroupWithMembers, type UpdateCustomerGroupInput } from './groups.js';
10
11
  import { type CustomerInvitationStatus } from './invitation-status.js';
11
12
  import { type CustomerNotesModule } from './notes.js';
@@ -1026,6 +1027,20 @@ export interface CustomersModule {
1026
1027
  * @effect read
1027
1028
  */
1028
1029
  savedLists(customerId: string, options?: Call): Promise<CustomerSavedList[] | null>;
1030
+ /**
1031
+ * The customer's delivery terms as an order placed now would take them: the preferred and
1032
+ * allowed shipping methods, the Incoterm, the dispatch lead time and whether a partial delivery
1033
+ * is accepted, from whichever source governs them (an ERP such as Exact Online). `null` when
1034
+ * nothing governs the customer's delivery. A local read of what the source last synchronized;
1035
+ * it calls no ERP.
1036
+ *
1037
+ * Refused `USER_INPUT_ERROR` (`ValidationError`, 400) for a customer this store does not hold
1038
+ * (another store's included). Needs `ReadCustomer`.
1039
+ *
1040
+ * @effect read
1041
+ * @permission ReadCustomer
1042
+ */
1043
+ deliveryTerms(customerId: string, options?: Call): Promise<CustomerDeliveryTerms | null>;
1029
1044
  groups: CustomerGroupsModule;
1030
1045
  addresses: CustomerAddressesModule;
1031
1046
  /** A customer's B2B credit: account, limit, terms, status and what may be invoiced. */
@@ -6,6 +6,7 @@ import { listCustomerOrders, listCustomerRecentOrders, } from '../orders/list.js
6
6
  import { createCustomerDateOfBirthModule, readCustomerAgeVerification, } from './age-verification.js';
7
7
  import { createCustomerCreditModule } from './credit.js';
8
8
  import { createCustomerDeliveryPreferencesModule, } from './delivery-preferences.js';
9
+ import { readCustomerDeliveryTerms } from './delivery-terms.js';
9
10
  import { createCustomerB2BGroupsModule, createCustomerGroup, deleteCustomerGroup, deleteCustomerGroups, listCustomerGroups, readCustomerGroup, updateCustomerGroup, } from './groups.js';
10
11
  import { readCustomerInvitationStatus, } from './invitation-status.js';
11
12
  import { createCustomerNotesModule } from './notes.js';
@@ -616,6 +617,9 @@ export function createCustomersModule(transport) {
616
617
  savedLists(customerId, options) {
617
618
  return readCustomerSavedLists(transport, customerId, options);
618
619
  },
620
+ deliveryTerms(customerId, options) {
621
+ return readCustomerDeliveryTerms(transport, customerId, options);
622
+ },
619
623
  // The answer holds exactly the blocks asked for; `CustomerFor` decides how many of them the
620
624
  // caller's type of `include` may rely on. The overloads narrow this one implementation, which
621
625
  // TypeScript cannot check for them.