@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,103 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import { type AssetFile } from '../catalog/assets.js';
|
|
3
|
+
import type { Call } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The settings groups a member store can take from its organization (multi-region spec §5.3), in
|
|
6
|
+
* the engine's catalog order. The catalog only grows, so a newer engine may answer a group this
|
|
7
|
+
* SDK does not name yet: every field and argument that carries a group also accepts any string.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SETTINGS_GROUPS: readonly ["settings.email", "settings.branding", "settings.returns", "settings.reviews", "settings.quotes", "settings.age_verification", "settings.tax_display", "settings.numbering_formats", "settings.seo"];
|
|
10
|
+
export type SettingsGroup = (typeof SETTINGS_GROUPS)[number];
|
|
11
|
+
/** Whose values a group reads: the store's own, or its organization's. */
|
|
12
|
+
export type SettingsSourceKind = 'channel' | 'organization';
|
|
13
|
+
/**
|
|
14
|
+
* Where one settings group of the active store comes from.
|
|
15
|
+
*
|
|
16
|
+
* The three states a settings page shows:
|
|
17
|
+
* - **Managed by the organization** — `locked`. The store reads the organization's values and
|
|
18
|
+
* cannot keep its own; `override` and `inherit` are refused.
|
|
19
|
+
* - **Inherited from the organization** — `inheritable && !overridden`. `override` makes the
|
|
20
|
+
* store keep its own values, starting from a copy of the organization's.
|
|
21
|
+
* - **Overridden** — `inheritable && overridden`. `inherit` goes back to the organization's.
|
|
22
|
+
*
|
|
23
|
+
* Anything else (`!inheritable && !locked`) is the store's own setting with no organization
|
|
24
|
+
* behind it: a store outside any organization, or a group its organization does not share.
|
|
25
|
+
*/
|
|
26
|
+
export interface ChannelSettingsSource {
|
|
27
|
+
group: SettingsGroup | (string & {});
|
|
28
|
+
/** A string on the wire: a newer engine may answer a source this SDK does not name yet. */
|
|
29
|
+
source: SettingsSourceKind | (string & {});
|
|
30
|
+
/** The organization locked the group: the store reads its values and cannot keep its own. */
|
|
31
|
+
locked: boolean;
|
|
32
|
+
/** The store keeps its own values for the group. A lock outranks it. */
|
|
33
|
+
overridden: boolean;
|
|
34
|
+
/** The organization shares the group without locking it, so the store may inherit or override. */
|
|
35
|
+
inheritable: boolean;
|
|
36
|
+
/** The store's organization. Null for a store outside any organization. */
|
|
37
|
+
organizationName: string | null;
|
|
38
|
+
/**
|
|
39
|
+
* The group's effective values, keyed by channel custom field name, read from the channel
|
|
40
|
+
* `source` names. Null for `settings.email` and `settings.numbering_formats`, which live
|
|
41
|
+
* outside the channel: their own reads (`tenant.notifications`, the numbering configs) already
|
|
42
|
+
* answer the effective values.
|
|
43
|
+
*/
|
|
44
|
+
values: Record<string, unknown> | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `admin.tenant.settingsSources` — where each settings group of the ACTIVE store comes from, and
|
|
48
|
+
* the store's choice to keep its own values for a group (`override`) or take its organization's
|
|
49
|
+
* (`inherit`). No channel argument: a tenant-scoped credential reaches its own store only. A
|
|
50
|
+
* SuperAdmin reads any store the same way, with that store as the tenant.
|
|
51
|
+
*
|
|
52
|
+
* `list` needs any permission on the store beyond `Authenticated`. `override` and `inherit` need
|
|
53
|
+
* `UpdateChannel`, or `UpdateSettings` for `settings.email` and `settings.numbering_formats`. Both
|
|
54
|
+
* answer the group's state after the call and are safe to repeat: a second call changes nothing.
|
|
55
|
+
* They are Admin API mutations, so an idempotency key deduplicates nothing.
|
|
56
|
+
*
|
|
57
|
+
* **Coded refusals**, GraphQL errors mapped by `core`:
|
|
58
|
+
* - `MANAGED_BY_ORGANIZATION` → {@link BusinessRuleError} (422): the organization locked the
|
|
59
|
+
* group. SuperAdmins are refused too.
|
|
60
|
+
* - `ORGANIZATION_SETTINGS_NOT_A_MEMBER` → {@link BusinessRuleError} (422): the store is in no
|
|
61
|
+
* organization, or it is an organization's own workspace.
|
|
62
|
+
* - `ORGANIZATION_SETTINGS_GROUP_INVALID` → {@link ValidationError} (400): not a group the engine
|
|
63
|
+
* knows. A blank group is refused the same way before anything is sent.
|
|
64
|
+
* - `FORBIDDEN` → {@link PermissionDeniedError} (403): the credential lacks the permission.
|
|
65
|
+
*
|
|
66
|
+
* While a store inherits a group, `override` it before changing its values. A platform release
|
|
67
|
+
* that guards settings writes refuses a write to an inherited group (for example `updateChannel`
|
|
68
|
+
* for `settings.returns`) with `ORGANIZATION_SETTINGS_INHERITED` ({@link BusinessRuleError},
|
|
69
|
+
* 422). An engine without that guard accepts the write, but the group still resolves to the
|
|
70
|
+
* organization's values.
|
|
71
|
+
*/
|
|
72
|
+
export interface SettingsSourcesModule {
|
|
73
|
+
/** Every settings group of the active store, in catalog order. */
|
|
74
|
+
list(call?: Call): Promise<ChannelSettingsSource[]>;
|
|
75
|
+
/**
|
|
76
|
+
* The store keeps its own values for `group` from now on. When it inherited the group, it
|
|
77
|
+
* starts from a copy of the organization's current values, so nothing changes for its
|
|
78
|
+
* customers. On a group the organization does not share, it only records the choice: the store
|
|
79
|
+
* keeps its values if the organization starts sharing the group later.
|
|
80
|
+
*/
|
|
81
|
+
override(group: SettingsGroup | (string & {}), call?: Call): Promise<ChannelSettingsSource>;
|
|
82
|
+
/**
|
|
83
|
+
* The store goes back to its organization's values for `group`. Its own values stay stored but
|
|
84
|
+
* unused; a later `override` starts from a fresh copy of the organization's, not from them.
|
|
85
|
+
*/
|
|
86
|
+
inherit(group: SettingsGroup | (string & {}), call?: Call): Promise<ChannelSettingsSource>;
|
|
87
|
+
/**
|
|
88
|
+
* The assets `group` uses for the active store, such as the logos and favicon of
|
|
89
|
+
* `settings.branding`, read from the channel its values come from. While the store inherits
|
|
90
|
+
* the group those are its organization's images, which the store's own library does not hold
|
|
91
|
+
* (`catalog.assets.get` answers null for them). Only assets the group's values name, in no
|
|
92
|
+
* particular order; empty for a group without images. Same permission as `list`, and an
|
|
93
|
+
* unknown group is refused like `override`'s. They carry no `catalogueOwnership`: a settings
|
|
94
|
+
* image is never edited from here, and leaving it out keeps this read working against an
|
|
95
|
+
* engine older than that field.
|
|
96
|
+
*/
|
|
97
|
+
assets(group: SettingsGroup | (string & {}), call?: Call): Promise<AssetFile[]>;
|
|
98
|
+
}
|
|
99
|
+
export declare const CHANNEL_SETTINGS_SOURCES_QUERY = "\n query ChannelSettingsSources {\n channelSettingsSources { \n group\n source\n locked\n overridden\n inheritable\n organizationName\n values\n }\n }\n";
|
|
100
|
+
export declare const CHANNEL_SETTINGS_ASSETS_QUERY = "\n query ChannelSettingsAssets($group: String!) {\n channelSettingsAssets(group: $group) { \n id\n createdAt\n updatedAt\n name\n type\n mimeType\n fileSize\n width\n height\n source\n preview\n focalPoint {\n x\n y\n }\n tags {\n value\n }\n }\n }\n";
|
|
101
|
+
export declare const OVERRIDE_ORGANIZATION_SETTINGS_MUTATION = "\n mutation OverrideOrganizationSettings($group: String!) {\n overrideOrganizationSettings(group: $group) { \n group\n source\n locked\n overridden\n inheritable\n organizationName\n values\n }\n }\n";
|
|
102
|
+
export declare const INHERIT_ORGANIZATION_SETTINGS_MUTATION = "\n mutation InheritOrganizationSettings($group: String!) {\n inheritOrganizationSettings(group: $group) { \n group\n source\n locked\n overridden\n inheritable\n organizationName\n values\n }\n }\n";
|
|
103
|
+
export declare function createSettingsSourcesModule(transport: Transport): SettingsSourcesModule;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ValidationError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { ASSET_FILE_FIELDS, toAsset } from '../catalog/assets.js';
|
|
3
|
+
/**
|
|
4
|
+
* The settings groups a member store can take from its organization (multi-region spec §5.3), in
|
|
5
|
+
* the engine's catalog order. The catalog only grows, so a newer engine may answer a group this
|
|
6
|
+
* SDK does not name yet: every field and argument that carries a group also accepts any string.
|
|
7
|
+
*/
|
|
8
|
+
export const SETTINGS_GROUPS = [
|
|
9
|
+
'settings.email',
|
|
10
|
+
'settings.branding',
|
|
11
|
+
'settings.returns',
|
|
12
|
+
'settings.reviews',
|
|
13
|
+
'settings.quotes',
|
|
14
|
+
'settings.age_verification',
|
|
15
|
+
'settings.tax_display',
|
|
16
|
+
'settings.numbering_formats',
|
|
17
|
+
'settings.seo',
|
|
18
|
+
];
|
|
19
|
+
const SOURCE_FIELDS = /* GraphQL */ `
|
|
20
|
+
group
|
|
21
|
+
source
|
|
22
|
+
locked
|
|
23
|
+
overridden
|
|
24
|
+
inheritable
|
|
25
|
+
organizationName
|
|
26
|
+
values
|
|
27
|
+
`;
|
|
28
|
+
export const CHANNEL_SETTINGS_SOURCES_QUERY = /* GraphQL */ `
|
|
29
|
+
query ChannelSettingsSources {
|
|
30
|
+
channelSettingsSources { ${SOURCE_FIELDS} }
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
export const CHANNEL_SETTINGS_ASSETS_QUERY = /* GraphQL */ `
|
|
34
|
+
query ChannelSettingsAssets($group: String!) {
|
|
35
|
+
channelSettingsAssets(group: $group) { ${ASSET_FILE_FIELDS} }
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
export const OVERRIDE_ORGANIZATION_SETTINGS_MUTATION = /* GraphQL */ `
|
|
39
|
+
mutation OverrideOrganizationSettings($group: String!) {
|
|
40
|
+
overrideOrganizationSettings(group: $group) { ${SOURCE_FIELDS} }
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
export const INHERIT_ORGANIZATION_SETTINGS_MUTATION = /* GraphQL */ `
|
|
44
|
+
mutation InheritOrganizationSettings($group: String!) {
|
|
45
|
+
inheritOrganizationSettings(group: $group) { ${SOURCE_FIELDS} }
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
/**
|
|
49
|
+
* Only the shape is checked here: which groups exist is the engine's call, so a group this SDK
|
|
50
|
+
* does not name still goes out. A blank one would come back `ORGANIZATION_SETTINGS_GROUP_INVALID`
|
|
51
|
+
* anyway (and a missing one as a GraphQL validation failure), so it is refused with that code now.
|
|
52
|
+
*/
|
|
53
|
+
function requireGroup(group) {
|
|
54
|
+
if (typeof group === 'string' && group.trim() !== '')
|
|
55
|
+
return group;
|
|
56
|
+
const code = 'ORGANIZATION_SETTINGS_GROUP_INVALID';
|
|
57
|
+
const message = `A settings group is required. Use one of: ${SETTINGS_GROUPS.join(', ')}.`;
|
|
58
|
+
throw new ValidationError({
|
|
59
|
+
code,
|
|
60
|
+
message,
|
|
61
|
+
status: 400,
|
|
62
|
+
service: 'vendure',
|
|
63
|
+
details: [{ field: 'group', code, message }],
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function createSettingsSourcesModule(transport) {
|
|
67
|
+
const write = async (field, query, group, call) => {
|
|
68
|
+
const variables = { group: requireGroup(group) };
|
|
69
|
+
const data = await transport.graphql({ api: 'admin', query, variables }, call);
|
|
70
|
+
return data[field];
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
async list(call) {
|
|
74
|
+
const data = await transport.graphql({ api: 'admin', query: CHANNEL_SETTINGS_SOURCES_QUERY }, call);
|
|
75
|
+
return data.channelSettingsSources;
|
|
76
|
+
},
|
|
77
|
+
override(group, call) {
|
|
78
|
+
return write('overrideOrganizationSettings', OVERRIDE_ORGANIZATION_SETTINGS_MUTATION, group, call);
|
|
79
|
+
},
|
|
80
|
+
inherit(group, call) {
|
|
81
|
+
return write('inheritOrganizationSettings', INHERIT_ORGANIZATION_SETTINGS_MUTATION, group, call);
|
|
82
|
+
},
|
|
83
|
+
async assets(group, call) {
|
|
84
|
+
const data = await transport.graphql({
|
|
85
|
+
api: 'admin',
|
|
86
|
+
query: CHANNEL_SETTINGS_ASSETS_QUERY,
|
|
87
|
+
variables: { group: requireGroup(group) },
|
|
88
|
+
}, call);
|
|
89
|
+
return data.channelSettingsAssets.map((wire) => toAsset(wire));
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,11 +6,19 @@ export interface Call {
|
|
|
6
6
|
* an ARGUMENT — the Admin API has no header for it — and only the mutations
|
|
7
7
|
* that declare one act on it. Today those are the money mutations
|
|
8
8
|
* `payments.record`, `orders.park`, `orders.settle` and all three
|
|
9
|
-
* `orders.surcharges.*`, plus `payments.providers.completeOnboarding
|
|
10
|
-
* `payments.providers.terminals.provision
|
|
9
|
+
* `orders.surcharges.*`, plus `payments.providers.completeOnboarding`,
|
|
10
|
+
* `payments.providers.terminals.provision`, `stock.adjust`,
|
|
11
|
+
* `hooks.assignments.assign`, `customers.upsertByExternalRef`,
|
|
12
|
+
* `customers.groups.replaceMembers`, `pricing.coupons.apply` / `remove`,
|
|
13
|
+
* `orders.setLinePrices` / `setOneTimeDiscounts`,
|
|
14
|
+
* `pos.sale.applyCouponCode` / `removeCouponCode`, `storedValue.issue` / `adjust` /
|
|
15
|
+
* `redeem`, `apps.releaseFieldGroup`, `catalog.products.bulk` and `orders.bulk` (both mint
|
|
16
|
+
* one when omitted), and `catalog.products.save`. Repeating one under the same key
|
|
11
17
|
* returns the first call's result instead of moving the money again; a
|
|
12
|
-
* DIFFERENT request under the same key is refused with
|
|
13
|
-
* `IDEMPOTENCY_CONFLICT
|
|
18
|
+
* DIFFERENT request under the same key is refused with a `ConflictError`
|
|
19
|
+
* (409) coded `IDEMPOTENCY_CONFLICT`, and a repeat that arrives while the
|
|
20
|
+
* first call is still running with one coded `IDEMPOTENCY_IN_PROGRESS` —
|
|
21
|
+
* the same classes the REST services answer.
|
|
14
22
|
*
|
|
15
23
|
* Two deliberate exceptions on that list. `customers.erase` takes a key but
|
|
16
24
|
* RECORDS it for provenance rather than keying on it: that mutation is
|
|
@@ -46,16 +54,26 @@ export interface ListOptions extends Call {
|
|
|
46
54
|
after?: string;
|
|
47
55
|
limit?: number;
|
|
48
56
|
}
|
|
57
|
+
/** One channel the signed-in principal holds a role on. */
|
|
58
|
+
export interface MeChannel {
|
|
59
|
+
id: string;
|
|
60
|
+
code: string;
|
|
61
|
+
token: string;
|
|
62
|
+
permissions: string[];
|
|
63
|
+
/**
|
|
64
|
+
* `store` or `hub` — an organization's workspace, which is never a store. A
|
|
65
|
+
* string on the wire: read any value other than `hub` as a store. The
|
|
66
|
+
* bootstrap root (`__default_channel__`) reads `store`; tell it by its code.
|
|
67
|
+
*/
|
|
68
|
+
channelKind: 'store' | 'hub' | (string & {});
|
|
69
|
+
/** The organization a store belongs to, or whose workspace a hub is; null for neither. */
|
|
70
|
+
organizationId: string | null;
|
|
71
|
+
}
|
|
49
72
|
/** The signed-in principal, as the Admin API reports it. */
|
|
50
73
|
export interface Me {
|
|
51
74
|
id: string;
|
|
52
75
|
identifier: string;
|
|
53
|
-
channels:
|
|
54
|
-
id: string;
|
|
55
|
-
code: string;
|
|
56
|
-
token: string;
|
|
57
|
-
permissions: string[];
|
|
58
|
-
}>;
|
|
76
|
+
channels: MeChannel[];
|
|
59
77
|
administrator: {
|
|
60
78
|
id: string;
|
|
61
79
|
firstName: string;
|
|
@@ -85,6 +103,15 @@ export interface WhoAmI {
|
|
|
85
103
|
id: string;
|
|
86
104
|
slug: string;
|
|
87
105
|
name: string;
|
|
106
|
+
/**
|
|
107
|
+
* The customer-data level this installation reads at — its approval,
|
|
108
|
+
* lowered to what the app declares now (a raise waits for the merchant's
|
|
109
|
+
* approval; a drop applies at once). Below `full` the Admin API blanks the
|
|
110
|
+
* customer fields it withholds (see `Customer.redacted`) and refuses a
|
|
111
|
+
* filter, sort or search on them (`CUSTOMER_DATA_NOT_GRANTED`). Absent from
|
|
112
|
+
* a server that predates it.
|
|
113
|
+
*/
|
|
114
|
+
customerData?: 'none' | 'identity' | 'full';
|
|
88
115
|
} | null;
|
|
89
116
|
/** Present only for `kind: 'app_installation'`; null otherwise. */
|
|
90
117
|
installation: {
|
|
@@ -122,6 +149,13 @@ export interface WhoAmI {
|
|
|
122
149
|
locations: string[] | null;
|
|
123
150
|
/** Effective permissions on the ACTIVE channel, not the union of all of them. */
|
|
124
151
|
permissions: string[];
|
|
152
|
+
/**
|
|
153
|
+
* The field groups this credential owns on its store (`price`, `stock`, `product_details`,
|
|
154
|
+
* `customer_details`): an API token's or an app installation's. Null for a human session,
|
|
155
|
+
* which never owns one, and absent or null from a server that predates it. While a group is
|
|
156
|
+
* owned, other writers will be refused it.
|
|
157
|
+
*/
|
|
158
|
+
fieldOwnership?: string[] | null;
|
|
125
159
|
/** Connected plugin-catalogue keys for the active channel. */
|
|
126
160
|
entitlements: string[];
|
|
127
161
|
/** Reserved for per-token rate limits; always null until they exist. */
|
|
@@ -153,12 +187,40 @@ export interface ApiToken {
|
|
|
153
187
|
lastUsedAt: string | null;
|
|
154
188
|
revokedAt: string | null;
|
|
155
189
|
createdAt: string;
|
|
190
|
+
/**
|
|
191
|
+
* The field groups this token owns on its store, by key: `price` (Price), `stock` (Stock),
|
|
192
|
+
* `product_details` (Product details), `customer_details` (Customer details). A store hands
|
|
193
|
+
* each group to one app or token at a time (`tenant.fieldGroupOwners()`); empty when it owns
|
|
194
|
+
* none.
|
|
195
|
+
*/
|
|
196
|
+
fieldOwnership: string[];
|
|
156
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* The field-group grant on `apiTokens.create` / `update` (and on `apps.install` /
|
|
200
|
+
* `approveConsent`) is judged by the engine, which refuses with:
|
|
201
|
+
* - `FIELD_GROUP_INVALID` (`ValidationError`) — a value that is not one of the four keys;
|
|
202
|
+
* - `FIELD_GROUP_PERMISSION_MISSING` (`BusinessRuleError`) — the token (or app) holds no
|
|
203
|
+
* permission that changes that group's data: Price and Product details need
|
|
204
|
+
* `UpdateCatalog` or `UpdateProduct`, Stock one of those or `AdjustStock` or
|
|
205
|
+
* `UpdateStockLocation`, Customer details `UpdateCustomer`;
|
|
206
|
+
* - `FIELD_GROUP_GRANT_NOT_ALLOWED` (`PermissionDeniedError`) — you cannot change that data
|
|
207
|
+
* yourself;
|
|
208
|
+
* - `FIELD_GROUP_OWNER_CONFLICT` (`ConflictError`) — another app or token owns it on the store
|
|
209
|
+
* (release it there first, `apps.releaseFieldGroup`);
|
|
210
|
+
* - `FIELD_GROUP_ERP_SYNCED` (`BusinessRuleError`) — a built-in writer writes it into the
|
|
211
|
+
* store: Exact Online (all four groups), or the store's organization (Price and Product
|
|
212
|
+
* details). `tenant.fieldGroupSyncSources()` lists them.
|
|
213
|
+
*/
|
|
214
|
+
export type FieldGroupGrantRefusalCode = 'FIELD_GROUP_INVALID' | 'FIELD_GROUP_PERMISSION_MISSING' | 'FIELD_GROUP_GRANT_NOT_ALLOWED' | 'FIELD_GROUP_OWNER_CONFLICT' | 'FIELD_GROUP_ERP_SYNCED';
|
|
157
215
|
export interface CreateApiTokenInput {
|
|
158
216
|
name: string;
|
|
159
217
|
/** Explicit grants. Union with whatever `templateKeys` expands to. */
|
|
160
218
|
permissions?: string[];
|
|
161
|
-
/**
|
|
219
|
+
/**
|
|
220
|
+
* Named presets from the platform's permission catalogue. A preset marked
|
|
221
|
+
* `sandbox` in `meta.catalog()` (`sandbox-full`) is minted on a sandbox store
|
|
222
|
+
* only; elsewhere the engine refuses it with `SANDBOX_ONLY_TEMPLATE`.
|
|
223
|
+
*/
|
|
162
224
|
templateKeys?: string[];
|
|
163
225
|
/**
|
|
164
226
|
* ISO-8601 instant WITH an offset; the engine refuses a past date. Wins over
|
|
@@ -180,6 +242,11 @@ export interface CreateApiTokenInput {
|
|
|
180
242
|
* `LOCATION_NOT_IN_CHANNEL`.
|
|
181
243
|
*/
|
|
182
244
|
locationIds?: string[] | null;
|
|
245
|
+
/**
|
|
246
|
+
* Field groups the token will own on its store, by key (see `ApiToken.fieldOwnership`).
|
|
247
|
+
* Omit for none. Refusals: {@link FieldGroupGrantRefusalCode}.
|
|
248
|
+
*/
|
|
249
|
+
fieldOwnership?: string[] | null;
|
|
183
250
|
}
|
|
184
251
|
/** Expiry is absent on purpose: it is fixed at creation (ADR 2026-07-29). */
|
|
185
252
|
export interface UpdateApiTokenInput {
|
|
@@ -192,5 +259,13 @@ export interface UpdateApiTokenInput {
|
|
|
192
259
|
* on create.
|
|
193
260
|
*/
|
|
194
261
|
locationIds?: string[] | null;
|
|
262
|
+
/**
|
|
263
|
+
* Replace the field groups the token owns. Omit or pass null to keep them (a group the
|
|
264
|
+
* token's new permissions cannot change is dropped); `[]` releases them all. A group the
|
|
265
|
+
* token does not own yet is judged as on create ({@link FieldGroupGrantRefusalCode}); one it
|
|
266
|
+
* owns already is kept as it is. To release one group from any owner in one step, use
|
|
267
|
+
* `apps.releaseFieldGroup`.
|
|
268
|
+
*/
|
|
269
|
+
fieldOwnership?: string[] | null;
|
|
195
270
|
}
|
|
196
271
|
export type { ChannelInfo, TenantContext, TenantScope };
|
package/dist/uploads/index.d.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
2
|
import type { Call } from '../types.js';
|
|
3
3
|
/**
|
|
4
|
-
* What an upload is for. It decides which permission stages it
|
|
5
|
-
* consume it.
|
|
4
|
+
* What an upload is for. It decides which permission stages it, WHICH BUCKET it
|
|
5
|
+
* lands in, and who may consume it.
|
|
6
6
|
*
|
|
7
|
-
* `asset`
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* - `asset` — a media file for the CMS library. It lands in the public asset
|
|
8
|
+
* bucket, which is what an asset is for: anything in it is readable by path.
|
|
9
|
+
* - `bulk` — a JSONL file for `admin.bulk.import`. It lands in PRIVATE storage,
|
|
10
|
+
* because a bulk file is a store's customers and orders, and it is reachable
|
|
11
|
+
* only through a signed link this platform mints.
|
|
12
|
+
*
|
|
13
|
+
* `document` (an order attachment) arrives with the call that consumes it, with
|
|
14
|
+
* its own storage decision.
|
|
11
15
|
*/
|
|
12
|
-
export type UploadPurpose = 'asset';
|
|
16
|
+
export type UploadPurpose = 'asset' | 'bulk';
|
|
13
17
|
export type UploadStatus = 'staged' | 'consumed' | 'expired';
|
|
14
18
|
export interface StagedUpload {
|
|
15
19
|
id: string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* What `users.ensure` did. A newer engine may name an outcome this SDK does not know yet; it is
|
|
5
|
+
* passed through, lower-cased, rather than dropped.
|
|
6
|
+
*/
|
|
7
|
+
export type EnsureUserOutcome = 'created' | 'attached' | 'restored' | 'unchanged';
|
|
8
|
+
/**
|
|
9
|
+
* One role, by store role template (`admin`, `orders`, `warehouse`, `catalog`, `content`,
|
|
10
|
+
* `viewer`: the store's `<store code>-<template>` role, created from the template the first time
|
|
11
|
+
* a store needs it) or by the id of a role of the active store.
|
|
12
|
+
*/
|
|
13
|
+
export type UserRoleRequest = {
|
|
14
|
+
template: string;
|
|
15
|
+
} | {
|
|
16
|
+
roleId: string;
|
|
17
|
+
};
|
|
18
|
+
export interface EnsureUserInput {
|
|
19
|
+
/** The person's login. Compared and stored lower-cased. */
|
|
20
|
+
emailAddress: string;
|
|
21
|
+
/** Only for a new account; defaults to the part of the address before the `@`. */
|
|
22
|
+
firstName?: string;
|
|
23
|
+
/** Only for a new account. */
|
|
24
|
+
lastName?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The person's roles in the active store, at least one and at most ten. Their other roles of
|
|
27
|
+
* this store are replaced; roles in other stores, and roles spanning several stores (an
|
|
28
|
+
* organization's), are kept.
|
|
29
|
+
*/
|
|
30
|
+
roles: UserRoleRequest[];
|
|
31
|
+
}
|
|
32
|
+
/** A staff member as these methods answer them. */
|
|
33
|
+
export interface StaffUser {
|
|
34
|
+
id: string;
|
|
35
|
+
firstName: string;
|
|
36
|
+
lastName: string;
|
|
37
|
+
emailAddress: string;
|
|
38
|
+
/** Every role the account holds, in every store. */
|
|
39
|
+
roles: Array<{
|
|
40
|
+
id: string;
|
|
41
|
+
code: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
export interface EnsureUserResult {
|
|
45
|
+
outcome: EnsureUserOutcome | (string & {});
|
|
46
|
+
administrator: StaffUser;
|
|
47
|
+
/** Whether an invitation (a link to set a password) was mailed to the address. */
|
|
48
|
+
invitationSent: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface UpdateUserInput {
|
|
51
|
+
/** The administrator's id. */
|
|
52
|
+
id: string;
|
|
53
|
+
firstName?: string;
|
|
54
|
+
lastName?: string;
|
|
55
|
+
/** The login email: refused to an API token (`TOKEN_CREDENTIAL_NOT_ALLOWED`). */
|
|
56
|
+
emailAddress?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `admin.users` — the active store's staff (headless spec §7 `users.ensure`). Store-only: an
|
|
60
|
+
* organization's workspace refuses `ensure` with `ORGANIZATION_WORKSPACE_NOT_A_STORE` (422),
|
|
61
|
+
* because its `<code>-admin` role IS the organization admin role.
|
|
62
|
+
*/
|
|
63
|
+
export interface UsersModule {
|
|
64
|
+
/**
|
|
65
|
+
* Bring a person to exactly these roles in the active store, as one call that can be repeated:
|
|
66
|
+
*
|
|
67
|
+
* - `created`: a new address; an account is made and invited;
|
|
68
|
+
* - `attached`: an existing account; its roles of this store alone are replaced, every other
|
|
69
|
+
* role kept. Invited when it is the person's first role here; someone who already works in
|
|
70
|
+
* the store and only changes role is not mailed;
|
|
71
|
+
* - `restored`: a removed member of this store; brought back with these roles and invited again;
|
|
72
|
+
* - `unchanged`: the account already holds exactly these roles here. Nothing is written, nothing
|
|
73
|
+
* mailed, so a retry of a call whose answer was lost is always safe.
|
|
74
|
+
*
|
|
75
|
+
* Adding someone (a new account, a first role in the store, a removed member restored) needs
|
|
76
|
+
* `CreateAdministrator`, and so does a template role the store does not have yet (it is
|
|
77
|
+
* created); changing the roles of someone who already works here needs `UpdateAdministrator`
|
|
78
|
+
* (else `FORBIDDEN`, 403; without `CreateAdministrator` every address that is not a member here
|
|
79
|
+
* answers that same `FORBIDDEN`). The caller must hold every permission of every role handed out
|
|
80
|
+
* (`ROLE_CEILING_NOT_ALLOWED`, 403), and of the account it changes; an account it may not touch
|
|
81
|
+
* (another store's, one above the caller, a platform administrator's, an API token's or app's
|
|
82
|
+
* sign-in) is `ADMINISTRATOR_INVITE_NOT_ALLOWED` (403). An API token may only invite a NEW
|
|
83
|
+
* address, with roles within its own permissions and with no location restriction
|
|
84
|
+
* (`TOKEN_INVITE_NOT_ALLOWED`, 403); its repeat of an invite the account already satisfies
|
|
85
|
+
* answers `unchanged`. Other refusals: `ADMINISTRATOR_ENSURE_INPUT_INVALID` (400, `details`
|
|
86
|
+
* names each field), `ROLE_TEMPLATE_NOT_FOUND` (404), a role id the store does not hold
|
|
87
|
+
* `ENTITY_NOT_FOUND` (404), `INVITATION_EMAIL_NOT_SENT` (422: nothing was changed, repeat the
|
|
88
|
+
* call).
|
|
89
|
+
*
|
|
90
|
+
* Requires a platform release that serves `ensureAdministrator`; an older engine refuses the
|
|
91
|
+
* call before running it, as a GraphQL validation failure (`ValidationError` `BAD_REQUEST`, 400).
|
|
92
|
+
*/
|
|
93
|
+
ensure(input: EnsureUserInput, call?: Call): Promise<EnsureUserResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Change a staff member's names or login email (core `updateAdministrator`; roles go through
|
|
96
|
+
* `ensure`). Needs `UpdateAdministrator`, and every permission the person holds, in every store
|
|
97
|
+
* they work in (`ADMINISTRATOR_CEILING_NOT_ALLOWED` / `SHARED_ADMINISTRATOR_NOT_ALLOWED`, 403).
|
|
98
|
+
* Someone outside the active store answers `ENTITY_NOT_FOUND`.
|
|
99
|
+
*/
|
|
100
|
+
update(input: UpdateUserInput, call?: Call): Promise<StaffUser>;
|
|
101
|
+
}
|
|
102
|
+
export declare const ENSURE_ADMINISTRATOR_MUTATION = "\n mutation EnsureAdministrator($input: EnsureAdministratorInput!) {\n ensureAdministrator(input: $input) {\n outcome\n invitationSent\n administrator { \n id\n firstName\n lastName\n emailAddress\n user {\n roles {\n id\n code\n }\n }\n }\n }\n }\n";
|
|
103
|
+
export declare const UPDATE_ADMINISTRATOR_MUTATION = "\n mutation UpdateStaffUser($input: UpdateAdministratorInput!) {\n updateAdministrator(input: $input) { \n id\n firstName\n lastName\n emailAddress\n user {\n roles {\n id\n code\n }\n }\n }\n }\n";
|
|
104
|
+
export declare function createUsersModule(transport: Transport): UsersModule;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const STAFF_USER_FIELDS = /* GraphQL */ `
|
|
2
|
+
id
|
|
3
|
+
firstName
|
|
4
|
+
lastName
|
|
5
|
+
emailAddress
|
|
6
|
+
user {
|
|
7
|
+
roles {
|
|
8
|
+
id
|
|
9
|
+
code
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
export const ENSURE_ADMINISTRATOR_MUTATION = /* GraphQL */ `
|
|
14
|
+
mutation EnsureAdministrator($input: EnsureAdministratorInput!) {
|
|
15
|
+
ensureAdministrator(input: $input) {
|
|
16
|
+
outcome
|
|
17
|
+
invitationSent
|
|
18
|
+
administrator { ${STAFF_USER_FIELDS} }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
export const UPDATE_ADMINISTRATOR_MUTATION = /* GraphQL */ `
|
|
23
|
+
mutation UpdateStaffUser($input: UpdateAdministratorInput!) {
|
|
24
|
+
updateAdministrator(input: $input) { ${STAFF_USER_FIELDS} }
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
function staffUserOf(wire) {
|
|
28
|
+
return {
|
|
29
|
+
id: wire.id,
|
|
30
|
+
firstName: wire.firstName,
|
|
31
|
+
lastName: wire.lastName,
|
|
32
|
+
emailAddress: wire.emailAddress,
|
|
33
|
+
roles: (wire.user?.roles ?? []).map((role) => ({ id: role.id, code: role.code })),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Only the keys the caller set: an absent name is "leave it", never "clear it". */
|
|
37
|
+
function defined(input) {
|
|
38
|
+
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
39
|
+
}
|
|
40
|
+
export function createUsersModule(transport) {
|
|
41
|
+
return {
|
|
42
|
+
async ensure(input, call) {
|
|
43
|
+
const data = await transport.graphql({
|
|
44
|
+
api: 'admin',
|
|
45
|
+
query: ENSURE_ADMINISTRATOR_MUTATION,
|
|
46
|
+
variables: {
|
|
47
|
+
input: {
|
|
48
|
+
...defined({
|
|
49
|
+
emailAddress: input.emailAddress,
|
|
50
|
+
firstName: input.firstName,
|
|
51
|
+
lastName: input.lastName,
|
|
52
|
+
}),
|
|
53
|
+
roles: input.roles.map((role) => 'template' in role ? { template: role.template } : { roleId: role.roleId }),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}, call);
|
|
57
|
+
const result = data.ensureAdministrator;
|
|
58
|
+
return {
|
|
59
|
+
outcome: result.outcome.toLowerCase(),
|
|
60
|
+
administrator: staffUserOf(result.administrator),
|
|
61
|
+
invitationSent: result.invitationSent,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
async update(input, call) {
|
|
65
|
+
const data = await transport.graphql({
|
|
66
|
+
api: 'admin',
|
|
67
|
+
query: UPDATE_ADMINISTRATOR_MUTATION,
|
|
68
|
+
variables: {
|
|
69
|
+
input: defined({
|
|
70
|
+
id: input.id,
|
|
71
|
+
firstName: input.firstName,
|
|
72
|
+
lastName: input.lastName,
|
|
73
|
+
emailAddress: input.emailAddress,
|
|
74
|
+
}),
|
|
75
|
+
},
|
|
76
|
+
}, call);
|
|
77
|
+
return staffUserOf(data.updateAdministrator);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sixtyninecommerce/admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Back-office SDK for sixtynine-commerce: every capability of the platform as a method",
|
|
5
5
|
"homepage": "https://github.com/SIXTYNINE-digital/sixtynine-commerce-engine/tree/main/packages/admin-client#readme",
|
|
6
6
|
"repository": {
|
|
@@ -33,10 +33,11 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sixtyninecommerce/core": "^0.
|
|
36
|
+
"@sixtyninecommerce/core": "^0.5.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "22.19.7",
|
|
40
|
+
"graphql": "16.14.0",
|
|
40
41
|
"typescript": "5.9.3",
|
|
41
42
|
"vitest": "4.1.6"
|
|
42
43
|
},
|