@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,130 @@
|
|
|
1
|
+
const ACCOUNT_FIELDS = `
|
|
2
|
+
id
|
|
3
|
+
createdAt
|
|
4
|
+
updatedAt
|
|
5
|
+
kind
|
|
6
|
+
unit
|
|
7
|
+
customerId
|
|
8
|
+
codeLast4
|
|
9
|
+
expiresAt
|
|
10
|
+
expiresOn
|
|
11
|
+
status
|
|
12
|
+
balance
|
|
13
|
+
spendable
|
|
14
|
+
`;
|
|
15
|
+
export const STORED_VALUE_ACCOUNTS_QUERY = /* GraphQL */ `
|
|
16
|
+
query StoredValueAccounts($customerId: ID, $kind: String, $take: Int, $skip: Int) {
|
|
17
|
+
storedValueAccounts(customerId: $customerId, kind: $kind, take: $take, skip: $skip) {
|
|
18
|
+
items { ${ACCOUNT_FIELDS} }
|
|
19
|
+
totalItems
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
export const STORED_VALUE_ACCOUNT_QUERY = /* GraphQL */ `
|
|
24
|
+
query StoredValueAccount($id: ID!) {
|
|
25
|
+
storedValueAccount(id: $id) { ${ACCOUNT_FIELDS} }
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
export const STORED_VALUE_STATEMENT_QUERY = /* GraphQL */ `
|
|
29
|
+
query StoredValueStatement($accountId: ID!, $from: DateTime, $to: DateTime, $take: Int, $skip: Int) {
|
|
30
|
+
storedValueStatement(accountId: $accountId, from: $from, to: $to, take: $take, skip: $skip) {
|
|
31
|
+
account { ${ACCOUNT_FIELDS} }
|
|
32
|
+
openingBalance
|
|
33
|
+
closingBalance
|
|
34
|
+
totalItems
|
|
35
|
+
entries {
|
|
36
|
+
id createdAt occurredAt entryType amount balanceAfter unit sourceType sourceId paymentId
|
|
37
|
+
orderId locationId actorType actorId onBehalfOfRef deviceRef reason
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
export const STORED_VALUE_LIABILITY_QUERY = /* GraphQL */ `
|
|
43
|
+
query StoredValueLiability($at: DateTime, $kind: String, $locationId: ID) {
|
|
44
|
+
storedValueLiability(at: $at, kind: $kind, locationId: $locationId) {
|
|
45
|
+
unit kind accounts issued redeemed reversed adjusted expired forfeited outstanding
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
export const ISSUE_STORED_VALUE_MUTATION = /* GraphQL */ `
|
|
50
|
+
mutation IssueStoredValue($input: IssueStoredValueInput!, $idempotencyKey: String) {
|
|
51
|
+
issueStoredValue(input: $input, idempotencyKey: $idempotencyKey) { ${ACCOUNT_FIELDS} }
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
export const ADJUST_STORED_VALUE_MUTATION = /* GraphQL */ `
|
|
55
|
+
mutation AdjustStoredValue($input: AdjustStoredValueInput!, $idempotencyKey: String) {
|
|
56
|
+
adjustStoredValue(input: $input, idempotencyKey: $idempotencyKey) { ${ACCOUNT_FIELDS} }
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
export const BLOCK_STORED_VALUE_MUTATION = /* GraphQL */ `
|
|
60
|
+
mutation BlockStoredValueAccount($id: ID!, $reason: String!) {
|
|
61
|
+
blockStoredValueAccount(id: $id, reason: $reason) { ${ACCOUNT_FIELDS} }
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
export const UNBLOCK_STORED_VALUE_MUTATION = /* GraphQL */ `
|
|
65
|
+
mutation UnblockStoredValueAccount($id: ID!, $reason: String!) {
|
|
66
|
+
unblockStoredValueAccount(id: $id, reason: $reason) { ${ACCOUNT_FIELDS} }
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
export const SET_STORED_VALUE_EXPIRY_MUTATION = /* GraphQL */ `
|
|
70
|
+
mutation SetStoredValueAccountExpiry(
|
|
71
|
+
$id: ID!
|
|
72
|
+
$expiresOn: String
|
|
73
|
+
$reason: String!
|
|
74
|
+
$idempotencyKey: String
|
|
75
|
+
) {
|
|
76
|
+
setStoredValueAccountExpiry(
|
|
77
|
+
id: $id
|
|
78
|
+
expiresOn: $expiresOn
|
|
79
|
+
reason: $reason
|
|
80
|
+
idempotencyKey: $idempotencyKey
|
|
81
|
+
) { ${ACCOUNT_FIELDS} }
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
export const REDEEM_STORED_VALUE_MUTATION = /* GraphQL */ `
|
|
85
|
+
mutation RedeemStoredValue($input: RedeemStoredValueInput!, $idempotencyKey: String) {
|
|
86
|
+
redeemStoredValue(input: $input, idempotencyKey: $idempotencyKey) {
|
|
87
|
+
orderId
|
|
88
|
+
paymentId
|
|
89
|
+
accountId
|
|
90
|
+
amount
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
export function createStoredValueModule(transport) {
|
|
95
|
+
const read = async (query, variables, options) => transport.graphql({ api: 'admin', query, variables }, options);
|
|
96
|
+
// The key is an ARGUMENT on the Admin API; `undefined` keeps an omitted key off the wire.
|
|
97
|
+
const write = async (query, variables, options) => transport.graphql({ api: 'admin', query, variables: { ...variables, idempotencyKey: options?.idempotencyKey } }, options);
|
|
98
|
+
return {
|
|
99
|
+
async accounts(filter = {}, options) {
|
|
100
|
+
return (await read(STORED_VALUE_ACCOUNTS_QUERY, filter, options)).storedValueAccounts;
|
|
101
|
+
},
|
|
102
|
+
async account(id, options) {
|
|
103
|
+
return (await read(STORED_VALUE_ACCOUNT_QUERY, { id }, options)).storedValueAccount;
|
|
104
|
+
},
|
|
105
|
+
async statement(accountId, window = {}, options) {
|
|
106
|
+
return (await read(STORED_VALUE_STATEMENT_QUERY, { accountId, ...window }, options)).storedValueStatement;
|
|
107
|
+
},
|
|
108
|
+
async liability(filter = {}, options) {
|
|
109
|
+
return (await read(STORED_VALUE_LIABILITY_QUERY, filter, options)).storedValueLiability;
|
|
110
|
+
},
|
|
111
|
+
async issue(input, options) {
|
|
112
|
+
return (await write(ISSUE_STORED_VALUE_MUTATION, { input }, options)).issueStoredValue;
|
|
113
|
+
},
|
|
114
|
+
async adjust(input, options) {
|
|
115
|
+
return (await write(ADJUST_STORED_VALUE_MUTATION, { input }, options)).adjustStoredValue;
|
|
116
|
+
},
|
|
117
|
+
async block(id, reason, options) {
|
|
118
|
+
return (await read(BLOCK_STORED_VALUE_MUTATION, { id, reason }, options)).blockStoredValueAccount;
|
|
119
|
+
},
|
|
120
|
+
async unblock(id, reason, options) {
|
|
121
|
+
return (await read(UNBLOCK_STORED_VALUE_MUTATION, { id, reason }, options)).unblockStoredValueAccount;
|
|
122
|
+
},
|
|
123
|
+
async setExpiry(id, expiresOn, reason, options) {
|
|
124
|
+
return (await write(SET_STORED_VALUE_EXPIRY_MUTATION, { id, expiresOn, reason }, options)).setStoredValueAccountExpiry;
|
|
125
|
+
},
|
|
126
|
+
async redeem(input, options) {
|
|
127
|
+
return (await write(REDEEM_STORED_VALUE_MUTATION, { input }, options)).redeemStoredValue;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -83,6 +83,16 @@ export interface CapabilityManifest {
|
|
|
83
83
|
* Optional because a platform older than this field simply does not send it.
|
|
84
84
|
*/
|
|
85
85
|
assertionAudience?: string | null;
|
|
86
|
+
/**
|
|
87
|
+
* Whether this store is a sandbox (`platform.provisionStore({ sandbox: true })`,
|
|
88
|
+
* spec §6.6): test-mode payments only, no ERP or marketing push, left out of
|
|
89
|
+
* platform metrics, purged past retention holds. Fixed at provisioning.
|
|
90
|
+
*
|
|
91
|
+
* `null` when the platform could not read the flag this time (such an answer
|
|
92
|
+
* is never cached, so `refresh()` asks again); absent from a platform older
|
|
93
|
+
* than this field. Treat anything but `true` as "not a sandbox".
|
|
94
|
+
*/
|
|
95
|
+
sandbox?: boolean | null;
|
|
86
96
|
/** A member added after this SDK release arrives camelised and untouched. */
|
|
87
97
|
[extra: string]: unknown;
|
|
88
98
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/** Where a plugin grant of the active store comes from. */
|
|
4
|
+
export type TenantEntitlementSource = 'channel' | 'organization';
|
|
5
|
+
/**
|
|
6
|
+
* One plugin the ACTIVE store may use: its own grant, or one its organization holds for every
|
|
7
|
+
* member store and its workspace. A plugin with no row is not granted.
|
|
8
|
+
*/
|
|
9
|
+
export interface TenantEntitlement {
|
|
10
|
+
/** The plugin's catalog key (`pos`, `afas`, `product-feeds`, …), exactly as the engine sends it. */
|
|
11
|
+
pluginKey: string;
|
|
12
|
+
/**
|
|
13
|
+
* `channel` for the store's own grant, `organization` for one granted to its whole organization.
|
|
14
|
+
* When both exist the store's own row wins, so a key appears once. A string on the wire: a newer
|
|
15
|
+
* engine may answer a source this SDK does not name yet.
|
|
16
|
+
*/
|
|
17
|
+
source: TenantEntitlementSource | (string & {});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The plugin grants of the ACTIVE store (plan W9-4, spec §9.2 `tenant` "entitlements (read)").
|
|
21
|
+
*
|
|
22
|
+
* `admin.tenant.entitlements(call?)` reads the engine's own union of the store's and its
|
|
23
|
+
* organization's grants, the one it reads to allow a plugin's back-office operations. The grant
|
|
24
|
+
* rows are read on every call, so a grant switched in Platform → Plugins answers on the next one.
|
|
25
|
+
* Only which organization a store belongs to is remembered, up to 60 s per server process: a store
|
|
26
|
+
* that has just joined an organization may take that long to list its organization's grants.
|
|
27
|
+
*
|
|
28
|
+
* Needs `ReadCatalog` or `ReadSettings` on the store; without either the engine answers
|
|
29
|
+
* `FORBIDDEN` ({@link PermissionDeniedError}, 403). No channel argument: a tenant-scoped
|
|
30
|
+
* credential reads its own store only, and a SuperAdmin reads any store with that store as the
|
|
31
|
+
* tenant.
|
|
32
|
+
*/
|
|
33
|
+
export type TenantEntitlementsReader = (call?: Call) => Promise<TenantEntitlement[]>;
|
|
34
|
+
export declare const ACTIVE_PLUGIN_ENTITLEMENTS_QUERY = "\n query TenantEntitlements {\n activePluginEntitlements {\n pluginKey\n source\n }\n }\n";
|
|
35
|
+
export declare function createTenantEntitlementsReader(transport: Transport): TenantEntitlementsReader;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const ACTIVE_PLUGIN_ENTITLEMENTS_QUERY = /* GraphQL */ `
|
|
2
|
+
query TenantEntitlements {
|
|
3
|
+
activePluginEntitlements {
|
|
4
|
+
pluginKey
|
|
5
|
+
source
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
`;
|
|
9
|
+
export function createTenantEntitlementsReader(transport) {
|
|
10
|
+
return async (call) => {
|
|
11
|
+
const data = await transport.graphql({ api: 'admin', query: ACTIVE_PLUGIN_ENTITLEMENTS_QUERY }, call);
|
|
12
|
+
// Only the two fields this type promises, whatever else a newer engine adds to a row.
|
|
13
|
+
return data.activePluginEntitlements.map(({ pluginKey, source }) => ({ pluginKey, source }));
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* One field group's owner on the ACTIVE store: the app installation or personal API token the
|
|
5
|
+
* store handed it to. A store hands each of the four groups — `price` (Price), `stock` (Stock),
|
|
6
|
+
* `product_details` (Product details), `customer_details` (Customer details) — to at most one
|
|
7
|
+
* owner at a time.
|
|
8
|
+
*/
|
|
9
|
+
export interface FieldGroupOwnership {
|
|
10
|
+
/** `price` | `stock` | `product_details` | `customer_details`. */
|
|
11
|
+
group: string;
|
|
12
|
+
/** `app_installation` | `api_token`. A string on the wire: read an unknown value as neither. */
|
|
13
|
+
ownerKind: 'app_installation' | 'api_token' | (string & {});
|
|
14
|
+
/** The installation id or the API token id. */
|
|
15
|
+
ownerId: string;
|
|
16
|
+
/** The app's name, or the API token's name. */
|
|
17
|
+
label: string;
|
|
18
|
+
/** The app's slug for an installation; null for an API token. */
|
|
19
|
+
appSlug: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* True while the owner is suspended (the store suspended the installation, or the platform
|
|
22
|
+
* suspended the app): its claim stands, so no other app or token can be granted the group,
|
|
23
|
+
* but nothing is held for it until it resumes. Always false for an API token.
|
|
24
|
+
*/
|
|
25
|
+
paused: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A field group a writer built into the platform already writes into the active store, so it
|
|
29
|
+
* cannot be handed to an app or API token there (a grant naming it is refused
|
|
30
|
+
* `FIELD_GROUP_ERP_SYNCED`).
|
|
31
|
+
*/
|
|
32
|
+
export interface FieldGroupSyncSource {
|
|
33
|
+
/** `price` | `stock` | `product_details` | `customer_details`. */
|
|
34
|
+
group: string;
|
|
35
|
+
/**
|
|
36
|
+
* `exact`: Exact Online is connected and writes all four groups (disconnect it first).
|
|
37
|
+
* `organization`: the store belongs to an organization, which can share its catalogue into it
|
|
38
|
+
* at any time and then writes its prices and product details, so they stay with it whether or
|
|
39
|
+
* not it shares them today. `workspace`: the channel is an organization's workspace, whose
|
|
40
|
+
* prices and product details are the catalogue the member stores receive. A string on the
|
|
41
|
+
* wire.
|
|
42
|
+
*/
|
|
43
|
+
source: 'exact' | 'organization' | 'workspace' | (string & {});
|
|
44
|
+
/** `Exact Online`, or the organization's name (`organization` and `workspace`). */
|
|
45
|
+
label: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Every field group's owner on the ACTIVE store (`admin.tenant.fieldGroupOwners(call?)`), in
|
|
49
|
+
* group order. A group with no entry has no owner. Needs a permission that reads or changes a
|
|
50
|
+
* group's data (`ReadCatalog`, `ReadProduct`, `ReadCustomer`, `ReadSettings`, or a group's write
|
|
51
|
+
* permission), `InstallApps` or `ManageApiToken`; anything else is `FORBIDDEN`.
|
|
52
|
+
*/
|
|
53
|
+
export type FieldGroupOwnersReader = (call?: Call) => Promise<FieldGroupOwnership[]>;
|
|
54
|
+
/**
|
|
55
|
+
* The field groups a built-in writer already writes into the ACTIVE store
|
|
56
|
+
* (`admin.tenant.fieldGroupSyncSources(call?)`), one entry per group and writer. Same
|
|
57
|
+
* permissions as {@link FieldGroupOwnersReader}.
|
|
58
|
+
*/
|
|
59
|
+
export type FieldGroupSyncSourcesReader = (call?: Call) => Promise<FieldGroupSyncSource[]>;
|
|
60
|
+
export declare const FIELD_GROUP_OWNERSHIP_FIELDS = "\n group\n ownerKind\n ownerId\n label\n appSlug\n paused\n";
|
|
61
|
+
export declare const FIELD_GROUP_OWNERS_QUERY = "\n query FieldGroupOwners {\n fieldGroupOwners { \n group\n ownerKind\n ownerId\n label\n appSlug\n paused\n }\n }\n";
|
|
62
|
+
export declare const FIELD_GROUP_SYNC_SOURCES_QUERY = "\n query FieldGroupSyncSources {\n fieldGroupSyncSources {\n group\n source\n label\n }\n }\n";
|
|
63
|
+
export declare function createFieldGroupOwnersReader(transport: Transport): FieldGroupOwnersReader;
|
|
64
|
+
export declare function createFieldGroupSyncSourcesReader(transport: Transport): FieldGroupSyncSourcesReader;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const FIELD_GROUP_OWNERSHIP_FIELDS = /* GraphQL */ `
|
|
2
|
+
group
|
|
3
|
+
ownerKind
|
|
4
|
+
ownerId
|
|
5
|
+
label
|
|
6
|
+
appSlug
|
|
7
|
+
paused
|
|
8
|
+
`;
|
|
9
|
+
export const FIELD_GROUP_OWNERS_QUERY = /* GraphQL */ `
|
|
10
|
+
query FieldGroupOwners {
|
|
11
|
+
fieldGroupOwners { ${FIELD_GROUP_OWNERSHIP_FIELDS} }
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
export const FIELD_GROUP_SYNC_SOURCES_QUERY = /* GraphQL */ `
|
|
15
|
+
query FieldGroupSyncSources {
|
|
16
|
+
fieldGroupSyncSources {
|
|
17
|
+
group
|
|
18
|
+
source
|
|
19
|
+
label
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
export function createFieldGroupOwnersReader(transport) {
|
|
24
|
+
return async (call) => {
|
|
25
|
+
const data = await transport.graphql({ api: 'admin', query: FIELD_GROUP_OWNERS_QUERY }, call);
|
|
26
|
+
return data.fieldGroupOwners;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function createFieldGroupSyncSourcesReader(transport) {
|
|
30
|
+
return async (call) => {
|
|
31
|
+
const data = await transport.graphql({ api: 'admin', query: FIELD_GROUP_SYNC_SOURCES_QUERY }, call);
|
|
32
|
+
return data.fieldGroupSyncSources;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import { type LegalEntity, type UpdateLegalEntityInput } from '../platform/legal-entities.js';
|
|
3
|
+
import { type TenantLegalEntityNumberingModule } from '../platform/legal-entity-numbering.js';
|
|
4
|
+
import type { Call } from '../types.js';
|
|
5
|
+
/**
|
|
6
|
+
* `admin.tenant.legalEntity` — the ACTIVE store's legal entity: the company its invoices name
|
|
7
|
+
* (multi-region spec §7.1). Reading it needs `ReadSettings` on the store. Changing it needs
|
|
8
|
+
* `UpdateSeller` on EVERY store of the entity, because the entity prints on all of their invoices;
|
|
9
|
+
* a store sharing its entity with stores the caller does not run reads it with `editable: false`,
|
|
10
|
+
* and `update` is refused with `SHARED_SELLER_NOT_ALLOWED` (403).
|
|
11
|
+
*/
|
|
12
|
+
export interface LegalEntityModule {
|
|
13
|
+
/** Null on a store with no legal entity, an organization's workspace and the root channel. */
|
|
14
|
+
get(call?: Call): Promise<LegalEntity | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Changes the fields given and answers the entity. Its legal name, VAT, EORI and registration
|
|
17
|
+
* numbers are copied onto every one of its stores in the same transaction; the registered office
|
|
18
|
+
* and time zone are the entity's alone. Two requests: the active store's entity is read first,
|
|
19
|
+
* and a store with none is refused with `LEGAL_ENTITY_NOT_FOUND` (404) before anything is
|
|
20
|
+
* written. Refusals from the engine: `SHARED_SELLER_NOT_ALLOWED` (403),
|
|
21
|
+
* `LEGAL_ENTITY_FIELD_INVALID`, `INVALID_COUNTRY_CODE`, `INVALID_TIMEZONE` (400).
|
|
22
|
+
*/
|
|
23
|
+
update(input: UpdateLegalEntityInput, call?: Call): Promise<LegalEntity>;
|
|
24
|
+
/**
|
|
25
|
+
* The numbering series the active store's legal entity shares with its other stores (B6c):
|
|
26
|
+
* `get` needs `ReadSettings` on the store; `update` needs `UpdateSettings` on EVERY store of the
|
|
27
|
+
* entity (`SHARED_SELLER_NOT_ALLOWED`, 403).
|
|
28
|
+
*/
|
|
29
|
+
numbering: TenantLegalEntityNumberingModule;
|
|
30
|
+
}
|
|
31
|
+
export declare const ACTIVE_LEGAL_ENTITY_QUERY = "\n query ActiveLegalEntity {\n activeLegalEntity { \n id\n name\n legalName\n vatNumber\n vatNumberUk\n eoriNumber\n registrationNumber\n registeredAddressLine1\n registeredAddressLine2\n registeredCity\n registeredPostalCode\n registeredProvince\n registeredCountry\n timezone\n stores {\n id\n code\n }\n storeCount\n editable\n createdAt\n updatedAt\n }\n }\n";
|
|
32
|
+
export declare function createLegalEntityModule(transport: Transport): LegalEntityModule;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { NotFoundError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { LEGAL_ENTITY_FIELDS, legalEntityInputOf, UPDATE_LEGAL_ENTITY_MUTATION, } from '../platform/legal-entities.js';
|
|
3
|
+
import { createTenantLegalEntityNumberingModule, } from '../platform/legal-entity-numbering.js';
|
|
4
|
+
export const ACTIVE_LEGAL_ENTITY_QUERY = /* GraphQL */ `
|
|
5
|
+
query ActiveLegalEntity {
|
|
6
|
+
activeLegalEntity { ${LEGAL_ENTITY_FIELDS} }
|
|
7
|
+
}
|
|
8
|
+
`;
|
|
9
|
+
const ACTIVE_LEGAL_ENTITY_ID_QUERY = /* GraphQL */ `
|
|
10
|
+
query ActiveLegalEntityId {
|
|
11
|
+
activeLegalEntity { id }
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
export function createLegalEntityModule(transport) {
|
|
15
|
+
return {
|
|
16
|
+
numbering: createTenantLegalEntityNumberingModule(transport),
|
|
17
|
+
async get(call) {
|
|
18
|
+
const data = await transport.graphql({ api: 'admin', query: ACTIVE_LEGAL_ENTITY_QUERY }, call);
|
|
19
|
+
return data.activeLegalEntity;
|
|
20
|
+
},
|
|
21
|
+
async update(input, call) {
|
|
22
|
+
const active = await transport.graphql({ api: 'admin', query: ACTIVE_LEGAL_ENTITY_ID_QUERY }, call);
|
|
23
|
+
const id = active.activeLegalEntity?.id;
|
|
24
|
+
if (!id) {
|
|
25
|
+
throw new NotFoundError({
|
|
26
|
+
code: 'LEGAL_ENTITY_NOT_FOUND',
|
|
27
|
+
message: 'The active store has no legal entity to change.',
|
|
28
|
+
status: 404,
|
|
29
|
+
service: 'vendure',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const { name: _ignored, ...fields } = input;
|
|
33
|
+
const data = await transport.graphql({
|
|
34
|
+
api: 'admin',
|
|
35
|
+
query: UPDATE_LEGAL_ENTITY_MUTATION,
|
|
36
|
+
variables: { id, input: legalEntityInputOf(fields) },
|
|
37
|
+
}, call);
|
|
38
|
+
return data.updateLegalEntity;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Who sends a notification event's email for this store (headless spec §8.15).
|
|
5
|
+
*
|
|
6
|
+
* - `platform` — the platform renders and sends it. The default.
|
|
7
|
+
* - `none` — the platform does not. Your own system sends something in its
|
|
8
|
+
* place, triggered by the matching platform event (`admin.events`; the
|
|
9
|
+
* entry's `replacementEventTypes` names it), and every
|
|
10
|
+
* dispatch the platform skips is still recorded on the store's email delivery
|
|
11
|
+
* log (status `skipped`, `metadata.skipReason: 'SENDER_NONE'`), so the
|
|
12
|
+
* silence is auditable.
|
|
13
|
+
*
|
|
14
|
+
* The merchant-facing labels are `admin.meta.catalog().labels.notificationSender`.
|
|
15
|
+
*/
|
|
16
|
+
export type NotificationSender = 'platform' | 'none';
|
|
17
|
+
/**
|
|
18
|
+
* One piece of a recipient field: a literal address (`text`) or a variable the
|
|
19
|
+
* platform resolves per email (`var`, e.g. `customer_email`). A field's segments
|
|
20
|
+
* are joined into one comma-separated list at send time.
|
|
21
|
+
*/
|
|
22
|
+
export type NotificationRecipientSegment = {
|
|
23
|
+
type: 'text';
|
|
24
|
+
value: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'var';
|
|
27
|
+
key: string;
|
|
28
|
+
};
|
|
29
|
+
export interface NotificationRecipients {
|
|
30
|
+
to: NotificationRecipientSegment[];
|
|
31
|
+
cc: NotificationRecipientSegment[];
|
|
32
|
+
bcc: NotificationRecipientSegment[];
|
|
33
|
+
}
|
|
34
|
+
/** One notification event's configuration on the active store. */
|
|
35
|
+
export interface NotificationEventConfig {
|
|
36
|
+
id: string;
|
|
37
|
+
/** The notification event key — `order_placed`, `magic_link_login`, … */
|
|
38
|
+
event: string;
|
|
39
|
+
/**
|
|
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.
|
|
43
|
+
*/
|
|
44
|
+
enabled: boolean;
|
|
45
|
+
sender: NotificationSender;
|
|
46
|
+
recipients: NotificationRecipients;
|
|
47
|
+
/** The email template the platform renders for it. */
|
|
48
|
+
templateType: string;
|
|
49
|
+
label: string;
|
|
50
|
+
description: string;
|
|
51
|
+
/** `order`, `customer`, `account` (the identity emails), `product` or `operational`. */
|
|
52
|
+
category: string;
|
|
53
|
+
/**
|
|
54
|
+
* The platform event types your system sends this email from when `sender`
|
|
55
|
+
* is `none` — `['order.created']` for `order_placed`. `null` when no event
|
|
56
|
+
* the platform emits replaces it; `sender: 'none'` is then refused.
|
|
57
|
+
*/
|
|
58
|
+
replacementEventTypes: string[] | null;
|
|
59
|
+
}
|
|
60
|
+
export interface UpdateNotificationEventConfigInput {
|
|
61
|
+
/**
|
|
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.
|
|
68
|
+
*/
|
|
69
|
+
enabled?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* `none` is refused on the identity emails — the `account` category:
|
|
72
|
+
* 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).
|
|
77
|
+
*
|
|
78
|
+
* `none` is also refused on an email whose `replacementEventTypes` is `null`,
|
|
79
|
+
* with `NO_REPLACEMENT_EVENT` ({@link ValidationError}): no platform event is
|
|
80
|
+
* emitted when it is due, so your system would never know to send it.
|
|
81
|
+
*/
|
|
82
|
+
sender?: NotificationSender;
|
|
83
|
+
/**
|
|
84
|
+
* Each list REPLACES that field's segments; an omitted list is left as it is.
|
|
85
|
+
*
|
|
86
|
+
* The identity emails (the `account` category) and `new_customer` carry sign-in
|
|
87
|
+
* links or codes, and one login serves the person in every store, so they go to
|
|
88
|
+
* the account holder alone: `to` is the `customer_email` variable, `cc` and
|
|
89
|
+
* `bcc` are empty, and `list` answers exactly that whatever was stored before.
|
|
90
|
+
* Any other list on them is refused with `IDENTITY_EMAIL_RECIPIENTS_FIXED`
|
|
91
|
+
* ({@link ValidationError}); resending the lists `list` answered is accepted.
|
|
92
|
+
*/
|
|
93
|
+
recipients?: Partial<NotificationRecipients>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The store's transactional emails (`admin.tenant.notifications`) — per event:
|
|
97
|
+
* on or off, who sends it, and to whom.
|
|
98
|
+
*
|
|
99
|
+
* Always the ACTIVE channel; no channel argument. Reading and writing both need
|
|
100
|
+
* `UpdateSettings`. The list is small and fixed (one entry per notification
|
|
101
|
+
* event the platform knows), so it is a plain array rather than a page.
|
|
102
|
+
*
|
|
103
|
+
* Every accepted update puts ONE `notification_config.changed` on the event
|
|
104
|
+
* stream with the resulting `enabled` and `sender` (never the recipients), so a
|
|
105
|
+
* head that sends some emails itself can start or stop without polling.
|
|
106
|
+
*
|
|
107
|
+
* **A store that takes `settings.email` from its organization**
|
|
108
|
+
* (`admin.tenant.settingsSources`: locked, or inheritable and not overridden)
|
|
109
|
+
* sends the emails the platform sends for it by the organization's `enabled`
|
|
110
|
+
* and recipient lists, or by the platform's default for an event the
|
|
111
|
+
* organization has none for. `sender` stays the store's own, and an email
|
|
112
|
+
* your system already sends (`sender: 'none'`) keeps the store's own entry
|
|
113
|
+
* whole: the organization never changes what your head must do. `list`
|
|
114
|
+
* answers what applies (a stable `id` also for an event the store holds no
|
|
115
|
+
* entry of its own for), and `update` refuses any change, `sender` included;
|
|
116
|
+
* resending an entry as `list` answered it is accepted and changes nothing.
|
|
117
|
+
* `override('settings.email')` first to change them. The From address stays
|
|
118
|
+
* the store's own gateway's.
|
|
119
|
+
*
|
|
120
|
+
* **Coded refusals**, GraphQL errors mapped by `core`:
|
|
121
|
+
* - `EVENT_ALWAYS_PLATFORM_SENT` → {@link ValidationError}: `sender: 'none'` on
|
|
122
|
+
* an identity email. The message names the event; `details[0].field` is
|
|
123
|
+
* `/sender`.
|
|
124
|
+
* - `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`.
|
|
127
|
+
* - `NO_REPLACEMENT_EVENT` → {@link ValidationError}: `sender: 'none'` on an
|
|
128
|
+
* email no emitted platform event replaces (`replacementEventTypes: null`).
|
|
129
|
+
* The message names the event, and the event planned to replace it when one
|
|
130
|
+
* is; `details[0].field` is `/sender`.
|
|
131
|
+
* - `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
|
|
134
|
+
* (`/recipientTo`, `/recipientCc`, `/recipientBcc`).
|
|
135
|
+
* - `INVALID_NOTIFICATION_SENDER` → {@link ValidationError}.
|
|
136
|
+
* - `NOTIFICATION_EVENT_NOT_FOUND` → {@link NotFoundError}: no such event key.
|
|
137
|
+
* - `ORGANIZATION_SETTINGS_INHERITED` → {@link BusinessRuleError} (422): the
|
|
138
|
+
* store inherits `settings.email` from its organization; override it first.
|
|
139
|
+
* - `MANAGED_BY_ORGANIZATION` → {@link BusinessRuleError} (422): the
|
|
140
|
+
* organization locked `settings.email`.
|
|
141
|
+
*/
|
|
142
|
+
export interface NotificationsModule {
|
|
143
|
+
list(options?: Call): Promise<NotificationEventConfig[]>;
|
|
144
|
+
update(event: string, input: UpdateNotificationEventConfigInput, options?: Call): Promise<NotificationEventConfig>;
|
|
145
|
+
}
|
|
146
|
+
export declare const NOTIFICATION_CONFIGS_QUERY = "\n query NotificationEventConfigs {\n notificationEventConfigs { \n id\n event\n enabled\n sender\n recipientTo { type value key }\n recipientCc { type value key }\n recipientBcc { type value key }\n templateType\n label\n description\n category\n replacementEventTypes\n }\n }\n";
|
|
147
|
+
export declare const UPDATE_NOTIFICATION_CONFIG_MUTATION = "\n mutation UpdateNotificationEventConfig(\n $event: String!\n $input: UpdateNotificationEventConfigInput!\n ) {\n updateNotificationEventConfig(event: $event, input: $input) { \n id\n event\n enabled\n sender\n recipientTo { type value key }\n recipientCc { type value key }\n recipientBcc { type value key }\n templateType\n label\n description\n category\n replacementEventTypes\n }\n }\n";
|
|
148
|
+
export declare function createNotificationsModule(transport: Transport): NotificationsModule;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const CONFIG_FIELDS = /* GraphQL */ `
|
|
2
|
+
id
|
|
3
|
+
event
|
|
4
|
+
enabled
|
|
5
|
+
sender
|
|
6
|
+
recipientTo { type value key }
|
|
7
|
+
recipientCc { type value key }
|
|
8
|
+
recipientBcc { type value key }
|
|
9
|
+
templateType
|
|
10
|
+
label
|
|
11
|
+
description
|
|
12
|
+
category
|
|
13
|
+
replacementEventTypes
|
|
14
|
+
`;
|
|
15
|
+
export const NOTIFICATION_CONFIGS_QUERY = /* GraphQL */ `
|
|
16
|
+
query NotificationEventConfigs {
|
|
17
|
+
notificationEventConfigs { ${CONFIG_FIELDS} }
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
export const UPDATE_NOTIFICATION_CONFIG_MUTATION = /* GraphQL */ `
|
|
21
|
+
mutation UpdateNotificationEventConfig(
|
|
22
|
+
$event: String!
|
|
23
|
+
$input: UpdateNotificationEventConfigInput!
|
|
24
|
+
) {
|
|
25
|
+
updateNotificationEventConfig(event: $event, input: $input) { ${CONFIG_FIELDS} }
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
function fromWireSegment(segment) {
|
|
29
|
+
// The server accepts any `type`; only null members are dropped, never a value.
|
|
30
|
+
return {
|
|
31
|
+
type: segment.type,
|
|
32
|
+
...(segment.value != null ? { value: segment.value } : {}),
|
|
33
|
+
...(segment.key != null ? { key: segment.key } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function fromWire(config) {
|
|
37
|
+
return {
|
|
38
|
+
id: config.id,
|
|
39
|
+
event: config.event,
|
|
40
|
+
enabled: config.enabled,
|
|
41
|
+
sender: config.sender,
|
|
42
|
+
recipients: {
|
|
43
|
+
to: config.recipientTo.map(fromWireSegment),
|
|
44
|
+
cc: config.recipientCc.map(fromWireSegment),
|
|
45
|
+
bcc: config.recipientBcc.map(fromWireSegment),
|
|
46
|
+
},
|
|
47
|
+
templateType: config.templateType,
|
|
48
|
+
label: config.label,
|
|
49
|
+
description: config.description,
|
|
50
|
+
category: config.category,
|
|
51
|
+
replacementEventTypes: config.replacementEventTypes ?? null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function toWireInput(input) {
|
|
55
|
+
const { recipients } = input;
|
|
56
|
+
return {
|
|
57
|
+
...(input.enabled !== undefined ? { enabled: input.enabled } : {}),
|
|
58
|
+
...(input.sender !== undefined ? { sender: input.sender } : {}),
|
|
59
|
+
...(recipients?.to !== undefined ? { recipientTo: recipients.to } : {}),
|
|
60
|
+
...(recipients?.cc !== undefined ? { recipientCc: recipients.cc } : {}),
|
|
61
|
+
...(recipients?.bcc !== undefined ? { recipientBcc: recipients.bcc } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function createNotificationsModule(transport) {
|
|
65
|
+
return {
|
|
66
|
+
async list(options) {
|
|
67
|
+
const data = await transport.graphql({ api: 'admin', query: NOTIFICATION_CONFIGS_QUERY }, options);
|
|
68
|
+
return data.notificationEventConfigs.map(fromWire);
|
|
69
|
+
},
|
|
70
|
+
async update(event, input, options) {
|
|
71
|
+
const data = await transport.graphql({
|
|
72
|
+
api: 'admin',
|
|
73
|
+
query: UPDATE_NOTIFICATION_CONFIG_MUTATION,
|
|
74
|
+
variables: { event, input: toWireInput(input) },
|
|
75
|
+
}, options);
|
|
76
|
+
return fromWire(data.updateNotificationEventConfig);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -19,7 +19,9 @@ export interface ChannelRetention {
|
|
|
19
19
|
retentionClass: string;
|
|
20
20
|
/**
|
|
21
21
|
* A time-based hold, independent of {@link holds}. While it is in the future
|
|
22
|
-
* the store cannot be hard-deleted, with no holder involved.
|
|
22
|
+
* the store cannot be hard-deleted, with no holder involved. It does NOT
|
|
23
|
+
* refuse a customer erasure (`customers.erase`): the window protects the
|
|
24
|
+
* store's records, which an erasure keeps. To block erasures, place a hold.
|
|
23
25
|
*/
|
|
24
26
|
holdUntil: string | null;
|
|
25
27
|
holds: RetentionHold[];
|
|
@@ -44,8 +46,10 @@ export interface SetRetentionInput {
|
|
|
44
46
|
* **Coded refusals**, all delivered as GraphQL errors with `extensions.code`
|
|
45
47
|
* and mapped by `core` onto the error hierarchy:
|
|
46
48
|
* - `RETENTION_HOLD_ACTIVE` → {@link BusinessRuleError}: `hardDeleteChannel`
|
|
47
|
-
* refused because a hold
|
|
48
|
-
*
|
|
49
|
+
* refused because a hold or the `holdUntil` window stands, or
|
|
50
|
+
* `customers.erase` refused because a named hold stands (the window alone
|
|
51
|
+
* does not refuse an erasure). It is thrown by the delete or the erasure,
|
|
52
|
+
* not by these methods.
|
|
49
53
|
* - `RETENTION_HOLD_NOT_YOURS` → {@link PermissionDeniedError}: `release()`
|
|
50
54
|
* called by a principal that holds none while another's hold stands. A
|
|
51
55
|
* SuperAdmin overrides and releases every hold.
|
|
@@ -63,7 +67,9 @@ export interface RetentionModule {
|
|
|
63
67
|
set(input: SetRetentionInput, options?: Call): Promise<ChannelRetention>;
|
|
64
68
|
/**
|
|
65
69
|
* Place the CALLING principal's hold. Idempotent per holder: restating
|
|
66
|
-
* updates the reason and leaves `placedAt` where it was.
|
|
70
|
+
* updates the reason and leaves `placedAt` where it was. While it stands,
|
|
71
|
+
* both a hard delete of the store and every customer erasure on it
|
|
72
|
+
* (`customers.erase`) are refused with `RETENTION_HOLD_ACTIVE`.
|
|
67
73
|
*/
|
|
68
74
|
hold(reason: string, options?: Call): Promise<ChannelRetention>;
|
|
69
75
|
/**
|