@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.
- package/lib/adminUiPlugins.d.ts +73 -0
- package/lib/adminUiPlugins.js +164 -0
- package/lib/chat/generateImageHandler.d.ts +273 -7
- package/lib/createGraphQLServer.js +6 -0
- package/lib/express/createMCPMiddleware.js +6 -3
- package/lib/fastify/mcpHandler.js +5 -2
- package/lib/mcp/tools/filter/handlers/createFilter.js +2 -4
- package/lib/mcp/tools/filter/handlers/createFilterOption.js +2 -4
- package/lib/mcp/tools/filter/handlers/removeFilterOption.js +2 -4
- package/lib/mcp/tools/filter/handlers/updateFilter.js +2 -2
- package/lib/mcp/tools/quotation/index.js +1 -1
- package/lib/mcp/utils/sharedSchemas.d.ts +1 -0
- package/lib/mcp/utils/sharedSchemas.js +1 -0
- package/lib/resolvers/mutations/bookmarks/createBookmark.d.ts +1 -1
- package/lib/resolvers/mutations/bookmarks/createBookmark.js +4 -3
- package/lib/resolvers/mutations/filters/createFilter.js +3 -4
- package/lib/resolvers/mutations/filters/createFilterOption.d.ts +1 -1
- package/lib/resolvers/mutations/filters/createFilterOption.js +3 -4
- package/lib/resolvers/mutations/filters/removeFilterOption.d.ts +1 -1
- package/lib/resolvers/mutations/filters/removeFilterOption.js +2 -8
- package/lib/resolvers/mutations/filters/updateFilter.d.ts +1 -1
- package/lib/resolvers/mutations/filters/updateFilter.js +2 -5
- package/lib/resolvers/scalars/LowerCaseString.d.ts +1 -1
- package/lib/resolvers/scalars/index.d.ts +1 -1
- package/lib/resolvers/type/index.d.ts +4 -0
- package/lib/resolvers/type/quotation-types.d.ts +4 -0
- package/lib/resolvers/type/quotation-types.js +1 -0
- package/lib/roles/loggedIn.js +1 -0
- package/lib/schema/types/quotation.js +5 -0
- 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
|
-
}) =>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 =
|
|
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
|
-
}),
|
|
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
|
-
}),
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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),
|
|
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
|
};
|
|
@@ -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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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("
|
|
7
|
+
}, context: Context): Promise<import("@unchainedshop/core-filters").Filter | null>;
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { log } from '@unchainedshop/logger';
|
|
2
|
-
import {
|
|
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
|
|
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("
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
12
|
-
await FilterDirector.invalidateProductIdCache(updatedFilter, context);
|
|
13
|
-
return updatedFilter;
|
|
10
|
+
return services.filters.updateFilter(filterId, filter);
|
|
14
11
|
}
|
|
@@ -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,
|
package/lib/roles/loggedIn.js
CHANGED
|
@@ -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.
|
|
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": "^
|
|
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": ">=
|
|
68
|
+
"@fastify/static": ">= 10.1.3 < 11",
|
|
68
69
|
"@modelcontextprotocol/sdk": ">= 1 < 2",
|
|
69
|
-
"ai": ">=
|
|
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": "^
|
|
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": "^
|
|
134
|
+
"@ai-sdk/mcp": "^2.0.34",
|
|
133
135
|
"@fastify/cookie": "^11.0.2",
|
|
134
|
-
"@fastify/multipart": "^10.
|
|
136
|
+
"@fastify/multipart": "^10.1.1",
|
|
135
137
|
"@fastify/session": "^11.1.0",
|
|
136
|
-
"@fastify/static": "^
|
|
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": "^
|
|
141
|
-
"ai": "^
|
|
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.
|
|
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"
|