@sixtyninecommerce/admin 0.8.0 → 0.10.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 (179) hide show
  1. package/README.md +2739 -17
  2. package/dist/api-tokens/index.d.ts +15 -5
  3. package/dist/api-tokens/index.js +1 -0
  4. package/dist/apps/event-subscription.d.ts +59 -0
  5. package/dist/apps/event-subscription.js +32 -0
  6. package/dist/apps/hook-secret.d.ts +22 -0
  7. package/dist/apps/hook-secret.js +36 -0
  8. package/dist/apps/index.d.ts +54 -3
  9. package/dist/apps/index.js +17 -0
  10. package/dist/audit/index.d.ts +7 -0
  11. package/dist/auth/queries.d.ts +5 -1
  12. package/dist/auth/queries.js +6 -0
  13. package/dist/bulk/index.d.ts +323 -0
  14. package/dist/bulk/index.js +162 -0
  15. package/dist/catalog/assets.d.ts +147 -0
  16. package/dist/catalog/assets.js +164 -0
  17. package/dist/catalog/collections.d.ts +109 -0
  18. package/dist/catalog/collections.js +91 -0
  19. package/dist/catalog/facets.d.ts +103 -0
  20. package/dist/catalog/facets.js +100 -0
  21. package/dist/catalog/index.d.ts +115 -0
  22. package/dist/catalog/index.js +21 -0
  23. package/dist/catalog/options.d.ts +89 -0
  24. package/dist/catalog/options.js +88 -0
  25. package/dist/catalog/products-bulk.d.ts +21 -0
  26. package/dist/catalog/products-bulk.js +58 -0
  27. package/dist/catalog/products.d.ts +650 -0
  28. package/dist/catalog/products.js +458 -0
  29. package/dist/catalog/properties.d.ts +234 -0
  30. package/dist/catalog/properties.js +158 -0
  31. package/dist/catalog/types.d.ts +174 -0
  32. package/dist/catalog/types.js +141 -0
  33. package/dist/catalog/variants.d.ts +324 -0
  34. package/dist/catalog/variants.js +288 -0
  35. package/dist/catalog/visibility.d.ts +57 -0
  36. package/dist/catalog/visibility.js +59 -0
  37. package/dist/client.d.ts +192 -3
  38. package/dist/client.js +87 -12
  39. package/dist/compliance/index.d.ts +88 -0
  40. package/dist/compliance/index.js +88 -0
  41. package/dist/customer-data-redaction.d.ts +14 -0
  42. package/dist/customer-data-redaction.js +22 -0
  43. package/dist/customers/index.d.ts +428 -17
  44. package/dist/customers/index.js +322 -15
  45. package/dist/documents/index.d.ts +513 -16
  46. package/dist/documents/index.js +229 -3
  47. package/dist/events/index.d.ts +259 -0
  48. package/dist/events/index.js +147 -0
  49. package/dist/feeds/ai.d.ts +46 -0
  50. package/dist/feeds/ai.js +43 -0
  51. package/dist/feeds/categories.d.ts +50 -0
  52. package/dist/feeds/categories.js +56 -0
  53. package/dist/feeds/configs.d.ts +54 -0
  54. package/dist/feeds/configs.js +85 -0
  55. package/dist/feeds/identifier-fields.d.ts +25 -0
  56. package/dist/feeds/identifier-fields.js +29 -0
  57. package/dist/feeds/index.d.ts +54 -0
  58. package/dist/feeds/index.js +46 -0
  59. package/dist/feeds/preserve.d.ts +38 -0
  60. package/dist/feeds/preserve.js +74 -0
  61. package/dist/feeds/rules.d.ts +42 -0
  62. package/dist/feeds/rules.js +56 -0
  63. package/dist/feeds/types.d.ts +654 -0
  64. package/dist/feeds/types.js +135 -0
  65. package/dist/fulfillments/index.d.ts +437 -0
  66. package/dist/fulfillments/index.js +534 -0
  67. package/dist/heads/index.d.ts +60 -0
  68. package/dist/heads/index.js +45 -0
  69. package/dist/hooks/assignments.d.ts +90 -0
  70. package/dist/hooks/assignments.js +53 -0
  71. package/dist/hooks/index.d.ts +97 -0
  72. package/dist/hooks/index.js +76 -0
  73. package/dist/index.d.ts +95 -9
  74. package/dist/index.js +21 -1
  75. package/dist/integrations/index.d.ts +14 -0
  76. package/dist/integrations/index.js +9 -0
  77. package/dist/integrations/pipelines.d.ts +113 -0
  78. package/dist/integrations/pipelines.js +62 -0
  79. package/dist/integrations/types.d.ts +176 -0
  80. package/dist/integrations/types.js +8 -0
  81. package/dist/jobs/bulk-action-run.d.ts +74 -0
  82. package/dist/jobs/bulk-action-run.js +69 -0
  83. package/dist/locations/index.d.ts +6 -2
  84. package/dist/meta/index.d.ts +32 -0
  85. package/dist/orders/bulk.d.ts +75 -0
  86. package/dist/orders/bulk.js +150 -0
  87. package/dist/orders/commit-edit.d.ts +123 -0
  88. package/dist/orders/commit-edit.js +142 -0
  89. package/dist/orders/deposit-policy.d.ts +103 -0
  90. package/dist/orders/deposit-policy.js +202 -0
  91. package/dist/orders/get.d.ts +722 -0
  92. package/dist/orders/get.js +530 -0
  93. package/dist/orders/index.d.ts +186 -21
  94. package/dist/orders/index.js +57 -0
  95. package/dist/orders/line-price-preview.d.ts +297 -0
  96. package/dist/orders/line-price-preview.js +50 -0
  97. package/dist/orders/manual-discounts.d.ts +141 -0
  98. package/dist/orders/manual-discounts.js +97 -0
  99. package/dist/orders/manual-pricing.d.ts +192 -0
  100. package/dist/orders/manual-pricing.js +72 -0
  101. package/dist/orders/order-entry.d.ts +566 -0
  102. package/dist/orders/order-entry.js +129 -0
  103. package/dist/orders/parcel-charge.d.ts +45 -0
  104. package/dist/orders/parcel-charge.js +55 -0
  105. package/dist/orders/plan-edit.d.ts +489 -0
  106. package/dist/orders/plan-edit.js +93 -0
  107. package/dist/orders/status.d.ts +235 -0
  108. package/dist/orders/status.js +96 -0
  109. package/dist/orders/surcharges.d.ts +75 -2
  110. package/dist/orders/surcharges.js +110 -4
  111. package/dist/payments/account-credit.d.ts +171 -0
  112. package/dist/payments/account-credit.js +107 -0
  113. package/dist/payments/index.d.ts +241 -9
  114. package/dist/payments/index.js +92 -0
  115. package/dist/payments/methods.d.ts +37 -0
  116. package/dist/payments/methods.js +33 -0
  117. package/dist/platform/catalog.d.ts +148 -0
  118. package/dist/platform/catalog.js +74 -0
  119. package/dist/platform/entitlements.d.ts +111 -0
  120. package/dist/platform/entitlements.js +86 -0
  121. package/dist/platform/legal-entities.d.ts +139 -0
  122. package/dist/platform/legal-entities.js +126 -0
  123. package/dist/platform/legal-entity-numbering.d.ts +103 -0
  124. package/dist/platform/legal-entity-numbering.js +101 -0
  125. package/dist/platform/organization-membership.d.ts +80 -0
  126. package/dist/platform/organization-membership.js +71 -0
  127. package/dist/platform/organization-settings.d.ts +33 -0
  128. package/dist/platform/organization-settings.js +42 -0
  129. package/dist/platform/organization-sharing.d.ts +172 -0
  130. package/dist/platform/organization-sharing.js +120 -0
  131. package/dist/platform/organizations.d.ts +205 -0
  132. package/dist/platform/organizations.js +180 -0
  133. package/dist/platform/provision-store.d.ts +88 -8
  134. package/dist/platform/provision-store.js +12 -0
  135. package/dist/platform/reports.d.ts +46 -0
  136. package/dist/platform/reports.js +31 -0
  137. package/dist/platform/server-metrics.d.ts +58 -0
  138. package/dist/platform/server-metrics.js +11 -0
  139. package/dist/pos/index.d.ts +567 -0
  140. package/dist/pos/index.js +205 -0
  141. package/dist/pricing/index.d.ts +408 -0
  142. package/dist/pricing/index.js +353 -0
  143. package/dist/pricing/vouchers.d.ts +103 -0
  144. package/dist/pricing/vouchers.js +69 -0
  145. package/dist/provenance/index.d.ts +93 -0
  146. package/dist/provenance/index.js +53 -0
  147. package/dist/reports/index.d.ts +199 -0
  148. package/dist/reports/index.js +154 -0
  149. package/dist/returns/index.d.ts +225 -0
  150. package/dist/returns/index.js +160 -0
  151. package/dist/search/index.d.ts +99 -0
  152. package/dist/search/index.js +62 -0
  153. package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
  154. package/dist/shipping/parcel-count-suggestion.js +73 -0
  155. package/dist/shipping/parcel-rule.d.ts +48 -0
  156. package/dist/shipping/parcel-rule.js +91 -0
  157. package/dist/stock/index.d.ts +166 -0
  158. package/dist/stock/index.js +115 -0
  159. package/dist/stock/transfers.d.ts +520 -0
  160. package/dist/stock/transfers.js +437 -0
  161. package/dist/stored-value/index.d.ts +154 -0
  162. package/dist/stored-value/index.js +130 -0
  163. package/dist/tenant/capabilities.d.ts +10 -0
  164. package/dist/tenant/entitlements.d.ts +35 -0
  165. package/dist/tenant/entitlements.js +15 -0
  166. package/dist/tenant/field-groups.d.ts +64 -0
  167. package/dist/tenant/field-groups.js +34 -0
  168. package/dist/tenant/legal-entity.d.ts +32 -0
  169. package/dist/tenant/legal-entity.js +41 -0
  170. package/dist/tenant/notifications.d.ts +148 -0
  171. package/dist/tenant/notifications.js +79 -0
  172. package/dist/tenant/retention.d.ts +10 -4
  173. package/dist/tenant/settings-sources.d.ts +103 -0
  174. package/dist/tenant/settings-sources.js +92 -0
  175. package/dist/types.d.ts +86 -11
  176. package/dist/uploads/index.d.ts +11 -7
  177. package/dist/users/index.d.ts +104 -0
  178. package/dist/users/index.js +80 -0
  179. package/package.json +3 -2
