@thorprovider/adapters 4.2.1 → 4.3.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 (37) hide show
  1. package/PROVIDER_IMPLEMENTATION_ANALYSIS.md +659 -0
  2. package/dist/index.d.mts +160 -1
  3. package/dist/index.d.ts +160 -1
  4. package/dist/index.js +384 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +384 -2
  7. package/dist/index.mjs.map +1 -1
  8. package/docs/RETRY_SOLUTION_SUMMARY.md +26 -11
  9. package/docs/authentication.md +115 -62
  10. package/docs/capabilities-system.md +4 -2
  11. package/docs/category-customization.md +37 -29
  12. package/docs/fetch-reliability-solution.md +127 -278
  13. package/docs/getting-started.md +43 -29
  14. package/docs/profile-management.md +68 -38
  15. package/docs/services-overview.md +4 -2
  16. package/package.json +1 -1
  17. package/quick-reference.md +33 -0
  18. package/src/providers/medusa/admin.test.ts +404 -0
  19. package/src/providers/medusa/admin.ts +442 -0
  20. package/src/providers/medusa/auth.test.ts +137 -0
  21. package/src/providers/medusa/auth.ts +96 -1
  22. package/src/providers/medusa/index.ts +21 -1
  23. package/src/providers/shopify/auth.ts +6 -0
  24. package/src/resilience/README.md +70 -48
  25. package/src/core/errors.js +0 -161
  26. package/src/core/fetch-utils.js +0 -240
  27. package/src/core/logger.js +0 -63
  28. package/src/metadata/channel-scope-validation.js +0 -136
  29. package/src/metadata/channel-scope.js +0 -174
  30. package/src/metadata/index.js +0 -31
  31. package/src/providers/medusa/auth.js +0 -601
  32. package/src/providers/medusa/index.js +0 -2485
  33. package/src/providers/medusa/payment.js +0 -265
  34. package/src/providers/medusa/transforms.js +0 -505
  35. package/src/types/capabilities.types.js +0 -141
  36. package/src/types/provider.js +0 -9
  37. package/src/utils/payment-utils.js +0 -152
