@sixtyninecommerce/admin 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/README.md +5772 -118
  2. package/dist/auth/index.d.ts +7 -1
  3. package/dist/auth/index.js +8 -1
  4. package/dist/catalog/assets.d.ts +120 -8
  5. package/dist/catalog/assets.js +98 -12
  6. package/dist/catalog/collections.d.ts +279 -14
  7. package/dist/catalog/collections.js +246 -15
  8. package/dist/catalog/documents.d.ts +142 -0
  9. package/dist/catalog/documents.js +114 -0
  10. package/dist/catalog/index.d.ts +54 -6
  11. package/dist/catalog/index.js +10 -1
  12. package/dist/catalog/metadata.d.ts +244 -0
  13. package/dist/catalog/metadata.js +220 -0
  14. package/dist/catalog/products.d.ts +159 -19
  15. package/dist/catalog/products.js +88 -13
  16. package/dist/catalog/properties.d.ts +140 -14
  17. package/dist/catalog/properties.js +65 -1
  18. package/dist/catalog/recommendations.d.ts +288 -0
  19. package/dist/catalog/recommendations.js +237 -0
  20. package/dist/catalog/reviews.d.ts +381 -0
  21. package/dist/catalog/reviews.js +325 -0
  22. package/dist/catalog/types.d.ts +17 -1
  23. package/dist/catalog/types.js +10 -0
  24. package/dist/catalog/variant-find.d.ts +136 -0
  25. package/dist/catalog/variant-find.js +239 -0
  26. package/dist/catalog/variants.d.ts +293 -26
  27. package/dist/catalog/variants.js +164 -7
  28. package/dist/catalog/visibility.d.ts +38 -7
  29. package/dist/catalog/visibility.js +19 -1
  30. package/dist/client.d.ts +121 -9
  31. package/dist/client.js +53 -14
  32. package/dist/content/assets.d.ts +285 -0
  33. package/dist/content/assets.js +181 -0
  34. package/dist/content/forms.d.ts +332 -1
  35. package/dist/content/forms.js +140 -4
  36. package/dist/content/items.d.ts +292 -0
  37. package/dist/content/items.js +170 -0
  38. package/dist/content/menus.d.ts +254 -0
  39. package/dist/content/menus.js +70 -0
  40. package/dist/content/strings.d.ts +137 -0
  41. package/dist/content/strings.js +82 -0
  42. package/dist/content/types.d.ts +282 -0
  43. package/dist/content/types.js +100 -0
  44. package/dist/customers/age-verification.d.ts +66 -0
  45. package/dist/customers/age-verification.js +79 -0
  46. package/dist/customers/credit.d.ts +263 -0
  47. package/dist/customers/credit.js +244 -0
  48. package/dist/customers/delivery-preferences.d.ts +120 -0
  49. package/dist/customers/delivery-preferences.js +97 -0
  50. package/dist/customers/delivery-terms.d.ts +44 -0
  51. package/dist/customers/delivery-terms.js +32 -0
  52. package/dist/customers/groups.d.ts +207 -0
  53. package/dist/customers/groups.js +438 -0
  54. package/dist/customers/index.d.ts +462 -37
  55. package/dist/customers/index.js +317 -40
  56. package/dist/customers/invitation-status.d.ts +57 -0
  57. package/dist/customers/invitation-status.js +21 -0
  58. package/dist/customers/notes.d.ts +67 -0
  59. package/dist/customers/notes.js +51 -0
  60. package/dist/customers/notification-recipients.d.ts +97 -0
  61. package/dist/customers/notification-recipients.js +84 -0
  62. package/dist/customers/saved-lists.d.ts +42 -0
  63. package/dist/customers/saved-lists.js +61 -0
  64. package/dist/customers/vat.d.ts +83 -0
  65. package/dist/customers/vat.js +46 -0
  66. package/dist/customers/wire.d.ts +20 -0
  67. package/dist/customers/wire.js +35 -0
  68. package/dist/documents/ar.d.ts +307 -0
  69. package/dist/documents/ar.js +254 -0
  70. package/dist/documents/emails.d.ts +150 -0
  71. package/dist/documents/emails.js +220 -0
  72. package/dist/documents/index.d.ts +744 -21
  73. package/dist/documents/index.js +438 -24
  74. package/dist/documents/pdf.d.ts +2 -0
  75. package/dist/documents/pdf.js +11 -0
  76. package/dist/fulfillments/create.d.ts +167 -0
  77. package/dist/fulfillments/create.js +216 -0
  78. package/dist/fulfillments/index.d.ts +163 -11
  79. package/dist/fulfillments/index.js +135 -6
  80. package/dist/fulfillments/payment-policy.d.ts +64 -0
  81. package/dist/fulfillments/payment-policy.js +140 -0
  82. package/dist/fulfillments/warehouse.d.ts +724 -0
  83. package/dist/fulfillments/warehouse.js +914 -0
  84. package/dist/geo/index.d.ts +119 -0
  85. package/dist/geo/index.js +107 -0
  86. package/dist/index.d.ts +147 -39
  87. package/dist/index.js +51 -12
  88. package/dist/integrations/afas-actions.d.ts +234 -0
  89. package/dist/integrations/afas-actions.js +310 -0
  90. package/dist/integrations/afas-types.d.ts +483 -0
  91. package/dist/integrations/afas-types.js +5 -0
  92. package/dist/integrations/afas.d.ts +234 -0
  93. package/dist/integrations/afas.js +290 -0
  94. package/dist/integrations/connections.d.ts +103 -0
  95. package/dist/integrations/connections.js +55 -0
  96. package/dist/integrations/esp.d.ts +313 -0
  97. package/dist/integrations/esp.js +169 -0
  98. package/dist/integrations/exact-online-types.d.ts +965 -0
  99. package/dist/integrations/exact-online-types.js +6 -0
  100. package/dist/integrations/exact-online.d.ts +784 -0
  101. package/dist/integrations/exact-online.js +1089 -0
  102. package/dist/integrations/index.d.ts +44 -3
  103. package/dist/integrations/index.js +23 -3
  104. package/dist/integrations/pipeline-discoveries.d.ts +65 -0
  105. package/dist/integrations/pipeline-discoveries.js +65 -0
  106. package/dist/integrations/pipeline-keys.d.ts +13 -0
  107. package/dist/integrations/pipeline-keys.js +22 -0
  108. package/dist/integrations/pipeline-runs.d.ts +119 -0
  109. package/dist/integrations/pipeline-runs.js +87 -0
  110. package/dist/integrations/pipelines.d.ts +87 -15
  111. package/dist/integrations/pipelines.js +41 -22
  112. package/dist/integrations/price-import.d.ts +223 -0
  113. package/dist/integrations/price-import.js +124 -0
  114. package/dist/integrations/types.d.ts +367 -3
  115. package/dist/integrations/types.js +4 -3
  116. package/dist/locations/index.d.ts +66 -0
  117. package/dist/locations/index.js +31 -0
  118. package/dist/meta/index.d.ts +172 -0
  119. package/dist/metafields/index.d.ts +8 -1
  120. package/dist/metafields/index.js +10 -0
  121. package/dist/orders/cancel-with-money.d.ts +237 -0
  122. package/dist/orders/cancel-with-money.js +155 -0
  123. package/dist/orders/commit-edit.d.ts +23 -1
  124. package/dist/orders/commit-edit.js +19 -2
  125. package/dist/orders/deposit-policy.d.ts +3 -1
  126. package/dist/orders/drafts.d.ts +800 -0
  127. package/dist/orders/drafts.js +771 -0
  128. package/dist/orders/fields.d.ts +245 -0
  129. package/dist/orders/fields.js +188 -0
  130. package/dist/orders/get.d.ts +29 -19
  131. package/dist/orders/get.js +17 -8
  132. package/dist/orders/index.d.ts +66 -15
  133. package/dist/orders/index.js +25 -2
  134. package/dist/orders/lifecycle.d.ts +128 -0
  135. package/dist/orders/lifecycle.js +158 -0
  136. package/dist/orders/line-price-preview.d.ts +7 -2
  137. package/dist/orders/list.d.ts +243 -0
  138. package/dist/orders/list.js +487 -0
  139. package/dist/orders/manual-discounts.d.ts +13 -5
  140. package/dist/orders/manual-pricing.d.ts +5 -0
  141. package/dist/orders/money-view.d.ts +149 -0
  142. package/dist/orders/money-view.js +99 -0
  143. package/dist/orders/order-entry.d.ts +20 -7
  144. package/dist/orders/plan-edit.d.ts +34 -2
  145. package/dist/orders/plan-edit.js +112 -11
  146. package/dist/orders/settings.d.ts +201 -0
  147. package/dist/orders/settings.js +325 -0
  148. package/dist/orders/status.d.ts +159 -13
  149. package/dist/orders/status.js +21 -0
  150. package/dist/orders/surcharges.d.ts +20 -1
  151. package/dist/orders/surcharges.js +37 -11
  152. package/dist/payments/account-credit.d.ts +46 -0
  153. package/dist/payments/account-credit.js +42 -0
  154. package/dist/payments/ar.d.ts +292 -0
  155. package/dist/payments/ar.js +223 -0
  156. package/dist/payments/connected-account.d.ts +47 -0
  157. package/dist/payments/connected-account.js +61 -0
  158. package/dist/payments/credit-noted-fields.d.ts +19 -0
  159. package/dist/payments/credit-noted-fields.js +69 -0
  160. package/dist/payments/customer-consent.d.ts +30 -0
  161. package/dist/payments/customer-consent.js +49 -0
  162. package/dist/payments/index.d.ts +340 -19
  163. package/dist/payments/index.js +151 -1
  164. package/dist/payments/methods.d.ts +303 -8
  165. package/dist/payments/methods.js +337 -7
  166. package/dist/payments/mollie-methods.d.ts +159 -0
  167. package/dist/payments/mollie-methods.js +141 -0
  168. package/dist/payments/payment-links.d.ts +180 -0
  169. package/dist/payments/payment-links.js +97 -0
  170. package/dist/payments/provider-keys.d.ts +150 -0
  171. package/dist/payments/provider-keys.js +118 -0
  172. package/dist/payments/providers.d.ts +86 -11
  173. package/dist/payments/providers.js +29 -0
  174. package/dist/payments/requests.d.ts +147 -0
  175. package/dist/payments/requests.js +93 -0
  176. package/dist/payments/store-credit-fields.d.ts +15 -0
  177. package/dist/payments/store-credit-fields.js +51 -0
  178. package/dist/pickups/index.d.ts +106 -0
  179. package/dist/pickups/index.js +108 -0
  180. package/dist/platform/channels.d.ts +132 -0
  181. package/dist/platform/channels.js +128 -0
  182. package/dist/platform/email-gateways.d.ts +165 -0
  183. package/dist/platform/email-gateways.js +78 -0
  184. package/dist/platform/geo.d.ts +123 -0
  185. package/dist/platform/geo.js +144 -0
  186. package/dist/platform/tax.d.ts +162 -0
  187. package/dist/platform/tax.js +169 -0
  188. package/dist/pos/anomalies.d.ts +158 -0
  189. package/dist/pos/anomalies.js +64 -0
  190. package/dist/pos/assisted-selling.d.ts +79 -0
  191. package/dist/pos/assisted-selling.js +38 -0
  192. package/dist/pos/exchanges.d.ts +108 -0
  193. package/dist/pos/exchanges.js +22 -0
  194. package/dist/pos/gift-cards.d.ts +135 -0
  195. package/dist/pos/gift-cards.js +47 -0
  196. package/dist/pos/index.d.ts +2370 -58
  197. package/dist/pos/index.js +854 -7
  198. package/dist/pos/no-receipt.d.ts +94 -0
  199. package/dist/pos/no-receipt.js +25 -0
  200. package/dist/pos/offline-sales.d.ts +228 -0
  201. package/dist/pos/offline-sales.js +170 -0
  202. package/dist/pos/pay-by-link.d.ts +143 -0
  203. package/dist/pos/pay-by-link.js +57 -0
  204. package/dist/pos/quick-keys.d.ts +103 -0
  205. package/dist/pos/quick-keys.js +45 -0
  206. package/dist/pos/reports.d.ts +360 -0
  207. package/dist/pos/reports.js +62 -0
  208. package/dist/pos/returns.d.ts +244 -0
  209. package/dist/pos/returns.js +83 -0
  210. package/dist/pricing/bulk-coupons.d.ts +139 -0
  211. package/dist/pricing/bulk-coupons.js +112 -0
  212. package/dist/pricing/index.d.ts +174 -4
  213. package/dist/pricing/index.js +174 -1
  214. package/dist/pricing/price-lists.d.ts +606 -0
  215. package/dist/pricing/price-lists.js +600 -0
  216. package/dist/purchasing/alerts.d.ts +104 -0
  217. package/dist/purchasing/alerts.js +88 -0
  218. package/dist/purchasing/backorders.d.ts +61 -0
  219. package/dist/purchasing/backorders.js +92 -0
  220. package/dist/purchasing/cost.d.ts +110 -0
  221. package/dist/purchasing/cost.js +86 -0
  222. package/dist/purchasing/index.d.ts +54 -0
  223. package/dist/purchasing/index.js +24 -0
  224. package/dist/purchasing/links.d.ts +263 -0
  225. package/dist/purchasing/links.js +192 -0
  226. package/dist/purchasing/purchase-orders.d.ts +551 -0
  227. package/dist/purchasing/purchase-orders.js +491 -0
  228. package/dist/purchasing/receipts.d.ts +396 -0
  229. package/dist/purchasing/receipts.js +287 -0
  230. package/dist/purchasing/replenishment.d.ts +281 -0
  231. package/dist/purchasing/replenishment.js +240 -0
  232. package/dist/purchasing/returns.d.ts +273 -0
  233. package/dist/purchasing/returns.js +229 -0
  234. package/dist/purchasing/settings.d.ts +152 -0
  235. package/dist/purchasing/settings.js +181 -0
  236. package/dist/purchasing/suppliers.d.ts +476 -0
  237. package/dist/purchasing/suppliers.js +438 -0
  238. package/dist/quotes/index.d.ts +518 -0
  239. package/dist/quotes/index.js +439 -0
  240. package/dist/reports/index.d.ts +71 -8
  241. package/dist/reports/index.js +74 -11
  242. package/dist/returns/index.d.ts +675 -21
  243. package/dist/returns/index.js +756 -11
  244. package/dist/search/index.d.ts +76 -1
  245. package/dist/search/index.js +63 -0
  246. package/dist/seo/config.d.ts +128 -0
  247. package/dist/seo/config.js +95 -0
  248. package/dist/seo/redirects.d.ts +125 -0
  249. package/dist/seo/redirects.js +60 -1
  250. package/dist/seo/settings.d.ts +147 -0
  251. package/dist/seo/settings.js +209 -0
  252. package/dist/shipping/definitions.d.ts +29 -0
  253. package/dist/shipping/definitions.js +47 -0
  254. package/dist/shipping/fulfillment-profiles.d.ts +174 -0
  255. package/dist/shipping/fulfillment-profiles.js +104 -0
  256. package/dist/shipping/hendriks.d.ts +97 -0
  257. package/dist/shipping/hendriks.js +50 -0
  258. package/dist/shipping/index.d.ts +54 -0
  259. package/dist/shipping/index.js +27 -0
  260. package/dist/shipping/methods.d.ts +205 -0
  261. package/dist/shipping/methods.js +240 -0
  262. package/dist/shipping/myparcel.d.ts +668 -0
  263. package/dist/shipping/myparcel.js +466 -0
  264. package/dist/shipping/parcel-count-suggestion.d.ts +2 -0
  265. package/dist/shipping/parcel-rule.d.ts +6 -5
  266. package/dist/shipping/parcel-rule.js +21 -23
  267. package/dist/shipping/rate-tables.d.ts +324 -0
  268. package/dist/shipping/rate-tables.js +390 -0
  269. package/dist/shipping/ship-to-countries.d.ts +75 -0
  270. package/dist/shipping/ship-to-countries.js +47 -0
  271. package/dist/stock/by-location.d.ts +86 -0
  272. package/dist/stock/by-location.js +94 -0
  273. package/dist/stock/index.d.ts +18 -4
  274. package/dist/stock/index.js +7 -0
  275. package/dist/stock/locations.d.ts +144 -0
  276. package/dist/stock/locations.js +168 -0
  277. package/dist/stock/settings.d.ts +106 -0
  278. package/dist/stock/settings.js +103 -0
  279. package/dist/stock/transfers.d.ts +386 -52
  280. package/dist/stock/transfers.js +222 -17
  281. package/dist/stored-value/index.d.ts +312 -11
  282. package/dist/stored-value/index.js +223 -4
  283. package/dist/subscriptions/index.d.ts +682 -0
  284. package/dist/subscriptions/index.js +815 -0
  285. package/dist/tax/display-policy.d.ts +105 -0
  286. package/dist/tax/display-policy.js +84 -0
  287. package/dist/tax/index.d.ts +79 -1
  288. package/dist/tax/index.js +52 -0
  289. package/dist/tenant/email-style.d.ts +102 -0
  290. package/dist/tenant/email-style.js +72 -0
  291. package/dist/tenant/email-templates.d.ts +287 -0
  292. package/dist/tenant/email-templates.js +271 -0
  293. package/dist/tenant/notifications.d.ts +23 -17
  294. package/dist/tenant/settings-sources.d.ts +1 -1
  295. package/dist/tenant/time-zone.d.ts +17 -0
  296. package/dist/tenant/time-zone.js +11 -0
  297. package/dist/types.d.ts +50 -3
  298. package/dist/users/index.d.ts +3 -0
  299. package/dist/users/index.js +2 -0
  300. package/dist/users/warehouses.d.ts +50 -0
  301. package/dist/users/warehouses.js +51 -0
  302. package/package.json +2 -2
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Product reviews (headless spec §9.2 `catalog`: "reviews moderation"; W10-117): the reviews the
3
+ * store's customers wrote, moderating them (what the storefront shows under the store's name),
4
+ * the store's reply to one, the review statistics, and the review attributes (the questions a
5
+ * review form asks: "Quality", "Fit", …).
6
+ *
7
+ * - **What the storefront shows**: only `APPROVED` reviews, with the reviewer's display name,
8
+ * rating, title, text, attribute answers and the store's reply. `PENDING` (waiting), `FLAGGED`
9
+ * (the automatic checks or a shopper's report asked for a look) and `REJECTED` reviews are never
10
+ * shown. Approving publishes the review; rejecting, flagging or re-opening it (`PENDING`) takes it
11
+ * off. Each moderation is recorded in the review's moderation log with the staff member and the
12
+ * reason given.
13
+ * - **The reply** is the store's own text, shown under an approved review on the storefront.
14
+ *
15
+ * Customer data (headless spec §6.3): an app installation below `customerData: full` reads every
16
+ * review with what the customer typed blanked: `title`, `body`, `attributeValues` (ratings and
17
+ * self-declared details such as an age band) and `ipAddressHash`, the edit history's previous
18
+ * texts and the moderation log's reasons and AI analysis answer `null`, and so does
19
+ * `customerDisplayName` below `identity`. Every record of such an answer carries `redacted: true`,
20
+ * so a blanked field never reads as "the customer wrote none". A `search` below `full` is refused
21
+ * `CUSTOMER_DATA_NOT_GRANTED` (`PermissionDeniedError`, 403). The answers of the writes are
22
+ * blanked the same way.
23
+ *
24
+ * Publishing needs `full` (user decision O6): approving a review (`moderate`, or `bulkModerate`
25
+ * with `APPROVED`) and `reply.set` publish under the store's name next to text a caller below
26
+ * `full` reads blanked, so an app installation below it (or a request narrowed below it) is
27
+ * refused them with `CUSTOMER_DATA_NOT_GRANTED` (`PermissionDeniedError`, 403; `bulkModerate`
28
+ * reports it on each review). It may still reject, flag or re-open a review and remove the
29
+ * store's reply. A staff session and a personal API token act at `full`.
30
+ *
31
+ * Permissions: reads need `ReadCatalog`, writes `UpdateCatalog`. Every row is the store's own: a
32
+ * review or attribute of another store is never answered and never changed.
33
+ *
34
+ * Refusals, GraphQL errors mapped by `core`. An unknown id and another store's get the same
35
+ * answer, so none tells whether another store holds the row:
36
+ * - `ENTITY_NOT_FOUND` → `NotFoundError` (404): a review or attribute the store does not have. An
37
+ * id that is not a uuid gets it too, before anything is read.
38
+ * - `REVIEW_ATTRIBUTE_SLUG_TAKEN` → `ConflictError` (409): another of the store's attributes holds
39
+ * the slug, the one derived from the label included, also when two writes race for it.
40
+ * - `REVIEW_ATTRIBUTE_COLLECTION_NOT_FOUND` → `NotFoundError` (404): `attributes.setCollections`
41
+ * named a collection the store does not hold, each in `details` (`field` `/collectionIds/<i>`).
42
+ * - `BULK_ACTION_INPUT_INVALID` → `ValidationError` (400): a `bulkModerate` selection of no
43
+ * review, more than {@link MAX_BULK_REVIEWS} or one twice, refused before anything is sent.
44
+ * - `USER_INPUT_ERROR` → `ValidationError` (400), in the engine's words: a reply over 2,000
45
+ * characters, an attribute type other than `rating` / `selection`, a selection with fewer than
46
+ * two options.
47
+ * - `FORBIDDEN` → `PermissionDeniedError` (403) without the permission.
48
+ *
49
+ * Release: `bulkModerate` needs a platform release with `moderateReviews` (deploy vendure-server
50
+ * first: an older one refuses the document). Every other method is served by every release this
51
+ * SDK supports; an older release answers each not-found and a taken slug as `USER_INPUT_ERROR`
52
+ * (400), a taken slug as an uncoded database error, and takes another store's collection in
53
+ * `setCollections`. No method deduplicates on `idempotencyKey`.
54
+ */
55
+ import { pageFromVendureList, readOffsetCursor, UpstreamError, ValidationError, } from '@sixtyninecommerce/core';
56
+ import { redactionWatcher } from '../customer-data-redaction.js';
57
+ import { assertDeleted, DELETION_FIELDS, fieldOf } from './types.js';
58
+ export const REVIEW_STATUSES = [
59
+ 'PENDING',
60
+ 'APPROVED',
61
+ 'REJECTED',
62
+ 'FLAGGED',
63
+ ];
64
+ /** How many reviews one list page holds at most: the engine answers no more. */
65
+ export const MAX_REVIEW_PAGE_SIZE = 50;
66
+ /** How many reviews one `bulkModerate` call may name. */
67
+ export const MAX_BULK_REVIEWS = 100;
68
+ const REVIEW_FIELDS = /* GraphQL */ `
69
+ id
70
+ channelId
71
+ customerId
72
+ productId
73
+ productName
74
+ variantId
75
+ rating
76
+ title
77
+ body
78
+ status
79
+ isVerifiedPurchase
80
+ purchaseDate
81
+ deliveryDate
82
+ isEdited
83
+ editCount
84
+ customerDisplayName
85
+ imageAssetIds
86
+ videoAssetIds
87
+ attributeValues
88
+ helpfulCount
89
+ notHelpfulCount
90
+ merchantReply
91
+ merchantReplyAt
92
+ ipAddressHash
93
+ createdAt
94
+ updatedAt
95
+ `;
96
+ const REVIEW_DETAIL_FIELDS = /* GraphQL */ `
97
+ ${REVIEW_FIELDS}
98
+ editHistory {
99
+ id
100
+ previousRating
101
+ previousTitle
102
+ previousBody
103
+ previousAttributeValues
104
+ editedAt
105
+ reEnteredModeration
106
+ createdAt
107
+ }
108
+ moderationLogs {
109
+ id
110
+ step
111
+ action
112
+ reason
113
+ confidence
114
+ aiAnalysis
115
+ performedBy
116
+ tokenUsage
117
+ createdAt
118
+ }
119
+ `;
120
+ const ATTRIBUTE_FIELDS = /* GraphQL */ `
121
+ id
122
+ slug
123
+ label
124
+ inputType
125
+ options
126
+ isRequired
127
+ enabled
128
+ section
129
+ position
130
+ collectionIds
131
+ createdAt
132
+ updatedAt
133
+ `;
134
+ export const PRODUCT_REVIEWS_QUERY = /* GraphQL */ `
135
+ query CatalogProductReviews($options: AdminReviewListOptions) {
136
+ adminProductReviews(options: $options) {
137
+ items { ${REVIEW_FIELDS} }
138
+ totalItems
139
+ }
140
+ }
141
+ `;
142
+ export const PRODUCT_REVIEW_QUERY = /* GraphQL */ `
143
+ query CatalogProductReview($id: ID!) {
144
+ adminProductReview(id: $id) { ${REVIEW_DETAIL_FIELDS} }
145
+ }
146
+ `;
147
+ export const REVIEW_STATS_QUERY = /* GraphQL */ `
148
+ query CatalogReviewStats {
149
+ reviewStats {
150
+ totalReviews
151
+ pendingCount
152
+ approvedCount
153
+ rejectedCount
154
+ flaggedCount
155
+ averageRating
156
+ ratingsThisWeek
157
+ ratingsThisMonth
158
+ }
159
+ }
160
+ `;
161
+ export const MODERATE_REVIEW_MUTATION = /* GraphQL */ `
162
+ mutation CatalogModerateReview($id: ID!, $action: ReviewStatus!, $reason: String) {
163
+ moderateReview(id: $id, action: $action, reason: $reason) { ${REVIEW_FIELDS} }
164
+ }
165
+ `;
166
+ export const MODERATE_REVIEWS_MUTATION = /* GraphQL */ `
167
+ mutation CatalogModerateReviews($input: ModerateReviewsInput!) {
168
+ moderateReviews(input: $input) {
169
+ items { id status code message }
170
+ }
171
+ }
172
+ `;
173
+ export const SET_MERCHANT_REPLY_MUTATION = /* GraphQL */ `
174
+ mutation CatalogSetMerchantReply($reviewId: ID!, $reply: String!) {
175
+ setMerchantReply(reviewId: $reviewId, reply: $reply) { ${REVIEW_FIELDS} }
176
+ }
177
+ `;
178
+ export const REMOVE_MERCHANT_REPLY_MUTATION = /* GraphQL */ `
179
+ mutation CatalogRemoveMerchantReply($reviewId: ID!) {
180
+ removeMerchantReply(reviewId: $reviewId) { ${REVIEW_FIELDS} }
181
+ }
182
+ `;
183
+ export const REVIEW_ATTRIBUTES_QUERY = /* GraphQL */ `
184
+ query CatalogReviewAttributes {
185
+ adminReviewAttributes { ${ATTRIBUTE_FIELDS} }
186
+ }
187
+ `;
188
+ export const CREATE_REVIEW_ATTRIBUTE_MUTATION = /* GraphQL */ `
189
+ mutation CatalogCreateReviewAttribute($input: AdminReviewAttributeInput!) {
190
+ createReviewAttribute(input: $input) { ${ATTRIBUTE_FIELDS} }
191
+ }
192
+ `;
193
+ export const UPDATE_REVIEW_ATTRIBUTE_MUTATION = /* GraphQL */ `
194
+ mutation CatalogUpdateReviewAttribute($id: ID!, $input: UpdateReviewAttributeInput!) {
195
+ updateReviewAttribute(id: $id, input: $input) { ${ATTRIBUTE_FIELDS} }
196
+ }
197
+ `;
198
+ export const DELETE_REVIEW_ATTRIBUTE_MUTATION = /* GraphQL */ `
199
+ mutation CatalogDeleteReviewAttribute($id: ID!) {
200
+ deleteReviewAttribute(id: $id) { ${DELETION_FIELDS} }
201
+ }
202
+ `;
203
+ export const REORDER_REVIEW_ATTRIBUTES_MUTATION = /* GraphQL */ `
204
+ mutation CatalogReorderReviewAttributes($ids: [ID!]!) {
205
+ reorderReviewAttributes(ids: $ids) { ${ATTRIBUTE_FIELDS} }
206
+ }
207
+ `;
208
+ export const SET_REVIEW_ATTRIBUTE_COLLECTIONS_MUTATION = /* GraphQL */ `
209
+ mutation CatalogSetReviewAttributeCollections($attributeId: ID!, $collectionIds: [ID!]!) {
210
+ setReviewAttributeCollections(attributeId: $attributeId, collectionIds: $collectionIds) {
211
+ ${ATTRIBUTE_FIELDS}
212
+ }
213
+ }
214
+ `;
215
+ function unexpected(field) {
216
+ return new UpstreamError({
217
+ code: 'UNEXPECTED_RESPONSE',
218
+ message: `The engine answered without "${field}".`,
219
+ status: 502,
220
+ service: 'vendure',
221
+ });
222
+ }
223
+ function invalid(code, message) {
224
+ return new ValidationError({ code, message, status: 400, service: 'vendure' });
225
+ }
226
+ /** Attributes in `position` order, the older first on a tie (the engine orders by position only). */
227
+ function byPosition(attributes) {
228
+ return [...attributes].sort((a, b) => a.position - b.position || a.createdAt.localeCompare(b.createdAt));
229
+ }
230
+ /** The selection of a bulk call, checked as the engine checks it, before anything is sent. */
231
+ function bulkVariables(input) {
232
+ const refused = (message) => invalid('BULK_ACTION_INPUT_INVALID', message);
233
+ if (typeof input !== 'object' || input === null) {
234
+ throw refused('A bulk moderation needs ids and an action.');
235
+ }
236
+ if (!REVIEW_STATUSES.includes(input.action)) {
237
+ throw refused(`action must be one of ${REVIEW_STATUSES.join(', ')}.`);
238
+ }
239
+ const ids = input.ids;
240
+ if (!Array.isArray(ids) || !ids.every((id) => typeof id === 'string')) {
241
+ throw refused('ids must be a list of strings.');
242
+ }
243
+ if (ids.length === 0)
244
+ throw refused('Name at least one review.');
245
+ if (ids.length > MAX_BULK_REVIEWS) {
246
+ throw refused(`A bulk moderation takes at most ${String(MAX_BULK_REVIEWS)} reviews.`);
247
+ }
248
+ if (new Set(ids.map((id) => id.toLowerCase())).size !== ids.length) {
249
+ throw refused('Name each review once.');
250
+ }
251
+ return {
252
+ input: {
253
+ ids: [...ids],
254
+ action: input.action,
255
+ ...(input.reason === undefined ? {} : { reason: input.reason }),
256
+ },
257
+ };
258
+ }
259
+ export function createReviewsModule(transport) {
260
+ async function reviewCall(document, field, variables, options, nullable = false) {
261
+ const redaction = redactionWatcher();
262
+ const data = await transport.graphql({ api: 'admin', query: document, variables, onExtensions: redaction.onExtensions }, options);
263
+ if (data === null || typeof data !== 'object' || !Object.hasOwn(data, field)) {
264
+ throw unexpected(field);
265
+ }
266
+ const review = data[field] ?? null;
267
+ if (review === null && !nullable)
268
+ throw unexpected(field);
269
+ return review === null ? null : redaction.mark(review);
270
+ }
271
+ async function reviewsPage(filters, skip, take, call) {
272
+ const redaction = redactionWatcher();
273
+ const data = await transport.graphql({
274
+ api: 'admin',
275
+ query: PRODUCT_REVIEWS_QUERY,
276
+ variables: { options: { ...filters, skip, take } },
277
+ onExtensions: redaction.onExtensions,
278
+ }, call);
279
+ const list = data?.adminProductReviews;
280
+ if (!list)
281
+ throw unexpected('adminProductReviews');
282
+ return pageFromVendureList({ items: list.items.map((review) => redaction.mark(review)), totalItems: list.totalItems }, skip, take, (cursor) => reviewsPage(filters, readOffsetCursor(cursor), take, call));
283
+ }
284
+ return {
285
+ // `async`: a refused option rejects the promise, as an engine refusal does.
286
+ async list(options) {
287
+ const { status, productId, customerId, search, rating, sort, limit, after, ...call } = options ?? {};
288
+ const take = limit ?? 25;
289
+ if (!Number.isInteger(take) || take < 1 || take > MAX_REVIEW_PAGE_SIZE) {
290
+ throw invalid('INVALID_PAGE_SIZE', `limit must be a whole number from 1 to ${String(MAX_REVIEW_PAGE_SIZE)}; got ${String(take)}.`);
291
+ }
292
+ const filters = Object.fromEntries(Object.entries({ status, productId, customerId, search, rating, sort }).filter(([, value]) => value !== undefined && value !== ''));
293
+ return reviewsPage(filters, after === undefined ? 0 : readOffsetCursor(after), take, call);
294
+ },
295
+ get: (id, options) => reviewCall(PRODUCT_REVIEW_QUERY, 'adminProductReview', { id }, options, true),
296
+ async stats(options) {
297
+ const stats = await fieldOf(transport, REVIEW_STATS_QUERY, 'reviewStats', {}, options);
298
+ if (stats === null)
299
+ throw unexpected('reviewStats');
300
+ return stats;
301
+ },
302
+ moderate: (id, input, options) => reviewCall(MODERATE_REVIEW_MUTATION, 'moderateReview', { id, action: input.action, reason: input.reason ?? null }, options),
303
+ async bulkModerate(input, options) {
304
+ const data = await fieldOf(transport, MODERATE_REVIEWS_MUTATION, 'moderateReviews', bulkVariables(input), options);
305
+ if (data === null)
306
+ throw unexpected('moderateReviews');
307
+ return data.items;
308
+ },
309
+ reply: {
310
+ set: (reviewId, reply, options) => reviewCall(SET_MERCHANT_REPLY_MUTATION, 'setMerchantReply', { reviewId, reply }, options),
311
+ remove: (reviewId, options) => reviewCall(REMOVE_MERCHANT_REPLY_MUTATION, 'removeMerchantReply', { reviewId }, options),
312
+ },
313
+ attributes: {
314
+ list: async (options) => byPosition(await fieldOf(transport, REVIEW_ATTRIBUTES_QUERY, 'adminReviewAttributes', {}, options)),
315
+ create: (input, options) => fieldOf(transport, CREATE_REVIEW_ATTRIBUTE_MUTATION, 'createReviewAttribute', { input }, options),
316
+ update: (id, input, options) => fieldOf(transport, UPDATE_REVIEW_ATTRIBUTE_MUTATION, 'updateReviewAttribute', { id, input }, options),
317
+ async delete(id, options) {
318
+ const response = await fieldOf(transport, DELETE_REVIEW_ATTRIBUTE_MUTATION, 'deleteReviewAttribute', { id }, options);
319
+ assertDeleted('REVIEW_ATTRIBUTE_NOT_DELETED', [response]);
320
+ },
321
+ reorder: async (ids, options) => byPosition(await fieldOf(transport, REORDER_REVIEW_ATTRIBUTES_MUTATION, 'reorderReviewAttributes', { ids }, options)),
322
+ setCollections: (attributeId, collectionIds, options) => fieldOf(transport, SET_REVIEW_ATTRIBUTE_COLLECTIONS_MUTATION, 'setReviewAttributeCollections', { attributeId, collectionIds }, options),
323
+ },
324
+ };
325
+ }
@@ -9,7 +9,23 @@ export interface AssetRef {
9
9
  /** A preview URL the asset server scales; absolute or relative to the engine. */
10
10
  preview: string;
11
11
  source: string;
12
+ /** A string on the wire: a newer engine may add a type. */
13
+ type: 'IMAGE' | 'VIDEO' | 'BINARY' | (string & {});
14
+ mimeType: string;
15
+ /**
16
+ * The image's alt text: the store's default-language description
17
+ * (`catalog.assets.descriptions`); null when it has none.
18
+ */
19
+ altText: string | null;
20
+ }
21
+ /** An {@link AssetRef} as the Admin API answers it, before {@link toAssetRef}. */
22
+ export interface WireAssetRef extends Omit<AssetRef, 'altText'> {
23
+ customFields: {
24
+ altText?: string | null;
25
+ } | null;
12
26
  }
27
+ /** Lift the alt text out of the asset's custom fields. */
28
+ export declare function toAssetRef(wire: WireAssetRef): AssetRef;
13
29
  export interface FacetValueRef {
14
30
  id: string;
15
31
  code: string;
@@ -121,7 +137,7 @@ export interface CatalogueOwnership {
121
137
  }
122
138
  /** `catalogueOwnership` on the top-level row of each catalogue read (never on nested refs). */
123
139
  export declare const CATALOGUE_OWNERSHIP_FIELDS = "\n catalogueOwnership {\n state\n organizationName\n editableHere\n }\n";
124
- export declare const ASSET_REF_FIELDS = "\n id\n name\n preview\n source\n";
140
+ export declare const ASSET_REF_FIELDS = "\n id\n name\n preview\n source\n type\n mimeType\n customFields {\n altText\n }\n";
125
141
  export declare const FACET_VALUE_REF_FIELDS = "\n id\n code\n name\n facet {\n id\n code\n name\n }\n";
126
142
  /**
127
143
  * Refuse a datetime with no offset, or one that is not a date at all (spec
@@ -1,4 +1,9 @@
1
1
  import { ConflictError, errorFromErrorResult, isErrorResult, pageFromVendureList, readOffsetCursor, UpstreamError, ValidationError, } from '@sixtyninecommerce/core';
2
+ /** Lift the alt text out of the asset's custom fields. */
3
+ export function toAssetRef(wire) {
4
+ const { customFields, ...rest } = wire;
5
+ return { ...rest, altText: customFields?.altText ?? null };
6
+ }
2
7
  /** `catalogueOwnership` on the top-level row of each catalogue read (never on nested refs). */
3
8
  export const CATALOGUE_OWNERSHIP_FIELDS = /* GraphQL */ `
4
9
  catalogueOwnership {
@@ -12,6 +17,11 @@ export const ASSET_REF_FIELDS = /* GraphQL */ `
12
17
  name
13
18
  preview
14
19
  source
20
+ type
21
+ mimeType
22
+ customFields {
23
+ altText
24
+ }
15
25
  `;
16
26
  export const FACET_VALUE_REF_FIELDS = /* GraphQL */ `
17
27
  id
@@ -0,0 +1,136 @@
1
+ import { type Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ import type { InventoryFlag, VariantStorePrice } from './variants.js';
4
+ /** The most variants one `variants.find` answers. */
5
+ export declare const MAX_VARIANT_FIND_TAKE = 500;
6
+ /** The most products one `variants.find` search reads (`take / perProduct`, rounded up). */
7
+ export declare const MAX_VARIANT_FIND_PRODUCTS = 100;
8
+ /** Which variants `variants.find` answers: exactly one of `ids`, `term` and `skuPrefix`. */
9
+ export interface FindVariantsInput {
10
+ /**
11
+ * Exactly these variants, in the order asked. An id the store does not hold (another store's,
12
+ * a deleted variant, a hidden carrier variant) is left out, never an error; a repeated id is
13
+ * answered once. More than `take` distinct ids is refused.
14
+ */
15
+ ids?: readonly string[];
16
+ /**
17
+ * Full text over the store's product index (a product's name, SKUs, slug, variant names and
18
+ * description): the variants of the best-matching products, best product first.
19
+ */
20
+ term?: string;
21
+ /**
22
+ * The products that have a SKU starting with this text, by name, and of each the variants
23
+ * whose own SKU starts with it. Capitals do not matter.
24
+ */
25
+ skuPrefix?: string;
26
+ /** The most variants the answer holds, 1 to {@link MAX_VARIANT_FIND_TAKE}. */
27
+ take: number;
28
+ /**
29
+ * A search only: the most variants of ONE product, so a product line of thousands cannot fill
30
+ * the answer. From 1 to `take`; a tenth of `take`, rounded up, when omitted. The search reads
31
+ * the best `take / perProduct` products (rounded up, at most
32
+ * {@link MAX_VARIANT_FIND_PRODUCTS}).
33
+ */
34
+ perProduct?: number;
35
+ /**
36
+ * Price the variants in this currency. The store's default when omitted. The engine refuses a
37
+ * currency the store does not sell.
38
+ */
39
+ currencyCode?: string;
40
+ }
41
+ /** A found variant's stock at one of the store's locations. */
42
+ export interface FoundVariantStockLevel {
43
+ stockLocationId: string;
44
+ stockLocationName: string;
45
+ stockOnHand: number;
46
+ /** Units open orders have reserved there. */
47
+ stockAllocated: number;
48
+ }
49
+ /** The product a found variant belongs to. */
50
+ export interface FoundVariantProduct {
51
+ id: string;
52
+ name: string;
53
+ slug: string;
54
+ featuredAsset: {
55
+ id: string;
56
+ preview: string;
57
+ } | null;
58
+ }
59
+ /** A variant as a picker shows it: what it is, what it costs here and what is in stock. */
60
+ export interface FoundVariant {
61
+ id: string;
62
+ name: string;
63
+ sku: string;
64
+ /** A disabled variant is listed too; the engine refuses to add it to an order. */
65
+ enabled: boolean;
66
+ /**
67
+ * The variant's price in the store, in the currency asked for, or null when the store has no
68
+ * price for it there. Never 0 for a missing price.
69
+ */
70
+ storePrice: VariantStorePrice | null;
71
+ trackInventory: InventoryFlag;
72
+ /** The variant's own threshold; the platform's applies instead when the flag below is true. */
73
+ outOfStockThreshold: number;
74
+ useGlobalOutOfStockThreshold: boolean;
75
+ /**
76
+ * Units on hand and reserved across the store's locations, as the engine's stock rule reads
77
+ * them. What an order can still take is `stockOnHand − stockAllocated − threshold`.
78
+ */
79
+ stockOnHand: number;
80
+ stockAllocated: number;
81
+ /** The same per location; the store's own locations only. */
82
+ stockLevels: FoundVariantStockLevel[];
83
+ product: FoundVariantProduct;
84
+ }
85
+ /** One product of a search's answer. */
86
+ export interface FoundVariantsProduct {
87
+ id: string;
88
+ /** How many of the product's variants `items` holds. */
89
+ shown: number;
90
+ /** How many of its variants match: all of them for `term`, the prefix matches for `skuPrefix`. */
91
+ total: number;
92
+ }
93
+ export interface FoundVariants {
94
+ /**
95
+ * `ids`: in the order asked. A search: best product first, each product's variants together,
96
+ * in a stable order (by id), which is not the order a merchant arranged them in.
97
+ */
98
+ items: FoundVariant[];
99
+ /**
100
+ * A search: one entry per product in `items`, in their order. `total` above `shown` means the
101
+ * product has more matching variants than the answer holds. Empty for `ids`.
102
+ */
103
+ products: FoundVariantsProduct[];
104
+ }
105
+ /**
106
+ * What a picker reads of a variant. `storePrice`, never core's `price` / `priceWithTax`: those
107
+ * throw for a variant the store has no price for, and one such variant would empty the answer.
108
+ */
109
+ export declare const FOUND_VARIANT_FIELDS = "\n id\n name\n sku\n enabled\n storePrice {\n price\n priceWithTax\n currencyCode\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockOnHand\n stockAllocated\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n stockLocation {\n id\n name\n }\n }\n";
110
+ /** A found variant with its product: the selection of every list that answers variants flat. */
111
+ export declare const FOUND_VARIANT_WITH_PRODUCT_FIELDS = "\n \n id\n name\n sku\n enabled\n storePrice {\n price\n priceWithTax\n currencyCode\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockOnHand\n stockAllocated\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n stockLocation {\n id\n name\n }\n }\n\n product {\n \n id\n name\n slug\n featuredAsset {\n id\n preview\n }\n\n }\n";
112
+ export declare const FIND_VARIANTS_BY_IDS_QUERY = "\n query CatalogFindVariantsByIds($options: ProductVariantListOptions) {\n productVariants(options: $options) {\n items {\n \n \n id\n name\n sku\n enabled\n storePrice {\n price\n priceWithTax\n currencyCode\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockOnHand\n stockAllocated\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n stockLocation {\n id\n name\n }\n }\n\n product {\n \n id\n name\n slug\n featuredAsset {\n id\n preview\n }\n\n }\n\n }\n totalItems\n }\n }\n";
113
+ /**
114
+ * The variants of some products, bounded PER PRODUCT (`variantList`, never `variants`, which the
115
+ * engine answers whole and then cuts at 1,000 without saying so).
116
+ */
117
+ export declare const FIND_VARIANTS_OF_PRODUCTS_QUERY = "\n query CatalogFindVariantsOfProducts(\n $options: ProductListOptions\n $variantOptions: ProductVariantListOptions\n ) {\n products(options: $options) {\n items {\n \n id\n name\n slug\n featuredAsset {\n id\n preview\n }\n\n variantList(options: $variantOptions) {\n items {\n \n id\n name\n sku\n enabled\n storePrice {\n price\n priceWithTax\n currencyCode\n }\n trackInventory\n outOfStockThreshold\n useGlobalOutOfStockThreshold\n stockOnHand\n stockAllocated\n stockLevels {\n stockLocationId\n stockOnHand\n stockAllocated\n stockLocation {\n id\n name\n }\n }\n\n }\n totalItems\n }\n }\n totalItems\n }\n }\n";
118
+ interface WireFoundVariant extends Omit<FoundVariant, 'stockLevels' | 'product'> {
119
+ stockLevels: Array<{
120
+ stockLocationId: string;
121
+ stockOnHand: number;
122
+ stockAllocated: number;
123
+ stockLocation: {
124
+ id: string;
125
+ name: string;
126
+ } | null;
127
+ }>;
128
+ }
129
+ /** A found variant as a flat list answers it, its product on the row. */
130
+ export type WireFoundVariantWithProduct = WireFoundVariant & {
131
+ product: FoundVariantProduct;
132
+ };
133
+ export declare function toFoundVariant(wire: WireFoundVariantWithProduct): FoundVariant;
134
+ /** `catalog.variants.find`; its contract is on `VariantsModule.find`. */
135
+ export declare function findVariants(transport: Transport, input: FindVariantsInput, options?: Call): Promise<FoundVariants>;
136
+ export {};