@sixtyninecommerce/admin 0.8.0 → 0.10.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 (179) hide show
  1. package/README.md +2739 -17
  2. package/dist/api-tokens/index.d.ts +15 -5
  3. package/dist/api-tokens/index.js +1 -0
  4. package/dist/apps/event-subscription.d.ts +59 -0
  5. package/dist/apps/event-subscription.js +32 -0
  6. package/dist/apps/hook-secret.d.ts +22 -0
  7. package/dist/apps/hook-secret.js +36 -0
  8. package/dist/apps/index.d.ts +54 -3
  9. package/dist/apps/index.js +17 -0
  10. package/dist/audit/index.d.ts +7 -0
  11. package/dist/auth/queries.d.ts +5 -1
  12. package/dist/auth/queries.js +6 -0
  13. package/dist/bulk/index.d.ts +323 -0
  14. package/dist/bulk/index.js +162 -0
  15. package/dist/catalog/assets.d.ts +147 -0
  16. package/dist/catalog/assets.js +164 -0
  17. package/dist/catalog/collections.d.ts +109 -0
  18. package/dist/catalog/collections.js +91 -0
  19. package/dist/catalog/facets.d.ts +103 -0
  20. package/dist/catalog/facets.js +100 -0
  21. package/dist/catalog/index.d.ts +115 -0
  22. package/dist/catalog/index.js +21 -0
  23. package/dist/catalog/options.d.ts +89 -0
  24. package/dist/catalog/options.js +88 -0
  25. package/dist/catalog/products-bulk.d.ts +21 -0
  26. package/dist/catalog/products-bulk.js +58 -0
  27. package/dist/catalog/products.d.ts +650 -0
  28. package/dist/catalog/products.js +458 -0
  29. package/dist/catalog/properties.d.ts +234 -0
  30. package/dist/catalog/properties.js +158 -0
  31. package/dist/catalog/types.d.ts +174 -0
  32. package/dist/catalog/types.js +141 -0
  33. package/dist/catalog/variants.d.ts +324 -0
  34. package/dist/catalog/variants.js +288 -0
  35. package/dist/catalog/visibility.d.ts +57 -0
  36. package/dist/catalog/visibility.js +59 -0
  37. package/dist/client.d.ts +192 -3
  38. package/dist/client.js +87 -12
  39. package/dist/compliance/index.d.ts +88 -0
  40. package/dist/compliance/index.js +88 -0
  41. package/dist/customer-data-redaction.d.ts +14 -0
  42. package/dist/customer-data-redaction.js +22 -0
  43. package/dist/customers/index.d.ts +428 -17
  44. package/dist/customers/index.js +322 -15
  45. package/dist/documents/index.d.ts +513 -16
  46. package/dist/documents/index.js +229 -3
  47. package/dist/events/index.d.ts +259 -0
  48. package/dist/events/index.js +147 -0
  49. package/dist/feeds/ai.d.ts +46 -0
  50. package/dist/feeds/ai.js +43 -0
  51. package/dist/feeds/categories.d.ts +50 -0
  52. package/dist/feeds/categories.js +56 -0
  53. package/dist/feeds/configs.d.ts +54 -0
  54. package/dist/feeds/configs.js +85 -0
  55. package/dist/feeds/identifier-fields.d.ts +25 -0
  56. package/dist/feeds/identifier-fields.js +29 -0
  57. package/dist/feeds/index.d.ts +54 -0
  58. package/dist/feeds/index.js +46 -0
  59. package/dist/feeds/preserve.d.ts +38 -0
  60. package/dist/feeds/preserve.js +74 -0
  61. package/dist/feeds/rules.d.ts +42 -0
  62. package/dist/feeds/rules.js +56 -0
  63. package/dist/feeds/types.d.ts +654 -0
  64. package/dist/feeds/types.js +135 -0
  65. package/dist/fulfillments/index.d.ts +437 -0
  66. package/dist/fulfillments/index.js +534 -0
  67. package/dist/heads/index.d.ts +60 -0
  68. package/dist/heads/index.js +45 -0
  69. package/dist/hooks/assignments.d.ts +90 -0
  70. package/dist/hooks/assignments.js +53 -0
  71. package/dist/hooks/index.d.ts +97 -0
  72. package/dist/hooks/index.js +76 -0
  73. package/dist/index.d.ts +95 -9
  74. package/dist/index.js +21 -1
  75. package/dist/integrations/index.d.ts +14 -0
  76. package/dist/integrations/index.js +9 -0
  77. package/dist/integrations/pipelines.d.ts +113 -0
  78. package/dist/integrations/pipelines.js +62 -0
  79. package/dist/integrations/types.d.ts +176 -0
  80. package/dist/integrations/types.js +8 -0
  81. package/dist/jobs/bulk-action-run.d.ts +74 -0
  82. package/dist/jobs/bulk-action-run.js +69 -0
  83. package/dist/locations/index.d.ts +6 -2
  84. package/dist/meta/index.d.ts +32 -0
  85. package/dist/orders/bulk.d.ts +75 -0
  86. package/dist/orders/bulk.js +150 -0
  87. package/dist/orders/commit-edit.d.ts +123 -0
  88. package/dist/orders/commit-edit.js +142 -0
  89. package/dist/orders/deposit-policy.d.ts +103 -0
  90. package/dist/orders/deposit-policy.js +202 -0
  91. package/dist/orders/get.d.ts +722 -0
  92. package/dist/orders/get.js +530 -0
  93. package/dist/orders/index.d.ts +186 -21
  94. package/dist/orders/index.js +57 -0
  95. package/dist/orders/line-price-preview.d.ts +297 -0
  96. package/dist/orders/line-price-preview.js +50 -0
  97. package/dist/orders/manual-discounts.d.ts +141 -0
  98. package/dist/orders/manual-discounts.js +97 -0
  99. package/dist/orders/manual-pricing.d.ts +192 -0
  100. package/dist/orders/manual-pricing.js +72 -0
  101. package/dist/orders/order-entry.d.ts +566 -0
  102. package/dist/orders/order-entry.js +129 -0
  103. package/dist/orders/parcel-charge.d.ts +45 -0
  104. package/dist/orders/parcel-charge.js +55 -0
  105. package/dist/orders/plan-edit.d.ts +489 -0
  106. package/dist/orders/plan-edit.js +93 -0
  107. package/dist/orders/status.d.ts +235 -0
  108. package/dist/orders/status.js +96 -0
  109. package/dist/orders/surcharges.d.ts +75 -2
  110. package/dist/orders/surcharges.js +110 -4
  111. package/dist/payments/account-credit.d.ts +171 -0
  112. package/dist/payments/account-credit.js +107 -0
  113. package/dist/payments/index.d.ts +241 -9
  114. package/dist/payments/index.js +92 -0
  115. package/dist/payments/methods.d.ts +37 -0
  116. package/dist/payments/methods.js +33 -0
  117. package/dist/platform/catalog.d.ts +148 -0
  118. package/dist/platform/catalog.js +74 -0
  119. package/dist/platform/entitlements.d.ts +111 -0
  120. package/dist/platform/entitlements.js +86 -0
  121. package/dist/platform/legal-entities.d.ts +139 -0
  122. package/dist/platform/legal-entities.js +126 -0
  123. package/dist/platform/legal-entity-numbering.d.ts +103 -0
  124. package/dist/platform/legal-entity-numbering.js +101 -0
  125. package/dist/platform/organization-membership.d.ts +80 -0
  126. package/dist/platform/organization-membership.js +71 -0
  127. package/dist/platform/organization-settings.d.ts +33 -0
  128. package/dist/platform/organization-settings.js +42 -0
  129. package/dist/platform/organization-sharing.d.ts +172 -0
  130. package/dist/platform/organization-sharing.js +120 -0
  131. package/dist/platform/organizations.d.ts +205 -0
  132. package/dist/platform/organizations.js +180 -0
  133. package/dist/platform/provision-store.d.ts +88 -8
  134. package/dist/platform/provision-store.js +12 -0
  135. package/dist/platform/reports.d.ts +46 -0
  136. package/dist/platform/reports.js +31 -0
  137. package/dist/platform/server-metrics.d.ts +58 -0
  138. package/dist/platform/server-metrics.js +11 -0
  139. package/dist/pos/index.d.ts +567 -0
  140. package/dist/pos/index.js +205 -0
  141. package/dist/pricing/index.d.ts +408 -0
  142. package/dist/pricing/index.js +353 -0
  143. package/dist/pricing/vouchers.d.ts +103 -0
  144. package/dist/pricing/vouchers.js +69 -0
  145. package/dist/provenance/index.d.ts +93 -0
  146. package/dist/provenance/index.js +53 -0
  147. package/dist/reports/index.d.ts +199 -0
  148. package/dist/reports/index.js +154 -0
  149. package/dist/returns/index.d.ts +225 -0
  150. package/dist/returns/index.js +160 -0
  151. package/dist/search/index.d.ts +99 -0
  152. package/dist/search/index.js +62 -0
  153. package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
  154. package/dist/shipping/parcel-count-suggestion.js +73 -0
  155. package/dist/shipping/parcel-rule.d.ts +48 -0
  156. package/dist/shipping/parcel-rule.js +91 -0
  157. package/dist/stock/index.d.ts +166 -0
  158. package/dist/stock/index.js +115 -0
  159. package/dist/stock/transfers.d.ts +520 -0
  160. package/dist/stock/transfers.js +437 -0
  161. package/dist/stored-value/index.d.ts +154 -0
  162. package/dist/stored-value/index.js +130 -0
  163. package/dist/tenant/capabilities.d.ts +10 -0
  164. package/dist/tenant/entitlements.d.ts +35 -0
  165. package/dist/tenant/entitlements.js +15 -0
  166. package/dist/tenant/field-groups.d.ts +64 -0
  167. package/dist/tenant/field-groups.js +34 -0
  168. package/dist/tenant/legal-entity.d.ts +32 -0
  169. package/dist/tenant/legal-entity.js +41 -0
  170. package/dist/tenant/notifications.d.ts +148 -0
  171. package/dist/tenant/notifications.js +79 -0
  172. package/dist/tenant/retention.d.ts +10 -4
  173. package/dist/tenant/settings-sources.d.ts +103 -0
  174. package/dist/tenant/settings-sources.js +92 -0
  175. package/dist/types.d.ts +86 -11
  176. package/dist/uploads/index.d.ts +11 -7
  177. package/dist/users/index.d.ts +104 -0
  178. package/dist/users/index.js +80 -0
  179. package/package.json +3 -2
