@amritk/generate-parsers 0.1.0 → 0.2.1
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/README.md +3 -1
- package/dist/generators/build-schema.d.ts +4 -1
- package/dist/generators/generate-files.d.ts +5 -0
- package/dist/generators/generate-parser-function.d.ts +6 -0
- package/dist/generators/generate-strict-assertion.d.ts +18 -0
- package/dist/index.js +198 -32
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ Write them to disk however you like.
|
|
|
75
75
|
|
|
76
76
|
## API
|
|
77
77
|
|
|
78
|
-
### `buildSchema(rootSchema, rootTypeName, extensions?, typesOnly?, logWarnings?)`
|
|
78
|
+
### `buildSchema(rootSchema, rootTypeName, extensions?, typesOnly?, logWarnings?, strict?)`
|
|
79
79
|
|
|
80
80
|
| Parameter | Type | Description |
|
|
81
81
|
|:---|:---|:---|
|
|
@@ -84,6 +84,7 @@ Write them to disk however you like.
|
|
|
84
84
|
| `extensions` | `SchemaExtensions` _(optional)_ | Map of definition name → extra optional properties to merge in before generation. |
|
|
85
85
|
| `typesOnly` | `boolean` _(optional)_ | When `true`, only emit `.ts` type definitions — skip parser functions and runtime helpers. |
|
|
86
86
|
| `logWarnings` | `boolean` _(optional)_ | When `true`, generated parsers emit a `console.warn` for every input key not declared in the schema's properties. |
|
|
87
|
+
| `strict` | `boolean` _(optional)_ | When `true`, generated parsers throw on type/shape mismatches (wrong type, missing required property, enum/pattern/min/max violations) instead of coercing invalid input to default values. |
|
|
87
88
|
|
|
88
89
|
Returns: `Promise<GeneratedFile[]>`.
|
|
89
90
|
|
|
@@ -96,6 +97,7 @@ Returns: `Promise<GeneratedFile[]>`.
|
|
|
96
97
|
|:---:|:---|:---|:---:|:---:|:---:|:---|
|
|
97
98
|
| 🏷️ | `typesOnly` | — | `boolean` | — | `false` | Generate only TypeScript type definitions without parser functions. Runtime helper files (validators, isObject) are also omitted since they are only needed for parsers. |
|
|
98
99
|
| ⚠️ | `logWarnings` | — | `boolean` | — | `false` | Emit a console.warn in the generated parsers for every input key that is not declared in the schema's properties. Useful for detecting schema drift or unexpected data shapes at runtime. |
|
|
100
|
+
| 🚫 | `strict` | — | `boolean` | — | `false` | Generate parsers that throw on type/shape mismatches (wrong type, missing required property, enum/pattern/min/max violations) instead of coercing invalid input to default values. Unknown extra keys are still allowed. |
|
|
99
101
|
<!-- config-table-end -->
|
|
100
102
|
|
|
101
103
|
The generator handles:
|
|
@@ -27,6 +27,9 @@ export type GeneratedFile = {
|
|
|
27
27
|
* @param typesOnly - When true, only generate TypeScript type definitions without parser functions.
|
|
28
28
|
* @param logWarnings - When true, the generated parsers emit a console.warn for every input key
|
|
29
29
|
* that is not declared in the schema's properties.
|
|
30
|
+
* @param strict - When true, the generated parsers throw on type/shape mismatches
|
|
31
|
+
* (wrong type, missing required property, enum/pattern/min/max violations) instead
|
|
32
|
+
* of coercing invalid input to default values.
|
|
30
33
|
* @returns An array of generated TypeScript files
|
|
31
34
|
*
|
|
32
35
|
* @example
|
|
@@ -59,4 +62,4 @@ export type GeneratedFile = {
|
|
|
59
62
|
* });
|
|
60
63
|
* ```
|
|
61
64
|
*/
|
|
62
|
-
export declare const buildSchema: (rootSchema: JSONSchema, rootTypeName: string, extensions?: SchemaExtensions, typesOnly?: boolean, logWarnings?: boolean) => Promise<GeneratedFile[]>;
|
|
65
|
+
export declare const buildSchema: (rootSchema: JSONSchema, rootTypeName: string, extensions?: SchemaExtensions, typesOnly?: boolean, logWarnings?: boolean, strict?: boolean) => Promise<GeneratedFile[]>;
|
|
@@ -24,6 +24,11 @@ type GenerateFileOptions = {
|
|
|
24
24
|
* that is not declared in the schema's properties.
|
|
25
25
|
*/
|
|
26
26
|
readonly logWarnings?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* When true, the generated parser throws on type/shape mismatches instead
|
|
29
|
+
* of coercing invalid input to default values.
|
|
30
|
+
*/
|
|
31
|
+
readonly strict?: boolean;
|
|
27
32
|
};
|
|
28
33
|
/**
|
|
29
34
|
* Generates a complete TypeScript file from a JSON Schema.
|
|
@@ -14,6 +14,12 @@ type GenerateParserOptions = {
|
|
|
14
14
|
* that is not declared in the schema's properties.
|
|
15
15
|
*/
|
|
16
16
|
readonly logWarnings?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* When true, the generated parser throws on type/shape mismatches instead
|
|
19
|
+
* of coercing invalid input to default values. Throws on the first violation
|
|
20
|
+
* with a path-aware Error. Unknown extra keys are still allowed.
|
|
21
|
+
*/
|
|
22
|
+
readonly strict?: boolean;
|
|
17
23
|
};
|
|
18
24
|
/**
|
|
19
25
|
* Generates a safe parser as an arrow function that never throws.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
2
|
+
/**
|
|
3
|
+
* Generates strict-mode assertions for the body of an object parser.
|
|
4
|
+
* Throws on:
|
|
5
|
+
* - non-object input
|
|
6
|
+
* - missing required property
|
|
7
|
+
* - property of the wrong primitive type
|
|
8
|
+
* - enum / pattern / length / min / max / multipleOf violations
|
|
9
|
+
*
|
|
10
|
+
* Properties with a `$ref` are validated by the nested parser's own strict
|
|
11
|
+
* check when that parser is invoked downstream.
|
|
12
|
+
*/
|
|
13
|
+
export declare const generateObjectStrictAssertion: (schema: JSONSchema, typeName: string) => string[];
|
|
14
|
+
/**
|
|
15
|
+
* Generates a single strict-mode assertion line for a non-object scalar parser.
|
|
16
|
+
* Returns null when the schema has no type information to assert on.
|
|
17
|
+
*/
|
|
18
|
+
export declare const generateScalarStrictAssertion: (schema: JSONSchema, typeName: string) => string | null;
|
package/dist/index.js
CHANGED
|
@@ -1142,6 +1142,119 @@ var getDefaultValue = (schema) => {
|
|
|
1142
1142
|
}
|
|
1143
1143
|
};
|
|
1144
1144
|
|
|
1145
|
+
// src/generators/generate-strict-assertion.ts
|
|
1146
|
+
var wrongTypeCondition = (accessor, type) => {
|
|
1147
|
+
switch (type) {
|
|
1148
|
+
case "string":
|
|
1149
|
+
return `typeof ${accessor} !== "string"`;
|
|
1150
|
+
case "number":
|
|
1151
|
+
case "integer":
|
|
1152
|
+
return `typeof ${accessor} !== "number"`;
|
|
1153
|
+
case "boolean":
|
|
1154
|
+
return `typeof ${accessor} !== "boolean"`;
|
|
1155
|
+
case "array":
|
|
1156
|
+
return `!Array.isArray(${accessor})`;
|
|
1157
|
+
case "object":
|
|
1158
|
+
return `!isObject(${accessor})`;
|
|
1159
|
+
default:
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
var typeLabel = (type) => type === "integer" ? "number" : type;
|
|
1164
|
+
var escapeRegexPattern = (pattern) => {
|
|
1165
|
+
return pattern.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
|
|
1166
|
+
};
|
|
1167
|
+
var generateConstraintChecks = (acc, propSchema, typeName, key) => {
|
|
1168
|
+
if (!isSchemaObject3(propSchema) || !hasType(propSchema))
|
|
1169
|
+
return [];
|
|
1170
|
+
const t = propSchema.type;
|
|
1171
|
+
const lines = [];
|
|
1172
|
+
if (t === "string") {
|
|
1173
|
+
if (hasPattern(propSchema)) {
|
|
1174
|
+
const pattern = escapeRegexPattern(propSchema.pattern);
|
|
1175
|
+
lines.push(` if (typeof ${acc} === "string" && !/${pattern}/.test(${acc})) throw new Error('[${typeName}] field "${key}" must match pattern ${propSchema.pattern}');`);
|
|
1176
|
+
}
|
|
1177
|
+
if (hasMinLength(propSchema)) {
|
|
1178
|
+
lines.push(` if (typeof ${acc} === "string" && ${acc}.length < ${propSchema.minLength}) throw new Error('[${typeName}] field "${key}" must have at least ${propSchema.minLength} characters');`);
|
|
1179
|
+
}
|
|
1180
|
+
if (hasMaxLength(propSchema)) {
|
|
1181
|
+
lines.push(` if (typeof ${acc} === "string" && ${acc}.length > ${propSchema.maxLength}) throw new Error('[${typeName}] field "${key}" must have at most ${propSchema.maxLength} characters');`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (t === "number" || t === "integer") {
|
|
1185
|
+
if (hasMinimum(propSchema)) {
|
|
1186
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} < ${propSchema.minimum}) throw new Error('[${typeName}] field "${key}" must be >= ${propSchema.minimum}');`);
|
|
1187
|
+
}
|
|
1188
|
+
if (hasMaximum(propSchema)) {
|
|
1189
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} > ${propSchema.maximum}) throw new Error('[${typeName}] field "${key}" must be <= ${propSchema.maximum}');`);
|
|
1190
|
+
}
|
|
1191
|
+
if (hasExclusiveMinimum(propSchema)) {
|
|
1192
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} <= ${propSchema.exclusiveMinimum}) throw new Error('[${typeName}] field "${key}" must be > ${propSchema.exclusiveMinimum}');`);
|
|
1193
|
+
}
|
|
1194
|
+
if (hasExclusiveMaximum(propSchema)) {
|
|
1195
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} >= ${propSchema.exclusiveMaximum}) throw new Error('[${typeName}] field "${key}" must be < ${propSchema.exclusiveMaximum}');`);
|
|
1196
|
+
}
|
|
1197
|
+
if (hasMultipleOf(propSchema)) {
|
|
1198
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} % ${propSchema.multipleOf} !== 0) throw new Error('[${typeName}] field "${key}" must be a multiple of ${propSchema.multipleOf}');`);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return lines;
|
|
1202
|
+
};
|
|
1203
|
+
var generatePropertyAssertion = (key, propSchema, isRequired, typeName) => {
|
|
1204
|
+
const acc = safeAccessor("input", key);
|
|
1205
|
+
const lines = [];
|
|
1206
|
+
if (isRequired) {
|
|
1207
|
+
lines.push(` if (!(${JSON.stringify(key)} in input)) throw new Error('[${typeName}] missing required property "${key}"');`);
|
|
1208
|
+
}
|
|
1209
|
+
if (!isSchemaObject3(propSchema))
|
|
1210
|
+
return lines;
|
|
1211
|
+
if (hasRef(propSchema))
|
|
1212
|
+
return lines;
|
|
1213
|
+
if (hasEnum(propSchema)) {
|
|
1214
|
+
const allowed = JSON.stringify(propSchema.enum);
|
|
1215
|
+
const label = propSchema.enum.map((v) => JSON.stringify(v)).join(", ");
|
|
1216
|
+
if (isRequired) {
|
|
1217
|
+
lines.push(` if (!(${allowed} as readonly unknown[]).includes(${acc})) throw new Error('[${typeName}] field "${key}" must be one of: ${label}');`);
|
|
1218
|
+
} else {
|
|
1219
|
+
lines.push(` if (${acc} !== undefined && !(${allowed} as readonly unknown[]).includes(${acc})) throw new Error('[${typeName}] field "${key}" must be one of: ${label}');`);
|
|
1220
|
+
}
|
|
1221
|
+
return lines;
|
|
1222
|
+
}
|
|
1223
|
+
if (hasType(propSchema)) {
|
|
1224
|
+
const t = propSchema.type;
|
|
1225
|
+
const wrongType = wrongTypeCondition(acc, t);
|
|
1226
|
+
if (wrongType) {
|
|
1227
|
+
if (isRequired) {
|
|
1228
|
+
lines.push(` if (${wrongType}) throw new Error(\`[${typeName}] field "${key}" expected ${typeLabel(t)}, got \${typeof ${acc}}\`);`);
|
|
1229
|
+
} else {
|
|
1230
|
+
lines.push(` if (${acc} !== undefined && (${wrongType})) throw new Error(\`[${typeName}] field "${key}" expected ${typeLabel(t)}, got \${typeof ${acc}}\`);`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
lines.push(...generateConstraintChecks(acc, propSchema, typeName, key));
|
|
1234
|
+
}
|
|
1235
|
+
return lines;
|
|
1236
|
+
};
|
|
1237
|
+
var generateObjectStrictAssertion = (schema, typeName) => {
|
|
1238
|
+
const lines = [];
|
|
1239
|
+
lines.push(` if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);`);
|
|
1240
|
+
if (!hasProperties(schema) || !isSchemaObject3(schema))
|
|
1241
|
+
return lines;
|
|
1242
|
+
const required = new Set(hasRequired(schema) ? schema.required : []);
|
|
1243
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
1244
|
+
lines.push(...generatePropertyAssertion(key, propSchema, required.has(key), typeName));
|
|
1245
|
+
}
|
|
1246
|
+
return lines;
|
|
1247
|
+
};
|
|
1248
|
+
var generateScalarStrictAssertion = (schema, typeName) => {
|
|
1249
|
+
if (!isSchemaObject3(schema) || !hasType(schema))
|
|
1250
|
+
return null;
|
|
1251
|
+
const t = schema.type;
|
|
1252
|
+
const wrongType = wrongTypeCondition("input", t);
|
|
1253
|
+
if (!wrongType)
|
|
1254
|
+
return null;
|
|
1255
|
+
return ` if (${wrongType}) throw new Error(\`[${typeName}] expected ${typeLabel(t)}, got \${input === null ? "null" : typeof input}\`);`;
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1145
1258
|
// src/helpers/find-discriminator.ts
|
|
1146
1259
|
var findDiscriminator = (schemas) => {
|
|
1147
1260
|
const propertyValues = new Map;
|
|
@@ -1712,11 +1825,22 @@ var generateFallbackObject = (schema, useRefImports, typeName) => {
|
|
|
1712
1825
|
}`;
|
|
1713
1826
|
return result;
|
|
1714
1827
|
};
|
|
1715
|
-
var generateNonObjectParser = (typeName, schema) => {
|
|
1828
|
+
var generateNonObjectParser = (typeName, schema, strict) => {
|
|
1716
1829
|
const functionName = generateParserName(typeName);
|
|
1717
1830
|
if (!isSchemaObject3(schema) || !hasType(schema)) {
|
|
1718
1831
|
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
1719
1832
|
}
|
|
1833
|
+
if (strict) {
|
|
1834
|
+
const assertion = generateScalarStrictAssertion(schema, typeName);
|
|
1835
|
+
if (assertion === null) {
|
|
1836
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
1837
|
+
}
|
|
1838
|
+
const returnExpr = schema.type === "array" ? `[...(input as readonly unknown[])] as ${typeName}` : `input as ${typeName}`;
|
|
1839
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1840
|
+
${assertion}
|
|
1841
|
+
return ${returnExpr};
|
|
1842
|
+
};`;
|
|
1843
|
+
}
|
|
1720
1844
|
switch (schema.type) {
|
|
1721
1845
|
case "string":
|
|
1722
1846
|
return `export const ${functionName} = (input: unknown): ${typeName} => typeof input === "string" ? input as ${typeName} : "" as ${typeName};`;
|
|
@@ -1731,8 +1855,14 @@ var generateNonObjectParser = (typeName, schema) => {
|
|
|
1731
1855
|
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
1732
1856
|
}
|
|
1733
1857
|
};
|
|
1734
|
-
var generateEmptyObjectParser = (typeName) => {
|
|
1858
|
+
var generateEmptyObjectParser = (typeName, strict) => {
|
|
1735
1859
|
const functionName = generateParserName(typeName);
|
|
1860
|
+
if (strict) {
|
|
1861
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1862
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
1863
|
+
return { ...input } as ${typeName};
|
|
1864
|
+
};`;
|
|
1865
|
+
}
|
|
1736
1866
|
return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? { ...input } as ${typeName} : {} as ${typeName};`;
|
|
1737
1867
|
};
|
|
1738
1868
|
var toVarName = (key) => {
|
|
@@ -1761,7 +1891,7 @@ var generatePropertyTypeCheck = (varName, schema, useRefImports) => {
|
|
|
1761
1891
|
case "string": {
|
|
1762
1892
|
checks.push(`typeof ${varName} === "string"`);
|
|
1763
1893
|
if (hasPattern(schema))
|
|
1764
|
-
checks.push(`/${
|
|
1894
|
+
checks.push(`/${escapeRegexPattern2(schema.pattern)}/.test(${varName})`);
|
|
1765
1895
|
if (hasMinLength(schema))
|
|
1766
1896
|
checks.push(`${varName}.length >= ${schema.minLength}`);
|
|
1767
1897
|
if (hasMaxLength(schema))
|
|
@@ -1814,16 +1944,28 @@ var generatePropertyTypeCheck = (varName, schema, useRefImports) => {
|
|
|
1814
1944
|
var shouldCacheVariable = (propSchema, _canFastPath, _useRefImports) => {
|
|
1815
1945
|
return isSchemaObject3(propSchema);
|
|
1816
1946
|
};
|
|
1817
|
-
var generateObjectParser = (schema, typeName, useRefImports, logWarnings) => {
|
|
1947
|
+
var generateObjectParser = (schema, typeName, useRefImports, logWarnings, strict) => {
|
|
1818
1948
|
const functionName = generateParserName(typeName);
|
|
1819
1949
|
if (!hasProperties(schema)) {
|
|
1950
|
+
if (strict) {
|
|
1951
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1952
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
1953
|
+
return input as ${typeName};
|
|
1954
|
+
};`;
|
|
1955
|
+
}
|
|
1820
1956
|
return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? input as ${typeName} : {} as ${typeName};`;
|
|
1821
1957
|
}
|
|
1822
1958
|
const fallbackObject = generateFallbackObject(schema, useRefImports, typeName);
|
|
1823
1959
|
const properties = Object.entries(schema.properties);
|
|
1824
1960
|
const lines = [];
|
|
1825
1961
|
lines.push(`export const ${functionName} = (input: unknown): ${typeName} => {`);
|
|
1826
|
-
|
|
1962
|
+
if (strict) {
|
|
1963
|
+
for (const assertionLine of generateObjectStrictAssertion(schema, typeName)) {
|
|
1964
|
+
lines.push(assertionLine);
|
|
1965
|
+
}
|
|
1966
|
+
} else {
|
|
1967
|
+
lines.push(` if (!isObject(input)) return ${fallbackObject};`);
|
|
1968
|
+
}
|
|
1827
1969
|
const propInfo = [];
|
|
1828
1970
|
const hasAllOfRefParsers = useRefImports && isSchemaObject3(schema) && hasAllOf(schema) && schema.allOf.some((entry) => isSchemaObject3(entry) && hasRef(entry));
|
|
1829
1971
|
let canFastPath = !hasAllOfRefParsers;
|
|
@@ -1946,7 +2088,7 @@ var generateObjectParser = (schema, typeName, useRefImports, logWarnings) => {
|
|
|
1946
2088
|
}
|
|
1947
2089
|
return result;
|
|
1948
2090
|
};
|
|
1949
|
-
var generateCombinedObjectParser = (schema, typeName, useRefImports, logWarnings) => {
|
|
2091
|
+
var generateCombinedObjectParser = (schema, typeName, useRefImports, logWarnings, strict) => {
|
|
1950
2092
|
const functionName = generateParserName(typeName);
|
|
1951
2093
|
const entries = generatePropertyEntries(schema, useRefImports);
|
|
1952
2094
|
const propertyLines = entries.map((entry) => {
|
|
@@ -1956,19 +2098,19 @@ var generateCombinedObjectParser = (schema, typeName, useRefImports, logWarnings
|
|
|
1956
2098
|
return ` ${safeKey2(entry.key)}: ${entry.value},`;
|
|
1957
2099
|
});
|
|
1958
2100
|
if (!isSchemaObject3(schema) || !("patternProperties" in schema)) {
|
|
1959
|
-
return generateObjectParser(schema, typeName, useRefImports, logWarnings);
|
|
2101
|
+
return generateObjectParser(schema, typeName, useRefImports, logWarnings, strict);
|
|
1960
2102
|
}
|
|
1961
2103
|
const patternProps = schema.patternProperties;
|
|
1962
2104
|
const patterns = Object.entries(patternProps);
|
|
1963
2105
|
const refPattern = patterns.find(([, ps]) => isSchemaObject3(ps) && hasRef(ps));
|
|
1964
2106
|
if (!refPattern || !useRefImports) {
|
|
1965
|
-
return generateObjectParser(schema, typeName, useRefImports, logWarnings);
|
|
2107
|
+
return generateObjectParser(schema, typeName, useRefImports, logWarnings, strict);
|
|
1966
2108
|
}
|
|
1967
2109
|
const [pattern, patternSchema] = refPattern;
|
|
1968
2110
|
const ref = patternSchema.$ref;
|
|
1969
2111
|
const parserName = generateParserName(refToName(ref));
|
|
1970
2112
|
const assignmentCode = `(result as Record<string, unknown>)[key] = ${parserName}(value);`;
|
|
1971
|
-
const escapedPattern =
|
|
2113
|
+
const escapedPattern = escapeRegexPattern2(pattern);
|
|
1972
2114
|
const inputSpread = " ...input,";
|
|
1973
2115
|
let objectProperties = inputSpread;
|
|
1974
2116
|
if (propertyLines.length > 0) {
|
|
@@ -1977,9 +2119,10 @@ var generateCombinedObjectParser = (schema, typeName, useRefImports, logWarnings
|
|
|
1977
2119
|
` + line;
|
|
1978
2120
|
}
|
|
1979
2121
|
}
|
|
2122
|
+
const notObjectBranch = strict ? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);` : ` return {} as unknown as ${typeName};`;
|
|
1980
2123
|
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1981
2124
|
if (!isObject(input)) {
|
|
1982
|
-
|
|
2125
|
+
${notObjectBranch}
|
|
1983
2126
|
}
|
|
1984
2127
|
const result = {
|
|
1985
2128
|
${objectProperties}
|
|
@@ -1993,23 +2136,41 @@ ${objectProperties}
|
|
|
1993
2136
|
return result;
|
|
1994
2137
|
};`;
|
|
1995
2138
|
};
|
|
1996
|
-
var generateAdditionalPropertiesParser = (schema, typeName, useRefImports) => {
|
|
2139
|
+
var generateAdditionalPropertiesParser = (schema, typeName, useRefImports, strict) => {
|
|
1997
2140
|
const functionName = generateParserName(typeName);
|
|
1998
2141
|
const additionalProps = schema.additionalProperties;
|
|
1999
2142
|
if (!additionalProps) {
|
|
2000
|
-
return generateEmptyObjectParser(typeName);
|
|
2143
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2001
2144
|
}
|
|
2002
2145
|
if (useRefImports && isSchemaObject3(additionalProps) && hasRef(additionalProps)) {
|
|
2003
2146
|
const ref = additionalProps.$ref;
|
|
2004
2147
|
const parserName = generateParserName(refToName(ref));
|
|
2148
|
+
if (strict) {
|
|
2149
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
2150
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
2151
|
+
return validateRecord(input, ${parserName}) as ${typeName};
|
|
2152
|
+
};`;
|
|
2153
|
+
}
|
|
2005
2154
|
return `export const ${functionName} = (input: unknown): ${typeName} => validateRecord(input, ${parserName}) as ${typeName};`;
|
|
2006
2155
|
}
|
|
2007
2156
|
if (isSchemaObject3(additionalProps) && hasType(additionalProps)) {
|
|
2008
2157
|
const inlineParser = generateInlineValueParser(additionalProps);
|
|
2009
2158
|
if (inlineParser) {
|
|
2159
|
+
if (strict) {
|
|
2160
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
2161
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
2162
|
+
return validateRecord(input, ${inlineParser}) as ${typeName};
|
|
2163
|
+
};`;
|
|
2164
|
+
}
|
|
2010
2165
|
return `export const ${functionName} = (input: unknown): ${typeName} => validateRecord(input, ${inlineParser}) as ${typeName};`;
|
|
2011
2166
|
}
|
|
2012
2167
|
}
|
|
2168
|
+
if (strict) {
|
|
2169
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
2170
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
2171
|
+
return { ...input } as ${typeName};
|
|
2172
|
+
};`;
|
|
2173
|
+
}
|
|
2013
2174
|
return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? { ...input } as ${typeName} : {};`;
|
|
2014
2175
|
};
|
|
2015
2176
|
var generateInlineValueParser = (schema) => {
|
|
@@ -2030,18 +2191,18 @@ var generateInlineValueParser = (schema) => {
|
|
|
2030
2191
|
return null;
|
|
2031
2192
|
}
|
|
2032
2193
|
};
|
|
2033
|
-
var
|
|
2194
|
+
var escapeRegexPattern2 = (pattern) => {
|
|
2034
2195
|
return pattern.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
|
|
2035
2196
|
};
|
|
2036
|
-
var generatePatternPropertiesParser = (schema, typeName, useRefImports) => {
|
|
2197
|
+
var generatePatternPropertiesParser = (schema, typeName, useRefImports, strict) => {
|
|
2037
2198
|
const functionName = generateParserName(typeName);
|
|
2038
2199
|
if (!("patternProperties" in schema) || typeof schema.patternProperties !== "object") {
|
|
2039
|
-
return generateEmptyObjectParser(typeName);
|
|
2200
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2040
2201
|
}
|
|
2041
2202
|
const patternProps = schema.patternProperties;
|
|
2042
2203
|
const patterns = Object.entries(patternProps);
|
|
2043
2204
|
if (patterns.length === 0) {
|
|
2044
|
-
return generateEmptyObjectParser(typeName);
|
|
2205
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2045
2206
|
}
|
|
2046
2207
|
const [pattern, patternSchema] = patterns[0];
|
|
2047
2208
|
let patternAssignment = "(result as Record<string, unknown>)[key] = value;";
|
|
@@ -2052,10 +2213,11 @@ var generatePatternPropertiesParser = (schema, typeName, useRefImports) => {
|
|
|
2052
2213
|
} else if (patternSchema === false) {
|
|
2053
2214
|
patternAssignment = "";
|
|
2054
2215
|
}
|
|
2055
|
-
const escapedPattern =
|
|
2216
|
+
const escapedPattern = escapeRegexPattern2(pattern);
|
|
2217
|
+
const notObjectBranch = strict ? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);` : ` return {} as unknown as ${typeName};`;
|
|
2056
2218
|
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
2057
2219
|
if (!isObject(input)) {
|
|
2058
|
-
|
|
2220
|
+
${notObjectBranch}
|
|
2059
2221
|
}
|
|
2060
2222
|
const result = {
|
|
2061
2223
|
...input,
|
|
@@ -2167,43 +2329,44 @@ var generateSchemaObjectParser = (typeName) => {
|
|
|
2167
2329
|
var selectParserStrategy = (schema, typeName, options) => {
|
|
2168
2330
|
const useRefImports = options?.useRefImports ?? false;
|
|
2169
2331
|
const logWarnings = options?.logWarnings ?? false;
|
|
2332
|
+
const strict = options?.strict ?? false;
|
|
2170
2333
|
if (typeName === "SchemaObject") {
|
|
2171
2334
|
return generateSchemaObjectParser(typeName);
|
|
2172
2335
|
}
|
|
2173
2336
|
const isObjectLikeSchema2 = isObjectSchema2(schema) || isSchemaObject3(schema) && (("patternProperties" in schema) || ("additionalProperties" in schema));
|
|
2174
2337
|
if (!isObjectLikeSchema2 && !isSchemaObject3(schema)) {
|
|
2175
|
-
return generateNonObjectParser(typeName, schema);
|
|
2338
|
+
return generateNonObjectParser(typeName, schema, strict);
|
|
2176
2339
|
}
|
|
2177
2340
|
if (hasProperties(schema) && isSchemaObject3(schema) && "patternProperties" in schema) {
|
|
2178
|
-
return generateCombinedObjectParser(schema, typeName, useRefImports, logWarnings);
|
|
2341
|
+
return generateCombinedObjectParser(schema, typeName, useRefImports, logWarnings, strict);
|
|
2179
2342
|
}
|
|
2180
2343
|
if (hasProperties(schema)) {
|
|
2181
|
-
return generateObjectParser(schema, typeName, useRefImports, logWarnings);
|
|
2344
|
+
return generateObjectParser(schema, typeName, useRefImports, logWarnings, strict);
|
|
2182
2345
|
}
|
|
2183
2346
|
if (isSchemaObject3(schema) && "if" in schema && "then" in schema && "else" in schema) {
|
|
2184
2347
|
return generateConditionalParser(schema, typeName);
|
|
2185
2348
|
}
|
|
2186
2349
|
const conditionalObjectSchema = getConditionalObjectSchema2(schema);
|
|
2187
2350
|
if (conditionalObjectSchema) {
|
|
2188
|
-
return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, logWarnings);
|
|
2351
|
+
return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, logWarnings, strict);
|
|
2189
2352
|
}
|
|
2190
2353
|
if (!isObjectLikeSchema2) {
|
|
2191
|
-
return generateNonObjectParser(typeName, schema);
|
|
2354
|
+
return generateNonObjectParser(typeName, schema, strict);
|
|
2192
2355
|
}
|
|
2193
2356
|
if ("patternProperties" in schema) {
|
|
2194
|
-
return generatePatternPropertiesParser(schema, typeName, useRefImports);
|
|
2357
|
+
return generatePatternPropertiesParser(schema, typeName, useRefImports, strict);
|
|
2195
2358
|
}
|
|
2196
2359
|
if ("additionalProperties" in schema && !hasProperties(schema)) {
|
|
2197
2360
|
const additionalProps = schema.additionalProperties;
|
|
2198
2361
|
if (additionalProps === true || additionalProps === false) {
|
|
2199
|
-
return generateEmptyObjectParser(typeName);
|
|
2362
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2200
2363
|
}
|
|
2201
|
-
return generateAdditionalPropertiesParser(schema, typeName, useRefImports);
|
|
2364
|
+
return generateAdditionalPropertiesParser(schema, typeName, useRefImports, strict);
|
|
2202
2365
|
}
|
|
2203
2366
|
if ("type" in schema && schema.type === "object" && !hasProperties(schema)) {
|
|
2204
|
-
return generateEmptyObjectParser(typeName);
|
|
2367
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2205
2368
|
}
|
|
2206
|
-
return generateEmptyObjectParser(typeName);
|
|
2369
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2207
2370
|
};
|
|
2208
2371
|
var generateParserFunction = (schema, typeName, options) => {
|
|
2209
2372
|
return selectParserStrategy(schema, typeName, options);
|
|
@@ -2273,7 +2436,8 @@ var generateFile = (schema, typeName, options) => {
|
|
|
2273
2436
|
}
|
|
2274
2437
|
const parserFunction = generateParserFunction(schema, typeName, {
|
|
2275
2438
|
useRefImports: true,
|
|
2276
|
-
...options?.logWarnings !== undefined ? { logWarnings: options.logWarnings } : {}
|
|
2439
|
+
...options?.logWarnings !== undefined ? { logWarnings: options.logWarnings } : {},
|
|
2440
|
+
...options?.strict !== undefined ? { strict: options.strict } : {}
|
|
2277
2441
|
});
|
|
2278
2442
|
const shapeValidator = generateShapeValidator(schema, typeName, true);
|
|
2279
2443
|
const combinedFunctions = `${shapeValidator}
|
|
@@ -2316,7 +2480,7 @@ var extractDynamicAnchorDefs = (schema) => {
|
|
|
2316
2480
|
}
|
|
2317
2481
|
return refs;
|
|
2318
2482
|
};
|
|
2319
|
-
var buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWarnings) => {
|
|
2483
|
+
var buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWarnings, strict) => {
|
|
2320
2484
|
rootSchema = upgradeDraft07Schema(rootSchema);
|
|
2321
2485
|
const files = [];
|
|
2322
2486
|
const processedRefs = new Set;
|
|
@@ -2328,7 +2492,8 @@ var buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWar
|
|
|
2328
2492
|
const rootContent = generateFile(extendedRootSchema, rootTypeName, {
|
|
2329
2493
|
typesOnly: typesOnly ?? false,
|
|
2330
2494
|
rootSchema,
|
|
2331
|
-
...logWarnings !== undefined ? { logWarnings } : {}
|
|
2495
|
+
...logWarnings !== undefined ? { logWarnings } : {},
|
|
2496
|
+
...strict !== undefined ? { strict } : {}
|
|
2332
2497
|
});
|
|
2333
2498
|
const rootFilename = rootTypeName.toLowerCase();
|
|
2334
2499
|
processedFilenames.add(rootFilename);
|
|
@@ -2359,7 +2524,8 @@ var buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWar
|
|
|
2359
2524
|
typesOnly: typesOnly ?? false,
|
|
2360
2525
|
selfRef: ref,
|
|
2361
2526
|
rootSchema,
|
|
2362
|
-
...logWarnings !== undefined ? { logWarnings } : {}
|
|
2527
|
+
...logWarnings !== undefined ? { logWarnings } : {},
|
|
2528
|
+
...strict !== undefined ? { strict } : {}
|
|
2363
2529
|
});
|
|
2364
2530
|
if (!processedFilenames.has(filename)) {
|
|
2365
2531
|
processedFilenames.add(filename);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/generate-parsers",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist"
|
|
27
27
|
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
28
31
|
"scripts": {
|
|
29
32
|
"build": "bun run build:code && bun run build:types",
|
|
30
33
|
"build:code": "bun build ./src/index.ts --outdir=dist --target=node",
|