@unchainedshop/api 4.4.0 → 4.6.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 (168) hide show
  1. package/README.md +204 -0
  2. package/lib/api-index.js +3 -0
  3. package/lib/chat/generateImageHandler.d.ts +7 -1
  4. package/lib/chat/generateImageHandler.js +4 -5
  5. package/lib/context.js +2 -13
  6. package/lib/errors.d.ts +1 -0
  7. package/lib/errors.js +2 -1
  8. package/lib/events.js +0 -2
  9. package/lib/express/chatHandler.js +4 -4
  10. package/lib/express/createERCMetadataMiddleware.js +1 -1
  11. package/lib/express/index.js +23 -9
  12. package/lib/fastify/chatHandler.js +4 -4
  13. package/lib/fastify/ercMetadataHandler.js +1 -1
  14. package/lib/fastify/index.js +9 -3
  15. package/lib/loaders/assortmentMediaTextLoader.js +1 -1
  16. package/lib/loaders/assortmentMediasLoader.js +1 -1
  17. package/lib/loaders/assortmentTextLoader.js +1 -1
  18. package/lib/loaders/countryLoader.js +1 -1
  19. package/lib/loaders/currencyLoader.js +1 -1
  20. package/lib/loaders/deliveryProviderLoader.js +1 -1
  21. package/lib/loaders/fileLoader.js +1 -1
  22. package/lib/loaders/filterTextLoader.js +1 -1
  23. package/lib/loaders/languageLoader.js +1 -1
  24. package/lib/loaders/orderLoader.js +1 -1
  25. package/lib/loaders/paymentProviderLoader.js +1 -1
  26. package/lib/loaders/productBySKULoader.js +2 -4
  27. package/lib/loaders/productLoader.js +1 -3
  28. package/lib/loaders/productMediaTextLoader.js +1 -1
  29. package/lib/loaders/productMediasLoader.js +1 -1
  30. package/lib/loaders/productProxiesLoader.js +2 -10
  31. package/lib/loaders/productTextLoader.js +1 -1
  32. package/lib/loaders/productVariationByKeyLoader.js +1 -1
  33. package/lib/loaders/productVariationLoader.js +1 -1
  34. package/lib/loaders/productVariationTextLoader.js +1 -1
  35. package/lib/loaders/quotationLoader.js +1 -1
  36. package/lib/loaders/userLoader.js +1 -1
  37. package/lib/loaders/warehousingProviderLoader.js +1 -1
  38. package/lib/mcp/tools/assortment/handlers/addAssortmentFilter.d.ts +0 -1
  39. package/lib/mcp/tools/assortment/handlers/addAssortmentLink.d.ts +0 -1
  40. package/lib/mcp/tools/assortment/handlers/addAssortmentMedia.d.ts +0 -1
  41. package/lib/mcp/tools/assortment/handlers/addAssortmentProduct.d.ts +0 -1
  42. package/lib/mcp/tools/assortment/handlers/createAssortment.d.ts +0 -1
  43. package/lib/mcp/tools/assortment/handlers/getAssortment.d.ts +0 -1
  44. package/lib/mcp/tools/assortment/handlers/getAssortmentChildren.d.ts +0 -2
  45. package/lib/mcp/tools/assortment/handlers/getAssortmentFilters.d.ts +0 -1
  46. package/lib/mcp/tools/assortment/handlers/getAssortmentLinks.d.ts +0 -2
  47. package/lib/mcp/tools/assortment/handlers/getAssortmentMedia.d.ts +0 -1
  48. package/lib/mcp/tools/assortment/handlers/getAssortmentProducts.d.ts +0 -1
  49. package/lib/mcp/tools/assortment/handlers/index.d.ts +0 -2
  50. package/lib/mcp/tools/assortment/handlers/index.js +0 -2
  51. package/lib/mcp/tools/assortment/handlers/listAssortments.d.ts +0 -1
  52. package/lib/mcp/tools/assortment/handlers/reorderAssortmentLinks.d.ts +0 -1
  53. package/lib/mcp/tools/assortment/handlers/searchAssortmentProducts.d.ts +0 -1
  54. package/lib/mcp/tools/assortment/handlers/updateAssortment.d.ts +0 -1
  55. package/lib/mcp/tools/assortment/handlers/updateAssortmentStatus.d.ts +0 -1
  56. package/lib/mcp/tools/assortment/schemas.d.ts +0 -5
  57. package/lib/mcp/tools/assortment/schemas.js +0 -4
  58. package/lib/mcp/tools/order/handlers/confirmOrder.d.ts +1 -1
  59. package/lib/mcp/tools/order/handlers/deliverOrder.d.ts +1 -1
  60. package/lib/mcp/tools/order/handlers/getOrder.d.ts +1 -1
  61. package/lib/mcp/tools/order/handlers/getTopCustomers.d.ts +6 -6
  62. package/lib/mcp/tools/order/handlers/getTopCustomers.js +5 -55
  63. package/lib/mcp/tools/order/handlers/getTopProducts.d.ts +3 -3
  64. package/lib/mcp/tools/order/handlers/getTopProducts.js +4 -43
  65. package/lib/mcp/tools/order/handlers/getUserCart.d.ts +1 -1
  66. package/lib/mcp/tools/order/handlers/listOrders.d.ts +1 -1
  67. package/lib/mcp/tools/order/handlers/payOrder.d.ts +1 -1
  68. package/lib/mcp/tools/order/handlers/rejectOrder.d.ts +1 -1
  69. package/lib/mcp/tools/provider/handlers/listProviders.js +4 -8
  70. package/lib/mcp/tools/quotation/handlers/getQuotation.d.ts +2 -2
  71. package/lib/mcp/tools/quotation/handlers/listQuotations.d.ts +2 -2
  72. package/lib/mcp/tools/quotation/handlers/makeQuotationProposal.d.ts +2 -2
  73. package/lib/mcp/tools/quotation/handlers/rejectQuotation.d.ts +2 -2
  74. package/lib/mcp/tools/quotation/handlers/rejectQuotation.js +1 -1
  75. package/lib/mcp/tools/quotation/handlers/verifyQuotation.d.ts +2 -2
  76. package/lib/mcp/tools/quotation/index.js +1 -1
  77. package/lib/mcp/tools/quotation/schemas.d.ts +5 -5
  78. package/lib/mcp/tools/quotation/schemas.js +3 -3
  79. package/lib/mcp/tools/system/schemas.d.ts +6 -6
  80. package/lib/mcp/tools/system/schemas.js +14 -7
  81. package/lib/mcp/tools/users/handlers/addUserEmail.d.ts +1 -1
  82. package/lib/mcp/tools/users/handlers/createUser.d.ts +1 -1
  83. package/lib/mcp/tools/users/handlers/enrollUser.d.ts +1 -1
  84. package/lib/mcp/tools/users/handlers/getCurrentUser.d.ts +1 -1
  85. package/lib/mcp/tools/users/handlers/getUser.d.ts +1 -1
  86. package/lib/mcp/tools/users/handlers/getUserOrders.d.ts +1 -1
  87. package/lib/mcp/tools/users/handlers/getUserQuotations.d.ts +1 -1
  88. package/lib/mcp/tools/users/handlers/listUsers.d.ts +1 -1
  89. package/lib/mcp/tools/users/handlers/setUserTags.d.ts +1 -1
  90. package/lib/mcp/tools/users/handlers/setUserUsername.d.ts +1 -1
  91. package/lib/mcp/tools/users/handlers/updateUser.d.ts +1 -1
  92. package/lib/mcp/utils/getNormalizedAssortmentDetails.d.ts +0 -1
  93. package/lib/mcp/utils/getNormalizedOrderDetails.d.ts +1 -1
  94. package/lib/mcp/utils/getNormalizedQuotationDetails.d.ts +2 -2
  95. package/lib/mcp/utils/getNormalizedUserDetails.d.ts +1 -1
  96. package/lib/mongo-store.d.ts +9 -5
  97. package/lib/mongo-store.js +31 -25
  98. package/lib/resolvers/mutations/accounts/addWeb3Address.d.ts +2 -2
  99. package/lib/resolvers/mutations/accounts/addWeb3Address.js +1 -14
  100. package/lib/resolvers/mutations/accounts/addWebAuthnCredentials.d.ts +2 -2
  101. package/lib/resolvers/mutations/accounts/addWebAuthnCredentials.js +2 -6
  102. package/lib/resolvers/mutations/accounts/createUser.js +3 -1
  103. package/lib/resolvers/mutations/accounts/createWebAuthnCredentialCreationOptions.d.ts +1 -4
  104. package/lib/resolvers/mutations/accounts/createWebAuthnCredentialRequestOptions.d.ts +1 -4
  105. package/lib/resolvers/mutations/accounts/forgotPassword.js +4 -4
  106. package/lib/resolvers/mutations/accounts/loginAsGuest.js +1 -3
  107. package/lib/resolvers/mutations/accounts/removeWeb3Address.d.ts +1 -1
  108. package/lib/resolvers/mutations/accounts/removeWeb3Address.js +3 -7
  109. package/lib/resolvers/mutations/accounts/removeWebAuthnCredentials.d.ts +1 -1
  110. package/lib/resolvers/mutations/accounts/removeWebAuthnCredentials.js +1 -5
  111. package/lib/resolvers/mutations/accounts/verifyWeb3Address.d.ts +2 -2
  112. package/lib/resolvers/mutations/accounts/verifyWeb3Address.js +8 -89
  113. package/lib/resolvers/mutations/filters/removeFilter.d.ts +2 -2
  114. package/lib/resolvers/mutations/filters/removeFilter.js +3 -5
  115. package/lib/resolvers/mutations/index.d.ts +0 -1
  116. package/lib/resolvers/mutations/index.js +0 -2
  117. package/lib/resolvers/mutations/orders/addMultipleCartProducts.js +23 -31
  118. package/lib/resolvers/mutations/orders/removeCartDiscount.d.ts +2 -2
  119. package/lib/resolvers/mutations/orders/removeCartDiscount.js +9 -14
  120. package/lib/resolvers/mutations/orders/updateCart.js +2 -8
  121. package/lib/resolvers/mutations/quotations/rejectQuotation.js +1 -1
  122. package/lib/resolvers/mutations/users/removeUser.d.ts +1 -1
  123. package/lib/resolvers/mutations/users/removeUser.js +2 -3
  124. package/lib/resolvers/mutations/warehousing/exportToken.js +9 -17
  125. package/lib/resolvers/queries/payment/paymentProvidersCount.d.ts +3 -3
  126. package/lib/resolvers/queries/payment/paymentProvidersCount.js +1 -1
  127. package/lib/resolvers/queries/shopInfo.js +4 -36
  128. package/lib/resolvers/type/delivery-provider-interface.d.ts +1 -7
  129. package/lib/resolvers/type/delivery-provider-interface.js +10 -20
  130. package/lib/resolvers/type/delivery-provider-pickup-types.d.ts +1 -7
  131. package/lib/resolvers/type/delivery-provider-shipping-types.d.ts +1 -7
  132. package/lib/resolvers/type/delivery-provider-types.d.ts +1 -7
  133. package/lib/resolvers/type/index.d.ts +33 -54
  134. package/lib/resolvers/type/media-types.d.ts +1 -1
  135. package/lib/resolvers/type/media-types.js +7 -5
  136. package/lib/resolvers/type/order/order-item-types.d.ts +2 -9
  137. package/lib/resolvers/type/order/order-item-types.js +2 -37
  138. package/lib/resolvers/type/order/order-statistics-types.d.ts +10 -10
  139. package/lib/resolvers/type/order/order-statistics-types.js +10 -64
  140. package/lib/resolvers/type/payment/payment-provider-types.d.ts +1 -7
  141. package/lib/resolvers/type/payment/payment-provider-types.js +7 -17
  142. package/lib/resolvers/type/price-types.d.ts +0 -1
  143. package/lib/resolvers/type/product/product-bundle-types.d.ts +1 -1
  144. package/lib/resolvers/type/product/product-configurable-types.d.ts +1 -1
  145. package/lib/resolvers/type/product/product-configurable-types.js +9 -32
  146. package/lib/resolvers/type/product/product-plan-types.d.ts +1 -1
  147. package/lib/resolvers/type/product/product-simple-types.d.ts +1 -1
  148. package/lib/resolvers/type/product/product-tokenized-types.d.ts +1 -1
  149. package/lib/resolvers/type/product/product-types.d.ts +1 -1
  150. package/lib/resolvers/type/product/product-types.js +7 -17
  151. package/lib/resolvers/type/shop-types.js +2 -5
  152. package/lib/resolvers/type/token-types.d.ts +3 -3
  153. package/lib/resolvers/type/token-types.js +4 -25
  154. package/lib/resolvers/type/user-types.js +1 -4
  155. package/lib/resolvers/type/webauthn-credentials-types.d.ts +10 -1
  156. package/lib/roles/index.d.ts +2 -1
  157. package/lib/roles/index.js +7 -1
  158. package/lib/schema/mutation.js +1 -7
  159. package/lib/schema/types/assortment.js +0 -1
  160. package/lib/schema/types/order/order.js +2 -2
  161. package/lib/schema/types/quotation.js +2 -2
  162. package/lib/utils/getConfiguredTags.d.ts +1 -0
  163. package/lib/utils/getConfiguredTags.js +9 -0
  164. package/package.json +14 -23
  165. package/lib/mcp/tools/assortment/handlers/setAssortmentBase.d.ts +0 -28
  166. package/lib/mcp/tools/assortment/handlers/setAssortmentBase.js +0 -11
  167. package/lib/resolvers/mutations/assortments/setBaseAssortment.d.ts +0 -4
  168. package/lib/resolvers/mutations/assortments/setBaseAssortment.js +0 -11
