@unchainedshop/api 4.8.18 → 4.8.20

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 (30) hide show
  1. package/lib/adminUiPlugins.d.ts +73 -0
  2. package/lib/adminUiPlugins.js +164 -0
  3. package/lib/chat/generateImageHandler.d.ts +273 -7
  4. package/lib/createGraphQLServer.js +6 -0
  5. package/lib/express/createMCPMiddleware.js +6 -3
  6. package/lib/fastify/mcpHandler.js +5 -2
  7. package/lib/mcp/tools/filter/handlers/createFilter.js +2 -4
  8. package/lib/mcp/tools/filter/handlers/createFilterOption.js +2 -4
  9. package/lib/mcp/tools/filter/handlers/removeFilterOption.js +2 -4
  10. package/lib/mcp/tools/filter/handlers/updateFilter.js +2 -2
  11. package/lib/mcp/tools/quotation/index.js +1 -1
  12. package/lib/mcp/utils/sharedSchemas.d.ts +1 -0
  13. package/lib/mcp/utils/sharedSchemas.js +1 -0
  14. package/lib/resolvers/mutations/bookmarks/createBookmark.d.ts +1 -1
  15. package/lib/resolvers/mutations/bookmarks/createBookmark.js +4 -3
  16. package/lib/resolvers/mutations/filters/createFilter.js +3 -4
  17. package/lib/resolvers/mutations/filters/createFilterOption.d.ts +1 -1
  18. package/lib/resolvers/mutations/filters/createFilterOption.js +3 -4
  19. package/lib/resolvers/mutations/filters/removeFilterOption.d.ts +1 -1
  20. package/lib/resolvers/mutations/filters/removeFilterOption.js +2 -8
  21. package/lib/resolvers/mutations/filters/updateFilter.d.ts +1 -1
  22. package/lib/resolvers/mutations/filters/updateFilter.js +2 -5
  23. package/lib/resolvers/scalars/LowerCaseString.d.ts +1 -1
  24. package/lib/resolvers/scalars/index.d.ts +1 -1
  25. package/lib/resolvers/type/index.d.ts +4 -0
  26. package/lib/resolvers/type/quotation-types.d.ts +4 -0
  27. package/lib/resolvers/type/quotation-types.js +1 -0
  28. package/lib/roles/loggedIn.js +1 -0
  29. package/lib/schema/types/quotation.js +5 -0
  30. package/package.json +14 -11
