@thorprovider/adapters 3.0.2

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 (95) hide show
  1. package/.turbo/turbo-build.log +18 -0
  2. package/.turbo/turbo-test.log +1 -0
  3. package/.turbo/turbo-type-check.log +0 -0
  4. package/AGENTS.md +1 -0
  5. package/CHANGELOG.md +271 -0
  6. package/README.md +634 -0
  7. package/dist/.tsbuildinfo +1 -0
  8. package/dist/index.d.mts +2524 -0
  9. package/dist/index.d.ts +2524 -0
  10. package/dist/index.js +8136 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/index.mjs +8045 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/docs/RETRY_SOLUTION_SUMMARY.md +55 -0
  15. package/docs/authentication.md +165 -0
  16. package/docs/capabilities-system.md +143 -0
  17. package/docs/category-customization.md +309 -0
  18. package/docs/fetch-reliability-solution.md +399 -0
  19. package/docs/getting-started.md +154 -0
  20. package/docs/multi-tenant-channel-scoping.md +225 -0
  21. package/docs/profile-management.md +147 -0
  22. package/docs/providers-overview.md +85 -0
  23. package/docs/services-overview.md +135 -0
  24. package/docs/update-documentation.md +113 -0
  25. package/package.json +43 -0
  26. package/publish.log +93 -0
  27. package/quick-reference.md +169 -0
  28. package/src/admin/AdminAuthService.ts +88 -0
  29. package/src/admin/AdminSessionService.ts +91 -0
  30. package/src/admin/MetricsService.ts +145 -0
  31. package/src/admin/index.ts +13 -0
  32. package/src/backend/BackendMetadataAdapter.ts +88 -0
  33. package/src/backend/factory.ts +107 -0
  34. package/src/backend/index.ts +12 -0
  35. package/src/backend/medusa/MedusaMetadataAdapter.ts +139 -0
  36. package/src/core/errors.js +161 -0
  37. package/src/core/errors.ts +132 -0
  38. package/src/core/fetch-utils.js +240 -0
  39. package/src/core/fetch-utils.ts +227 -0
  40. package/src/core/index.ts +8 -0
  41. package/src/core/logger.js +63 -0
  42. package/src/core/logger.ts +37 -0
  43. package/src/factory/index.ts +102 -0
  44. package/src/index.ts +205 -0
  45. package/src/lib/money-transform.ts +80 -0
  46. package/src/metadata/channel-scope-validation.js +136 -0
  47. package/src/metadata/channel-scope-validation.ts +151 -0
  48. package/src/metadata/channel-scope.js +174 -0
  49. package/src/metadata/channel-scope.ts +215 -0
  50. package/src/metadata/index.js +31 -0
  51. package/src/metadata/index.ts +37 -0
  52. package/src/providers/bigcommerce/bigcommerce.ts +6 -0
  53. package/src/providers/bigcommerce/index.ts +315 -0
  54. package/src/providers/bigcommerce/transforms.ts +227 -0
  55. package/src/providers/index.ts +22 -0
  56. package/src/providers/magento/index.ts +387 -0
  57. package/src/providers/magento/magento.ts +5 -0
  58. package/src/providers/magento/queries.ts +267 -0
  59. package/src/providers/magento/transforms.ts +249 -0
  60. package/src/providers/medusa/auth.js +601 -0
  61. package/src/providers/medusa/auth.ts +496 -0
  62. package/src/providers/medusa/fulfillment-planner.ts +225 -0
  63. package/src/providers/medusa/index.js +2485 -0
  64. package/src/providers/medusa/index.ts +4239 -0
  65. package/src/providers/medusa/medusa.ts +8 -0
  66. package/src/providers/medusa/payment.js +265 -0
  67. package/src/providers/medusa/payment.ts +269 -0
  68. package/src/providers/medusa/transforms.js +505 -0
  69. package/src/providers/medusa/transforms.ts +625 -0
  70. package/src/providers/shopify/index.ts +444 -0
  71. package/src/providers/shopify/mutations.ts +102 -0
  72. package/src/providers/shopify/queries.ts +324 -0
  73. package/src/providers/shopify/shopify.ts +7 -0
  74. package/src/providers/shopify/transforms.ts +270 -0
  75. package/src/providers/spree/index.ts +344 -0
  76. package/src/providers/spree/spree.ts +5 -0
  77. package/src/providers/spree/transforms.ts +254 -0
  78. package/src/providers/woocommerce/index.ts +310 -0
  79. package/src/providers/woocommerce/transforms.ts +254 -0
  80. package/src/providers/woocommerce/woocommerce.ts +5 -0
  81. package/src/resilience/README.md +299 -0
  82. package/src/resilience/circuit-breaker.ts +222 -0
  83. package/src/resilience/index.ts +10 -0
  84. package/src/types/capabilities.types.js +141 -0
  85. package/src/types/capabilities.types.ts +366 -0
  86. package/src/types/index.ts +24 -0
  87. package/src/types/provider.js +9 -0
  88. package/src/types/provider.ts +9 -0
  89. package/src/types/ui-responses.ts +261 -0
  90. package/src/utils/city-coordinates.ts +40 -0
  91. package/src/utils/geolocation.ts +52 -0
  92. package/src/utils/payment-utils.js +152 -0
  93. package/src/utils/payment-utils.ts +203 -0
  94. package/tsconfig.json +15 -0
  95. package/tsup.config.ts +14 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @thorprovider/adapters v2.0