@@ -0,0 +1,458 @@
1
+ import { ValidationError } from '@sixtyninecommerce/core';
2
+ import { bulkProducts, readProductBulkRun, } from './products-bulk.js';
3
+ import { ASSET_REF_FIELDS, assertDeleted, availabilityCustomFields, CATALOGUE_OWNERSHIP_FIELDS, DELETION_FIELDS, FACET_VALUE_REF_FIELDS, fieldOf, unwrapResult, vendureListPage, } from './types.js';
4
+ import { assertPricesStateBasis, } from './variants.js';
5
+ /** The engine's bound on one `exclude` or `include` call (`STORE_EXCLUSION_BATCH_LIMIT`). */
6
+ export const MAX_STORE_EXCLUSION_PRODUCTS = 500;
7
+ /**
8
+ * The engine's bounds on one `setAvailabilityInStores` or `availabilityInStores` call
9
+ * (`AVAILABILITY_*_LIMIT`).
10
+ */
11
+ export const MAX_AVAILABILITY_PRODUCTS = 100;
12
+ export const MAX_AVAILABILITY_STORES = 50;
13
+ /** The engine's bound on each list of one {@link ProductsModule.setCollections} call. */
14
+ export const MAX_PRODUCT_COLLECTION_IDS = 200;
15
+ export const PRODUCT_FIELDS = /* GraphQL */ `
16
+ id
17
+ ${CATALOGUE_OWNERSHIP_FIELDS}
18
+ createdAt
19
+ updatedAt
20
+ languageCode
21
+ name
22
+ slug
23
+ description
24
+ enabled
25
+ featuredAsset { ${ASSET_REF_FIELDS} }
26
+ assets { ${ASSET_REF_FIELDS} }
27
+ facetValues { ${FACET_VALUE_REF_FIELDS} }
28
+ optionGroups {
29
+ id
30
+ code
31
+ name
32
+ options {
33
+ id
34
+ code
35
+ name
36
+ }
37
+ }
38
+ translations {
39
+ id
40
+ languageCode
41
+ name
42
+ slug
43
+ description
44
+ }
45
+ excludedFromStore
46
+ variantList(options: { take: 1 }) {
47
+ totalItems
48
+ }
49
+ customFields {
50
+ availabilityState
51
+ availableFrom
52
+ preorderLimit
53
+ }
54
+ `;
55
+ export const PRODUCTS_QUERY = /* GraphQL */ `
56
+ query CatalogProducts($options: ProductListOptions) {
57
+ products(options: $options) {
58
+ items { ${PRODUCT_FIELDS} }
59
+ totalItems
60
+ }
61
+ }
62
+ `;
63
+ export const PRODUCT_QUERY = /* GraphQL */ `
64
+ query CatalogProduct($id: ID!) {
65
+ product(id: $id) { ${PRODUCT_FIELDS} }
66
+ }
67
+ `;
68
+ export const CREATE_PRODUCT_MUTATION = /* GraphQL */ `
69
+ mutation CatalogCreateProduct($input: CreateProductInput!) {
70
+ createProduct(input: $input) { ${PRODUCT_FIELDS} }
71
+ }
72
+ `;
73
+ export const UPDATE_PRODUCT_MUTATION = /* GraphQL */ `
74
+ mutation CatalogUpdateProduct($input: UpdateProductInput!) {
75
+ updateProduct(input: $input) { ${PRODUCT_FIELDS} }
76
+ }
77
+ `;
78
+ export const DELETE_PRODUCT_MUTATION = /* GraphQL */ `
79
+ mutation CatalogDeleteProduct($id: ID!) {
80
+ deleteProduct(id: $id) { ${DELETION_FIELDS} }
81
+ }
82
+ `;
83
+ export const CLONE_PRODUCT_MUTATION = /* GraphQL */ `
84
+ mutation CatalogCloneProduct($id: ID!) {
85
+ cloneProduct(id: $id)
86
+ }
87
+ `;
88
+ export const ADD_OPTION_GROUP_MUTATION = /* GraphQL */ `
89
+ mutation CatalogAddOptionGroup($productId: ID!, $optionGroupId: ID!) {
90
+ addOptionGroupToProduct(productId: $productId, optionGroupId: $optionGroupId) { ${PRODUCT_FIELDS} }
91
+ }
92
+ `;
93
+ export const REMOVE_OPTION_GROUP_MUTATION = /* GraphQL */ `
94
+ mutation CatalogRemoveOptionGroup($productId: ID!, $optionGroupId: ID!, $force: Boolean) {
95
+ removeOptionGroupFromProduct(productId: $productId, optionGroupId: $optionGroupId, force: $force) {
96
+ ... on Product { ${PRODUCT_FIELDS} }
97
+ ... on ErrorResult {
98
+ errorCode
99
+ message
100
+ }
101
+ }
102
+ }
103
+ `;
104
+ export const EXCLUDE_PRODUCTS_FROM_STORE_MUTATION = /* GraphQL */ `
105
+ mutation CatalogExcludeProductsFromStore($productIds: [ID!]!) {
106
+ excludeProductsFromStore(productIds: $productIds) { ${PRODUCT_FIELDS} }
107
+ }
108
+ `;
109
+ export const INCLUDE_PRODUCTS_IN_STORE_MUTATION = /* GraphQL */ `
110
+ mutation CatalogIncludeProductsInStore($productIds: [ID!]!) {
111
+ includeProductsInStore(productIds: $productIds) { ${PRODUCT_FIELDS} }
112
+ }
113
+ `;
114
+ export const SET_ORGANIZATION_PRODUCT_AVAILABILITY_MUTATION = /* GraphQL */ `
115
+ mutation CatalogSetOrganizationProductAvailability(
116
+ $productIds: [ID!]!
117
+ $channelIds: [ID!]!
118
+ $available: Boolean!
119
+ ) {
120
+ setOrganizationProductAvailability(
121
+ productIds: $productIds
122
+ channelIds: $channelIds
123
+ available: $available
124
+ ) {
125
+ productId
126
+ channelId
127
+ action
128
+ }
129
+ }
130
+ `;
131
+ export const ORGANIZATION_PRODUCT_AVAILABILITY_QUERY = /* GraphQL */ `
132
+ query CatalogOrganizationProductAvailability($productIds: [ID!]!, $channelIds: [ID!]!) {
133
+ organizationProductAvailability(productIds: $productIds, channelIds: $channelIds) {
134
+ productId
135
+ channelId
136
+ state
137
+ localCopyId
138
+ }
139
+ }
140
+ `;
141
+ export const MAKE_LOCAL_COPY_MUTATION = /* GraphQL */ `
142
+ mutation CatalogMakeLocalCopy($productId: ID!, $skus: [LocalCopySkuInput!]) {
143
+ makeLocalCopy(productId: $productId, skus: $skus) {
144
+ product { ${PRODUCT_FIELDS} }
145
+ originalProductId
146
+ original
147
+ droppedOrganizationPropertyCount
148
+ }
149
+ }
150
+ `;
151
+ export const SET_PRODUCT_COLLECTIONS_MUTATION = /* GraphQL */ `
152
+ mutation CatalogSetProductCollections(
153
+ $productId: ID!
154
+ $collectionIds: [ID!]!
155
+ $fromCollectionIds: [ID!]
156
+ ) {
157
+ setProductCollections(
158
+ productId: $productId
159
+ collectionIds: $collectionIds
160
+ fromCollectionIds: $fromCollectionIds
161
+ ) {
162
+ added
163
+ removed
164
+ unchanged
165
+ notRemoved {
166
+ collectionId
167
+ reason
168
+ }
169
+ }
170
+ }
171
+ `;
172
+ export const SAVE_PRODUCT_MUTATION = /* GraphQL */ `
173
+ mutation CatalogSaveProduct($input: SaveProductInput!, $idempotencyKey: String) {
174
+ saveProduct(input: $input, idempotencyKey: $idempotencyKey) {
175
+ product { ${PRODUCT_FIELDS} }
176
+ collections {
177
+ added
178
+ removed
179
+ unchanged
180
+ notRemoved {
181
+ collectionId
182
+ reason
183
+ }
184
+ }
185
+ warnings {
186
+ code
187
+ message
188
+ propertyIds
189
+ }
190
+ }
191
+ }
192
+ `;
193
+ /**
194
+ * Lift the availability custom fields to first-class fields. A server older
195
+ * than the pre-order engine has no such fields and would refuse the document
196
+ * outright, so the defaults below only cover a NULL value, never an absent one.
197
+ */
198
+ export function toProduct(wire) {
199
+ const { variantList, customFields, ...rest } = wire;
200
+ return {
201
+ ...rest,
202
+ variantCount: variantList.totalItems,
203
+ availabilityState: customFields?.availabilityState ?? 'none',
204
+ availableFrom: customFields?.availableFrom ?? null,
205
+ preorderLimit: customFields?.preorderLimit ?? null,
206
+ };
207
+ }
208
+ function toWireInput(input) {
209
+ const { availabilityState, availableFrom, preorderLimit, brand, condition, ...rest } = input;
210
+ const customFields = availabilityCustomFields({
211
+ availabilityState,
212
+ availableFrom,
213
+ preorderLimit,
214
+ });
215
+ if (brand !== undefined)
216
+ customFields.brand = brand;
217
+ if (condition !== undefined)
218
+ customFields.condition = condition;
219
+ return Object.keys(customFields).length > 0 ? { ...rest, customFields } : rest;
220
+ }
221
+ export function productFilter(filter) {
222
+ const out = {};
223
+ if (!filter)
224
+ return out;
225
+ if (filter.ids !== undefined)
226
+ out.id = { in: filter.ids };
227
+ if (filter.name !== undefined)
228
+ out.name = { contains: filter.name };
229
+ if (filter.sku !== undefined)
230
+ out.sku = { contains: filter.sku };
231
+ if (filter.enabled !== undefined)
232
+ out.enabled = { eq: filter.enabled };
233
+ if (filter.facetValueIds !== undefined)
234
+ out.facetValueId = { in: filter.facetValueIds };
235
+ if (filter.availabilityState !== undefined) {
236
+ out.availabilityState = { eq: filter.availabilityState };
237
+ }
238
+ return out;
239
+ }
240
+ function refuse(code, message) {
241
+ return new ValidationError({ code, message, status: 400, service: 'vendure' });
242
+ }
243
+ /** Distinct ids the way the engine counts them: it stores a uuid lower-case. */
244
+ function distinctCount(ids) {
245
+ return new Set(ids.map((id) => String(id).toLowerCase())).size;
246
+ }
247
+ function assertExclusionBatch(productIds) {
248
+ const count = distinctCount(productIds);
249
+ if (count <= MAX_STORE_EXCLUSION_PRODUCTS)
250
+ return;
251
+ throw refuse('STORE_EXCLUSION_INPUT_INVALID', `Exclude or include at most ${String(MAX_STORE_EXCLUSION_PRODUCTS)} products per call; this one named ${String(count)}. Split it into smaller calls. Nothing was sent.`);
252
+ }
253
+ function assertAvailabilityInput(input, verb = 'set') {
254
+ const bounds = [
255
+ ['products', input?.productIds, MAX_AVAILABILITY_PRODUCTS],
256
+ ['stores', input?.channelIds, MAX_AVAILABILITY_STORES],
257
+ ];
258
+ for (const [noun, ids, limit] of bounds) {
259
+ if (!Array.isArray(ids) || ids.length === 0) {
260
+ throw refuse('ORGANIZATION_AVAILABILITY_INPUT_INVALID', `Name at least one of the ${noun}. Nothing was sent.`);
261
+ }
262
+ const count = distinctCount(ids);
263
+ if (count > limit) {
264
+ throw refuse('ORGANIZATION_AVAILABILITY_INPUT_INVALID', `At most ${String(limit)} ${noun} can be ${verb} in one call; this one named ${String(count)}. Split it into smaller calls. Nothing was sent.`);
265
+ }
266
+ }
267
+ if (verb === 'set' && typeof input.available !== 'boolean') {
268
+ throw refuse('ORGANIZATION_AVAILABILITY_INPUT_INVALID', '`available` must be true (offer the products) or false (stop offering them). Nothing was sent.');
269
+ }
270
+ }
271
+ /** The engine's check of each list (`PRODUCT_COLLECTIONS_INPUT_INVALID`), before sending. */
272
+ function assertCollectionLists(lists) {
273
+ for (const [argument, ids] of Object.entries(lists)) {
274
+ if (ids === undefined)
275
+ continue;
276
+ if (!Array.isArray(ids)) {
277
+ throw refuse('PRODUCT_COLLECTIONS_INPUT_INVALID', `${argument} must be a list of collection ids. Nothing was sent.`);
278
+ }
279
+ if (ids.length > MAX_PRODUCT_COLLECTION_IDS) {
280
+ throw refuse('PRODUCT_COLLECTIONS_INPUT_INVALID', `Name at most ${String(MAX_PRODUCT_COLLECTION_IDS)} collections in ${argument}; this call named ${String(ids.length)}. Nothing was sent.`);
281
+ }
282
+ if (distinctCount(ids) !== ids.length) {
283
+ throw refuse('PRODUCT_COLLECTIONS_INPUT_INVALID', `${argument} names a collection more than once. Name each collection once. Nothing was sent.`);
284
+ }
285
+ }
286
+ }
287
+ export function createProductsModule(transport) {
288
+ const write = async (document, field, variables, options) => {
289
+ return toProduct(await fieldOf(transport, document, field, variables, options));
290
+ };
291
+ return {
292
+ async list(options) {
293
+ const filter = productFilter(options?.filter);
294
+ return vendureListPage(transport, PRODUCTS_QUERY, 'products', (skip, take) => ({
295
+ options: {
296
+ skip,
297
+ take,
298
+ ...(Object.keys(filter).length > 0 ? { filter, filterOperator: 'AND' } : {}),
299
+ ...(options?.sort ? { sort: { [options.sort.field]: options.sort.direction } } : {}),
300
+ },
301
+ }), options, toProduct);
302
+ },
303
+ async get(id, options) {
304
+ const product = await fieldOf(transport, PRODUCT_QUERY, 'product', { id }, options);
305
+ return product ? toProduct(product) : null;
306
+ },
307
+ // `async` so every refusal below — including `toWireInput`'s datetime
308
+ // check — arrives as a rejection, never as a synchronous throw a caller's
309
+ // `.catch()` would miss.
310
+ async create(input, options) {
311
+ if (!Array.isArray(input.translations) || input.translations.length === 0) {
312
+ throw new ValidationError({
313
+ code: 'INVALID_PRODUCT_INPUT',
314
+ message: 'A product needs at least one translation (languageCode + name).',
315
+ status: 400,
316
+ service: 'vendure',
317
+ });
318
+ }
319
+ return write(CREATE_PRODUCT_MUTATION, 'createProduct', { input: toWireInput(input) }, options);
320
+ },
321
+ async update(input, options) {
322
+ return write(UPDATE_PRODUCT_MUTATION, 'updateProduct', { input: toWireInput(input) }, options);
323
+ },
324
+ async delete(id, options) {
325
+ assertDeleted('PRODUCT_NOT_DELETED', [
326
+ await fieldOf(transport, DELETE_PRODUCT_MUTATION, 'deleteProduct', { id }, options),
327
+ ]);
328
+ },
329
+ async clone(id, options) {
330
+ return fieldOf(transport, CLONE_PRODUCT_MUTATION, 'cloneProduct', { id }, options);
331
+ },
332
+ addOptionGroup(productId, optionGroupId, options) {
333
+ return write(ADD_OPTION_GROUP_MUTATION, 'addOptionGroupToProduct', { productId, optionGroupId }, options);
334
+ },
335
+ async removeOptionGroup(productId, optionGroupId, options) {
336
+ const result = await fieldOf(transport, REMOVE_OPTION_GROUP_MUTATION, 'removeOptionGroupFromProduct', { productId, optionGroupId, force: options?.force ?? false }, options);
337
+ return toProduct(unwrapResult(result));
338
+ },
339
+ async exclude(productIds, options) {
340
+ assertExclusionBatch(productIds);
341
+ if (productIds.length === 0)
342
+ return [];
343
+ const rows = await fieldOf(transport, EXCLUDE_PRODUCTS_FROM_STORE_MUTATION, 'excludeProductsFromStore', { productIds }, options);
344
+ return rows.map(toProduct);
345
+ },
346
+ async include(productIds, options) {
347
+ assertExclusionBatch(productIds);
348
+ if (productIds.length === 0)
349
+ return [];
350
+ const rows = await fieldOf(transport, INCLUDE_PRODUCTS_IN_STORE_MUTATION, 'includeProductsInStore', { productIds }, options);
351
+ return rows.map(toProduct);
352
+ },
353
+ async setAvailabilityInStores(input, options) {
354
+ assertAvailabilityInput(input);
355
+ return fieldOf(transport, SET_ORGANIZATION_PRODUCT_AVAILABILITY_MUTATION, 'setOrganizationProductAvailability', {
356
+ productIds: input.productIds,
357
+ channelIds: input.channelIds,
358
+ available: input.available,
359
+ }, options);
360
+ },
361
+ async availabilityInStores(input, options) {
362
+ assertAvailabilityInput(input, 'read');
363
+ return fieldOf(transport, ORGANIZATION_PRODUCT_AVAILABILITY_QUERY, 'organizationProductAvailability', { productIds: input.productIds, channelIds: input.channelIds }, options);
364
+ },
365
+ async makeLocalCopy(productId, options) {
366
+ const { skus, ...call } = options ?? {};
367
+ const result = await fieldOf(transport, MAKE_LOCAL_COPY_MUTATION, 'makeLocalCopy', {
368
+ productId,
369
+ skus: skus === undefined
370
+ ? null
371
+ : skus.map(({ productVariantId, sku }) => ({ productVariantId, sku })),
372
+ }, call);
373
+ return { ...result, product: toProduct(result.product) };
374
+ },
375
+ async setCollections(productId, collectionIds, options) {
376
+ const { from, ...call } = options ?? {};
377
+ assertCollectionLists({ collectionIds, fromCollectionIds: from });
378
+ return fieldOf(transport, SET_PRODUCT_COLLECTIONS_MUTATION, 'setProductCollections', {
379
+ productId,
380
+ collectionIds: [...collectionIds],
381
+ fromCollectionIds: from === undefined ? null : [...from],
382
+ }, call);
383
+ },
384
+ async save(input, options) {
385
+ assertCollectionLists({
386
+ collectionIds: input.collectionIds,
387
+ fromCollectionIds: input.fromCollectionIds,
388
+ });
389
+ await assertPricesStateBasis(transport, input.variants ?? [], options);
390
+ const result = await fieldOf(transport, SAVE_PRODUCT_MUTATION, 'saveProduct', { input: toWireSaveInput(input), idempotencyKey: options?.idempotencyKey ?? null }, options);
391
+ return { ...result, product: toProduct(result.product) };
392
+ },
393
+ bulk(input, options) {
394
+ return bulkProducts(transport, input, options);
395
+ },
396
+ bulkRun(runId, options) {
397
+ return readProductBulkRun(transport, runId, options);
398
+ },
399
+ };
400
+ }
401
+ /** The variant fields a save writes as custom fields on the wire. */
402
+ const SAVE_VARIANT_CUSTOM_FIELDS = [
403
+ 'minOrderQuantity',
404
+ 'salesUnit',
405
+ 'cost',
406
+ 'packageWeightGrams',
407
+ 'packageLengthCm',
408
+ 'packageWidthCm',
409
+ 'packageHeightCm',
410
+ 'parcelCapacity',
411
+ 'parcelPacking',
412
+ 'backorderPolicy',
413
+ 'backorderLimit',
414
+ 'gtin',
415
+ 'mpn',
416
+ ];
417
+ /** The SDK's flat save as the engine's `SaveProductInput`: custom fields nested, no basis. */
418
+ function toWireSaveInput(input) {
419
+ const wire = { id: input.id, languageCode: input.languageCode };
420
+ if (input.product) {
421
+ const { availabilityState, availableFrom, preorderLimit, brand, condition, ...rest } = input.product;
422
+ const customFields = availabilityCustomFields({
423
+ availabilityState,
424
+ availableFrom,
425
+ preorderLimit,
426
+ });
427
+ if (brand !== undefined)
428
+ customFields.brand = brand;
429
+ if (condition !== undefined)
430
+ customFields.condition = condition;
431
+ wire.product = Object.keys(customFields).length > 0 ? { ...rest, customFields } : rest;
432
+ }
433
+ if (input.variants) {
434
+ wire.variants = input.variants.map((variant) => {
435
+ const { availabilityState, availableFrom, preorderLimit, priceBasis, ...rest } = variant;
436
+ void priceBasis;
437
+ const customFields = availabilityCustomFields({
438
+ availabilityState,
439
+ availableFrom,
440
+ preorderLimit,
441
+ });
442
+ const out = { ...rest };
443
+ for (const key of SAVE_VARIANT_CUSTOM_FIELDS) {
444
+ if (key in out) {
445
+ if (out[key] !== undefined)
446
+ customFields[key] = out[key];
447
+ delete out[key];
448
+ }
449
+ }
450
+ return Object.keys(customFields).length > 0 ? { ...out, customFields } : out;
451
+ });
452
+ }
453
+ for (const key of ['collectionIds', 'fromCollectionIds', 'propertyValues']) {
454
+ if (input[key] !== undefined)
455
+ wire[key] = input[key];
456
+ }
457
+ return wire;
458
+ }