@@ -0,0 +1,73 @@
1
+ export interface AdminUIPluginEntityConfig {
2
+ path: string;
3
+ label: string;
4
+ icon?: string;
5
+ requiredRole?: string;
6
+ sortOrder?: number;
7
+ components: {
8
+ list: string;
9
+ detail: string;
10
+ create?: string;
11
+ };
12
+ }
13
+ export interface AdminUIPluginPageConfig {
14
+ path: string;
15
+ label: string;
16
+ icon?: string;
17
+ requiredRole?: string;
18
+ sortOrder?: number;
19
+ component: string;
20
+ }
21
+ export interface AdminUIPluginTabConfig {
22
+ label: string;
23
+ component: string;
24
+ requiredRole?: string;
25
+ }
26
+ export interface AdminUIPluginWidgetConfig {
27
+ component: string;
28
+ width?: 'full' | 'half' | 'third';
29
+ }
30
+ export interface AdminUIPluginSlotConfig {
31
+ component: string;
32
+ }
33
+ export interface AdminUIPluginConfig {
34
+ name: string;
35
+ version?: string;
36
+ bundlePath: string;
37
+ navigation?: {
38
+ label: string;
39
+ icon?: string;
40
+ requiredRole?: string;
41
+ sortOrder?: number;
42
+ };
43
+ slots: {
44
+ entities?: AdminUIPluginEntityConfig[];
45
+ pages?: AdminUIPluginPageConfig[];
46
+ 'dashboard:widgets'?: AdminUIPluginWidgetConfig[];
47
+ 'product:tabs'?: AdminUIPluginTabConfig[];
48
+ 'assortment:tabs'?: AdminUIPluginTabConfig[];
49
+ 'filter:tabs'?: AdminUIPluginTabConfig[];
50
+ 'user:tabs'?: AdminUIPluginTabConfig[];
51
+ 'order:tabs'?: AdminUIPluginTabConfig[];
52
+ [key: string]: AdminUIPluginTabConfig[] | AdminUIPluginSlotConfig[] | AdminUIPluginEntityConfig[] | AdminUIPluginPageConfig[] | AdminUIPluginWidgetConfig[] | undefined;
53
+ };
54
+ }
55
+ interface StaticAsset {
56
+ content: string | (() => string);
57
+ contentType: string;
58
+ cacheControl: string;
59
+ etag?: string;
60
+ }
61
+ export interface PreparedPluginAssets {
62
+ routes: Map<string, StaticAsset>;
63
+ validPlugins: AdminUIPluginConfig[];
64
+ }
65
+ export declare const resolveAdminUIPath: () => string | null;
66
+ export declare const parseBundleExports: (bundle: string) => Set<string> | null;
67
+ export declare function preparePluginAssets(plugins: AdminUIPluginConfig[], log: {
68
+ info: (...args: any[]) => void;
69
+ warn: (...args: any[]) => void;
70
+ }, options?: {
71
+ devMode?: boolean;
72
+ }): PreparedPluginAssets;
73
+ export {};
@@ -0,0 +1,164 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync, statSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ const PLUGIN_NAME_RE = /^[a-z0-9]([a-z0-9_-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9_-]*[a-z0-9])?)*$/i;
5
+ const IMMUTABLE_CACHE = 'public, max-age=31536000, immutable';
6
+ export const resolveAdminUIPath = () => {
7
+ try {
8
+ const staticURL = import.meta.resolve('@unchainedshop/admin-ui');
9
+ return new URL(staticURL).pathname.split('/').slice(0, -1).join('/');
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ };
15
+ const contentHash = (content) => createHash('sha256').update(content).digest('hex').slice(0, 8);
16
+ export const parseBundleExports = (bundle) => {
17
+ const returnMatch = bundle.match(/return __toCommonJS\(([\w$]+)\);/);
18
+ if (!returnMatch)
19
+ return null;
20
+ const blockMatch = bundle.match(new RegExp(`__export\\(${returnMatch[1].replace(/\$/g, '\\$')},\\s*\\{([\\s\\S]*?)\\}\\);`));
21
+ if (!blockMatch)
22
+ return null;
23
+ const names = new Set();
24
+ for (const m of blockMatch[1].matchAll(/(?:^|,)\s*(?:"([^"]+)"|([\w$]+)):\s*\(\)\s*=>/g)) {
25
+ names.add(m[1] ?? m[2]);
26
+ }
27
+ return names.size > 0 ? names : null;
28
+ };
29
+ const collectReferencedComponents = (plugin) => {
30
+ const names = new Set();
31
+ for (const [slotId, configs] of Object.entries(plugin.slots || {})) {
32
+ if (!Array.isArray(configs))
33
+ continue;
34
+ for (const config of configs) {
35
+ if (slotId === 'entities') {
36
+ const components = config.components;
37
+ if (components?.list)
38
+ names.add(components.list);
39
+ if (components?.detail)
40
+ names.add(components.detail);
41
+ if (components?.create)
42
+ names.add(components.create);
43
+ }
44
+ else if (typeof config.component === 'string') {
45
+ names.add(config.component);
46
+ }
47
+ }
48
+ }
49
+ return [...names];
50
+ };
51
+ export function preparePluginAssets(plugins, log, options = {}) {
52
+ const { devMode = false } = options;
53
+ const routes = new Map();
54
+ const devCacheControl = 'no-cache, no-store, must-revalidate';
55
+ const validPlugins = plugins.filter((p) => {
56
+ if (!PLUGIN_NAME_RE.test(p.name)) {
57
+ log.warn(`Skipping admin-ui plugin with invalid name: "${p.name}"`);
58
+ return false;
59
+ }
60
+ return true;
61
+ });
62
+ const seenNames = new Set();
63
+ for (const plugin of validPlugins) {
64
+ if (seenNames.has(plugin.name)) {
65
+ log.warn(`Duplicate admin-ui plugin name "${plugin.name}": later entries override earlier ones`);
66
+ }
67
+ seenNames.add(plugin.name);
68
+ }
69
+ if (validPlugins.length > 0) {
70
+ const pluginList = validPlugins
71
+ .map((p) => `${p.name}${p.version ? `@${p.version}` : ''}`)
72
+ .join(', ');
73
+ log.info(`Loading ${validPlugins.length} admin-ui plugin(s): ${pluginList}`);
74
+ }
75
+ const resolvedBundlePaths = new Map();
76
+ const pluginBundles = new Map();
77
+ for (const plugin of validPlugins) {
78
+ try {
79
+ const bundlePath = resolve(plugin.bundlePath);
80
+ const content = readFileSync(bundlePath, 'utf-8');
81
+ resolvedBundlePaths.set(plugin.name, bundlePath);
82
+ pluginBundles.set(plugin.name, { content, hash: contentHash(content) });
83
+ }
84
+ catch (err) {
85
+ log.warn(`Failed to read bundle for plugin "${plugin.name}" at ${plugin.bundlePath}: ${err.message}`);
86
+ }
87
+ }
88
+ const pluginsWithBundles = validPlugins.filter((p) => pluginBundles.has(p.name));
89
+ for (const plugin of pluginsWithBundles) {
90
+ const exportNames = parseBundleExports(pluginBundles.get(plugin.name).content);
91
+ if (!exportNames)
92
+ continue;
93
+ const missing = collectReferencedComponents(plugin).filter((name) => !exportNames.has(name));
94
+ if (missing.length > 0) {
95
+ log.warn(`admin-ui plugin "${plugin.name}" references component(s) not exported by its bundle: ${missing.join(', ')}. Exported: ${[...exportNames].join(', ')}`);
96
+ }
97
+ }
98
+ const bundleMtimes = new Map();
99
+ const bundleHashes = new Map();
100
+ for (const [name, bundle] of pluginBundles) {
101
+ bundleHashes.set(name, bundle.hash);
102
+ try {
103
+ bundleMtimes.set(name, statSync(resolvedBundlePaths.get(name)).mtimeMs);
104
+ }
105
+ catch {
106
+ }
107
+ }
108
+ const refreshBundleHash = (name) => {
109
+ const bundlePath = resolvedBundlePaths.get(name);
110
+ try {
111
+ const currentMtime = statSync(bundlePath).mtimeMs;
112
+ const cachedMtime = bundleMtimes.get(name);
113
+ if (cachedMtime !== undefined && cachedMtime === currentMtime) {
114
+ return bundleHashes.get(name);
115
+ }
116
+ const hash = contentHash(readFileSync(bundlePath, 'utf-8'));
117
+ bundleMtimes.set(name, currentMtime);
118
+ bundleHashes.set(name, hash);
119
+ return hash;
120
+ }
121
+ catch {
122
+ return bundleHashes.get(name) ?? pluginBundles.get(name).hash;
123
+ }
124
+ };
125
+ const buildManifestJSON = () => JSON.stringify(pluginsWithBundles.map((plugin) => ({
126
+ ...Object.fromEntries(Object.entries(plugin).filter(([k]) => k !== 'bundlePath')),
127
+ bundleUrl: `/admin-plugins/${plugin.name}.js?v=${devMode ? refreshBundleHash(plugin.name) : pluginBundles.get(plugin.name).hash}`,
128
+ })));
129
+ if (devMode) {
130
+ routes.set('/admin-ui-plugins.json', {
131
+ content: buildManifestJSON,
132
+ contentType: 'application/json',
133
+ cacheControl: devCacheControl,
134
+ });
135
+ }
136
+ else {
137
+ const manifestJSON = buildManifestJSON();
138
+ routes.set('/admin-ui-plugins.json', {
139
+ content: manifestJSON,
140
+ contentType: 'application/json',
141
+ cacheControl: 'public, max-age=0, must-revalidate',
142
+ etag: `"${contentHash(manifestJSON)}"`,
143
+ });
144
+ }
145
+ for (const plugin of pluginsWithBundles) {
146
+ const bundlePath = resolvedBundlePaths.get(plugin.name);
147
+ if (devMode) {
148
+ routes.set(`/admin-plugins/${plugin.name}.js`, {
149
+ content: () => readFileSync(bundlePath, 'utf-8'),
150
+ contentType: 'application/javascript',
151
+ cacheControl: devCacheControl,
152
+ });
153
+ }
154
+ else {
155
+ const { content } = pluginBundles.get(plugin.name);
156
+ routes.set(`/admin-plugins/${plugin.name}.js`, {
157
+ content,
158
+ contentType: 'application/javascript',
159
+ cacheControl: IMMUTABLE_CACHE,
160
+ });
161
+ }
162
+ }
163
+ return { routes, validPlugins };
164
+ }
@@ -2,11 +2,277 @@ 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<{
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
- }>;
5
+ }) => ({
6
+ title?: string;
7
+ providerOptions?: import("@ai-sdk/provider-utils").ProviderOptions;
8
+ metadata?: import("@ai-sdk/provider").JSONObject;
9
+ inputSchema: aiTypes.FlexibleSchema<{
10
+ prompt: any;
11
+ size?: string | undefined;
12
+ }>;
13
+ contextSchema?: aiTypes.FlexibleSchema<import("@ai-sdk/provider-utils").Context> | undefined;
14
+ needsApproval?: boolean | import("@ai-sdk/provider-utils").ToolNeedsApprovalFunction<{
15
+ prompt: any;
16
+ size?: string | undefined;
17
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>> | undefined;
18
+ onInputStart?: ((options: aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
19
+ onInputDelta?: ((options: {
20
+ inputTextDelta: string;
21
+ } & aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
22
+ onInputAvailable?: ((options: {
23
+ input: {
24
+ prompt: any;
25
+ size?: string | undefined;
26
+ };
27
+ } & aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
28
+ toModelOutput?: ((options: {
29
+ toolCallId: string;
30
+ input: {
31
+ prompt: any;
32
+ size?: string | undefined;
33
+ };
34
+ output: NoInfer<string | {
35
+ imageUrl: any;
36
+ prompt: any;
37
+ }>;
38
+ }) => import("@ai-sdk/provider-utils").ToolResultOutput | PromiseLike<import("@ai-sdk/provider-utils").ToolResultOutput>) | undefined;
39
+ } & {
40
+ outputSchema?: aiTypes.FlexibleSchema<string | {
41
+ imageUrl: any;
42
+ prompt: any;
43
+ }> | undefined;
44
+ execute: aiTypes.ToolExecuteFunction<{
45
+ prompt: any;
46
+ size?: string | undefined;
47
+ }, string | {
48
+ imageUrl: any;
49
+ prompt: any;
50
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>>;
51
+ } & {
52
+ description?: string | ((options: {
53
+ context: NoInfer<import("@ai-sdk/provider-utils").Context>;
54
+ experimental_sandbox?: aiTypes.Experimental_SandboxSession;
55
+ }) => string) | undefined;
56
+ strict?: boolean;
57
+ inputExamples?: {
58
+ input: NoInfer<{
59
+ prompt: any;
60
+ size?: string | undefined;
61
+ }>;
62
+ }[] | undefined;
63
+ id?: never;
64
+ isProviderExecuted?: never;
65
+ args?: never;
66
+ supportsDeferredResults?: never;
67
+ } & {
68
+ type?: undefined | "function";
69
+ } & {
70
+ execute: aiTypes.ToolExecuteFunction<{
71
+ prompt: any;
72
+ size?: string | undefined;
73
+ }, string | {
74
+ imageUrl: any;
75
+ prompt: any;
76
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>>;
77
+ }) | ({
78
+ title?: string;
79
+ providerOptions?: import("@ai-sdk/provider-utils").ProviderOptions;
80
+ metadata?: import("@ai-sdk/provider").JSONObject;
81
+ inputSchema: aiTypes.FlexibleSchema<{
82
+ prompt: any;
83
+ size?: string | undefined;
84
+ }>;
85
+ contextSchema?: aiTypes.FlexibleSchema<import("@ai-sdk/provider-utils").Context> | undefined;
86
+ needsApproval?: boolean | import("@ai-sdk/provider-utils").ToolNeedsApprovalFunction<{
87
+ prompt: any;
88
+ size?: string | undefined;
89
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>> | undefined;
90
+ onInputStart?: ((options: aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
91
+ onInputDelta?: ((options: {
92
+ inputTextDelta: string;
93
+ } & aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
94
+ onInputAvailable?: ((options: {
95
+ input: {
96
+ prompt: any;
97
+ size?: string | undefined;
98
+ };
99
+ } & aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
100
+ toModelOutput?: ((options: {
101
+ toolCallId: string;
102
+ input: {
103
+ prompt: any;
104
+ size?: string | undefined;
105
+ };
106
+ output: NoInfer<string | {
107
+ imageUrl: any;
108
+ prompt: any;
109
+ }>;
110
+ }) => import("@ai-sdk/provider-utils").ToolResultOutput | PromiseLike<import("@ai-sdk/provider-utils").ToolResultOutput>) | undefined;
111
+ } & {
112
+ outputSchema?: aiTypes.FlexibleSchema<string | {
113
+ imageUrl: any;
114
+ prompt: any;
115
+ }> | undefined;
116
+ execute: aiTypes.ToolExecuteFunction<{
117
+ prompt: any;
118
+ size?: string | undefined;
119
+ }, string | {
120
+ imageUrl: any;
121
+ prompt: any;
122
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>>;
123
+ } & {
124
+ description?: string | ((options: {
125
+ context: NoInfer<import("@ai-sdk/provider-utils").Context>;
126
+ experimental_sandbox?: aiTypes.Experimental_SandboxSession;
127
+ }) => string) | undefined;
128
+ strict?: boolean;
129
+ inputExamples?: {
130
+ input: NoInfer<{
131
+ prompt: any;
132
+ size?: string | undefined;
133
+ }>;
134
+ }[] | undefined;
135
+ id?: never;
136
+ isProviderExecuted?: never;
137
+ args?: never;
138
+ supportsDeferredResults?: never;
139
+ } & {
140
+ type: "dynamic";
141
+ } & {
142
+ execute: aiTypes.ToolExecuteFunction<{
143
+ prompt: any;
144
+ size?: string | undefined;
145
+ }, string | {
146
+ imageUrl: any;
147
+ prompt: any;
148
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>>;
149
+ }) | ({
150
+ title?: string;
151
+ providerOptions?: import("@ai-sdk/provider-utils").ProviderOptions;
152
+ metadata?: import("@ai-sdk/provider").JSONObject;
153
+ inputSchema: aiTypes.FlexibleSchema<{
154
+ prompt: any;
155
+ size?: string | undefined;
156
+ }>;
157
+ contextSchema?: aiTypes.FlexibleSchema<import("@ai-sdk/provider-utils").Context> | undefined;
158
+ needsApproval?: boolean | import("@ai-sdk/provider-utils").ToolNeedsApprovalFunction<{
159
+ prompt: any;
160
+ size?: string | undefined;
161
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>> | undefined;
162
+ onInputStart?: ((options: aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
163
+ onInputDelta?: ((options: {
164
+ inputTextDelta: string;
165
+ } & aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
166
+ onInputAvailable?: ((options: {
167
+ input: {
168
+ prompt: any;
169
+ size?: string | undefined;
170
+ };
171
+ } & aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
172
+ toModelOutput?: ((options: {
173
+ toolCallId: string;
174
+ input: {
175
+ prompt: any;
176
+ size?: string | undefined;
177
+ };
178
+ output: NoInfer<string | {
179
+ imageUrl: any;
180
+ prompt: any;
181
+ }>;
182
+ }) => import("@ai-sdk/provider-utils").ToolResultOutput | PromiseLike<import("@ai-sdk/provider-utils").ToolResultOutput>) | undefined;
183
+ } & {
184
+ outputSchema?: aiTypes.FlexibleSchema<string | {
185
+ imageUrl: any;
186
+ prompt: any;
187
+ }> | undefined;
188
+ execute: aiTypes.ToolExecuteFunction<{
189
+ prompt: any;
190
+ size?: string | undefined;
191
+ }, string | {
192
+ imageUrl: any;
193
+ prompt: any;
194
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>>;
195
+ } & {
196
+ type: "provider";
197
+ id: `${string}.${string}`;
198
+ args: Record<string, unknown>;
199
+ description?: never;
200
+ strict?: never;
201
+ inputExamples?: never;
202
+ } & {
203
+ isProviderExecuted: false;
204
+ supportsDeferredResults?: never;
205
+ } & {
206
+ execute: aiTypes.ToolExecuteFunction<{
207
+ prompt: any;
208
+ size?: string | undefined;
209
+ }, string | {
210
+ imageUrl: any;
211
+ prompt: any;
212
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>>;
213
+ }) | ({
214
+ title?: string;
215
+ providerOptions?: import("@ai-sdk/provider-utils").ProviderOptions;
216
+ metadata?: import("@ai-sdk/provider").JSONObject;
217
+ inputSchema: aiTypes.FlexibleSchema<{
218
+ prompt: any;
219
+ size?: string | undefined;
220
+ }>;
221
+ contextSchema?: aiTypes.FlexibleSchema<import("@ai-sdk/provider-utils").Context> | undefined;
222
+ needsApproval?: boolean | import("@ai-sdk/provider-utils").ToolNeedsApprovalFunction<{
223
+ prompt: any;
224
+ size?: string | undefined;
225
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>> | undefined;
226
+ onInputStart?: ((options: aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
227
+ onInputDelta?: ((options: {
228
+ inputTextDelta: string;
229
+ } & aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
230
+ onInputAvailable?: ((options: {
231
+ input: {
232
+ prompt: any;
233
+ size?: string | undefined;
234
+ };
235
+ } & aiTypes.ToolExecutionOptions<NoInfer<import("@ai-sdk/provider-utils").Context>>) => void | PromiseLike<void>) | undefined;
236
+ toModelOutput?: ((options: {
237
+ toolCallId: string;
238
+ input: {
239
+ prompt: any;
240
+ size?: string | undefined;
241
+ };
242
+ output: NoInfer<string | {
243
+ imageUrl: any;
244
+ prompt: any;
245
+ }>;
246
+ }) => import("@ai-sdk/provider-utils").ToolResultOutput | PromiseLike<import("@ai-sdk/provider-utils").ToolResultOutput>) | undefined;
247
+ } & {
248
+ outputSchema?: aiTypes.FlexibleSchema<string | {
249
+ imageUrl: any;
250
+ prompt: any;
251
+ }> | undefined;
252
+ execute: aiTypes.ToolExecuteFunction<{
253
+ prompt: any;
254
+ size?: string | undefined;
255
+ }, string | {
256
+ imageUrl: any;
257
+ prompt: any;
258
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>>;
259
+ } & {
260
+ type: "provider";
261
+ id: `${string}.${string}`;
262
+ args: Record<string, unknown>;
263
+ description?: never;
264
+ strict?: never;
265
+ inputExamples?: never;
266
+ } & {
267
+ isProviderExecuted: true;
268
+ supportsDeferredResults?: boolean;
269
+ } & {
270
+ execute: aiTypes.ToolExecuteFunction<{
271
+ prompt: any;
272
+ size?: string | undefined;
273
+ }, string | {
274
+ imageUrl: any;
275
+ prompt: any;
276
+ }, NoInfer<import("@ai-sdk/provider-utils").Context>>;
277
+ });
12
278
  export default generateImageHandler;
@@ -1,5 +1,7 @@
1
1
  import { createLogger } from '@unchainedshop/logger';
2
2
  import { createYoga, createSchema, } from 'graphql-yoga';
3
+ import { useEngine } from '@envelop/core';
4
+ import { parse, validate, specifiedRules, execute, subscribe } from 'graphql';
3
5
  const logger = createLogger('unchained:api');
4
6
  export default async (options) => {
5
7
  const schema = 'schema' in options
@@ -15,6 +17,10 @@ export default async (options) => {
15
17
  return ctx.req?.unchainedContext;
16
18
  },
17
19
  ...options,
20
+ plugins: [
21
+ useEngine({ parse, validate, specifiedRules, execute, subscribe }),
22
+ ...(options.plugins ?? []),
23
+ ],
18
24
  });
19
25
  return server;
20
26
  };
@@ -31,13 +31,15 @@ const handlePostRequest = async (req, res) => {
31
31
  });
32
32
  return;
33
33
  }
34
+ Object.assign(transports[sessionId].context, req.unchainedContext);
34
35
  transport = transports[sessionId].transport;
35
36
  }
36
37
  else if (!sessionId && isInitializeRequest(req.body)) {
38
+ const contextHolder = req.unchainedContext;
37
39
  transport = new StreamableHTTPServerTransport({
38
40
  sessionIdGenerator: () => crypto.randomUUID(),
39
41
  onsessioninitialized: (sessionId) => {
40
- transports[sessionId] = { transport, userId: currentUserId };
42
+ transports[sessionId] = { transport, userId: currentUserId, context: contextHolder };
41
43
  },
42
44
  });
43
45
  transport.onclose = () => {
@@ -45,12 +47,12 @@ const handlePostRequest = async (req, res) => {
45
47
  delete transports[transport.sessionId];
46
48
  }
47
49
  };
48
- const roles = req.unchainedContext.user?.roles || [];
50
+ const roles = contextHolder.user?.roles || [];
49
51
  const { default: initMCPServer } = await import("../mcp/index.js");
50
52
  const server = initMCPServer(new McpServer({
51
53
  name: 'Unchained MCP Server',
52
54
  version: '1.0.0',
53
- }), req.unchainedContext, roles);
55
+ }), contextHolder, roles);
54
56
  await server.connect(transport);
55
57
  }
56
58
  else {
@@ -74,6 +76,7 @@ const handleSessionRequest = async (req, res) => {
74
76
  res.status(400).send('Invalid or missing session ID');
75
77
  return;
76
78
  }
79
+ Object.assign(transports[sessionId].context, req.unchainedContext);
77
80
  const transport = transports[sessionId].transport;
78
81
  await transport.handleRequest(req, res);
79
82
  };
@@ -45,13 +45,15 @@ const mcpHandler = async (req, res) => {
45
45
  id: null,
46
46
  }));
47
47
  }
48
+ Object.assign(transports[sessionId].context, req.unchainedContext);
48
49
  transport = transports[sessionId].transport;
49
50
  }
50
51
  else if (!sessionId && isInitializeRequest(req.body)) {
52
+ const contextHolder = req.unchainedContext;
51
53
  transport = new StreamableHTTPServerTransport({
52
54
  sessionIdGenerator: () => crypto.randomUUID(),
53
55
  onsessioninitialized: (sessionId) => {
54
- transports[sessionId] = { transport, userId: currentUserId };
56
+ transports[sessionId] = { transport, userId: currentUserId, context: contextHolder };
55
57
  },
56
58
  });
57
59
  transport.onclose = () => {
@@ -64,7 +66,7 @@ const mcpHandler = async (req, res) => {
64
66
  const server = initMCPServer(new McpServer({
65
67
  name: 'Unchained MCP Server',
66
68
  version: '1.0.0',
67
- }), req.unchainedContext, roles);
69
+ }), contextHolder, roles);
68
70
  await server.connect(transport);
69
71
  }
70
72
  else {
@@ -85,6 +87,7 @@ const mcpHandler = async (req, res) => {
85
87
  if (!sessionId || !transports[sessionId] || transports[sessionId].userId !== currentUserId) {
86
88
  return res.status(400).send('Invalid or missing session ID');
87
89
  }
90
+ Object.assign(transports[sessionId].context, req.unchainedContext);
88
91
  const transport = transports[sessionId].transport;
89
92
  await transport.handleRequest(req.raw, res.raw);
90
93
  return res;
@@ -1,16 +1,14 @@
1
- import { FilterDirector } from '@unchainedshop/core';
2
1
  import { getNormalizedFilterDetails } from "../../../utils/getNormalizedFilterDetails.js";
3
2
  export default async function createFilter(context, params) {
4
- const { modules } = context;
3
+ const { modules, services } = context;
5
4
  const { filter, texts } = params;
6
5
  const { key, type, options = [] } = filter;
7
- const newFilter = await modules.filters.create({
6
+ const newFilter = await services.filters.createFilter({
8
7
  key,
9
8
  type,
10
9
  options,
11
10
  isActive: true,
12
11
  });
13
- await FilterDirector.invalidateProductIdCache(newFilter, context);
14
12
  if (texts && texts.length > 0) {
15
13
  await modules.filters.texts.updateTexts({ filterId: newFilter._id }, texts);
16
14
  }
@@ -1,16 +1,14 @@
1
- import { FilterDirector } from '@unchainedshop/core';
2
1
  import { FilterNotFoundError } from "../../../../errors.js";
3
2
  import { getNormalizedFilterDetails } from "../../../utils/getNormalizedFilterDetails.js";
4
3
  export default async function createFilterOption(context, params) {
5
- const { modules } = context;
4
+ const { modules, services } = context;
6
5
  const { filterId, option, optionTexts } = params;
7
6
  if (!(await modules.filters.filterExists({ filterId }))) {
8
7
  throw new FilterNotFoundError({ filterId });
9
8
  }
10
- const newOption = await modules.filters.createFilterOption(filterId, { value: option });
9
+ const newOption = await services.filters.createFilterOption(filterId, { value: option });
11
10
  if (!newOption)
12
11
  return { filter: null };
13
- await FilterDirector.invalidateProductIdCache(newOption, context);
14
12
  if (optionTexts && optionTexts.length > 0) {
15
13
  await modules.filters.texts.updateTexts({ filterId, filterOptionValue: option }, optionTexts);
16
14
  }
@@ -1,19 +1,17 @@
1
- import { FilterDirector } from '@unchainedshop/core';
2
1
  import { FilterNotFoundError } from "../../../../errors.js";
3
2
  import { getNormalizedFilterDetails } from "../../../utils/getNormalizedFilterDetails.js";
4
3
  export default async function removeFilterOption(context, params) {
5
- const { modules } = context;
4
+ const { modules, services } = context;
6
5
  const { filterId, option } = params;
7
6
  if (!(await modules.filters.filterExists({ filterId }))) {
8
7
  throw new FilterNotFoundError({ filterId });
9
8
  }
10
- const removedFilterOption = await modules.filters.removeFilterOption({
9
+ const removedFilterOption = await services.filters.removeFilterOption({
11
10
  filterId,
12
11
  filterOptionValue: option,
13
12
  });
14
13
  if (!removedFilterOption)
15
14
  return { filter: null };
16
- await FilterDirector.invalidateProductIdCache(removedFilterOption, context);
17
15
  const normalizedFilter = await getNormalizedFilterDetails(filterId, context);
18
16
  return { filter: normalizedFilter };
19
17
  }
@@ -1,12 +1,12 @@
1
1
  import { FilterNotFoundError } from "../../../../errors.js";
2
2
  import { getNormalizedFilterDetails } from "../../../utils/getNormalizedFilterDetails.js";
3
3
  export default async function updateFilter(context, params) {
4
- const { modules } = context;
4
+ const { modules, services } = context;
5
5
  const { filterId, updateData } = params;
6
6
  if (!(await modules.filters.filterExists({ filterId }))) {
7
7
  throw new FilterNotFoundError({ filterId });
8
8
  }
9
- await modules.filters.update(filterId, updateData);
9
+ await services.filters.updateFilter(filterId, updateData);
10
10
  const normalizedFilter = await getNormalizedFilterDetails(filterId, context);
11
11
  return { filter: normalizedFilter };
12
12
  }
@@ -1,4 +1,4 @@
1
1
  import { quotationManagement, QuotationManagementSchema } from "./quotationManagement.js";
2
2
  export const registerQuotationTools = (server, context) => {
3
- server.tool('quotation_management', 'Unified quotation management system. Supports: LIST (get quotations with filters and pagination), GET (single quotation by ID), COUNT (count quotations), REQUEST (create new quotation request for a product), VERIFY (verify a REQUESTED quotation), MAKE_PROPOSAL (create proposal for PROCESSING quotation), REJECT (reject any quotation except FULFILLED). Quotations go through lifecycle: REQUESTED → PROCESSING → PROPOSED → FULFILLED/REJECTED.', QuotationManagementSchema, async (params) => quotationManagement(context, params));
3
+ server.tool('quotation_management', 'Unified quotation management system. Supports: LIST (get quotations with filters and pagination), GET (single quotation by ID), COUNT (count quotations), VERIFY (verify a REQUESTED quotation), MAKE_PROPOSAL (create proposal for PROCESSING quotation), REJECT (reject any quotation except FULFILLED). Quotations go through lifecycle: REQUESTED → PROCESSING → PROPOSED → FULFILLED/REJECTED.', QuotationManagementSchema, async (params) => quotationManagement(context, params));
4
4
  };
@@ -73,6 +73,7 @@ export declare function createMcpResponse(response: any): {
73
73
  }[];
74
74
  };
75
75
  export declare function createMcpErrorResponse(action: string, error: Error): {
76
+ isError: boolean;
76
77
  content: {
77
78
  type: "text";
78
79
  text: string;
@@ -88,6 +88,7 @@ export function createMcpResponse(response) {
88
88
  }
89
89
  export function createMcpErrorResponse(action, error) {
90
90
  return {
91
+ isError: true,
91
92
  content: [
92
93
  {
93
94
  type: 'text',
@@ -1,6 +1,6 @@
1
1
  import type { Context } from '../../../context.ts';
2
2
  export default function createBookmark(root: never, { productId, userId, meta }: {
3
3
  productId: string;
4
- userId: string;
4
+ userId?: string;
5
5
  meta?: any;
6
6
  }, { modules, userId: currentUserId }: Context): Promise<import("mongodb").WithId<import("@unchainedshop/core-bookmarks").Bookmark> | null>;
@@ -1,7 +1,8 @@
1
1
  import { log } from '@unchainedshop/logger';
2
2
  import { BookmarkAlreadyExistsError, InvalidIdError, ProductNotFoundError } from "../../../errors.js";
3
3
  export default async function createBookmark(root, { productId, userId, meta }, { modules, userId: currentUserId }) {
4
- log(`mutation createBookmark for ${userId}`, {
4
+ const targetUserId = userId || currentUserId;
5
+ log(`mutation createBookmark for ${targetUserId}`, {
5
6
  productId,
6
7
  userId: currentUserId,
7
8
  });
@@ -11,13 +12,13 @@ export default async function createBookmark(root, { productId, userId, meta },
11
12
  throw new ProductNotFoundError({ productId });
12
13
  const [bookmark] = await modules.bookmarks.findBookmarks({
13
14
  productId,
14
- userId,
15
+ userId: targetUserId,
15
16
  meta,
16
17
  });
17
18
  if (bookmark)
18
19
  throw new BookmarkAlreadyExistsError({ bookmarkId: bookmark._id });
19
20
  const bookmarkId = await modules.bookmarks.create({
20
- userId,
21
+ userId: targetUserId,
21
22
  productId,
22
23
  meta,
23
24
  });
@@ -1,12 +1,11 @@
1
1
  import { log } from '@unchainedshop/logger';
2
- import { FilterDirector } from '@unchainedshop/core';
2
+ import {} from '@unchainedshop/core';
3
3
  import { DuplicateFilterKeyError } from "../../../errors.js";
4
4
  export default async function createFilter(root, { filter, texts }, context) {
5
- const { modules, userId } = context;
5
+ const { modules, services, userId } = context;
6
6
  log('mutation createFilter', { userId });
7
7
  try {
8
- const newFilter = await modules.filters.create(filter);
9
- await FilterDirector.invalidateProductIdCache(newFilter, context);
8
+ const newFilter = await services.filters.createFilter(filter);
10
9
  if (texts) {
11
10
  await modules.filters.texts.updateTexts({ filterId: newFilter._id }, texts);
12
11
  }
@@ -4,4 +4,4 @@ export default function createFilterOption(root: never, params: {
4
4
  filterId: string;
5
5
  option: string;
6
6
  texts?: FilterInputText[];
7
- }, context: Context): Promise<import("mongodb").WithId<import("@unchainedshop/core-filters").Filter> | null>;
7
+ }, context: Context): Promise<import("@unchainedshop/core-filters").Filter | null>;
@@ -1,16 +1,15 @@
1
1
  import { log } from '@unchainedshop/logger';
2
- import { FilterDirector } from '@unchainedshop/core';
2
+ import {} from '@unchainedshop/core';
3
3
  import { FilterNotFoundError, InvalidIdError } from "../../../errors.js";
4
4
  export default async function createFilterOption(root, params, context) {
5
- const { modules, userId } = context;
5
+ const { modules, services, userId } = context;
6
6
  const { filterId, option, texts } = params;
7
7
  log(`mutation createFilterOption ${filterId}`, { userId });
8
8
  if (!filterId)
9
9
  throw new InvalidIdError({ filterId });
10
10
  if (!(await modules.filters.filterExists({ filterId })))
11
11
  throw new FilterNotFoundError({ filterId });
12
- const filter = await modules.filters.createFilterOption(filterId, { value: option });
13
- await FilterDirector.invalidateProductIdCache(filter, context);
12
+ const filter = await services.filters.createFilterOption(filterId, { value: option });
14
13
  if (texts) {
15
14
  await modules.filters.texts.updateTexts({ filterId, filterOptionValue: option }, texts);
16
15
  }
@@ -2,4 +2,4 @@ import type { Context } from '../../../context.ts';
2
2
  export default function removeFilterOption(root: never, { filterId, filterOptionValue }: {
3
3
  filterId: string;
4
4
  filterOptionValue: string;
5
- }, context: Context): Promise<import("mongodb").WithId<import("@unchainedshop/core-filters").Filter> | null>;
5
+ }, context: Context): Promise<import("@unchainedshop/core-filters").Filter | null>;
@@ -1,17 +1,11 @@
1
1
  import { log } from '@unchainedshop/logger';
2
2
  import { FilterNotFoundError, InvalidIdError } from "../../../errors.js";
3
- import { FilterDirector } from '@unchainedshop/core';
4
3
  export default async function removeFilterOption(root, { filterId, filterOptionValue }, context) {
5
- const { modules, userId } = context;
4
+ const { modules, services, userId } = context;
6
5
  log(`mutation removeFilterOption ${filterId}`, { userId });
7
6
  if (!filterId || !filterOptionValue)
8
7
  throw new InvalidIdError({ filterId, filterOptionValue });
9
8
  if (!(await modules.filters.filterExists({ filterId })))
10
9
  throw new FilterNotFoundError({ filterId });
11
- const filter = await modules.filters.removeFilterOption({
12
- filterId,
13
- filterOptionValue,
14
- });
15
- await FilterDirector.invalidateProductIdCache(filter, context);
16
- return filter;
10
+ return services.filters.removeFilterOption({ filterId, filterOptionValue });
17
11
  }
@@ -3,4 +3,4 @@ import type { Context } from '../../../context.ts';
3
3
  export default function updateFilter(root: never, { filter, filterId }: {
4
4
  filter: Filter;
5
5
  filterId: string;
6
- }, context: Context): Promise<import("mongodb").WithId<Filter> | null>;
6
+ }, context: Context): Promise<Filter | null>;
@@ -1,14 +1,11 @@
1
1
  import { log } from '@unchainedshop/logger';
2
2
  import { FilterNotFoundError, InvalidIdError } from "../../../errors.js";
3
- import { FilterDirector } from '@unchainedshop/core';
4
3
  export default async function updateFilter(root, { filter, filterId }, context) {
5
- const { modules, userId } = context;
4
+ const { modules, services, userId } = context;
6
5
  log(`mutation updateFilter ${filterId}`, { userId });
7
6
  if (!filterId)
8
7
  throw new InvalidIdError({ filterId });
9
8
  if (!(await modules.filters.filterExists({ filterId })))
10
9
  throw new FilterNotFoundError({ filterId });
11
- const updatedFilter = await modules.filters.update(filterId, filter);
12
- await FilterDirector.invalidateProductIdCache(updatedFilter, context);
13
- return updatedFilter;
10
+ return services.filters.updateFilter(filterId, filter);
14
11
  }
@@ -1,3 +1,3 @@
1
1
  import { GraphQLScalarType } from 'graphql';
2
- declare const LowerCaseString: GraphQLScalarType<string | null, any>;
2
+ declare const LowerCaseString: GraphQLScalarType<string, any>;
3
3
  export default LowerCaseString;
@@ -1,4 +1,4 @@
1
1
  declare const _default: {
2
- LowerCaseString: import("graphql").GraphQLScalarType<string | null, any>;
2
+ LowerCaseString: import("graphql").GraphQLScalarType<string, any>;
3
3
  };
4
4
  export default _default;
@@ -623,6 +623,10 @@ declare const types: {
623
623
  isExpired: (obj: import("@unchainedshop/core-quotations").Quotation, { referenceDate }: {
624
624
  referenceDate: Date;
625
625
  }, { modules }: import("../../context.ts").Context) => boolean;
626
+ price: (obj: import("@unchainedshop/core-quotations").Quotation) => {
627
+ amount: number;
628
+ currencyCode: string;
629
+ } | null;
626
630
  product: (obj: import("@unchainedshop/core-quotations").Quotation, _: never, { loaders }: import("../../context.ts").Context) => Promise<import("@unchainedshop/core-products").Product>;
627
631
  status: (obj: import("@unchainedshop/core-quotations").Quotation, _: never, { modules }: import("../../context.ts").Context) => import("@unchainedshop/core-quotations").QuotationStatus;
628
632
  user: (obj: import("@unchainedshop/core-quotations").Quotation, _: never, { loaders }: import("../../context.ts").Context) => Promise<import("@unchainedshop/core-users").User>;
@@ -6,6 +6,10 @@ export declare const Quotation: {
6
6
  isExpired: (obj: QuotationType, { referenceDate }: {
7
7
  referenceDate: Date;
8
8
  }, { modules }: Context) => boolean;
9
+ price: (obj: QuotationType) => {
10
+ amount: number;
11
+ currencyCode: string;
12
+ } | null;
9
13
  product: (obj: QuotationType, _: never, { loaders }: Context) => Promise<import("@unchainedshop/core-products").Product>;
10
14
  status: (obj: QuotationType, _: never, { modules }: Context) => import("@unchainedshop/core-quotations").QuotationStatus;
11
15
  user: (obj: QuotationType, _: never, { loaders }: Context) => Promise<import("@unchainedshop/core-users").User>;
@@ -2,6 +2,7 @@ export const Quotation = {
2
2
  country: async (obj, _, { loaders }) => obj.countryCode ? loaders.countryLoader.load({ isoCode: obj.countryCode }) : null,
3
3
  currency: async (obj, _, { loaders }) => obj.currencyCode ? loaders.currencyLoader.load({ isoCode: obj.currencyCode }) : null,
4
4
  isExpired: (obj, { referenceDate }, { modules }) => modules.quotations.isExpired(obj, { referenceDate }),
5
+ price: (obj) => obj.price != null && obj.currencyCode ? { amount: obj.price, currencyCode: obj.currencyCode } : null,
5
6
  product: async (obj, _, { loaders }) => {
6
7
  const product = await loaders.productLoader.load({
7
8
  productId: obj.productId,
@@ -171,6 +171,7 @@ export const loggedIn = (role, actions) => {
171
171
  role.allow(actions.reviewProduct, () => true);
172
172
  role.allow(actions.updateProductReview, isOwnedProductReview);
173
173
  role.allow(actions.requestQuotation, () => true);
174
+ role.allow(actions.viewQuotation, isOwnedQuotation);
174
175
  role.allow(actions.answerQuotation, isOwnedQuotation);
175
176
  role.allow(actions.manageBookmarks, isOwnedBookmark);
176
177
  role.allow(actions.createBookmark, isOwnBookmarkUser);
@@ -57,6 +57,11 @@ export default [
57
57
  country: Country
58
58
  currency: Currency
59
59
  configuration: [ProductConfigurationParameter!]
60
+
61
+ """
62
+ Proposed unit price (minor units of the quotation's currency), set when the quotation reaches PROPOSED
63
+ """
64
+ price: Price
60
65
  }
61
66
  `,
62
67
  ];
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": "4.8.18",
4
+ "version": "4.8.20",
5
5
  "main": "lib/api-index.js",
6
6
  "types": "lib/api-index.d.ts",
7
7
  "type": "module",
@@ -60,13 +60,14 @@
60
60
  },
61
61
  "homepage": "https://github.com/unchainedshop/unchained#readme",
62
62
  "peerDependencies": {
63
- "@ai-sdk/mcp": "^1",
63
+ "@ai-sdk/mcp": "^2.0.29",
64
+ "graphql": ">= 16.14 < 18",
64
65
  "@fastify/cookie": ">= 11 < 12",
65
66
  "@fastify/multipart": ">= 9 < 11",
66
67
  "@fastify/session": ">= 11 < 12",
67
- "@fastify/static": ">= 9 < 10",
68
+ "@fastify/static": ">= 10.1.3 < 11",
68
69
  "@modelcontextprotocol/sdk": ">= 1 < 2",
69
- "ai": ">= 6 < 7",
70
+ "ai": ">= 7 < 8",
70
71
  "express": ">= 5 < 6",
71
72
  "express-session": ">= 1.18 < 2",
72
73
  "fastify": ">= 5.2 < 6",
@@ -116,6 +117,7 @@
116
117
  }
117
118
  },
118
119
  "dependencies": {
120
+ "@envelop/core": "^5.6.0",
119
121
  "@unchainedshop/core": "^4.8.12",
120
122
  "@unchainedshop/events": "^4.8.12",
121
123
  "@unchainedshop/logger": "^4.8.12",
@@ -123,25 +125,26 @@
123
125
  "@unchainedshop/utils": "^4.8.12",
124
126
  "dataloader": "^2.2.3",
125
127
  "expiry-map": "^2.0.0",
126
- "graphql-scalars": "^1.24.2",
128
+ "graphql-scalars": "^2.0.0",
127
129
  "mime": ">= 4 < 5",
128
130
  "p-memoize": "^8.0.0",
129
131
  "zod": "^3.25.76 || ^4"
130
132
  },
131
133
  "devDependencies": {
132
- "@ai-sdk/mcp": "^1.0.5",
134
+ "@ai-sdk/mcp": "^2.0.34",
133
135
  "@fastify/cookie": "^11.0.2",
134
- "@fastify/multipart": "^10.0.0",
136
+ "@fastify/multipart": "^10.1.1",
135
137
  "@fastify/session": "^11.1.0",
136
- "@fastify/static": "^9.0.0",
138
+ "@fastify/static": "^10.1.3",
137
139
  "@modelcontextprotocol/sdk": "^1.15.1",
138
140
  "@types/express": "^5.0.3",
139
141
  "@types/express-session": "^1.18.2",
140
- "@types/node": "^25.0.0",
141
- "ai": "^6.0.14",
142
+ "@types/node": "^26.2.0",
143
+ "ai": "^7.0.73",
142
144
  "express": "^5.1.0",
143
145
  "express-session": "^1.18.1",
144
- "fastify": "^5.4.0",
146
+ "fastify": "^5.12.1",
147
+ "graphql": "^17.0.2",
145
148
  "multer": "^2.0.1",
146
149
  "passport": "^0.7.0",
147
150
  "typescript": "^5.8.3"