@unchainedshop/api 3.1.10 → 3.1.12
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/express/createMCPMiddleware.d.ts +4 -0
- package/lib/express/createMCPMiddleware.d.ts.map +1 -0
- package/lib/express/createMCPMiddleware.js +68 -0
- package/lib/express/createMCPMiddleware.js.map +1 -0
- package/lib/express/mcpHandler.d.ts.map +1 -0
- package/lib/express/mcpHandler.js +68 -0
- package/lib/express/mcpHandler.js.map +1 -0
- package/lib/fastify/createMCPMiddleware.d.ts +2 -0
- package/lib/fastify/createMCPMiddleware.d.ts.map +1 -0
- package/lib/fastify/createMCPMiddleware.js +2 -0
- package/lib/fastify/createMCPMiddleware.js.map +1 -0
- package/lib/fastify/index copy.d.ts.map +1 -0
- package/lib/fastify/index copy.js +157 -0
- package/lib/fastify/index copy.js.map +1 -0
- package/lib/fastify/mcpHandler.d.ts +8 -0
- package/lib/fastify/mcpHandler.d.ts.map +1 -0
- package/lib/fastify/mcpHandler.js +46 -0
- package/lib/fastify/mcpHandler.js.map +1 -0
- package/lib/loaders/assortmentLinkLoader.d.ts +12 -0
- package/lib/loaders/assortmentLinkLoader.d.ts.map +1 -0
- package/lib/loaders/assortmentLinkLoader.js +23 -0
- package/lib/loaders/assortmentLinkLoader.js.map +1 -0
- package/lib/loaders/assortmentLinksLoader.d.ts +14 -0
- package/lib/loaders/assortmentLinksLoader.d.ts.map +1 -0
- package/lib/loaders/assortmentLinksLoader.js +38 -0
- package/lib/loaders/assortmentLinksLoader.js.map +1 -0
- package/lib/loaders/assortmentLoader.d.ts +10 -0
- package/lib/loaders/assortmentLoader.d.ts.map +1 -0
- package/lib/loaders/assortmentLoader.js +15 -0
- package/lib/loaders/assortmentLoader.js.map +1 -0
- package/lib/loaders/assortmentMediaTextLoader.d.ts +12 -0
- package/lib/loaders/assortmentMediaTextLoader.d.ts.map +1 -0
- package/lib/loaders/assortmentMediaTextLoader.js +15 -0
- package/lib/loaders/assortmentMediaTextLoader.js.map +1 -0
- package/lib/loaders/assortmentMediasLoader.d.ts +10 -0
- package/lib/loaders/assortmentMediasLoader.d.ts.map +1 -0
- package/lib/loaders/assortmentMediasLoader.js +18 -0
- package/lib/loaders/assortmentMediasLoader.js.map +1 -0
- package/lib/loaders/assortmentProductLoader.d.ts +12 -0
- package/lib/loaders/assortmentProductLoader.d.ts.map +1 -0
- package/lib/loaders/assortmentProductLoader.js +14 -0
- package/lib/loaders/assortmentProductLoader.js.map +1 -0
- package/lib/loaders/assortmentProductsLoader.d.ts +10 -0
- package/lib/loaders/assortmentProductsLoader.d.ts.map +1 -0
- package/lib/loaders/assortmentProductsLoader.js +18 -0
- package/lib/loaders/assortmentProductsLoader.js.map +1 -0
- package/lib/loaders/assortmentTextLoader.d.ts +12 -0
- package/lib/loaders/assortmentTextLoader.d.ts.map +1 -0
- package/lib/loaders/assortmentTextLoader.js +15 -0
- package/lib/loaders/assortmentTextLoader.js.map +1 -0
- package/lib/loaders/buildLocaleMap.d.ts +6 -0
- package/lib/loaders/buildLocaleMap.d.ts.map +1 -0
- package/lib/loaders/buildLocaleMap.js +38 -0
- package/lib/loaders/buildLocaleMap.js.map +1 -0
- package/lib/loaders/buildTextMap.d.ts +1 -1
- package/lib/loaders/buildTextMap.d.ts.map +1 -1
- package/lib/loaders/buildTextMap.js +16 -6
- package/lib/loaders/buildTextMap.js.map +1 -1
- package/lib/loaders/countryLoader.d.ts +10 -0
- package/lib/loaders/countryLoader.d.ts.map +1 -0
- package/lib/loaders/countryLoader.js +14 -0
- package/lib/loaders/countryLoader.js.map +1 -0
- package/lib/loaders/currencyLoader.d.ts +10 -0
- package/lib/loaders/currencyLoader.d.ts.map +1 -0
- package/lib/loaders/currencyLoader.js +14 -0
- package/lib/loaders/currencyLoader.js.map +1 -0
- package/lib/loaders/deliveryProviderLoader.d.ts +10 -0
- package/lib/loaders/deliveryProviderLoader.d.ts.map +1 -0
- package/lib/loaders/deliveryProviderLoader.js +13 -0
- package/lib/loaders/deliveryProviderLoader.js.map +1 -0
- package/lib/loaders/fileLoader.d.ts +10 -0
- package/lib/loaders/fileLoader.d.ts.map +1 -0
- package/lib/loaders/fileLoader.js +13 -0
- package/lib/loaders/fileLoader.js.map +1 -0
- package/lib/loaders/filterLoader.d.ts +10 -0
- package/lib/loaders/filterLoader.d.ts.map +1 -0
- package/lib/loaders/filterLoader.js +14 -0
- package/lib/loaders/filterLoader.js.map +1 -0
- package/lib/loaders/filterTextLoader.d.ts +14 -0
- package/lib/loaders/filterTextLoader.d.ts.map +1 -0
- package/lib/loaders/filterTextLoader.js +15 -0
- package/lib/loaders/filterTextLoader.js.map +1 -0
- package/lib/loaders/index.d.ts.map +1 -1
- package/lib/loaders/index.js +1 -29
- package/lib/loaders/index.js.map +1 -1
- package/lib/loaders/languageLoader.d.ts +10 -0
- package/lib/loaders/languageLoader.d.ts.map +1 -0
- package/lib/loaders/languageLoader.js +14 -0
- package/lib/loaders/languageLoader.js.map +1 -0
- package/lib/loaders/orderLoader.d.ts +10 -0
- package/lib/loaders/orderLoader.d.ts.map +1 -0
- package/lib/loaders/orderLoader.js +16 -0
- package/lib/loaders/orderLoader.js.map +1 -0
- package/lib/loaders/paymentProviderLoader.d.ts +10 -0
- package/lib/loaders/paymentProviderLoader.d.ts.map +1 -0
- package/lib/loaders/paymentProviderLoader.js +13 -0
- package/lib/loaders/paymentProviderLoader.js.map +1 -0
- package/lib/loaders/productLoader.d.ts +10 -0
- package/lib/loaders/productLoader.d.ts.map +1 -0
- package/lib/loaders/productLoader.js +16 -0
- package/lib/loaders/productLoader.js.map +1 -0
- package/lib/loaders/productLoaderBySKU.d.ts +10 -0
- package/lib/loaders/productLoaderBySKU.d.ts.map +1 -0
- package/lib/loaders/productLoaderBySKU.js +16 -0
- package/lib/loaders/productLoaderBySKU.js.map +1 -0
- package/lib/loaders/productMediaTextLoader.d.ts +12 -0
- package/lib/loaders/productMediaTextLoader.d.ts.map +1 -0
- package/lib/loaders/productMediaTextLoader.js +15 -0
- package/lib/loaders/productMediaTextLoader.js.map +1 -0
- package/lib/loaders/productMediasLoader.d.ts +10 -0
- package/lib/loaders/productMediasLoader.d.ts.map +1 -0
- package/lib/loaders/productMediasLoader.js +18 -0
- package/lib/loaders/productMediasLoader.js.map +1 -0
- package/lib/loaders/productTextLoader.d.ts +12 -0
- package/lib/loaders/productTextLoader.d.ts.map +1 -0
- package/lib/loaders/productTextLoader.js +15 -0
- package/lib/loaders/productTextLoader.js.map +1 -0
- package/lib/loaders/userLoader.d.ts +10 -0
- package/lib/loaders/userLoader.d.ts.map +1 -0
- package/lib/loaders/userLoader.js +17 -0
- package/lib/loaders/userLoader.js.map +1 -0
- package/lib/loaders/utils.d.ts +7 -0
- package/lib/loaders/utils.d.ts.map +1 -0
- package/lib/loaders/utils.js +29 -0
- package/lib/loaders/utils.js.map +1 -0
- package/lib/mcp/index.d.ts +4 -0
- package/lib/mcp/index.d.ts.map +1 -0
- package/lib/mcp/index.js +12 -0
- package/lib/mcp/index.js.map +1 -0
- package/lib/mcp/server.d.ts +4 -0
- package/lib/mcp/server.d.ts.map +1 -0
- package/lib/mcp/server.js +60 -0
- package/lib/mcp/server.js.map +1 -0
- package/lib/mcp/tools/getProduct.d.ts +51 -0
- package/lib/mcp/tools/getProduct.d.ts.map +1 -0
- package/lib/mcp/tools/getProduct.js +428 -0
- package/lib/mcp/tools/getProduct.js.map +1 -0
- package/lib/mcp/tools/index.d.ts +6 -0
- package/lib/mcp/tools/index.d.ts.map +1 -0
- package/lib/mcp/tools/index.js +6 -0
- package/lib/mcp/tools/index.js.map +1 -0
- package/lib/mcp/tools/listProducts.d.ts +112 -0
- package/lib/mcp/tools/listProducts.d.ts.map +1 -0
- package/lib/mcp/tools/listProducts.js +111 -0
- package/lib/mcp/tools/listProducts.js.map +1 -0
- package/lib/mcp/types.d.ts +27 -0
- package/lib/mcp/types.d.ts.map +1 -0
- package/lib/mcp/types.js +5 -0
- package/lib/mcp/types.js.map +1 -0
- package/lib/mcp/utils/html.d.ts +16 -0
- package/lib/mcp/utils/html.d.ts.map +1 -0
- package/lib/mcp/utils/html.js +102 -0
- package/lib/mcp/utils/html.js.map +1 -0
- package/lib/middleware/accountLockout.d.ts.map +1 -0
- package/lib/middleware/accountLockout.js +84 -0
- package/lib/middleware/accountLockout.js.map +1 -0
- package/lib/middleware/rateLimiter.d.ts.map +1 -0
- package/lib/middleware/rateLimiter.js +91 -0
- package/lib/middleware/rateLimiter.js.map +1 -0
- package/lib/plugins/inputValidation.d.ts.map +1 -0
- package/lib/plugins/inputValidation.js +95 -0
- package/lib/plugins/inputValidation.js.map +1 -0
- package/lib/schema/mutation.js +1 -1
- package/lib/utils/inputValidation.d.ts.map +1 -0
- package/lib/utils/inputValidation.js +51 -0
- package/lib/utils/inputValidation.js.map +1 -0
- package/package.json +1 -1
- package/src/loaders/buildLocaleMap.test.ts +72 -0
- package/src/loaders/buildLocaleMap.ts +39 -0
- package/src/loaders/buildTextMap.test.ts +55 -0
- package/src/loaders/buildTextMap.ts +22 -6
- package/src/loaders/index.ts +1 -31
- package/src/schema/mutation.ts +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GraphQLError } from 'graphql';
|
|
2
|
+
// Dangerous patterns to check for
|
|
3
|
+
const DANGEROUS_PATTERNS = [
|
|
4
|
+
/<script/i, // Script tags
|
|
5
|
+
/<iframe/i, // Iframes
|
|
6
|
+
/<object/i, // Object embeds
|
|
7
|
+
/<embed/i, // Embed tags
|
|
8
|
+
/<link/i, // Link tags (can load CSS)
|
|
9
|
+
/javascript:/i, // JavaScript protocol
|
|
10
|
+
/vbscript:/i, // VBScript protocol
|
|
11
|
+
/on\w+\s*=/i, // Event handlers (onclick, onload, etc)
|
|
12
|
+
/<meta/i, // Meta tags
|
|
13
|
+
/data:text\/html/i, // Data URLs with HTML
|
|
14
|
+
/<svg.*onload/i, // SVG with event handlers
|
|
15
|
+
/&#x?[0-9a-f]+;/i, // HTML entities (could be obfuscation)
|
|
16
|
+
/\\u[0-9a-f]{4}/i, // Unicode escapes
|
|
17
|
+
/expression\s*\(/i, // CSS expressions
|
|
18
|
+
/@import/i, // CSS imports
|
|
19
|
+
/<!--|-->/, // HTML comments
|
|
20
|
+
];
|
|
21
|
+
// Check for dangerous patterns in any string
|
|
22
|
+
export function checkForDangerousContent(value) {
|
|
23
|
+
if (!value)
|
|
24
|
+
return;
|
|
25
|
+
for (const dangerous of DANGEROUS_PATTERNS) {
|
|
26
|
+
if (dangerous.test(value)) {
|
|
27
|
+
throw new GraphQLError('Input contains potentially dangerous content', {
|
|
28
|
+
extensions: { code: 'SECURITY_VALIDATION_ERROR' },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Validate an entire object recursively
|
|
34
|
+
export function validateObject(obj, path = '') {
|
|
35
|
+
if (typeof obj === 'string') {
|
|
36
|
+
checkForDangerousContent(obj);
|
|
37
|
+
return obj;
|
|
38
|
+
}
|
|
39
|
+
if (Array.isArray(obj)) {
|
|
40
|
+
return obj.map((item, index) => validateObject(item, `${path}[${index}]`));
|
|
41
|
+
}
|
|
42
|
+
if (obj && typeof obj === 'object') {
|
|
43
|
+
const validated = {};
|
|
44
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
45
|
+
validated[key] = validateObject(value, path ? `${path}.${key}` : key);
|
|
46
|
+
}
|
|
47
|
+
return validated;
|
|
48
|
+
}
|
|
49
|
+
return obj;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=inputValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inputValidation.js","sourceRoot":"","sources":["../../src/utils/inputValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,kCAAkC;AAClC,MAAM,kBAAkB,GAAG;IACzB,UAAU,EAAE,cAAc;IAC1B,UAAU,EAAE,UAAU;IACtB,UAAU,EAAE,gBAAgB;IAC5B,SAAS,EAAE,aAAa;IACxB,QAAQ,EAAE,2BAA2B;IACrC,cAAc,EAAE,sBAAsB;IACtC,YAAY,EAAE,oBAAoB;IAClC,YAAY,EAAE,wCAAwC;IACtD,QAAQ,EAAE,YAAY;IACtB,kBAAkB,EAAE,sBAAsB;IAC1C,eAAe,EAAE,0BAA0B;IAC3C,iBAAiB,EAAE,uCAAuC;IAC1D,iBAAiB,EAAE,kBAAkB;IACrC,kBAAkB,EAAE,kBAAkB;IACtC,UAAU,EAAE,cAAc;IAC1B,UAAU,EAAE,gBAAgB;CAC7B,CAAC;AAEF,6CAA6C;AAC7C,MAAM,UAAU,wBAAwB,CAAC,KAAa;IACpD,IAAI,CAAC,KAAK;QAAE,OAAO;IAEnB,KAAK,MAAM,SAAS,IAAI,kBAAkB,EAAE,CAAC;QAC3C,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,YAAY,CAAC,8CAA8C,EAAE;gBACrE,UAAU,EAAE,EAAE,IAAI,EAAE,2BAA2B,EAAE;aAClD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,cAAc,CAAC,GAAQ,EAAE,IAAI,GAAG,EAAE;IAChD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,wBAAwB,CAAC,GAAG,CAAC,CAAC;QAC9B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,EAAE,CAAC;QACrB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,SAAS,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import buildLocaleMap from './buildLocaleMap.js';
|
|
4
|
+
|
|
5
|
+
const testTexts = [
|
|
6
|
+
// 1 has translations in French and German
|
|
7
|
+
{ locale: 'fr', _id: '1', entityId: '1' },
|
|
8
|
+
{ locale: 'de', _id: '2', entityId: '1' },
|
|
9
|
+
|
|
10
|
+
// 2 only has Ialien translations
|
|
11
|
+
{ locale: 'it', _id: '3', entityId: '2' },
|
|
12
|
+
|
|
13
|
+
// 3 only has English translations
|
|
14
|
+
{ locale: 'en', _id: '4', entityId: '3' },
|
|
15
|
+
|
|
16
|
+
// 4 only has German translations
|
|
17
|
+
{ locale: 'de', _id: '5', entityId: '4' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
describe('buildLocaleMap', () => {
|
|
21
|
+
it('Mixed usage of default locale and forced languages', () => {
|
|
22
|
+
const queries = [
|
|
23
|
+
{ locale: 'de-CH' },
|
|
24
|
+
{ locale: 'fr' },
|
|
25
|
+
{ locale: 'it' },
|
|
26
|
+
];
|
|
27
|
+
const expected = {
|
|
28
|
+
'de': ['de-CH', 'fr', 'it'],
|
|
29
|
+
'fr': ['fr'],
|
|
30
|
+
'it': ['it'],
|
|
31
|
+
};
|
|
32
|
+
const result = buildLocaleMap(queries, testTexts);
|
|
33
|
+
assert.deepStrictEqual(result, expected);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('Mixed usage of forced languages', () => {
|
|
37
|
+
const queries = [
|
|
38
|
+
{ locale: 'fr' },
|
|
39
|
+
{ locale: 'it' },
|
|
40
|
+
];
|
|
41
|
+
const expected = {
|
|
42
|
+
'de': ['fr', 'it'],
|
|
43
|
+
'fr': ['fr'],
|
|
44
|
+
'it': ['it'],
|
|
45
|
+
};
|
|
46
|
+
const result = buildLocaleMap(queries, testTexts);
|
|
47
|
+
assert.deepStrictEqual(result, expected);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('Only default locale', () => {
|
|
51
|
+
const queries = [
|
|
52
|
+
{ locale: 'de_CH' },
|
|
53
|
+
];
|
|
54
|
+
const expected = {
|
|
55
|
+
'de': ['de_CH'],
|
|
56
|
+
};
|
|
57
|
+
const result = buildLocaleMap(queries, testTexts);
|
|
58
|
+
assert.deepStrictEqual(result, expected);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('Only one forced language locale', () => {
|
|
62
|
+
const queries = [
|
|
63
|
+
{ locale: 'fr' },
|
|
64
|
+
];
|
|
65
|
+
const expected = {
|
|
66
|
+
'fr': ['fr'],
|
|
67
|
+
'de': ['fr'],
|
|
68
|
+
};
|
|
69
|
+
const result = buildLocaleMap(queries, testTexts);
|
|
70
|
+
assert.deepStrictEqual(result, expected);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { systemLocale } from '@unchainedshop/utils';
|
|
2
|
+
|
|
3
|
+
function getLocaleStrings(locale: string): string[] {
|
|
4
|
+
try {
|
|
5
|
+
const localeObj = new Intl.Locale(locale);
|
|
6
|
+
// Optimized Set creation and spread
|
|
7
|
+
const locales = new Set<string>();
|
|
8
|
+
locales.add(localeObj.baseName);
|
|
9
|
+
locales.add(localeObj.language);
|
|
10
|
+
locales.add(systemLocale.baseName);
|
|
11
|
+
locales.add(systemLocale.language);
|
|
12
|
+
return Array.from(locales);
|
|
13
|
+
} catch {
|
|
14
|
+
// Optimized Set creation and spread
|
|
15
|
+
const locales = new Set<string>();
|
|
16
|
+
locales.add(systemLocale.baseName);
|
|
17
|
+
locales.add(systemLocale.language);
|
|
18
|
+
return Array.from(locales);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export default function buildLocaleMap(
|
|
22
|
+
queries: Readonly<Array<{ locale: string }>>,
|
|
23
|
+
texts: Readonly<Array<{ locale?: string }>>,
|
|
24
|
+
): Record<string, string[]> {
|
|
25
|
+
// key = texts.locale
|
|
26
|
+
// value = input query locale
|
|
27
|
+
const queryLocales = new Set(queries.map((q) => q.locale));
|
|
28
|
+
const textLocales = new Set(texts.map((t) => t.locale));
|
|
29
|
+
const localeMap = {};
|
|
30
|
+
for (const queryLocale of queryLocales) {
|
|
31
|
+
const potentialMatches = getLocaleStrings(queryLocale);
|
|
32
|
+
const matches = potentialMatches.filter((l) => textLocales.has(l));
|
|
33
|
+
for (const match of matches) {
|
|
34
|
+
if (!localeMap[match]) localeMap[match] = [];
|
|
35
|
+
localeMap[match].push(queryLocale);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return localeMap;
|
|
39
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import buildTextMap from './buildTextMap.js';
|
|
4
|
+
|
|
5
|
+
const testTexts = [
|
|
6
|
+
// 1 has translations in French and German
|
|
7
|
+
{ locale: 'fr', _id: 'french-1', entityId: '1' },
|
|
8
|
+
{ locale: 'de', _id: 'german-1', entityId: '1' },
|
|
9
|
+
|
|
10
|
+
// 2 only has Ialien translations
|
|
11
|
+
{ locale: 'it', _id: 'italian-2', entityId: '2' },
|
|
12
|
+
|
|
13
|
+
// 3 only has English translations
|
|
14
|
+
{ locale: 'en', _id: 'english-3', entityId: '3' },
|
|
15
|
+
|
|
16
|
+
// 4 only has German translations
|
|
17
|
+
{ locale: 'de', _id: 'german-4', entityId: '4' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
describe('buildTextMap', () => {
|
|
21
|
+
it('Generate a correct text map for all fallbacks', () => {
|
|
22
|
+
const localeMap = {
|
|
23
|
+
'de': ['de-CH', 'fr', 'it'],
|
|
24
|
+
'fr': ['fr'],
|
|
25
|
+
'it': ['it'],
|
|
26
|
+
};
|
|
27
|
+
const expected = {
|
|
28
|
+
'de-CH1': {
|
|
29
|
+
_id: 'german-1',
|
|
30
|
+
},
|
|
31
|
+
'fr1': {
|
|
32
|
+
_id: 'french-1',
|
|
33
|
+
},
|
|
34
|
+
'it1': {
|
|
35
|
+
_id: 'german-1',
|
|
36
|
+
},
|
|
37
|
+
'de-CH4': {
|
|
38
|
+
_id: 'german-4',
|
|
39
|
+
},
|
|
40
|
+
'fr4': {
|
|
41
|
+
_id: 'german-4',
|
|
42
|
+
},
|
|
43
|
+
'it4': {
|
|
44
|
+
_id: 'german-4',
|
|
45
|
+
},
|
|
46
|
+
'it2': {
|
|
47
|
+
_id: 'italian-2',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const result = buildTextMap(localeMap, testTexts, (text) => text.entityId);
|
|
51
|
+
|
|
52
|
+
assert.partialDeepStrictEqual(result, expected);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
});
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
type ArrayElement<T> = T extends (infer U)[] ? U : never;
|
|
2
2
|
|
|
3
|
+
/* This file has been optimized for performance to the last drop. Don't refactor! */
|
|
4
|
+
|
|
3
5
|
export default function buildTextMap<T extends Array<{ locale?: string }>>(
|
|
4
6
|
localeMap: Record<string, string[]>,
|
|
5
7
|
texts: Readonly<T>,
|
|
6
8
|
buildId: (item: ArrayElement<T>) => string,
|
|
7
9
|
) {
|
|
8
|
-
const textsMap = {};
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const textsMap: Record<string, ArrayElement<T>> = {}; // Added type for textsMap
|
|
11
|
+
const localeMapKeys = Object.keys(localeMap);
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < localeMapKeys.length; i++) {
|
|
14
|
+
const originLocale = localeMapKeys[i];
|
|
15
|
+
const localesForText = localeMap[originLocale];
|
|
16
|
+
|
|
17
|
+
for (let j = 0; j < texts.length; j++) {
|
|
18
|
+
const text = texts[j];
|
|
11
19
|
if (originLocale !== text.locale) continue;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
|
|
21
|
+
const idPart = buildId(text as ArrayElement<T>); // Ensured correct type for buildId argument
|
|
22
|
+
|
|
23
|
+
for (let k = 0; k < localesForText.length; k++) {
|
|
24
|
+
const locale = localesForText[k];
|
|
25
|
+
const key = locale + idPart;
|
|
26
|
+
if (textsMap[key] && locale !== text.locale) {
|
|
27
|
+
// If the key already exists and the locale is a fallback, skip this text
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
textsMap[key] = text as ArrayElement<T>; // Ensured correct type for assignment
|
|
15
31
|
}
|
|
16
32
|
}
|
|
17
33
|
}
|
package/src/loaders/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import DataLoader from 'dataloader';
|
|
2
|
-
import { systemLocale } from '@unchainedshop/utils';
|
|
3
2
|
import { UnchainedCore } from '@unchainedshop/core';
|
|
4
3
|
import { Product, ProductText, ProductMediaText, ProductMedia } from '@unchainedshop/core-products';
|
|
5
4
|
import { Filter, FilterText } from '@unchainedshop/core-filters';
|
|
@@ -13,37 +12,8 @@ import {
|
|
|
13
12
|
} from '@unchainedshop/core-assortments';
|
|
14
13
|
import { File } from '@unchainedshop/core-files';
|
|
15
14
|
import buildTextMap from './buildTextMap.js';
|
|
15
|
+
import buildLocaleMap from './buildLocaleMap.js';
|
|
16
16
|
|
|
17
|
-
function getLocaleStrings(locale: string) {
|
|
18
|
-
try {
|
|
19
|
-
const localeObj = new Intl.Locale(locale);
|
|
20
|
-
return [
|
|
21
|
-
...new Set([localeObj.baseName, localeObj.language, systemLocale.baseName, systemLocale.language]),
|
|
22
|
-
];
|
|
23
|
-
} catch {
|
|
24
|
-
return [...new Set([systemLocale.baseName, systemLocale.language])];
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function buildLocaleMap(
|
|
29
|
-
queries: Readonly<Array<{ locale: string }>>,
|
|
30
|
-
texts: Readonly<Array<{ locale?: string }>>,
|
|
31
|
-
): Record<string, string[]> {
|
|
32
|
-
// key = texts.locale
|
|
33
|
-
// value = input query locale
|
|
34
|
-
const queryLocales: Array<any> = [...new Set(queries.map((q) => q.locale))];
|
|
35
|
-
const textLocales: Array<string> = [...new Set(texts.map((t) => t.locale))];
|
|
36
|
-
const localeMap = {};
|
|
37
|
-
for (const queryLocale of queryLocales) {
|
|
38
|
-
const potentialMatches = getLocaleStrings(queryLocale);
|
|
39
|
-
const matches = potentialMatches.filter((l) => textLocales.includes(l));
|
|
40
|
-
for (const match of matches) {
|
|
41
|
-
if (!localeMap[match]) localeMap[match] = [];
|
|
42
|
-
localeMap[match].push(queryLocale);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return localeMap;
|
|
46
|
-
}
|
|
47
17
|
|
|
48
18
|
const loaders = async (unchainedAPI: UnchainedCore) => {
|
|
49
19
|
return {
|
package/src/schema/mutation.ts
CHANGED