@reactionary/medusa 0.10.51 → 0.10.52
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.
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
import { FetchError } from "@medusajs/js-sdk";
|
|
32
32
|
import {} from "@medusajs/types";
|
|
33
33
|
import createDebug from "debug";
|
|
34
|
+
import { MedusaAdminAPI } from "../core/client.js";
|
|
35
|
+
import { MedusaCachedPluginListSchema } from "../schema/medusa.schema.js";
|
|
34
36
|
import {
|
|
35
37
|
handleProviderError
|
|
36
38
|
} from "../utils/medusa-helpers.js";
|
|
@@ -52,7 +54,87 @@ class MedusaCartCapability extends CartCapability {
|
|
|
52
54
|
* example: this.includedFields = [includedFields, '+discounts.*'].join(',');
|
|
53
55
|
*/
|
|
54
56
|
includedFields = ["+items.*", "+items.adjustments.*", "+shipping_methods.adjustments.*"].join(",");
|
|
57
|
+
/**
|
|
58
|
+
* Fields requested when listing owned carts, both via the cart-ownership
|
|
59
|
+
* endpoint and via the session-tracked fallback. Every entry must be in
|
|
60
|
+
* the allowed field set of both routes; `customer.id` stands in for
|
|
61
|
+
* `customer_id`, which is not an allowed field (see normalizeOwnedCart).
|
|
62
|
+
*/
|
|
63
|
+
ownedCartListFields = ["id", "updated_at", "metadata", "items.id", "customer.id"].join(",");
|
|
64
|
+
/** Sort order for the server-side owned-carts listing. */
|
|
65
|
+
ownedCartListOrder = "-updated_at";
|
|
66
|
+
/**
|
|
67
|
+
* Package name of the optional backend plugin that serves
|
|
68
|
+
* `GET /store/customers/me/carts` (a logged-in customer's open carts).
|
|
69
|
+
*/
|
|
70
|
+
cartOwnershipPluginName = "@solteq-excom/medusa-cart-ownership";
|
|
71
|
+
/**
|
|
72
|
+
* How long a backend's plugin list stays cached. Bounds how late a newly
|
|
73
|
+
* (un)installed cart-ownership plugin is noticed.
|
|
74
|
+
*/
|
|
75
|
+
pluginDetectionTtlSeconds = 900;
|
|
55
76
|
async listCarts(payload) {
|
|
77
|
+
let data = null;
|
|
78
|
+
if (await this.shouldUseOwnershipEndpoint(payload)) {
|
|
79
|
+
data = await this.listCartsViaOwnershipEndpoint(payload);
|
|
80
|
+
}
|
|
81
|
+
data ??= await this.listCartsFromSession(payload);
|
|
82
|
+
return success(this.factory.parseCartPaginatedSearchResult(this.context, data, payload));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The server-side owned-carts listing only applies to a logged-in
|
|
86
|
+
* customer's own carts: a company bucket and anonymous/guest carts are
|
|
87
|
+
* only known to the session, and the backend only serves the route when
|
|
88
|
+
* the cart-ownership plugin is installed.
|
|
89
|
+
*/
|
|
90
|
+
async shouldUseOwnershipEndpoint(payload) {
|
|
91
|
+
if (payload.search.company) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (this.context.session.identityContext.identity.type !== "Registered") {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return await this.isCartOwnershipPluginEnabled();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Lists the customer's open carts through the cart-ownership plugin's
|
|
101
|
+
* `GET /store/customers/me/carts`. Returns null when the backend turns
|
|
102
|
+
* out not to serve the route after all (404: plugin configured but not
|
|
103
|
+
* built; 401: the token is no longer valid), so the caller falls back to
|
|
104
|
+
* the session-tracked list. Other errors are real backend faults and
|
|
105
|
+
* propagate.
|
|
106
|
+
*/
|
|
107
|
+
async listCartsViaOwnershipEndpoint(payload) {
|
|
108
|
+
const client = await this.getClient();
|
|
109
|
+
const { pageNumber, pageSize } = payload.search.paginationOptions;
|
|
110
|
+
try {
|
|
111
|
+
const response = await client.client.fetch("/store/customers/me/carts", {
|
|
112
|
+
method: "GET",
|
|
113
|
+
query: {
|
|
114
|
+
fields: this.ownedCartListFields,
|
|
115
|
+
limit: pageSize,
|
|
116
|
+
offset: (pageNumber - 1) * pageSize,
|
|
117
|
+
order: this.ownedCartListOrder
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
items: response.carts.map((cart) => this.normalizeOwnedCart(cart)),
|
|
122
|
+
totalCount: response.count
|
|
123
|
+
};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (this.shouldFallBackToSessionList(err)) {
|
|
126
|
+
debug("Owned-carts endpoint unavailable, falling back to session-tracked carts:", err);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Lists carts from the ids tracked in the session: the behavior from
|
|
134
|
+
* before the cart-ownership plugin existed, and still the only source for
|
|
135
|
+
* anonymous/guest carts and company buckets.
|
|
136
|
+
*/
|
|
137
|
+
async listCartsFromSession(payload) {
|
|
56
138
|
const client = await this.getClient();
|
|
57
139
|
const sessionData = this.medusaApi.getSessionData();
|
|
58
140
|
let cartCollection = payload.search.company ? sessionData.allOwnedCarts?.[payload.search.company.taxIdentifier] : sessionData.allOwnedCarts?.["_me"];
|
|
@@ -62,18 +144,58 @@ class MedusaCartCapability extends CartCapability {
|
|
|
62
144
|
} else {
|
|
63
145
|
cartCollection = [];
|
|
64
146
|
}
|
|
65
|
-
const
|
|
66
|
-
const allPromises = cartCollection.map((cartIdentifier) => client.store.cart.retrieve(cartIdentifier.key, { fields: shortFields }));
|
|
147
|
+
const allPromises = cartCollection.map((cartIdentifier) => client.store.cart.retrieve(cartIdentifier.key, { fields: this.ownedCartListFields }));
|
|
67
148
|
const responses = await Promise.all(allPromises);
|
|
68
149
|
const carts = responses.map((response) => response.cart).filter((cart) => !!cart);
|
|
69
|
-
return
|
|
70
|
-
this.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
150
|
+
return {
|
|
151
|
+
items: carts.map((cart) => this.normalizeOwnedCart(cart)),
|
|
152
|
+
totalCount
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The factory reads `customer_id`, but only the `customer.id` relation is
|
|
157
|
+
* an allowed list field, so copy it over when `customer_id` itself wasn't
|
|
158
|
+
* returned.
|
|
159
|
+
*/
|
|
160
|
+
normalizeOwnedCart(cart) {
|
|
161
|
+
return { ...cart, customer_id: cart.customer_id ?? cart.customer?.id };
|
|
162
|
+
}
|
|
163
|
+
/** A 404 (route not served) or 401 (token no longer valid) degrades to the session list. */
|
|
164
|
+
shouldFallBackToSessionList(err) {
|
|
165
|
+
return err instanceof FetchError && (err.status === 404 || err.status === 401);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Checks whether the backend has the cart-ownership plugin, by its name in
|
|
169
|
+
* core's `GET /admin/plugins`. Detection must never break cart listing, so
|
|
170
|
+
* any failure just reports the plugin as absent.
|
|
171
|
+
*/
|
|
172
|
+
async isCartOwnershipPluginEnabled() {
|
|
173
|
+
const plugins = await this.fetchEnabledPlugins();
|
|
174
|
+
return plugins.includes(this.cartOwnershipPluginName);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The backend's configured plugin list, kept in the reactionary cache
|
|
178
|
+
* (shared across sessions) so not every request re-asks the admin API.
|
|
179
|
+
* Failures are not cached: a plugin-less answer from here only costs the
|
|
180
|
+
* fallback behavior, while caching a transient failure would hide the
|
|
181
|
+
* plugin for a whole TTL.
|
|
182
|
+
*/
|
|
183
|
+
async fetchEnabledPlugins() {
|
|
184
|
+
const cacheKey = `medusa:plugins:${this.config.apiUrl}`;
|
|
185
|
+
const cached = await this.cache.get(cacheKey, MedusaCachedPluginListSchema);
|
|
186
|
+
if (cached) {
|
|
187
|
+
return cached.plugins;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const adminClient = await new MedusaAdminAPI(this.config, this.context).getClient();
|
|
191
|
+
const response = await adminClient.client.fetch("/admin/plugins", { method: "GET" });
|
|
192
|
+
const plugins = response.plugins.map((plugin) => plugin.name);
|
|
193
|
+
await this.cache.put(cacheKey, { plugins }, { ttlSeconds: this.pluginDetectionTtlSeconds, dependencyIds: [] });
|
|
194
|
+
return plugins;
|
|
195
|
+
} catch (err) {
|
|
196
|
+
debug("Failed to list backend plugins:", err);
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
77
199
|
}
|
|
78
200
|
async getById(payload) {
|
|
79
201
|
try {
|
|
@@ -219,6 +341,21 @@ class MedusaCartCapability extends CartCapability {
|
|
|
219
341
|
}
|
|
220
342
|
};
|
|
221
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Session tracking is skipped for a logged-in customer's own carts when
|
|
346
|
+
* the backend has the cart-ownership plugin: the backend attributes the
|
|
347
|
+
* cart to the customer at creation and lists it server-side. Anonymous
|
|
348
|
+
* carts and company buckets are still only known to the session.
|
|
349
|
+
*/
|
|
350
|
+
async shouldTrackOwnedCartInSession(payload) {
|
|
351
|
+
if (payload.company) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
if (this.context.session.identityContext.identity.type !== "Registered") {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
return !await this.isCartOwnershipPluginEnabled();
|
|
358
|
+
}
|
|
222
359
|
addCartToOwnedList(cartIdentifier, companyId) {
|
|
223
360
|
const sessionData = this.medusaApi.getSessionData();
|
|
224
361
|
const companyIdToUse = companyId ? companyId.taxIdentifier : "_me";
|
|
@@ -278,7 +415,9 @@ class MedusaCartCapability extends CartCapability {
|
|
|
278
415
|
}
|
|
279
416
|
);
|
|
280
417
|
if (response.cart) {
|
|
281
|
-
|
|
418
|
+
if (await this.shouldTrackOwnedCartInSession(payload)) {
|
|
419
|
+
this.addCartToOwnedList(this.factory.parseCartIdentifier(this.context, response.cart), payload.company);
|
|
420
|
+
}
|
|
282
421
|
this.medusaApi.setSessionData({
|
|
283
422
|
activeCartId: this.factory.parseCartIdentifier(this.context, response.cart)
|
|
284
423
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/medusa",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@medusajs/js-sdk": "^2.13.3",
|
|
13
|
-
"@reactionary/core": "0.10.
|
|
13
|
+
"@reactionary/core": "0.10.52",
|
|
14
14
|
"debug": "^4.4.3",
|
|
15
15
|
"@medusajs/types": "^2.13.3",
|
|
16
16
|
"zod": "4.1.9"
|
package/schema/medusa.schema.js
CHANGED
|
@@ -11,6 +11,9 @@ const MedusaRegionSchema = z.looseObject({
|
|
|
11
11
|
name: z.string(),
|
|
12
12
|
currency_code: z.string()
|
|
13
13
|
});
|
|
14
|
+
const MedusaCachedPluginListSchema = z.looseObject({
|
|
15
|
+
plugins: z.array(z.string())
|
|
16
|
+
});
|
|
14
17
|
const MedusaSessionSchema = z.looseObject({
|
|
15
18
|
activeCartId: MedusaCartIdentifierSchema.optional(),
|
|
16
19
|
allOwnedCarts: z.record(z.string(), z.array(MedusaCartIdentifierSchema)).optional(),
|
|
@@ -20,6 +23,7 @@ const MedusaSessionSchema = z.looseObject({
|
|
|
20
23
|
selectedRegion: MedusaRegionSchema.optional()
|
|
21
24
|
});
|
|
22
25
|
export {
|
|
26
|
+
MedusaCachedPluginListSchema,
|
|
23
27
|
MedusaCartIdentifierSchema,
|
|
24
28
|
MedusaOrderIdentifierSchema,
|
|
25
29
|
MedusaRegionSchema,
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { Cache, CartFactory, CartFactoryCartOutput, CartFactoryIdentifierOutput, CartFactoryWithOutput, CartMutationApplyCoupon, CartMutationChangeCurrency, CartMutationCreateCart, CartMutationDeleteCart, CartMutationItemAdd, CartMutationItemQuantityChange, CartMutationItemRemove, CartMutationRemoveCoupon, CartMutationRenameCart, CartPaginatedSearchResult, CartQueryById, CartQueryList, CompanyIdentifier, NotFoundError, RequestContext, Result } from '@reactionary/core';
|
|
2
2
|
import { CartCapability } from '@reactionary/core';
|
|
3
|
+
import type { StoreCart } from '@medusajs/types';
|
|
3
4
|
import { type StoreAddCartLineItem, type StoreCartAddPromotion, type StoreCartRemovePromotion, type StoreCreateCart, type StoreUpdateCart, type StoreUpdateCartLineItem } from '@medusajs/types';
|
|
4
|
-
import type
|
|
5
|
-
import type { MedusaCartFactory } from '../factories/cart/cart.factory.js';
|
|
5
|
+
import { type MedusaAPI } from '../core/client.js';
|
|
6
|
+
import type { MedusaCartFactory, ParseMedusaCartPaginatedSearchResultInput } from '../factories/cart/cart.factory.js';
|
|
6
7
|
import type { MedusaConfiguration } from '../schema/configuration.schema.js';
|
|
7
|
-
import type
|
|
8
|
+
import { type MedusaCartIdentifier } from '../schema/medusa.schema.js';
|
|
9
|
+
/**
|
|
10
|
+
* A cart as returned when the `customer.id` relation field is requested;
|
|
11
|
+
* `StoreCart` itself has no `customer` property.
|
|
12
|
+
*/
|
|
13
|
+
interface MedusaOwnedCart extends StoreCart {
|
|
14
|
+
customer?: {
|
|
15
|
+
id: string;
|
|
16
|
+
} | null;
|
|
17
|
+
}
|
|
8
18
|
export declare class MedusaCartCapability<TFactory extends CartFactory = MedusaCartFactory> extends CartCapability<CartFactoryCartOutput<TFactory>, CartFactoryIdentifierOutput<TFactory>> {
|
|
9
19
|
medusaApi: MedusaAPI;
|
|
10
20
|
protected config: MedusaConfiguration;
|
|
@@ -16,8 +26,71 @@ export declare class MedusaCartCapability<TFactory extends CartFactory = MedusaC
|
|
|
16
26
|
* example: this.includedFields = [includedFields, '+discounts.*'].join(',');
|
|
17
27
|
*/
|
|
18
28
|
protected includedFields: string;
|
|
29
|
+
/**
|
|
30
|
+
* Fields requested when listing owned carts, both via the cart-ownership
|
|
31
|
+
* endpoint and via the session-tracked fallback. Every entry must be in
|
|
32
|
+
* the allowed field set of both routes; `customer.id` stands in for
|
|
33
|
+
* `customer_id`, which is not an allowed field (see normalizeOwnedCart).
|
|
34
|
+
*/
|
|
35
|
+
protected ownedCartListFields: string;
|
|
36
|
+
/** Sort order for the server-side owned-carts listing. */
|
|
37
|
+
protected ownedCartListOrder: string;
|
|
38
|
+
/**
|
|
39
|
+
* Package name of the optional backend plugin that serves
|
|
40
|
+
* `GET /store/customers/me/carts` (a logged-in customer's open carts).
|
|
41
|
+
*/
|
|
42
|
+
protected cartOwnershipPluginName: string;
|
|
43
|
+
/**
|
|
44
|
+
* How long a backend's plugin list stays cached. Bounds how late a newly
|
|
45
|
+
* (un)installed cart-ownership plugin is noticed.
|
|
46
|
+
*/
|
|
47
|
+
protected pluginDetectionTtlSeconds: number;
|
|
19
48
|
constructor(config: MedusaConfiguration, cache: Cache, context: RequestContext, medusaApi: MedusaAPI, factory: CartFactoryWithOutput<TFactory>);
|
|
20
49
|
listCarts(payload: CartQueryList): Promise<Result<CartPaginatedSearchResult>>;
|
|
50
|
+
/**
|
|
51
|
+
* The server-side owned-carts listing only applies to a logged-in
|
|
52
|
+
* customer's own carts: a company bucket and anonymous/guest carts are
|
|
53
|
+
* only known to the session, and the backend only serves the route when
|
|
54
|
+
* the cart-ownership plugin is installed.
|
|
55
|
+
*/
|
|
56
|
+
protected shouldUseOwnershipEndpoint(payload: CartQueryList): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Lists the customer's open carts through the cart-ownership plugin's
|
|
59
|
+
* `GET /store/customers/me/carts`. Returns null when the backend turns
|
|
60
|
+
* out not to serve the route after all (404: plugin configured but not
|
|
61
|
+
* built; 401: the token is no longer valid), so the caller falls back to
|
|
62
|
+
* the session-tracked list. Other errors are real backend faults and
|
|
63
|
+
* propagate.
|
|
64
|
+
*/
|
|
65
|
+
protected listCartsViaOwnershipEndpoint(payload: CartQueryList): Promise<ParseMedusaCartPaginatedSearchResultInput | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Lists carts from the ids tracked in the session: the behavior from
|
|
68
|
+
* before the cart-ownership plugin existed, and still the only source for
|
|
69
|
+
* anonymous/guest carts and company buckets.
|
|
70
|
+
*/
|
|
71
|
+
protected listCartsFromSession(payload: CartQueryList): Promise<ParseMedusaCartPaginatedSearchResultInput>;
|
|
72
|
+
/**
|
|
73
|
+
* The factory reads `customer_id`, but only the `customer.id` relation is
|
|
74
|
+
* an allowed list field, so copy it over when `customer_id` itself wasn't
|
|
75
|
+
* returned.
|
|
76
|
+
*/
|
|
77
|
+
protected normalizeOwnedCart(cart: MedusaOwnedCart): StoreCart;
|
|
78
|
+
/** A 404 (route not served) or 401 (token no longer valid) degrades to the session list. */
|
|
79
|
+
protected shouldFallBackToSessionList(err: unknown): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Checks whether the backend has the cart-ownership plugin, by its name in
|
|
82
|
+
* core's `GET /admin/plugins`. Detection must never break cart listing, so
|
|
83
|
+
* any failure just reports the plugin as absent.
|
|
84
|
+
*/
|
|
85
|
+
protected isCartOwnershipPluginEnabled(): Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* The backend's configured plugin list, kept in the reactionary cache
|
|
88
|
+
* (shared across sessions) so not every request re-asks the admin API.
|
|
89
|
+
* Failures are not cached: a plugin-less answer from here only costs the
|
|
90
|
+
* fallback behavior, while caching a transient failure would hide the
|
|
91
|
+
* plugin for a whole TTL.
|
|
92
|
+
*/
|
|
93
|
+
protected fetchEnabledPlugins(): Promise<string[]>;
|
|
21
94
|
getById(payload: CartQueryById): Promise<Result<CartFactoryCartOutput<TFactory>, NotFoundError>>;
|
|
22
95
|
/**
|
|
23
96
|
* Extension point for the `add` operation to control the payload sent to Medusa when adding an item to the cart. By default, it only includes the variant ID and quantity, but you can override it to include more fields as needed.
|
|
@@ -35,6 +108,13 @@ export declare class MedusaCartCapability<TFactory extends CartFactory = MedusaC
|
|
|
35
108
|
* @returns
|
|
36
109
|
*/
|
|
37
110
|
protected createCartPayload(payload: CartMutationCreateCart): Promise<StoreCreateCart>;
|
|
111
|
+
/**
|
|
112
|
+
* Session tracking is skipped for a logged-in customer's own carts when
|
|
113
|
+
* the backend has the cart-ownership plugin: the backend attributes the
|
|
114
|
+
* cart to the customer at creation and lists it server-side. Anonymous
|
|
115
|
+
* carts and company buckets are still only known to the session.
|
|
116
|
+
*/
|
|
117
|
+
protected shouldTrackOwnedCartInSession(payload: CartMutationCreateCart): Promise<boolean>;
|
|
38
118
|
protected addCartToOwnedList(cartIdentifier: MedusaCartIdentifier, companyId?: CompanyIdentifier): void;
|
|
39
119
|
/**
|
|
40
120
|
* Removes every trace of a cart from the session: the active cart marker
|
|
@@ -84,3 +164,4 @@ export declare class MedusaCartCapability<TFactory extends CartFactory = MedusaC
|
|
|
84
164
|
auth: import("@medusajs/js-sdk").Auth;
|
|
85
165
|
}>;
|
|
86
166
|
}
|
|
167
|
+
export {};
|
|
@@ -13,6 +13,13 @@ export declare const MedusaRegionSchema: z.ZodObject<{
|
|
|
13
13
|
name: z.ZodString;
|
|
14
14
|
currency_code: z.ZodString;
|
|
15
15
|
}, z.core.$loose>;
|
|
16
|
+
/**
|
|
17
|
+
* Cache entry shape for a backend's configured plugin list, as reported by
|
|
18
|
+
* core's `GET /admin/plugins`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const MedusaCachedPluginListSchema: z.ZodObject<{
|
|
21
|
+
plugins: z.ZodArray<z.ZodString>;
|
|
22
|
+
}, z.core.$loose>;
|
|
16
23
|
export declare const MedusaSessionSchema: z.ZodObject<{
|
|
17
24
|
activeCartId: z.ZodOptional<z.ZodObject<{
|
|
18
25
|
key: z.ZodString;
|
|
@@ -37,5 +44,6 @@ export declare const MedusaSessionSchema: z.ZodObject<{
|
|
|
37
44
|
}, z.core.$loose>;
|
|
38
45
|
export type MedusaCartIdentifier = InferType<typeof MedusaCartIdentifierSchema>;
|
|
39
46
|
export type MedusaOrderIdentifier = z.infer<typeof MedusaOrderIdentifierSchema>;
|
|
47
|
+
export type MedusaCachedPluginList = z.infer<typeof MedusaCachedPluginListSchema>;
|
|
40
48
|
export type MedusaSession = z.infer<typeof MedusaSessionSchema>;
|
|
41
49
|
export type MedusaRegion = z.infer<typeof MedusaRegionSchema>;
|