@sixtyninecommerce/admin 0.10.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 (247) hide show
  1. package/README.md +4347 -171
  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 +159 -19
  9. package/dist/catalog/products.js +88 -13
  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 +233 -26
  21. package/dist/catalog/variants.js +125 -7
  22. package/dist/catalog/visibility.d.ts +20 -5
  23. package/dist/catalog/visibility.js +19 -1
  24. package/dist/client.d.ts +95 -9
  25. package/dist/client.js +35 -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 +44 -10
  63. package/dist/fulfillments/index.js +11 -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 +123 -35
  71. package/dist/index.js +45 -12
  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 +29 -3
  87. package/dist/integrations/index.js +18 -3
  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 +15 -14
  91. package/dist/integrations/pipelines.js +6 -22
  92. package/dist/integrations/types.d.ts +146 -0
  93. package/dist/locations/index.d.ts +66 -0
  94. package/dist/locations/index.js +31 -0
  95. package/dist/meta/index.d.ts +162 -0
  96. package/dist/orders/cancel-with-money.d.ts +227 -0
  97. package/dist/orders/cancel-with-money.js +153 -0
  98. package/dist/orders/commit-edit.d.ts +7 -1
  99. package/dist/orders/commit-edit.js +5 -1
  100. package/dist/orders/drafts.d.ts +800 -0
  101. package/dist/orders/drafts.js +771 -0
  102. package/dist/orders/fields.d.ts +245 -0
  103. package/dist/orders/fields.js +188 -0
  104. package/dist/orders/get.d.ts +29 -19
  105. package/dist/orders/get.js +17 -8
  106. package/dist/orders/index.d.ts +49 -9
  107. package/dist/orders/index.js +22 -2
  108. package/dist/orders/lifecycle.d.ts +128 -0
  109. package/dist/orders/lifecycle.js +158 -0
  110. package/dist/orders/line-price-preview.d.ts +7 -2
  111. package/dist/orders/list.d.ts +222 -0
  112. package/dist/orders/list.js +377 -0
  113. package/dist/orders/manual-pricing.d.ts +5 -0
  114. package/dist/orders/order-entry.d.ts +20 -7
  115. package/dist/orders/plan-edit.d.ts +1 -1
  116. package/dist/orders/plan-edit.js +4 -1
  117. package/dist/orders/settings.d.ts +201 -0
  118. package/dist/orders/settings.js +325 -0
  119. package/dist/orders/status.d.ts +159 -13
  120. package/dist/orders/status.js +21 -0
  121. package/dist/orders/surcharges.d.ts +8 -1
  122. package/dist/orders/surcharges.js +3 -2
  123. package/dist/payments/account-credit.d.ts +30 -0
  124. package/dist/payments/account-credit.js +19 -0
  125. package/dist/payments/ar.d.ts +292 -0
  126. package/dist/payments/ar.js +223 -0
  127. package/dist/payments/connected-account.d.ts +47 -0
  128. package/dist/payments/connected-account.js +61 -0
  129. package/dist/payments/credit-noted-fields.d.ts +12 -0
  130. package/dist/payments/credit-noted-fields.js +37 -0
  131. package/dist/payments/index.d.ts +257 -11
  132. package/dist/payments/index.js +131 -1
  133. package/dist/payments/methods.d.ts +303 -8
  134. package/dist/payments/methods.js +337 -7
  135. package/dist/payments/mollie-methods.d.ts +159 -0
  136. package/dist/payments/mollie-methods.js +141 -0
  137. package/dist/payments/payment-links.d.ts +180 -0
  138. package/dist/payments/payment-links.js +97 -0
  139. package/dist/payments/provider-keys.d.ts +150 -0
  140. package/dist/payments/provider-keys.js +118 -0
  141. package/dist/payments/providers.d.ts +86 -11
  142. package/dist/payments/providers.js +29 -0
  143. package/dist/payments/requests.d.ts +144 -0
  144. package/dist/payments/requests.js +92 -0
  145. package/dist/payments/store-credit-fields.d.ts +15 -0
  146. package/dist/payments/store-credit-fields.js +51 -0
  147. package/dist/pickups/index.d.ts +104 -0
  148. package/dist/pickups/index.js +105 -0
  149. package/dist/platform/channels.d.ts +132 -0
  150. package/dist/platform/channels.js +128 -0
  151. package/dist/platform/geo.d.ts +123 -0
  152. package/dist/platform/geo.js +144 -0
  153. package/dist/platform/tax.d.ts +154 -0
  154. package/dist/platform/tax.js +139 -0
  155. package/dist/pos/anomalies.d.ts +158 -0
  156. package/dist/pos/anomalies.js +64 -0
  157. package/dist/pos/assisted-selling.d.ts +79 -0
  158. package/dist/pos/assisted-selling.js +38 -0
  159. package/dist/pos/exchanges.d.ts +103 -0
  160. package/dist/pos/exchanges.js +22 -0
  161. package/dist/pos/gift-cards.d.ts +135 -0
  162. package/dist/pos/gift-cards.js +47 -0
  163. package/dist/pos/index.d.ts +2125 -32
  164. package/dist/pos/index.js +742 -4
  165. package/dist/pos/no-receipt.d.ts +94 -0
  166. package/dist/pos/no-receipt.js +25 -0
  167. package/dist/pos/offline-sales.d.ts +187 -0
  168. package/dist/pos/offline-sales.js +116 -0
  169. package/dist/pos/pay-by-link.d.ts +143 -0
  170. package/dist/pos/pay-by-link.js +57 -0
  171. package/dist/pos/quick-keys.d.ts +103 -0
  172. package/dist/pos/quick-keys.js +45 -0
  173. package/dist/pos/reports.d.ts +360 -0
  174. package/dist/pos/reports.js +62 -0
  175. package/dist/pos/returns.d.ts +217 -0
  176. package/dist/pos/returns.js +53 -0
  177. package/dist/pricing/bulk-coupons.d.ts +139 -0
  178. package/dist/pricing/bulk-coupons.js +112 -0
  179. package/dist/pricing/index.d.ts +174 -4
  180. package/dist/pricing/index.js +174 -1
  181. package/dist/pricing/price-lists.d.ts +604 -0
  182. package/dist/pricing/price-lists.js +600 -0
  183. package/dist/purchasing/alerts.d.ts +89 -0
  184. package/dist/purchasing/alerts.js +62 -0
  185. package/dist/purchasing/cost.d.ts +110 -0
  186. package/dist/purchasing/cost.js +86 -0
  187. package/dist/purchasing/index.d.ts +36 -0
  188. package/dist/purchasing/index.js +16 -0
  189. package/dist/purchasing/purchase-orders.d.ts +548 -0
  190. package/dist/purchasing/purchase-orders.js +491 -0
  191. package/dist/purchasing/replenishment.d.ts +281 -0
  192. package/dist/purchasing/replenishment.js +240 -0
  193. package/dist/purchasing/settings.d.ts +152 -0
  194. package/dist/purchasing/settings.js +181 -0
  195. package/dist/purchasing/suppliers.d.ts +476 -0
  196. package/dist/purchasing/suppliers.js +438 -0
  197. package/dist/quotes/index.d.ts +501 -0
  198. package/dist/quotes/index.js +439 -0
  199. package/dist/reports/index.d.ts +71 -8
  200. package/dist/reports/index.js +74 -11
  201. package/dist/returns/index.d.ts +614 -21
  202. package/dist/returns/index.js +695 -11
  203. package/dist/search/index.d.ts +76 -1
  204. package/dist/search/index.js +63 -0
  205. package/dist/shipping/definitions.d.ts +29 -0
  206. package/dist/shipping/definitions.js +47 -0
  207. package/dist/shipping/fulfillment-profiles.d.ts +174 -0
  208. package/dist/shipping/fulfillment-profiles.js +104 -0
  209. package/dist/shipping/hendriks.d.ts +97 -0
  210. package/dist/shipping/hendriks.js +50 -0
  211. package/dist/shipping/index.d.ts +48 -0
  212. package/dist/shipping/index.js +25 -0
  213. package/dist/shipping/methods.d.ts +198 -0
  214. package/dist/shipping/methods.js +240 -0
  215. package/dist/shipping/myparcel.d.ts +254 -0
  216. package/dist/shipping/myparcel.js +178 -0
  217. package/dist/shipping/parcel-count-suggestion.d.ts +2 -0
  218. package/dist/shipping/parcel-rule.d.ts +6 -5
  219. package/dist/shipping/parcel-rule.js +21 -23
  220. package/dist/shipping/rate-tables.d.ts +179 -0
  221. package/dist/shipping/rate-tables.js +184 -0
  222. package/dist/stock/index.d.ts +10 -3
  223. package/dist/stock/index.js +3 -0
  224. package/dist/stock/locations.d.ts +144 -0
  225. package/dist/stock/locations.js +168 -0
  226. package/dist/stock/transfers.d.ts +216 -43
  227. package/dist/stock/transfers.js +140 -13
  228. package/dist/stored-value/index.d.ts +214 -4
  229. package/dist/stored-value/index.js +152 -0
  230. package/dist/subscriptions/index.d.ts +682 -0
  231. package/dist/subscriptions/index.js +815 -0
  232. package/dist/tax/index.d.ts +76 -1
  233. package/dist/tax/index.js +50 -0
  234. package/dist/tenant/email-style.d.ts +102 -0
  235. package/dist/tenant/email-style.js +72 -0
  236. package/dist/tenant/email-templates.d.ts +66 -0
  237. package/dist/tenant/email-templates.js +54 -0
  238. package/dist/tenant/notifications.d.ts +23 -17
  239. package/dist/tenant/settings-sources.d.ts +1 -1
  240. package/dist/tenant/time-zone.d.ts +17 -0
  241. package/dist/tenant/time-zone.js +11 -0
  242. package/dist/types.d.ts +50 -3
  243. package/dist/users/index.d.ts +3 -0
  244. package/dist/users/index.js +2 -0
  245. package/dist/users/warehouses.d.ts +50 -0
  246. package/dist/users/warehouses.js +51 -0
  247. package/package.json +2 -2