3
+ * Core logger utility
4
+ */
5
+
6
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
7
+
8
+ export interface Logger {
9
+ debug(message: string, ...args: any[]): void;
10
+ info(message: string, ...args: any[]): void;
11
+ warn(message: string, ...args: any[]): void;
12
+ error(message: string, ...args: any[]): void;
13
+ }
14
+
15
+ /**
16
+ * Create a logger instance
17
+ */
18
+ export function createLogger(
19
+ namespace: string,
20
+ enabled: boolean = false
21
+ ): Logger {
22
+ const log = (level: LogLevel, message: string, ...args: any[]) => {
23
+ if (!enabled && level !== 'error') return;
24
+
25
+ const timestamp = new Date().toISOString();
26
+ const prefix = `[${timestamp}] [@thorprovider/adapters:${namespace}] ${level.toUpperCase()}:`;
27
+
28
+ console[level](prefix, message, ...args);
29
+ };
30
+
31
+ return {
32
+ debug: (message, ...args) => log('debug', message, ...args),
33
+ info: (message, ...args) => log('info', message, ...args),
34
+ warn: (message, ...args) => log('warn', message, ...args),
35
+ error: (message, ...args) => log('error', message, ...args),
36
+ };
37
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @thorprovider/adapters v2.0
3
+ * Provider Factory - Create commerce providers dynamically
4
+ *
5
+ * This factory is bound to SupportedProviderType from @thorprovider/types (L1).
6
+ * When adding a new provider:
7
+ * 1. Update SupportedProviderType enum in packages/types/src/provider.ts
8
+ * 2. Add new discriminated union case here
9
+ * 3. Create packages/adapters/src/providers/[platform]/
10
+ * 4. Implement CommerceProvider interface
11
+ */
12
+
13
+ import type { CommerceProvider } from '../types';
14
+ import { MedusaProvider, type MedusaProviderConfig } from '../providers/medusa';
15
+ import { ShopifyProvider, type ShopifyProviderConfig } from '../providers/shopify';
16
+ import { BigCommerceProvider, type BigCommerceProviderConfig } from '../providers/bigcommerce';
17
+ import { WooCommerceProvider, type WooCommerceProviderConfig } from '../providers/woocommerce';
18
+ import { SpreeProvider, type SpreeProviderConfig } from '../providers/spree';
19
+ import { MagentoProvider, type MagentoProviderConfig } from '../providers/magento';
20
+ import { ProviderNotFoundError, ConfigurationError } from '../core/errors';
21
+ import type { ProviderTypeString } from '@thorprovider/types';
22
+
23
+ /**
24
+ * Supported provider types (imported from @thorprovider/types L1)
25
+ * Kept for backward compatibility; prefer ProviderTypeString from @thorprovider/types
26
+ */
27
+ export type ProviderType = ProviderTypeString;
28
+
29
+ /**
30
+ * Provider configuration discriminated union
31
+ */
32
+ export type ProviderFactoryConfig =
33
+ | { provider: 'medusa'; config: MedusaProviderConfig }
34
+ | { provider: 'shopify'; config: ShopifyProviderConfig }
35
+ | { provider: 'bigcommerce'; config: BigCommerceProviderConfig }
36
+ | { provider: 'woocommerce'; config: WooCommerceProviderConfig }
37
+ | { provider: 'spree'; config: SpreeProviderConfig }
38
+ | { provider: 'magento'; config: MagentoProviderConfig };
39
+
40
+ /**
41
+ * Runtime provider configuration (accepts any ProviderTypeString from env vars)
42
+ */
43
+ export type RuntimeProviderConfig = {
44
+ provider: ProviderTypeString;
45
+ config: Record<string, unknown>;
46
+ };
47
+
48
+ /**
49
+ * Create a commerce provider instance
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const commerce = createProvider({
54
+ * provider: 'medusa',
55
+ * config: {
56
+ * baseUrl: process.env.MEDUSA_BACKEND_URL,
57
+ * publishableKey: process.env.MEDUSA_PUBLISHABLE_KEY,
58
+ * }
59
+ * });
60
+ *
61
+ * const products = await commerce.getProducts();
62
+ * ```
63
+ */
64
+ export function createProvider(options: ProviderFactoryConfig | RuntimeProviderConfig): CommerceProvider {
65
+ if (!options || !options.provider) {
66
+ throw new ConfigurationError('Provider type is required');
67
+ }
68
+
69
+ if (!options.config) {
70
+ throw new ConfigurationError('Provider configuration is required');
71
+ }
72
+
73
+ const config = options.config as any;
74
+
75
+ switch (options.provider) {
76
+ case 'medusa':
77
+ return new MedusaProvider(config);
78
+ case 'shopify':
79
+ return new ShopifyProvider(config);
80
+ case 'bigcommerce':
81
+ return new BigCommerceProvider(config);
82
+ case 'woocommerce':
83
+ return new WooCommerceProvider(config);
84
+ case 'spree':
85
+ return new SpreeProvider(config);
86
+ case 'magento':
87
+ return new MagentoProvider(config);
88
+
89
+ default:
90
+ throw new ProviderNotFoundError(String((options as any).provider));
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Type guard to check if a provider is of a specific type
96
+ */
97
+ export function isProviderType<T extends ProviderType>(
98
+ provider: CommerceProvider,
99
+ type: T
100
+ ): boolean {
101
+ return provider.name === type;
102
+ }
package/src/index.ts ADDED
@@ -0,0 +1,205 @@
1
+ /**
2
+ * @thorprovider/adapters v2.0
3
+ * Main package exports
4
+ *
5
+ * Unified commerce adapters for Thor Commerce ecosystem
6
+ * Supports multiple e-commerce platforms through a common interface
7
+ */
8
+
9
+ // ============================================
10
+ // Types
11
+ // ============================================
12
+ export type * from './types';
13
+
14
+ // ============================================
15
+ // Commerce Provider Interface (from @thorprovider/types)
16
+ // ============================================
17
+ // Re-export for backward compatibility
18
+ export type {
19
+ CommerceProvider,
20
+ BackendCapabilities,
21
+ ProviderConfig,
22
+ StorefrontContext,
23
+ StorefrontPlatformType,
24
+ StorefrontConfig,
25
+ StorefrontSeoDefaults,
26
+ DesignerConfig,
27
+ DesignerThemeConfig,
28
+ DesignerThemePreset,
29
+ DesignerNavItem,
30
+ DesignerHistoryEntry,
31
+ } from '@thorprovider/types';
32
+
33
+ export {
34
+ StorefrontConfigError,
35
+ } from '@thorprovider/types';
36
+
37
+ // ============================================
38
+ // Capabilities
39
+ // ============================================
40
+ export {
41
+ MEDUSA_CAPABILITIES,
42
+ SHOPIFY_CAPABILITIES,
43
+ BIGCOMMERCE_CAPABILITIES,
44
+ WOOCOMMERCE_CAPABILITIES,
45
+ SPREE_CAPABILITIES,
46
+ MAGENTO_CAPABILITIES,
47
+ isBackendCapabilities,
48
+ getCapabilityDescription,
49
+ } from './types/capabilities.types';
50
+
51
+ // ============================================
52
+ // Core Utilities
53
+ // ============================================
54
+ export {
55
+ CommerceError,
56
+ ProviderNotFoundError,
57
+ ConfigurationError,
58
+ ResourceNotFoundError,
59
+ ProviderAPIError,
60
+ createLogger,
61
+ type Logger,
62
+ type LogLevel,
63
+ } from './core';
64
+
65
+ // ============================================
66
+ // Resilience Patterns
67
+ // ============================================
68
+ export {
69
+ CircuitBreaker,
70
+ backendCircuitBreaker,
71
+ CircuitBreakerOpenError,
72
+ type CircuitState,
73
+ type CircuitBreakerConfig,
74
+ type CircuitBreakerStats,
75
+ } from './resilience';
76
+
77
+ // ============================================
78
+ // Transformation Utilities
79
+ // ============================================
80
+ export {
81
+ moneyToNumber,
82
+ numberToMoney,
83
+ moneyToNumberSafe,
84
+ } from './lib/money-transform';
85
+
86
+ // ============================================
87
+ // Payment Utilities
88
+ // ============================================
89
+ export {
90
+ getBackendProviderId,
91
+ validatePaymentId,
92
+ type BackendMapping,
93
+ } from './utils/payment-utils';
94
+
95
+ // ============================================
96
+ // Inventory & Fulfillment Utilities
97
+ // ============================================
98
+ export {
99
+ calculateDistance,
100
+ geocodeAddress,
101
+ type Coordinates,
102
+ } from './utils/geolocation';
103
+
104
+ export {
105
+ planFulfillment,
106
+ type FulfillmentGroup,
107
+ type FulfillmentPlan,
108
+ type PlanFulfillmentShippingAddress,
109
+ } from './providers/medusa/fulfillment-planner';
110
+
111
+ export {
112
+ transformStockLocation,
113
+ transformInventoryLevel,
114
+ transformFulfillmentOption,
115
+ transformStorefrontConfig,
116
+ } from './providers/medusa/transforms';
117
+
118
+ // ============================================
119
+ // Admin Services
120
+ // ============================================
121
+ export {
122
+ AdminAuthService,
123
+ AdminSessionService,
124
+ MetricsService,
125
+ } from './admin';
126
+
127
+ export type {
128
+ AdminSession,
129
+ MetricsOrderInput,
130
+ MetricsProductInput,
131
+ } from './admin';
132
+
133
+ // ============================================
134
+ // Backend Metadata Adapters
135
+ // ============================================
136
+ export type {
137
+ BackendMetadataAdapter,
138
+ } from './backend/BackendMetadataAdapter';
139
+
140
+ export type {
141
+ BackendMetadataAdapterConfig,
142
+ } from './backend/factory';
143
+
144
+ export {
145
+ createBackendMetadataAdapter,
146
+ } from './backend/factory';
147
+
148
+ export {
149
+ MedusaMetadataAdapter,
150
+ } from './backend/medusa/MedusaMetadataAdapter';
151
+
152
+ // ============================================
153
+ // Channel Scope (Multi-Tenant Metadata Helpers)
154
+ // ============================================
155
+ export {
156
+ CHANNEL_SCOPE_KEY,
157
+ CHANNEL_ASSIGNED_AT_KEY,
158
+ CHANNEL_ASSIGNED_BY_KEY,
159
+ buildChannelScopeMetadata,
160
+ getChannelScope,
161
+ isScopedToChannel,
162
+ isGlobalEntity,
163
+ filterByChannel,
164
+ filterGlobalOnly,
165
+ partitionByChannel,
166
+ ChannelScopeValidationError,
167
+ validateChannelId,
168
+ validateEntityId,
169
+ validateAssignedBy,
170
+ validateChannelScopeAssignment,
171
+ assertEntityBelongsToChannel,
172
+ } from './metadata';
173
+
174
+ export type {
175
+ ChannelScopeable,
176
+ ChannelScopeMetadata,
177
+ } from './metadata';
178
+
179
+ // ============================================
180
+ // Factory
181
+ // ============================================
182
+ export {
183
+ createProvider,
184
+ isProviderType,
185
+ type ProviderType,
186
+ type ProviderFactoryConfig,
187
+ } from './factory';
188
+
189
+ // ============================================
190
+ // Providers
191
+ // ============================================
192
+ export {
193
+ MedusaProvider,
194
+ type MedusaProviderConfig,
195
+ ShopifyProvider,
196
+ type ShopifyProviderConfig,
197
+ BigCommerceProvider,
198
+ type BigCommerceProviderConfig,
199
+ WooCommerceProvider,
200
+ type WooCommerceProviderConfig,
201
+ SpreeProvider,
202
+ type SpreeProviderConfig,
203
+ MagentoProvider,
204
+ type MagentoProviderConfig,
205
+ } from './providers';
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @thorprovider/adapters
3
+ * Money transformation utilities
4
+ *
5
+ * Handles conversion between domain Money types (string decimals) and
6
+ * UI-ready number representations (smallest currency unit, e.g., cents).
7
+ */
8
+
9
+ import type { Money } from '@thorprovider/types';
10
+
11
+ /**
12
+ * Converts Money object to number in smallest currency unit
13
+ *
14
+ * Money.amount is a decimal string (e.g., "45.99").
15
+ * This function converts it to smallest unit (e.g., 4599 cents).
16
+ *
17
+ * @param money - Money object with decimal string amount
18
+ * @returns Amount in smallest currency unit (e.g., cents for USD)
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * moneyToNumber({ amount: "45.99", currencyCode: "USD" }) // => 4599
23
+ * moneyToNumber({ amount: "100.00", currencyCode: "EUR" }) // => 10000
24
+ * moneyToNumber({ amount: "0.50", currencyCode: "USD" }) // => 50
25
+ * ```
26
+ */
27
+ export function moneyToNumber(money: Money): number {
28
+ // Parse decimal string and convert to cents (multiply by 100)
29
+ const amountInMajorUnits = parseFloat(money.amount);
30
+ const amountInMinorUnits = Math.round(amountInMajorUnits * 100);
31
+ return amountInMinorUnits;
32
+ }
33
+
34
+ /**
35
+ * Converts number (smallest unit) to Money object
36
+ *
37
+ * Takes an amount in smallest currency unit (e.g., cents) and
38
+ * converts it to a Money object with decimal string amount.
39
+ *
40
+ * @param amount - Amount in smallest currency unit (e.g., cents)
41
+ * @param currency - ISO 4217 currency code (e.g., 'USD', 'EUR')
42
+ * @returns Money object with decimal string amount
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * numberToMoney(4599, 'USD') // => { amount: "45.99", currencyCode: "USD" }
47
+ * numberToMoney(10000, 'EUR') // => { amount: "100.00", currencyCode: "EUR" }
48
+ * numberToMoney(50, 'USD') // => { amount: "0.50", currencyCode: "USD" }
49
+ * ```
50
+ */
51
+ export function numberToMoney(amount: number, currency: string): Money {
52
+ // Convert cents to dollars and format as decimal string
53
+ const amountInMajorUnits = amount / 100;
54
+ const formattedAmount = amountInMajorUnits.toFixed(2);
55
+
56
+ return {
57
+ amount: formattedAmount,
58
+ currencyCode: currency,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Safely converts Money to number, returning 0 for null/undefined
64
+ *
65
+ * Useful for optional Money fields that might not always be present.
66
+ *
67
+ * @param money - Money object or null/undefined
68
+ * @returns Amount in smallest unit, or 0 if money is null/undefined
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * moneyToNumberSafe({ amount: "10.00", currencyCode: "USD" }) // => 1000
73
+ * moneyToNumberSafe(undefined) // => 0
74
+ * moneyToNumberSafe(null) // => 0
75
+ * ```
76
+ */
77
+ export function moneyToNumberSafe(money: Money | null | undefined): number {
78
+ if (!money) return 0;
79
+ return moneyToNumber(money);
80
+ }
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ /**
3
+ * Channel Scope Validation — Pure TypeScript (no external deps)
4
+ *
5
+ * Validates channel-scope metadata before it is written to the backend.
6
+ * Keeps all validation logic in L2 so that L5 starters don't duplicate checks.
7
+ *
8
+ * @module metadata/channel-scope-validation
9
+ */
10
+ var __extends = (this && this.__extends) || (function () {
11
+ var extendStatics = function (d, b) {
12
+ extendStatics = Object.setPrototypeOf ||
13
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
14
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
15
+ return extendStatics(d, b);
16
+ };
17
+ return function (d, b) {
18
+ if (typeof b !== "function" && b !== null)
19
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
20
+ extendStatics(d, b);
21
+ function __() { this.constructor = d; }
22
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
23
+ };
24
+ })();
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ChannelScopeValidationError = void 0;
27
+ exports.validateChannelId = validateChannelId;
28
+ exports.validateEntityId = validateEntityId;
29
+ exports.validateAssignedBy = validateAssignedBy;
30
+ exports.validateChannelScopeAssignment = validateChannelScopeAssignment;
31
+ exports.assertEntityBelongsToChannel = assertEntityBelongsToChannel;
32
+ var channel_scope_1 = require("./channel-scope");
33
+ // ────────────────────────────────────────────────────────────────────
34
+ // Errors
35
+ // ────────────────────────────────────────────────────────────────────
36
+ /**
37
+ * Thrown when channel-scope validation fails.
38
+ *
39
+ * Consumers can catch this specific type to provide user-friendly feedback
40
+ * without leaking internal details.
41
+ */
42
+ var ChannelScopeValidationError = /** @class */ (function (_super) {
43
+ __extends(ChannelScopeValidationError, _super);
44
+ function ChannelScopeValidationError(message, field, value) {
45
+ var _this = _super.call(this, message) || this;
46
+ _this.name = 'ChannelScopeValidationError';
47
+ _this.field = field;
48
+ _this.value = value;
49
+ return _this;
50
+ }
51
+ return ChannelScopeValidationError;
52
+ }(Error));
53
+ exports.ChannelScopeValidationError = ChannelScopeValidationError;
54
+ // ────────────────────────────────────────────────────────────────────
55
+ // Validation functions
56
+ // ────────────────────────────────────────────────────────────────────
57
+ /**
58
+ * Validate that a channel ID is a non-empty string.
59
+ *
60
+ * Does **not** enforce a specific prefix (e.g. `"sc_"`) because different
61
+ * backends use different conventions:
62
+ * - Medusa: `"sc_01HXXX"`
63
+ * - Shopify: `"gid://shopify/Channel/789"`
64
+ * - WooCommerce: `"1"` (numeric string)
65
+ *
66
+ * @throws {ChannelScopeValidationError}
67
+ */
68
+ function validateChannelId(channelId) {
69
+ if (typeof channelId !== 'string' || channelId.trim().length === 0) {
70
+ throw new ChannelScopeValidationError('Channel ID must be a non-empty string', 'channelId', channelId);
71
+ }
72
+ // Sanity upper-bound for ID length (guards against payload abuse)
73
+ if (channelId.length > 256) {
74
+ throw new ChannelScopeValidationError('Channel ID exceeds maximum length (256)', 'channelId', channelId);
75
+ }
76
+ }
77
+ /**
78
+ * Validate that an entity ID is a non-empty string.
79
+ *
80
+ * @throws {ChannelScopeValidationError}
81
+ */
82
+ function validateEntityId(entityId, label) {
83
+ if (label === void 0) { label = 'entityId'; }
84
+ if (typeof entityId !== 'string' || entityId.trim().length === 0) {
85
+ throw new ChannelScopeValidationError("".concat(label, " must be a non-empty string"), label, entityId);
86
+ }
87
+ }
88
+ /**
89
+ * Validate that the `assignedBy` value (if provided) is a reasonable string.
90
+ *
91
+ * @throws {ChannelScopeValidationError}
92
+ */
93
+ function validateAssignedBy(assignedBy) {
94
+ if (assignedBy === undefined || assignedBy === null)
95
+ return;
96
+ if (typeof assignedBy !== 'string') {
97
+ throw new ChannelScopeValidationError('assignedBy must be a string', 'assignedBy', assignedBy);
98
+ }
99
+ if (assignedBy.length > 512) {
100
+ throw new ChannelScopeValidationError('assignedBy exceeds maximum length (512)', 'assignedBy', assignedBy);
101
+ }
102
+ }
103
+ /**
104
+ * Full validation for a channel-scope assignment operation.
105
+ *
106
+ * Call before writing channel scope metadata to any entity.
107
+ *
108
+ * @throws {ChannelScopeValidationError} with descriptive field + value
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * validateChannelScopeAssignment('cus_123', 'sc_01HXXX', 'admin@store.com');
113
+ * // If no error, safe to proceed
114
+ * ```
115
+ */
116
+ function validateChannelScopeAssignment(entityId, channelId, assignedBy) {
117
+ validateEntityId(entityId, 'entityId');
118
+ validateChannelId(channelId);
119
+ validateAssignedBy(assignedBy);
120
+ }
121
+ /**
122
+ * Validate that an entity is currently scoped to the expected channel.
123
+ *
124
+ * Useful as a guard before performing operations that should only
125
+ * affect entities within the caller's channel (e.g. a dropshipper editing
126
+ * a customer that must belong to their channel).
127
+ *
128
+ * @throws {ChannelScopeValidationError} if entity does not belong to expectedChannelId
129
+ */
130
+ function assertEntityBelongsToChannel(entity, expectedChannelId) {
131
+ var _a;
132
+ var actual = (_a = entity.metadata) === null || _a === void 0 ? void 0 : _a[channel_scope_1.CHANNEL_SCOPE_KEY];
133
+ if (actual !== expectedChannelId) {
134
+ throw new ChannelScopeValidationError("Entity \"".concat(entity.id, "\" does not belong to channel \"").concat(expectedChannelId, "\" (actual: \"").concat(actual !== null && actual !== void 0 ? actual : 'none', "\")"), channel_scope_1.CHANNEL_SCOPE_KEY, actual);
135
+ }
136
+ }