aws-service-stack 0.18.449 → 0.18.451

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.
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearMappingCache = exports.resolveSortFields = exports.applyKeywordSortFields = void 0;
4
+ const KEYWORD_SUFFIX = ".keyword";
5
+ /** One in-flight/resolved mapping lookup per index, for the lifetime of the container. */
6
+ const mappingCache = new Map();
7
+ /** Walk a dotted path such as `location.area` through nested `properties` blocks. */
8
+ const findMappingNode = (properties, path) => {
9
+ const segments = path.split(".");
10
+ let node = properties[segments[0]];
11
+ for (const segment of segments.slice(1)) {
12
+ if (!node?.properties)
13
+ return undefined;
14
+ node = node.properties[segment];
15
+ }
16
+ return node;
17
+ };
18
+ /** The name of a keyword sub-field, if this node is a text field that has one. */
19
+ const keywordSubField = (node) => {
20
+ if (node?.type !== "text" || !node.fields)
21
+ return undefined;
22
+ return Object.entries(node.fields).find(([, subField]) => subField?.type === "keyword")?.[0];
23
+ };
24
+ /**
25
+ * Rewrite sort entries that target an analysed `text` field onto its keyword sub-field.
26
+ *
27
+ * OpenSearch cannot sort on a `text` field (fielddata is disabled by default), so sorting on one
28
+ * fails the whole search. The correct target is decided from the index mapping rather than guessed:
29
+ * `keyword`, numeric and date fields are left alone, and an unknown field is left alone too, so a
30
+ * genuine mapping mistake still surfaces as a real error instead of being silently swallowed.
31
+ */
32
+ const applyKeywordSortFields = (sort, properties) => {
33
+ if (!Array.isArray(sort) || sort.length === 0)
34
+ return [];
35
+ if (!properties)
36
+ return sort;
37
+ return sort.map((entry) => {
38
+ if (!entry || typeof entry !== "object")
39
+ return entry;
40
+ const [field, order] = Object.entries(entry)[0] ?? [];
41
+ if (typeof field !== "string" || field.endsWith(KEYWORD_SUFFIX))
42
+ return entry;
43
+ const subField = keywordSubField(findMappingNode(properties, field));
44
+ if (!subField)
45
+ return entry;
46
+ return { [`${field}.${subField}`]: order };
47
+ });
48
+ };
49
+ exports.applyKeywordSortFields = applyKeywordSortFields;
50
+ const readMappingProperties = async (client, index) => {
51
+ const response = await client.indices.getMapping({ index });
52
+ const body = response?.body ?? response;
53
+ // getMapping is keyed by concrete index name, which may differ from the alias we asked for.
54
+ const indexBlock = body?.[index] ?? Object.values(body ?? {})[0];
55
+ return indexBlock?.mappings?.properties;
56
+ };
57
+ /**
58
+ * Resolve a query's sort fields against the live index mapping, caching the mapping per index.
59
+ *
60
+ * Fails open: if the mapping cannot be read the sort is returned untouched, so a mapping-service
61
+ * hiccup degrades to today's behaviour instead of breaking search.
62
+ */
63
+ const resolveSortFields = async (client, index, sort) => {
64
+ if (!Array.isArray(sort) || sort.length === 0)
65
+ return sort;
66
+ if (!client?.indices?.getMapping || !index)
67
+ return sort;
68
+ try {
69
+ let pending = mappingCache.get(index);
70
+ if (!pending) {
71
+ pending = readMappingProperties(client, index).catch((err) => {
72
+ mappingCache.delete(index); // a failed lookup must not be cached forever
73
+ throw err;
74
+ });
75
+ mappingCache.set(index, pending);
76
+ }
77
+ const properties = await pending;
78
+ if (!properties)
79
+ return sort;
80
+ return (0, exports.applyKeywordSortFields)(sort, properties);
81
+ }
82
+ catch {
83
+ return sort;
84
+ }
85
+ };
86
+ exports.resolveSortFields = resolveSortFields;
87
+ /** Exposed for tests and for callers that rebuild an index. */
88
+ const clearMappingCache = (index) => {
89
+ if (index)
90
+ mappingCache.delete(index);
91
+ else
92
+ mappingCache.clear();
93
+ };
94
+ exports.clearMappingCache = clearMappingCache;
95
+ //# sourceMappingURL=opensearch.mapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opensearch.mapping.js","sourceRoot":"","sources":["../../../src/utils/opensearch/opensearch.mapping.ts"],"names":[],"mappings":"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","sourcesContent":["const KEYWORD_SUFFIX = \".keyword\";\n\ntype MappingNode = {\n type?: string;\n fields?: Record<string, { type?: string }>;\n properties?: Record<string, MappingNode>;\n};\n\ntype MappingProperties = Record<string, MappingNode>;\n\ntype MappingClient = {\n indices: { getMapping: (params: { index: string }) => Promise<any> };\n};\n\n/** One in-flight/resolved mapping lookup per index, for the lifetime of the container. */\nconst mappingCache = new Map<string, Promise<MappingProperties | undefined>>();\n\n/** Walk a dotted path such as `location.area` through nested `properties` blocks. */\nconst findMappingNode = (properties: MappingProperties, path: string): MappingNode | undefined => {\n const segments = path.split(\".\");\n let node: MappingNode | undefined = properties[segments[0]];\n\n for (const segment of segments.slice(1)) {\n if (!node?.properties) return undefined;\n node = node.properties[segment];\n }\n\n return node;\n};\n\n/** The name of a keyword sub-field, if this node is a text field that has one. */\nconst keywordSubField = (node: MappingNode | undefined): string | undefined => {\n if (node?.type !== \"text\" || !node.fields) return undefined;\n\n return Object.entries(node.fields).find(([, subField]) => subField?.type === \"keyword\")?.[0];\n};\n\n/**\n * Rewrite sort entries that target an analysed `text` field onto its keyword sub-field.\n *\n * OpenSearch cannot sort on a `text` field (fielddata is disabled by default), so sorting on one\n * fails the whole search. The correct target is decided from the index mapping rather than guessed:\n * `keyword`, numeric and date fields are left alone, and an unknown field is left alone too, so a\n * genuine mapping mistake still surfaces as a real error instead of being silently swallowed.\n */\nexport const applyKeywordSortFields = (sort: any[], properties: MappingProperties): any[] => {\n if (!Array.isArray(sort) || sort.length === 0) return [];\n if (!properties) return sort;\n\n return sort.map((entry) => {\n if (!entry || typeof entry !== \"object\") return entry;\n\n const [field, order] = Object.entries(entry)[0] ?? [];\n if (typeof field !== \"string\" || field.endsWith(KEYWORD_SUFFIX)) return entry;\n\n const subField = keywordSubField(findMappingNode(properties, field));\n if (!subField) return entry;\n\n return { [`${field}.${subField}`]: order };\n });\n};\n\nconst readMappingProperties = async (client: MappingClient, index: string): Promise<MappingProperties | undefined> => {\n const response = await client.indices.getMapping({ index });\n const body = response?.body ?? response;\n\n // getMapping is keyed by concrete index name, which may differ from the alias we asked for.\n const indexBlock = body?.[index] ?? Object.values(body ?? {})[0];\n\n return (indexBlock as any)?.mappings?.properties;\n};\n\n/**\n * Resolve a query's sort fields against the live index mapping, caching the mapping per index.\n *\n * Fails open: if the mapping cannot be read the sort is returned untouched, so a mapping-service\n * hiccup degrades to today's behaviour instead of breaking search.\n */\nexport const resolveSortFields = async (client: MappingClient, index: string, sort: any[]): Promise<any[]> => {\n if (!Array.isArray(sort) || sort.length === 0) return sort;\n if (!client?.indices?.getMapping || !index) return sort;\n\n try {\n let pending = mappingCache.get(index);\n\n if (!pending) {\n pending = readMappingProperties(client, index).catch((err) => {\n mappingCache.delete(index); // a failed lookup must not be cached forever\n throw err;\n });\n mappingCache.set(index, pending);\n }\n\n const properties = await pending;\n if (!properties) return sort;\n\n return applyKeywordSortFields(sort, properties);\n } catch {\n return sort;\n }\n};\n\n/** Exposed for tests and for callers that rebuild an index. */\nexport const clearMappingCache = (index?: string): void => {\n if (index) mappingCache.delete(index);\n else mappingCache.clear();\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-service-stack",
3
- "version": "0.18.449",
3
+ "version": "0.18.451",
4
4
  "packageManager": "pnpm@10.33.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,7 +15,7 @@
