@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.
- package/README.md +2739 -17
- package/dist/api-tokens/index.d.ts +15 -5
- package/dist/api-tokens/index.js +1 -0
- package/dist/apps/event-subscription.d.ts +59 -0
- package/dist/apps/event-subscription.js +32 -0
- package/dist/apps/hook-secret.d.ts +22 -0
- package/dist/apps/hook-secret.js +36 -0
- package/dist/apps/index.d.ts +54 -3
- package/dist/apps/index.js +17 -0
- package/dist/audit/index.d.ts +7 -0
- package/dist/auth/queries.d.ts +5 -1
- package/dist/auth/queries.js +6 -0
- package/dist/bulk/index.d.ts +323 -0
- package/dist/bulk/index.js +162 -0
- package/dist/catalog/assets.d.ts +147 -0
- package/dist/catalog/assets.js +164 -0
- package/dist/catalog/collections.d.ts +109 -0
- package/dist/catalog/collections.js +91 -0
- package/dist/catalog/facets.d.ts +103 -0
- package/dist/catalog/facets.js +100 -0
- package/dist/catalog/index.d.ts +115 -0
- package/dist/catalog/index.js +21 -0
- package/dist/catalog/options.d.ts +89 -0
- package/dist/catalog/options.js +88 -0
- package/dist/catalog/products-bulk.d.ts +21 -0
- package/dist/catalog/products-bulk.js +58 -0
- package/dist/catalog/products.d.ts +650 -0
- package/dist/catalog/products.js +458 -0
- package/dist/catalog/properties.d.ts +234 -0
- package/dist/catalog/properties.js +158 -0
- package/dist/catalog/types.d.ts +174 -0
- package/dist/catalog/types.js +141 -0
- package/dist/catalog/variants.d.ts +324 -0
- package/dist/catalog/variants.js +288 -0
- package/dist/catalog/visibility.d.ts +57 -0
- package/dist/catalog/visibility.js +59 -0
- package/dist/client.d.ts +192 -3
- package/dist/client.js +87 -12
- package/dist/compliance/index.d.ts +88 -0
- package/dist/compliance/index.js +88 -0
- package/dist/customer-data-redaction.d.ts +14 -0
- package/dist/customer-data-redaction.js +22 -0
- package/dist/customers/index.d.ts +428 -17
- package/dist/customers/index.js +322 -15
- package/dist/documents/index.d.ts +513 -16
- package/dist/documents/index.js +229 -3
- package/dist/events/index.d.ts +259 -0
- package/dist/events/index.js +147 -0
- package/dist/feeds/ai.d.ts +46 -0
- package/dist/feeds/ai.js +43 -0
- package/dist/feeds/categories.d.ts +50 -0
- package/dist/feeds/categories.js +56 -0
- package/dist/feeds/configs.d.ts +54 -0
- package/dist/feeds/configs.js +85 -0
- package/dist/feeds/identifier-fields.d.ts +25 -0
- package/dist/feeds/identifier-fields.js +29 -0
- package/dist/feeds/index.d.ts +54 -0
- package/dist/feeds/index.js +46 -0
- package/dist/feeds/preserve.d.ts +38 -0
- package/dist/feeds/preserve.js +74 -0
- package/dist/feeds/rules.d.ts +42 -0
- package/dist/feeds/rules.js +56 -0
- package/dist/feeds/types.d.ts +654 -0
- package/dist/feeds/types.js +135 -0
- package/dist/fulfillments/index.d.ts +437 -0
- package/dist/fulfillments/index.js +534 -0
- package/dist/heads/index.d.ts +60 -0
- package/dist/heads/index.js +45 -0
- package/dist/hooks/assignments.d.ts +90 -0
- package/dist/hooks/assignments.js +53 -0
- package/dist/hooks/index.d.ts +97 -0
- package/dist/hooks/index.js +76 -0
- package/dist/index.d.ts +95 -9
- package/dist/index.js +21 -1
- package/dist/integrations/index.d.ts +14 -0
- package/dist/integrations/index.js +9 -0
- package/dist/integrations/pipelines.d.ts +113 -0
- package/dist/integrations/pipelines.js +62 -0
- package/dist/integrations/types.d.ts +176 -0
- package/dist/integrations/types.js +8 -0
- package/dist/jobs/bulk-action-run.d.ts +74 -0
- package/dist/jobs/bulk-action-run.js +69 -0
- package/dist/locations/index.d.ts +6 -2
- package/dist/meta/index.d.ts +32 -0
- package/dist/orders/bulk.d.ts +75 -0
- package/dist/orders/bulk.js +150 -0
- package/dist/orders/commit-edit.d.ts +123 -0
- package/dist/orders/commit-edit.js +142 -0
- package/dist/orders/deposit-policy.d.ts +103 -0
- package/dist/orders/deposit-policy.js +202 -0
- package/dist/orders/get.d.ts +722 -0
- package/dist/orders/get.js +530 -0
- package/dist/orders/index.d.ts +186 -21
- package/dist/orders/index.js +57 -0
- package/dist/orders/line-price-preview.d.ts +297 -0
- package/dist/orders/line-price-preview.js +50 -0
- package/dist/orders/manual-discounts.d.ts +141 -0
- package/dist/orders/manual-discounts.js +97 -0
- package/dist/orders/manual-pricing.d.ts +192 -0
- package/dist/orders/manual-pricing.js +72 -0
- package/dist/orders/order-entry.d.ts +566 -0
- package/dist/orders/order-entry.js +129 -0
- package/dist/orders/parcel-charge.d.ts +45 -0
- package/dist/orders/parcel-charge.js +55 -0
- package/dist/orders/plan-edit.d.ts +489 -0
- package/dist/orders/plan-edit.js +93 -0
- package/dist/orders/status.d.ts +235 -0
- package/dist/orders/status.js +96 -0
- package/dist/orders/surcharges.d.ts +75 -2
- package/dist/orders/surcharges.js +110 -4
- package/dist/payments/account-credit.d.ts +171 -0
- package/dist/payments/account-credit.js +107 -0
- package/dist/payments/index.d.ts +241 -9
- package/dist/payments/index.js +92 -0
- package/dist/payments/methods.d.ts +37 -0
- package/dist/payments/methods.js +33 -0
- package/dist/platform/catalog.d.ts +148 -0
- package/dist/platform/catalog.js +74 -0
- package/dist/platform/entitlements.d.ts +111 -0
- package/dist/platform/entitlements.js +86 -0
- package/dist/platform/legal-entities.d.ts +139 -0
- package/dist/platform/legal-entities.js +126 -0
- package/dist/platform/legal-entity-numbering.d.ts +103 -0
- package/dist/platform/legal-entity-numbering.js +101 -0
- package/dist/platform/organization-membership.d.ts +80 -0
- package/dist/platform/organization-membership.js +71 -0
- package/dist/platform/organization-settings.d.ts +33 -0
- package/dist/platform/organization-settings.js +42 -0
- package/dist/platform/organization-sharing.d.ts +172 -0
- package/dist/platform/organization-sharing.js +120 -0
- package/dist/platform/organizations.d.ts +205 -0
- package/dist/platform/organizations.js +180 -0
- package/dist/platform/provision-store.d.ts +88 -8
- package/dist/platform/provision-store.js +12 -0
- package/dist/platform/reports.d.ts +46 -0
- package/dist/platform/reports.js +31 -0
- package/dist/platform/server-metrics.d.ts +58 -0
- package/dist/platform/server-metrics.js +11 -0
- package/dist/pos/index.d.ts +567 -0
- package/dist/pos/index.js +205 -0
- package/dist/pricing/index.d.ts +408 -0
- package/dist/pricing/index.js +353 -0
- package/dist/pricing/vouchers.d.ts +103 -0
- package/dist/pricing/vouchers.js +69 -0
- package/dist/provenance/index.d.ts +93 -0
- package/dist/provenance/index.js +53 -0
- package/dist/reports/index.d.ts +199 -0
- package/dist/reports/index.js +154 -0
- package/dist/returns/index.d.ts +225 -0
- package/dist/returns/index.js +160 -0
- package/dist/search/index.d.ts +99 -0
- package/dist/search/index.js +62 -0
- package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
- package/dist/shipping/parcel-count-suggestion.js +73 -0
- package/dist/shipping/parcel-rule.d.ts +48 -0
- package/dist/shipping/parcel-rule.js +91 -0
- package/dist/stock/index.d.ts +166 -0
- package/dist/stock/index.js +115 -0
- package/dist/stock/transfers.d.ts +520 -0
- package/dist/stock/transfers.js +437 -0
- package/dist/stored-value/index.d.ts +154 -0
- package/dist/stored-value/index.js +130 -0
- package/dist/tenant/capabilities.d.ts +10 -0
- package/dist/tenant/entitlements.d.ts +35 -0
- package/dist/tenant/entitlements.js +15 -0
- package/dist/tenant/field-groups.d.ts +64 -0
- package/dist/tenant/field-groups.js +34 -0
- package/dist/tenant/legal-entity.d.ts +32 -0
- package/dist/tenant/legal-entity.js +41 -0
- package/dist/tenant/notifications.d.ts +148 -0
- package/dist/tenant/notifications.js +79 -0
- package/dist/tenant/retention.d.ts +10 -4
- package/dist/tenant/settings-sources.d.ts +103 -0
- package/dist/tenant/settings-sources.js +92 -0
- package/dist/types.d.ts +86 -11
- package/dist/uploads/index.d.ts +11 -7
- package/dist/users/index.d.ts +104 -0
- package/dist/users/index.js +80 -0
- 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
|
-
|
|
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
|
-
*
|
|
73
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
+
}
|