@@ -0,0 +1,180 @@
1
+ import { pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
2
+ import { requireHumanSession } from './guard.js';
3
+ import { createOrganizationMembershipMethods, } from './organization-membership.js';
4
+ import { createOrganizationSettingsMethods, } from './organization-settings.js';
5
+ import { createOrganizationSharingModule, } from './organization-sharing.js';
6
+ const ORGANIZATION_FIELDS = /* GraphQL */ `
7
+ id
8
+ code
9
+ name
10
+ hubChannelId
11
+ hubChannel {
12
+ id
13
+ code
14
+ pricesIncludeTax
15
+ }
16
+ reportingCurrencyCode
17
+ customerPolicy
18
+ assortmentMode
19
+ memberCount
20
+ createdAt
21
+ updatedAt
22
+ `;
23
+ const ENTITLEMENT_FIELDS = /* GraphQL */ `
24
+ id
25
+ organizationId
26
+ pluginKey
27
+ connectedAt
28
+ connectedByUserId
29
+ notes
30
+ createdAt
31
+ updatedAt
32
+ `;
33
+ export const ORGANIZATIONS_QUERY = /* GraphQL */ `
34
+ query PlatformOrganizations($options: OrganizationListOptions) {
35
+ organizations(options: $options) {
36
+ totalItems
37
+ items { ${ORGANIZATION_FIELDS} }
38
+ }
39
+ }
40
+ `;
41
+ export const ORGANIZATION_QUERY = /* GraphQL */ `
42
+ query PlatformOrganization($id: ID!) {
43
+ organization(id: $id) { ${ORGANIZATION_FIELDS} }
44
+ }
45
+ `;
46
+ export const ORGANIZATION_BY_CODE_QUERY = /* GraphQL */ `
47
+ query PlatformOrganizationByCode($code: String!) {
48
+ organizationByCode(code: $code) { ${ORGANIZATION_FIELDS} }
49
+ }
50
+ `;
51
+ export const CREATE_ORGANIZATION_MUTATION = /* GraphQL */ `
52
+ mutation PlatformCreateOrganization($input: CreateOrganizationInput!) {
53
+ createOrganization(input: $input) { ${ORGANIZATION_FIELDS} }
54
+ }
55
+ `;
56
+ export const REGISTER_ORGANIZATION_WORKSPACE_MUTATION = /* GraphQL */ `
57
+ mutation PlatformRegisterOrganizationWorkspace($id: ID!) {
58
+ registerOrganizationWorkspace(id: $id) { ${ORGANIZATION_FIELDS} }
59
+ }
60
+ `;
61
+ export const UPDATE_ORGANIZATION_MUTATION = /* GraphQL */ `
62
+ mutation PlatformUpdateOrganization($input: UpdateOrganizationInput!) {
63
+ updateOrganization(input: $input) { ${ORGANIZATION_FIELDS} }
64
+ }
65
+ `;
66
+ export const ORGANIZATION_ENTITLEMENTS_QUERY = /* GraphQL */ `
67
+ query PlatformOrganizationPluginEntitlements($organizationId: ID!) {
68
+ organizationPluginEntitlements(organizationId: $organizationId) { ${ENTITLEMENT_FIELDS} }
69
+ }
70
+ `;
71
+ export const SET_ORGANIZATION_ENTITLEMENT_MUTATION = /* GraphQL */ `
72
+ mutation PlatformSetOrganizationPluginEntitlement(
73
+ $organizationId: ID!
74
+ $pluginKey: String!
75
+ $connected: Boolean!
76
+ $notes: String
77
+ ) {
78
+ setOrganizationPluginEntitlement(
79
+ organizationId: $organizationId
80
+ pluginKey: $pluginKey
81
+ connected: $connected
82
+ notes: $notes
83
+ ) { ${ENTITLEMENT_FIELDS} }
84
+ }
85
+ `;
86
+ /** The server's own default; it caps a page at 100. */
87
+ const DEFAULT_PAGE_SIZE = 50;
88
+ export function createPlatformOrganizationsModule(transport, config) {
89
+ // Platform-wide, so a SuperAdmin must not need a tenant. An organization
90
+ // admin passes its workspace channel as the tenant, which this leaves alone:
91
+ // the opt-in only matters when the scope is `none`.
92
+ const unscoped = (call) => ({ ...call, allowUnscoped: true });
93
+ const listPage = async (skip, take, options) => {
94
+ requireHumanSession(config);
95
+ const data = await transport.graphql({ api: 'admin', query: ORGANIZATIONS_QUERY, variables: { options: { skip, take } } }, unscoped(options));
96
+ return pageFromVendureList(data.organizations, skip, take, (cursor) => listPage(readOffsetCursor(cursor), take, options));
97
+ };
98
+ return {
99
+ async create(input, call) {
100
+ requireHumanSession(config);
101
+ // Named field by field, so a caller spreading a wider object sends only
102
+ // what `CreateOrganizationInput` declares instead of failing validation.
103
+ const variables = {
104
+ input: {
105
+ code: input.code,
106
+ name: input.name,
107
+ defaultLanguageCode: input.defaultLanguageCode,
108
+ defaultCurrencyCode: input.defaultCurrencyCode,
109
+ country: input.country,
110
+ defaultTaxZoneId: input.defaultTaxZoneId,
111
+ defaultShippingZoneId: input.defaultShippingZoneId,
112
+ pricesIncludeTax: input.pricesIncludeTax,
113
+ reportingCurrencyCode: input.reportingCurrencyCode,
114
+ },
115
+ };
116
+ const data = await transport.graphql({ api: 'admin', query: CREATE_ORGANIZATION_MUTATION, variables }, unscoped(call));
117
+ return data.createOrganization;
118
+ },
119
+ async registerWorkspace(id, call) {
120
+ requireHumanSession(config);
121
+ const data = await transport.graphql({ api: 'admin', query: REGISTER_ORGANIZATION_WORKSPACE_MUTATION, variables: { id } }, unscoped(call));
122
+ return data.registerOrganizationWorkspace;
123
+ },
124
+ async update(input, call) {
125
+ requireHumanSession(config);
126
+ const variables = {
127
+ input: {
128
+ id: input.id,
129
+ name: input.name,
130
+ reportingCurrencyCode: input.reportingCurrencyCode,
131
+ assortmentMode: input.assortmentMode,
132
+ },
133
+ };
134
+ const data = await transport.graphql({ api: 'admin', query: UPDATE_ORGANIZATION_MUTATION, variables }, unscoped(call));
135
+ return data.updateOrganization;
136
+ },
137
+ async list(options) {
138
+ // Guarded here as well as per page: a bad cursor must not be reported
139
+ // ahead of the credential refusal.
140
+ requireHumanSession(config);
141
+ const take = options?.limit ?? DEFAULT_PAGE_SIZE;
142
+ const skip = options?.after === undefined ? 0 : readOffsetCursor(options.after);
143
+ return listPage(skip, take, options);
144
+ },
145
+ async get(id, call) {
146
+ requireHumanSession(config);
147
+ const data = await transport.graphql({ api: 'admin', query: ORGANIZATION_QUERY, variables: { id } }, unscoped(call));
148
+ return data.organization;
149
+ },
150
+ async getByCode(code, call) {
151
+ requireHumanSession(config);
152
+ const data = await transport.graphql({ api: 'admin', query: ORGANIZATION_BY_CODE_QUERY, variables: { code } }, unscoped(call));
153
+ return data.organizationByCode;
154
+ },
155
+ entitlements: {
156
+ async list(organizationId, call) {
157
+ requireHumanSession(config);
158
+ const data = await transport.graphql({ api: 'admin', query: ORGANIZATION_ENTITLEMENTS_QUERY, variables: { organizationId } }, unscoped(call));
159
+ return data.organizationPluginEntitlements;
160
+ },
161
+ async set(input, call) {
162
+ requireHumanSession(config);
163
+ const data = await transport.graphql({
164
+ api: 'admin',
165
+ query: SET_ORGANIZATION_ENTITLEMENT_MUTATION,
166
+ variables: {
167
+ organizationId: input.organizationId,
168
+ pluginKey: input.pluginKey,
169
+ connected: input.connected,
170
+ notes: input.notes ?? null,
171
+ },
172
+ }, unscoped(call));
173
+ return data.setOrganizationPluginEntitlement;
174
+ },
175
+ },
176
+ ...createOrganizationMembershipMethods(transport, config),
177
+ ...createOrganizationSettingsMethods(transport, config),
178
+ sharing: createOrganizationSharingModule(transport, config),
179
+ };
180
+ }
@@ -27,6 +27,9 @@ export interface ProvisionStoreInput {
27
27
  * Lowercase kebab-case. It is also the object-storage prefix, the CMS key and
28
28
  * the order-number namespace, and it can only be changed later by a rename
29
29
  * job — so it is worth getting right.
30
+ *
31
+ * Codes starting with `org-` are reserved for organizations and refused with
32
+ * `CHANNEL_CODE_RESERVED` (a `ValidationError`).
30
33
  */
31
34
  code: string;
32
35
  /** Defaults to `commerce`, which is the full webshop. */
@@ -35,6 +38,13 @@ export interface ProvisionStoreInput {
35
38
  availableLanguageCodes?: string[];
36
39
  defaultCurrencyCode: string;
37
40
  availableCurrencyCodes?: string[];
41
+ /**
42
+ * Defaults to `true`; with {@link ProvisionStoreInput.organization}, to the
43
+ * organization's own mode, because every store of an organization enters
44
+ * prices the same way. A value that differs from the organization's is
45
+ * refused with `ORGANIZATION_TAX_MODE_MISMATCH` (a {@link BusinessRuleError},
46
+ * 422) before any run exists.
47
+ */
38
48
  pricesIncludeTax?: boolean;
39
49
  /**
40
50
  * The store's tax and shipping zones. Send BOTH or NEITHER — a request naming
@@ -57,8 +67,16 @@ export interface ProvisionStoreInput {
57
67
  country: string;
58
68
  /** IANA zone. Every day-resolution comparison for this store uses it. */
59
69
  timezone: string;
60
- legalName: string;
70
+ /**
71
+ * The legal name of the NEW legal entity this store defines. Required unless
72
+ * `legalEntity` is set: the server refuses a request with neither
73
+ * (`VALIDATION_FAILED`, naming `legal_name`). Optional in the type only
74
+ * because a store created under an existing entity must leave it out.
75
+ */
76
+ legalName?: string;
77
+ /** Of the new legal entity. Left out under `legalEntity`. */
61
78
  vatNumber?: string;
79
+ /** Of the new legal entity. Left out under `legalEntity`. */
62
80
  registrationNumber?: string;
63
81
  /**
64
82
  * Invite a merchant administrator to the new store. SOFT-FAIL: a bounced
@@ -69,19 +87,75 @@ export interface ProvisionStoreInput {
69
87
  email: string;
70
88
  };
71
89
  /**
72
- * Reserved. Refused today with `SANDBOX_NOT_AVAILABLE` (a
73
- * {@link ValidationError}) until the sandbox channel flag ships.
90
+ * Provision a SANDBOX store (spec §6.6): the store you point CI and
91
+ * conformance runs at.
92
+ *
93
+ * - Payment providers connect in test mode only: a live account is refused
94
+ * `LIVE_CONNECTION_REFUSED` by `payments.providers.completeOnboarding`, and
95
+ * so is a live Mollie/Stripe key stored on a payment method.
96
+ * - ERP and marketing pushes are refused, and the store is left out of
97
+ * platform metrics.
98
+ * - It is purged without the retention checks: a hold placed on it never
99
+ * stops its hard delete.
100
+ * - Apps declared `sandboxOnly` install here and nowhere else, and the
101
+ * `sandbox-full` token preset is minted here and nowhere else.
102
+ * - In production it never emails its customers: every customer-facing
103
+ * email (order, account and form confirmations, invoices) is recorded on
104
+ * the store's email delivery log as `suppressed_sandbox` instead of sent.
105
+ * Staff mail and the store's own addresses on an email still get theirs.
106
+ * A development stack delivers everything to its mail catcher.
107
+ *
108
+ * Fixed at creation: it can never be cleared, and a store created without it
109
+ * can never become one (`SANDBOX_IS_PERMANENT`, `SANDBOX_ONLY_AT_PROVISIONING`).
110
+ * `tenant.capabilities()` reports it as `sandbox`. Defaults to `false`.
74
111
  */
75
112
  sandbox?: boolean;
76
- /** An organization code. Refused with `ORGANIZATIONS_NOT_AVAILABLE`. */
113
+ /**
114
+ * An organization code. The new store joins that organization as the run's
115
+ * LAST step (`organization`), once everything else is built.
116
+ *
117
+ * An unknown code is refused with `ORGANIZATION_NOT_FOUND` (a
118
+ * {@link NotFoundError}, 404) before any run exists, so a typo builds
119
+ * nothing. If the join itself fails, the store is KEPT, not rolled back: the
120
+ * run ends `failed` at step `organization` and still carries its `result`,
121
+ * `wait()` throws `PROVISION_RUN_FAILED` saying so, and the store can be
122
+ * joined afterwards with `platform.organizations.join` (the Admin API's
123
+ * `joinOrganization`), which is safe to repeat.
124
+ */
77
125
  organization?: string;
78
- /** A Seller id. Refused with `ORGANIZATIONS_NOT_AVAILABLE`. */
126
+ /**
127
+ * An existing legal entity's id (`platform.legalEntities`; a Seller id). The
128
+ * store is created under it and takes its legal name, VAT and registration
129
+ * numbers; its address, country and time zone stay its own. Left out, the
130
+ * store defines a new legal entity from `legalName`, `vatNumber` and
131
+ * `registrationNumber`.
132
+ *
133
+ * Every refusal arrives before any run exists, so nothing is built:
134
+ * - `legalName`, `vatNumber` or `registrationNumber` sent with it:
135
+ * `LEGAL_ENTITY_FIELDS_CONFLICT` (a {@link ValidationError}, 400). Change
136
+ * the entity's own values with `platform.legalEntities.update` instead.
137
+ * - an unknown id, an organization workspace's Seller or the platform's own:
138
+ * `LEGAL_ENTITY_NOT_FOUND` (a {@link NotFoundError}, 404).
139
+ * - an entity with no legal name: `LEGAL_ENTITY_INCOMPLETE` (a
140
+ * {@link BusinessRuleError}, 422).
141
+ * - a commerce engine without legal entities (multi-region phase 2):
142
+ * `ORGANIZATIONS_NOT_AVAILABLE` (a {@link ValidationError}).
143
+ *
144
+ * A sandbox (`sandbox: true`) may be created under an entity live stores
145
+ * trade under, and the other way round. Its invoices and credit notes are
146
+ * numbered on its own sequence and start with `TEST-`, so they never take a
147
+ * number of the entity's series, and it never pays through the entity's live
148
+ * provider accounts (`LIVE_PAYMENT_REFUSED`).
149
+ *
150
+ * A failed run never removes the entity: only the store it was building is
151
+ * undone.
152
+ */
79
153
  legalEntity?: string;
80
154
  }
81
155
  export type ProvisionRunStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'compensated';
82
156
  export type ProvisionStepStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'skipped' | 'compensated';
83
157
  export interface ProvisionStep {
84
- /** `zones`, `seller`, `channel`, `shipping-methods`, `stock-location`, `roles`, `cms-channel`, `cms-content`, `invite`. */
158
+ /** `zones`, `seller`, `channel`, `shipping-methods`, `stock-location`, `roles`, `cms-channel`, `cms-content`, `invite`, `organization`. */
85
159
  name: string;
86
160
  /** `skipped` is a DECISION — the profile did not ask for this object. */
87
161
  status: ProvisionStepStatus;
@@ -107,7 +181,10 @@ export interface ProvisionRun {
107
181
  profile: string;
108
182
  channelCode: string;
109
183
  steps: ProvisionStep[];
110
- /** Present only on a `succeeded` run. */
184
+ /**
185
+ * The store. Present on a `succeeded` run, and on a `failed` run that stopped
186
+ * at `organization`: that store was built and kept, only the join failed.
187
+ */
111
188
  result?: ProvisionStoreResult;
112
189
  /** Non-fatal notes. A merchant invite that bounced is the only one today. */
113
190
  warnings: string[];
@@ -121,10 +198,13 @@ export interface PlatformModule {
121
198
  /**
122
199
  * Build a whole store: seller, channel, zones, shipping methods, stock
123
200
  * location, roles, CMS registration and the default CMS menus and forms —
124
- * which of them depends on `profile`.
201
+ * which of them depends on `profile` — and, with `organization`, join it to
202
+ * that organization.
125
203
  *
126
204
  * Returns as soon as the run is accepted. The work runs server-side as a saga
127
205
  * with recorded compensation, so a failure half-way leaves nothing behind.
206
+ * The one exception is the organization join, which runs last: when only it
207
+ * fails, the finished store is kept (see {@link ProvisionStoreInput.organization}).
128
208
  *
129
209
  * `options.idempotencyKey` makes a retry return the run already in flight
130
210
  * instead of starting a second one — send one, and reuse it only for retries
@@ -59,6 +59,18 @@ function resultOf(run) {
59
59
  // run into the sentence that follows it.
60
60
  const raw = run.error ?? `Provisioning failed at step "${run.step ?? 'unknown'}"`;
61
61
  const detail = /[.!?]$/.test(raw.trim()) ? raw.trim() : `${raw.trim()}.`;
62
+ // A `failed` run that still carries its store stopped at a step that is never
63
+ // rolled back — the organization join. Nothing is half-built, so the generic
64
+ // "remove what the message names" would send someone to delete a working
65
+ // store.
66
+ if (run.status === 'failed' && run.result) {
67
+ throw new BusinessRuleError({
68
+ code: 'PROVISION_RUN_FAILED',
69
+ message: `${detail} Read the store from platform.provisionRun("${run.id}").result.`,
70
+ status: 422,
71
+ service: 'plugins',
72
+ });
73
+ }
62
74
  throw new BusinessRuleError({
63
75
  code: run.status === 'compensated' ? 'PROVISION_RUN_COMPENSATED' : 'PROVISION_RUN_FAILED',
64
76
  message: run.status === 'compensated'
@@ -0,0 +1,46 @@
1
+ import type { Transport } from '@sixtyninecommerce/core';
2
+ import type { AdminClientConfig } from '../client.js';
3
+ import type { ReportPeriod } from '../reports/index.js';
4
+ import type { Call } from '../types.js';
5
+ export interface FleetReportInput {
6
+ /**
7
+ * Two instants WITH an explicit offset. A fleet spans every store, so there
8
+ * is no single store time zone to read a bare date in: a date is refused
9
+ * (`INVALID_REPORT_PERIOD`). Omitted = lifetime.
10
+ */
11
+ period?: ReportPeriod;
12
+ }
13
+ /** One store's activity. */
14
+ export interface FleetChannelMetric {
15
+ channelId: string;
16
+ /** Placed, uncancelled orders in the period (lifetime without one). Carts never count. */
17
+ orderCount: number;
18
+ /**
19
+ * What those orders BILLED (goodwill rounding folded in), in minor units of
20
+ * `currencyCode` — the store's default currency; orders in another currency
21
+ * are not in it.
22
+ */
23
+ gmvMinor: number;
24
+ /** Null when the store has no placed order at all. */
25
+ currencyCode: string | null;
26
+ /** Lifetime counts, whatever the period. */
27
+ customerCount: number;
28
+ productCount: number;
29
+ /** The store's most recent placed order, ISO-8601 UTC; lifetime. */
30
+ lastOrderAt: string | null;
31
+ }
32
+ /**
33
+ * `admin.platform.reports` — cross-store reporting for a human SuperAdmin
34
+ * session (spec §9.2 `platform` → fleet). An API token or an app credential is
35
+ * refused BEFORE anything is sent (`PLATFORM_REQUIRES_SESSION`); the server
36
+ * refuses one anyway (`channelFleetMetrics` is SuperAdmin-only).
37
+ */
38
+ export interface PlatformReportsModule {
39
+ /**
40
+ * Every LIVE store with some activity; a store absent from the list has none.
41
+ * A sandbox store (`provisionStore({ sandbox: true })`) is never measured.
42
+ */
43
+ fleet(input?: FleetReportInput, options?: Call): Promise<FleetChannelMetric[]>;
44
+ }
45
+ export declare const FLEET_QUERY = "\n query PlatformFleetReport($range: ReportPeriodInput) {\n channelFleetMetrics(range: $range) {\n channelId\n orderCount\n gmvMinor\n currencyCode\n customerCount\n productCount\n lastOrderAt\n }\n }\n";
46
+ export declare function createPlatformReportsModule(transport: Transport, config: AdminClientConfig): PlatformReportsModule;
@@ -0,0 +1,31 @@
1
+ import { requireHumanSession } from './guard.js';
2
+ export const FLEET_QUERY = /* GraphQL */ `
3
+ query PlatformFleetReport($range: ReportPeriodInput) {
4
+ channelFleetMetrics(range: $range) {
5
+ channelId
6
+ orderCount
7
+ gmvMinor
8
+ currencyCode
9
+ customerCount
10
+ productCount
11
+ lastOrderAt
12
+ }
13
+ }
14
+ `;
15
+ export function createPlatformReportsModule(transport, config) {
16
+ return {
17
+ async fleet(input, options) {
18
+ requireHumanSession(config);
19
+ const data = await transport.graphql({
20
+ api: 'admin',
21
+ query: FLEET_QUERY,
22
+ variables: {
23
+ range: input?.period ? { from: input.period.from, to: input.period.to } : null,
24
+ },
25
+ },
26
+ // Cross-store: it is about no one tenant, so it must not need one.
27
+ { ...options, allowUnscoped: true });
28
+ return data.channelFleetMetrics;
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,58 @@
1
+ import type { Transport } from '@sixtyninecommerce/core';
2
+ import type { AdminClientConfig } from '../client.js';
3
+ import type { Call } from '../types.js';
4
+ /** How far back the history series reach. */
5
+ export type ServerMetricsRange = '1h' | '6h' | '24h' | '7d';
6
+ export interface ServerMetricsInput {
7
+ /** Default `1h`. */
8
+ range?: ServerMetricsRange;
9
+ }
10
+ export interface ServerMetricPoint {
11
+ /** Unix epoch milliseconds. */
12
+ timestamp: number;
13
+ value: number;
14
+ }
15
+ /**
16
+ * One host reporting `node_exporter` metrics to the platform's Prometheus. A
17
+ * reading the host did not report (a down host, a metric its exporter does not
18
+ * expose) is `null`, never zero.
19
+ */
20
+ export interface ServerMetrics {
21
+ /** Prometheus `instance` label (`host:port`) — the stable key. */
22
+ instance: string;
23
+ /** The host's own name, else its instance without the port. */
24
+ name: string;
25
+ status: 'up' | 'down';
26
+ cpuPercent: number | null;
27
+ memoryPercent: number | null;
28
+ memoryTotalBytes: number | null;
29
+ /** The root filesystem. */
30
+ diskPercent: number | null;
31
+ load1: number | null;
32
+ cpuCores: number | null;
33
+ uptimeSeconds: number | null;
34
+ networkReceiveBytesPerSecond: number | null;
35
+ networkTransmitBytesPerSecond: number | null;
36
+ cpuHistory: ServerMetricPoint[];
37
+ memoryHistory: ServerMetricPoint[];
38
+ networkReceiveHistory: ServerMetricPoint[];
39
+ networkTransmitHistory: ServerMetricPoint[];
40
+ }
41
+ export interface ServerMetricsOverview {
42
+ /** False when this deployment has no Prometheus wired in; `servers` is then empty. */
43
+ configured: boolean;
44
+ range: ServerMetricsRange;
45
+ /** ISO-8601 UTC. The server caches a snapshot for a few seconds. */
46
+ generatedAt: string;
47
+ /** Down servers first, then by name. */
48
+ servers: ServerMetrics[];
49
+ }
50
+ /**
51
+ * `admin.platform.serverMetrics` — basic health of the platform's own servers,
52
+ * for a human SuperAdmin session. An API token or an app credential is refused
53
+ * BEFORE anything is sent (`PLATFORM_REQUIRES_SESSION`).
54
+ */
55
+ export interface PlatformServerMetricsModule {
56
+ overview(input?: ServerMetricsInput, options?: Call): Promise<ServerMetricsOverview>;
57
+ }
58
+ export declare function createPlatformServerMetricsModule(transport: Transport, config: AdminClientConfig): PlatformServerMetricsModule;
@@ -0,0 +1,11 @@
1
+ import { requireHumanSession } from './guard.js';
2
+ export function createPlatformServerMetricsModule(transport, config) {
3
+ return {
4
+ async overview(input, options) {
5
+ requireHumanSession(config);
6
+ return transport.rest('plugins', { method: 'GET', path: '/server-metrics', query: { range: input?.range } },
7
+ // Cross-tenant: it is about no one store, so it must not need one.
8
+ { ...options, allowUnscoped: true });
9
+ },
10
+ };
11
+ }