@sixtyninecommerce/admin 0.9.0 → 0.11.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 (258) hide show
  1. package/README.md +4598 -128
  2. package/dist/catalog/assets.d.ts +120 -8
  3. package/dist/catalog/assets.js +98 -12
  4. package/dist/catalog/collections.d.ts +279 -14
  5. package/dist/catalog/collections.js +246 -15
  6. package/dist/catalog/index.d.ts +9 -2
  7. package/dist/catalog/index.js +4 -0
  8. package/dist/catalog/products.d.ts +318 -17
  9. package/dist/catalog/products.js +182 -14
  10. package/dist/catalog/properties.d.ts +102 -14
  11. package/dist/catalog/properties.js +13 -0
  12. package/dist/catalog/recommendations.d.ts +261 -0
  13. package/dist/catalog/recommendations.js +145 -0
  14. package/dist/catalog/reviews.d.ts +368 -0
  15. package/dist/catalog/reviews.js +318 -0
  16. package/dist/catalog/types.d.ts +17 -1
  17. package/dist/catalog/types.js +10 -0
  18. package/dist/catalog/variant-find.d.ts +136 -0
  19. package/dist/catalog/variant-find.js +239 -0
  20. package/dist/catalog/variants.d.ts +282 -25
  21. package/dist/catalog/variants.js +173 -25
  22. package/dist/catalog/visibility.d.ts +20 -5
  23. package/dist/catalog/visibility.js +19 -1
  24. package/dist/client.d.ts +116 -9
  25. package/dist/client.js +41 -7
  26. package/dist/content/assets.d.ts +286 -0
  27. package/dist/content/assets.js +181 -0
  28. package/dist/content/items.d.ts +269 -0
  29. package/dist/content/items.js +170 -0
  30. package/dist/content/types.d.ts +282 -0
  31. package/dist/content/types.js +100 -0
  32. package/dist/customers/age-verification.d.ts +66 -0
  33. package/dist/customers/age-verification.js +79 -0
  34. package/dist/customers/credit.d.ts +263 -0
  35. package/dist/customers/credit.js +244 -0
  36. package/dist/customers/delivery-preferences.d.ts +120 -0
  37. package/dist/customers/delivery-preferences.js +97 -0
  38. package/dist/customers/groups.d.ts +207 -0
  39. package/dist/customers/groups.js +438 -0
  40. package/dist/customers/index.d.ts +447 -37
  41. package/dist/customers/index.js +313 -40
  42. package/dist/customers/invitation-status.d.ts +57 -0
  43. package/dist/customers/invitation-status.js +21 -0
  44. package/dist/customers/notes.d.ts +67 -0
  45. package/dist/customers/notes.js +51 -0
  46. package/dist/customers/notification-recipients.d.ts +97 -0
  47. package/dist/customers/notification-recipients.js +84 -0
  48. package/dist/customers/saved-lists.d.ts +42 -0
  49. package/dist/customers/saved-lists.js +61 -0
  50. package/dist/customers/vat.d.ts +83 -0
  51. package/dist/customers/vat.js +46 -0
  52. package/dist/customers/wire.d.ts +20 -0
  53. package/dist/customers/wire.js +35 -0
  54. package/dist/documents/ar.d.ts +307 -0
  55. package/dist/documents/ar.js +254 -0
  56. package/dist/documents/index.d.ts +731 -21
  57. package/dist/documents/index.js +436 -24
  58. package/dist/documents/pdf.d.ts +2 -0
  59. package/dist/documents/pdf.js +11 -0
  60. package/dist/fulfillments/create.d.ts +167 -0
  61. package/dist/fulfillments/create.js +216 -0
  62. package/dist/fulfillments/index.d.ts +121 -10
  63. package/dist/fulfillments/index.js +120 -2
  64. package/dist/fulfillments/payment-policy.d.ts +64 -0
  65. package/dist/fulfillments/payment-policy.js +140 -0
  66. package/dist/fulfillments/warehouse.d.ts +724 -0
  67. package/dist/fulfillments/warehouse.js +914 -0
  68. package/dist/geo/index.d.ts +119 -0
  69. package/dist/geo/index.js +107 -0
  70. package/dist/index.d.ts +129 -30
  71. package/dist/index.js +47 -9
  72. package/dist/integrations/afas-actions.d.ts +234 -0
  73. package/dist/integrations/afas-actions.js +310 -0
  74. package/dist/integrations/afas-types.d.ts +483 -0
  75. package/dist/integrations/afas-types.js +5 -0
  76. package/dist/integrations/afas.d.ts +234 -0
  77. package/dist/integrations/afas.js +290 -0
  78. package/dist/integrations/connections.d.ts +103 -0
  79. package/dist/integrations/connections.js +55 -0
  80. package/dist/integrations/esp.d.ts +313 -0
  81. package/dist/integrations/esp.js +169 -0
  82. package/dist/integrations/exact-online-types.d.ts +423 -0
  83. package/dist/integrations/exact-online-types.js +6 -0
  84. package/dist/integrations/exact-online.d.ts +248 -0
  85. package/dist/integrations/exact-online.js +376 -0
  86. package/dist/integrations/index.d.ts +40 -0
  87. package/dist/integrations/index.js +24 -0
  88. package/dist/integrations/pipeline-keys.d.ts +13 -0
  89. package/dist/integrations/pipeline-keys.js +22 -0
  90. package/dist/integrations/pipelines.d.ts +114 -0
  91. package/dist/integrations/pipelines.js +46 -0
  92. package/dist/integrations/types.d.ts +322 -0
  93. package/dist/integrations/types.js +8 -0
  94. package/dist/jobs/bulk-action-run.d.ts +11 -2
  95. package/dist/jobs/bulk-action-run.js +4 -5
  96. package/dist/locations/index.d.ts +66 -0
  97. package/dist/locations/index.js +31 -0
  98. package/dist/meta/index.d.ts +162 -0
  99. package/dist/orders/bulk.d.ts +75 -0
  100. package/dist/orders/bulk.js +150 -0
  101. package/dist/orders/cancel-with-money.d.ts +227 -0
  102. package/dist/orders/cancel-with-money.js +153 -0
  103. package/dist/orders/commit-edit.d.ts +7 -1
  104. package/dist/orders/commit-edit.js +5 -1
  105. package/dist/orders/deposit-policy.d.ts +103 -0
  106. package/dist/orders/deposit-policy.js +202 -0
  107. package/dist/orders/drafts.d.ts +800 -0
  108. package/dist/orders/drafts.js +771 -0
  109. package/dist/orders/fields.d.ts +245 -0
  110. package/dist/orders/fields.js +188 -0
  111. package/dist/orders/get.d.ts +29 -19
  112. package/dist/orders/get.js +17 -8
  113. package/dist/orders/index.d.ts +125 -5
  114. package/dist/orders/index.js +35 -2
  115. package/dist/orders/lifecycle.d.ts +128 -0
  116. package/dist/orders/lifecycle.js +158 -0
  117. package/dist/orders/line-price-preview.d.ts +7 -2
  118. package/dist/orders/list.d.ts +222 -0
  119. package/dist/orders/list.js +377 -0
  120. package/dist/orders/manual-discounts.d.ts +11 -2
  121. package/dist/orders/manual-discounts.js +2 -0
  122. package/dist/orders/manual-pricing.d.ts +5 -0
  123. package/dist/orders/order-entry.d.ts +20 -7
  124. package/dist/orders/plan-edit.d.ts +1 -1
  125. package/dist/orders/plan-edit.js +4 -1
  126. package/dist/orders/settings.d.ts +201 -0
  127. package/dist/orders/settings.js +325 -0
  128. package/dist/orders/status.d.ts +159 -13
  129. package/dist/orders/status.js +21 -0
  130. package/dist/orders/surcharges.d.ts +8 -1
  131. package/dist/orders/surcharges.js +3 -2
  132. package/dist/payments/account-credit.d.ts +30 -0
  133. package/dist/payments/account-credit.js +19 -0
  134. package/dist/payments/ar.d.ts +292 -0
  135. package/dist/payments/ar.js +223 -0
  136. package/dist/payments/connected-account.d.ts +47 -0
  137. package/dist/payments/connected-account.js +61 -0
  138. package/dist/payments/credit-noted-fields.d.ts +12 -0
  139. package/dist/payments/credit-noted-fields.js +37 -0
  140. package/dist/payments/index.d.ts +263 -10
  141. package/dist/payments/index.js +133 -1
  142. package/dist/payments/methods.d.ts +332 -0
  143. package/dist/payments/methods.js +363 -0
  144. package/dist/payments/mollie-methods.d.ts +159 -0
  145. package/dist/payments/mollie-methods.js +141 -0
  146. package/dist/payments/payment-links.d.ts +180 -0
  147. package/dist/payments/payment-links.js +97 -0
  148. package/dist/payments/provider-keys.d.ts +150 -0
  149. package/dist/payments/provider-keys.js +118 -0
  150. package/dist/payments/providers.d.ts +86 -11
  151. package/dist/payments/providers.js +29 -0
  152. package/dist/payments/requests.d.ts +144 -0
  153. package/dist/payments/requests.js +92 -0
  154. package/dist/payments/store-credit-fields.d.ts +15 -0
  155. package/dist/payments/store-credit-fields.js +51 -0
  156. package/dist/pickups/index.d.ts +104 -0
  157. package/dist/pickups/index.js +105 -0
  158. package/dist/platform/channels.d.ts +132 -0
  159. package/dist/platform/channels.js +128 -0
  160. package/dist/platform/geo.d.ts +123 -0
  161. package/dist/platform/geo.js +144 -0
  162. package/dist/platform/tax.d.ts +154 -0
  163. package/dist/platform/tax.js +139 -0
  164. package/dist/pos/anomalies.d.ts +158 -0
  165. package/dist/pos/anomalies.js +64 -0
  166. package/dist/pos/assisted-selling.d.ts +79 -0
  167. package/dist/pos/assisted-selling.js +38 -0
  168. package/dist/pos/exchanges.d.ts +103 -0
  169. package/dist/pos/exchanges.js +22 -0
  170. package/dist/pos/gift-cards.d.ts +135 -0
  171. package/dist/pos/gift-cards.js +47 -0
  172. package/dist/pos/index.d.ts +2125 -32
  173. package/dist/pos/index.js +742 -4
  174. package/dist/pos/no-receipt.d.ts +94 -0
  175. package/dist/pos/no-receipt.js +25 -0
  176. package/dist/pos/offline-sales.d.ts +187 -0
  177. package/dist/pos/offline-sales.js +116 -0
  178. package/dist/pos/pay-by-link.d.ts +143 -0
  179. package/dist/pos/pay-by-link.js +57 -0
  180. package/dist/pos/quick-keys.d.ts +103 -0
  181. package/dist/pos/quick-keys.js +45 -0
  182. package/dist/pos/reports.d.ts +360 -0
  183. package/dist/pos/reports.js +62 -0
  184. package/dist/pos/returns.d.ts +217 -0
  185. package/dist/pos/returns.js +53 -0
  186. package/dist/pricing/bulk-coupons.d.ts +139 -0
  187. package/dist/pricing/bulk-coupons.js +112 -0
  188. package/dist/pricing/index.d.ts +177 -4
  189. package/dist/pricing/index.js +176 -1
  190. package/dist/pricing/price-lists.d.ts +604 -0
  191. package/dist/pricing/price-lists.js +600 -0
  192. package/dist/pricing/vouchers.d.ts +103 -0
  193. package/dist/pricing/vouchers.js +69 -0
  194. package/dist/purchasing/alerts.d.ts +89 -0
  195. package/dist/purchasing/alerts.js +62 -0
  196. package/dist/purchasing/cost.d.ts +110 -0
  197. package/dist/purchasing/cost.js +86 -0
  198. package/dist/purchasing/index.d.ts +36 -0
  199. package/dist/purchasing/index.js +16 -0
  200. package/dist/purchasing/purchase-orders.d.ts +548 -0
  201. package/dist/purchasing/purchase-orders.js +491 -0
  202. package/dist/purchasing/replenishment.d.ts +281 -0
  203. package/dist/purchasing/replenishment.js +240 -0
  204. package/dist/purchasing/settings.d.ts +152 -0
  205. package/dist/purchasing/settings.js +181 -0
  206. package/dist/purchasing/suppliers.d.ts +476 -0
  207. package/dist/purchasing/suppliers.js +438 -0
  208. package/dist/quotes/index.d.ts +501 -0
  209. package/dist/quotes/index.js +439 -0
  210. package/dist/reports/index.d.ts +71 -8
  211. package/dist/reports/index.js +74 -11
  212. package/dist/returns/index.d.ts +818 -0
  213. package/dist/returns/index.js +844 -0
  214. package/dist/search/index.d.ts +174 -0
  215. package/dist/search/index.js +125 -0
  216. package/dist/shipping/definitions.d.ts +29 -0
  217. package/dist/shipping/definitions.js +47 -0
  218. package/dist/shipping/fulfillment-profiles.d.ts +174 -0
  219. package/dist/shipping/fulfillment-profiles.js +104 -0
  220. package/dist/shipping/hendriks.d.ts +97 -0
  221. package/dist/shipping/hendriks.js +50 -0
  222. package/dist/shipping/index.d.ts +48 -0
  223. package/dist/shipping/index.js +25 -0
  224. package/dist/shipping/methods.d.ts +198 -0
  225. package/dist/shipping/methods.js +240 -0
  226. package/dist/shipping/myparcel.d.ts +254 -0
  227. package/dist/shipping/myparcel.js +178 -0
  228. package/dist/shipping/parcel-count-suggestion.d.ts +2 -0
  229. package/dist/shipping/parcel-rule.d.ts +6 -5
  230. package/dist/shipping/parcel-rule.js +21 -23
  231. package/dist/shipping/rate-tables.d.ts +179 -0
  232. package/dist/shipping/rate-tables.js +184 -0
  233. package/dist/stock/index.d.ts +10 -3
  234. package/dist/stock/index.js +3 -0
  235. package/dist/stock/locations.d.ts +144 -0
  236. package/dist/stock/locations.js +168 -0
  237. package/dist/stock/transfers.d.ts +216 -43
  238. package/dist/stock/transfers.js +140 -13
  239. package/dist/stored-value/index.d.ts +214 -4
  240. package/dist/stored-value/index.js +152 -0
  241. package/dist/subscriptions/index.d.ts +682 -0
  242. package/dist/subscriptions/index.js +815 -0
  243. package/dist/tax/index.d.ts +76 -1
  244. package/dist/tax/index.js +50 -0
  245. package/dist/tenant/email-style.d.ts +102 -0
  246. package/dist/tenant/email-style.js +72 -0
  247. package/dist/tenant/email-templates.d.ts +66 -0
  248. package/dist/tenant/email-templates.js +54 -0
  249. package/dist/tenant/notifications.d.ts +23 -17
  250. package/dist/tenant/settings-sources.d.ts +1 -1
  251. package/dist/tenant/time-zone.d.ts +17 -0
  252. package/dist/tenant/time-zone.js +11 -0
  253. package/dist/types.d.ts +51 -4
  254. package/dist/users/index.d.ts +3 -0
  255. package/dist/users/index.js +2 -0
  256. package/dist/users/warehouses.d.ts +50 -0
  257. package/dist/users/warehouses.js +51 -0
  258. package/package.json +2 -2
