@sixtyninecommerce/admin 0.7.0 → 0.9.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 (161) hide show
  1. package/README.md +2445 -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 +489 -0
  28. package/dist/catalog/products.js +365 -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 +274 -0
  34. package/dist/catalog/variants.js +258 -0
  35. package/dist/catalog/visibility.d.ts +57 -0
  36. package/dist/catalog/visibility.js +59 -0
  37. package/dist/client.d.ts +171 -3
  38. package/dist/client.js +81 -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 +360 -0
  66. package/dist/fulfillments/index.js +425 -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 +84 -9
  74. package/dist/index.js +16 -1
  75. package/dist/jobs/bulk-action-run.d.ts +65 -0
  76. package/dist/jobs/bulk-action-run.js +70 -0
  77. package/dist/locations/index.d.ts +6 -2
  78. package/dist/meta/index.d.ts +32 -0
  79. package/dist/orders/commit-edit.d.ts +123 -0
  80. package/dist/orders/commit-edit.js +142 -0
  81. package/dist/orders/get.d.ts +722 -0
  82. package/dist/orders/get.js +530 -0
  83. package/dist/orders/index.d.ts +105 -20
  84. package/dist/orders/index.js +44 -0
  85. package/dist/orders/line-price-preview.d.ts +297 -0
  86. package/dist/orders/line-price-preview.js +50 -0
  87. package/dist/orders/manual-discounts.d.ts +132 -0
  88. package/dist/orders/manual-discounts.js +95 -0
  89. package/dist/orders/manual-pricing.d.ts +192 -0
  90. package/dist/orders/manual-pricing.js +72 -0
  91. package/dist/orders/order-entry.d.ts +566 -0
  92. package/dist/orders/order-entry.js +129 -0
  93. package/dist/orders/parcel-charge.d.ts +45 -0
  94. package/dist/orders/parcel-charge.js +55 -0
  95. package/dist/orders/plan-edit.d.ts +489 -0
  96. package/dist/orders/plan-edit.js +93 -0
  97. package/dist/orders/status.d.ts +235 -0
  98. package/dist/orders/status.js +96 -0
  99. package/dist/orders/surcharges.d.ts +75 -2
  100. package/dist/orders/surcharges.js +110 -4
  101. package/dist/payments/account-credit.d.ts +171 -0
  102. package/dist/payments/account-credit.js +107 -0
  103. package/dist/payments/index.d.ts +368 -2
  104. package/dist/payments/index.js +138 -0
  105. package/dist/platform/catalog.d.ts +148 -0
  106. package/dist/platform/catalog.js +74 -0
  107. package/dist/platform/entitlements.d.ts +111 -0
  108. package/dist/platform/entitlements.js +86 -0
  109. package/dist/platform/legal-entities.d.ts +139 -0
  110. package/dist/platform/legal-entities.js +126 -0
  111. package/dist/platform/legal-entity-numbering.d.ts +103 -0
  112. package/dist/platform/legal-entity-numbering.js +101 -0
  113. package/dist/platform/organization-membership.d.ts +80 -0
  114. package/dist/platform/organization-membership.js +71 -0
  115. package/dist/platform/organization-settings.d.ts +33 -0
  116. package/dist/platform/organization-settings.js +42 -0
  117. package/dist/platform/organization-sharing.d.ts +172 -0
  118. package/dist/platform/organization-sharing.js +120 -0
  119. package/dist/platform/organizations.d.ts +205 -0
  120. package/dist/platform/organizations.js +180 -0
  121. package/dist/platform/provision-store.d.ts +88 -8
  122. package/dist/platform/provision-store.js +12 -0
  123. package/dist/platform/reports.d.ts +46 -0
  124. package/dist/platform/reports.js +31 -0
  125. package/dist/platform/server-metrics.d.ts +58 -0
  126. package/dist/platform/server-metrics.js +11 -0
  127. package/dist/pos/index.d.ts +567 -0
  128. package/dist/pos/index.js +205 -0
  129. package/dist/pricing/index.d.ts +405 -0
  130. package/dist/pricing/index.js +351 -0
  131. package/dist/provenance/index.d.ts +93 -0
  132. package/dist/provenance/index.js +53 -0
  133. package/dist/reports/index.d.ts +199 -0
  134. package/dist/reports/index.js +154 -0
  135. package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
  136. package/dist/shipping/parcel-count-suggestion.js +73 -0
  137. package/dist/shipping/parcel-rule.d.ts +48 -0
  138. package/dist/shipping/parcel-rule.js +91 -0
  139. package/dist/stock/index.d.ts +166 -0
  140. package/dist/stock/index.js +115 -0
  141. package/dist/stock/transfers.d.ts +520 -0
  142. package/dist/stock/transfers.js +437 -0
  143. package/dist/stored-value/index.d.ts +154 -0
  144. package/dist/stored-value/index.js +130 -0
  145. package/dist/tenant/capabilities.d.ts +10 -0
  146. package/dist/tenant/entitlements.d.ts +35 -0
  147. package/dist/tenant/entitlements.js +15 -0
  148. package/dist/tenant/field-groups.d.ts +64 -0
  149. package/dist/tenant/field-groups.js +34 -0
  150. package/dist/tenant/legal-entity.d.ts +32 -0
  151. package/dist/tenant/legal-entity.js +41 -0
  152. package/dist/tenant/notifications.d.ts +148 -0
  153. package/dist/tenant/notifications.js +79 -0
  154. package/dist/tenant/retention.d.ts +10 -4
  155. package/dist/tenant/settings-sources.d.ts +103 -0
  156. package/dist/tenant/settings-sources.js +92 -0
  157. package/dist/types.d.ts +86 -11
  158. package/dist/uploads/index.d.ts +11 -7
  159. package/dist/users/index.d.ts +104 -0
  160. package/dist/users/index.js +80 -0
  161. package/package.json +3 -2
