@sixtyninecommerce/admin 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/README.md +2739 -17
  2. package/dist/api-tokens/index.d.ts +15 -5
  3. package/dist/api-tokens/index.js +1 -0
  4. package/dist/apps/event-subscription.d.ts +59 -0
  5. package/dist/apps/event-subscription.js +32 -0
  6. package/dist/apps/hook-secret.d.ts +22 -0
  7. package/dist/apps/hook-secret.js +36 -0
  8. package/dist/apps/index.d.ts +54 -3
  9. package/dist/apps/index.js +17 -0
  10. package/dist/audit/index.d.ts +7 -0
  11. package/dist/auth/queries.d.ts +5 -1
  12. package/dist/auth/queries.js +6 -0
  13. package/dist/bulk/index.d.ts +323 -0
  14. package/dist/bulk/index.js +162 -0
  15. package/dist/catalog/assets.d.ts +147 -0
  16. package/dist/catalog/assets.js +164 -0
  17. package/dist/catalog/collections.d.ts +109 -0
  18. package/dist/catalog/collections.js +91 -0
  19. package/dist/catalog/facets.d.ts +103 -0
  20. package/dist/catalog/facets.js +100 -0
  21. package/dist/catalog/index.d.ts +115 -0
  22. package/dist/catalog/index.js +21 -0
  23. package/dist/catalog/options.d.ts +89 -0
  24. package/dist/catalog/options.js +88 -0
  25. package/dist/catalog/products-bulk.d.ts +21 -0
  26. package/dist/catalog/products-bulk.js +58 -0
  27. package/dist/catalog/products.d.ts +650 -0
  28. package/dist/catalog/products.js +458 -0
  29. package/dist/catalog/properties.d.ts +234 -0
  30. package/dist/catalog/properties.js +158 -0
  31. package/dist/catalog/types.d.ts +174 -0
  32. package/dist/catalog/types.js +141 -0
  33. package/dist/catalog/variants.d.ts +324 -0
  34. package/dist/catalog/variants.js +288 -0
  35. package/dist/catalog/visibility.d.ts +57 -0
  36. package/dist/catalog/visibility.js +59 -0
  37. package/dist/client.d.ts +192 -3
  38. package/dist/client.js +87 -12
  39. package/dist/compliance/index.d.ts +88 -0
  40. package/dist/compliance/index.js +88 -0
  41. package/dist/customer-data-redaction.d.ts +14 -0
  42. package/dist/customer-data-redaction.js +22 -0
  43. package/dist/customers/index.d.ts +428 -17
  44. package/dist/customers/index.js +322 -15
  45. package/dist/documents/index.d.ts +513 -16
  46. package/dist/documents/index.js +229 -3
  47. package/dist/events/index.d.ts +259 -0
  48. package/dist/events/index.js +147 -0
  49. package/dist/feeds/ai.d.ts +46 -0
  50. package/dist/feeds/ai.js +43 -0
  51. package/dist/feeds/categories.d.ts +50 -0
  52. package/dist/feeds/categories.js +56 -0
  53. package/dist/feeds/configs.d.ts +54 -0
  54. package/dist/feeds/configs.js +85 -0
  55. package/dist/feeds/identifier-fields.d.ts +25 -0
  56. package/dist/feeds/identifier-fields.js +29 -0
  57. package/dist/feeds/index.d.ts +54 -0
  58. package/dist/feeds/index.js +46 -0
  59. package/dist/feeds/preserve.d.ts +38 -0
  60. package/dist/feeds/preserve.js +74 -0
  61. package/dist/feeds/rules.d.ts +42 -0
  62. package/dist/feeds/rules.js +56 -0
  63. package/dist/feeds/types.d.ts +654 -0
  64. package/dist/feeds/types.js +135 -0
  65. package/dist/fulfillments/index.d.ts +437 -0
  66. package/dist/fulfillments/index.js +534 -0
  67. package/dist/heads/index.d.ts +60 -0
  68. package/dist/heads/index.js +45 -0
  69. package/dist/hooks/assignments.d.ts +90 -0
  70. package/dist/hooks/assignments.js +53 -0
  71. package/dist/hooks/index.d.ts +97 -0
  72. package/dist/hooks/index.js +76 -0
  73. package/dist/index.d.ts +95 -9
  74. package/dist/index.js +21 -1
  75. package/dist/integrations/index.d.ts +14 -0
  76. package/dist/integrations/index.js +9 -0
  77. package/dist/integrations/pipelines.d.ts +113 -0
  78. package/dist/integrations/pipelines.js +62 -0
  79. package/dist/integrations/types.d.ts +176 -0
  80. package/dist/integrations/types.js +8 -0
  81. package/dist/jobs/bulk-action-run.d.ts +74 -0
  82. package/dist/jobs/bulk-action-run.js +69 -0
  83. package/dist/locations/index.d.ts +6 -2
  84. package/dist/meta/index.d.ts +32 -0
  85. package/dist/orders/bulk.d.ts +75 -0
  86. package/dist/orders/bulk.js +150 -0
  87. package/dist/orders/commit-edit.d.ts +123 -0
  88. package/dist/orders/commit-edit.js +142 -0
  89. package/dist/orders/deposit-policy.d.ts +103 -0
  90. package/dist/orders/deposit-policy.js +202 -0
  91. package/dist/orders/get.d.ts +722 -0
  92. package/dist/orders/get.js +530 -0
  93. package/dist/orders/index.d.ts +186 -21
  94. package/dist/orders/index.js +57 -0
  95. package/dist/orders/line-price-preview.d.ts +297 -0
  96. package/dist/orders/line-price-preview.js +50 -0
  97. package/dist/orders/manual-discounts.d.ts +141 -0
  98. package/dist/orders/manual-discounts.js +97 -0
  99. package/dist/orders/manual-pricing.d.ts +192 -0
  100. package/dist/orders/manual-pricing.js +72 -0
  101. package/dist/orders/order-entry.d.ts +566 -0
  102. package/dist/orders/order-entry.js +129 -0
  103. package/dist/orders/parcel-charge.d.ts +45 -0
  104. package/dist/orders/parcel-charge.js +55 -0
  105. package/dist/orders/plan-edit.d.ts +489 -0
  106. package/dist/orders/plan-edit.js +93 -0
  107. package/dist/orders/status.d.ts +235 -0
  108. package/dist/orders/status.js +96 -0
  109. package/dist/orders/surcharges.d.ts +75 -2
  110. package/dist/orders/surcharges.js +110 -4
  111. package/dist/payments/account-credit.d.ts +171 -0
  112. package/dist/payments/account-credit.js +107 -0
  113. package/dist/payments/index.d.ts +241 -9
  114. package/dist/payments/index.js +92 -0
  115. package/dist/payments/methods.d.ts +37 -0
  116. package/dist/payments/methods.js +33 -0
  117. package/dist/platform/catalog.d.ts +148 -0
  118. package/dist/platform/catalog.js +74 -0
  119. package/dist/platform/entitlements.d.ts +111 -0
  120. package/dist/platform/entitlements.js +86 -0
  121. package/dist/platform/legal-entities.d.ts +139 -0
  122. package/dist/platform/legal-entities.js +126 -0
  123. package/dist/platform/legal-entity-numbering.d.ts +103 -0
  124. package/dist/platform/legal-entity-numbering.js +101 -0
  125. package/dist/platform/organization-membership.d.ts +80 -0
  126. package/dist/platform/organization-membership.js +71 -0
  127. package/dist/platform/organization-settings.d.ts +33 -0
  128. package/dist/platform/organization-settings.js +42 -0
  129. package/dist/platform/organization-sharing.d.ts +172 -0
  130. package/dist/platform/organization-sharing.js +120 -0
  131. package/dist/platform/organizations.d.ts +205 -0
  132. package/dist/platform/organizations.js +180 -0
  133. package/dist/platform/provision-store.d.ts +88 -8
  134. package/dist/platform/provision-store.js +12 -0
  135. package/dist/platform/reports.d.ts +46 -0
  136. package/dist/platform/reports.js +31 -0
  137. package/dist/platform/server-metrics.d.ts +58 -0
  138. package/dist/platform/server-metrics.js +11 -0
  139. package/dist/pos/index.d.ts +567 -0
  140. package/dist/pos/index.js +205 -0
  141. package/dist/pricing/index.d.ts +408 -0
  142. package/dist/pricing/index.js +353 -0
  143. package/dist/pricing/vouchers.d.ts +103 -0
  144. package/dist/pricing/vouchers.js +69 -0
  145. package/dist/provenance/index.d.ts +93 -0
  146. package/dist/provenance/index.js +53 -0
  147. package/dist/reports/index.d.ts +199 -0
  148. package/dist/reports/index.js +154 -0
  149. package/dist/returns/index.d.ts +225 -0
  150. package/dist/returns/index.js +160 -0
  151. package/dist/search/index.d.ts +99 -0
  152. package/dist/search/index.js +62 -0
  153. package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
  154. package/dist/shipping/parcel-count-suggestion.js +73 -0
  155. package/dist/shipping/parcel-rule.d.ts +48 -0
  156. package/dist/shipping/parcel-rule.js +91 -0
  157. package/dist/stock/index.d.ts +166 -0
  158. package/dist/stock/index.js +115 -0
  159. package/dist/stock/transfers.d.ts +520 -0
  160. package/dist/stock/transfers.js +437 -0
  161. package/dist/stored-value/index.d.ts +154 -0
  162. package/dist/stored-value/index.js +130 -0
  163. package/dist/tenant/capabilities.d.ts +10 -0
  164. package/dist/tenant/entitlements.d.ts +35 -0
  165. package/dist/tenant/entitlements.js +15 -0
  166. package/dist/tenant/field-groups.d.ts +64 -0
  167. package/dist/tenant/field-groups.js +34 -0
  168. package/dist/tenant/legal-entity.d.ts +32 -0
  169. package/dist/tenant/legal-entity.js +41 -0
  170. package/dist/tenant/notifications.d.ts +148 -0
  171. package/dist/tenant/notifications.js +79 -0
  172. package/dist/tenant/retention.d.ts +10 -4
  173. package/dist/tenant/settings-sources.d.ts +103 -0
  174. package/dist/tenant/settings-sources.js +92 -0
  175. package/dist/types.d.ts +86 -11
  176. package/dist/uploads/index.d.ts +11 -7
  177. package/dist/users/index.d.ts +104 -0
  178. package/dist/users/index.js +80 -0
  179. package/package.json +3 -2
@@ -0,0 +1,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` and
10
- * `payments.providers.terminals.provision`. Repeating one under the same key
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: Array<{
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
- /** Named presets from the platform's permission catalogue. */
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 };
@@ -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 and who may
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` is the only purpose today. `bulk` and `document` arrive with the
8
- * calls that consume them, each with its own storage decision — everything
9
- * staged currently lands in the public asset bucket, which is right for a media
10
- * asset and not for a customer export or an order attachment.
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.8.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.4.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
  },