@@ -1,240 +0,0 @@
1
- "use strict";
2
- /**
3
- * @thorprovider/adapters v2.0
4
- * Fetch Utilities - Retry logic, timeouts, and error handling
5
- *
6
- * This module provides enhanced fetch capabilities with:
7
- * - Automatic retries with exponential backoff
8
- * - Configurable timeouts
9
- * - Better error handling for network failures
10
- */
11
- var __assign = (this && this.__assign) || function () {
12
- __assign = Object.assign || function(t) {
13
- for (var s, i = 1, n = arguments.length; i < n; i++) {
14
- s = arguments[i];
15
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
16
- t[p] = s[p];
17
- }
18
- return t;
19
- };
20
- return __assign.apply(this, arguments);
21
- };
22
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
23
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
24
- return new (P || (P = Promise))(function (resolve, reject) {
25
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
26
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
27
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
28
- step((generator = generator.apply(thisArg, _arguments || [])).next());
29
- });
30
- };
31
- var __generator = (this && this.__generator) || function (thisArg, body) {
32
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
33
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
34
- function verb(n) { return function (v) { return step([n, v]); }; }
35
- function step(op) {
36
- if (f) throw new TypeError("Generator is already executing.");
37
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
38
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
39
- if (y = 0, t) op = [op[0] & 2, t.value];
40
- switch (op[0]) {
41
- case 0: case 1: t = op; break;
42
- case 4: _.label++; return { value: op[1], done: false };
43
- case 5: _.label++; y = op[1]; op = [0]; continue;
44
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
45
- default:
46
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
47
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
48
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
49
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
50
- if (t[2]) _.ops.pop();
51
- _.trys.pop(); continue;
52
- }
53
- op = body.call(thisArg, _);
54
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
55
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
56
- }
57
- };
58
- Object.defineProperty(exports, "__esModule", { value: true });
59
- exports.fetchWithRetry = fetchWithRetry;
60
- exports.createFetchWithRetry = createFetchWithRetry;
61
- var logger_1 = require("./logger");
62
- var logger = (0, logger_1.createLogger)('FetchUtils');
63
- var DEFAULT_RETRY_CONFIG = {
64
- maxRetries: 3,
65
- initialDelayMs: 300,
66
- backoffMultiplier: 2,
67
- maxDelayMs: 5000,
68
- timeoutMs: 10000, // 10 seconds
69
- retryableStatusCodes: [408, 429, 500, 502, 503, 504],
70
- };
71
- /**
72
- * Sleep utility for delays between retries
73
- */
74
- function sleep(ms) {
75
- return new Promise(function (resolve) { return setTimeout(resolve, ms); });
76
- }
77
- /**
78
- * Calculate delay for exponential backoff
79
- */
80
- function calculateDelay(attempt, config) {
81
- var delay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
82
- return Math.min(delay, config.maxDelayMs);
83
- }
84
- /**
85
- * Check if error is retryable
86
- */
87
- function isRetryableError(error, config) {
88
- var _a, _b;
89
- // Network errors (fetch failed, connection refused, etc.)
90
- if (error.name === 'TypeError' || ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('fetch failed'))) {
91
- return true;
92
- }
93
- // Timeout errors
94
- if (error.name === 'AbortError' || ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('timeout'))) {
95
- return true;
96
- }
97
- // HTTP status codes
98
- if (error.status && config.retryableStatusCodes.includes(error.status)) {
99
- return true;
100
- }
101
- return false;
102
- }
103
- /**
104
- * Enhanced fetch with retry logic and timeout
105
- *
106
- * @example
107
- * ```typescript
108
- * import { fetchWithRetry } from '@thorprovider/adapters/core/fetch-utils';
109
- *
110
- * // Basic usage with defaults (3 retries, 10s timeout)
111
- * const response = await fetchWithRetry('https://api.example.com/products');
112
- *
113
- * // Custom configuration
114
- * const response = await fetchWithRetry(
115
- * 'https://api.example.com/products',
116
- * {
117
- * method: 'POST',
118
- * body: JSON.stringify({ name: 'Product' }),
119
- * },
120
- * {
121
- * maxRetries: 5,
122
- * timeoutMs: 15000, // 15 seconds
123
- * initialDelayMs: 500,
124
- * }
125
- * );
126
- * ```
127
- */
128
- function fetchWithRetry(url, init, retryConfig) {
129
- return __awaiter(this, void 0, void 0, function () {
130
- var config, lastError, _loop_1, attempt, state_1;
131
- return __generator(this, function (_a) {
132
- switch (_a.label) {
133
- case 0:
134
- config = __assign(__assign({}, DEFAULT_RETRY_CONFIG), retryConfig);
135
- _loop_1 = function (attempt) {
136
- var controller_1, timeoutId, fetchOptions, response, error, error_1, shouldRetry, delay;
137
- return __generator(this, function (_b) {
138
- switch (_b.label) {
139
- case 0:
140
- _b.trys.push([0, 2, , 6]);
141
- controller_1 = new AbortController();
142
- timeoutId = setTimeout(function () { return controller_1.abort(); }, config.timeoutMs);
143
- fetchOptions = __assign(__assign({}, init), { signal: controller_1.signal });
144
- logger.debug('Fetch attempt', {
145
- url: url,
146
- attempt: attempt + 1,
147
- maxAttempts: config.maxRetries + 1,
148
- timeout: config.timeoutMs,
149
- });
150
- return [4 /*yield*/, fetch(url, fetchOptions)];
151
- case 1:
152
- response = _b.sent();
153
- // Clear timeout
154
- clearTimeout(timeoutId);
155
- // Check if status code is retryable
156
- if (!response.ok && config.retryableStatusCodes.includes(response.status)) {
157
- error = new Error("HTTP ".concat(response.status, ": ").concat(response.statusText));
158
- error.status = response.status;
159
- throw error;
160
- }
161
- // Success!
162
- logger.debug('Fetch successful', { url: url, attempt: attempt + 1 });
163
- return [2 /*return*/, { value: response }];
164
- case 2:
165
- error_1 = _b.sent();
166
- lastError = error_1;
167
- // Clear timeout if it exists
168
- if (error_1.name === 'AbortError') {
169
- lastError = new Error("Request timeout after ".concat(config.timeoutMs, "ms"));
170
- lastError.name = 'TimeoutError';
171
- }
172
- shouldRetry = attempt < config.maxRetries && isRetryableError(error_1, config);
173
- if (!shouldRetry) return [3 /*break*/, 4];
174
- delay = calculateDelay(attempt, config);
175
- logger.warn('Fetch failed, retrying...', {
176
- url: url,
177
- attempt: attempt + 1,
178
- maxAttempts: config.maxRetries + 1,
179
- nextRetryIn: "".concat(delay, "ms"),
180
- error: error_1.message,
181
- });
182
- return [4 /*yield*/, sleep(delay)];
183
- case 3:
184
- _b.sent();
185
- return [3 /*break*/, 5];
186
- case 4:
187
- logger.error('Fetch failed after all retries', {
188
- url: url,
189
- totalAttempts: attempt + 1,
190
- error: error_1.message,
191
- });
192
- return [2 /*return*/, "break"];
193
- case 5: return [3 /*break*/, 6];
194
- case 6: return [2 /*return*/];
195
- }
196
- });
197
- };
198
- attempt = 0;
199
- _a.label = 1;
200
- case 1:
201
- if (!(attempt <= config.maxRetries)) return [3 /*break*/, 4];
202
- return [5 /*yield**/, _loop_1(attempt)];
203
- case 2:
204
- state_1 = _a.sent();
205
- if (typeof state_1 === "object")
206
- return [2 /*return*/, state_1.value];
207
- if (state_1 === "break")
208
- return [3 /*break*/, 4];
209
- _a.label = 3;
210
- case 3:
211
- attempt++;
212
- return [3 /*break*/, 1];
213
- case 4:
214
- // All retries exhausted, throw the last error
215
- throw lastError;
216
- }
217
- });
218
- });
219
- }
220
- /**
221
- * Create a fetch function with custom retry configuration
222
- * Useful for creating a configured fetch instance
223
- *
224
- * @example
225
- * ```typescript
226
- * import { createFetchWithRetry } from '@thorprovider/adapters/core/fetch-utils';
227
- *
228
- * // Create configured fetch
229
- * const customFetch = createFetchWithRetry({
230
- * maxRetries: 5,
231
- * timeoutMs: 15000,
232
- * });
233
- *
234
- * // Use it like regular fetch
235
- * const response = await customFetch('https://api.example.com/products');
236
- * ```
237
- */
238
- function createFetchWithRetry(retryConfig) {
239
- return function (url, init) { return fetchWithRetry(url, init, retryConfig); };
240
- }
@@ -1,63 +0,0 @@
1
- "use strict";
2
- /**
3
- * @thorprovider/adapters v2.0
4
- * Core logger utility
5
- */
6
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
7
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
8
- if (ar || !(i in from)) {
9
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
10
- ar[i] = from[i];
11
- }
12
- }
13
- return to.concat(ar || Array.prototype.slice.call(from));
14
- };
15
- Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.createLogger = createLogger;
17
- /**
18
- * Create a logger instance
19
- */
20
- function createLogger(namespace, enabled) {
21
- if (enabled === void 0) { enabled = false; }
22
- var log = function (level, message) {
23
- var args = [];
24
- for (var _i = 2; _i < arguments.length; _i++) {
25
- args[_i - 2] = arguments[_i];
26
- }
27
- if (!enabled && level !== 'error')
28
- return;
29
- var timestamp = new Date().toISOString();
30
- var prefix = "[".concat(timestamp, "] [@thorprovider/adapters:").concat(namespace, "] ").concat(level.toUpperCase(), ":");
31
- console[level].apply(console, __spreadArray([prefix, message], args, false));
32
- };
33
- return {
34
- debug: function (message) {
35
- var args = [];
36
- for (var _i = 1; _i < arguments.length; _i++) {
37
- args[_i - 1] = arguments[_i];
38
- }
39
- return log.apply(void 0, __spreadArray(['debug', message], args, false));
40
- },
41
- info: function (message) {
42
- var args = [];
43
- for (var _i = 1; _i < arguments.length; _i++) {
44
- args[_i - 1] = arguments[_i];
45
- }
46
- return log.apply(void 0, __spreadArray(['info', message], args, false));
47
- },
48
- warn: function (message) {
49
- var args = [];
50
- for (var _i = 1; _i < arguments.length; _i++) {
51
- args[_i - 1] = arguments[_i];
52
- }
53
- return log.apply(void 0, __spreadArray(['warn', message], args, false));
54
- },
55
- error: function (message) {
56
- var args = [];
57
- for (var _i = 1; _i < arguments.length; _i++) {
58
- args[_i - 1] = arguments[_i];
59
- }
60
- return log.apply(void 0, __spreadArray(['error', message], args, false));
61
- },
62
- };
63
- }
@@ -1,136 +0,0 @@
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
- }
@@ -1,174 +0,0 @@
1
- "use strict";
2
- /**
3
- * Channel Scope — Backend-Agnostic Multi-Tenant Helpers
4
- *
5
- * Provides utilities for tagging, reading and filtering entity metadata by
6
- * sales-channel (or any future tenant key). Works with any backend that
7
- * exposes a writable `metadata: Record<string, unknown>` field on its
8
- * entities (Medusa, Shopify metafields, WooCommerce meta, etc.).
9
- *
10
- * ## Design decisions
11
- *
12
- * | Decision | Rationale |
13
- * |----------|-----------|
14
- * | Flat key (`sales_channel_id`) | Simpler queries, no prefix parsing required |
15
- * | Metadata, not custom fields | Zero DB migrations; works across backends |
16
- * | Pure functions | No side effects → easily testable; usable in any layer |
17
- * | `assigned_at` timestamp | Audit trail for dropshipper / admin assignment |
18
- *
19
- * @module metadata/channel-scope
20
- */
21
- Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.CHANNEL_ASSIGNED_BY_KEY = exports.CHANNEL_ASSIGNED_AT_KEY = exports.CHANNEL_SCOPE_KEY = void 0;
23
- exports.buildChannelScopeMetadata = buildChannelScopeMetadata;
24
- exports.getChannelScope = getChannelScope;
25
- exports.isScopedToChannel = isScopedToChannel;
26
- exports.isGlobalEntity = isGlobalEntity;
27
- exports.filterByChannel = filterByChannel;
28
- exports.filterGlobalOnly = filterGlobalOnly;
29
- exports.partitionByChannel = partitionByChannel;
30
- // ────────────────────────────────────────────────────────────────────
31
- // Constants
32
- // ────────────────────────────────────────────────────────────────────
33
- /**
34
- * Well-known metadata key used to store the owning sales-channel on any
35
- * entity that Medusa (or the backend) does not natively scope.
36
- *
37
- * Convention: every entity whose metadata contains this key is
38
- * considered "owned" by the referenced channel. Entities without it
39
- * are global / unscoped.
40
- */
41
- exports.CHANNEL_SCOPE_KEY = 'sales_channel_id';
42
- /**
43
- * Optional audit key — ISO-8601 timestamp of when the channel was assigned.
44
- */
45
- exports.CHANNEL_ASSIGNED_AT_KEY = 'channel_assigned_at';
46
- /**
47
- * Optional audit key — who assigned the entity to the channel.
48
- * Value is a free-form string (admin email, user id, "system", etc.).
49
- */
50
- exports.CHANNEL_ASSIGNED_BY_KEY = 'channel_assigned_by';
51
- // ────────────────────────────────────────────────────────────────────
52
- // Builders
53
- // ────────────────────────────────────────────────────────────────────
54
- /**
55
- * Build channel-scope metadata payload.
56
- *
57
- * The returned object should be **merged** into the entity's existing
58
- * `metadata` field when calling `updateCustomer`, `updateOrder`, etc.
59
- *
60
- * @param channelId - Sales-channel ID (e.g. `"sc_01HXXX"`)
61
- * @param assignedBy - Optional identifier of who performed the assignment
62
- * @returns Metadata fragment ready to spread into an update payload
63
- *
64
- * @example
65
- * ```typescript
66
- * const scope = buildChannelScopeMetadata('sc_01HXXX', 'admin@store.com');
67
- * await commerce.updateCustomer(customerId, {
68
- * metadata: { ...existingMeta, ...scope },
69
- * });
70
- * ```
71
- */
72
- function buildChannelScopeMetadata(channelId, assignedBy) {
73
- var _a;
74
- var payload = (_a = {},
75
- _a[exports.CHANNEL_SCOPE_KEY] = channelId,
76
- _a[exports.CHANNEL_ASSIGNED_AT_KEY] = new Date().toISOString(),
77
- _a);
78
- if (assignedBy) {
79
- payload[exports.CHANNEL_ASSIGNED_BY_KEY] = assignedBy;
80
- }
81
- return payload;
82
- }
83
- // ────────────────────────────────────────────────────────────────────
84
- // Readers
85
- // ────────────────────────────────────────────────────────────────────
86
- /**
87
- * Read the channel-scope value from an entity's metadata.
88
- *
89
- * @returns The channel ID string, or `null` if the entity has no scope set.
90
- */
91
- function getChannelScope(entity) {
92
- var _a;
93
- var value = (_a = entity.metadata) === null || _a === void 0 ? void 0 : _a[exports.CHANNEL_SCOPE_KEY];
94
- return typeof value === 'string' && value.length > 0 ? value : null;
95
- }
96
- /**
97
- * Check if an entity is scoped to a specific channel.
98
- *
99
- * @param entity - Any entity with optional `metadata`
100
- * @param channelId - Channel ID to match against
101
- * @returns `true` if entity's metadata contains a matching scope key
102
- */
103
- function isScopedToChannel(entity, channelId) {
104
- return getChannelScope(entity) === channelId;
105
- }
106
- /**
107
- * Check if an entity has **no** channel scope (i.e. it is "global").
108
- */
109
- function isGlobalEntity(entity) {
110
- return getChannelScope(entity) === null;
111
- }
112
- // ────────────────────────────────────────────────────────────────────
113
- // Filters
114
- // ────────────────────────────────────────────────────────────────────
115
- /**
116
- * Filter an array of entities to only those belonging to a channel.
117
- *
118
- * If `channelId` is `undefined` or empty, returns the full array unfiltered.
119
- * This preserves backward compatibility for admin views that intentionally
120
- * list all entities regardless of tenant.
121
- *
122
- * @param entities - Array of channel-scopeable entities
123
- * @param channelId - Optional channel ID to filter by
124
- * @returns Filtered (or unfiltered) array
125
- *
126
- * @example
127
- * ```typescript
128
- * const allCustomers = await commerce.getCustomers();
129
- * const myCustomers = filterByChannel(allCustomers, salesChannelId);
130
- * ```
131
- */
132
- function filterByChannel(entities, channelId) {
133
- if (!channelId)
134
- return entities;
135
- return entities.filter(function (e) { return isScopedToChannel(e, channelId); });
136
- }
137
- /**
138
- * Filter an array of entities to only those that are global (unscoped).
139
- *
140
- * Useful for admin views that want to list entities not yet assigned to
141
- * any channel.
142
- */
143
- function filterGlobalOnly(entities) {
144
- return entities.filter(isGlobalEntity);
145
- }
146
- /**
147
- * Partition entities into `{ scoped, global }` groups.
148
- *
149
- * @param entities - Array of channel-scopeable entities
150
- * @param channelId - Channel ID to partition against
151
- * @returns Object with `scoped` (matching channel) and `global` (no scope) arrays
152
- *
153
- * @example
154
- * ```typescript
155
- * const { scoped, global } = partitionByChannel(customers, 'sc_123');
156
- * // scoped → customers assigned to sc_123
157
- * // global → customers not assigned to any channel
158
- * ```
159
- */
160
- function partitionByChannel(entities, channelId) {
161
- var scoped = [];
162
- var global = [];
163
- for (var _i = 0, entities_1 = entities; _i < entities_1.length; _i++) {
164
- var entity = entities_1[_i];
165
- if (isScopedToChannel(entity, channelId)) {
166
- scoped.push(entity);
167
- }
168
- else if (isGlobalEntity(entity)) {
169
- global.push(entity);
170
- }
171
- // Entities scoped to a *different* channel are silently excluded.
172
- }
173
- return { scoped: scoped, global: global };
174
- }
@@ -1,31 +0,0 @@
1
- "use strict";
2
- /**
3
- * Metadata — Channel Scope Utilities
4
- *
5
- * Backend-agnostic helpers for multi-tenant channel scoping.
6
- * Used by L2 adapters to tag, validate and filter entities by sales channel.
7
- *
8
- * @module metadata
9
- */
10
- Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.assertEntityBelongsToChannel = exports.validateChannelScopeAssignment = exports.validateAssignedBy = exports.validateEntityId = exports.validateChannelId = exports.ChannelScopeValidationError = exports.partitionByChannel = exports.filterGlobalOnly = exports.filterByChannel = exports.isGlobalEntity = exports.isScopedToChannel = exports.getChannelScope = exports.buildChannelScopeMetadata = exports.CHANNEL_ASSIGNED_BY_KEY = exports.CHANNEL_ASSIGNED_AT_KEY = exports.CHANNEL_SCOPE_KEY = void 0;
12
- // Builders, readers & filters
13
- var channel_scope_1 = require("./channel-scope");
14
- Object.defineProperty(exports, "CHANNEL_SCOPE_KEY", { enumerable: true, get: function () { return channel_scope_1.CHANNEL_SCOPE_KEY; } });
15
- Object.defineProperty(exports, "CHANNEL_ASSIGNED_AT_KEY", { enumerable: true, get: function () { return channel_scope_1.CHANNEL_ASSIGNED_AT_KEY; } });
16
- Object.defineProperty(exports, "CHANNEL_ASSIGNED_BY_KEY", { enumerable: true, get: function () { return channel_scope_1.CHANNEL_ASSIGNED_BY_KEY; } });
17
- Object.defineProperty(exports, "buildChannelScopeMetadata", { enumerable: true, get: function () { return channel_scope_1.buildChannelScopeMetadata; } });
18
- Object.defineProperty(exports, "getChannelScope", { enumerable: true, get: function () { return channel_scope_1.getChannelScope; } });
19
- Object.defineProperty(exports, "isScopedToChannel", { enumerable: true, get: function () { return channel_scope_1.isScopedToChannel; } });
20
- Object.defineProperty(exports, "isGlobalEntity", { enumerable: true, get: function () { return channel_scope_1.isGlobalEntity; } });
21
- Object.defineProperty(exports, "filterByChannel", { enumerable: true, get: function () { return channel_scope_1.filterByChannel; } });
22
- Object.defineProperty(exports, "filterGlobalOnly", { enumerable: true, get: function () { return channel_scope_1.filterGlobalOnly; } });
23
- Object.defineProperty(exports, "partitionByChannel", { enumerable: true, get: function () { return channel_scope_1.partitionByChannel; } });
24
- // Validation
25
- var channel_scope_validation_1 = require("./channel-scope-validation");
26
- Object.defineProperty(exports, "ChannelScopeValidationError", { enumerable: true, get: function () { return channel_scope_validation_1.ChannelScopeValidationError; } });
27
- Object.defineProperty(exports, "validateChannelId", { enumerable: true, get: function () { return channel_scope_validation_1.validateChannelId; } });
28
- Object.defineProperty(exports, "validateEntityId", { enumerable: true, get: function () { return channel_scope_validation_1.validateEntityId; } });
29
- Object.defineProperty(exports, "validateAssignedBy", { enumerable: true, get: function () { return channel_scope_validation_1.validateAssignedBy; } });
30
- Object.defineProperty(exports, "validateChannelScopeAssignment", { enumerable: true, get: function () { return channel_scope_validation_1.validateChannelScopeAssignment; } });
31
- Object.defineProperty(exports, "assertEntityBelongsToChannel", { enumerable: true, get: function () { return channel_scope_validation_1.assertEntityBelongsToChannel; } });