@amritk/generate-parsers 0.1.0 → 0.3.0
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 +8 -1
- package/dist/generators/generate-files.d.ts +20 -2
- package/dist/generators/generate-parser-function.d.ts +6 -0
- package/dist/generators/generate-strict-assertion.d.ts +18 -0
- package/dist/helpers/collect-helpers.d.ts +16 -2
- package/dist/index.js +261 -50
- package/package.json +6 -2
- package/src/generators/build-schema.test.ts +830 -0
- package/src/generators/build-schema.ts +280 -0
- package/src/generators/generate-default-from-pattern.test.ts +81 -0
- package/src/generators/generate-default-from-pattern.ts +39 -0
- package/src/generators/generate-discriminated-union-validation.test.ts +84 -0
- package/src/generators/generate-discriminated-union-validation.ts +54 -0
- package/src/generators/generate-enum-check.test.ts +45 -0
- package/src/generators/generate-enum-check.ts +8 -0
- package/src/generators/generate-files.test.ts +435 -0
- package/src/generators/generate-files.ts +136 -0
- package/src/generators/generate-non-discriminated-union-validation.test.ts +74 -0
- package/src/generators/generate-non-discriminated-union-validation.ts +40 -0
- package/src/generators/generate-parser-function.test.ts +1798 -0
- package/src/generators/generate-parser-function.ts +1319 -0
- package/src/generators/generate-schema-checks.test.ts +322 -0
- package/src/generators/generate-schema-checks.ts +114 -0
- package/src/generators/generate-strict-assertion.ts +210 -0
- package/src/generators/generate-validation-expression.test.ts +794 -0
- package/src/generators/generate-validation-expression.ts +286 -0
- package/src/helpers/apply-schema-extensions.test.ts +194 -0
- package/src/helpers/apply-schema-extensions.ts +63 -0
- package/src/helpers/collect-helpers.test.ts +103 -0
- package/src/helpers/collect-helpers.ts +64 -0
- package/src/helpers/collect-imports.test.ts +587 -0
- package/src/helpers/collect-imports.ts +268 -0
- package/src/helpers/find-discriminator.test.ts +88 -0
- package/src/helpers/find-discriminator.ts +39 -0
- package/src/helpers/get-default-value.test.ts +135 -0
- package/src/helpers/get-default-value.ts +89 -0
- package/src/helpers/get-discriminator-value.test.ts +83 -0
- package/src/helpers/get-discriminator-value.ts +21 -0
- package/src/index.ts +1 -0
- package/src/types/schema-extensions.ts +35 -0
package/dist/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// src/generators/build-schema.ts
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, resolve as resolvePath } from "node:path";
|
|
5
|
+
|
|
1
6
|
// ../helpers/dist/build-dynamic-ref-map.js
|
|
2
7
|
var isSchemaObject = (schema) => {
|
|
3
8
|
return typeof schema === "object" && schema !== null && typeof schema !== "boolean";
|
|
@@ -792,24 +797,46 @@ var generateTypeDefinition = (schema, typeName) => {
|
|
|
792
797
|
};
|
|
793
798
|
|
|
794
799
|
// src/helpers/collect-helpers.ts
|
|
795
|
-
var
|
|
800
|
+
var PACKAGE_IMPORTS = {
|
|
801
|
+
"is-object": "import { isObject } from '@amritk/helpers/is-object';",
|
|
802
|
+
"validate-array": "import { validateArray } from '@amritk/helpers/validate-array';",
|
|
803
|
+
"validate-record": "import { validateRecord } from '@amritk/helpers/validate-record';",
|
|
804
|
+
"has-ref": "import { hasRef } from '@amritk/helpers/schema-guards';"
|
|
805
|
+
};
|
|
806
|
+
var EMBEDDED_IMPORTS = {
|
|
807
|
+
"is-object": "import { isObject } from './_helpers/is-object';",
|
|
808
|
+
"validate-array": "import { validateArray } from './_helpers/validate-array';",
|
|
809
|
+
"validate-record": "import { validateRecord } from './_helpers/validate-record';",
|
|
810
|
+
"has-ref": "import { hasRef } from './_helpers/has-ref';"
|
|
811
|
+
};
|
|
812
|
+
var collectHelpers = (parserFunction, mode) => {
|
|
796
813
|
const imports = [];
|
|
814
|
+
const used = new Set;
|
|
815
|
+
const table = mode === "embedded" ? EMBEDDED_IMPORTS : PACKAGE_IMPORTS;
|
|
797
816
|
if (parserFunction.includes("validateArray")) {
|
|
798
|
-
imports.push("
|
|
817
|
+
imports.push(table["validate-array"]);
|
|
818
|
+
used.add("validate-array");
|
|
799
819
|
}
|
|
800
820
|
if (parserFunction.includes("validateRecord")) {
|
|
801
|
-
imports.push("
|
|
821
|
+
imports.push(table["validate-record"]);
|
|
822
|
+
used.add("validate-record");
|
|
823
|
+
used.add("is-object");
|
|
802
824
|
}
|
|
803
825
|
if (parserFunction.includes("isObject")) {
|
|
804
|
-
imports.push("
|
|
826
|
+
imports.push(table["is-object"]);
|
|
827
|
+
used.add("is-object");
|
|
805
828
|
}
|
|
806
829
|
if (parserFunction.includes("hasRef(")) {
|
|
807
|
-
imports.push("
|
|
830
|
+
imports.push(table["has-ref"]);
|
|
831
|
+
used.add("has-ref");
|
|
808
832
|
}
|
|
809
|
-
return imports;
|
|
833
|
+
return { imports, used };
|
|
810
834
|
};
|
|
811
835
|
|
|
812
836
|
// ../helpers/dist/schema-guards.js
|
|
837
|
+
var hasRef = (value) => {
|
|
838
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "$ref" in value && typeof value.$ref === "string";
|
|
839
|
+
};
|
|
813
840
|
var isSchemaObject3 = (schema) => {
|
|
814
841
|
return typeof schema === "object" && schema !== null && typeof schema !== "boolean";
|
|
815
842
|
};
|
|
@@ -891,9 +918,6 @@ var hasMinProperties = (schema) => {
|
|
|
891
918
|
var hasMaxProperties = (schema) => {
|
|
892
919
|
return isSchemaObject3(schema) && "maxProperties" in schema && typeof schema.maxProperties === "number";
|
|
893
920
|
};
|
|
894
|
-
var hasRef = (schema) => {
|
|
895
|
-
return isSchemaObject3(schema) && "$ref" in schema && typeof schema.$ref === "string";
|
|
896
|
-
};
|
|
897
921
|
|
|
898
922
|
// src/helpers/collect-imports.ts
|
|
899
923
|
var getImportPathForFilename = (filename) => `./${filename}`;
|
|
@@ -1142,6 +1166,119 @@ var getDefaultValue = (schema) => {
|
|
|
1142
1166
|
}
|
|
1143
1167
|
};
|
|
1144
1168
|
|
|
1169
|
+
// src/generators/generate-strict-assertion.ts
|
|
1170
|
+
var wrongTypeCondition = (accessor, type) => {
|
|
1171
|
+
switch (type) {
|
|
1172
|
+
case "string":
|
|
1173
|
+
return `typeof ${accessor} !== "string"`;
|
|
1174
|
+
case "number":
|
|
1175
|
+
case "integer":
|
|
1176
|
+
return `typeof ${accessor} !== "number"`;
|
|
1177
|
+
case "boolean":
|
|
1178
|
+
return `typeof ${accessor} !== "boolean"`;
|
|
1179
|
+
case "array":
|
|
1180
|
+
return `!Array.isArray(${accessor})`;
|
|
1181
|
+
case "object":
|
|
1182
|
+
return `!isObject(${accessor})`;
|
|
1183
|
+
default:
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
var typeLabel = (type) => type === "integer" ? "number" : type;
|
|
1188
|
+
var escapeRegexPattern = (pattern) => {
|
|
1189
|
+
return pattern.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
|
|
1190
|
+
};
|
|
1191
|
+
var generateConstraintChecks = (acc, propSchema, typeName, key) => {
|
|
1192
|
+
if (!isSchemaObject3(propSchema) || !hasType(propSchema))
|
|
1193
|
+
return [];
|
|
1194
|
+
const t = propSchema.type;
|
|
1195
|
+
const lines = [];
|
|
1196
|
+
if (t === "string") {
|
|
1197
|
+
if (hasPattern(propSchema)) {
|
|
1198
|
+
const pattern = escapeRegexPattern(propSchema.pattern);
|
|
1199
|
+
lines.push(` if (typeof ${acc} === "string" && !/${pattern}/.test(${acc})) throw new Error('[${typeName}] field "${key}" must match pattern ${propSchema.pattern}');`);
|
|
1200
|
+
}
|
|
1201
|
+
if (hasMinLength(propSchema)) {
|
|
1202
|
+
lines.push(` if (typeof ${acc} === "string" && ${acc}.length < ${propSchema.minLength}) throw new Error('[${typeName}] field "${key}" must have at least ${propSchema.minLength} characters');`);
|
|
1203
|
+
}
|
|
1204
|
+
if (hasMaxLength(propSchema)) {
|
|
1205
|
+
lines.push(` if (typeof ${acc} === "string" && ${acc}.length > ${propSchema.maxLength}) throw new Error('[${typeName}] field "${key}" must have at most ${propSchema.maxLength} characters');`);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if (t === "number" || t === "integer") {
|
|
1209
|
+
if (hasMinimum(propSchema)) {
|
|
1210
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} < ${propSchema.minimum}) throw new Error('[${typeName}] field "${key}" must be >= ${propSchema.minimum}');`);
|
|
1211
|
+
}
|
|
1212
|
+
if (hasMaximum(propSchema)) {
|
|
1213
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} > ${propSchema.maximum}) throw new Error('[${typeName}] field "${key}" must be <= ${propSchema.maximum}');`);
|
|
1214
|
+
}
|
|
1215
|
+
if (hasExclusiveMinimum(propSchema)) {
|
|
1216
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} <= ${propSchema.exclusiveMinimum}) throw new Error('[${typeName}] field "${key}" must be > ${propSchema.exclusiveMinimum}');`);
|
|
1217
|
+
}
|
|
1218
|
+
if (hasExclusiveMaximum(propSchema)) {
|
|
1219
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} >= ${propSchema.exclusiveMaximum}) throw new Error('[${typeName}] field "${key}" must be < ${propSchema.exclusiveMaximum}');`);
|
|
1220
|
+
}
|
|
1221
|
+
if (hasMultipleOf(propSchema)) {
|
|
1222
|
+
lines.push(` if (typeof ${acc} === "number" && ${acc} % ${propSchema.multipleOf} !== 0) throw new Error('[${typeName}] field "${key}" must be a multiple of ${propSchema.multipleOf}');`);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return lines;
|
|
1226
|
+
};
|
|
1227
|
+
var generatePropertyAssertion = (key, propSchema, isRequired, typeName) => {
|
|
1228
|
+
const acc = safeAccessor("input", key);
|
|
1229
|
+
const lines = [];
|
|
1230
|
+
if (isRequired) {
|
|
1231
|
+
lines.push(` if (!(${JSON.stringify(key)} in input)) throw new Error('[${typeName}] missing required property "${key}"');`);
|
|
1232
|
+
}
|
|
1233
|
+
if (!isSchemaObject3(propSchema))
|
|
1234
|
+
return lines;
|
|
1235
|
+
if (hasRef(propSchema))
|
|
1236
|
+
return lines;
|
|
1237
|
+
if (hasEnum(propSchema)) {
|
|
1238
|
+
const allowed = JSON.stringify(propSchema.enum);
|
|
1239
|
+
const label = propSchema.enum.map((v) => JSON.stringify(v)).join(", ");
|
|
1240
|
+
if (isRequired) {
|
|
1241
|
+
lines.push(` if (!(${allowed} as readonly unknown[]).includes(${acc})) throw new Error('[${typeName}] field "${key}" must be one of: ${label}');`);
|
|
1242
|
+
} else {
|
|
1243
|
+
lines.push(` if (${acc} !== undefined && !(${allowed} as readonly unknown[]).includes(${acc})) throw new Error('[${typeName}] field "${key}" must be one of: ${label}');`);
|
|
1244
|
+
}
|
|
1245
|
+
return lines;
|
|
1246
|
+
}
|
|
1247
|
+
if (hasType(propSchema)) {
|
|
1248
|
+
const t = propSchema.type;
|
|
1249
|
+
const wrongType = wrongTypeCondition(acc, t);
|
|
1250
|
+
if (wrongType) {
|
|
1251
|
+
if (isRequired) {
|
|
1252
|
+
lines.push(` if (${wrongType}) throw new Error(\`[${typeName}] field "${key}" expected ${typeLabel(t)}, got \${typeof ${acc}}\`);`);
|
|
1253
|
+
} else {
|
|
1254
|
+
lines.push(` if (${acc} !== undefined && (${wrongType})) throw new Error(\`[${typeName}] field "${key}" expected ${typeLabel(t)}, got \${typeof ${acc}}\`);`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
lines.push(...generateConstraintChecks(acc, propSchema, typeName, key));
|
|
1258
|
+
}
|
|
1259
|
+
return lines;
|
|
1260
|
+
};
|
|
1261
|
+
var generateObjectStrictAssertion = (schema, typeName) => {
|
|
1262
|
+
const lines = [];
|
|
1263
|
+
lines.push(` if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);`);
|
|
1264
|
+
if (!hasProperties(schema) || !isSchemaObject3(schema))
|
|
1265
|
+
return lines;
|
|
1266
|
+
const required = new Set(hasRequired(schema) ? schema.required : []);
|
|
1267
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
1268
|
+
lines.push(...generatePropertyAssertion(key, propSchema, required.has(key), typeName));
|
|
1269
|
+
}
|
|
1270
|
+
return lines;
|
|
1271
|
+
};
|
|
1272
|
+
var generateScalarStrictAssertion = (schema, typeName) => {
|
|
1273
|
+
if (!isSchemaObject3(schema) || !hasType(schema))
|
|
1274
|
+
return null;
|
|
1275
|
+
const t = schema.type;
|
|
1276
|
+
const wrongType = wrongTypeCondition("input", t);
|
|
1277
|
+
if (!wrongType)
|
|
1278
|
+
return null;
|
|
1279
|
+
return ` if (${wrongType}) throw new Error(\`[${typeName}] expected ${typeLabel(t)}, got \${input === null ? "null" : typeof input}\`);`;
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1145
1282
|
// src/helpers/find-discriminator.ts
|
|
1146
1283
|
var findDiscriminator = (schemas) => {
|
|
1147
1284
|
const propertyValues = new Map;
|
|
@@ -1712,11 +1849,22 @@ var generateFallbackObject = (schema, useRefImports, typeName) => {
|
|
|
1712
1849
|
}`;
|
|
1713
1850
|
return result;
|
|
1714
1851
|
};
|
|
1715
|
-
var generateNonObjectParser = (typeName, schema) => {
|
|
1852
|
+
var generateNonObjectParser = (typeName, schema, strict) => {
|
|
1716
1853
|
const functionName = generateParserName(typeName);
|
|
1717
1854
|
if (!isSchemaObject3(schema) || !hasType(schema)) {
|
|
1718
1855
|
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
1719
1856
|
}
|
|
1857
|
+
if (strict) {
|
|
1858
|
+
const assertion = generateScalarStrictAssertion(schema, typeName);
|
|
1859
|
+
if (assertion === null) {
|
|
1860
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
1861
|
+
}
|
|
1862
|
+
const returnExpr = schema.type === "array" ? `[...(input as readonly unknown[])] as ${typeName}` : `input as ${typeName}`;
|
|
1863
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1864
|
+
${assertion}
|
|
1865
|
+
return ${returnExpr};
|
|
1866
|
+
};`;
|
|
1867
|
+
}
|
|
1720
1868
|
switch (schema.type) {
|
|
1721
1869
|
case "string":
|
|
1722
1870
|
return `export const ${functionName} = (input: unknown): ${typeName} => typeof input === "string" ? input as ${typeName} : "" as ${typeName};`;
|
|
@@ -1731,8 +1879,14 @@ var generateNonObjectParser = (typeName, schema) => {
|
|
|
1731
1879
|
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
1732
1880
|
}
|
|
1733
1881
|
};
|
|
1734
|
-
var generateEmptyObjectParser = (typeName) => {
|
|
1882
|
+
var generateEmptyObjectParser = (typeName, strict) => {
|
|
1735
1883
|
const functionName = generateParserName(typeName);
|
|
1884
|
+
if (strict) {
|
|
1885
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1886
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
1887
|
+
return { ...input } as ${typeName};
|
|
1888
|
+
};`;
|
|
1889
|
+
}
|
|
1736
1890
|
return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? { ...input } as ${typeName} : {} as ${typeName};`;
|
|
1737
1891
|
};
|
|
1738
1892
|
var toVarName = (key) => {
|
|
@@ -1761,7 +1915,7 @@ var generatePropertyTypeCheck = (varName, schema, useRefImports) => {
|
|
|
1761
1915
|
case "string": {
|
|
1762
1916
|
checks.push(`typeof ${varName} === "string"`);
|
|
1763
1917
|
if (hasPattern(schema))
|
|
1764
|
-
checks.push(`/${
|
|
1918
|
+
checks.push(`/${escapeRegexPattern2(schema.pattern)}/.test(${varName})`);
|
|
1765
1919
|
if (hasMinLength(schema))
|
|
1766
1920
|
checks.push(`${varName}.length >= ${schema.minLength}`);
|
|
1767
1921
|
if (hasMaxLength(schema))
|
|
@@ -1814,16 +1968,28 @@ var generatePropertyTypeCheck = (varName, schema, useRefImports) => {
|
|
|
1814
1968
|
var shouldCacheVariable = (propSchema, _canFastPath, _useRefImports) => {
|
|
1815
1969
|
return isSchemaObject3(propSchema);
|
|
1816
1970
|
};
|
|
1817
|
-
var generateObjectParser = (schema, typeName, useRefImports, logWarnings) => {
|
|
1971
|
+
var generateObjectParser = (schema, typeName, useRefImports, logWarnings, strict) => {
|
|
1818
1972
|
const functionName = generateParserName(typeName);
|
|
1819
1973
|
if (!hasProperties(schema)) {
|
|
1974
|
+
if (strict) {
|
|
1975
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1976
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
1977
|
+
return input as ${typeName};
|
|
1978
|
+
};`;
|
|
1979
|
+
}
|
|
1820
1980
|
return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? input as ${typeName} : {} as ${typeName};`;
|
|
1821
1981
|
}
|
|
1822
1982
|
const fallbackObject = generateFallbackObject(schema, useRefImports, typeName);
|
|
1823
1983
|
const properties = Object.entries(schema.properties);
|
|
1824
1984
|
const lines = [];
|
|
1825
1985
|
lines.push(`export const ${functionName} = (input: unknown): ${typeName} => {`);
|
|
1826
|
-
|
|
1986
|
+
if (strict) {
|
|
1987
|
+
for (const assertionLine of generateObjectStrictAssertion(schema, typeName)) {
|
|
1988
|
+
lines.push(assertionLine);
|
|
1989
|
+
}
|
|
1990
|
+
} else {
|
|
1991
|
+
lines.push(` if (!isObject(input)) return ${fallbackObject};`);
|
|
1992
|
+
}
|
|
1827
1993
|
const propInfo = [];
|
|
1828
1994
|
const hasAllOfRefParsers = useRefImports && isSchemaObject3(schema) && hasAllOf(schema) && schema.allOf.some((entry) => isSchemaObject3(entry) && hasRef(entry));
|
|
1829
1995
|
let canFastPath = !hasAllOfRefParsers;
|
|
@@ -1946,7 +2112,7 @@ var generateObjectParser = (schema, typeName, useRefImports, logWarnings) => {
|
|
|
1946
2112
|
}
|
|
1947
2113
|
return result;
|
|
1948
2114
|
};
|
|
1949
|
-
var generateCombinedObjectParser = (schema, typeName, useRefImports, logWarnings) => {
|
|
2115
|
+
var generateCombinedObjectParser = (schema, typeName, useRefImports, logWarnings, strict) => {
|
|
1950
2116
|
const functionName = generateParserName(typeName);
|
|
1951
2117
|
const entries = generatePropertyEntries(schema, useRefImports);
|
|
1952
2118
|
const propertyLines = entries.map((entry) => {
|
|
@@ -1956,19 +2122,19 @@ var generateCombinedObjectParser = (schema, typeName, useRefImports, logWarnings
|
|
|
1956
2122
|
return ` ${safeKey2(entry.key)}: ${entry.value},`;
|
|
1957
2123
|
});
|
|
1958
2124
|
if (!isSchemaObject3(schema) || !("patternProperties" in schema)) {
|
|
1959
|
-
return generateObjectParser(schema, typeName, useRefImports, logWarnings);
|
|
2125
|
+
return generateObjectParser(schema, typeName, useRefImports, logWarnings, strict);
|
|
1960
2126
|
}
|
|
1961
2127
|
const patternProps = schema.patternProperties;
|
|
1962
2128
|
const patterns = Object.entries(patternProps);
|
|
1963
2129
|
const refPattern = patterns.find(([, ps]) => isSchemaObject3(ps) && hasRef(ps));
|
|
1964
2130
|
if (!refPattern || !useRefImports) {
|
|
1965
|
-
return generateObjectParser(schema, typeName, useRefImports, logWarnings);
|
|
2131
|
+
return generateObjectParser(schema, typeName, useRefImports, logWarnings, strict);
|
|
1966
2132
|
}
|
|
1967
2133
|
const [pattern, patternSchema] = refPattern;
|
|
1968
2134
|
const ref = patternSchema.$ref;
|
|
1969
2135
|
const parserName = generateParserName(refToName(ref));
|
|
1970
2136
|
const assignmentCode = `(result as Record<string, unknown>)[key] = ${parserName}(value);`;
|
|
1971
|
-
const escapedPattern =
|
|
2137
|
+
const escapedPattern = escapeRegexPattern2(pattern);
|
|
1972
2138
|
const inputSpread = " ...input,";
|
|
1973
2139
|
let objectProperties = inputSpread;
|
|
1974
2140
|
if (propertyLines.length > 0) {
|
|
@@ -1977,9 +2143,10 @@ var generateCombinedObjectParser = (schema, typeName, useRefImports, logWarnings
|
|
|
1977
2143
|
` + line;
|
|
1978
2144
|
}
|
|
1979
2145
|
}
|
|
2146
|
+
const notObjectBranch = strict ? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);` : ` return {} as unknown as ${typeName};`;
|
|
1980
2147
|
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1981
2148
|
if (!isObject(input)) {
|
|
1982
|
-
|
|
2149
|
+
${notObjectBranch}
|
|
1983
2150
|
}
|
|
1984
2151
|
const result = {
|
|
1985
2152
|
${objectProperties}
|
|
@@ -1993,23 +2160,41 @@ ${objectProperties}
|
|
|
1993
2160
|
return result;
|
|
1994
2161
|
};`;
|
|
1995
2162
|
};
|
|
1996
|
-
var generateAdditionalPropertiesParser = (schema, typeName, useRefImports) => {
|
|
2163
|
+
var generateAdditionalPropertiesParser = (schema, typeName, useRefImports, strict) => {
|
|
1997
2164
|
const functionName = generateParserName(typeName);
|
|
1998
2165
|
const additionalProps = schema.additionalProperties;
|
|
1999
2166
|
if (!additionalProps) {
|
|
2000
|
-
return generateEmptyObjectParser(typeName);
|
|
2167
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2001
2168
|
}
|
|
2002
2169
|
if (useRefImports && isSchemaObject3(additionalProps) && hasRef(additionalProps)) {
|
|
2003
2170
|
const ref = additionalProps.$ref;
|
|
2004
2171
|
const parserName = generateParserName(refToName(ref));
|
|
2172
|
+
if (strict) {
|
|
2173
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
2174
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
2175
|
+
return validateRecord(input, ${parserName}) as ${typeName};
|
|
2176
|
+
};`;
|
|
2177
|
+
}
|
|
2005
2178
|
return `export const ${functionName} = (input: unknown): ${typeName} => validateRecord(input, ${parserName}) as ${typeName};`;
|
|
2006
2179
|
}
|
|
2007
2180
|
if (isSchemaObject3(additionalProps) && hasType(additionalProps)) {
|
|
2008
2181
|
const inlineParser = generateInlineValueParser(additionalProps);
|
|
2009
2182
|
if (inlineParser) {
|
|
2183
|
+
if (strict) {
|
|
2184
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
2185
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
2186
|
+
return validateRecord(input, ${inlineParser}) as ${typeName};
|
|
2187
|
+
};`;
|
|
2188
|
+
}
|
|
2010
2189
|
return `export const ${functionName} = (input: unknown): ${typeName} => validateRecord(input, ${inlineParser}) as ${typeName};`;
|
|
2011
2190
|
}
|
|
2012
2191
|
}
|
|
2192
|
+
if (strict) {
|
|
2193
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
2194
|
+
if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);
|
|
2195
|
+
return { ...input } as ${typeName};
|
|
2196
|
+
};`;
|
|
2197
|
+
}
|
|
2013
2198
|
return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? { ...input } as ${typeName} : {};`;
|
|
2014
2199
|
};
|
|
2015
2200
|
var generateInlineValueParser = (schema) => {
|
|
@@ -2030,18 +2215,18 @@ var generateInlineValueParser = (schema) => {
|
|
|
2030
2215
|
return null;
|
|
2031
2216
|
}
|
|
2032
2217
|
};
|
|
2033
|
-
var
|
|
2218
|
+
var escapeRegexPattern2 = (pattern) => {
|
|
2034
2219
|
return pattern.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
|
|
2035
2220
|
};
|
|
2036
|
-
var generatePatternPropertiesParser = (schema, typeName, useRefImports) => {
|
|
2221
|
+
var generatePatternPropertiesParser = (schema, typeName, useRefImports, strict) => {
|
|
2037
2222
|
const functionName = generateParserName(typeName);
|
|
2038
2223
|
if (!("patternProperties" in schema) || typeof schema.patternProperties !== "object") {
|
|
2039
|
-
return generateEmptyObjectParser(typeName);
|
|
2224
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2040
2225
|
}
|
|
2041
2226
|
const patternProps = schema.patternProperties;
|
|
2042
2227
|
const patterns = Object.entries(patternProps);
|
|
2043
2228
|
if (patterns.length === 0) {
|
|
2044
|
-
return generateEmptyObjectParser(typeName);
|
|
2229
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2045
2230
|
}
|
|
2046
2231
|
const [pattern, patternSchema] = patterns[0];
|
|
2047
2232
|
let patternAssignment = "(result as Record<string, unknown>)[key] = value;";
|
|
@@ -2052,10 +2237,11 @@ var generatePatternPropertiesParser = (schema, typeName, useRefImports) => {
|
|
|
2052
2237
|
} else if (patternSchema === false) {
|
|
2053
2238
|
patternAssignment = "";
|
|
2054
2239
|
}
|
|
2055
|
-
const escapedPattern =
|
|
2240
|
+
const escapedPattern = escapeRegexPattern2(pattern);
|
|
2241
|
+
const notObjectBranch = strict ? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);` : ` return {} as unknown as ${typeName};`;
|
|
2056
2242
|
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
2057
2243
|
if (!isObject(input)) {
|
|
2058
|
-
|
|
2244
|
+
${notObjectBranch}
|
|
2059
2245
|
}
|
|
2060
2246
|
const result = {
|
|
2061
2247
|
...input,
|
|
@@ -2167,43 +2353,44 @@ var generateSchemaObjectParser = (typeName) => {
|
|
|
2167
2353
|
var selectParserStrategy = (schema, typeName, options) => {
|
|
2168
2354
|
const useRefImports = options?.useRefImports ?? false;
|
|
2169
2355
|
const logWarnings = options?.logWarnings ?? false;
|
|
2356
|
+
const strict = options?.strict ?? false;
|
|
2170
2357
|
if (typeName === "SchemaObject") {
|
|
2171
2358
|
return generateSchemaObjectParser(typeName);
|
|
2172
2359
|
}
|
|
2173
2360
|
const isObjectLikeSchema2 = isObjectSchema2(schema) || isSchemaObject3(schema) && (("patternProperties" in schema) || ("additionalProperties" in schema));
|
|
2174
2361
|
if (!isObjectLikeSchema2 && !isSchemaObject3(schema)) {
|
|
2175
|
-
return generateNonObjectParser(typeName, schema);
|
|
2362
|
+
return generateNonObjectParser(typeName, schema, strict);
|
|
2176
2363
|
}
|
|
2177
2364
|
if (hasProperties(schema) && isSchemaObject3(schema) && "patternProperties" in schema) {
|
|
2178
|
-
return generateCombinedObjectParser(schema, typeName, useRefImports, logWarnings);
|
|
2365
|
+
return generateCombinedObjectParser(schema, typeName, useRefImports, logWarnings, strict);
|
|
2179
2366
|
}
|
|
2180
2367
|
if (hasProperties(schema)) {
|
|
2181
|
-
return generateObjectParser(schema, typeName, useRefImports, logWarnings);
|
|
2368
|
+
return generateObjectParser(schema, typeName, useRefImports, logWarnings, strict);
|
|
2182
2369
|
}
|
|
2183
2370
|
if (isSchemaObject3(schema) && "if" in schema && "then" in schema && "else" in schema) {
|
|
2184
2371
|
return generateConditionalParser(schema, typeName);
|
|
2185
2372
|
}
|
|
2186
2373
|
const conditionalObjectSchema = getConditionalObjectSchema2(schema);
|
|
2187
2374
|
if (conditionalObjectSchema) {
|
|
2188
|
-
return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, logWarnings);
|
|
2375
|
+
return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, logWarnings, strict);
|
|
2189
2376
|
}
|
|
2190
2377
|
if (!isObjectLikeSchema2) {
|
|
2191
|
-
return generateNonObjectParser(typeName, schema);
|
|
2378
|
+
return generateNonObjectParser(typeName, schema, strict);
|
|
2192
2379
|
}
|
|
2193
2380
|
if ("patternProperties" in schema) {
|
|
2194
|
-
return generatePatternPropertiesParser(schema, typeName, useRefImports);
|
|
2381
|
+
return generatePatternPropertiesParser(schema, typeName, useRefImports, strict);
|
|
2195
2382
|
}
|
|
2196
2383
|
if ("additionalProperties" in schema && !hasProperties(schema)) {
|
|
2197
2384
|
const additionalProps = schema.additionalProperties;
|
|
2198
2385
|
if (additionalProps === true || additionalProps === false) {
|
|
2199
|
-
return generateEmptyObjectParser(typeName);
|
|
2386
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2200
2387
|
}
|
|
2201
|
-
return generateAdditionalPropertiesParser(schema, typeName, useRefImports);
|
|
2388
|
+
return generateAdditionalPropertiesParser(schema, typeName, useRefImports, strict);
|
|
2202
2389
|
}
|
|
2203
2390
|
if ("type" in schema && schema.type === "object" && !hasProperties(schema)) {
|
|
2204
|
-
return generateEmptyObjectParser(typeName);
|
|
2391
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2205
2392
|
}
|
|
2206
|
-
return generateEmptyObjectParser(typeName);
|
|
2393
|
+
return generateEmptyObjectParser(typeName, strict);
|
|
2207
2394
|
};
|
|
2208
2395
|
var generateParserFunction = (schema, typeName, options) => {
|
|
2209
2396
|
return selectParserStrategy(schema, typeName, options);
|
|
@@ -2255,6 +2442,7 @@ var generateFile = (schema, typeName, options) => {
|
|
|
2255
2442
|
const typesOnly = options?.typesOnly === true;
|
|
2256
2443
|
const selfRef = options?.selfRef;
|
|
2257
2444
|
const rootSchema = options?.rootSchema;
|
|
2445
|
+
const helpersMode = options?.helpersMode ?? "package";
|
|
2258
2446
|
const typeDefinition = generateTypeDefinition(schema, typeName);
|
|
2259
2447
|
if (typesOnly) {
|
|
2260
2448
|
const imports2 = collectImports(schema, { typesOnly: true, selfRef, rootSchema });
|
|
@@ -2269,17 +2457,19 @@ var generateFile = (schema, typeName, options) => {
|
|
|
2269
2457
|
|
|
2270
2458
|
`;
|
|
2271
2459
|
}
|
|
2272
|
-
return result2 + typeDefinition;
|
|
2460
|
+
return { content: result2 + typeDefinition, usedHelpers: new Set };
|
|
2273
2461
|
}
|
|
2274
2462
|
const parserFunction = generateParserFunction(schema, typeName, {
|
|
2275
2463
|
useRefImports: true,
|
|
2276
|
-
...options?.logWarnings !== undefined ? { logWarnings: options.logWarnings } : {}
|
|
2464
|
+
...options?.logWarnings !== undefined ? { logWarnings: options.logWarnings } : {},
|
|
2465
|
+
...options?.strict !== undefined ? { strict: options.strict } : {}
|
|
2277
2466
|
});
|
|
2278
2467
|
const shapeValidator = generateShapeValidator(schema, typeName, true);
|
|
2279
2468
|
const combinedFunctions = `${shapeValidator}
|
|
2280
2469
|
|
|
2281
2470
|
${parserFunction}`;
|
|
2282
|
-
const
|
|
2471
|
+
const helpers = collectHelpers(combinedFunctions, helpersMode);
|
|
2472
|
+
const imports = [...collectImports(schema, { selfRef, rootSchema }), ...helpers.imports];
|
|
2283
2473
|
let result = "";
|
|
2284
2474
|
if (imports.length > 0) {
|
|
2285
2475
|
result += imports[0];
|
|
@@ -2291,12 +2481,18 @@ ${parserFunction}`;
|
|
|
2291
2481
|
|
|
2292
2482
|
`;
|
|
2293
2483
|
}
|
|
2294
|
-
return result + typeDefinition + `
|
|
2484
|
+
return { content: result + typeDefinition + `
|
|
2295
2485
|
|
|
2296
|
-
` + combinedFunctions;
|
|
2486
|
+
` + combinedFunctions, usedHelpers: helpers.used };
|
|
2297
2487
|
};
|
|
2298
2488
|
|
|
2299
2489
|
// src/generators/build-schema.ts
|
|
2490
|
+
var readHelperSource = async (helper) => {
|
|
2491
|
+
const require2 = createRequire(import.meta.url);
|
|
2492
|
+
const helpersPkgPath = require2.resolve("@amritk/helpers/package.json");
|
|
2493
|
+
const helpersRoot = dirname(helpersPkgPath);
|
|
2494
|
+
return readFile(resolvePath(helpersRoot, "src", `${helper}.ts`), "utf-8");
|
|
2495
|
+
};
|
|
2300
2496
|
var extractDynamicAnchorDefs = (schema) => {
|
|
2301
2497
|
const refs = [];
|
|
2302
2498
|
if (typeof schema !== "object" || schema === null) {
|
|
@@ -2316,26 +2512,31 @@ var extractDynamicAnchorDefs = (schema) => {
|
|
|
2316
2512
|
}
|
|
2317
2513
|
return refs;
|
|
2318
2514
|
};
|
|
2319
|
-
var buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWarnings) => {
|
|
2515
|
+
var buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWarnings, strict, helpersMode = "package") => {
|
|
2320
2516
|
rootSchema = upgradeDraft07Schema(rootSchema);
|
|
2321
2517
|
const files = [];
|
|
2322
2518
|
const processedRefs = new Set;
|
|
2323
2519
|
const processedFilenames = new Set;
|
|
2324
2520
|
const refsToProcess = [];
|
|
2521
|
+
const usedHelpers = new Set;
|
|
2325
2522
|
const dynamicRefMap = buildDynamicRefMap(rootSchema);
|
|
2326
2523
|
const processedRootSchema = resolveDynamicRefs(rootSchema, dynamicRefMap);
|
|
2327
2524
|
const extendedRootSchema = extensions ? applySchemaExtensions(processedRootSchema, rootTypeName.toLowerCase(), extensions) : processedRootSchema;
|
|
2328
|
-
const
|
|
2525
|
+
const rootResult = generateFile(extendedRootSchema, rootTypeName, {
|
|
2329
2526
|
typesOnly: typesOnly ?? false,
|
|
2330
2527
|
rootSchema,
|
|
2331
|
-
|
|
2528
|
+
helpersMode,
|
|
2529
|
+
...logWarnings !== undefined ? { logWarnings } : {},
|
|
2530
|
+
...strict !== undefined ? { strict } : {}
|
|
2332
2531
|
});
|
|
2333
2532
|
const rootFilename = rootTypeName.toLowerCase();
|
|
2334
2533
|
processedFilenames.add(rootFilename);
|
|
2335
2534
|
files.push({
|
|
2336
2535
|
filename: `${rootFilename}.ts`,
|
|
2337
|
-
content:
|
|
2536
|
+
content: rootResult.content
|
|
2338
2537
|
});
|
|
2538
|
+
for (const helper of rootResult.usedHelpers)
|
|
2539
|
+
usedHelpers.add(helper);
|
|
2339
2540
|
const rootRefs = extractRefs(rootSchema);
|
|
2340
2541
|
refsToProcess.push(...rootRefs);
|
|
2341
2542
|
const dynamicAnchorRefs = extractDynamicAnchorDefs(rootSchema);
|
|
@@ -2355,19 +2556,23 @@ var buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWar
|
|
|
2355
2556
|
const filename = refToFilename(ref);
|
|
2356
2557
|
const processedSchema = resolveDynamicRefs(resolvedSchema, dynamicRefMap);
|
|
2357
2558
|
const extendedSchema = extensions ? applySchemaExtensions(processedSchema, filename, extensions) : processedSchema;
|
|
2358
|
-
const
|
|
2559
|
+
const fileResult = generateFile(extendedSchema, typeName, {
|
|
2359
2560
|
typesOnly: typesOnly ?? false,
|
|
2360
2561
|
selfRef: ref,
|
|
2361
2562
|
rootSchema,
|
|
2362
|
-
|
|
2563
|
+
helpersMode,
|
|
2564
|
+
...logWarnings !== undefined ? { logWarnings } : {},
|
|
2565
|
+
...strict !== undefined ? { strict } : {}
|
|
2363
2566
|
});
|
|
2364
2567
|
if (!processedFilenames.has(filename)) {
|
|
2365
2568
|
processedFilenames.add(filename);
|
|
2366
2569
|
files.push({
|
|
2367
2570
|
filename: `${filename}.ts`,
|
|
2368
|
-
content
|
|
2571
|
+
content: fileResult.content
|
|
2369
2572
|
});
|
|
2370
2573
|
}
|
|
2574
|
+
for (const helper of fileResult.usedHelpers)
|
|
2575
|
+
usedHelpers.add(helper);
|
|
2371
2576
|
const nestedRefs = extractRefs(resolvedSchema);
|
|
2372
2577
|
for (const nestedRef of nestedRefs) {
|
|
2373
2578
|
if (!processedRefs.has(nestedRef)) {
|
|
@@ -2375,9 +2580,15 @@ var buildSchema = async (rootSchema, rootTypeName, extensions, typesOnly, logWar
|
|
|
2375
2580
|
}
|
|
2376
2581
|
}
|
|
2377
2582
|
}
|
|
2583
|
+
if (helpersMode === "embedded" && !typesOnly) {
|
|
2584
|
+
for (const helper of usedHelpers) {
|
|
2585
|
+
const source = await readHelperSource(helper);
|
|
2586
|
+
files.push({ filename: `_helpers/${helper}.ts`, content: source });
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2378
2589
|
const TYPE_EXPORT_RE = /^export type (\w+)/gm;
|
|
2379
2590
|
const CONST_EXPORT_RE = /^export const (\w+)/gm;
|
|
2380
|
-
const sortedFiles = [...files].sort((a, b) => a.filename.localeCompare(b.filename));
|
|
2591
|
+
const sortedFiles = [...files].filter((f) => !f.filename.startsWith("_helpers/")).sort((a, b) => a.filename.localeCompare(b.filename));
|
|
2381
2592
|
let indexContent = "";
|
|
2382
2593
|
for (const file of sortedFiles) {
|
|
2383
2594
|
const moduleName = file.filename.replace(/\.ts$/, "");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/generate-parsers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,8 +23,12 @@
|
|
|
23
23
|
"url": "https://github.com/amritk/mjst/issues"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
|
-
"dist"
|
|
26
|
+
"dist",
|
|
27
|
+
"src"
|
|
27
28
|
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
28
32
|
"scripts": {
|
|
29
33
|
"build": "bun run build:code && bun run build:types",
|
|
30
34
|
"build:code": "bun build ./src/index.ts --outdir=dist --target=node",
|