@unchainedshop/api 4.8.19 → 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 +4 -1
- package/lib/adminUiPlugins.js +110 -57
- package/lib/createGraphQLServer.js +6 -0
- 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/utils/sharedSchemas.d.ts +1 -0
- package/lib/mcp/utils/sharedSchemas.js +1 -0
- 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/package.json +9 -6
package/lib/adminUiPlugins.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export interface AdminUIPluginEntityConfig {
|
|
|
3
3
|
label: string;
|
|
4
4
|
icon?: string;
|
|
5
5
|
requiredRole?: string;
|
|
6
|
+
sortOrder?: number;
|
|
6
7
|
components: {
|
|
7
8
|
list: string;
|
|
8
9
|
detail: string;
|
|
@@ -14,6 +15,7 @@ export interface AdminUIPluginPageConfig {
|
|
|
14
15
|
label: string;
|
|
15
16
|
icon?: string;
|
|
16
17
|
requiredRole?: string;
|
|
18
|
+
sortOrder?: number;
|
|
17
19
|
component: string;
|
|
18
20
|
}
|
|
19
21
|
export interface AdminUIPluginTabConfig {
|
|
@@ -36,6 +38,7 @@ export interface AdminUIPluginConfig {
|
|
|
36
38
|
label: string;
|
|
37
39
|
icon?: string;
|
|
38
40
|
requiredRole?: string;
|
|
41
|
+
sortOrder?: number;
|
|
39
42
|
};
|
|
40
43
|
slots: {
|
|
41
44
|
entities?: AdminUIPluginEntityConfig[];
|
|
@@ -58,9 +61,9 @@ interface StaticAsset {
|
|
|
58
61
|
export interface PreparedPluginAssets {
|
|
59
62
|
routes: Map<string, StaticAsset>;
|
|
60
63
|
validPlugins: AdminUIPluginConfig[];
|
|
61
|
-
importMapTag: string | null;
|
|
62
64
|
}
|
|
63
65
|
export declare const resolveAdminUIPath: () => string | null;
|
|
66
|
+
export declare const parseBundleExports: (bundle: string) => Set<string> | null;
|
|
64
67
|
export declare function preparePluginAssets(plugins: AdminUIPluginConfig[], log: {
|
|
65
68
|
info: (...args: any[]) => void;
|
|
66
69
|
warn: (...args: any[]) => void;
|
package/lib/adminUiPlugins.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
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
5
|
const IMMUTABLE_CACHE = 'public, max-age=31536000, immutable';
|
|
6
|
-
const SDK_FILES = ['ui.mjs', 'form.mjs', 'hooks.mjs', 'providers.mjs', 'modal.mjs'];
|
|
7
|
-
const IMPORT_MAP = {
|
|
8
|
-
imports: {
|
|
9
|
-
'@unchainedshop/admin-ui/ui': '/admin-ui-sdk/ui.mjs',
|
|
10
|
-
'@unchainedshop/admin-ui/form': '/admin-ui-sdk/form.mjs',
|
|
11
|
-
'@unchainedshop/admin-ui/hooks': '/admin-ui-sdk/hooks.mjs',
|
|
12
|
-
'@unchainedshop/admin-ui/modal': '/admin-ui-sdk/modal.mjs',
|
|
13
|
-
'@unchainedshop/admin-ui/providers': '/admin-ui-sdk/providers.mjs',
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
6
|
export const resolveAdminUIPath = () => {
|
|
17
7
|
try {
|
|
18
8
|
const staticURL = import.meta.resolve('@unchainedshop/admin-ui');
|
|
@@ -22,9 +12,46 @@ export const resolveAdminUIPath = () => {
|
|
|
22
12
|
return null;
|
|
23
13
|
}
|
|
24
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
|
+
};
|
|
25
51
|
export function preparePluginAssets(plugins, log, options = {}) {
|
|
26
52
|
const { devMode = false } = options;
|
|
27
53
|
const routes = new Map();
|
|
54
|
+
const devCacheControl = 'no-cache, no-store, must-revalidate';
|
|
28
55
|
const validPlugins = plugins.filter((p) => {
|
|
29
56
|
if (!PLUGIN_NAME_RE.test(p.name)) {
|
|
30
57
|
log.warn(`Skipping admin-ui plugin with invalid name: "${p.name}"`);
|
|
@@ -32,42 +59,91 @@ export function preparePluginAssets(plugins, log, options = {}) {
|
|
|
32
59
|
}
|
|
33
60
|
return true;
|
|
34
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
|
+
}
|
|
35
69
|
if (validPlugins.length > 0) {
|
|
36
70
|
const pluginList = validPlugins
|
|
37
71
|
.map((p) => `${p.name}${p.version ? `@${p.version}` : ''}`)
|
|
38
72
|
.join(', ');
|
|
39
73
|
log.info(`Loading ${validPlugins.length} admin-ui plugin(s): ${pluginList}`);
|
|
40
74
|
}
|
|
41
|
-
const
|
|
75
|
+
const resolvedBundlePaths = new Map();
|
|
42
76
|
const pluginBundles = new Map();
|
|
43
77
|
for (const plugin of validPlugins) {
|
|
44
78
|
try {
|
|
45
|
-
const
|
|
79
|
+
const bundlePath = resolve(plugin.bundlePath);
|
|
80
|
+
const content = readFileSync(bundlePath, 'utf-8');
|
|
81
|
+
resolvedBundlePaths.set(plugin.name, bundlePath);
|
|
46
82
|
pluginBundles.set(plugin.name, { content, hash: contentHash(content) });
|
|
47
83
|
}
|
|
48
84
|
catch (err) {
|
|
49
85
|
log.warn(`Failed to read bundle for plugin "${plugin.name}" at ${plugin.bundlePath}: ${err.message}`);
|
|
50
86
|
}
|
|
51
87
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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);
|
|
71
147
|
if (devMode) {
|
|
72
148
|
routes.set(`/admin-plugins/${plugin.name}.js`, {
|
|
73
149
|
content: () => readFileSync(bundlePath, 'utf-8'),
|
|
@@ -84,28 +160,5 @@ export function preparePluginAssets(plugins, log, options = {}) {
|
|
|
84
160
|
});
|
|
85
161
|
}
|
|
86
162
|
}
|
|
87
|
-
|
|
88
|
-
if (pluginBundles.size > 0) {
|
|
89
|
-
const adminUIPath = resolveAdminUIPath();
|
|
90
|
-
if (adminUIPath) {
|
|
91
|
-
for (const file of SDK_FILES) {
|
|
92
|
-
const sdkPath = join(adminUIPath, '..', 'dist', file);
|
|
93
|
-
if (existsSync(sdkPath)) {
|
|
94
|
-
routes.set(`/admin-ui-sdk/${file}`, {
|
|
95
|
-
content: readFileSync(sdkPath, 'utf-8'),
|
|
96
|
-
contentType: 'application/javascript',
|
|
97
|
-
cacheControl: IMMUTABLE_CACHE,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const importMapJSON = JSON.stringify(IMPORT_MAP);
|
|
102
|
-
routes.set('/admin-ui-importmap.json', {
|
|
103
|
-
content: importMapJSON,
|
|
104
|
-
contentType: 'application/json',
|
|
105
|
-
cacheControl: IMMUTABLE_CACHE,
|
|
106
|
-
});
|
|
107
|
-
importMapTag = `<script type="importmap">${importMapJSON}</script>`;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return { routes, validPlugins, importMapTag };
|
|
163
|
+
return { routes, validPlugins };
|
|
111
164
|
}
|
|
@@ -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
|
};
|
|
@@ -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,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
|
}
|
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",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
62
62
|
"peerDependencies": {
|
|
63
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",
|
|
@@ -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": "^2.0.
|
|
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
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
142
|
"@types/node": "^26.2.0",
|
|
141
|
-
"ai": "^7.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"
|