package/README.md CHANGED
@@ -149,6 +149,210 @@ import { createMCPServer } from '@unchainedshop/api/mcp';
149
149
  const mcpServer = createMCPServer(unchainedCore);
150
150
  ```
151
151
 
152
+ ## Security
153
+
154
+ The API layer implements comprehensive security controls.
155
+
156
+ ### Access Control
157
+
158
+ - **128+ permission actions** covering all API operations
159
+ - **Role-Based Access Control (RBAC)** with built-in and custom roles
160
+ - **ACL enforcement** on all GraphQL mutations
161
+ - **Ownership validation** ensuring users can only access their resources
162
+
163
+ ### Session Security
164
+
165
+ | Variable | Purpose | Default |
166
+ |----------|---------|---------|
167
+ | `UNCHAINED_TOKEN_SECRET` | Session encryption (min 32 chars) | Required |
168
+ | `UNCHAINED_COOKIE_NAME` | Cookie name | `unchained_token` |
169
+ | `UNCHAINED_COOKIE_SAMESITE` | SameSite attribute | `none` |
170
+ | `UNCHAINED_COOKIE_INSECURE` | Disable secure flag | `false` |
171
+
172
+ Cookies are `httpOnly` and `secure` by default.
173
+
174
+ ### Error Handling
175
+
176
+ Errors are designed to prevent information leakage:
177
+ - Generic authentication error messages
178
+ - No distinction between "invalid" vs "expired" tokens
179
+ - Permission errors don't reveal action details
180
+
181
+ ### CORS Configuration
182
+
183
+ CORS behavior depends on `NODE_ENV`:
184
+
185
+ | Environment | Default Behavior |
186
+ |-------------|------------------|
187
+ | `development` | Permissive CORS (reflects any origin) + trust proxy headers |
188
+ | `production` | No CORS headers (reverse proxy should handle it) |
189
+
190
+ #### Environment Variable
191
+
192
+ | Variable | Purpose | Default |
193
+ |----------|---------|---------|
194
+ | `UNCHAINED_CORS_ORIGINS` | Allowed CORS origins | Auto (see above) |
195
+
196
+ #### Programmatic Configuration
197
+
198
+ ```typescript
199
+ // Auto behavior (recommended) - permissive in dev, none in prod
200
+ connect(app, { graphqlHandler, db, unchainedAPI });
201
+
202
+ // Explicit whitelist (production)
203
+ connect(app, { graphqlHandler, db, unchainedAPI }, {
204
+ corsOrigins: "https://shop.example.com,https://admin.example.com",
205
+ });
206
+
207
+ // Force permissive (not recommended in production)
208
+ connect(app, { graphqlHandler, db, unchainedAPI }, {
209
+ corsOrigins: true,
210
+ });
211
+
212
+ // Disable CORS entirely
213
+ connect(app, { graphqlHandler, db, unchainedAPI }, {
214
+ corsOrigins: false,
215
+ });
216
+ ```
217
+
218
+ ### Deployment Scenarios
219
+
220
+ #### Scenario 1: Direct TLS (No Reverse Proxy)
221
+
222
+ For simple deployments where the Node.js server handles TLS directly:
223
+
224
+ ```typescript
225
+ import express from 'express';
226
+ import https from 'https';
227
+ import fs from 'fs';
228
+ import { connect } from '@unchainedshop/api/express';
229
+
230
+ const app = express();
231
+
232
+ // Configure CORS for your frontend origins
233
+ connect(app, { graphqlHandler, db, unchainedAPI }, {
234
+ corsOrigins: "https://shop.example.com,https://admin.example.com",
235
+ });
236
+
237
+ // Create HTTPS server with your certificates
238
+ https.createServer({
239
+ key: fs.readFileSync('/path/to/privkey.pem'),
240
+ cert: fs.readFileSync('/path/to/fullchain.pem'),
241
+ }, app).listen(443);
242
+ ```
243
+
244
+ Environment:
245
+ ```bash
246
+ NODE_ENV=production
247
+ UNCHAINED_CORS_ORIGINS=https://shop.example.com,https://admin.example.com
248
+ UNCHAINED_TOKEN_SECRET=your-32-char-secret-here
249
+ ```
250
+
251
+ #### Scenario 2: Behind Reverse Proxy (Recommended)
252
+
253
+ For production deployments behind nginx, Caddy, or a cloud load balancer:
254
+
255
+ ```typescript
256
+ import express from 'express';
257
+ import { connect } from '@unchainedshop/api/express';
258
+
259
+ const app = express();
260
+
261
+ // Trust the reverse proxy for client IP headers
262
+ app.set('trust proxy', 1);
263
+
264
+ // Let the proxy handle CORS, or configure here
265
+ connect(app, { graphqlHandler, db, unchainedAPI }, {
266
+ corsOrigins: "https://shop.example.com,https://admin.example.com",
267
+ });
268
+
269
+ app.listen(4010); // Internal port, not exposed
270
+ ```
271
+
272
+ **Nginx configuration:**
273
+ ```nginx
274
+ server {
275
+ listen 443 ssl http2;
276
+ server_name api.example.com;
277
+
278
+ ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
279
+ ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
280
+
281
+ location / {
282
+ # Pass client IP to the app
283
+ proxy_set_header X-Real-IP $remote_addr;
284
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
285
+ proxy_set_header X-Forwarded-Proto $scheme;
286
+ proxy_set_header Host $host;
287
+
288
+ proxy_pass http://localhost:4010;
289
+ }
290
+ }
291
+ ```
292
+
293
+ **Caddy configuration:**
294
+ ```caddyfile
295
+ api.example.com {
296
+ reverse_proxy localhost:4010
297
+ }
298
+ ```
299
+
300
+ #### Scenario 3: Proxy Handles CORS
301
+
302
+ For complex multi-origin setups, let the reverse proxy manage CORS:
303
+
304
+ ```typescript
305
+ // Don't set corsOrigins - let proxy handle it
306
+ app.set('trust proxy', 1);
307
+ connect(app, { graphqlHandler, db, unchainedAPI });
308
+ ```
309
+
310
+ **Nginx with CORS:**
311
+ ```nginx
312
+ location / {
313
+ proxy_set_header X-Real-IP $remote_addr;
314
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
315
+ proxy_set_header X-Forwarded-Proto $scheme;
316
+
317
+ # CORS whitelist
318
+ set $cors "";
319
+ if ($http_origin ~* "^https://(shop|admin)\.example\.com$") {
320
+ set $cors $http_origin;
321
+ }
322
+ add_header 'Access-Control-Allow-Origin' $cors always;
323
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
324
+ add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
325
+ add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
326
+
327
+ if ($request_method = 'OPTIONS') { return 204; }
328
+
329
+ proxy_pass http://localhost:4010;
330
+ }
331
+ ```
332
+
333
+ ### Trust Proxy
334
+
335
+ In development mode (`NODE_ENV=development`), trust proxy is automatically enabled.
336
+
337
+ In production, configure it on your Express/Fastify app before calling `connect()`:
338
+
339
+ ```typescript
340
+ // Express
341
+ app.set('trust proxy', 1); // Trust first hop
342
+ app.set('trust proxy', 'loopback'); // Trust loopback addresses
343
+ app.set('trust proxy', '10.0.0.0/8'); // Trust specific CIDR
344
+
345
+ // Fastify
346
+ const fastify = Fastify({ trustProxy: true });
347
+ ```
348
+
349
+ **SECURITY**: Only enable trust proxy if your reverse proxy:
350
+ 1. Strips incoming `X-Real-IP` and `X-Forwarded-For` headers from clients
351
+ 2. Sets `X-Real-IP` to the actual client IP
352
+ 3. Is the only way to reach your API server
353
+
354
+ See [SECURITY.md](../../SECURITY.md) for complete security documentation including FIPS 140-3 mode.
355
+
152
356
  ## License
153
357
 
154
358
  EUPL-1.2
package/lib/api-index.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { registerEvents } from '@unchainedshop/events';
1
2
  import createGraphQLServer, {} from "./createGraphQLServer.js";
2
3
  import { createContextResolver, setCurrentContextResolver, getCurrentContextResolver, } from "./context.js";
4
+ import { API_EVENTS } from "./events.js";
3
5
  export * from "./events.js";
4
6
  export * from "./context.js";
5
7
  export * from "./locale-context.js";
@@ -12,6 +14,7 @@ import { buildDefaultTypeDefs } from "./schema/index.js";
12
14
  import resolvers from "./resolvers/index.js";
13
15
  import { actions } from "./roles/index.js";
14
16
  export const startAPIServer = async (options) => {
17
+ registerEvents(Object.keys(API_EVENTS));
15
18
  const { unchainedAPI, context: customContext, roles, adminUiConfig = {}, ...serverOptions } = options;
16
19
  const contextResolver = createContextResolver(unchainedAPI, {
17
20
  roles,
@@ -2,5 +2,11 @@ import type * as aiTypes from 'ai';
2
2
  declare const generateImageHandler: (req: any) => ({ model, uploadUrl, }: {
3
3
  model: aiTypes.ImageModel;
4
4
  uploadUrl?: string;
5
- }) => aiTypes.Tool<never, any>;
5
+ }) => aiTypes.Tool<{
6
+ prompt: string;
7
+ size?: "512x512" | "768x768" | "1024x1024" | "512x896" | "640x1120" | "768x1344" | "1024x1792" | "896x512" | "1120x640" | "1344x768" | "1792x1024" | undefined;
8
+ }, string | {
9
+ imageUrl: any;
10
+ prompt: string;
11
+ }>;
6
12
  export default generateImageHandler;
@@ -2,12 +2,12 @@ import { z } from 'zod';
2
2
  import mime from 'mime/lite';
3
3
  import { createLogger } from '@unchainedshop/logger';
4
4
  const logger = createLogger('unchained:api:mcp');
5
- let experimental_generateImage;
5
+ let generateImage;
6
6
  let tool;
7
7
  try {
8
8
  const aiTools = await import('ai');
9
9
  tool = aiTools.tool;
10
- experimental_generateImage = aiTools.experimental_generateImage;
10
+ generateImage = aiTools.generateImage;
11
11
  }
12
12
  catch {
13
13
  }
@@ -33,11 +33,10 @@ const inputSchema = z.object({
33
33
  const generateImageHandler = (req) => ({ model, uploadUrl = `${process.env.ROOT_URL}/temp-upload`, }) => {
34
34
  return tool({
35
35
  description: 'Generate an image based on the prompt',
36
- inputSchema: inputSchema,
37
- name: 'generate_image',
36
+ inputSchema,
38
37
  execute: async ({ prompt, size = '1024x1024' }) => {
39
38
  try {
40
- const { image } = await experimental_generateImage({
39
+ const { image } = await generateImage({
41
40
  model,
42
41
  prompt,
43
42
  size: size,
package/lib/context.js CHANGED
@@ -1,22 +1,11 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
1
  import instantiateLoaders, {} from "./loaders/index.js";
10
2
  import { getLocaleContext } from "./locale-context.js";
3
+ const { npm_package_version, UNCHAINED_API_VERSION } = process.env;
11
4
  let context;
12
5
  export const getCurrentContextResolver = () => context;
13
6
  export const setCurrentContextResolver = (newContext) => {
14
7
  context = newContext;
15
8
  };
16
- const { default: packageJson } = await import(__rewriteRelativeImportExtension(`${import.meta.dirname}/../package.json`), {
17
- with: { type: 'json' },
18
- });
19
- const { UNCHAINED_API_VERSION = packageJson?.version || process.env.npm_package_version || 'n/a' } = process.env;
20
9
  export const createContextResolver = (unchainedAPI, unchainedConfig) => async ({ getHeader, setHeader, remoteAddress, remotePort, userId, impersonatorId, accessToken, login, logout, }) => {
21
10
  const abstractHttpServerContext = { remoteAddress, remotePort, getHeader, setHeader };
22
11
  const loaders = instantiateLoaders(unchainedAPI);
@@ -43,6 +32,6 @@ export const createContextResolver = (unchainedAPI, unchainedConfig) => async ({
43
32
  ...userContext,
44
33
  ...abstractHttpServerContext,
45
34
  loaders,
46
- version: UNCHAINED_API_VERSION,
35
+ version: UNCHAINED_API_VERSION || npm_package_version || 'n/a',
47
36
  };
48
37
  };
package/lib/errors.d.ts CHANGED
@@ -66,6 +66,7 @@ export declare const UsernameAlreadyExistsError: any;
66
66
  export declare const UsernameOrEmailRequiredError: any;
67
67
  export declare const PasswordInvalidError: any;
68
68
  export declare const PasswordOrWebAuthnPublicKeyRequiredError: any;
69
+ export declare const WebAuthnVerificationFailedError: any;
69
70
  export declare const InvalidCredentialsError: any;
70
71
  export declare const InvalidResetTokenError: any;
71
72
  export declare const InvalidEmailVerificationTokenError: any;
package/lib/errors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { GraphQLError } from 'graphql';
2
2
  export const createError = (code, message) => class extends GraphQLError {
3
- constructor({ message: explicitMessage, ...data }) {
3
+ constructor({ message: explicitMessage, ...data } = {}) {
4
4
  super(explicitMessage || message, {
5
5
  extensions: {
6
6
  code,
@@ -76,6 +76,7 @@ export const UsernameAlreadyExistsError = createError('UsernameAlreadyExists', '
76
76
  export const UsernameOrEmailRequiredError = createError('UsernameOrEmailRequired', 'No username or email is provided.');
77
77
  export const PasswordInvalidError = createError('PasswordInvalidError', 'The provided password is invalid, maybe too insecure');
78
78
  export const PasswordOrWebAuthnPublicKeyRequiredError = createError('PasswordOrWebAuthnPublicKeyRequired', 'A password or a WebAuthn public key is required');
79
+ export const WebAuthnVerificationFailedError = createError('WebAuthnVerificationFailedError', 'WebAuthn credential verification failed');
79
80
  export const InvalidCredentialsError = createError('InvalidCredentials', 'Invalid credentials provided');
80
81
  export const InvalidResetTokenError = createError('InvalidResetTokenError', 'Token invalid or expired');
81
82
  export const InvalidEmailVerificationTokenError = createError('InvalidEmailVerificationTokenError', 'Token invalid or expired');
package/lib/events.js CHANGED
@@ -1,6 +1,4 @@
1
- import { registerEvents } from '@unchainedshop/events';
2
1
  export const API_EVENTS = {
3
2
  API_LOGIN_TOKEN_CREATED: 'API_LOGIN_TOKEN_CREATED',
4
3
  API_LOGOUT: 'API_LOGOUT',
5
4
  };
6
- registerEvents(Object.keys(API_EVENTS));
@@ -21,7 +21,7 @@ try {
21
21
  convertToModelMessages = aiTools.convertToModelMessages;
22
22
  stepCountIs = aiTools.stepCountIs;
23
23
  streamText = aiTools.streamText;
24
- createMCPClient = mcpTools.experimental_createMCPClient;
24
+ createMCPClient = mcpTools.createMCPClient;
25
25
  }
26
26
  catch {
27
27
  logger.warn(`optional peer npm packages 'ai', '@ai-sdk/mcp' and '@modelcontextprotocol/sdk' not installed, chat will not work`);
@@ -76,7 +76,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
76
76
  }
77
77
  }
78
78
  catch (e) {
79
- logger.error(`Failed to read resource ${resource.uri}:`, e);
79
+ logger.error(`Failed to read resource ${resource.uri}: ${e.message}`);
80
80
  }
81
81
  return null;
82
82
  }));
@@ -85,7 +85,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
85
85
  }
86
86
  }
87
87
  catch (e) {
88
- logger.error('Failed to fetch MCP resources:', e);
88
+ logger.error(`Failed to fetch MCP resources: ${e.message}`);
89
89
  }
90
90
  const tools = {
91
91
  ...defaultUnchainedTools,
@@ -111,7 +111,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
111
111
  anthropic: { cacheControl: { type: 'ephemeral' } },
112
112
  },
113
113
  };
114
- const normalizedMessages = convertToModelMessages(messages, { tools: cacheControlledTools });
114
+ const normalizedMessages = await convertToModelMessages(messages, { tools: cacheControlledTools });
115
115
  if (normalizedMessages.length > 0) {
116
116
  const lastIndex = normalizedMessages.length - 1;
117
117
  normalizedMessages[lastIndex] = {
@@ -1,6 +1,5 @@
1
1
  import { createLogger } from '@unchainedshop/logger';
2
2
  const logger = createLogger('unchained:erc-metadata');
3
- const { ROOT_URL = 'http://localhost:4010' } = process.env;
4
3
  const methodWrongHandler = (res) => {
5
4
  logger.error('Method not supported, return 404');
6
5
  res.status(404).end();
@@ -11,6 +10,7 @@ const ercMetadataMiddleware = async (req, res) => {
11
10
  if (req.method !== 'GET') {
12
11
  return methodWrongHandler(res);
13
12
  }
13
+ const { ROOT_URL = 'http://localhost:4010' } = process.env;
14
14
  const { services, modules, loaders, locale } = req.unchainedContext;
15
15
  const url = new URL(req.url, ROOT_URL);
16
16
  const pathname = url.pathname;
@@ -64,19 +64,33 @@ const addContext = async function middlewareWithContext(req, res, next) {
64
64
  const { user } = req;
65
65
  if (!user)
66
66
  return false;
67
+ const currentSessionId = req.sessionID;
68
+ const targetSessionId = sessionId || currentSessionId;
67
69
  const tokenObject = {
68
- _id: sessionId || req.sessionID,
70
+ _id: targetSessionId,
69
71
  userId: user._id,
70
72
  };
71
- await new Promise((resolve, reject) => {
72
- req.logout((error, result) => {
73
- if (error) {
74
- return reject(error);
75
- }
76
- req.session.impersonatorId = null;
77
- return resolve(result);
73
+ if (sessionId && sessionId !== currentSessionId) {
74
+ await new Promise((resolve, reject) => {
75
+ req.sessionStore.destroy(sessionId, (error) => {
76
+ if (error) {
77
+ return reject(error);
78
+ }
79
+ return resolve();
80
+ });
78
81
  });
79
- });
82
+ }
83
+ else {
84
+ await new Promise((resolve, reject) => {
85
+ req.logout((error, result) => {
86
+ if (error) {
87
+ return reject(error);
88
+ }
89
+ req.session.impersonatorId = null;
90
+ return resolve(result);
91
+ });
92
+ });
93
+ }
80
94
  await emit(API_EVENTS.API_LOGOUT, tokenObject);
81
95
  return true;
82
96
  };
@@ -20,7 +20,7 @@ try {
20
20
  convertToModelMessages = aiTools.convertToModelMessages;
21
21
  stepCountIs = aiTools.stepCountIs;
22
22
  streamText = aiTools.streamText;
23
- createMCPClient = mcpTools.experimental_createMCPClient;
23
+ createMCPClient = mcpTools.createMCPClient;
24
24
  }
25
25
  catch {
26
26
  logger.warn(`optional peer npm packages 'ai' and '@ai-sdk/mcp' not installed, chat will not work`);
@@ -73,7 +73,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
73
73
  }
74
74
  }
75
75
  catch (e) {
76
- logger.error(`Failed to read resource ${resource.uri}:`, e);
76
+ logger.error(`Failed to read resource ${resource.uri}: ${e.message}`);
77
77
  }
78
78
  return null;
79
79
  }));
@@ -82,7 +82,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
82
82
  }
83
83
  }
84
84
  catch (e) {
85
- logger.error('Failed to fetch MCP resources:', e);
85
+ logger.error(`Failed to fetch MCP resources: ${e.message}`);
86
86
  }
87
87
  const tools = {
88
88
  ...defaultUnchainedTools,
@@ -107,7 +107,7 @@ const setupMCPChatHandler = (chatConfiguration) => {
107
107
  anthropic: { cacheControl: { type: 'ephemeral' } },
108
108
  },
109
109
  };
110
- const normalizedMessages = convertToModelMessages(messages, { tools: cacheControlledTools });
110
+ const normalizedMessages = await convertToModelMessages(messages, { tools: cacheControlledTools });
111
111
  if (normalizedMessages.length > 0) {
112
112
  const lastIndex = normalizedMessages.length - 1;
113
113
  normalizedMessages[lastIndex] = {
@@ -1,7 +1,6 @@
1
1
  import { createLogger } from '@unchainedshop/logger';
2
2
  import { ProductContractStandard } from '@unchainedshop/core-products';
3
3
  const logger = createLogger('unchained:api:erc-metadata');
4
- const { ROOT_URL = 'http://localhost:4010' } = process.env;
5
4
  const notFoundHandler = (res) => {
6
5
  logger.error('Method not supported, return 404');
7
6
  res.status(404);
@@ -9,6 +8,7 @@ const notFoundHandler = (res) => {
9
8
  };
10
9
  const ercMetadataHandler = async (req, res) => {
11
10
  try {
11
+ const { ROOT_URL = 'http://localhost:4010' } = process.env;
12
12
  const { services, modules, locale, loaders } = req.unchainedContext;
13
13
  const url = new URL(req.url, ROOT_URL);
14
14
  if (!url.pathname.toLowerCase().endsWith('.json'))
@@ -189,9 +189,15 @@ export const connect = (fastify, { graphqlHandler, db, unchainedAPI, }, { allowR
189
189
  };
190
190
  const fallbackLandingPageHandler = (request, reply) => {
191
191
  if (request.raw.method === 'GET') {
192
- const staticURL = import.meta.resolve('@unchainedshop/api/index.html');
193
- const staticPath = new URL(staticURL).pathname.split('/').slice(0, -1).join('/');
194
- return reply.type('text/html').send(readFileSync(staticPath + '/index.html'));
192
+ try {
193
+ const staticURL = import.meta.resolve('@unchainedshop/api/index.html');
194
+ const staticPath = new URL(staticURL).pathname;
195
+ return reply.type('text/html').send(readFileSync(staticPath));
196
+ }
197
+ catch {
198
+ const fallbackPath = new URL('../../index.html', import.meta.url).pathname;
199
+ return reply.type('text/html').send(readFileSync(fallbackPath));
200
+ }
195
201
  }
196
202
  else {
197
203
  return reply.status(404).send();
@@ -3,7 +3,7 @@ import buildTextMap from "./buildTextMap.js";
3
3
  import buildLocaleMap from "./buildLocaleMap.js";
4
4
  export default (unchainedAPI) => new DataLoader(async (queries) => {
5
5
  const assortmentMediaIds = [...new Set(queries.map((q) => q.assortmentMediaId).filter(Boolean))];
6
- const texts = await unchainedAPI.modules.assortments.media.texts.findMediaTexts({ assortmentMediaId: { $in: assortmentMediaIds } }, {
6
+ const texts = await unchainedAPI.modules.assortments.media.texts.findMediaTexts({ assortmentMediaIds }, {
7
7
  sort: {
8
8
  assortmentMediaId: 1,
9
9
  },
@@ -2,7 +2,7 @@ import DataLoader from 'dataloader';
2
2
  export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const assortmentIds = [...new Set(queries.map((q) => q.assortmentId).filter(Boolean))];
4
4
  const assortmentMediaItems = await unchainedAPI.modules.assortments.media.findAssortmentMedias({
5
- assortmentId: { $in: assortmentIds },
5
+ assortmentIds,
6
6
  });
7
7
  const assortmentMediaMap = {};
8
8
  for (const assortmentMedia of assortmentMediaItems) {
@@ -3,7 +3,7 @@ import buildTextMap from "./buildTextMap.js";
3
3
  import buildLocaleMap from "./buildLocaleMap.js";
4
4
  export default (unchainedAPI) => new DataLoader(async (queries) => {
5
5
  const assortmentIds = [...new Set(queries.map((q) => q.assortmentId).filter(Boolean))];
6
- const texts = await unchainedAPI.modules.assortments.texts.findTexts({ assortmentId: { $in: assortmentIds } }, {
6
+ const texts = await unchainedAPI.modules.assortments.texts.findTexts({ assortmentIds }, {
7
7
  sort: {
8
8
  assortmentId: 1,
9
9
  },
@@ -2,7 +2,7 @@ import DataLoader from 'dataloader';
2
2
  export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const isoCodes = [...new Set(queries.map((q) => q.isoCode).filter(Boolean))];
4
4
  const countries = await unchainedAPI.modules.countries.findCountries({
5
- isoCode: { $in: isoCodes },
5
+ isoCodes,
6
6
  includeInactive: true,
7
7
  });
8
8
  const countryMap = {};
@@ -2,7 +2,7 @@ import DataLoader from 'dataloader';
2
2
  export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const isoCodes = [...new Set(queries.map((q) => q.isoCode).filter(Boolean))];
4
4
  const currencies = await unchainedAPI.modules.currencies.findCurrencies({
5
- isoCode: { $in: isoCodes },
5
+ isoCodes,
6
6
  includeInactive: true,
7
7
  });
8
8
  const currencyMap = {};
@@ -2,7 +2,7 @@ import DataLoader from 'dataloader';
2
2
  export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const deliveryProviderIds = [...new Set(queries.map((q) => q.deliveryProviderId).filter(Boolean))];
4
4
  const deliveryProviders = await unchainedAPI.modules.delivery.findProviders({
5
- _id: { $in: deliveryProviderIds },
5
+ deliveryProviderIds,
6
6
  includeDeleted: true,
7
7
  });
8
8
  const deliveryProviderMap = {};
@@ -2,7 +2,7 @@ import DataLoader from 'dataloader';
2
2
  export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const fileIds = [...new Set(queries.map((q) => q.fileId).filter(Boolean))];
4
4
  const files = await unchainedAPI.modules.files.findFiles({
5
- _id: { $in: fileIds },
5
+ fileIds,
6
6
  });
7
7
  const fileMap = {};
8
8
  for (const file of files) {
@@ -3,7 +3,7 @@ import buildLocaleMap from "./buildLocaleMap.js";
3
3
  import buildTextMap from "./buildTextMap.js";
4
4
  export default (unchainedAPI) => new DataLoader(async (queries) => {
5
5
  const filterIds = [...new Set(queries.map((q) => q.filterId).filter(Boolean))];
6
- const texts = await unchainedAPI.modules.filters.texts.findTexts({ filterId: { $in: filterIds } }, {
6
+ const texts = await unchainedAPI.modules.filters.texts.findTexts({ filterIds }, {
7
7
  sort: {
8
8
  filterId: 1,
9
9
  },
@@ -2,7 +2,7 @@ import DataLoader from 'dataloader';
2
2
  export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const isoCodes = [...new Set(queries.map((q) => q.isoCode).filter(Boolean))];
4
4
  const languages = await unchainedAPI.modules.languages.findLanguages({
5
- isoCode: { $in: isoCodes },
5
+ isoCodes,
6
6
  includeInactive: true,
7
7
  });
8
8
  const languageMap = {};
@@ -3,7 +3,7 @@ export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const orderIds = [...new Set(queries.map((q) => q.orderId).filter(Boolean))];
4
4
  const orders = await unchainedAPI.modules.orders.findOrders({
5
5
  includeCarts: true,
6
- _id: { $in: orderIds },
6
+ orderIds,
7
7
  });
8
8
  const orderMap = {};
9
9
  for (const order of orders) {
@@ -2,7 +2,7 @@ import DataLoader from 'dataloader';
2
2
  export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const paymentProviderIds = [...new Set(queries.map((q) => q.paymentProviderId).filter(Boolean))];
4
4
  const paymentProviders = await unchainedAPI.modules.payment.paymentProviders.findProviders({
5
- _id: { $in: paymentProviderIds },
5
+ paymentProviderIds,
6
6
  includeDeleted: true,
7
7
  });
8
8
  const paymentProviderMap = {};
@@ -2,10 +2,8 @@ import DataLoader from 'dataloader';
2
2
  export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const skus = [...new Set(queries.map((q) => q.sku).filter(Boolean))];
4
4
  const products = await unchainedAPI.modules.products.findProducts({
5
- productSelector: {
6
- 'warehousing.sku': { $in: skus },
7
- status: { $exists: true },
8
- },
5
+ skus,
6
+ includeDeleted: true,
9
7
  });
10
8
  const productMap = {};
11
9
  for (const product of products) {
@@ -3,9 +3,7 @@ export default (unchainedAPI) => new DataLoader(async (queries) => {
3
3
  const productIds = [...new Set(queries.map((q) => q.productId).filter(Boolean))];
4
4
  const products = await unchainedAPI.modules.products.findProducts({
5
5
  productIds,
6
- productSelector: {
7
- status: { $exists: true },
8
- },
6
+ includeDeleted: true,
9
7
  });
10
8
  const productMap = {};
11
9
  for (const product of products) {
@@ -3,7 +3,7 @@ import buildLocaleMap from "./buildLocaleMap.js";
3
3
  import buildTextMap from "./buildTextMap.js";
4
4
  export default (unchainedAPI) => new DataLoader(async (queries) => {
5
5
  const productMediaIds = [...new Set(queries.map((q) => q.productMediaId).filter(Boolean))];
6
- const texts = await unchainedAPI.modules.products.media.texts.findMediaTexts({ productMediaId: { $in: productMediaIds } }, {
6
+ const texts = await unchainedAPI.modules.products.media.texts.findMediaTexts({ productMediaIds }, {
7
7
  sort: {
8
8
  productMediaId: 1,
9
9
  },