@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.
- package/.turbo/turbo-build.log +18 -0
- package/.turbo/turbo-test.log +1 -0
- package/.turbo/turbo-type-check.log +0 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +271 -0
- package/README.md +634 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.mts +2524 -0
- package/dist/index.d.ts +2524 -0
- package/dist/index.js +8136 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +8045 -0
- package/dist/index.mjs.map +1 -0
- package/docs/RETRY_SOLUTION_SUMMARY.md +55 -0
- package/docs/authentication.md +165 -0
- package/docs/capabilities-system.md +143 -0
- package/docs/category-customization.md +309 -0
- package/docs/fetch-reliability-solution.md +399 -0
- package/docs/getting-started.md +154 -0
- package/docs/multi-tenant-channel-scoping.md +225 -0
- package/docs/profile-management.md +147 -0
- package/docs/providers-overview.md +85 -0
- package/docs/services-overview.md +135 -0
- package/docs/update-documentation.md +113 -0
- package/package.json +43 -0
- package/publish.log +93 -0
- package/quick-reference.md +169 -0
- package/src/admin/AdminAuthService.ts +88 -0
- package/src/admin/AdminSessionService.ts +91 -0
- package/src/admin/MetricsService.ts +145 -0
- package/src/admin/index.ts +13 -0
- package/src/backend/BackendMetadataAdapter.ts +88 -0
- package/src/backend/factory.ts +107 -0
- package/src/backend/index.ts +12 -0
- package/src/backend/medusa/MedusaMetadataAdapter.ts +139 -0
- package/src/core/errors.js +161 -0
- package/src/core/errors.ts +132 -0
- package/src/core/fetch-utils.js +240 -0
- package/src/core/fetch-utils.ts +227 -0
- package/src/core/index.ts +8 -0
- package/src/core/logger.js +63 -0
- package/src/core/logger.ts +37 -0
- package/src/factory/index.ts +102 -0
- package/src/index.ts +205 -0
- package/src/lib/money-transform.ts +80 -0
- package/src/metadata/channel-scope-validation.js +136 -0
- package/src/metadata/channel-scope-validation.ts +151 -0
- package/src/metadata/channel-scope.js +174 -0
- package/src/metadata/channel-scope.ts +215 -0
- package/src/metadata/index.js +31 -0
- package/src/metadata/index.ts +37 -0
- package/src/providers/bigcommerce/bigcommerce.ts +6 -0
- package/src/providers/bigcommerce/index.ts +315 -0
- package/src/providers/bigcommerce/transforms.ts +227 -0
- package/src/providers/index.ts +22 -0
- package/src/providers/magento/index.ts +387 -0
- package/src/providers/magento/magento.ts +5 -0
- package/src/providers/magento/queries.ts +267 -0
- package/src/providers/magento/transforms.ts +249 -0
- package/src/providers/medusa/auth.js +601 -0
- package/src/providers/medusa/auth.ts +496 -0
- package/src/providers/medusa/fulfillment-planner.ts +225 -0
- package/src/providers/medusa/index.js +2485 -0
- package/src/providers/medusa/index.ts +4239 -0
- package/src/providers/medusa/medusa.ts +8 -0
- package/src/providers/medusa/payment.js +265 -0
- package/src/providers/medusa/payment.ts +269 -0
- package/src/providers/medusa/transforms.js +505 -0
- package/src/providers/medusa/transforms.ts +625 -0
- package/src/providers/shopify/index.ts +444 -0
- package/src/providers/shopify/mutations.ts +102 -0
- package/src/providers/shopify/queries.ts +324 -0
- package/src/providers/shopify/shopify.ts +7 -0
- package/src/providers/shopify/transforms.ts +270 -0
- package/src/providers/spree/index.ts +344 -0
- package/src/providers/spree/spree.ts +5 -0
- package/src/providers/spree/transforms.ts +254 -0
- package/src/providers/woocommerce/index.ts +310 -0
- package/src/providers/woocommerce/transforms.ts +254 -0
- package/src/providers/woocommerce/woocommerce.ts +5 -0
- package/src/resilience/README.md +299 -0
- package/src/resilience/circuit-breaker.ts +222 -0
- package/src/resilience/index.ts +10 -0
- package/src/types/capabilities.types.js +141 -0
- package/src/types/capabilities.types.ts +366 -0
- package/src/types/index.ts +24 -0
- package/src/types/provider.js +9 -0
- package/src/types/provider.ts +9 -0
- package/src/types/ui-responses.ts +261 -0
- package/src/utils/city-coordinates.ts +40 -0
- package/src/utils/geolocation.ts +52 -0
- package/src/utils/payment-utils.js +152 -0
- package/src/utils/payment-utils.ts +203 -0
- package/tsconfig.json +15 -0
- 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
|
+
}
|