@thorprovider/adapters 4.3.0 → 5.0.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.
@@ -1,387 +0,0 @@
1
- /**
2
- * @thorprovider/adapters — Magento (Adobe Commerce) Provider
3
- * Implements CommerceProvider for Magento 2 GraphQL + REST APIs
4
- *
5
- * Uses GraphQL Storefront for read operations (products, categories, cart).
6
- * REST API for customer/order operations requiring auth tokens.
7
- * PaaS-first approach (not Adobe IMS/SaaS).
8
- */
9
-
10
- import type {
11
- Product,
12
- GetProductsOptions,
13
- Cart,
14
- CartLineInput,
15
- CartLineUpdate,
16
- Collection,
17
- CollectionProductsOptions,
18
- GetCollectionsOptions,
19
- ProductCategory,
20
- GetCategoriesOptions,
21
- Order,
22
- GetOrdersOptions,
23
- Customer,
24
- Address,
25
- PaymentMethod,
26
- PaymentMethodsOptions,
27
- ShippingMethod,
28
- StockLocation,
29
- InventoryLevel,
30
- StockValidation,
31
- FulfillmentOption,
32
- StorefrontContext,
33
- BackendCapabilities,
34
- } from '@thorprovider/types';
35
- import type { CommerceProvider, ProviderConfig } from '../../types/provider';
36
- import { MAGENTO_CAPABILITIES } from '../../types/capabilities.types';
37
- import { createLogger } from '../../core/logger';
38
- import { ProviderAPIError } from '../../core/errors';
39
- import { transformProduct, transformCart, transformCollection, transformOrder, transformCustomer } from './transforms';
40
- import {
41
- GET_PRODUCTS,
42
- GET_PRODUCT_BY_URL_KEY,
43
- GET_CATEGORIES,
44
- GET_CATEGORY_BY_URL_KEY,
45
- GET_CATEGORY_PRODUCTS,
46
- GET_CART,
47
- CREATE_EMPTY_CART,
48
- ADD_TO_CART,
49
- REMOVE_FROM_CART,
50
- UPDATE_CART_ITEMS,
51
- APPLY_COUPON,
52
- REMOVE_COUPON,
53
- GET_CUSTOMER,
54
- GET_CUSTOMER_ORDERS,
55
- } from './queries';
56
-
57
- /**
58
- * Magento provider configuration
59
- */
60
- export interface MagentoProviderConfig extends ProviderConfig {
61
- /** Magento base URL (e.g., 'https://mymagento.com') */
62
- baseUrl: string;
63
- /** Store code for multi-tenant (default: 'default') */
64
- storeCode?: string;
65
- /** Customer bearer token (for authenticated operations) */
66
- customerToken?: string;
67
- /** Integration access token (for admin-level operations) */
68
- accessToken?: string;
69
- /** Default currency code (default: 'USD') */
70
- currencyCode?: string;
71
- }
72
-
73
- export class MagentoProvider implements CommerceProvider {
74
- readonly name = 'magento';
75
- readonly version = '1.0.0';
76
- readonly capabilities: BackendCapabilities = MAGENTO_CAPABILITIES;
77
-
78
- private logger: ReturnType<typeof createLogger>;
79
- private baseUrl: string;
80
- private storeCode: string;
81
- private currencyCode: string;
82
- private graphqlEndpoint: string;
83
- private customerToken?: string;
84
- private accessToken?: string;
85
-
86
- constructor(config: MagentoProviderConfig) {
87
- this.logger = createLogger('MagentoProvider', config.debug);
88
- this.baseUrl = config.baseUrl.replace(/\/$/, '');
89
- this.storeCode = config.storeCode ?? 'default';
90
- this.currencyCode = config.currencyCode ?? 'USD';
91
- this.graphqlEndpoint = `${this.baseUrl}/graphql`;
92
- this.customerToken = config.customerToken;
93
- this.accessToken = config.accessToken;
94
-
95
- this.logger.info('Initializing Magento Provider', {
96
- baseUrl: this.baseUrl,
97
- storeCode: this.storeCode,
98
- });
99
- }
100
-
101
- private async graphql<T = any>(query: string, variables?: Record<string, any>): Promise<T> {
102
- const headers: Record<string, string> = {
103
- 'Content-Type': 'application/json',
104
- 'Store': this.storeCode,
105
- };
106
-
107
- if (this.customerToken) {
108
- headers['Authorization'] = `Bearer ${this.customerToken}`;
109
- }
110
-
111
- const response = await fetch(this.graphqlEndpoint, {
112
- method: 'POST',
113
- headers,
114
- body: JSON.stringify({ query, variables }),
115
- });
116
-
117
- if (!response.ok) {
118
- throw new ProviderAPIError(
119
- `Magento GraphQL error: ${response.status}`,
120
- 'MAGENTO_API_ERROR',
121
- response.status,
122
- );
123
- }
124
-
125
- const json = await response.json() as { data?: T; errors?: Array<{ message: string }> };
126
-
127
- if (json.errors?.length) {
128
- const message = json.errors.map((e) => e.message).join('; ');
129
- throw new ProviderAPIError(`Magento GraphQL error: ${message}`, 'MAGENTO_GRAPHQL_ERROR');
130
- }
131
-
132
- return json.data as T;
133
- }
134
-
135
- // ============================================
136
- // Product Operations (GraphQL)
137
- // ============================================
138
-
139
- async getProduct(handle: string): Promise<Product | undefined> {
140
- try {
141
- const data = await this.graphql<{ products: { items: any[] } }>(GET_PRODUCT_BY_URL_KEY, { urlKey: handle });
142
- return data.products.items[0] ? transformProduct(data.products.items[0], this.currencyCode) : undefined;
143
- } catch {
144
- return undefined;
145
- }
146
- }
147
-
148
- async getProducts(options?: GetProductsOptions): Promise<Product[]> {
149
- const variables: Record<string, any> = {
150
- pageSize: options?.first ?? 20,
151
- currentPage: 1,
152
- };
153
-
154
- if (options?.query) variables.search = options.query;
155
- if (options?.sortKey) {
156
- const sortMap: Record<string, Record<string, string>> = {
157
- 'price-asc': { price: 'ASC' },
158
- 'price-desc': { price: 'DESC' },
159
- 'title-asc': { name: 'ASC' },
160
- 'title-desc': { name: 'DESC' },
161
- 'created-desc': { created_at: 'DESC' },
162
- 'relevance': { relevance: 'DESC' },
163
- };
164
- variables.sort = sortMap[options.sortKey] ?? { relevance: 'DESC' };
165
- }
166
-
167
- const data = await this.graphql<{ products: { items: any[] } }>(GET_PRODUCTS, variables);
168
- return (data.products?.items ?? []).map((p) => transformProduct(p, this.currencyCode));
169
- }
170
-
171
- async getProductRecommendations(_productId: string): Promise<Product[]> {
172
- return [];
173
- }
174
-
175
- // ============================================
176
- // Collection/Category Operations (GraphQL)
177
- // ============================================
178
-
179
- async getCollection(handle: string): Promise<Collection | undefined> {
180
- try {
181
- const data = await this.graphql<{ categories: { items: any[] } }>(GET_CATEGORY_BY_URL_KEY, { urlKey: handle });
182
- return data.categories.items[0] ? transformCollection(data.categories.items[0]) : undefined;
183
- } catch {
184
- return undefined;
185
- }
186
- }
187
-
188
- async getCollections(options?: GetCollectionsOptions): Promise<Collection[]> {
189
- const data = await this.graphql<{ categories: { items: any[] } }>(GET_CATEGORIES, {
190
- pageSize: options?.limit ?? 50,
191
- });
192
- return (data.categories?.items ?? []).map(transformCollection);
193
- }
194
-
195
- async getCollectionProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
196
- const data = await this.graphql<{ products: { items: any[] } }>(GET_CATEGORY_PRODUCTS, {
197
- urlKey: handle,
198
- pageSize: options?.first ?? 20,
199
- });
200
- return (data.products?.items ?? []).map((p) => transformProduct(p, this.currencyCode));
201
- }
202
-
203
- async getCategory(handle: string): Promise<ProductCategory | undefined> {
204
- return this.getCollection(handle) as any;
205
- }
206
-
207
- async getCategories(options?: GetCategoriesOptions): Promise<ProductCategory[]> {
208
- return this.getCollections(options) as any;
209
- }
210
-
211
- async getCategoryProducts(handle: string, options?: CollectionProductsOptions): Promise<Product[]> {
212
- return this.getCollectionProducts(handle, options);
213
- }
214
-
215
- // ============================================
216
- // Cart Operations (GraphQL)
217
- // ============================================
218
-
219
- async createCart(): Promise<Cart> {
220
- const data = await this.graphql<{ createEmptyCart: string }>(CREATE_EMPTY_CART);
221
- const cartId = data.createEmptyCart;
222
- const cartData = await this.graphql<{ cart: any }>(GET_CART, { cartId });
223
- return transformCart(cartData.cart ?? { id: cartId, items: [], prices: {} }, this.currencyCode);
224
- }
225
-
226
- async getCart(cartId: string): Promise<Cart | undefined> {
227
- try {
228
- const data = await this.graphql<{ cart: any }>(GET_CART, { cartId });
229
- return data.cart ? transformCart(data.cart, this.currencyCode) : undefined;
230
- } catch {
231
- return undefined;
232
- }
233
- }
234
-
235
- async addToCart(cartId: string, lines: CartLineInput[]): Promise<Cart> {
236
- const cartItems = lines.map((l) => ({
237
- sku: l.merchandiseId,
238
- quantity: l.quantity,
239
- }));
240
-
241
- const data = await this.graphql<{ addProductsToCart: { cart: any; user_errors: any[] } }>(ADD_TO_CART, {
242
- cartId,
243
- cartItems,
244
- });
245
-
246
- if (data.addProductsToCart.user_errors?.length) {
247
- throw new ProviderAPIError(
248
- `Add to cart failed: ${data.addProductsToCart.user_errors.map((e: any) => e.message).join('; ')}`,
249
- 'MAGENTO_CART_ERROR',
250
- );
251
- }
252
-
253
- return transformCart(data.addProductsToCart.cart, this.currencyCode);
254
- }
255
-
256
- async removeFromCart(cartId: string, lineIds: string[]): Promise<Cart> {
257
- let cart: any;
258
- for (const itemUid of lineIds) {
259
- const data = await this.graphql<{ removeItemFromCart: { cart: any } }>(REMOVE_FROM_CART, {
260
- cartId,
261
- cartItemUid: itemUid,
262
- });
263
- cart = data.removeItemFromCart.cart;
264
- }
265
- return transformCart(cart ?? { id: cartId, items: [], prices: {} }, this.currencyCode);
266
- }
267
-
268
- async updateCart(cartId: string, lines: CartLineUpdate[]): Promise<Cart> {
269
- const cartItems = lines.map((l) => ({
270
- cart_item_uid: l.id,
271
- quantity: l.quantity,
272
- }));
273
-
274
- const data = await this.graphql<{ updateCartItems: { cart: any } }>(UPDATE_CART_ITEMS, {
275
- cartId,
276
- cartItems,
277
- });
278
-
279
- return transformCart(data.updateCartItems.cart, this.currencyCode);
280
- }
281
-
282
- // ============================================
283
- // Checkout Operations
284
- // ============================================
285
-
286
- async completeCheckout(_cartId: string): Promise<Order> {
287
- throw new ProviderAPIError(
288
- 'Magento checkout requires multi-step process (set addresses, shipping, payment, place order). Use the Magento checkout flow.',
289
- 'MAGENTO_CHECKOUT_MULTI_STEP',
290
- );
291
- }
292
-
293
- async getShippingMethods(_cartId: string): Promise<ShippingMethod[]> {
294
- return [];
295
- }
296
-
297
- async getRegionCountries(): Promise<import('@thorprovider/types').Country[]> {
298
- return [];
299
- }
300
-
301
- async addShippingMethod(_cartId: string, _methodId: string): Promise<Cart> {
302
- throw new ProviderAPIError(
303
- 'Magento shipping selection requires set-shipping-methods mutation.',
304
- 'MAGENTO_CHECKOUT_MULTI_STEP',
305
- );
306
- }
307
-
308
- async initiatePaymentSession(_cartId: string, _providerId: string): Promise<void> {
309
- // Magento handles payment via set-payment-method mutation
310
- }
311
-
312
- async applyDiscountCode(cartId: string, code: string): Promise<Cart> {
313
- const data = await this.graphql<{ applyCouponToCart: { cart: any } }>(APPLY_COUPON, {
314
- cartId,
315
- couponCode: code,
316
- });
317
- return transformCart(data.applyCouponToCart.cart, this.currencyCode);
318
- }
319
-
320
- async removeDiscountCode(cartId: string, _code: string): Promise<Cart> {
321
- const data = await this.graphql<{ removeCouponFromCart: { cart: any } }>(REMOVE_COUPON, {
322
- cartId,
323
- });
324
- return transformCart(data.removeCouponFromCart.cart, this.currencyCode);
325
- }
326
-
327
- // ============================================
328
- // Customer & Orders (GraphQL — requires customerToken)
329
- // ============================================
330
-
331
- async getOrders(_options: GetOrdersOptions): Promise<Order[]> {
332
- if (!this.customerToken) return [];
333
- try {
334
- const data = await this.graphql<{ customer: { orders: { items: any[] } } }>(GET_CUSTOMER_ORDERS, {
335
- pageSize: 20,
336
- currentPage: 1,
337
- });
338
- return (data.customer?.orders?.items ?? []).map((o) => transformOrder(o, this.currencyCode));
339
- } catch {
340
- return [];
341
- }
342
- }
343
-
344
- async getPaymentMethods(_options: PaymentMethodsOptions): Promise<PaymentMethod[]> {
345
- return [];
346
- }
347
-
348
- // ============================================
349
- // Inventory
350
- // ============================================
351
-
352
- async getStockLocations(): Promise<StockLocation[]> { return []; }
353
- async checkInventoryLevels(): Promise<InventoryLevel[]> { return []; }
354
- async validateCartStock(): Promise<StockValidation> { return { allAvailable: true, unavailableItems: [] }; }
355
- async getFulfillmentOptions(): Promise<FulfillmentOption[]> { return []; }
356
-
357
- // ============================================
358
- // Admin Operations
359
- // ============================================
360
-
361
- async cancelOrder(_orderId: string): Promise<Order> {
362
- throw new ProviderAPIError('Order cancellation requires Magento Admin API.', 'MAGENTO_ADMIN_REQUIRED');
363
- }
364
- async createFulfillment(_orderId: string): Promise<void> {
365
- throw new ProviderAPIError('Fulfillment requires Magento Admin API.', 'MAGENTO_ADMIN_REQUIRED');
366
- }
367
- async createRefund(_orderId: string): Promise<void> {
368
- throw new ProviderAPIError('Refunds require Magento Admin API.', 'MAGENTO_ADMIN_REQUIRED');
369
- }
370
- async getAdminOrders(): Promise<{ orders: Order[]; count: number }> { return { orders: [], count: 0 }; }
371
-
372
- // ============================================
373
- // Storefront / Multi-Tenant
374
- // ============================================
375
-
376
- async getStorefrontContext(): Promise<StorefrontContext> {
377
- return {
378
- id: this.storeCode,
379
- name: `Magento ${this.storeCode}`,
380
- platformType: 'magento',
381
- requiresProductScoping: true,
382
- currencyCode: this.currencyCode,
383
- };
384
- }
385
-
386
- isStorefrontRequired(): boolean { return true; }
387
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Magento (Adobe Commerce) Provider — barrel exports
3
- */
4
- export { MagentoProvider } from './index';
5
- export type { MagentoProviderConfig } from './index';
@@ -1,267 +0,0 @@
1
- /**
2
- * Magento GraphQL Queries for Storefront
3
- * Used by MagentoProvider to query products, categories, cart, and customer data.
4
- */
5
-
6
- export const PRODUCT_FIELDS = `
7
- id
8
- uid
9
- sku
10
- name
11
- url_key
12
- stock_status
13
- short_description { html }
14
- description { html }
15
- meta_title
16
- meta_description
17
- created_at
18
- updated_at
19
- price_range {
20
- minimum_price {
21
- regular_price { value currency }
22
- final_price { value currency }
23
- }
24
- maximum_price {
25
- regular_price { value currency }
26
- final_price { value currency }
27
- }
28
- }
29
- small_image { url label }
30
- image { url label }
31
- media_gallery {
32
- url
33
- label
34
- }
35
- ... on ConfigurableProduct {
36
- configurable_options {
37
- attribute_code
38
- attribute_id
39
- label
40
- values {
41
- label
42
- value_index
43
- }
44
- }
45
- variants {
46
- product {
47
- id uid sku name
48
- stock_status
49
- price_range {
50
- minimum_price {
51
- final_price { value currency }
52
- }
53
- }
54
- small_image { url label }
55
- }
56
- attributes {
57
- label
58
- value_index
59
- }
60
- }
61
- }
62
- `;
63
-
64
- export const GET_PRODUCTS = `
65
- query GetProducts($search: String, $pageSize: Int, $currentPage: Int, $sort: ProductAttributeSortInput) {
66
- products(search: $search, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {
67
- items { ${PRODUCT_FIELDS} }
68
- total_count
69
- }
70
- }
71
- `;
72
-
73
- export const GET_PRODUCT_BY_URL_KEY = `
74
- query GetProductByUrlKey($urlKey: String!) {
75
- products(filter: { url_key: { eq: $urlKey } }, pageSize: 1) {
76
- items { ${PRODUCT_FIELDS} }
77
- }
78
- }
79
- `;
80
-
81
- export const GET_CATEGORIES = `
82
- query GetCategories($pageSize: Int) {
83
- categories(pageSize: $pageSize) {
84
- items {
85
- id uid name url_key url_path description meta_title meta_description
86
- }
87
- }
88
- }
89
- `;
90
-
91
- export const GET_CATEGORY_BY_URL_KEY = `
92
- query GetCategoryByUrlKey($urlKey: String!) {
93
- categories(filters: { url_key: { eq: $urlKey } }, pageSize: 1) {
94
- items {
95
- id uid name url_key url_path description meta_title meta_description
96
- }
97
- }
98
- }
99
- `;
100
-
101
- export const GET_CATEGORY_PRODUCTS = `
102
- query GetCategoryProducts($urlKey: String!, $pageSize: Int) {
103
- products(filter: { category_url_key: { eq: $urlKey } }, pageSize: $pageSize) {
104
- items { ${PRODUCT_FIELDS} }
105
- total_count
106
- }
107
- }
108
- `;
109
-
110
- export const GET_CART = `
111
- query GetCart($cartId: String!) {
112
- cart(cart_id: $cartId) {
113
- id
114
- items {
115
- id uid quantity
116
- product {
117
- id uid sku name url_key
118
- small_image { url }
119
- thumbnail { url }
120
- price_range {
121
- minimum_price {
122
- final_price { value currency }
123
- }
124
- }
125
- }
126
- prices {
127
- price { value currency }
128
- row_total { value currency }
129
- }
130
- ... on ConfigurableCartItem {
131
- configurable_options {
132
- option_label
133
- value_label
134
- }
135
- }
136
- }
137
- prices {
138
- grand_total { value currency }
139
- subtotal_excluding_tax { value currency }
140
- subtotal_including_tax { value currency }
141
- applied_taxes {
142
- amount { value currency }
143
- label
144
- }
145
- }
146
- }
147
- }
148
- `;
149
-
150
- export const CREATE_EMPTY_CART = `
151
- mutation CreateEmptyCart {
152
- createEmptyCart
153
- }
154
- `;
155
-
156
- export const ADD_TO_CART = `
157
- mutation AddToCart($cartId: String!, $cartItems: [CartItemInput!]!) {
158
- addProductsToCart(cartId: $cartId, cartItems: $cartItems) {
159
- cart {
160
- id
161
- items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
162
- prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
163
- }
164
- user_errors { code message }
165
- }
166
- }
167
- `;
168
-
169
- export const REMOVE_FROM_CART = `
170
- mutation RemoveFromCart($cartId: String!, $cartItemUid: ID!) {
171
- removeItemFromCart(input: { cart_id: $cartId, cart_item_uid: $cartItemUid }) {
172
- cart {
173
- id
174
- items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
175
- prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
176
- }
177
- }
178
- }
179
- `;
180
-
181
- export const UPDATE_CART_ITEMS = `
182
- mutation UpdateCartItems($cartId: String!, $cartItems: [CartItemUpdateInput!]!) {
183
- updateCartItems(input: { cart_id: $cartId, cart_items: $cartItems }) {
184
- cart {
185
- id
186
- items { id uid quantity product { sku name } prices { price { value currency } row_total { value currency } } }
187
- prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
188
- }
189
- }
190
- }
191
- `;
192
-
193
- export const APPLY_COUPON = `
194
- mutation ApplyCoupon($cartId: String!, $couponCode: String!) {
195
- applyCouponToCart(input: { cart_id: $cartId, coupon_code: $couponCode }) {
196
- cart {
197
- id
198
- prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
199
- }
200
- }
201
- }
202
- `;
203
-
204
- export const REMOVE_COUPON = `
205
- mutation RemoveCoupon($cartId: String!) {
206
- removeCouponFromCart(input: { cart_id: $cartId }) {
207
- cart {
208
- id
209
- prices { grand_total { value currency } subtotal_excluding_tax { value currency } }
210
- }
211
- }
212
- }
213
- `;
214
-
215
- export const GET_CUSTOMER = `
216
- query GetCustomer {
217
- customer {
218
- id
219
- email
220
- firstname
221
- lastname
222
- addresses {
223
- id
224
- firstname lastname company
225
- street city
226
- region { region region_code }
227
- postcode
228
- country_code
229
- telephone
230
- default_shipping
231
- default_billing
232
- }
233
- }
234
- }
235
- `;
236
-
237
- export const GET_CUSTOMER_ORDERS = `
238
- query GetCustomerOrders($pageSize: Int, $currentPage: Int) {
239
- customer {
240
- orders(pageSize: $pageSize, currentPage: $currentPage) {
241
- items {
242
- id order_number order_date status
243
- total {
244
- grand_total { value currency }
245
- subtotal { value currency }
246
- total_tax { value currency }
247
- total_shipping { value currency }
248
- }
249
- items {
250
- id product_name product_sku
251
- quantity_ordered
252
- product_sale_price { value currency }
253
- }
254
- shipping_address {
255
- firstname lastname street city
256
- region postcode country_code telephone
257
- }
258
- billing_address {
259
- firstname lastname street city
260
- region postcode country_code telephone
261
- }
262
- }
263
- total_count
264
- }
265
- }
266
- }
267
- `;