@rexeus/typeweaver-types 0.10.0 → 0.10.2
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/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/lib/RequestValidator.d.ts +1 -1
- package/dist/lib/RequestValidator.js +22 -16
- package/dist/lib/ResponseValidator.d.ts +1 -1
- package/dist/lib/ResponseValidator.js +98 -97
- package/dist/lib/Validator.js +193 -180
- package/dist/lib/definitionLookup.js +16 -10
- package/dist/lib/index.ts +4 -4
- package/dist/templates/RequestValidator.ejs +1 -1
- package/dist/templates/ResponseValidator.ejs +1 -1
- package/package.json +7 -8
package/dist/lib/Validator.js
CHANGED
|
@@ -1,185 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file was automatically generated by typeweaver.
|
|
3
|
+
* DO NOT EDIT. Instead, modify the source definition file and generate again.
|
|
4
|
+
*
|
|
5
|
+
* @generated by @rexeus/typeweaver
|
|
6
|
+
*/
|
|
1
7
|
import z from "zod";
|
|
2
8
|
import { $ZodArray, $ZodOptional } from "zod/v4/core";
|
|
3
9
|
/**
|
|
4
|
-
* Abstract base class for HTTP validation.
|
|
5
|
-
*
|
|
6
|
-
* This class provides the foundation for request and response validators that:
|
|
7
|
-
* - Analyze Zod schemas for correct single/multi value handling of headers and query parameters
|
|
8
|
-
* - Coerce objects to match schema expectations
|
|
9
|
-
*/
|
|
10
|
+
* Abstract base class for HTTP validation.
|
|
11
|
+
*
|
|
12
|
+
* This class provides the foundation for request and response validators that:
|
|
13
|
+
* - Analyze Zod schemas for correct single/multi value handling of headers and query parameters
|
|
14
|
+
* - Coerce objects to match schema expectations
|
|
15
|
+
*/
|
|
10
16
|
export class Validator {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
17
|
+
static schemaCacheCaseSensitive = new WeakMap();
|
|
18
|
+
static schemaCacheCaseInsensitive = new WeakMap();
|
|
19
|
+
/**
|
|
20
|
+
* Analyzes a Zod schema shape to create an efficient lookup map.
|
|
21
|
+
* Results are cached using WeakMap for optimal performance.
|
|
22
|
+
*
|
|
23
|
+
* @param shape - The Zod schema shape to analyze
|
|
24
|
+
* @param caseSensitive - Whether to preserve key casing (true) or normalize to lowercase (false)
|
|
25
|
+
* @returns Map with lookup keys pointing to original key and array type information
|
|
26
|
+
*/
|
|
27
|
+
analyzeSchema(shape, caseSensitive) {
|
|
28
|
+
const cache = caseSensitive
|
|
29
|
+
? Validator.schemaCacheCaseSensitive
|
|
30
|
+
: Validator.schemaCacheCaseInsensitive;
|
|
31
|
+
const cached = cache.get(shape);
|
|
32
|
+
if (cached) {
|
|
33
|
+
return cached;
|
|
34
|
+
}
|
|
35
|
+
const schemaMap = this.buildSchemaMap(shape, caseSensitive);
|
|
36
|
+
cache.set(shape, schemaMap);
|
|
37
|
+
return schemaMap;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* Extracts a Zod schema shape from header or query schemas.
|
|
42
|
+
* This is used to support schema coercion and analysis.
|
|
43
|
+
*
|
|
44
|
+
* @param headerSchema
|
|
45
|
+
* @returns
|
|
46
|
+
*/
|
|
47
|
+
getSchema(headerSchema) {
|
|
48
|
+
if (headerSchema instanceof z.ZodObject) {
|
|
49
|
+
return headerSchema.shape;
|
|
50
|
+
}
|
|
51
|
+
if (headerSchema instanceof z.ZodOptional) {
|
|
52
|
+
const unwrapped = headerSchema.unwrap();
|
|
53
|
+
if (unwrapped instanceof z.ZodObject) {
|
|
54
|
+
return unwrapped.shape;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Builds a schema map by analyzing the Zod shape structure.
|
|
61
|
+
* Extracts type information for each field to support proper coercion.
|
|
62
|
+
*/
|
|
63
|
+
buildSchemaMap(shape, caseSensitive) {
|
|
64
|
+
const schemaMap = new Map();
|
|
65
|
+
for (const [key, zodType] of Object.entries(shape)) {
|
|
66
|
+
if (!zodType)
|
|
67
|
+
continue;
|
|
68
|
+
const isArray = zodType instanceof $ZodArray ||
|
|
69
|
+
(zodType instanceof $ZodOptional &&
|
|
70
|
+
zodType._zod.def.innerType instanceof $ZodArray);
|
|
71
|
+
const lookupKey = caseSensitive ? key : key.toLowerCase();
|
|
72
|
+
schemaMap.set(lookupKey, { originalKey: key, isArray });
|
|
73
|
+
}
|
|
74
|
+
return schemaMap;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Coerces objects to match schema expectations with configurable case sensitivity.
|
|
78
|
+
* Values not in the schema are ignored.
|
|
79
|
+
*
|
|
80
|
+
* @param data - The data object to coerce
|
|
81
|
+
* @param shape - The Zod schema shape to match against
|
|
82
|
+
* @param caseSensitive - Whether keys should match exactly (true) or case-insensitively (false)
|
|
83
|
+
* @returns Coerced data object with proper key casing and array coercion
|
|
84
|
+
*/
|
|
85
|
+
coerceToSchema(data, shape, caseSensitive) {
|
|
86
|
+
if (typeof data !== "object" || data === null) {
|
|
87
|
+
return data;
|
|
88
|
+
}
|
|
89
|
+
const schemaMap = this.analyzeSchema(shape, caseSensitive);
|
|
90
|
+
const coerced = {};
|
|
91
|
+
for (const [key, value] of Object.entries(data)) {
|
|
92
|
+
const normalizedKey = caseSensitive ? key : key.toLowerCase();
|
|
93
|
+
const schemaInfo = schemaMap.get(normalizedKey);
|
|
94
|
+
if (schemaInfo) {
|
|
95
|
+
this.addValueToCoerced(coerced, normalizedKey, value, schemaInfo.isArray);
|
|
96
|
+
}
|
|
97
|
+
// Headers/params not in schema are ignored (strict validation)
|
|
98
|
+
}
|
|
99
|
+
// If case-sensitive, return coerced object as is
|
|
100
|
+
if (caseSensitive) {
|
|
101
|
+
return coerced;
|
|
102
|
+
}
|
|
103
|
+
// If case-insensitive, map back to original keys from schema
|
|
104
|
+
return this.mapToOriginalKeys(coerced, schemaMap);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Adds a value to the coerced object, handling collisions when multiple
|
|
108
|
+
* values exist for the same key (e.g., duplicate headers with different casing).
|
|
109
|
+
* Preserves all values as arrays when collisions occur to prevent data loss.
|
|
110
|
+
*/
|
|
111
|
+
addValueToCoerced(coerced, key, value, expectsArray) {
|
|
112
|
+
const existing = coerced[key];
|
|
113
|
+
const newValue = this.coerceValueStructure(value, expectsArray);
|
|
114
|
+
if (existing === undefined) {
|
|
115
|
+
coerced[key] = newValue;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Merge existing and new values
|
|
119
|
+
const existingArray = Array.isArray(existing) ? existing : [existing];
|
|
120
|
+
const newArray = Array.isArray(newValue) ? newValue : [newValue];
|
|
121
|
+
const merged = [...existingArray, ...newArray];
|
|
122
|
+
// If schema expects a single value but we have multiple, preserve as array
|
|
123
|
+
// to avoid data loss (validation will catch this later)
|
|
124
|
+
coerced[key] = expectsArray || merged.length > 1 ? merged : merged[0];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Coerces a value's structure to match schema expectations.
|
|
128
|
+
* Wraps single values in arrays when schema expects array type,
|
|
129
|
+
* unwraps single-element arrays when schema expects single value.
|
|
130
|
+
*/
|
|
131
|
+
coerceValueStructure(value, expectsArray) {
|
|
132
|
+
if (expectsArray && !Array.isArray(value)) {
|
|
133
|
+
return [value];
|
|
134
|
+
}
|
|
135
|
+
if (!expectsArray && Array.isArray(value) && value.length === 1) {
|
|
136
|
+
return value[0];
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Maps normalized (lowercase) keys back to their original casing as defined in the schema.
|
|
142
|
+
* Used for case-insensitive matching where the output should preserve schema-defined casing.
|
|
143
|
+
*/
|
|
144
|
+
mapToOriginalKeys(coerced, schemaMap) {
|
|
145
|
+
const withOriginalKeys = {};
|
|
146
|
+
for (const [key, value] of Object.entries(coerced)) {
|
|
147
|
+
const originalKey = schemaMap.get(key)?.originalKey ?? key;
|
|
148
|
+
withOriginalKeys[originalKey] = value;
|
|
149
|
+
}
|
|
150
|
+
return withOriginalKeys;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Coerces header data to match schema expectations with case-insensitive matching.
|
|
154
|
+
*
|
|
155
|
+
* @param header - The header object to coerce
|
|
156
|
+
* @param shape - The Zod schema shape for headers
|
|
157
|
+
* @returns Coerced header object
|
|
158
|
+
*/
|
|
159
|
+
coerceHeaderToSchema(header, shape) {
|
|
160
|
+
if (typeof header !== "object" || header === null) {
|
|
161
|
+
return this.coerceToSchema(header ?? {}, shape, false);
|
|
162
|
+
}
|
|
163
|
+
const preprocessed = this.splitCommaDelimitedValues(header, shape);
|
|
164
|
+
return this.coerceToSchema(preprocessed, shape, false);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Splits comma-separated header strings into arrays per RFC 7230.
|
|
168
|
+
* Only applies to fields where the schema expects an array type.
|
|
169
|
+
* Values that are already arrays pass through unchanged.
|
|
170
|
+
*/
|
|
171
|
+
splitCommaDelimitedValues(header, shape) {
|
|
172
|
+
const schemaMap = this.analyzeSchema(shape, false);
|
|
173
|
+
const result = {};
|
|
174
|
+
for (const [key, value] of Object.entries(header)) {
|
|
175
|
+
const schemaInfo = schemaMap.get(key.toLowerCase());
|
|
176
|
+
if (schemaInfo?.isArray && typeof value === "string") {
|
|
177
|
+
result[key] = value
|
|
178
|
+
.split(",")
|
|
179
|
+
.map(v => v.trim())
|
|
180
|
+
.filter(v => v !== "");
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
result[key] = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Coerces query data to match schema expectations with case-sensitive matching.
|
|
190
|
+
*
|
|
191
|
+
* @param query - The query object to coerce
|
|
192
|
+
* @param shape - The Zod schema shape for query parameters
|
|
193
|
+
* @returns Coerced query object
|
|
194
|
+
*/
|
|
195
|
+
coerceQueryToSchema(query, shape) {
|
|
196
|
+
return this.coerceToSchema(query ?? {}, shape, true); // case-sensitive
|
|
197
|
+
}
|
|
185
198
|
}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file was automatically generated by typeweaver.
|
|
3
|
+
* DO NOT EDIT. Instead, modify the source definition file and generate again.
|
|
4
|
+
*
|
|
5
|
+
* @generated by @rexeus/typeweaver
|
|
6
|
+
*/
|
|
1
7
|
export const getOperationDefinition = (spec, resourceName, operationId) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
const operation = spec.resources[resourceName]?.operations.find(candidate => candidate.operationId === operationId);
|
|
9
|
+
if (operation === undefined) {
|
|
10
|
+
throw new Error(`Missing operation definition '${String(resourceName)}.${String(operationId)}'.`);
|
|
11
|
+
}
|
|
12
|
+
return operation;
|
|
7
13
|
};
|
|
8
14
|
export const getResponseDefinition = (responses, responseName) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
const response = responses.find(candidate => candidate.name === responseName);
|
|
16
|
+
if (response === undefined) {
|
|
17
|
+
throw new Error(`Missing response definition '${String(responseName)}'.`);
|
|
18
|
+
}
|
|
19
|
+
return response;
|
|
14
20
|
};
|
package/dist/lib/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @generated by @rexeus/typeweaver
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export * from "./definitionLookup";
|
|
9
|
-
export * from "./RequestValidator";
|
|
10
|
-
export * from "./ResponseValidator";
|
|
11
|
-
export * from "./Validator";
|
|
8
|
+
export * from "./definitionLookup.js";
|
|
9
|
+
export * from "./RequestValidator.js";
|
|
10
|
+
export * from "./ResponseValidator.js";
|
|
11
|
+
export * from "./Validator.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type SafeRequestValidationResult,
|
|
13
13
|
RequestValidationError
|
|
14
14
|
} from "@rexeus/typeweaver-core";
|
|
15
|
-
import { getOperationDefinition, RequestValidator } from "../lib/types";
|
|
15
|
+
import { getOperationDefinition, RequestValidator } from "../lib/types/index.js";
|
|
16
16
|
import type { I<%= pascalCaseOperationId %>Request } from "<%= requestFile %>";
|
|
17
17
|
|
|
18
18
|
const definition = getOperationDefinition(spec, "<%= resourceName %>", "<%= operationId %>");
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
getResponseDefinition,
|
|
13
13
|
type ResponseEntry,
|
|
14
14
|
ResponseValidator,
|
|
15
|
-
} from "../lib/types";
|
|
15
|
+
} from "../lib/types/index.js";
|
|
16
16
|
import type { <%= pascalCaseOperationId %>Response } from "<%= responseFile %>";
|
|
17
17
|
|
|
18
18
|
const definition = getOperationDefinition(spec, "<%= resourceName %>", "<%= operationId %>");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rexeus/typeweaver-types",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Generates request and response types plus validators aligned with your API contract. Powered by Typeweaver 🧵✨",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -47,22 +47,21 @@
|
|
|
47
47
|
},
|
|
48
48
|
"homepage": "https://github.com/rexeus/typeweaver#readme",
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@rexeus/typeweaver-core": "^0.10.
|
|
51
|
-
"@rexeus/typeweaver-gen": "^0.10.
|
|
50
|
+
"@rexeus/typeweaver-core": "^0.10.2",
|
|
51
|
+
"@rexeus/typeweaver-gen": "^0.10.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"oxc-transform": "^0.121.0",
|
|
55
54
|
"test-utils": "file:../test-utils",
|
|
56
|
-
"@rexeus/typeweaver-core": "^0.10.
|
|
57
|
-
"@rexeus/typeweaver-gen": "^0.10.
|
|
55
|
+
"@rexeus/typeweaver-core": "^0.10.2",
|
|
56
|
+
"@rexeus/typeweaver-gen": "^0.10.2"
|
|
58
57
|
},
|
|
59
58
|
"dependencies": {
|
|
60
|
-
"@rexeus/typeweaver-zod-to-ts": "^0.10.
|
|
59
|
+
"@rexeus/typeweaver-zod-to-ts": "^0.10.2"
|
|
61
60
|
},
|
|
62
61
|
"scripts": {
|
|
63
62
|
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
|
64
63
|
"format": "oxfmt",
|
|
65
|
-
"build": "tsdown
|
|
64
|
+
"build": "tsdown",
|
|
66
65
|
"test": "vitest --run",
|
|
67
66
|
"preversion": "npm run build"
|
|
68
67
|
}
|