@@ -0,0 +1,55 @@
1
+ import { createPage, NotFoundError, readRestPage, } from '@sixtyninecommerce/core';
2
+ import { PIPELINE_DRAFT_PRESERVE_KEYS, PIPELINE_DRAFT_SLOW_TIMEOUT_MS } from './pipeline-keys.js';
3
+ /**
4
+ * The draft set plus a probe's `entityCounts`, keyed by the source's own entity names (a WordPress
5
+ * post type such as `product_brand`). `test` and `retest` answer the analysis at the top level,
6
+ * where no passthrough key covers it.
7
+ */
8
+ export const PIPELINE_CONNECTION_PRESERVE_KEYS = new Set([
9
+ ...PIPELINE_DRAFT_PRESERVE_KEYS,
10
+ 'entity_counts',
11
+ 'entityCounts',
12
+ ]);
13
+ const CONNECTIONS = '/pipelines/connections';
14
+ const segment = (value) => encodeURIComponent(value);
15
+ export function createPipelineConnectionsModule(transport) {
16
+ const call = (method, path, body, options, timeoutMs) => transport.rest('plugins', { method, path, ...(body === undefined ? {} : { body }) }, {
17
+ ...options,
18
+ preserveKeys: PIPELINE_CONNECTION_PRESERVE_KEYS,
19
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
20
+ });
21
+ async function list(options) {
22
+ const body = await transport.rest('plugins', {
23
+ method: 'GET',
24
+ path: CONNECTIONS,
25
+ query: { cursor: 1, after: options?.after, limit: options?.limit },
26
+ }, { ...options, preserveKeys: PIPELINE_CONNECTION_PRESERVE_KEYS });
27
+ const { items, meta } = readRestPage(body);
28
+ return createPage(items, meta, (cursor) => list({ ...options, after: cursor }));
29
+ }
30
+ return {
31
+ list,
32
+ async get(id, options) {
33
+ try {
34
+ return await call('GET', `${CONNECTIONS}/${segment(id)}`, undefined, options);
35
+ }
36
+ catch (error) {
37
+ if (error instanceof NotFoundError)
38
+ return null;
39
+ throw error;
40
+ }
41
+ },
42
+ create: (input, options) => call('POST', CONNECTIONS, input, options, PIPELINE_DRAFT_SLOW_TIMEOUT_MS),
43
+ update: (id, input, options) => call('PATCH', `${CONNECTIONS}/${segment(id)}`, input, options, PIPELINE_DRAFT_SLOW_TIMEOUT_MS),
44
+ async delete(id, options) {
45
+ await transport.rest('plugins', { method: 'DELETE', path: `${CONNECTIONS}/${segment(id)}`, expect: 'void' }, options);
46
+ },
47
+ test: (input, options) => call('POST', `${CONNECTIONS}/test`, input, options, PIPELINE_DRAFT_SLOW_TIMEOUT_MS),
48
+ retest: (id, options) => call('POST', `${CONNECTIONS}/${segment(id)}/test`, undefined, options, PIPELINE_DRAFT_SLOW_TIMEOUT_MS),
49
+ detectConfig: (input, options) => call('POST', `${CONNECTIONS}/detect-config`, input, options, PIPELINE_DRAFT_SLOW_TIMEOUT_MS),
50
+ };
51
+ }
52
+ /** The connectors this deployment offers, `coming_soon` ones included. */
53
+ export async function listPipelineConnectors(transport, options) {
54
+ return transport.rest('plugins', { method: 'GET', path: '/pipelines/connectors' }, { ...options, preserveKeys: PIPELINE_CONNECTION_PRESERVE_KEYS });
55
+ }
@@ -0,0 +1,313 @@
1
+ import { type Page, type Transport } from '@sixtyninecommerce/core';
2
+ import type { Call, ListOptions } from '../types.js';
3
+ /**
4
+ * State of the credentials the engine hands BACK to the e-mail platform (its read access to
5
+ * this store), as the connect handshake or a retry left it.
6
+ */
7
+ export interface EspCredentialsPushState {
8
+ /** ISO-8601: when the platform accepted them. */
9
+ pushedAt?: string;
10
+ /** Why the platform refused them, in a sentence for staff. */
11
+ error?: string;
12
+ /** ISO-8601: the last attempt. */
13
+ attemptedAt?: string;
14
+ /** The first characters of the token handed over; never the token. */
15
+ tokenPrefix?: string;
16
+ adminApiUrl?: string;
17
+ /**
18
+ * Whether repeating could change the outcome. `false` for a contract or credential fault the
19
+ * same request would hit again: reconnect instead of retrying.
20
+ */
21
+ retryable?: boolean;
22
+ /** What the platform did with its first import: `queued` or `already_running`. */
23
+ initialSync?: string;
24
+ }
25
+ /**
26
+ * State of the REVERSE direction: the platform pushing suppressions, bounces, complaints and
27
+ * consent changes back to this engine. Soft like the credentials push: without it the engine
28
+ * still learns of them, 15 minutes later, from its reconcile poll.
29
+ */
30
+ export interface EspInboundWebhookState {
31
+ registered: boolean;
32
+ subscriptionId?: string;
33
+ error?: string;
34
+ /** ISO-8601. */
35
+ at?: string;
36
+ }
37
+ /**
38
+ * What the one-paste connect handshake recorded; null on a connection configured by hand.
39
+ * Carries no secret. Every member is optional: an older connection recorded less.
40
+ */
41
+ export interface EspConnectMetadata {
42
+ webhookUrl?: string;
43
+ events?: string[];
44
+ apiEndpoint?: string;
45
+ /** ISO-8601. */
46
+ connectedAt?: string;
47
+ pluginVersion?: string;
48
+ credentialsPush?: EspCredentialsPushState;
49
+ emailGateway?: {
50
+ provisioned: boolean;
51
+ skippedReason?: string;
52
+ at?: string;
53
+ };
54
+ inboundWebhook?: EspInboundWebhookState;
55
+ }
56
+ /**
57
+ * A store's connection to its e-mail marketing platform (sixtynine.email): one per store. The
58
+ * API key and webhook secret are never read back in any form.
59
+ */
60
+ export interface EspConnection {
61
+ id: string;
62
+ channelId: string;
63
+ /** `sixtynine_email`, the one provider today. */
64
+ provider: string;
65
+ /** Whether the store's customers, orders, products and carts are sent. */
66
+ enabled: boolean;
67
+ /** The platform the store's data and stored key go to; a host the deployment allows. */
68
+ apiBaseUrl: string;
69
+ /** The store's id on the platform; null on a connection configured by hand without one. */
70
+ storeId: string | null;
71
+ defaultListId: string | null;
72
+ syncContacts: boolean;
73
+ syncOrders: boolean;
74
+ syncProducts: boolean;
75
+ syncCarts: boolean;
76
+ syncSubscriptions: boolean;
77
+ /** How long a cart sits unchanged before it is reported abandoned. */
78
+ cartIdleMinutes: number;
79
+ /** A cart last changed longer ago is never reported. */
80
+ cartMaxAgeDays: number;
81
+ /** How far back the first order import reaches. */
82
+ orderBackfillDays: number;
83
+ /** Platform field → store field, stored exactly as it was sent. */
84
+ fieldMappings: unknown;
85
+ connectMetadata: EspConnectMetadata | null;
86
+ /** ISO-8601. */
87
+ createdAt: string;
88
+ /** ISO-8601. */
89
+ updatedAt: string;
90
+ }
91
+ /**
92
+ * A PATCH: a key left out keeps the stored value. The first save must carry `apiBaseUrl`,
93
+ * `apiKey` and `webhookSecret` (`connectWithToken` is the one-paste way in). The two credentials
94
+ * are write-only: absent, null or empty keeps the stored one.
95
+ */
96
+ export interface UpdateEspConnectionInput {
97
+ provider?: string;
98
+ enabled?: boolean;
99
+ apiBaseUrl?: string;
100
+ storeId?: string | null;
101
+ apiKey?: string | null;
102
+ webhookSecret?: string | null;
103
+ defaultListId?: string | null;
104
+ syncContacts?: boolean;
105
+ syncOrders?: boolean;
106
+ syncProducts?: boolean;
107
+ syncCarts?: boolean;
108
+ syncSubscriptions?: boolean;
109
+ cartIdleMinutes?: number;
110
+ /** At least 1, and longer than `cartIdleMinutes`. */
111
+ cartMaxAgeDays?: number;
112
+ orderBackfillDays?: number;
113
+ fieldMappings?: unknown;
114
+ }
115
+ /** The one-paste way in: where the platform lives, and the one-time token copied out of it. */
116
+ export interface ConnectEspWithTokenInput {
117
+ apiBaseUrl: string;
118
+ connectToken: string;
119
+ }
120
+ /** What `test` found: a result, not a refusal. */
121
+ export interface EspConnectionTestResult {
122
+ ok: boolean;
123
+ message: string;
124
+ }
125
+ /**
126
+ * What a backfill can send, in the order a full one sends it: products first, so order lines
127
+ * can match a product the platform already holds; customer groups before customers, because a
128
+ * group the platform first meets inside a customer has only its id and name. Pinned to the
129
+ * engine's list by this package's spec.
130
+ */
131
+ export declare const ESP_BACKFILL_RESOURCES: readonly ["products", "customer_groups", "customers", "orders"];
132
+ export type EspBackfillResource = (typeof ESP_BACKFILL_RESOURCES)[number];
133
+ /**
134
+ * One attempt to send one record to the e-mail platform. The request the engine sent and the
135
+ * platform's raw answer are deliberately not read here: they carry the customer's personal data
136
+ * and are kept as audit evidence, not as an API.
137
+ */
138
+ export interface EspSyncLog {
139
+ id: string;
140
+ channelId: string;
141
+ provider: string;
142
+ /** `customer`, `order`, `product`, `cart`, `subscription`, `customer_group`. */
143
+ entityType: string;
144
+ entityId: string;
145
+ /** The platform's reference for the record, when it answered one. */
146
+ externalRef: string | null;
147
+ /** The dotted event sent, e.g. `order.created`. */
148
+ eventType: string;
149
+ /** `pending`, `success`, `failed` or `skipped`; only a `failed` attempt can be retried. */
150
+ status: string;
151
+ /** The platform's HTTP status; null when it never answered. */
152
+ responseStatus: number | null;
153
+ /** Why it failed, in a sentence for staff; null otherwise. */
154
+ error: string | null;
155
+ attempt: number;
156
+ durationMs: number | null;
157
+ /** ISO-8601. */
158
+ createdAt: string;
159
+ }
160
+ /** Which attempts `syncLogs` lists; every member narrows, and none means all. */
161
+ export interface EspSyncLogFilter {
162
+ entityType?: string;
163
+ entityId?: string;
164
+ status?: string;
165
+ }
166
+ /** The largest page `syncLogs` asks for; the default is 25. */
167
+ export declare const ESP_SYNC_LOG_PAGE_LIMIT = 100;
168
+ /**
169
+ * The store's e-mail marketing connection (sixtynine.email): connecting and configuring it,
170
+ * sending the store's history, and the log of what was sent (`admin.integrations.esp`). Always
171
+ * the store the client is scoped to; a SuperAdmin head acts on ANOTHER store with
172
+ * `admin.withTenant({ scope: { kind: 'channel', code } })`, or a per-call `tenant`.
173
+ *
174
+ * Every method needs `UpdateSettings` (the reads too: the engine guards them with the write's
175
+ * permission). An app installation is refused the connection lifecycle
176
+ * (`APP_CREDENTIAL_NOT_ALLOWED`), and a store without the e-mail marketing plugin every method
177
+ * (`PLUGIN_ACCESS_DENIED`, 403; never for a SuperAdmin).
178
+ */
179
+ export interface EspModule {
180
+ /**
181
+ * The store's connection, or `null` when it has none.
182
+ *
183
+ * @effect read
184
+ * @permission UpdateSettings
185
+ */
186
+ connection(options?: Call): Promise<EspConnection | null>;
187
+ /**
188
+ * Configure the connection by hand; the first call creates it. Answers the saved connection.
189
+ *
190
+ * Refused, with nothing saved:
191
+ * - `ESP_INPUT_INVALID` (`ValidationError`, 400): a first save without `apiBaseUrl`, `apiKey`
192
+ * or `webhookSecret`; an abandoned-cart window that can never report a cart; an explicit
193
+ * null for a stored setting. `details[]` names each field;
194
+ * - `ESP_API_BASE_URL_INVALID` (400): a URL that is not https, carries credentials, or names a
195
+ * host the deployment does not allow;
196
+ * - `ESP_HOST_CHANGE_REQUIRES_CREDENTIALS` (422): moving to another host without that host's
197
+ * `apiKey` AND `webhookSecret` in the same call;
198
+ * - `ORGANIZATION_WORKSPACE_NOT_A_STORE` (422): switching on, or storing a credential, in an
199
+ * organization's workspace.
200
+ *
201
+ * @effect write
202
+ * @permission UpdateSettings
203
+ */
204
+ update(input: UpdateEspConnectionInput, options?: Call): Promise<EspConnection>;
205
+ /**
206
+ * The one-paste way in: spend a one-time connect token at the platform for the store's
207
+ * credentials, store them, switch the connection on, and hand the platform back a read
208
+ * credential for this store, with which it starts importing the store's customers, orders and
209
+ * products. Answers the connection.
210
+ *
211
+ * The hand-back, the e-mail gateway and the inbound webhook are SOFT: a failure there still
212
+ * answers the (live) connection, with the reason in `connectMetadata.credentialsPush.error`,
213
+ * `.emailGateway.skippedReason` or `.inboundWebhook.error`. Read them: a resolved call is not
214
+ * a full success.
215
+ *
216
+ * Refused, with nothing saved: `ESP_INPUT_INVALID` (400, a blank token);
217
+ * `ESP_API_BASE_URL_INVALID` (400); `ESP_CONNECT_TOKEN_REJECTED` (422, the platform refused
218
+ * the token: unknown, used, expired, or too many attempts; the sentence says which);
219
+ * `ESP_PROVIDER_UNAVAILABLE` (422, the platform could not be reached or answered something
220
+ * unusable; the token may still be good); `ORGANIZATION_WORKSPACE_NOT_A_STORE` (422).
221
+ *
222
+ * @effect irreversible
223
+ * @permission UpdateSettings
224
+ */
225
+ connectWithToken(input: ConnectEspWithTokenInput, options?: Call): Promise<EspConnection>;
226
+ /**
227
+ * Hand the platform a FRESH read credential for this store (the previous one is not resent:
228
+ * the engine never stores it). Soft like the connect: a refusal answers the connection with
229
+ * `connectMetadata.credentialsPush.error` set, and `.retryable` says whether repeating helps.
230
+ *
231
+ * Refused: `ESP_CONNECTION_NOT_FOUND` (404); `ESP_RECONNECT_REQUIRED` (422, a connection
232
+ * configured by hand has no store id on the platform).
233
+ *
234
+ * @effect irreversible
235
+ * @permission UpdateSettings
236
+ */
237
+ retryCredentialsPush(options?: Call): Promise<EspConnection>;
238
+ /**
239
+ * Ask the platform again to send suppressions, bounces, complaints and consent changes back
240
+ * to this store. Soft: a failure answers the connection with
241
+ * `connectMetadata.inboundWebhook.error` set.
242
+ *
243
+ * Refused: `ESP_CONNECTION_NOT_FOUND` (404).
244
+ *
245
+ * @effect write
246
+ * @permission UpdateSettings
247
+ */
248
+ retryInboundWebhook(options?: Call): Promise<EspConnection>;
249
+ /**
250
+ * Remove the store's connection: the platform is told to stop sending first, then the stored
251
+ * credentials are deleted. `true` when a connection was removed, `false` when there was none.
252
+ * A platform that cannot be told never blocks the removal.
253
+ *
254
+ * @effect destructive
255
+ * @permission UpdateSettings
256
+ */
257
+ delete(options?: Call): Promise<boolean>;
258
+ /**
259
+ * Ask the platform whether the stored credentials work. Never refuses for what it finds: no
260
+ * connection, or credentials the platform rejects, answers `ok: false` with the reason.
261
+ *
262
+ * @effect read
263
+ * @permission UpdateSettings
264
+ */
265
+ test(options?: Call): Promise<EspConnectionTestResult>;
266
+ /**
267
+ * Send the store's history to the platform, without firing its automations: the named
268
+ * resources, in {@link ESP_BACKFILL_RESOURCES} order whatever order they are named in, or
269
+ * every resource when none are named. Each resource goes only while its sync is on
270
+ * (`syncProducts`, `syncContacts` for customers and groups, `syncOrders`). Queued: the call
271
+ * answers once the work is queued, and the sync log shows each record.
272
+ *
273
+ * It sends personal data (the customer list) to a third party, which cannot be taken back.
274
+ *
275
+ * Refused, with nothing queued: `ESP_INPUT_INVALID` (400, an empty list, or a resource the
276
+ * backfill does not know); `ESP_CONNECTION_NOT_FOUND` (404); `ESP_CONNECTION_DISABLED` (422).
277
+ *
278
+ * @effect irreversible
279
+ * @permission UpdateSettings
280
+ */
281
+ backfill(resources?: readonly EspBackfillResource[], options?: Call): Promise<void>;
282
+ /**
283
+ * The attempts to send this store's records, newest first, 25 to a page (at most
284
+ * {@link ESP_SYNC_LOG_PAGE_LIMIT}).
285
+ *
286
+ * @effect read
287
+ * @permission UpdateSettings
288
+ */
289
+ syncLogs(filter?: EspSyncLogFilter & ListOptions): Promise<Page<EspSyncLog>>;
290
+ /**
291
+ * Queue a FAILED attempt again, rebuilt from what it recorded. Answers the attempt retried
292
+ * (its row is not changed; the new attempt gets a row of its own).
293
+ *
294
+ * Refused, with nothing queued: `ESP_SYNC_LOG_NOT_FOUND` (404, not this store's);
295
+ * `ESP_SYNC_NOT_RETRYABLE` (422, the attempt did not fail); `ESP_SYNC_RETRY_NOT_POSSIBLE` (422,
296
+ * it failed but cannot be rebuilt; the sentence says what to do by hand).
297
+ *
298
+ * @effect irreversible
299
+ * @permission UpdateSettings
300
+ */
301
+ retrySync(syncLogId: string, options?: Call): Promise<EspSyncLog>;
302
+ }
303
+ export declare const ESP_CONNECTION_QUERY = "\n query EspConnection {\n espConnection { \n id\n channelId\n provider\n enabled\n apiBaseUrl\n storeId\n defaultListId\n syncContacts\n syncOrders\n syncProducts\n syncCarts\n syncSubscriptions\n cartIdleMinutes\n cartMaxAgeDays\n orderBackfillDays\n fieldMappings\n connectMetadata\n createdAt\n updatedAt\n }\n }\n";
304
+ export declare const UPDATE_ESP_CONNECTION_MUTATION = "\n mutation UpdateEspConnection($input: UpdateEspConnectionInput!) {\n updateEspConnection(input: $input) { \n id\n channelId\n provider\n enabled\n apiBaseUrl\n storeId\n defaultListId\n syncContacts\n syncOrders\n syncProducts\n syncCarts\n syncSubscriptions\n cartIdleMinutes\n cartMaxAgeDays\n orderBackfillDays\n fieldMappings\n connectMetadata\n createdAt\n updatedAt\n }\n }\n";
305
+ export declare const CONNECT_ESP_WITH_TOKEN_MUTATION = "\n mutation ConnectEspWithToken($input: ConnectEspWithTokenInput!) {\n connectEspWithToken(input: $input) { \n id\n channelId\n provider\n enabled\n apiBaseUrl\n storeId\n defaultListId\n syncContacts\n syncOrders\n syncProducts\n syncCarts\n syncSubscriptions\n cartIdleMinutes\n cartMaxAgeDays\n orderBackfillDays\n fieldMappings\n connectMetadata\n createdAt\n updatedAt\n }\n }\n";
306
+ export declare const RETRY_ESP_CREDENTIALS_PUSH_MUTATION = "\n mutation RetryEspCredentialsPush {\n retryEspCredentialsPush { \n id\n channelId\n provider\n enabled\n apiBaseUrl\n storeId\n defaultListId\n syncContacts\n syncOrders\n syncProducts\n syncCarts\n syncSubscriptions\n cartIdleMinutes\n cartMaxAgeDays\n orderBackfillDays\n fieldMappings\n connectMetadata\n createdAt\n updatedAt\n }\n }\n";
307
+ export declare const RETRY_ESP_INBOUND_WEBHOOK_MUTATION = "\n mutation RetryEspInboundWebhook {\n retryEspInboundWebhook { \n id\n channelId\n provider\n enabled\n apiBaseUrl\n storeId\n defaultListId\n syncContacts\n syncOrders\n syncProducts\n syncCarts\n syncSubscriptions\n cartIdleMinutes\n cartMaxAgeDays\n orderBackfillDays\n fieldMappings\n connectMetadata\n createdAt\n updatedAt\n }\n }\n";
308
+ export declare const DELETE_ESP_CONNECTION_MUTATION = "\n mutation DeleteEspConnection {\n deleteEspConnection\n }\n";
309
+ export declare const TEST_ESP_CONNECTION_MUTATION = "\n mutation TestEspConnection {\n testEspConnection { ok message }\n }\n";
310
+ export declare const TRIGGER_ESP_BACKFILL_MUTATION = "\n mutation TriggerMarketingBackfill($resources: [String!]) {\n triggerMarketingBackfill(resources: $resources)\n }\n";
311
+ export declare const ESP_SYNC_LOGS_QUERY = "\n query EspSyncLogs($options: EspSyncLogListOptions) {\n espSyncLogs(options: $options) {\n items { \n id\n channelId\n provider\n entityType\n entityId\n externalRef\n eventType\n status\n responseStatus\n error\n attempt\n durationMs\n createdAt\n }\n totalItems\n }\n }\n";
312
+ export declare const RETRY_ESP_SYNC_MUTATION = "\n mutation RetryEspSync($syncLogId: ID!) {\n retryEspSync(syncLogId: $syncLogId) { \n id\n channelId\n provider\n entityType\n entityId\n externalRef\n eventType\n status\n responseStatus\n error\n attempt\n durationMs\n createdAt\n }\n }\n";
313
+ export declare function createEspModule(transport: Transport): EspModule;
@@ -0,0 +1,169 @@
1
+ import { pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
2
+ /**
3
+ * What a backfill can send, in the order a full one sends it: products first, so order lines
4
+ * can match a product the platform already holds; customer groups before customers, because a
5
+ * group the platform first meets inside a customer has only its id and name. Pinned to the
6
+ * engine's list by this package's spec.
7
+ */
8
+ export const ESP_BACKFILL_RESOURCES = [
9
+ 'products',
10
+ 'customer_groups',
11
+ 'customers',
12
+ 'orders',
13
+ ];
14
+ /** The largest page `syncLogs` asks for; the default is 25. */
15
+ export const ESP_SYNC_LOG_PAGE_LIMIT = 100;
16
+ const ESP_SYNC_LOG_PAGE_SIZE = 25;
17
+ const CONNECTION_FIELDS = /* GraphQL */ `
18
+ id
19
+ channelId
20
+ provider
21
+ enabled
22
+ apiBaseUrl
23
+ storeId
24
+ defaultListId
25
+ syncContacts
26
+ syncOrders
27
+ syncProducts
28
+ syncCarts
29
+ syncSubscriptions
30
+ cartIdleMinutes
31
+ cartMaxAgeDays
32
+ orderBackfillDays
33
+ fieldMappings
34
+ connectMetadata
35
+ createdAt
36
+ updatedAt
37
+ `;
38
+ const SYNC_LOG_FIELDS = /* GraphQL */ `
39
+ id
40
+ channelId
41
+ provider
42
+ entityType
43
+ entityId
44
+ externalRef
45
+ eventType
46
+ status
47
+ responseStatus
48
+ error
49
+ attempt
50
+ durationMs
51
+ createdAt
52
+ `;
53
+ export const ESP_CONNECTION_QUERY = /* GraphQL */ `
54
+ query EspConnection {
55
+ espConnection { ${CONNECTION_FIELDS} }
56
+ }
57
+ `;
58
+ export const UPDATE_ESP_CONNECTION_MUTATION = /* GraphQL */ `
59
+ mutation UpdateEspConnection($input: UpdateEspConnectionInput!) {
60
+ updateEspConnection(input: $input) { ${CONNECTION_FIELDS} }
61
+ }
62
+ `;
63
+ export const CONNECT_ESP_WITH_TOKEN_MUTATION = /* GraphQL */ `
64
+ mutation ConnectEspWithToken($input: ConnectEspWithTokenInput!) {
65
+ connectEspWithToken(input: $input) { ${CONNECTION_FIELDS} }
66
+ }
67
+ `;
68
+ export const RETRY_ESP_CREDENTIALS_PUSH_MUTATION = /* GraphQL */ `
69
+ mutation RetryEspCredentialsPush {
70
+ retryEspCredentialsPush { ${CONNECTION_FIELDS} }
71
+ }
72
+ `;
73
+ export const RETRY_ESP_INBOUND_WEBHOOK_MUTATION = /* GraphQL */ `
74
+ mutation RetryEspInboundWebhook {
75
+ retryEspInboundWebhook { ${CONNECTION_FIELDS} }
76
+ }
77
+ `;
78
+ export const DELETE_ESP_CONNECTION_MUTATION = /* GraphQL */ `
79
+ mutation DeleteEspConnection {
80
+ deleteEspConnection
81
+ }
82
+ `;
83
+ export const TEST_ESP_CONNECTION_MUTATION = /* GraphQL */ `
84
+ mutation TestEspConnection {
85
+ testEspConnection { ok message }
86
+ }
87
+ `;
88
+ export const TRIGGER_ESP_BACKFILL_MUTATION = /* GraphQL */ `
89
+ mutation TriggerMarketingBackfill($resources: [String!]) {
90
+ triggerMarketingBackfill(resources: $resources)
91
+ }
92
+ `;
93
+ export const ESP_SYNC_LOGS_QUERY = /* GraphQL */ `
94
+ query EspSyncLogs($options: EspSyncLogListOptions) {
95
+ espSyncLogs(options: $options) {
96
+ items { ${SYNC_LOG_FIELDS} }
97
+ totalItems
98
+ }
99
+ }
100
+ `;
101
+ export const RETRY_ESP_SYNC_MUTATION = /* GraphQL */ `
102
+ mutation RetryEspSync($syncLogId: ID!) {
103
+ retryEspSync(syncLogId: $syncLogId) { ${SYNC_LOG_FIELDS} }
104
+ }
105
+ `;
106
+ export function createEspModule(transport) {
107
+ const connectionMutation = async (query, field, variables, options) => {
108
+ const data = await transport.graphql({ api: 'admin', query, ...(variables ? { variables } : {}) }, options);
109
+ const connection = data[field];
110
+ if (!connection)
111
+ throw new Error(`The engine answered no ${field}`);
112
+ return connection;
113
+ };
114
+ const syncLogPage = async (filter, skip, take, options) => {
115
+ const data = await transport.graphql({
116
+ api: 'admin',
117
+ query: ESP_SYNC_LOGS_QUERY,
118
+ variables: { options: { ...filter, skip, take } },
119
+ }, options);
120
+ return pageFromVendureList(data.espSyncLogs, skip, take, (cursor) => syncLogPage(filter, readOffsetCursor(cursor), take, options));
121
+ };
122
+ return {
123
+ async connection(options) {
124
+ const data = await transport.graphql({ api: 'admin', query: ESP_CONNECTION_QUERY }, options);
125
+ return data.espConnection ?? null;
126
+ },
127
+ update(input, options) {
128
+ // Sent as given: an absent key keeps the stored value, and the engine names anything it
129
+ // refuses.
130
+ return connectionMutation(UPDATE_ESP_CONNECTION_MUTATION, 'updateEspConnection', { input }, options);
131
+ },
132
+ connectWithToken(input, options) {
133
+ return connectionMutation(CONNECT_ESP_WITH_TOKEN_MUTATION, 'connectEspWithToken', { input }, options);
134
+ },
135
+ retryCredentialsPush(options) {
136
+ return connectionMutation(RETRY_ESP_CREDENTIALS_PUSH_MUTATION, 'retryEspCredentialsPush', undefined, options);
137
+ },
138
+ retryInboundWebhook(options) {
139
+ return connectionMutation(RETRY_ESP_INBOUND_WEBHOOK_MUTATION, 'retryEspInboundWebhook', undefined, options);
140
+ },
141
+ async delete(options) {
142
+ const data = await transport.graphql({ api: 'admin', query: DELETE_ESP_CONNECTION_MUTATION }, options);
143
+ return data.deleteEspConnection === true;
144
+ },
145
+ async test(options) {
146
+ const data = await transport.graphql({ api: 'admin', query: TEST_ESP_CONNECTION_MUTATION }, options);
147
+ return data.testEspConnection;
148
+ },
149
+ async backfill(resources, options) {
150
+ // `undefined` stays off the wire (every resource); a list goes as given, an empty one
151
+ // included, so the engine is the one that refuses it.
152
+ await transport.graphql({
153
+ api: 'admin',
154
+ query: TRIGGER_ESP_BACKFILL_MUTATION,
155
+ variables: { resources: resources === undefined ? undefined : [...resources] },
156
+ }, options);
157
+ },
158
+ syncLogs(filter) {
159
+ const { after, limit, entityType, entityId, status, ...options } = filter ?? {};
160
+ const take = Math.min(Math.max(limit ?? ESP_SYNC_LOG_PAGE_SIZE, 1), ESP_SYNC_LOG_PAGE_LIMIT);
161
+ const skip = after === undefined ? 0 : readOffsetCursor(after);
162
+ return syncLogPage({ entityType, entityId, status }, skip, take, options);
163
+ },
164
+ async retrySync(syncLogId, options) {
165
+ const data = await transport.graphql({ api: 'admin', query: RETRY_ESP_SYNC_MUTATION, variables: { syncLogId } }, options);
166
+ return data.retryEspSync;
167
+ },
168
+ };
169
+ }