15
15
  "access": "public"
16
16
  },
17
17
  "engines": {
18
- "node": ">=20"
18
+ "node": ">=24"
19
19
  },
20
20
  "scripts": {
21
21
  "check": "tsc --noEmit",
@@ -33,19 +33,19 @@
33
33
  "reflect-metadata": "^0.2.2"
34
34
  },
35
35
  "peerDependencies": {
36
- "@aws-sdk/client-apigatewaymanagementapi": "^3.1111.0",
37
- "@aws-sdk/client-cognito-identity-provider": "^3.1111.0",
38
- "@aws-sdk/client-dynamodb": "^3.1111.0",
39
- "@aws-sdk/client-secrets-manager": "^3.1111.0",
40
- "@aws-sdk/credential-provider-node": "^3.972.80",
41
- "@aws-sdk/lib-dynamodb": "^3.1111.0",
36
+ "@aws-sdk/client-apigatewaymanagementapi": "^3.1167.0",
37
+ "@aws-sdk/client-cognito-identity-provider": "^3.1167.0",
38
+ "@aws-sdk/client-dynamodb": "^3.1167.0",
39
+ "@aws-sdk/client-secrets-manager": "^3.1167.0",
40
+ "@aws-sdk/credential-provider-node": "^3.972.97",
41
+ "@aws-sdk/lib-dynamodb": "^3.1167.0",
42
42
  "@aws-sdk/util-dynamodb": "^3.996.9",
43
- "@opensearch-project/opensearch": "^3.6.0",
44
- "axios": "^1.19.0",
45
- "dayjs": "^1.11.21",
43
+ "@opensearch-project/opensearch": "^3.10.0",
44
+ "axios": "^1.21.1",
45
+ "dayjs": "^1.11.23",
46
46
  "typedi": "0.10.0",
47
47
  "yup": "^1.7.1",
48
- "zod": "^4.3.6"
48
+ "zod": "^4.6.7"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@aws-sdk/client-apigatewaymanagementapi": {
@@ -89,27 +89,27 @@
89
89
  }