@@ -0,0 +1,351 @@
1
+ import { createPage, errorFromErrorResult, isErrorResult, NotFoundError, ValidationError, } from '@sixtyninecommerce/core';
2
+ /**
3
+ * The targeting conditions (incentives sprint 2), encoded as the engine stores
4
+ * them — every argument a string, lists JSON-encoded. Their argument names and
5
+ * shapes are a stored contract.
6
+ */
7
+ export const promotionConditions = {
8
+ /**
9
+ * `selling_location`: the order is sold from one of these stock locations of
10
+ * the store — a till there, an order `orders.createOrRecover` recorded for it
11
+ * (`location`), or a register sending it as `x-location` on the Admin API. A
12
+ * web order has no location and never matches.
13
+ */
14
+ sellingLocation(locationIds) {
15
+ return {
16
+ code: 'selling_location',
17
+ arguments: [{ name: 'locationIds', value: JSON.stringify([...locationIds]) }],
18
+ };
19
+ },
20
+ /**
21
+ * `time_window`: on one of `days`, from `from` (included) to `until`
22
+ * (excluded), local time — the selling location's profile zone, else the
23
+ * store's. An `until` not after `from` runs into the next day and belongs to
24
+ * the day it opened (`fri 22:00–02:00`); equal times are 24 hours
25
+ * (`00:00`–`00:00`: all day). Judged on the wall clock, so a window in the
26
+ * hour clocks repeat in autumn is open twice, one in the hour they skip in
27
+ * spring never opens. A placed order keeps a window it was placed with.
28
+ */
29
+ timeWindow(window) {
30
+ return {
31
+ code: 'time_window',
32
+ arguments: [
33
+ { name: 'daysOfWeek', value: JSON.stringify([...window.days]) },
34
+ { name: 'startTime', value: window.from },
35
+ { name: 'endTime', value: window.until },
36
+ ],
37
+ };
38
+ },
39
+ /**
40
+ * `first_order`: the customer has no earlier placed order in this store. An
41
+ * order with no customer, or the till's walk-in, never matches.
42
+ */
43
+ firstOrder() {
44
+ return { code: 'first_order', arguments: [] };
45
+ },
46
+ /**
47
+ * `bundle_fixed_price` (incentives sprint 4): each complete set of
48
+ * `components` costs `price` — minor units of the store's DEFAULT currency, in
49
+ * the store's tax basis (as its catalogue prices are entered), converted into
50
+ * an order's currency at the checkout's rate. The discount is spread over the
51
+ * set's lines by value; a set worth `price` or less stays at list price. Pair
52
+ * it with {@link promotionActions}`.bundleFixedPrice()`.
53
+ *
54
+ * `price` is required and must be a whole amount above 0: anything else throws
55
+ * `ValidationError` (`PROMOTION_CONDITION_INVALID`) before a request is sent,
56
+ * as the engine would refuse it. To give sets away use
57
+ * {@link promotionConditions}`.nthSetPercentage` with `discount: 100`.
58
+ */
59
+ bundleFixedPrice(set) {
60
+ assertSetPrice('bundle_fixed_price', set.price);
61
+ return {
62
+ code: 'bundle_fixed_price',
63
+ arguments: [
64
+ { name: 'components', value: componentsValue(set.components) },
65
+ { name: 'price', value: String(set.price) },
66
+ ],
67
+ };
68
+ },
69
+ /**
70
+ * `nth_set_percentage`: of every `nth` complete sets, the cheapest is
71
+ * `discount`% off ("second pair at 50%" is `nth: 2, discount: 50`; `nth: 1`
72
+ * discounts every set). Pair it with {@link promotionActions}`.nthSetPercentage()`.
73
+ */
74
+ nthSetPercentage(set) {
75
+ return {
76
+ code: 'nth_set_percentage',
77
+ arguments: [
78
+ { name: 'components', value: componentsValue(set.components) },
79
+ { name: 'nth', value: String(set.nth) },
80
+ { name: 'discount', value: String(set.discount) },
81
+ ],
82
+ };
83
+ },
84
+ /**
85
+ * `mix_and_match`: any `quantity` units matching the selector (all of
86
+ * `facetValueIds`, any of `collectionIds`) for `price`, per complete group;
87
+ * leftover units stay at list price. `price` as in `bundleFixedPrice`
88
+ * (required, above 0, refused before sending otherwise). Pair it with
89
+ * {@link promotionActions}`.mixAndMatch()`.
90
+ */
91
+ mixAndMatch(group) {
92
+ assertSetPrice('mix_and_match', group.price);
93
+ return {
94
+ code: 'mix_and_match',
95
+ arguments: [
96
+ { name: 'facetValueIds', value: JSON.stringify([...(group.facetValueIds ?? [])]) },
97
+ { name: 'collectionIds', value: JSON.stringify([...(group.collectionIds ?? [])]) },
98
+ { name: 'quantity', value: String(group.quantity) },
99
+ { name: 'price', value: String(group.price) },
100
+ ],
101
+ };
102
+ },
103
+ };
104
+ /**
105
+ * A set or group price as the engine accepts it: a whole number of minor units above 0. A 0
106
+ * would make every set free (the engine refuses it on save and discounts nothing for one it
107
+ * finds stored), so it is refused here, before a request is sent, with the engine's own code.
108
+ */
109
+ function assertSetPrice(condition, price) {
110
+ if (Number.isSafeInteger(price) && price >= 1 && price <= 2_147_483_647)
111
+ return;
112
+ const message = `The "${condition}" condition's price must be a whole amount in minor units above 0; got ${String(price)}. To give sets away, use promotionConditions.nthSetPercentage with discount: 100.`;
113
+ throw new ValidationError({
114
+ code: 'PROMOTION_CONDITION_INVALID',
115
+ message,
116
+ status: 400,
117
+ service: 'vendure',
118
+ details: [{ field: 'price', code: 'PROMOTION_CONDITION_INVALID', message }],
119
+ });
120
+ }
121
+ function componentsValue(components) {
122
+ return JSON.stringify(components.map((component) => ({
123
+ facetValueIds: [...(component.facetValueIds ?? [])],
124
+ collectionIds: [...(component.collectionIds ?? [])],
125
+ quantity: component.quantity,
126
+ })));
127
+ }
128
+ /**
129
+ * The set actions (incentives sprint 4). Each takes no arguments and applies
130
+ * what its same-code condition computed, so it goes on the promotion together
131
+ * with that condition: the engine refuses one without the other.
132
+ */
133
+ export const promotionActions = {
134
+ bundleFixedPrice() {
135
+ return { code: 'bundle_fixed_price', arguments: [] };
136
+ },
137
+ nthSetPercentage() {
138
+ return { code: 'nth_set_percentage', arguments: [] };
139
+ },
140
+ mixAndMatch() {
141
+ return { code: 'mix_and_match', arguments: [] };
142
+ },
143
+ };
144
+ const DISCOUNT_FIELDS = 'promotionId adjustmentSource type description amount amountWithTax';
145
+ /** An order's pricing readout; the manual pricing doors (`orders.setLinePrices`, …) answer it too. */
146
+ export const ORDER_DISCOUNTS_FIELDS = /* GraphQL */ `
147
+ id
148
+ code
149
+ state
150
+ currencyCode
151
+ couponCodes
152
+ subTotal
153
+ subTotalWithTax
154
+ shipping
155
+ shippingWithTax
156
+ total
157
+ totalWithTax
158
+ discounts { ${DISCOUNT_FIELDS} }
159
+ promotions { id name }
160
+ lines {
161
+ id
162
+ quantity
163
+ productVariant { id sku name }
164
+ unitPrice
165
+ unitPriceWithTax
166
+ linePrice
167
+ linePriceWithTax
168
+ discountedLinePrice
169
+ discountedLinePriceWithTax
170
+ proratedLinePrice
171
+ proratedLinePriceWithTax
172
+ discounts { ${DISCOUNT_FIELDS} }
173
+ }
174
+ surcharges { id description sku price priceWithTax }
175
+ packages { promotionId label price priceWithTax lineIds quantity }
176
+ `;
177
+ const OPERATION_FIELDS = 'code args { name value } summary';
178
+ export const ACTIVE_PROMOTIONS_QUERY = /* GraphQL */ `
179
+ query ActivePromotions($locationId: ID, $customerId: ID) {
180
+ activePromotions(locationId: $locationId, customerId: $customerId) {
181
+ id
182
+ name
183
+ description
184
+ startsAt
185
+ endsAt
186
+ requiresCouponCode
187
+ usageLimit
188
+ perCustomerUsageLimit
189
+ conditions { ${OPERATION_FIELDS} }
190
+ actions { ${OPERATION_FIELDS} }
191
+ summary
192
+ }
193
+ }
194
+ `;
195
+ const PROMOTION_FIELDS = /* GraphQL */ `
196
+ id
197
+ name
198
+ description
199
+ enabled
200
+ couponCode
201
+ startsAt
202
+ endsAt
203
+ usageLimit
204
+ perCustomerUsageLimit
205
+ conditions { code args { name value } }
206
+ actions { code args { name value } }
207
+ customFields { combineWithB2b combineWithOtherPromotions priority }
208
+ `;
209
+ export const CREATE_PROMOTION_MUTATION = /* GraphQL */ `
210
+ mutation CreatePromotion($input: CreatePromotionInput!) {
211
+ createPromotion(input: $input) {
212
+ ... on Promotion { ${PROMOTION_FIELDS} }
213
+ ... on ErrorResult { errorCode message }
214
+ }
215
+ }
216
+ `;
217
+ export const UPDATE_PROMOTION_MUTATION = /* GraphQL */ `
218
+ mutation UpdatePromotion($input: UpdatePromotionInput!) {
219
+ updatePromotion(input: $input) {
220
+ ... on Promotion { ${PROMOTION_FIELDS} }
221
+ ... on ErrorResult { errorCode message }
222
+ }
223
+ }
224
+ `;
225
+ /** The condition the dashboard and the admin app put on every promotion they save. */
226
+ export const STACKING_GUARD_CONDITION = 'promotion_stacking_guard';
227
+ function withStackingGuard(conditions) {
228
+ return conditions.some((condition) => condition.code === STACKING_GUARD_CONDITION)
229
+ ? conditions
230
+ : [{ code: STACKING_GUARD_CONDITION, arguments: [] }, ...conditions];
231
+ }
232
+ /** The SDK input as the engine's `Create`/`UpdatePromotionInput`. */
233
+ function promotionInputOf(input) {
234
+ const { combineWithB2b, combineWithOtherPromotions, priority, conditions, ...rest } = input;
235
+ const customFields = {
236
+ ...(combineWithB2b === undefined ? {} : { combineWithB2b }),
237
+ ...(combineWithOtherPromotions === undefined ? {} : { combineWithOtherPromotions }),
238
+ ...(priority === undefined ? {} : { priority }),
239
+ };
240
+ return {
241
+ ...rest,
242
+ ...(conditions === undefined ? {} : { conditions: withStackingGuard(conditions) }),
243
+ ...(Object.keys(customFields).length === 0 ? {} : { customFields }),
244
+ };
245
+ }
246
+ export const APPLY_COUPON_CODE_TO_ORDER_MUTATION = /* GraphQL */ `
247
+ mutation ApplyCouponCodeToOrder($orderId: ID!, $couponCode: String!, $idempotencyKey: String) {
248
+ applyCouponCodeToOrder(
249
+ orderId: $orderId
250
+ couponCode: $couponCode
251
+ idempotencyKey: $idempotencyKey
252
+ ) {
253
+ ${ORDER_DISCOUNTS_FIELDS}
254
+ }
255
+ }
256
+ `;
257
+ export const REMOVE_COUPON_CODE_FROM_ORDER_MUTATION = /* GraphQL */ `
258
+ mutation RemoveCouponCodeFromOrder($orderId: ID!, $couponCode: String!, $idempotencyKey: String) {
259
+ removeCouponCodeFromOrder(
260
+ orderId: $orderId
261
+ couponCode: $couponCode
262
+ idempotencyKey: $idempotencyKey
263
+ ) {
264
+ ${ORDER_DISCOUNTS_FIELDS}
265
+ }
266
+ }
267
+ `;
268
+ export const ORDER_DISCOUNTS_QUERY = /* GraphQL */ `
269
+ query OrderDiscounts($id: ID!) {
270
+ order(id: $id) {
271
+ ${ORDER_DISCOUNTS_FIELDS}
272
+ }
273
+ }
274
+ `;
275
+ /** `undefined` for an absent or blank filter id: an explicit null is a value the engine reads. */
276
+ function idVariable(value) {
277
+ return typeof value === 'string' && value.trim() !== '' ? value : undefined;
278
+ }
279
+ export function createPricingModule(transport) {
280
+ return {
281
+ promotions: {
282
+ async active(filter, options) {
283
+ const data = await transport.graphql({
284
+ api: 'admin',
285
+ query: ACTIVE_PROMOTIONS_QUERY,
286
+ variables: {
287
+ locationId: idVariable(filter?.locationId),
288
+ customerId: idVariable(filter?.customerId),
289
+ },
290
+ }, options);
291
+ const items = data.activePromotions;
292
+ return createPage(items, { nextCursor: null, hasNext: false, total: items.length }, () => {
293
+ throw new Error('activePromotions returns a complete list; there is no next page');
294
+ });
295
+ },
296
+ async create(input, options) {
297
+ const data = await transport.graphql({
298
+ api: 'admin',
299
+ query: CREATE_PROMOTION_MUTATION,
300
+ variables: { input: promotionInputOf(input) },
301
+ }, options);
302
+ // A union at HTTP 200 (`Promotion | MissingConditionsError`). With the guard
303
+ // always sent, core does not answer the error member for this input; should
304
+ // it ever, it is thrown, never handed back as a promotion.
305
+ if (isErrorResult(data.createPromotion))
306
+ throw errorFromErrorResult(data.createPromotion);
307
+ return data.createPromotion;
308
+ },
309
+ async update(input, options) {
310
+ const data = await transport.graphql({
311
+ api: 'admin',
312
+ query: UPDATE_PROMOTION_MUTATION,
313
+ variables: { input: promotionInputOf(input) },
314
+ }, options);
315
+ if (isErrorResult(data.updatePromotion))
316
+ throw errorFromErrorResult(data.updatePromotion);
317
+ return data.updatePromotion;
318
+ },
319
+ },
320
+ coupons: {
321
+ async apply(orderId, couponCode, options) {
322
+ const data = await transport.graphql({
323
+ api: 'admin',
324
+ query: APPLY_COUPON_CODE_TO_ORDER_MUTATION,
325
+ variables: { orderId, couponCode, idempotencyKey: options?.idempotencyKey },
326
+ }, options);
327
+ return data.applyCouponCodeToOrder;
328
+ },
329
+ async remove(orderId, couponCode, options) {
330
+ const data = await transport.graphql({
331
+ api: 'admin',
332
+ query: REMOVE_COUPON_CODE_FROM_ORDER_MUTATION,
333
+ variables: { orderId, couponCode, idempotencyKey: options?.idempotencyKey },
334
+ }, options);
335
+ return data.removeCouponCodeFromOrder;
336
+ },
337
+ },
338
+ async discounts(orderId, options) {
339
+ const data = await transport.graphql({ api: 'admin', query: ORDER_DISCOUNTS_QUERY, variables: { id: orderId } }, options);
340
+ if (!data.order) {
341
+ throw new NotFoundError({
342
+ code: 'ORDER_NOT_FOUND',
343
+ message: `This store has no order ${orderId}.`,
344
+ status: 404,
345
+ service: 'vendure',
346
+ });
347
+ }
348
+ return data.order;
349
+ },
350
+ };
351
+ }
@@ -0,0 +1,93 @@
1
+ import { type Page, type Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ /** The records provenance is kept for. An address change is recorded on its customer. */
4
+ export type ProvenanceEntityType = 'PRODUCT' | 'VARIANT' | 'CUSTOMER';
5
+ /**
6
+ * The field groups a row can be about: the four groups an app can be granted (spec §8.10).
7
+ */
8
+ export type ProvenanceFieldGroup = 'price' | 'stock' | 'product_details' | 'customer_details';
9
+ /** Who made the last change of a field group. */
10
+ export interface ProvenanceActor {
11
+ /**
12
+ * `user` (a staff member), `customer` (the shopper, on their own record), `api_token`,
13
+ * `app_installation`, `system` (a platform job), `service`, `anonymous` (a guest), or
14
+ * `unknown` when nothing could name the writer.
15
+ */
16
+ type: string;
17
+ /**
18
+ * A User id for `user` and `customer`, an API token or app installation id, or
19
+ * `system:<job>`. Null for a guest, an unknown writer, or a customer since erased.
20
+ */
21
+ id: string | null;
22
+ /**
23
+ * The app's name, the API token's name, the job (`system:<job>`), or a staff member's name for
24
+ * a caller holding `ReadAdministrator` who can see that staff member. Null otherwise, and never
25
+ * a customer's name.
26
+ */
27
+ label: string | null;
28
+ }
29
+ /** The app or API key whose change a field group last carries: the "Managed by" badge. */
30
+ export interface ProvenanceManagedBy {
31
+ kind: 'app_installation' | 'api_token';
32
+ /** The app installation's or API token's id. */
33
+ id: string;
34
+ /** The app's name or the API token's name. */
35
+ label: string;
36
+ }
37
+ /** The last change of one field group of one record, in the store the client acts on. */
38
+ export interface EntityProvenance {
39
+ entityType: ProvenanceEntityType;
40
+ entityId: string;
41
+ /** A {@link ProvenanceFieldGroup}; a group a newer engine adds reaches you as it is. */
42
+ fieldGroup: string;
43
+ actor: ProvenanceActor;
44
+ /** The platform job that carried a change made for someone else (a bulk import), or null. */
45
+ via: string | null;
46
+ /** When the change committed, ISO-8601 UTC. */
47
+ writtenAt: string;
48
+ /** The trace of the change: the key into `admin.audit.correlation` and the event log. */
49
+ traceId: string | null;
50
+ /**
51
+ * Non-null only when the last change of THIS group was made by an app or an API key of this
52
+ * store: the rule for showing "Managed by <name>". A staff, customer or job change clears it.
53
+ */
54
+ managedBy: ProvenanceManagedBy | null;
55
+ }
56
+ /** The most records one read may name; more is refused with `PROVENANCE_REQUEST_INVALID`. */
57
+ export declare const PROVENANCE_MAX_IDS = 250;
58
+ export declare const ENTITY_PROVENANCE_QUERY = "\n query EntityProvenance($entityType: ProvenanceEntityType!, $ids: [ID!]!) {\n entityProvenance(entityType: $entityType, ids: $ids) {\n entityType\n entityId\n fieldGroup\n actor {\n type\n id\n label\n }\n via\n writtenAt\n traceId\n managedBy {\n kind\n id\n label\n }\n }\n }\n";
59
+ /**
60
+ * Who last changed each field group of a product, variant or customer (spec §8.10): the price, the
61
+ * stock, the product details, the customer's details. Recorded for every change, made by staff,
62
+ * an app, an API key, an import or the shopper, in the change's own transaction.
63
+ *
64
+ * - **Last writer only, per group.** One row per (record, group), replaced by the next write
65
+ * that NAMES a field of that group, an unchanged value re-sent included (the engine sees no
66
+ * prior value, as with `product_variant.price_changed`). What changed and when, in order, is the platform event log filtered by the
67
+ * record (`GET /event-log?aggregate_type=&aggregate_id=` in the plugins service, which needs
68
+ * `ReadPlatformEvents`); it is not here.
69
+ * - **`managedBy` is the badge rule.** Show "Managed by <label>" only when it is non-null.
70
+ * - **Only the store you act on.** A record the store does not hold, and one it holds with
71
+ * nothing recorded since provenance began, answer the same way: no rows.
72
+ * - **Permissions:** products and variants need `ReadCatalog` or `ReadProduct`, customers
73
+ * `ReadCustomer` (`FORBIDDEN` → `PermissionDeniedError`). Staff names need `ReadAdministrator`.
74
+ *
75
+ * Refusals: `PROVENANCE_REQUEST_INVALID` (`ValidationError`) for more than
76
+ * {@link PROVENANCE_MAX_IDS} ids; `ENTITY_NOT_FOUND` (`NotFoundError`) for an id that is not
77
+ * a record id at all, and from `get` for a record with nothing recorded.
78
+ */
79
+ export interface ProvenanceModule {
80
+ /**
81
+ * Every recorded group of each named record, in the order the ids were given, then by group.
82
+ * A `Page` because every list in this SDK is one; it is always complete (one round trip, at
83
+ * most {@link PROVENANCE_MAX_IDS} ids).
84
+ */
85
+ list(entityType: ProvenanceEntityType, ids: readonly string[], options?: Call): Promise<Page<EntityProvenance>>;
86
+ /**
87
+ * The recorded groups of one record. Throws `NotFoundError` (`ENTITY_NOT_FOUND`) when the
88
+ * store holds none: the record is not this store's, or nothing has changed it since
89
+ * provenance began. The two are deliberately indistinguishable.
90
+ */
91
+ get(entityType: ProvenanceEntityType, id: string, options?: Call): Promise<EntityProvenance[]>;
92
+ }
93
+ export declare function createProvenanceModule(transport: Transport): ProvenanceModule;
@@ -0,0 +1,53 @@
1
+ import { createPage, NotFoundError } from '@sixtyninecommerce/core';
2
+ /** The most records one read may name; more is refused with `PROVENANCE_REQUEST_INVALID`. */
3
+ export const PROVENANCE_MAX_IDS = 250;
4
+ export const ENTITY_PROVENANCE_QUERY = /* GraphQL */ `
5
+ query EntityProvenance($entityType: ProvenanceEntityType!, $ids: [ID!]!) {
6
+ entityProvenance(entityType: $entityType, ids: $ids) {
7
+ entityType
8
+ entityId
9
+ fieldGroup
10
+ actor {
11
+ type
12
+ id
13
+ label
14
+ }
15
+ via
16
+ writtenAt
17
+ traceId
18
+ managedBy {
19
+ kind
20
+ id
21
+ label
22
+ }
23
+ }
24
+ }
25
+ `;
26
+ export function createProvenanceModule(transport) {
27
+ async function read(entityType, ids, options) {
28
+ const data = await transport.graphql({ api: 'admin', query: ENTITY_PROVENANCE_QUERY, variables: { entityType, ids: [...ids] } }, options);
29
+ return data.entityProvenance;
30
+ }
31
+ return {
32
+ async list(entityType, ids, options) {
33
+ const items = await read(entityType, ids, options);
34
+ return createPage(items, { nextCursor: null, hasNext: false, total: items.length }, () => {
35
+ // Unreachable while `hasNext` is false — `Page.next()` short-circuits — and a rejection
36
+ // rather than a silent re-fetch if that changes.
37
+ throw new Error('entityProvenance returns a complete list; there is no next page');
38
+ });
39
+ },
40
+ async get(entityType, id, options) {
41
+ const rows = await read(entityType, [id], options);
42
+ if (rows.length === 0) {
43
+ throw new NotFoundError({
44
+ code: 'ENTITY_NOT_FOUND',
45
+ message: `This store has no recorded change of ${entityType.toLowerCase()} ${id}.`,
46
+ status: 404,
47
+ service: 'vendure',
48
+ });
49
+ }
50
+ return rows;
51
+ },
52
+ };
53
+ }