@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,287 @@
1
+ import { type Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ import { type EmailStylePatch } from './email-style.js';
4
+ /** One template rendered as the store would send it, filled from the store's own records. */
5
+ export interface EmailPreview {
6
+ subject: string;
7
+ /** The whole HTML document, ready for an `<iframe srcdoc>`. */
8
+ html: string;
9
+ /** Each recipient field resolved for the sample, as one comma-separated list ('' for none). */
10
+ recipients: {
11
+ to: string;
12
+ cc: string;
13
+ bcc: string;
14
+ };
15
+ /**
16
+ * `true` when the credential's customer-data level withheld part of the answer (an app
17
+ * installation below `customerData: full`): `subject` and `html` then read `''`, and so do the
18
+ * recipients below `identity`. Absent otherwise.
19
+ */
20
+ redacted?: true;
21
+ }
22
+ export interface EmailPreviewOptions extends Call {
23
+ /** The language to render the template's texts and subject in, e.g. `nl`. */
24
+ languageCode: string;
25
+ /**
26
+ * Style fields to preview instead of the stored ones, e.g. a form's unsaved values. Nothing is
27
+ * saved. A value `tenant.emailStyle.set` would refuse is not refused here: the renderer draws it
28
+ * with its own fallback.
29
+ */
30
+ style?: EmailStylePatch;
31
+ }
32
+ /**
33
+ * One part of a subject line or of a block's text: literal text (`{ type: 'text', value }`), or a
34
+ * variable the email fills in when it is sent (`{ type: 'var', key }`, a key from the template
35
+ * type's `variables` in {@link EmailTemplateCatalog}). A variable key the type does not offer is
36
+ * printed as `[key]`.
37
+ */
38
+ export interface EmailTokenSegment {
39
+ type: 'text' | 'var';
40
+ /** The text of a `text` part. */
41
+ value?: string;
42
+ /** The variable of a `var` part. */
43
+ key?: string;
44
+ }
45
+ /** A block's texts in one language. */
46
+ export interface EmailTemplateBlockContent {
47
+ languageCode: string;
48
+ /**
49
+ * One entry per text of the block, keyed by the block type's field names (most hold
50
+ * {@link EmailTokenSegment} lists). The renderer falls back to the block's defaults for a
51
+ * missing entry.
52
+ */
53
+ content: Record<string, unknown>;
54
+ }
55
+ /** One block (the engine calls them chips) of a template's body, e.g. a heading or the order lines. */
56
+ export interface EmailTemplateBlock {
57
+ id: string;
58
+ /** The block type: one of the catalog's `blockTypes`. */
59
+ type: string;
60
+ /** Its place in the body: the blocks are answered in this order. */
61
+ position: number;
62
+ /** The block's switches and settings (e.g. `{ showImage: true }`), the same in every language. */
63
+ config: Record<string, unknown>;
64
+ /** Its texts, one entry per language the store wrote them in. */
65
+ contents: EmailTemplateBlockContent[];
66
+ }
67
+ /** A template's subject line in one language. */
68
+ export interface EmailTemplateSubject {
69
+ languageCode: string;
70
+ segments: EmailTokenSegment[];
71
+ }
72
+ /** The template of one type, as the store sends it now. */
73
+ export interface EmailTemplate {
74
+ /**
75
+ * The template's id. It changes when the store starts or stops taking `settings.email` from its
76
+ * organization, so the methods here address a template by its `type`; keep the id only to tell
77
+ * that the template a page shows was replaced.
78
+ */
79
+ id: string;
80
+ type: string;
81
+ enabled: boolean;
82
+ /** Who it is sent to, as recipient variables and fixed addresses. */
83
+ recipients: {
84
+ to: EmailTokenSegment[];
85
+ cc: EmailTokenSegment[];
86
+ bcc: EmailTokenSegment[];
87
+ };
88
+ /** The body, in `position` order. */
89
+ blocks: EmailTemplateBlock[];
90
+ subjects: EmailTemplateSubject[];
91
+ }
92
+ export interface EmailTemplateVariable {
93
+ key: string;
94
+ label: string;
95
+ }
96
+ export interface EmailTemplateTypeInfo {
97
+ type: string;
98
+ label: string;
99
+ description: string;
100
+ /** The variables a subject or block text of this type may use. */
101
+ variables: EmailTemplateVariable[];
102
+ }
103
+ export interface EmailBlockTypeInfo {
104
+ type: string;
105
+ label: string;
106
+ /** A grouping for a picker: `structural`, `content`, `commerce_data`, `action`, `cms_data`, … */
107
+ category: string;
108
+ }
109
+ /** What the template editor can offer: the same for every store. */
110
+ export interface EmailTemplateCatalog {
111
+ templateTypes: EmailTemplateTypeInfo[];
112
+ blockTypes: EmailBlockTypeInfo[];
113
+ /** The variables a recipient field may use. */
114
+ recipientVariables: EmailTemplateVariable[];
115
+ }
116
+ export interface AddEmailTemplateBlockInput {
117
+ /** The block type: one of the catalog's `blockTypes`. */
118
+ type: string;
119
+ /**
120
+ * Where to put it: `0` is first, and the blocks at that place and after it move down one. At the
121
+ * end when omitted.
122
+ */
123
+ position?: number;
124
+ /** Its switches and settings; `{}` when omitted. */
125
+ config?: Record<string, unknown>;
126
+ }
127
+ /**
128
+ * The template writes, by template type or by block id. Every one is refused while the store takes
129
+ * `settings.email` from its organization (see {@link EmailTemplatesModule}).
130
+ *
131
+ * **Coded refusals shared by every write:**
132
+ * - `ORGANIZATION_SETTINGS_INHERITED` → `BusinessRuleError` (422): the store inherits
133
+ * `settings.email`; `tenant.settingsSources.override('settings.email')` first.
134
+ * - `MANAGED_BY_ORGANIZATION` → `BusinessRuleError` (422): the organization locks it.
135
+ * - `FORBIDDEN` → `PermissionDeniedError` (403): the credential lacks `UpdateSettings`.
136
+ */
137
+ export interface EmailTemplateBlocksModule {
138
+ /**
139
+ * Add a block to the template of `templateType`, and answer it (with no `contents`: a new block
140
+ * has no texts until `setContent`). Two requests: the template the store uses is looked up
141
+ * first.
142
+ *
143
+ * **Coded refusals:** `EMAIL_TEMPLATE_NOT_FOUND` → `NotFoundError` (404), no template of the
144
+ * type; `EMAIL_CHIP_TYPE_INVALID` → `ValidationError` (400), a block type outside the catalog;
145
+ * `EMAIL_CHIP_CONFIG_INVALID` (400), a `config` that is not an object; `ENTITY_NOT_FOUND` →
146
+ * `NotFoundError` (404), the template was replaced between the two requests (ask again).
147
+ *
148
+ * @effect write
149
+ * @permission UpdateSettings
150
+ */
151
+ add(templateType: string, block: AddEmailTemplateBlockInput, options?: Call): Promise<EmailTemplateBlock>;
152
+ /**
153
+ * Replace a block's `config` as a whole (send every key it keeps), and answer the block without
154
+ * its `contents` (`getByType` reads them).
155
+ *
156
+ * **Coded refusals:** `EMAIL_CHIP_CONFIG_INVALID` → `ValidationError` (400), a config that is not
157
+ * an object; `ENTITY_NOT_FOUND` → `NotFoundError` (404), no block of the store's with that id.
158
+ *
159
+ * @effect write
160
+ * @permission UpdateSettings
161
+ */
162
+ update(blockId: string, patch: {
163
+ config: Record<string, unknown>;
164
+ }, options?: Call): Promise<Omit<EmailTemplateBlock, 'contents'>>;
165
+ /**
166
+ * Delete a block with its texts in every language. The blocks after it move up one. There is
167
+ * no undo: a block added back starts from its defaults.
168
+ *
169
+ * **Coded refusal:** `ENTITY_NOT_FOUND` → `NotFoundError` (404), no block of the store's with
170
+ * that id.
171
+ *
172
+ * @effect destructive
173
+ * @permission UpdateSettings
174
+ */
175
+ remove(blockId: string, options?: Call): Promise<void>;
176
+ /**
177
+ * Put the blocks of the template of `templateType` in a new order: `blockIds` lists EVERY block
178
+ * of the template once, first to last. Two requests: the template is looked up first.
179
+ *
180
+ * **Coded refusals:** `EMAIL_CHIP_ORDER_INVALID` → `ValidationError` (400), a block left out,
181
+ * listed twice, or not one of the template's (the message names it); `EMAIL_TEMPLATE_NOT_FOUND`
182
+ * → `NotFoundError` (404), no template of the type; `ENTITY_NOT_FOUND` (404), the template was
183
+ * replaced between the two requests.
184
+ *
185
+ * @effect write
186
+ * @permission UpdateSettings
187
+ */
188
+ reorder(templateType: string, blockIds: readonly string[], options?: Call): Promise<void>;
189
+ /**
190
+ * Set a block's texts in one language, replacing what it had in that language (the other
191
+ * languages keep theirs). Answers what is stored.
192
+ *
193
+ * **Coded refusals:** `EMAIL_CHIP_CONTENT_INVALID` → `ValidationError` (400), content that is
194
+ * not an object; `EMAIL_LANGUAGE_CODE_INVALID` (400), a language code that is none (`en`, `nl`,
195
+ * `pt_BR`, …); `ENTITY_NOT_FOUND` → `NotFoundError` (404), no block of the store's with that id.
196
+ *
197
+ * @effect write
198
+ * @permission UpdateSettings
199
+ */
200
+ setContent(blockId: string, languageCode: string, content: Record<string, unknown>, options?: Call): Promise<EmailTemplateBlockContent>;
201
+ }
202
+ /**
203
+ * `admin.tenant.emailTemplates` — the active store's email templates (part of the `settings.email`
204
+ * group). Always the ACTIVE channel; no channel argument.
205
+ *
206
+ * Templates are addressed by TYPE (`order_confirmation`, `shipping_confirmation`, …): a template's
207
+ * id changes when the store starts or stops taking `settings.email` from its organization, its
208
+ * type never does. While the store inherits, every read answers the organization's template, and
209
+ * every write (`setSubject`, `blocks.*`) is refused (`ORGANIZATION_SETTINGS_INHERITED` or
210
+ * `MANAGED_BY_ORGANIZATION`, 422).
211
+ *
212
+ * A change applies to the next email of its type the store sends; an email already sent keeps
213
+ * what it said.
214
+ */
215
+ export interface EmailTemplatesModule {
216
+ /**
217
+ * What the editor can offer: each template type with its label, description and variables, the
218
+ * block types, and the recipient variables. The same for every store.
219
+ *
220
+ * @effect read
221
+ * @permission ReadSettings
222
+ */
223
+ catalog(options?: Call): Promise<EmailTemplateCatalog>;
224
+ /**
225
+ * The template of `type` the store sends, with its blocks (in order) and subjects, or `null`
226
+ * for a type the engine does not know.
227
+ *
228
+ * For a store on its own settings, the first read of a known type the store has no template
229
+ * of yet creates it from the platform's default.
230
+ *
231
+ * @effect read
232
+ * @permission ReadSettings
233
+ */
234
+ getByType(type: string, options?: Call): Promise<EmailTemplate | null>;
235
+ /**
236
+ * Set the subject of the template of `templateType` in one language, replacing what it had in
237
+ * that language. Two requests: the template the store uses is looked up first. Answers what is
238
+ * stored.
239
+ *
240
+ * **Coded refusals:** `EMAIL_SUBJECT_INVALID` → `ValidationError` (400), a part that is neither
241
+ * text with its `value` nor a variable with its `key` (the message names it);
242
+ * `EMAIL_LANGUAGE_CODE_INVALID` (400), a language code that is none; `EMAIL_TEMPLATE_NOT_FOUND`
243
+ * → `NotFoundError` (404), no template of the type; `ENTITY_NOT_FOUND` (404), the template was
244
+ * replaced between the two requests. And those of {@link EmailTemplateBlocksModule}.
245
+ *
246
+ * @effect write
247
+ * @permission UpdateSettings
248
+ */
249
+ setSubject(templateType: string, languageCode: string, segments: readonly EmailTokenSegment[], options?: Call): Promise<EmailTemplateSubject>;
250
+ /** The blocks of a template's body. */
251
+ blocks: EmailTemplateBlocksModule;
252
+ /**
253
+ * Render the template of `type` as the store would send it now, optionally with unsaved style
254
+ * changes. Two requests: the template the store uses for `type` is looked up first.
255
+ *
256
+ * **The answer is customer data.** The sample is filled from the store's LATEST REAL order and
257
+ * customer (synthetic values only in a store with none), so `subject`, `html` and the
258
+ * recipients carry a real buyer's name, email and addresses. Do not log or forward them; a
259
+ * credential below `customerData: full` gets them withheld ({@link EmailPreview.redacted}).
260
+ *
261
+ * For a store on its own settings, the first look-up of a known type the store has no template
262
+ * of yet creates it from the platform's default (as the dashboard's editor does).
263
+ *
264
+ * **Coded refusals:**
265
+ * - `EMAIL_TEMPLATE_NOT_FOUND` → `NotFoundError` (404): no template of `type` (an unknown type).
266
+ * - `ENTITY_NOT_FOUND` → `NotFoundError` (404): the template changed between the two requests
267
+ * (the store's `settings.email` source moved); ask again.
268
+ * - `FORBIDDEN` → `PermissionDeniedError` (403): the credential lacks `ReadSettings`.
269
+ *
270
+ * @effect read
271
+ * @permission ReadSettings
272
+ */
273
+ preview(type: string, options: EmailPreviewOptions): Promise<EmailPreview>;
274
+ }
275
+ export declare const EMAIL_TEMPLATE_CATALOG_QUERY = "\n query EmailTemplateCatalog {\n emailTemplateCatalog {\n templateTypes { type label description variables { key label } }\n chipTypes { type label category }\n recipientVariables { key label }\n }\n }\n";
276
+ export declare const EMAIL_TEMPLATE_BY_TYPE_QUERY = "\n query EmailTemplateByType($type: String!) {\n emailTemplateByType(type: $type) {\n id\n type\n enabled\n recipientTo { type value key }\n recipientCc { type value key }\n recipientBcc { type value key }\n chips { id type position config translations { languageCode content } }\n subjects { languageCode subject { type value key } }\n }\n }\n";
277
+ export declare const EMAIL_TEMPLATE_ID_BY_TYPE_QUERY = "\n query EmailTemplateIdByType($type: String!) {\n emailTemplateByType(type: $type) { id }\n }\n";
278
+ /** The look-up `blocks.add` makes: the id, and where the body ends. */
279
+ export declare const EMAIL_TEMPLATE_END_BY_TYPE_QUERY = "\n query EmailTemplateEndByType($type: String!) {\n emailTemplateByType(type: $type) { id chips { position } }\n }\n";
280
+ export declare const ADD_EMAIL_TEMPLATE_BLOCK_MUTATION = "\n mutation AddEmailTemplateBlock($templateId: ID!, $input: AddChipInput!) {\n addEmailTemplateChip(templateId: $templateId, input: $input) { id type position config }\n }\n";
281
+ export declare const UPDATE_EMAIL_TEMPLATE_BLOCK_MUTATION = "\n mutation UpdateEmailTemplateBlock($chipId: ID!, $input: UpdateChipInput!) {\n updateEmailTemplateChip(chipId: $chipId, input: $input) { id type position config }\n }\n";
282
+ export declare const REMOVE_EMAIL_TEMPLATE_BLOCK_MUTATION = "\n mutation RemoveEmailTemplateBlock($chipId: ID!) {\n removeEmailTemplateChip(chipId: $chipId)\n }\n";
283
+ export declare const REORDER_EMAIL_TEMPLATE_BLOCKS_MUTATION = "\n mutation ReorderEmailTemplateBlocks($templateId: ID!, $input: ReorderChipsInput!) {\n reorderEmailTemplateChips(templateId: $templateId, input: $input)\n }\n";
284
+ export declare const SET_EMAIL_TEMPLATE_BLOCK_CONTENT_MUTATION = "\n mutation SetEmailTemplateBlockContent($chipId: ID!, $input: UpsertChipTranslationInput!) {\n upsertEmailChipTranslation(chipId: $chipId, input: $input) { languageCode content }\n }\n";
285
+ export declare const SET_EMAIL_TEMPLATE_SUBJECT_MUTATION = "\n mutation SetEmailTemplateSubject($templateId: ID!, $input: UpsertSubjectInput!) {\n upsertEmailTemplateSubject(templateId: $templateId, input: $input) {\n languageCode\n subject { type value key }\n }\n }\n";
286
+ export declare const EMAIL_PREVIEW_QUERY = "\n query EmailPreview(\n $templateId: ID!\n $languageCode: String!\n $styleOverrides: UpdateEmailStyleConfigInput\n ) {\n renderEmailPreview(\n templateId: $templateId\n languageCode: $languageCode\n styleOverrides: $styleOverrides\n ) {\n subject\n html\n recipientTo\n recipientCc\n recipientBcc\n }\n }\n";
287
+ export declare function createEmailTemplatesModule(transport: Transport): EmailTemplatesModule;
@@ -0,0 +1,271 @@
1
+ import { NotFoundError } from '@sixtyninecommerce/core';
2
+ import { customerDataField } from '../customers/wire.js';
3
+ import { emailStyleInputOf } from './email-style.js';
4
+ const SEGMENT_FIELDS = 'type value key';
5
+ const BLOCK_FIELDS = `id type position config translations { languageCode content }`;
6
+ /**
7
+ * A block as the two block WRITES answer it: no `translations`. An engine before 2026-10-15 saves
8
+ * the chip and only then fails to answer that (non-null) relation, so a write selecting it would
9
+ * report a failure for a change that was stored, and a retry would add the block twice.
10
+ */
11
+ const WRITTEN_BLOCK_FIELDS = 'id type position config';
12
+ export const EMAIL_TEMPLATE_CATALOG_QUERY = /* GraphQL */ `
13
+ query EmailTemplateCatalog {
14
+ emailTemplateCatalog {
15
+ templateTypes { type label description variables { key label } }
16
+ chipTypes { type label category }
17
+ recipientVariables { key label }
18
+ }
19
+ }
20
+ `;
21
+ export const EMAIL_TEMPLATE_BY_TYPE_QUERY = /* GraphQL */ `
22
+ query EmailTemplateByType($type: String!) {
23
+ emailTemplateByType(type: $type) {
24
+ id
25
+ type
26
+ enabled
27
+ recipientTo { ${SEGMENT_FIELDS} }
28
+ recipientCc { ${SEGMENT_FIELDS} }
29
+ recipientBcc { ${SEGMENT_FIELDS} }
30
+ chips { ${BLOCK_FIELDS} }
31
+ subjects { languageCode subject { ${SEGMENT_FIELDS} } }
32
+ }
33
+ }
34
+ `;
35
+ export const EMAIL_TEMPLATE_ID_BY_TYPE_QUERY = /* GraphQL */ `
36
+ query EmailTemplateIdByType($type: String!) {
37
+ emailTemplateByType(type: $type) { id }
38
+ }
39
+ `;
40
+ /** The look-up `blocks.add` makes: the id, and where the body ends. */
41
+ export const EMAIL_TEMPLATE_END_BY_TYPE_QUERY = /* GraphQL */ `
42
+ query EmailTemplateEndByType($type: String!) {
43
+ emailTemplateByType(type: $type) { id chips { position } }
44
+ }
45
+ `;
46
+ export const ADD_EMAIL_TEMPLATE_BLOCK_MUTATION = /* GraphQL */ `
47
+ mutation AddEmailTemplateBlock($templateId: ID!, $input: AddChipInput!) {
48
+ addEmailTemplateChip(templateId: $templateId, input: $input) { ${WRITTEN_BLOCK_FIELDS} }
49
+ }
50
+ `;
51
+ export const UPDATE_EMAIL_TEMPLATE_BLOCK_MUTATION = /* GraphQL */ `
52
+ mutation UpdateEmailTemplateBlock($chipId: ID!, $input: UpdateChipInput!) {
53
+ updateEmailTemplateChip(chipId: $chipId, input: $input) { ${WRITTEN_BLOCK_FIELDS} }
54
+ }
55
+ `;
56
+ export const REMOVE_EMAIL_TEMPLATE_BLOCK_MUTATION = /* GraphQL */ `
57
+ mutation RemoveEmailTemplateBlock($chipId: ID!) {
58
+ removeEmailTemplateChip(chipId: $chipId)
59
+ }
60
+ `;
61
+ export const REORDER_EMAIL_TEMPLATE_BLOCKS_MUTATION = /* GraphQL */ `
62
+ mutation ReorderEmailTemplateBlocks($templateId: ID!, $input: ReorderChipsInput!) {
63
+ reorderEmailTemplateChips(templateId: $templateId, input: $input)
64
+ }
65
+ `;
66
+ export const SET_EMAIL_TEMPLATE_BLOCK_CONTENT_MUTATION = /* GraphQL */ `
67
+ mutation SetEmailTemplateBlockContent($chipId: ID!, $input: UpsertChipTranslationInput!) {
68
+ upsertEmailChipTranslation(chipId: $chipId, input: $input) { languageCode content }
69
+ }
70
+ `;
71
+ export const SET_EMAIL_TEMPLATE_SUBJECT_MUTATION = /* GraphQL */ `
72
+ mutation SetEmailTemplateSubject($templateId: ID!, $input: UpsertSubjectInput!) {
73
+ upsertEmailTemplateSubject(templateId: $templateId, input: $input) {
74
+ languageCode
75
+ subject { ${SEGMENT_FIELDS} }
76
+ }
77
+ }
78
+ `;
79
+ export const EMAIL_PREVIEW_QUERY = /* GraphQL */ `
80
+ query EmailPreview(
81
+ $templateId: ID!
82
+ $languageCode: String!
83
+ $styleOverrides: UpdateEmailStyleConfigInput
84
+ ) {
85
+ renderEmailPreview(
86
+ templateId: $templateId
87
+ languageCode: $languageCode
88
+ styleOverrides: $styleOverrides
89
+ ) {
90
+ subject
91
+ html
92
+ recipientTo
93
+ recipientCc
94
+ recipientBcc
95
+ }
96
+ }
97
+ `;
98
+ /** A part as stored, without the null of the field the other kind uses. */
99
+ function segmentOf(wire) {
100
+ const segment = { type: wire.type };
101
+ if (wire.value != null)
102
+ segment.value = wire.value;
103
+ if (wire.key != null)
104
+ segment.key = wire.key;
105
+ return segment;
106
+ }
107
+ /** A part as the engine takes it: only the fields the caller set. */
108
+ function segmentInputOf(segment) {
109
+ const input = { type: segment.type };
110
+ if (segment.value !== undefined)
111
+ input.value = segment.value;
112
+ if (segment.key !== undefined)
113
+ input.key = segment.key;
114
+ return input;
115
+ }
116
+ function contentOf(wire) {
117
+ return { languageCode: wire.languageCode, content: wire.content ?? {} };
118
+ }
119
+ function writtenBlockOf(wire) {
120
+ return {
121
+ id: String(wire.id),
122
+ type: wire.type,
123
+ position: wire.position,
124
+ config: wire.config ?? {},
125
+ };
126
+ }
127
+ function blockOf(wire) {
128
+ return { ...writtenBlockOf(wire), contents: (wire.translations ?? []).map(contentOf) };
129
+ }
130
+ function templateOf(wire) {
131
+ return {
132
+ id: String(wire.id),
133
+ type: wire.type,
134
+ enabled: wire.enabled,
135
+ recipients: {
136
+ to: wire.recipientTo.map(segmentOf),
137
+ cc: wire.recipientCc.map(segmentOf),
138
+ bcc: wire.recipientBcc.map(segmentOf),
139
+ },
140
+ // The engine orders them on its own rows; the inherited read builds them in code. Sorted
141
+ // here so the promise holds either way.
142
+ blocks: wire.chips.map(blockOf).sort((a, b) => a.position - b.position),
143
+ subjects: wire.subjects.map((row) => ({
144
+ languageCode: row.languageCode,
145
+ segments: row.subject.map(segmentOf),
146
+ })),
147
+ };
148
+ }
149
+ function templateNotFound(type) {
150
+ return new NotFoundError({
151
+ code: 'EMAIL_TEMPLATE_NOT_FOUND',
152
+ message: `The store has no email template of type "${type}".`,
153
+ status: 404,
154
+ service: 'vendure',
155
+ });
156
+ }
157
+ export function createEmailTemplatesModule(transport) {
158
+ /** The id of the template of `type` the store uses, or the coded not-found. */
159
+ const templateIdOf = async (type, call) => {
160
+ const found = await transport.graphql({ api: 'admin', query: EMAIL_TEMPLATE_ID_BY_TYPE_QUERY, variables: { type } }, call);
161
+ const templateId = found.emailTemplateByType?.id;
162
+ if (!templateId)
163
+ throw templateNotFound(type);
164
+ return String(templateId);
165
+ };
166
+ const blocks = {
167
+ async add(templateType, block, options) {
168
+ let templateId;
169
+ let position = block.position;
170
+ if (position === undefined) {
171
+ // "At the end" is after the LAST position, not the count: positions may have gaps.
172
+ const found = await transport.graphql({
173
+ api: 'admin',
174
+ query: EMAIL_TEMPLATE_END_BY_TYPE_QUERY,
175
+ variables: { type: templateType },
176
+ }, options);
177
+ if (!found.emailTemplateByType)
178
+ throw templateNotFound(templateType);
179
+ templateId = String(found.emailTemplateByType.id);
180
+ position = found.emailTemplateByType.chips.reduce((end, chip) => Math.max(end, chip.position + 1), 0);
181
+ }
182
+ else {
183
+ templateId = await templateIdOf(templateType, options);
184
+ }
185
+ const data = await transport.graphql({
186
+ api: 'admin',
187
+ query: ADD_EMAIL_TEMPLATE_BLOCK_MUTATION,
188
+ variables: {
189
+ templateId,
190
+ input: {
191
+ type: block.type,
192
+ position,
193
+ ...(block.config !== undefined ? { config: block.config } : {}),
194
+ },
195
+ },
196
+ }, options);
197
+ // The engine writes no texts for a new block (`addChip`), so it has none to answer.
198
+ return { ...writtenBlockOf(data.addEmailTemplateChip), contents: [] };
199
+ },
200
+ async update(blockId, patch, options) {
201
+ const data = await transport.graphql({
202
+ api: 'admin',
203
+ query: UPDATE_EMAIL_TEMPLATE_BLOCK_MUTATION,
204
+ variables: { chipId: blockId, input: { config: patch.config } },
205
+ }, options);
206
+ return writtenBlockOf(data.updateEmailTemplateChip);
207
+ },
208
+ async remove(blockId, options) {
209
+ await transport.graphql({
210
+ api: 'admin',
211
+ query: REMOVE_EMAIL_TEMPLATE_BLOCK_MUTATION,
212
+ variables: { chipId: blockId },
213
+ }, options);
214
+ },
215
+ async reorder(templateType, blockIds, options) {
216
+ const templateId = await templateIdOf(templateType, options);
217
+ await transport.graphql({
218
+ api: 'admin',
219
+ query: REORDER_EMAIL_TEMPLATE_BLOCKS_MUTATION,
220
+ variables: { templateId, input: { chipIds: [...blockIds] } },
221
+ }, options);
222
+ },
223
+ async setContent(blockId, languageCode, content, options) {
224
+ const data = await transport.graphql({
225
+ api: 'admin',
226
+ query: SET_EMAIL_TEMPLATE_BLOCK_CONTENT_MUTATION,
227
+ variables: { chipId: blockId, input: { languageCode, content } },
228
+ }, options);
229
+ return contentOf(data.upsertEmailChipTranslation);
230
+ },
231
+ };
232
+ return {
233
+ async catalog(options) {
234
+ const data = await transport.graphql({ api: 'admin', query: EMAIL_TEMPLATE_CATALOG_QUERY }, options);
235
+ const { templateTypes, chipTypes, recipientVariables } = data.emailTemplateCatalog;
236
+ return { templateTypes, blockTypes: chipTypes, recipientVariables };
237
+ },
238
+ async getByType(type, options) {
239
+ const data = await transport.graphql({ api: 'admin', query: EMAIL_TEMPLATE_BY_TYPE_QUERY, variables: { type } }, options);
240
+ return data.emailTemplateByType ? templateOf(data.emailTemplateByType) : null;
241
+ },
242
+ async setSubject(templateType, languageCode, segments, options) {
243
+ const templateId = await templateIdOf(templateType, options);
244
+ const data = await transport.graphql({
245
+ api: 'admin',
246
+ query: SET_EMAIL_TEMPLATE_SUBJECT_MUTATION,
247
+ variables: {
248
+ templateId,
249
+ input: { languageCode, subject: segments.map(segmentInputOf) },
250
+ },
251
+ }, options);
252
+ const saved = data.upsertEmailTemplateSubject;
253
+ return { languageCode: saved.languageCode, segments: saved.subject.map(segmentOf) };
254
+ },
255
+ blocks,
256
+ async preview(type, options) {
257
+ const { languageCode, style, ...call } = options;
258
+ const templateId = await templateIdOf(type, call);
259
+ const { value, mark } = await customerDataField(transport, EMAIL_PREVIEW_QUERY, 'renderEmailPreview', {
260
+ templateId,
261
+ languageCode,
262
+ ...(style !== undefined ? { styleOverrides: emailStyleInputOf(style) } : {}),
263
+ }, call);
264
+ return mark({
265
+ subject: value.subject,
266
+ html: value.html,
267
+ recipients: { to: value.recipientTo, cc: value.recipientCc, bcc: value.recipientBcc },
268
+ });
269
+ },
270
+ };
271
+ }
@@ -38,8 +38,8 @@ export interface NotificationEventConfig {
38
38
  event: string;
39
39
  /**
40
40
  * `false` switches the email off altogether; nothing is logged for it. Always
41
- * `true` for `password_reset`, `email_change` and `account_setup`, which
42
- * cannot be switched off.
41
+ * `true` for `password_reset`, `email_change`, `account_setup` and
42
+ * `gift_card_issued`, which cannot be switched off.
43
43
  */
44
44
  enabled: boolean;
45
45
  sender: NotificationSender;
@@ -60,20 +60,23 @@ export interface NotificationEventConfig {
60
60
  export interface UpdateNotificationEventConfigInput {
61
61
  /**
62
62
  * `false` is refused with `EVENT_ALWAYS_ENABLED` ({@link ValidationError}) on
63
- * `password_reset`, `email_change` and `account_setup`: each is the only way
64
- * a customer resets a password (or sets the first one on an imported
65
- * account), confirms a new address, or finishes a storefront registration,
66
- * so they are always sent. `magic_link_login` and `customer_invite` can be
67
- * switched off.
63
+ * `password_reset`, `email_change`, `account_setup` and `gift_card_issued`:
64
+ * each is the only way a customer resets a password (or sets the first one
65
+ * on an imported account), confirms a new address, finishes a storefront
66
+ * registration, or receives the code of a gift card they paid for (the
67
+ * platform keeps only its hash), so they are always sent. `magic_link_login`
68
+ * and `customer_invite` can be switched off.
68
69
  */
69
70
  enabled?: boolean;
70
71
  /**
71
72
  * `none` is refused on the identity emails — the `account` category:
72
73
  * password reset, magic link, invitation, email change, email and chat
73
- * verification — with `EVENT_ALWAYS_PLATFORM_SENT` ({@link ValidationError}).
74
- * Each carries a one-time credential only the platform can issue, and no
75
- * platform event carries one, so there is no event your system could send it
76
- * from. What you control there is where its link lands (your head's URL).
74
+ * verification — and on `gift_card_issued`, with `EVENT_ALWAYS_PLATFORM_SENT`
75
+ * ({@link ValidationError}). Each carries a one-time credential, or a gift
76
+ * card's code, only the platform can issue, and no platform event carries
77
+ * one (`stored_value.gift_card.issued` names the card, never its code), so
78
+ * there is no event your system could send it from. What you control there
79
+ * is where its link lands (your head's URL).
77
80
  *
78
81
  * `none` is also refused on an email whose `replacementEventTypes` is `null`,
79
82
  * with `NO_REPLACEMENT_EVENT` ({@link ValidationError}): no platform event is
@@ -87,6 +90,8 @@ export interface UpdateNotificationEventConfigInput {
87
90
  * links or codes, and one login serves the person in every store, so they go to
88
91
  * the account holder alone: `to` is the `customer_email` variable, `cc` and
89
92
  * `bcc` are empty, and `list` answers exactly that whatever was stored before.
93
+ * So does `gift_card_issued`: whoever holds a copy of its code can spend the
94
+ * card, so it goes to the buyer alone.
90
95
  * Any other list on them is refused with `IDENTITY_EMAIL_RECIPIENTS_FIXED`
91
96
  * ({@link ValidationError}); resending the lists `list` answered is accepted.
92
97
  */
@@ -119,18 +124,19 @@ export interface UpdateNotificationEventConfigInput {
119
124
  *
120
125
  * **Coded refusals**, GraphQL errors mapped by `core`:
121
126
  * - `EVENT_ALWAYS_PLATFORM_SENT` → {@link ValidationError}: `sender: 'none'` on
122
- * an identity email. The message names the event; `details[0].field` is
123
- * `/sender`.
127
+ * an identity email or `gift_card_issued`. The message names the event;
128
+ * `details[0].field` is `/sender`.
124
129
  * - `EVENT_ALWAYS_ENABLED` → {@link ValidationError}: `enabled: false` on
125
- * `password_reset`, `email_change` or `account_setup`. The message names the
126
- * event and why it cannot be switched off; `details[0].field` is `/enabled`.
130
+ * `password_reset`, `email_change`, `account_setup` or `gift_card_issued`.
131
+ * The message names the event and why it cannot be switched off;
132
+ * `details[0].field` is `/enabled`.
127
133
  * - `NO_REPLACEMENT_EVENT` → {@link ValidationError}: `sender: 'none'` on an
128
134
  * email no emitted platform event replaces (`replacementEventTypes: null`).
129
135
  * The message names the event, and the event planned to replace it when one
130
136
  * is; `details[0].field` is `/sender`.
131
137
  * - `IDENTITY_EMAIL_RECIPIENTS_FIXED` → {@link ValidationError}: a recipient
132
- * list on an identity email (or `new_customer`) that names anyone but the
133
- * account holder. `details[].field` names each offending list
138
+ * list on an identity email (or `new_customer`, or `gift_card_issued`) that
139
+ * names anyone but the account holder. `details[].field` names each offending list
134
140
  * (`/recipientTo`, `/recipientCc`, `/recipientBcc`).
135
141
  * - `INVALID_NOTIFICATION_SENDER` → {@link ValidationError}.
136
142
  * - `NOTIFICATION_EVENT_NOT_FOUND` → {@link NotFoundError}: no such event key.
@@ -97,7 +97,7 @@ export interface SettingsSourcesModule {
97
97
  assets(group: SettingsGroup | (string & {}), call?: Call): Promise<AssetFile[]>;
98
98
  }
99
99
  export declare const CHANNEL_SETTINGS_SOURCES_QUERY = "\n query ChannelSettingsSources {\n channelSettingsSources { \n group\n source\n locked\n overridden\n inheritable\n organizationName\n values\n }\n }\n";
100
- export declare const CHANNEL_SETTINGS_ASSETS_QUERY = "\n query ChannelSettingsAssets($group: String!) {\n channelSettingsAssets(group: $group) { \n id\n createdAt\n updatedAt\n name\n type\n mimeType\n fileSize\n width\n height\n source\n preview\n focalPoint {\n x\n y\n }\n tags {\n value\n }\n }\n }\n";
100
+ export declare const CHANNEL_SETTINGS_ASSETS_QUERY = "\n query ChannelSettingsAssets($group: String!) {\n channelSettingsAssets(group: $group) { \n id\n createdAt\n updatedAt\n name\n type\n mimeType\n fileSize\n width\n height\n source\n preview\n focalPoint {\n x\n y\n }\n tags {\n value\n }\n customFields {\n altText\n }\n }\n }\n";
101
101
  export declare const OVERRIDE_ORGANIZATION_SETTINGS_MUTATION = "\n mutation OverrideOrganizationSettings($group: String!) {\n overrideOrganizationSettings(group: $group) { \n group\n source\n locked\n overridden\n inheritable\n organizationName\n values\n }\n }\n";
102
102
  export declare const INHERIT_ORGANIZATION_SETTINGS_MUTATION = "\n mutation InheritOrganizationSettings($group: String!) {\n inheritOrganizationSettings(group: $group) { \n group\n source\n locked\n overridden\n inheritable\n organizationName\n values\n }\n }\n";
103
103
  export declare function createSettingsSourcesModule(transport: Transport): SettingsSourcesModule;
@@ -0,0 +1,17 @@
1
+ import type { Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ /**
4
+ * `admin.tenant.timeZone(call?)`: the ACTIVE store's IANA time zone (`Europe/Amsterdam`,
5
+ * `America/New_York`, …) as the engine resolves it: the store's own setting, or
6
+ * `Europe/Amsterdam` when it has none or an invalid one. It is the zone the API counts calendar
7
+ * days in (a requested delivery day, "shipped today", an invoice date), so render dates and
8
+ * "today" in it, never in the browser's or the server's own zone, and never re-check the
9
+ * setting yourself: the engine owns the rule.
10
+ *
11
+ * Any signed-in caller of the store may read it. No channel argument: the tenant is the store.
12
+ *
13
+ * @effect read
14
+ */
15
+ export type TenantTimeZoneReader = (call?: Call) => Promise<string>;
16
+ export declare const STORE_TIME_ZONE_QUERY = "\n query StoreTimeZone {\n storeTimeZone\n }\n";
17
+ export declare function createTenantTimeZoneReader(transport: Transport): TenantTimeZoneReader;