90
90
  },
91
91
  "devDependencies": {
92
- "@aws-sdk/client-apigatewaymanagementapi": "^3.1111.0",
93
- "@aws-sdk/client-cognito-identity-provider": "^3.1111.0",
94
- "@aws-sdk/client-dynamodb": "^3.1111.0",
95
- "@aws-sdk/client-secrets-manager": "^3.1111.0",
96
- "@aws-sdk/credential-provider-node": "^3.972.80",
97
- "@aws-sdk/lib-dynamodb": "^3.1111.0",
92
+ "@aws-sdk/client-apigatewaymanagementapi": "^3.1167.0",
93
+ "@aws-sdk/client-cognito-identity-provider": "^3.1167.0",
94
+ "@aws-sdk/client-dynamodb": "^3.1167.0",
95
+ "@aws-sdk/client-secrets-manager": "^3.1167.0",
96
+ "@aws-sdk/credential-provider-node": "^3.972.97",
97
+ "@aws-sdk/lib-dynamodb": "^3.1167.0",
98
98
  "@aws-sdk/util-dynamodb": "^3.996.9",
99
- "@biomejs/biome": "^2.5.8",
100
- "@opensearch-project/opensearch": "^3.6.0",
101
- "@types/aws-lambda": "^8.10.162",
102
- "@types/node": "^20.19.43",
103
- "@vitest/coverage-v8": "^4.1.10",
104
- "axios": "^1.19.0",
105
- "dayjs": "^1.11.21",
99
+ "@biomejs/biome": "^2.6.7",
100
+ "@opensearch-project/opensearch": "^3.10.0",
101
+ "@types/aws-lambda": "^8.10.164",
102
+ "@types/node": "^24.19.2",
103
+ "@vitest/coverage-v8": "^5.1.0",
104
+ "axios": "^1.21.1",
105
+ "dayjs": "^1.11.23",
106
106
  "ts-node": "^10.9.2",
107
- "tsc-alias": "^1.9.1",
107
+ "tsc-alias": "^1.9.10",
108
108
  "typedi": "0.10.0",
109
109
  "typescript": "^7.0.2",
110
110
  "typescript-transform-paths": "^4.0.0",
111
- "vitest": "^4.1.10",
111
+ "vitest": "^5.1.0",
112
112
  "yup": "^1.7.1",
113
- "zod": "^4.3.6"
113
+ "zod": "^4.6.7"
114
114
  }
115
115
  }