@unchainedshop/api 5.0.0-alpha.1 → 5.0.0-alpha.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/lib/loaders/enrollmentByOrderLoader.d.ts +9 -0
- package/lib/loaders/enrollmentByOrderLoader.js +17 -0
- package/lib/loaders/index.d.ts +5 -0
- package/lib/loaders/index.js +2 -0
- package/lib/loaders/orderDeliveryLoader.d.ts +9 -0
- package/lib/loaders/orderDeliveryLoader.js +12 -0
- package/lib/loaders/orderDiscountsLoader.d.ts +9 -0
- package/lib/loaders/orderDiscountsLoader.js +17 -0
- package/lib/loaders/orderPaymentLoader.d.ts +9 -0
- package/lib/loaders/orderPaymentLoader.js +12 -0
- package/lib/loaders/orderPositionsLoader.d.ts +9 -0
- package/lib/loaders/orderPositionsLoader.js +17 -0
- package/lib/loaders/tokenExportStatusLoader.d.ts +9 -0
- package/lib/loaders/tokenExportStatusLoader.js +31 -0
- package/lib/mcp/tools/assortment/handlers/getAssortment.js +1 -1
- package/lib/mcp/tools/product/handlers/getProduct.js +1 -1
- package/lib/middleware/createAuthMiddleware.js +1 -3
- package/lib/resolvers/type/index.d.ts +3 -3
- package/lib/resolvers/type/token-types.d.ts +3 -3
- package/lib/resolvers/type/token-types.js +6 -5
- package/lib/resolvers/type/user-types.d.ts +1 -1
- package/lib/roles/all.js +1 -1
- package/lib/schema/types/user.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UnchainedCore } from '@unchainedshop/core';
|
|
2
|
+
import type { Enrollment } from '@unchainedshop/core-enrollments';
|
|
3
|
+
import DataLoader from 'dataloader';
|
|
4
|
+
declare const _default: (unchainedAPI: UnchainedCore) => DataLoader<{
|
|
5
|
+
orderId: string;
|
|
6
|
+
}, Enrollment | null, {
|
|
7
|
+
orderId: string;
|
|
8
|
+
}>;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import DataLoader from 'dataloader';
|
|
2
|
+
export default (unchainedAPI) => new DataLoader(async (queries) => {
|
|
3
|
+
const orderIds = [...new Set(queries.map((q) => q.orderId).filter(Boolean))];
|
|
4
|
+
const requestedOrderIds = new Set(orderIds);
|
|
5
|
+
const enrollments = await unchainedAPI.modules.enrollments.findEnrollmentsByOrderIds({
|
|
6
|
+
orderIds,
|
|
7
|
+
});
|
|
8
|
+
const enrollmentMap = {};
|
|
9
|
+
for (const enrollment of enrollments) {
|
|
10
|
+
for (const period of enrollment.periods || []) {
|
|
11
|
+
if (period.orderId && requestedOrderIds.has(period.orderId) && !enrollmentMap[period.orderId]) {
|
|
12
|
+
enrollmentMap[period.orderId] = enrollment;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return queries.map((q) => enrollmentMap[q.orderId] ?? null);
|
|
17
|
+
});
|
package/lib/loaders/index.d.ts
CHANGED
|
@@ -171,6 +171,11 @@ declare const loaders: (unchainedAPI: UnchainedCore) => {
|
|
|
171
171
|
}, import("@unchainedshop/core-quotations").Quotation | null, {
|
|
172
172
|
quotationId: string;
|
|
173
173
|
}>;
|
|
174
|
+
tokenExportStatusLoader: import("dataloader")<{
|
|
175
|
+
token: import("@unchainedshop/core-warehousing").TokenSurrogate;
|
|
176
|
+
}, import("@unchainedshop/core-warehousing").TokenStatus, {
|
|
177
|
+
token: import("@unchainedshop/core-warehousing").TokenSurrogate;
|
|
178
|
+
}>;
|
|
174
179
|
};
|
|
175
180
|
export type UnchainedLoaders = Awaited<ReturnType<typeof loaders>>;
|
|
176
181
|
export default loaders;
|
package/lib/loaders/index.js
CHANGED
|
@@ -27,6 +27,7 @@ import paymentProviderLoader from "./paymentProviderLoader.js";
|
|
|
27
27
|
import warehousingProviderLoader from "./warehousingProviderLoader.js";
|
|
28
28
|
import orderLoader from "./orderLoader.js";
|
|
29
29
|
import quotationLoader from "./quotationLoader.js";
|
|
30
|
+
import tokenExportStatusLoader from "./tokenExportStatusLoader.js";
|
|
30
31
|
const loaders = (unchainedAPI) => {
|
|
31
32
|
return {
|
|
32
33
|
assortmentLoader: assortmentLoader(unchainedAPI),
|
|
@@ -58,6 +59,7 @@ const loaders = (unchainedAPI) => {
|
|
|
58
59
|
warehousingProviderLoader: warehousingProviderLoader(unchainedAPI),
|
|
59
60
|
orderLoader: orderLoader(unchainedAPI),
|
|
60
61
|
quotationLoader: quotationLoader(unchainedAPI),
|
|
62
|
+
tokenExportStatusLoader: tokenExportStatusLoader(unchainedAPI),
|
|
61
63
|
};
|
|
62
64
|
};
|
|
63
65
|
export default loaders;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UnchainedCore } from '@unchainedshop/core';
|
|
2
|
+
import type { OrderDelivery } from '@unchainedshop/core-orders';
|
|
3
|
+
import DataLoader from 'dataloader';
|
|
4
|
+
declare const _default: (unchainedAPI: UnchainedCore) => DataLoader<{
|
|
5
|
+
orderDeliveryId: string;
|
|
6
|
+
}, OrderDelivery | null, {
|
|
7
|
+
orderDeliveryId: string;
|
|
8
|
+
}>;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import DataLoader from 'dataloader';
|
|
2
|
+
export default (unchainedAPI) => new DataLoader(async (queries) => {
|
|
3
|
+
const orderDeliveryIds = [...new Set(queries.map((q) => q.orderDeliveryId).filter(Boolean))];
|
|
4
|
+
const deliveries = await unchainedAPI.modules.orders.deliveries.findDeliveries({
|
|
5
|
+
orderDeliveryIds,
|
|
6
|
+
});
|
|
7
|
+
const deliveryMap = {};
|
|
8
|
+
for (const delivery of deliveries) {
|
|
9
|
+
deliveryMap[delivery._id] = delivery;
|
|
10
|
+
}
|
|
11
|
+
return queries.map((q) => deliveryMap[q.orderDeliveryId] ?? null);
|
|
12
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UnchainedCore } from '@unchainedshop/core';
|
|
2
|
+
import type { OrderDiscount } from '@unchainedshop/core-orders';
|
|
3
|
+
import DataLoader from 'dataloader';
|
|
4
|
+
declare const _default: (unchainedAPI: UnchainedCore) => DataLoader<{
|
|
5
|
+
orderId: string;
|
|
6
|
+
}, OrderDiscount[], {
|
|
7
|
+
orderId: string;
|
|
8
|
+
}>;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import DataLoader from 'dataloader';
|
|
2
|
+
export default (unchainedAPI) => new DataLoader(async (queries) => {
|
|
3
|
+
const orderIds = [...new Set(queries.map((q) => q.orderId).filter(Boolean))];
|
|
4
|
+
const discounts = await unchainedAPI.modules.orders.discounts.findOrderDiscounts({
|
|
5
|
+
orderIds,
|
|
6
|
+
});
|
|
7
|
+
const discountsMap = {};
|
|
8
|
+
for (const discount of discounts) {
|
|
9
|
+
if (!discountsMap[discount.orderId]) {
|
|
10
|
+
discountsMap[discount.orderId] = [discount];
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
discountsMap[discount.orderId].push(discount);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return queries.map((q) => discountsMap[q.orderId] || []);
|
|
17
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UnchainedCore } from '@unchainedshop/core';
|
|
2
|
+
import type { OrderPayment } from '@unchainedshop/core-orders';
|
|
3
|
+
import DataLoader from 'dataloader';
|
|
4
|
+
declare const _default: (unchainedAPI: UnchainedCore) => DataLoader<{
|
|
5
|
+
orderPaymentId: string;
|
|
6
|
+
}, OrderPayment | null, {
|
|
7
|
+
orderPaymentId: string;
|
|
8
|
+
}>;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import DataLoader from 'dataloader';
|
|
2
|
+
export default (unchainedAPI) => new DataLoader(async (queries) => {
|
|
3
|
+
const orderPaymentIds = [...new Set(queries.map((q) => q.orderPaymentId).filter(Boolean))];
|
|
4
|
+
const payments = await unchainedAPI.modules.orders.payments.findOrderPayments({
|
|
5
|
+
orderPaymentIds,
|
|
6
|
+
});
|
|
7
|
+
const paymentMap = {};
|
|
8
|
+
for (const payment of payments) {
|
|
9
|
+
paymentMap[payment._id] = payment;
|
|
10
|
+
}
|
|
11
|
+
return queries.map((q) => paymentMap[q.orderPaymentId] ?? null);
|
|
12
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UnchainedCore } from '@unchainedshop/core';
|
|
2
|
+
import type { OrderPosition } from '@unchainedshop/core-orders';
|
|
3
|
+
import DataLoader from 'dataloader';
|
|
4
|
+
declare const _default: (unchainedAPI: UnchainedCore) => DataLoader<{
|
|
5
|
+
orderId: string;
|
|
6
|
+
}, OrderPosition[], {
|
|
7
|
+
orderId: string;
|
|
8
|
+
}>;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import DataLoader from 'dataloader';
|
|
2
|
+
export default (unchainedAPI) => new DataLoader(async (queries) => {
|
|
3
|
+
const orderIds = [...new Set(queries.map((q) => q.orderId).filter(Boolean))];
|
|
4
|
+
const positions = await unchainedAPI.modules.orders.positions.findOrderPositions({
|
|
5
|
+
orderIds,
|
|
6
|
+
});
|
|
7
|
+
const positionsMap = {};
|
|
8
|
+
for (const position of positions) {
|
|
9
|
+
if (!positionsMap[position.orderId]) {
|
|
10
|
+
positionsMap[position.orderId] = [position];
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
positionsMap[position.orderId].push(position);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return queries.map((q) => positionsMap[q.orderId] || []);
|
|
17
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UnchainedCore } from '@unchainedshop/core';
|
|
2
|
+
import type { TokenSurrogate, TokenStatus as TokenStatusType } from '@unchainedshop/core-warehousing';
|
|
3
|
+
import DataLoader from 'dataloader';
|
|
4
|
+
declare const _default: (unchainedAPI: UnchainedCore) => DataLoader<{
|
|
5
|
+
token: TokenSurrogate;
|
|
6
|
+
}, TokenStatusType, {
|
|
7
|
+
token: TokenSurrogate;
|
|
8
|
+
}>;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TokenStatus } from '@unchainedshop/core-warehousing';
|
|
2
|
+
import { WorkStatus } from '@unchainedshop/core-worker';
|
|
3
|
+
import DataLoader from 'dataloader';
|
|
4
|
+
export default (unchainedAPI) => new DataLoader(async (queries) => {
|
|
5
|
+
const tokensNeedingWorkCheck = [];
|
|
6
|
+
for (const { token } of queries) {
|
|
7
|
+
if (!(token.walletAddress && !token.userId)) {
|
|
8
|
+
tokensNeedingWorkCheck.push(token);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
let exportingTokenIds;
|
|
12
|
+
if (tokensNeedingWorkCheck.length > 0) {
|
|
13
|
+
const workItems = await unchainedAPI.modules.worker.findWorkQueue({
|
|
14
|
+
types: ['EXPORT_TOKEN'],
|
|
15
|
+
status: [WorkStatus.NEW, WorkStatus.ALLOCATED],
|
|
16
|
+
});
|
|
17
|
+
exportingTokenIds = new Set(workItems.map((item) => item.input?.token?._id).filter(Boolean));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
exportingTokenIds = new Set();
|
|
21
|
+
}
|
|
22
|
+
return queries.map(({ token }) => {
|
|
23
|
+
if (token.walletAddress && !token.userId) {
|
|
24
|
+
return TokenStatus.DECENTRALIZED;
|
|
25
|
+
}
|
|
26
|
+
if (exportingTokenIds.has(token._id)) {
|
|
27
|
+
return TokenStatus.EXPORTING;
|
|
28
|
+
}
|
|
29
|
+
return TokenStatus.CENTRALIZED;
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -3,7 +3,7 @@ import { getNormalizedAssortmentDetails } from "../../../utils/getNormalizedAsso
|
|
|
3
3
|
export default async function getAssortment(context, params) {
|
|
4
4
|
const { modules } = context;
|
|
5
5
|
const { assortmentId, slug } = params;
|
|
6
|
-
let query
|
|
6
|
+
let query;
|
|
7
7
|
if (assortmentId)
|
|
8
8
|
query = { assortmentId };
|
|
9
9
|
else if (slug)
|
|
@@ -3,7 +3,7 @@ import { getNormalizedProductDetails } from "../../../utils/getNormalizedProduct
|
|
|
3
3
|
export default async function getProduct(context, params) {
|
|
4
4
|
const { modules } = context;
|
|
5
5
|
const { productId, slug, sku } = params;
|
|
6
|
-
let query
|
|
6
|
+
let query;
|
|
7
7
|
if (productId)
|
|
8
8
|
query = { productId };
|
|
9
9
|
else if (slug)
|
|
@@ -65,9 +65,8 @@ export async function createAuthContext(params, authConfig) {
|
|
|
65
65
|
const fingerprintCookie = getCookie(UNCHAINED_FINGERPRINT_COOKIE_NAME);
|
|
66
66
|
const verifyToken = createAuthHandler(authConfig);
|
|
67
67
|
const authResult = token ? await verifyToken(token) : {};
|
|
68
|
-
let fingerprintValid = true;
|
|
69
68
|
if (authResult.fingerprintHash && fingerprintCookie) {
|
|
70
|
-
fingerprintValid = verifyFingerprint(fingerprintCookie, authResult.fingerprintHash);
|
|
69
|
+
const fingerprintValid = verifyFingerprint(fingerprintCookie, authResult.fingerprintHash);
|
|
71
70
|
if (!fingerprintValid) {
|
|
72
71
|
logger.warn('Token sidejacking detected: fingerprint mismatch', {
|
|
73
72
|
userId: authResult.userId,
|
|
@@ -81,7 +80,6 @@ export async function createAuthContext(params, authConfig) {
|
|
|
81
80
|
logger.warn('Token sidejacking detected: fingerprint cookie missing', {
|
|
82
81
|
userId: authResult.userId,
|
|
83
82
|
});
|
|
84
|
-
fingerprintValid = false;
|
|
85
83
|
authResult.userId = undefined;
|
|
86
84
|
authResult.tokenVersion = undefined;
|
|
87
85
|
authResult.impersonatorId = undefined;
|
|
@@ -775,12 +775,12 @@ declare const types: {
|
|
|
775
775
|
Token: {
|
|
776
776
|
product: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, { loaders }: import("../../context.ts").Context) => Promise<import("@unchainedshop/core-products").Product>;
|
|
777
777
|
user: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, { loaders }: import("../../context.ts").Context) => Promise<import("@unchainedshop/core-users").User | null>;
|
|
778
|
-
status: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, {
|
|
778
|
+
status: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, { loaders }: import("../../context.ts").Context) => Promise<import("@unchainedshop/core-warehousing").TokenStatus>;
|
|
779
779
|
ercMetadata: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, { forceLocale }: {
|
|
780
780
|
forceLocale: string;
|
|
781
781
|
}, context: import("../../context.ts").Context) => Promise<any>;
|
|
782
|
-
isInvalidateable: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, _params: never, { services }: import("../../context.ts").Context) => Promise<boolean>;
|
|
783
|
-
accessKey: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, requestContext: import("../../context.ts").Context) => Promise<string
|
|
782
|
+
isInvalidateable: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, _params: never, { loaders, services }: import("../../context.ts").Context) => Promise<boolean>;
|
|
783
|
+
accessKey: (token: import("@unchainedshop/core-warehousing").TokenSurrogate, params: never, requestContext: import("../../context.ts").Context) => Promise<string>;
|
|
784
784
|
};
|
|
785
785
|
User: import("./user-types.ts").UserHelperTypes;
|
|
786
786
|
WebAuthnCredentials: {
|
|
@@ -3,10 +3,10 @@ import type { TokenSurrogate } from '@unchainedshop/core-warehousing';
|
|
|
3
3
|
export declare const Token: {
|
|
4
4
|
product: (token: TokenSurrogate, params: never, { loaders }: Context) => Promise<import("@unchainedshop/core-products").Product>;
|
|
5
5
|
user: (token: TokenSurrogate, params: never, { loaders }: Context) => Promise<import("@unchainedshop/core-users").User | null>;
|
|
6
|
-
status: (token: TokenSurrogate, params: never, {
|
|
6
|
+
status: (token: TokenSurrogate, params: never, { loaders }: Context) => Promise<import("@unchainedshop/core-warehousing").TokenStatus>;
|
|
7
7
|
ercMetadata: (token: TokenSurrogate, { forceLocale }: {
|
|
8
8
|
forceLocale: string;
|
|
9
9
|
}, context: Context) => Promise<any>;
|
|
10
|
-
isInvalidateable: (token: TokenSurrogate, _params: never, { services }: Context) => Promise<boolean>;
|
|
11
|
-
accessKey: (token: TokenSurrogate, params: never, requestContext: Context) => Promise<string
|
|
10
|
+
isInvalidateable: (token: TokenSurrogate, _params: never, { loaders, services }: Context) => Promise<boolean>;
|
|
11
|
+
accessKey: (token: TokenSurrogate, params: never, requestContext: Context) => Promise<string>;
|
|
12
12
|
};
|
|
@@ -9,8 +9,8 @@ export const Token = {
|
|
|
9
9
|
return null;
|
|
10
10
|
return loaders.userLoader.load({ userId: token.userId });
|
|
11
11
|
},
|
|
12
|
-
status: async (token, params, {
|
|
13
|
-
return
|
|
12
|
+
status: async (token, params, { loaders }) => {
|
|
13
|
+
return loaders.tokenExportStatusLoader.load({ token });
|
|
14
14
|
},
|
|
15
15
|
ercMetadata: async (token, { forceLocale }, context) => {
|
|
16
16
|
const { loaders, services } = context;
|
|
@@ -21,12 +21,13 @@ export const Token = {
|
|
|
21
21
|
locale: forceLocale ? new Intl.Locale(forceLocale) : context.locale,
|
|
22
22
|
});
|
|
23
23
|
},
|
|
24
|
-
isInvalidateable: async (token, _params, { services }) => {
|
|
25
|
-
|
|
24
|
+
isInvalidateable: async (token, _params, { loaders, services }) => {
|
|
25
|
+
const product = await loaders.productLoader.load({ productId: token.productId });
|
|
26
|
+
return services.warehousing.isTokenInvalidateable({ token, product });
|
|
26
27
|
},
|
|
27
28
|
accessKey: async (token, params, requestContext) => {
|
|
28
29
|
const { modules } = requestContext;
|
|
29
30
|
await checkAction(requestContext, actions.updateToken, [undefined, { tokenId: token._id }]);
|
|
30
|
-
return modules.warehousing.
|
|
31
|
+
return modules.warehousing.buildAccessKeyFromToken(token);
|
|
31
32
|
},
|
|
32
33
|
};
|
package/lib/roles/all.js
CHANGED
|
@@ -20,7 +20,7 @@ export const all = (role, actions) => {
|
|
|
20
20
|
if (isOwnedByUser)
|
|
21
21
|
return true;
|
|
22
22
|
const accessKeyHeader = context.getHeader('x-token-accesskey');
|
|
23
|
-
const accessKey = await context.modules.warehousing.
|
|
23
|
+
const accessKey = await context.modules.warehousing.buildAccessKeyFromToken(token);
|
|
24
24
|
if (accessKeyHeader && accessKey && (await timingSafeStringEqual(accessKeyHeader, accessKey)))
|
|
25
25
|
return true;
|
|
26
26
|
return false;
|
package/lib/schema/types/user.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/api",
|
|
3
3
|
"description": "GraphQL API layer for the Unchained Engine with Express/Fastify adapters and MCP server",
|
|
4
|
-
"version": "5.0.0-alpha.
|
|
4
|
+
"version": "5.0.0-alpha.2",
|
|
5
5
|
"main": "lib/api-index.js",
|
|
6
6
|
"types": "lib/api-index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@ai-sdk/mcp": "^1",
|
|
64
64
|
"@fastify/cookie": ">= 11 < 12",
|
|
65
|
-
"@fastify/multipart": ">=
|
|
65
|
+
"@fastify/multipart": ">= 10 < 11",
|
|
66
66
|
"@fastify/static": ">= 9 < 10",
|
|
67
67
|
"@modelcontextprotocol/sdk": ">= 1 < 2",
|
|
68
68
|
"ai": ">= 6 < 7",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"devDependencies": {
|
|
124
124
|
"@ai-sdk/mcp": "^1.0.5",
|
|
125
125
|
"@fastify/cookie": "^11.0.2",
|
|
126
|
-
"@fastify/multipart": "^
|
|
126
|
+
"@fastify/multipart": "^10.0.0",
|
|
127
127
|
"@fastify/static": "^9.0.0",
|
|
128
128
|
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
129
129
|
"@types/cookie-parser": "^1.4.8",
|