@@ -0,0 +1,105 @@
1
+ import { redactionWatcher } from '../customer-data-redaction.js';
2
+ /**
3
+ * Store pickup (click & collect, POS C1/33): webshop orders a shopper collects at one of the
4
+ * store's shops. Placing such an order writes its pickup (`preparing`); "ready" starts the hold
5
+ * and emails the customer once; the shop's till hands it over (`collected`); a ready pickup not
6
+ * collected within the location's hold days goes back to stock (`expired`), and the pickup of a
7
+ * cancelled order is `cancelled`. A location's settings are `admin.locations.pickup/setPickup`.
8
+ */
9
+ /** The statuses a pickup has today. More may come: read any other value as itself. */
10
+ export const STORE_PICKUP_STATUSES = [
11
+ 'preparing',
12
+ 'ready',
13
+ 'collected',
14
+ 'expired',
15
+ 'cancelled',
16
+ ];
17
+ /**
18
+ * Why the release at expiry moved nothing (D23): `invoice_locked`, the order is invoiced and
19
+ * locked; `order_state`, the order is past the states a reservation is released from. More may
20
+ * come.
21
+ */
22
+ export const STORE_PICKUP_RELEASE_BLOCKED = ['invoice_locked', 'order_state'];
23
+ const STORE_PICKUP_FIELDS = /* GraphQL */ `
24
+ id
25
+ orderId
26
+ orderCode
27
+ stockLocationId
28
+ stockLocationName
29
+ status
30
+ customerName
31
+ totalQuantity
32
+ stockShort
33
+ placedAt
34
+ readyAt
35
+ holdUntil
36
+ collectedAt
37
+ expiredAt
38
+ releaseBlocked
39
+ createdAt
40
+ `;
41
+ export const STORE_PICKUPS_QUERY = /* GraphQL */ `
42
+ query StorePickups($input: StorePickupListInput) {
43
+ storePickups(input: $input) {
44
+ items { ${STORE_PICKUP_FIELDS} }
45
+ totalItems
46
+ }
47
+ }
48
+ `;
49
+ export const STORE_PICKUP_FOR_ORDER_QUERY = /* GraphQL */ `
50
+ query StorePickupForOrder($orderId: ID!) {
51
+ storePickupForOrder(orderId: $orderId) { ${STORE_PICKUP_FIELDS} }
52
+ }
53
+ `;
54
+ export const MARK_STORE_PICKUP_READY_MUTATION = /* GraphQL */ `
55
+ mutation MarkStorePickupReady($id: ID!) {
56
+ markStorePickupReady(id: $id) { ${STORE_PICKUP_FIELDS} }
57
+ }
58
+ `;
59
+ export function createPickupsModule(transport) {
60
+ return {
61
+ async list(filter, options) {
62
+ const redaction = redactionWatcher();
63
+ // Only what the caller set: an absent key leaves the engine's default (50, every status).
64
+ const input = {};
65
+ if (filter?.stockLocationId !== undefined)
66
+ input.stockLocationId = filter.stockLocationId;
67
+ if (filter?.statuses !== undefined)
68
+ input.statuses = [...filter.statuses];
69
+ if (filter?.take !== undefined)
70
+ input.take = filter.take;
71
+ if (filter?.skip !== undefined)
72
+ input.skip = filter.skip;
73
+ const data = await transport.graphql({
74
+ api: 'admin',
75
+ query: STORE_PICKUPS_QUERY,
76
+ variables: { input },
77
+ onExtensions: redaction.onExtensions,
78
+ }, options);
79
+ return {
80
+ items: data.storePickups.items.map((item) => redaction.mark(item)),
81
+ totalItems: data.storePickups.totalItems,
82
+ };
83
+ },
84
+ async forOrder(orderId, options) {
85
+ const redaction = redactionWatcher();
86
+ const data = await transport.graphql({
87
+ api: 'admin',
88
+ query: STORE_PICKUP_FOR_ORDER_QUERY,
89
+ variables: { orderId },
90
+ onExtensions: redaction.onExtensions,
91
+ }, options);
92
+ return redaction.mark(data.storePickupForOrder);
93
+ },
94
+ async markReady(id, options) {
95
+ const redaction = redactionWatcher();
96
+ const data = await transport.graphql({
97
+ api: 'admin',
98
+ query: MARK_STORE_PICKUP_READY_MUTATION,
99
+ variables: { id },
100
+ onExtensions: redaction.onExtensions,
101
+ }, options);
102
+ return redaction.mark(data.markStorePickupReady);
103
+ },
104
+ };
105
+ }
@@ -0,0 +1,132 @@
1
+ import { type Transport } from '@sixtyninecommerce/core';
2
+ import type { AdminClientConfig } from '../client.js';
3
+ import { type JobHandle } from '../jobs/index.js';
4
+ import type { Call } from '../types.js';
5
+ /**
6
+ * Why a code is held:
7
+ * - `rename-source`: the code a rename is moving OFF, held until its files are moved forward.
8
+ * - `rename-target`: the code a rename is moving ONTO.
9
+ * - `rename-alias`: a renamed store's old code, still answering for its storefronts until
10
+ * `expiresAt`.
11
+ */
12
+ export type ChannelCodeHoldReason = 'rename-source' | 'rename-target' | 'rename-alias';
13
+ /** What a hold's rename job is doing: `running` blocks a release; the rest are releasable. */
14
+ export type ChannelCodeHoldActivity = 'running' | 'stalled' | 'finished' | 'no-job';
15
+ /** Why a release ended without freeing the code. The hold stays; releasing again retries. */
16
+ export interface ChannelCodeReleaseFailure {
17
+ /**
18
+ * `CLEANUP_FAILED` (the cleanup failed, reported errors, or stopped short), `CLEANUP_JOB_LOST`
19
+ * (the plugins service no longer knows the cleanup), `CLEANUP_LEFT_OBJECTS` (objects remained
20
+ * after a cleanup that reported success), `CLEANUP_TIMED_OUT` (not decided within 24 hours), or
21
+ * `CHANNEL_CODE_TAKEN` (another store took the code meanwhile).
22
+ */
23
+ code: string;
24
+ message: string;
25
+ }
26
+ /** A code a rename still holds against reuse, with what is happening to it. */
27
+ export interface ChannelCodeReservation {
28
+ code: string;
29
+ channelId: string | null;
30
+ /** The code that store carries NOW; null when the store is gone. */
31
+ channelCode: string | null;
32
+ reason: ChannelCodeHoldReason;
33
+ jobId: string | null;
34
+ jobState: string | null;
35
+ /** ISO-8601 UTC. */
36
+ jobUpdatedAt: string | null;
37
+ /** The rename stopped moving for longer than the recovery window. */
38
+ jobStale: boolean;
39
+ createdAt: string;
40
+ /** `rename-alias` only: when the old code stops answering and frees. */
41
+ expiresAt: string | null;
42
+ activity: ChannelCodeHoldActivity;
43
+ /** Whether {@link PlatformChannelsModule.releaseCode} may be asked now. */
44
+ releasable: boolean;
45
+ /** When a release started the cleanup it waits on (or the one that failed). */
46
+ releaseRequestedAt: string | null;
47
+ releaseCleanupJobId: string | null;
48
+ releaseCleanupMode: 'drain-source' | 'delete-source' | null;
49
+ /** Set when the last release ended without freeing the code; null while one is pending. */
50
+ releaseFailure: ChannelCodeReleaseFailure | null;
51
+ }
52
+ /** How a release ended when it ended well. */
53
+ export type ChannelCodeReleaseOutcome = 'released' | 'aliased';
54
+ export interface ChannelCodeRelease {
55
+ code: string;
56
+ /**
57
+ * - `released`: the code is free now.
58
+ * - `aliased`: the rename behind the hold had committed and its old code still answers for the
59
+ * renamed store, so the hold became that old-code alias. Release it again to end it early.
60
+ * - `pending`: files remained, so the engine started a cleanup (or one already was running).
61
+ * The engine finishes the release itself once the cleanup ends; nobody has to stay and call
62
+ * again. Follow it with `handle.wait()`, or read `codeReservations()` later.
63
+ */
64
+ outcome: ChannelCodeReleaseOutcome | 'pending';
65
+ /** The cleanup a `pending` release waits on; null otherwise. */
66
+ cleanup: {
67
+ jobId: string;
68
+ mode: 'drain-source' | 'delete-source' | null;
69
+ /** Files under the prefix when the cleanup started; null for a repeat of a pending release. */
70
+ objectsRemaining: number | null;
71
+ } | null;
72
+ /**
73
+ * `runId` is the cleanup's job id when there is one, else the code. `wait()` resolves at once
74
+ * for a release that already ended; for a `pending` one it polls {@link
75
+ * PlatformChannelsModule.codeReservations} until the code is no longer held (`released`), is
76
+ * held as an old-code alias (`aliased`), or shows a `releaseFailure`, which it throws as
77
+ * `CHANNEL_CODE_RELEASE_FAILED` (a {@link BusinessRuleError}, 422) with the failure's code in
78
+ * `details[0].code`. Defaults: a read every 5 s, giving up after 15 minutes with
79
+ * `CHANNEL_CODE_RELEASE_TIMEOUT` (retryable: the release goes on regardless).
80
+ *
81
+ * When a `pending` release ends as the alias while nobody is calling (the engine's worker
82
+ * finished it), the engine remembers that ending until a release has answered it: the next
83
+ * `releaseCode` answers `aliased` again and ends nothing. This keeps an older dashboard's
84
+ * automatic second call from cutting the alias window short. A release that answered `aliased`
85
+ * itself leaves nothing to answer. Either way, to end the alias early, call `releaseCode` until
86
+ * it answers `released`.
87
+ */
88
+ handle: JobHandle<ChannelCodeReleaseOutcome>;
89
+ }
90
+ /**
91
+ * `admin.platform.channels` — the codes renames hold against reuse, and their release. A human
92
+ * SuperAdmin session only: an API token or an app credential is refused before anything is sent
93
+ * (`PLATFORM_REQUIRES_SESSION`), and the engine answers anyone else `FORBIDDEN`.
94
+ */
95
+ export interface PlatformChannelsModule {
96
+ /** Every live hold on the platform, oldest first. */
97
+ codeReservations(options?: Call): Promise<ChannelCodeReservation[]>;
98
+ /**
99
+ * Free a held code, or start the cleanup that makes that safe. Refusals:
100
+ * - `CHANNEL_CODE_HOLD_NOT_FOUND` (404): nothing holds that code.
101
+ * - `CHANNEL_CODE_RELEASE_CONFLICT` (409): something live is in the way; `details[0].code` is
102
+ * `rename_queued`, `rename_running`, `rename_stalled_postponed`, `move_running` or
103
+ * `cdn_route`.
104
+ * - `CHANNEL_CODE_TAKEN` (409): another store carries the code.
105
+ * - `CHANNEL_CODE_RELEASE_UNVERIFIED` (422): the object store, the store's object moves, the
106
+ * CDN route, the database or the content service could not be read. Nothing changed; it is
107
+ * safe to try again (`details[0].code` names what).
108
+ *
109
+ * - `CHANNEL_CODE_RELEASE_FAILED` (422): a repeat of a pending release found it ended without
110
+ * freeing the code; `details[0].code` is the failure's code (`CLEANUP_…`).
111
+ *
112
+ * A repeat while a release is pending starts nothing: it finishes the release if its cleanup
113
+ * has ended, and answers `released`, `aliased`, the same `pending` while the cleanup runs, or
114
+ * the failure above. A cleanup that has ended but whose release cannot finish right now is
115
+ * refused with the reason (`CHANNEL_CODE_RELEASE_CONFLICT` or `…_UNVERIFIED`). A repeat after
116
+ * a failure has been answered is a fresh attempt.
117
+ */
118
+ releaseCode(code: string, options?: Call): Promise<ChannelCodeRelease>;
119
+ }
120
+ export declare const CHANNEL_CODE_RESERVATIONS_QUERY = "\n query PlatformChannelCodeReservations {\n channelCodeReservations { \n code\n channelId\n channelCode\n reason\n jobId\n jobState\n jobUpdatedAt\n jobStale\n createdAt\n expiresAt\n activity\n releasable\n releaseRequestedAt\n releaseCleanupJobId\n releaseCleanupMode\n releaseFailure {\n code\n message\n }\n }\n }\n";
121
+ export declare const RELEASE_CHANNEL_CODE_MUTATION = "\n mutation PlatformReleaseChannelCode($code: String!) {\n releaseChannelCode(code: $code) {\n code\n outcome\n cleanupJobId\n cleanupMode\n objectsRemaining\n }\n }\n";
122
+ /** Where a release stands, read from the holds: still pending, or how it ended. */
123
+ export declare function releaseStateOf(code: string, holds: readonly ChannelCodeReservation[]): {
124
+ state: 'pending';
125
+ } | {
126
+ state: 'ended';
127
+ outcome: ChannelCodeReleaseOutcome;
128
+ } | {
129
+ state: 'failed';
130
+ failure: ChannelCodeReleaseFailure;
131
+ };
132
+ export declare function createPlatformChannelsModule(transport: Transport, config: AdminClientConfig): PlatformChannelsModule;
@@ -0,0 +1,128 @@
1
+ import { BusinessRuleError, TransportError } from '@sixtyninecommerce/core';
2
+ import { pollUntilTerminal } from '../jobs/index.js';
3
+ import { requireHumanSession } from './guard.js';
4
+ const RESERVATION_FIELDS = /* GraphQL */ `
5
+ code
6
+ channelId
7
+ channelCode
8
+ reason
9
+ jobId
10
+ jobState
11
+ jobUpdatedAt
12
+ jobStale
13
+ createdAt
14
+ expiresAt
15
+ activity
16
+ releasable
17
+ releaseRequestedAt
18
+ releaseCleanupJobId
19
+ releaseCleanupMode
20
+ releaseFailure {
21
+ code
22
+ message
23
+ }
24
+ `;
25
+ export const CHANNEL_CODE_RESERVATIONS_QUERY = /* GraphQL */ `
26
+ query PlatformChannelCodeReservations {
27
+ channelCodeReservations { ${RESERVATION_FIELDS} }
28
+ }
29
+ `;
30
+ export const RELEASE_CHANNEL_CODE_MUTATION = /* GraphQL */ `
31
+ mutation PlatformReleaseChannelCode($code: String!) {
32
+ releaseChannelCode(code: $code) {
33
+ code
34
+ outcome
35
+ cleanupJobId
36
+ cleanupMode
37
+ objectsRemaining
38
+ }
39
+ }
40
+ `;
41
+ const DEFAULT_WAIT_INTERVAL_MS = 5_000;
42
+ const DEFAULT_WAIT_TIMEOUT_MS = 15 * 60_000;
43
+ /** Where a release stands, read from the holds: still pending, or how it ended. */
44
+ export function releaseStateOf(code, holds) {
45
+ const key = code.trim().toLowerCase();
46
+ const hold = holds.find((candidate) => candidate.code.toLowerCase() === key);
47
+ if (!hold)
48
+ return { state: 'ended', outcome: 'released' };
49
+ if (hold.reason === 'rename-alias')
50
+ return { state: 'ended', outcome: 'aliased' };
51
+ if (hold.releaseFailure)
52
+ return { state: 'failed', failure: hold.releaseFailure };
53
+ return { state: 'pending' };
54
+ }
55
+ export function createPlatformChannelsModule(transport, config) {
56
+ // Platform-wide: a SuperAdmin needs no tenant on the client.
57
+ const unscoped = (call) => ({ ...call, allowUnscoped: true });
58
+ async function codeReservations(options) {
59
+ requireHumanSession(config);
60
+ const data = await transport.graphql({ api: 'admin', query: CHANNEL_CODE_RESERVATIONS_QUERY }, unscoped(options));
61
+ return data.channelCodeReservations;
62
+ }
63
+ return {
64
+ codeReservations,
65
+ async releaseCode(code, options) {
66
+ requireHumanSession(config);
67
+ const data = await transport.graphql({ api: 'admin', query: RELEASE_CHANNEL_CODE_MUTATION, variables: { code } }, unscoped(options));
68
+ const wire = data.releaseChannelCode;
69
+ // `cleanup-started` is what an engine before the pending release answered: the cleanup it
70
+ // started is then NOT finished for you, so it is reported as pending and followed the same
71
+ // way, and `wait()` times out unless something calls the release again.
72
+ const pending = wire.outcome === 'pending' || wire.outcome === 'cleanup-started';
73
+ const ended = wire.outcome === 'released' || wire.outcome === 'aliased' ? wire.outcome : null;
74
+ const cleanup = pending && wire.cleanupJobId
75
+ ? {
76
+ jobId: wire.cleanupJobId,
77
+ mode: wire.cleanupMode,
78
+ objectsRemaining: wire.objectsRemaining,
79
+ }
80
+ : null;
81
+ const runId = cleanup?.jobId ?? wire.code;
82
+ return {
83
+ code: wire.code,
84
+ outcome: ended ?? 'pending',
85
+ cleanup,
86
+ handle: {
87
+ runId,
88
+ async wait(waitOptions) {
89
+ if (ended)
90
+ return ended;
91
+ const holds = await pollUntilTerminal({
92
+ read: (signal) => codeReservations({ ...options, ...(signal ? { signal } : {}) }),
93
+ isTerminal: (value) => releaseStateOf(wire.code, value).state !== 'pending',
94
+ onTimeout: () => timedOut(wire.code, runId),
95
+ intervalMs: DEFAULT_WAIT_INTERVAL_MS,
96
+ timeoutMs: DEFAULT_WAIT_TIMEOUT_MS,
97
+ ...waitOptions,
98
+ });
99
+ const state = releaseStateOf(wire.code, holds);
100
+ if (state.state === 'ended')
101
+ return state.outcome;
102
+ if (state.state === 'failed')
103
+ throw releaseFailed(wire.code, state.failure);
104
+ throw timedOut(wire.code, runId);
105
+ },
106
+ },
107
+ };
108
+ },
109
+ };
110
+ }
111
+ function releaseFailed(code, failure) {
112
+ return new BusinessRuleError({
113
+ code: 'CHANNEL_CODE_RELEASE_FAILED',
114
+ message: `The release of "${code}" did not free it (${failure.code}): ${failure.message}`,
115
+ status: 422,
116
+ service: 'vendure',
117
+ details: [{ code: failure.code, message: failure.message }],
118
+ });
119
+ }
120
+ function timedOut(code, runId) {
121
+ return new TransportError({
122
+ code: 'CHANNEL_CODE_RELEASE_TIMEOUT',
123
+ message: `Stopped waiting for the release of "${code}" (cleanup ${runId}). The engine finishes it on its own; read platform.channels.codeReservations() later to see how it ended.`,
124
+ status: 0,
125
+ service: 'vendure',
126
+ retryable: true,
127
+ });
128
+ }
@@ -0,0 +1,123 @@
1
+ import { type Transport } from '@sixtyninecommerce/core';
2
+ import type { AdminClientConfig } from '../client.js';
3
+ import { type Country, type ZoneWithMembers } from '../geo/index.js';
4
+ import type { Call } from '../types.js';
5
+ /** A new zone, as {@link PlatformZonesModule.create} takes it. */
6
+ export interface CreateZoneInput {
7
+ name: string;
8
+ /** The countries in it (`geo.countries.list` ids). None when left out. */
9
+ memberIds?: string[];
10
+ }
11
+ /** A change to a zone. A field left out keeps what the zone has; members have their own calls. */
12
+ export interface UpdateZoneInput {
13
+ name?: string;
14
+ }
15
+ /** A country's name in one language. */
16
+ export interface CountryTranslationInput {
17
+ /** As the engine spells it: `en`, `nl`, `pt_BR`. */
18
+ languageCode: string;
19
+ name: string;
20
+ }
21
+ /**
22
+ * A change to a country. A field left out keeps what the country has.
23
+ *
24
+ * The ISO code is not changeable here: tax class mappings, a store's ship-to list and every
25
+ * address carry it, so a changed code would silently re-route them.
26
+ */
27
+ export interface UpdateCountryInput {
28
+ /**
29
+ * A disabled country is kept for history; checkout and address forms refuse it, in every
30
+ * store.
31
+ */
32
+ enabled?: boolean;
33
+ /** Writing a language the country has replaces that name; another language is added. */
34
+ translations?: CountryTranslationInput[];
35
+ }
36
+ /**
37
+ * `admin.platform.geo.zones` — the SuperAdmin writes of the platform's zones: the named sets
38
+ * of countries every store's tax rates and default zones point at (spec §9.2 `platform`, ruling
39
+ * G128). Reads are `geo.zones.list()` / `geo.zones.get()`. Every write answers the zone with its
40
+ * members, as `geo.zones.get()` reads it.
41
+ *
42
+ * Every method needs a human SuperAdmin session: an API token or an app credential is refused
43
+ * with `PLATFORM_REQUIRES_SESSION` (`PermissionDeniedError`, 403) BEFORE anything is sent, and a
44
+ * store administrator is refused by the engine with `PLATFORM_SETTINGS_NOT_ALLOWED`
45
+ * (`PermissionDeniedError`, 403) before anything is read or written. No tenant is needed on the
46
+ * client.
47
+ */
48
+ export interface PlatformZonesModule {
49
+ /**
50
+ * Create a zone. A member id that names no country is skipped by the engine, so read the
51
+ * answer's `members` for what the zone holds.
52
+ *
53
+ * `INVALID_ZONE_INPUT` (`ValidationError`, 400): a missing or blank name, or `memberIds` that
54
+ * is not a list of ids, refused here before anything is sent.
55
+ *
56
+ * @effect write
57
+ */
58
+ create(input: CreateZoneInput, options?: Call): Promise<ZoneWithMembers>;
59
+ /**
60
+ * Rename a zone. `ENTITY_NOT_FOUND` (`NotFoundError`, 404): no zone has that id.
61
+ *
62
+ * @effect write
63
+ */
64
+ update(id: string, patch: UpdateZoneInput, options?: Call): Promise<ZoneWithMembers>;
65
+ /**
66
+ * Delete a zone.
67
+ *
68
+ * - `ZONE_NOT_DELETED` (`ConflictError`, 409): a store uses it as its default tax or shipping
69
+ * zone, or a tax rate prices in it; the engine's reason names the store codes or the rates.
70
+ * Nothing is deleted.
71
+ * - `ENTITY_NOT_FOUND` (`NotFoundError`, 404): no zone has that id.
72
+ *
73
+ * @effect destructive
74
+ */
75
+ delete(id: string, options?: Call): Promise<void>;
76
+ /**
77
+ * Add countries to a zone. A country already in it stays once, and an id that names no
78
+ * country is skipped by the engine: read the answer's `members` for what the zone holds.
79
+ *
80
+ * `INVALID_ZONE_INPUT` (`ValidationError`, 400): no ids, refused here. `ENTITY_NOT_FOUND`
81
+ * (`NotFoundError`, 404): no zone has that id.
82
+ *
83
+ * @effect write
84
+ */
85
+ addMembers(zoneId: string, memberIds: string[], options?: Call): Promise<ZoneWithMembers>;
86
+ /**
87
+ * Take countries out of a zone. An id that is not a member is ignored.
88
+ *
89
+ * `INVALID_ZONE_INPUT` (`ValidationError`, 400): no ids, refused here. `ENTITY_NOT_FOUND`
90
+ * (`NotFoundError`, 404): no zone has that id.
91
+ *
92
+ * @effect write
93
+ */
94
+ removeMembers(zoneId: string, memberIds: string[], options?: Call): Promise<ZoneWithMembers>;
95
+ }
96
+ /**
97
+ * `admin.platform.geo.countries` — the SuperAdmin write of the platform's countries (G128).
98
+ * Reads are `geo.countries.list()`. The same session rules as {@link PlatformZonesModule}.
99
+ */
100
+ export interface PlatformCountriesModule {
101
+ /**
102
+ * Switch a country on or off, or rename it in a language, and answer it as read.
103
+ *
104
+ * `INVALID_COUNTRY_INPUT` (`ValidationError`, 400): an `enabled` that is not a boolean, a
105
+ * translation without a language or a name, or a `code` (not changeable here), refused before
106
+ * anything is sent. `ENTITY_NOT_FOUND` (`NotFoundError`, 404): no country has that id.
107
+ *
108
+ * @effect write
109
+ */
110
+ update(id: string, patch: UpdateCountryInput, options?: Call): Promise<Country>;
111
+ }
112
+ /** `admin.platform.geo`: SuperAdmin writes of the platform's zones and countries (G128). */
113
+ export interface PlatformGeoModule {
114
+ zones: PlatformZonesModule;
115
+ countries: PlatformCountriesModule;
116
+ }
117
+ export declare const CREATE_ZONE_MUTATION = "\n mutation PlatformCreateZone($input: CreateZoneInput!) {\n createZone(input: $input) { \n id\n name\n createdAt\n updatedAt\n \n members {\n id\n name\n code\n enabled\n type\n }\n }\n }\n";
118
+ export declare const UPDATE_ZONE_MUTATION = "\n mutation PlatformUpdateZone($input: UpdateZoneInput!) {\n updateZone(input: $input) { \n id\n name\n createdAt\n updatedAt\n \n members {\n id\n name\n code\n enabled\n type\n }\n }\n }\n";
119
+ export declare const DELETE_ZONE_MUTATION = "\n mutation PlatformDeleteZone($id: ID!) {\n deleteZone(id: $id) { \n result\n message\n }\n }\n";
120
+ export declare const ADD_MEMBERS_TO_ZONE_MUTATION = "\n mutation PlatformAddMembersToZone($zoneId: ID!, $memberIds: [ID!]!) {\n addMembersToZone(zoneId: $zoneId, memberIds: $memberIds) { \n id\n name\n createdAt\n updatedAt\n \n members {\n id\n name\n code\n enabled\n type\n }\n }\n }\n";
121
+ export declare const REMOVE_MEMBERS_FROM_ZONE_MUTATION = "\n mutation PlatformRemoveMembersFromZone($zoneId: ID!, $memberIds: [ID!]!) {\n removeMembersFromZone(zoneId: $zoneId, memberIds: $memberIds) { \n id\n name\n createdAt\n updatedAt\n \n members {\n id\n name\n code\n enabled\n type\n }\n }\n }\n";
122
+ export declare const UPDATE_COUNTRY_MUTATION = "\n mutation PlatformUpdateCountry($input: UpdateCountryInput!) {\n updateCountry(input: $input) { \n id\n name\n code\n enabled\n createdAt\n updatedAt\n }\n }\n";
123
+ export declare function createPlatformGeoModule(transport: Transport, config: AdminClientConfig): PlatformGeoModule;
@@ -0,0 +1,144 @@
1
+ import { ValidationError } from '@sixtyninecommerce/core';
2
+ import { assertDeleted, DELETION_FIELDS, fieldOf, } from '../catalog/types.js';
3
+ import { COUNTRY_FIELDS, ZONE_FIELDS, ZONE_MEMBER_FIELDS, } from '../geo/index.js';
4
+ import { requireHumanSession } from './guard.js';
5
+ const ZONE_ANSWER = `${ZONE_FIELDS} ${ZONE_MEMBER_FIELDS}`;
6
+ export const CREATE_ZONE_MUTATION = /* GraphQL */ `
7
+ mutation PlatformCreateZone($input: CreateZoneInput!) {
8
+ createZone(input: $input) { ${ZONE_ANSWER} }
9
+ }
10
+ `;
11
+ export const UPDATE_ZONE_MUTATION = /* GraphQL */ `
12
+ mutation PlatformUpdateZone($input: UpdateZoneInput!) {
13
+ updateZone(input: $input) { ${ZONE_ANSWER} }
14
+ }
15
+ `;
16
+ export const DELETE_ZONE_MUTATION = /* GraphQL */ `
17
+ mutation PlatformDeleteZone($id: ID!) {
18
+ deleteZone(id: $id) { ${DELETION_FIELDS} }
19
+ }
20
+ `;
21
+ export const ADD_MEMBERS_TO_ZONE_MUTATION = /* GraphQL */ `
22
+ mutation PlatformAddMembersToZone($zoneId: ID!, $memberIds: [ID!]!) {
23
+ addMembersToZone(zoneId: $zoneId, memberIds: $memberIds) { ${ZONE_ANSWER} }
24
+ }
25
+ `;
26
+ export const REMOVE_MEMBERS_FROM_ZONE_MUTATION = /* GraphQL */ `
27
+ mutation PlatformRemoveMembersFromZone($zoneId: ID!, $memberIds: [ID!]!) {
28
+ removeMembersFromZone(zoneId: $zoneId, memberIds: $memberIds) { ${ZONE_ANSWER} }
29
+ }
30
+ `;
31
+ export const UPDATE_COUNTRY_MUTATION = /* GraphQL */ `
32
+ mutation PlatformUpdateCountry($input: UpdateCountryInput!) {
33
+ updateCountry(input: $input) { ${COUNTRY_FIELDS} }
34
+ }
35
+ `;
36
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
37
+ const refusal = (code) => (message) => new ValidationError({ code, message, status: 400, service: 'vendure' });
38
+ const zoneRefusal = refusal('INVALID_ZONE_INPUT');
39
+ const countryRefusal = refusal('INVALID_COUNTRY_INPUT');
40
+ const isId = (value) => typeof value === 'string' && value.trim() !== '';
41
+ function requireId(id, what, refuse) {
42
+ if (!isId(id))
43
+ throw refuse(`${what} must be an id.`);
44
+ return id;
45
+ }
46
+ function memberIdsInput(value, allowEmpty) {
47
+ if (!Array.isArray(value) || !value.every(isId)) {
48
+ throw zoneRefusal('memberIds must be a list of country ids.');
49
+ }
50
+ if (!allowEmpty && value.length === 0) {
51
+ throw zoneRefusal('Name at least one country.');
52
+ }
53
+ return [...value];
54
+ }
55
+ function zoneInput(input, creating) {
56
+ if (!isObject(input))
57
+ throw zoneRefusal('A zone input must be an object.');
58
+ const out = {};
59
+ if (input.name !== undefined || creating) {
60
+ if (typeof input.name !== 'string' || input.name.trim() === '') {
61
+ throw zoneRefusal('A zone needs a name.');
62
+ }
63
+ out.name = input.name;
64
+ }
65
+ if (creating && input.memberIds !== undefined) {
66
+ out.memberIds = memberIdsInput(input.memberIds, true);
67
+ }
68
+ return out;
69
+ }
70
+ function countryInput(input) {
71
+ if (!isObject(input))
72
+ throw countryRefusal('A country change must be an object.');
73
+ if (input.code !== undefined) {
74
+ throw countryRefusal('A country keeps its ISO code: tax class mappings, ship-to lists and addresses carry it.');
75
+ }
76
+ const out = {};
77
+ if (input.enabled !== undefined) {
78
+ if (typeof input.enabled !== 'boolean')
79
+ throw countryRefusal('enabled must be a boolean.');
80
+ out.enabled = input.enabled;
81
+ }
82
+ if (input.translations !== undefined) {
83
+ const rows = input.translations;
84
+ if (!Array.isArray(rows) ||
85
+ !rows.every((row) => isObject(row) &&
86
+ typeof row.languageCode === 'string' &&
87
+ row.languageCode !== '' &&
88
+ typeof row.name === 'string' &&
89
+ row.name.trim() !== '')) {
90
+ throw countryRefusal('translations must be a list of { languageCode, name } with strings.');
91
+ }
92
+ out.translations = rows.map((row) => ({
93
+ languageCode: row.languageCode,
94
+ name: row.name,
95
+ }));
96
+ }
97
+ return out;
98
+ }
99
+ export function createPlatformGeoModule(transport, config) {
100
+ // Platform-wide rows: no store is involved, so no tenant is needed. Scoped to a store instead,
101
+ // the transport would first resolve it through the CMS channel mirror and fail on one missing
102
+ // there.
103
+ const unscoped = (call) => ({ ...call, allowUnscoped: true });
104
+ return {
105
+ zones: {
106
+ async create(input, options) {
107
+ requireHumanSession(config);
108
+ return fieldOf(transport, CREATE_ZONE_MUTATION, 'createZone', { input: zoneInput(input, true) }, unscoped(options));
109
+ },
110
+ async update(id, patch, options) {
111
+ requireHumanSession(config);
112
+ const zoneId = requireId(id, 'id', zoneRefusal);
113
+ return fieldOf(transport, UPDATE_ZONE_MUTATION, 'updateZone', { input: { ...zoneInput(patch, false), id: zoneId } }, unscoped(options));
114
+ },
115
+ async delete(id, options) {
116
+ requireHumanSession(config);
117
+ assertDeleted('ZONE_NOT_DELETED', [
118
+ await fieldOf(transport, DELETE_ZONE_MUTATION, 'deleteZone', { id: requireId(id, 'id', zoneRefusal) }, unscoped(options)),
119
+ ]);
120
+ },
121
+ async addMembers(zoneId, memberIds, options) {
122
+ requireHumanSession(config);
123
+ return fieldOf(transport, ADD_MEMBERS_TO_ZONE_MUTATION, 'addMembersToZone', {
124
+ zoneId: requireId(zoneId, 'zoneId', zoneRefusal),
125
+ memberIds: memberIdsInput(memberIds, false),
126
+ }, unscoped(options));
127
+ },
128
+ async removeMembers(zoneId, memberIds, options) {
129
+ requireHumanSession(config);
130
+ return fieldOf(transport, REMOVE_MEMBERS_FROM_ZONE_MUTATION, 'removeMembersFromZone', {
131
+ zoneId: requireId(zoneId, 'zoneId', zoneRefusal),
132
+ memberIds: memberIdsInput(memberIds, false),
133
+ }, unscoped(options));
134
+ },
135
+ },
136
+ countries: {
137
+ async update(id, patch, options) {
138
+ requireHumanSession(config);
139
+ const countryId = requireId(id, 'id', countryRefusal);
140
+ return fieldOf(transport, UPDATE_COUNTRY_MUTATION, 'updateCountry', { input: { ...countryInput(patch), id: countryId } }, unscoped(options));
141
+ },
142
+ },
143
+ };
144
+ }