@amritk/generate-parsers 0.16.1 → 0.16.3
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/LICENSE +21 -0
- package/README.md +4 -4
- package/dist/generators/generate-enum-check.js +16 -7
- package/dist/generators/generate-parser-function.js +54 -3
- package/dist/generators/generate-strict-assertion.js +41 -20
- package/dist/generators/generate-validation-expression.js +9 -2
- package/dist/helpers/get-default-value.js +8 -0
- package/package.json +5 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 amritk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -173,10 +173,10 @@ x64), parsing valid input at steady state:
|
|
|
173
173
|
|
|
174
174
|
| case | mode | mjst (generated) | zod | typebox |
|
|
175
175
|
|:--|:--|--:|--:|--:|
|
|
176
|
-
| user (4 fields) | parseSafe | **~
|
|
177
|
-
| order (nested + array) | parseSafe | **~
|
|
178
|
-
| user (4 fields) | parseStrict | **~
|
|
179
|
-
| order (nested + array) | parseStrict | **~
|
|
176
|
+
| user (4 fields) | parseSafe | **~16M** ops/s | ~3.3M ops/s | ~1.3M ops/s |
|
|
177
|
+
| order (nested + array) | parseSafe | **~5.8M** ops/s | ~0.6M ops/s | ~0.18M ops/s |
|
|
178
|
+
| user (4 fields) | parseStrict | **~13M** ops/s | ~1.8M ops/s | ~1.85M ops/s |
|
|
179
|
+
| order (nested + array) | parseStrict | **~7.5M** ops/s | ~0.36M ops/s | ~0.28M ops/s |
|
|
180
180
|
|
|
181
181
|
The upstream `assert` case (seven scalar roots plus a nested object) runs faster
|
|
182
182
|
still — tens of millions of ops/s — but at that size the numbers swing enough
|
|
@@ -37,18 +37,27 @@ export const generateEnumCheck = (accessor, enumValues) => {
|
|
|
37
37
|
* keeps its plain fallback and emits no extra code.
|
|
38
38
|
*/
|
|
39
39
|
export const generateEnumCaseInsensitiveCoercion = (accessor, enumValues, fallback) => {
|
|
40
|
-
const
|
|
40
|
+
const entries = [];
|
|
41
|
+
const seen = new Set();
|
|
41
42
|
for (const value of enumValues) {
|
|
42
43
|
if (typeof value !== 'string')
|
|
43
44
|
continue;
|
|
44
45
|
const lower = value.toLowerCase();
|
|
45
46
|
// First writer wins, so declaration order breaks ties between members that
|
|
46
|
-
// fold to the same key (e.g. `"on"` and `"ON"`).
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// fold to the same key (e.g. `"on"` and `"ON"`). `Set` membership is used
|
|
48
|
+
// instead of `lower in obj` so a member folding to an inherited name like
|
|
49
|
+
// `constructor` or `toString` is not wrongly treated as already-present.
|
|
50
|
+
if (seen.has(lower))
|
|
51
|
+
continue;
|
|
52
|
+
seen.add(lower);
|
|
53
|
+
entries.push([lower, value]);
|
|
49
54
|
}
|
|
50
|
-
if (
|
|
55
|
+
if (entries.length === 0)
|
|
51
56
|
return null;
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
// A `Map` — not a plain object — so a folded key that collides with an
|
|
58
|
+
// inherited member (`constructor`, `toString`, `__proto__`, …) resolves to
|
|
59
|
+
// `undefined` (→ `fallback`) rather than an `Object.prototype` value. A
|
|
60
|
+
// plain-object lookup would return the inherited function/object for such a key.
|
|
61
|
+
const map = `new Map(${JSON.stringify(entries)})`;
|
|
62
|
+
return `(typeof ${accessor} === "string" ? (${map}.get((${accessor} as string).toLowerCase()) ?? ${fallback}) : ${fallback})`;
|
|
54
63
|
};
|
|
@@ -454,6 +454,18 @@ const generateNonObjectParser = (typeName, schema, strict, unionCtx) => {
|
|
|
454
454
|
return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
|
+
// Strict counterpart of the block above for a *type-less* `const`/`enum` root
|
|
458
|
+
// (`{ enum: [...] }` / `{ const: ... }` with no `type`): the no-`type` bail
|
|
459
|
+
// below would otherwise return a bare cast, so strict mode has to assert
|
|
460
|
+
// membership here. A typed `const`/`enum` (`{ type: 'string', enum: [...] }`)
|
|
461
|
+
// keeps flowing to the strict scalar path, which asserts it via
|
|
462
|
+
// generateScalarStrictAssertion.
|
|
463
|
+
if (strict && isSchemaObject(schema) && !hasType(schema) && (hasConst(schema) || hasEnum(schema))) {
|
|
464
|
+
const assertion = generateScalarStrictAssertion(schema, typeName, unionCtx?.rootSchema);
|
|
465
|
+
if (assertion !== null) {
|
|
466
|
+
return `export const ${functionName} = (input: unknown): ${typeName} => {\n${assertion}\n return input as ${typeName};\n};`;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
457
469
|
// Root-level arrays with rich item schemas delegate every element to a real
|
|
458
470
|
// parser, so nested enums and $refs inside array items are validated instead
|
|
459
471
|
// of spread through unchecked. $ref items call the imported parser; inline
|
|
@@ -1243,7 +1255,18 @@ const generateStrictCombinedParser = (schema, typeName, functionName, propertyLi
|
|
|
1243
1255
|
const resultBody = propertyLines.length > 0 ? `{\n${propertyLines.join('\n')}\n }` : '{}';
|
|
1244
1256
|
// Only the Set form needs a declaration; the inline === chain is stateless.
|
|
1245
1257
|
const knownKeysDeclaration = knownKeyCheck.declarations.map((decl) => ` ${decl};\n`).join('');
|
|
1246
|
-
|
|
1258
|
+
// In strict mode assert the declared properties (type, required, enum,
|
|
1259
|
+
// constraints) — the `result` below is built from the *coercing* property
|
|
1260
|
+
// lines, which silently repair a wrong type and default a missing required key
|
|
1261
|
+
// instead of throwing. `generateObjectStrictAssertion` throws first; the
|
|
1262
|
+
// coercing build then runs only on already-valid input (a no-op there). It also
|
|
1263
|
+
// covers the object-level keyword checks, so it replaces the keyword-only
|
|
1264
|
+
// prelude. `.slice(1)` drops its `isObject` check (emitted just below). Coerce
|
|
1265
|
+
// mode (stripUnknown) keeps its lighter prelude.
|
|
1266
|
+
const assertionPrelude = strict
|
|
1267
|
+
? generateObjectStrictAssertion(schema, typeName, { useRefImports, suffix }).slice(1)
|
|
1268
|
+
: [];
|
|
1269
|
+
const keywordPrelude = assertionPrelude.length > 0 ? `\n${assertionPrelude.join('\n')}` : '';
|
|
1247
1270
|
return `export const ${functionName} = (input: unknown): ${typeName} => {
|
|
1248
1271
|
if (!isObject(input)) {
|
|
1249
1272
|
${notObjectBranch}
|
|
@@ -1284,6 +1307,14 @@ const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, l
|
|
|
1284
1307
|
if (strictKeys) {
|
|
1285
1308
|
return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, strict);
|
|
1286
1309
|
}
|
|
1310
|
+
// `stripUnknown` (coerce mode) must keep keys a `patternProperties` regex
|
|
1311
|
+
// declares and drop only the genuinely-undeclared ones. Delegating to
|
|
1312
|
+
// `generateObjectParser` below would strip the pattern-matching keys too — its
|
|
1313
|
+
// strip logic only knows the declared `properties`. The selective combined
|
|
1314
|
+
// copy keeps declared + pattern-matching keys, exactly like the interpreter.
|
|
1315
|
+
if (stripUnknown && !strict) {
|
|
1316
|
+
return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, false);
|
|
1317
|
+
}
|
|
1287
1318
|
if (!refPattern || !useRefImports) {
|
|
1288
1319
|
return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, undefined, NO_RESERVED_NAMES, caseInsensitive);
|
|
1289
1320
|
}
|
|
@@ -1341,7 +1372,7 @@ const generateAdditionalPropertiesParser = (schema, typeName, useRefImports, suf
|
|
|
1341
1372
|
}
|
|
1342
1373
|
// Handle additionalProperties with a known type by generating inline validation
|
|
1343
1374
|
if (isSchemaObject(additionalProps) && hasType(additionalProps)) {
|
|
1344
|
-
const inlineParser = generateInlineValueParser(additionalProps);
|
|
1375
|
+
const inlineParser = generateInlineValueParser(additionalProps, strict, `[${typeName}]`);
|
|
1345
1376
|
if (inlineParser) {
|
|
1346
1377
|
if (strict) {
|
|
1347
1378
|
return `export const ${functionName} = (input: unknown): ${typeName} => {\n if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${keywordPrelude}\n return validateRecord(input, ${inlineParser}) as ${typeName};\n};`;
|
|
@@ -1362,10 +1393,30 @@ const generateAdditionalPropertiesParser = (schema, typeName, useRefImports, suf
|
|
|
1362
1393
|
* Used to generate parsers for Record-like types where values have a known
|
|
1363
1394
|
* primitive or array type (e.g. Record<string, string> or Record<string, string[]>).
|
|
1364
1395
|
*/
|
|
1365
|
-
const generateInlineValueParser = (schema) => {
|
|
1396
|
+
const generateInlineValueParser = (schema, strict = false, label = '') => {
|
|
1366
1397
|
if (!isSchemaObject(schema) || !hasType(schema)) {
|
|
1367
1398
|
return null;
|
|
1368
1399
|
}
|
|
1400
|
+
// Strict mode must *throw* on a wrong-typed record value, not repair it — the
|
|
1401
|
+
// coercing branch below would silently turn `{ a: 'x' }` into `{ a: 0 }` for a
|
|
1402
|
+
// `Record<string, number>`, which strict mode is documented never to do.
|
|
1403
|
+
if (strict) {
|
|
1404
|
+
const err = (t) => `throw new Error(${JSON.stringify(`${label} record value must be ${t}`)})`;
|
|
1405
|
+
switch (schema.type) {
|
|
1406
|
+
case 'string':
|
|
1407
|
+
return `(value: unknown) => { if (typeof value !== "string") ${err('string')}; return value; }`;
|
|
1408
|
+
case 'number':
|
|
1409
|
+
return `(value: unknown) => { if (typeof value !== "number") ${err('number')}; return value; }`;
|
|
1410
|
+
case 'integer':
|
|
1411
|
+
return `(value: unknown) => { if (typeof value !== "number" || !Number.isInteger(value)) ${err('integer')}; return value; }`;
|
|
1412
|
+
case 'boolean':
|
|
1413
|
+
return `(value: unknown) => { if (typeof value !== "boolean") ${err('boolean')}; return value; }`;
|
|
1414
|
+
case 'array':
|
|
1415
|
+
return `(value: unknown) => { if (!Array.isArray(value)) ${err('array')}; return value; }`;
|
|
1416
|
+
default:
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1369
1420
|
switch (schema.type) {
|
|
1370
1421
|
case 'string':
|
|
1371
1422
|
return '(value: unknown) => typeof value === "string" ? value : ""';
|
|
@@ -4,7 +4,7 @@ import { multipleOfFailExpr } from '@amritk/helpers/multiple-of-check';
|
|
|
4
4
|
import { quoteJsString } from '@amritk/helpers/quote-js-string';
|
|
5
5
|
import { resolveRef } from '@amritk/helpers/resolve-ref';
|
|
6
6
|
import { safeAccessor } from '@amritk/helpers/safe-accessor';
|
|
7
|
-
import { hasDependentRequired, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMinItems, hasMinimum, hasMinLength, hasMultipleOf, hasPattern, hasProperties, hasPropertyNames, hasRef, hasRequired, hasType, hasUniqueItems, isSchemaObject, } from '@amritk/helpers/schema-guards';
|
|
7
|
+
import { hasConst, hasDependentRequired, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMinItems, hasMinimum, hasMinLength, hasMultipleOf, hasPattern, hasProperties, hasPropertyNames, hasRef, hasRequired, hasType, hasUniqueItems, isSchemaObject, } from '@amritk/helpers/schema-guards';
|
|
8
8
|
import { generateEnumCheck } from './generate-enum-check.js';
|
|
9
9
|
import { canEnforceUnion, generateUnionCheck, getUnionBranches, isInlineObjectProperty } from './generate-type-checks.js';
|
|
10
10
|
import { getPrefixItems, prefixItemsCapsLength, scalarItemTypeCheck } from './generate-validation-expression.js';
|
|
@@ -57,12 +57,11 @@ const throwError = (message, suffixExpr) => {
|
|
|
57
57
|
* Generates strict-mode constraint checks for a typed property
|
|
58
58
|
* (pattern, length, min/max, multipleOf).
|
|
59
59
|
*/
|
|
60
|
-
const generateConstraintChecks = (acc, propSchema,
|
|
60
|
+
const generateConstraintChecks = (acc, propSchema, field, context = {}) => {
|
|
61
61
|
if (!isSchemaObject(propSchema) || !hasType(propSchema))
|
|
62
62
|
return [];
|
|
63
63
|
const t = propSchema.type;
|
|
64
64
|
const lines = [];
|
|
65
|
-
const field = `[${typeName}] field '${key}'`;
|
|
66
65
|
if (t === 'string') {
|
|
67
66
|
if (hasPattern(propSchema)) {
|
|
68
67
|
const pattern = escapeRegexPattern(propSchema.pattern);
|
|
@@ -375,7 +374,7 @@ const generatePropertyAssertion = (key, propSchema, isRequired, typeName, contex
|
|
|
375
374
|
lines.push(` if (${acc} !== undefined && (${wrongType})) ${expected};`);
|
|
376
375
|
}
|
|
377
376
|
}
|
|
378
|
-
lines.push(...generateConstraintChecks(acc, propSchema,
|
|
377
|
+
lines.push(...generateConstraintChecks(acc, propSchema, field, context));
|
|
379
378
|
}
|
|
380
379
|
// Type-independent constraints, each runtime-gated on the value's shape:
|
|
381
380
|
// - `contains` asserts on arrays (no-op otherwise),
|
|
@@ -444,23 +443,45 @@ export const generateScalarStrictAssertion = (schema, typeName, rootSchema) => {
|
|
|
444
443
|
if (primitive) {
|
|
445
444
|
return ` if (typeof input !== "${primitive}") ${throwError(`[${typeName}] expected ${primitive}, got `, got)};`;
|
|
446
445
|
}
|
|
447
|
-
if (!isSchemaObject(schema)
|
|
448
|
-
return null;
|
|
449
|
-
const t = schema.type;
|
|
450
|
-
const wrongType = wrongTypeCondition('input', t);
|
|
451
|
-
if (!wrongType)
|
|
446
|
+
if (!isSchemaObject(schema))
|
|
452
447
|
return null;
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
//
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
448
|
+
const label = `[${typeName}]`;
|
|
449
|
+
const lines = [];
|
|
450
|
+
// `const` / `enum` apply to a root value regardless of a declared `type` — a
|
|
451
|
+
// root `{ enum: [...] }` or `{ const: ... }` must reject a non-member, not
|
|
452
|
+
// silently coerce it. `const` is compared through `JSON.stringify` so it works
|
|
453
|
+
// for any JSON value; the value must match exactly.
|
|
454
|
+
if (hasConst(schema)) {
|
|
455
|
+
const wanted = JSON.stringify(JSON.stringify(schema.const));
|
|
456
|
+
lines.push(` if (JSON.stringify(input) !== ${wanted}) ${throwError(`${label} must be ${JSON.stringify(schema.const)}`)};`);
|
|
457
|
+
}
|
|
458
|
+
else if (hasEnum(schema)) {
|
|
459
|
+
const allowed = JSON.stringify(schema.enum);
|
|
460
|
+
const enumLabel = schema.enum.map((v) => JSON.stringify(v)).join(', ');
|
|
461
|
+
lines.push(` if (!(${allowed} as readonly unknown[]).includes(input)) ${throwError(`${label} must be one of: ${enumLabel}`)};`);
|
|
462
|
+
}
|
|
463
|
+
if (hasType(schema)) {
|
|
464
|
+
const t = schema.type;
|
|
465
|
+
const wrongType = wrongTypeCondition('input', t);
|
|
466
|
+
if (wrongType) {
|
|
467
|
+
lines.push(` if (${wrongType}) ${throwError(`${label} expected ${typeLabel(t)}, got `, got)};`);
|
|
460
468
|
}
|
|
461
|
-
|
|
462
|
-
//
|
|
463
|
-
|
|
469
|
+
// Root-level arrays enforce scalar/enum item types too — the same gap the
|
|
470
|
+
// property path closes in generateConstraintChecks — plus `contains`.
|
|
471
|
+
if (t === 'array') {
|
|
472
|
+
const itemCheck = generateItemCheck(schema);
|
|
473
|
+
if (itemCheck) {
|
|
474
|
+
lines.push(` if (!(input as readonly unknown[]).every((_it) => ${itemCheck.check})) ${throwError(`${label} ${itemCheck.message}`)};`);
|
|
475
|
+
}
|
|
476
|
+
lines.push(...generateContainsCheck('input', schema, label));
|
|
477
|
+
// Tuple `prefixItems`: assert each position and cap length under items:false.
|
|
478
|
+
lines.push(...generatePrefixItemsAssertion('input', label, schema, rootSchema));
|
|
479
|
+
}
|
|
480
|
+
// String (pattern, min/maxLength), number/integer (bounds, multipleOf) and
|
|
481
|
+
// array (length, uniqueItems) constraints — previously enforced only for
|
|
482
|
+
// named properties, so a root scalar accepted e.g. `{type:'string',minLength:5}`
|
|
483
|
+
// violations.
|
|
484
|
+
lines.push(...generateConstraintChecks('input', schema, label, rootSchema ? { rootSchema } : {}));
|
|
464
485
|
}
|
|
465
|
-
return lines.join('\n');
|
|
486
|
+
return lines.length > 0 ? lines.join('\n') : null;
|
|
466
487
|
};
|
|
@@ -132,13 +132,20 @@ const getTypeCoercion = (accessor, schema, defaultValue) => {
|
|
|
132
132
|
switch (schema.type) {
|
|
133
133
|
case 'string':
|
|
134
134
|
return `String(${accessor})`;
|
|
135
|
-
case 'number':
|
|
136
|
-
case 'integer': {
|
|
135
|
+
case 'number': {
|
|
137
136
|
// Number() of a non-numeric string produces NaN, which silently poisons
|
|
138
137
|
// arithmetic. Guard with Number.isFinite and fall back to the default.
|
|
139
138
|
const n = `Number(${accessor})`;
|
|
140
139
|
return `(Number.isFinite(${n}) ? ${n} : ${defaultValue})`;
|
|
141
140
|
}
|
|
141
|
+
case 'integer': {
|
|
142
|
+
// An `integer` must coerce to a whole number, or the "repaired" value still
|
|
143
|
+
// fails validation (a bare `Number.isFinite` accepts `1.5`). Require
|
|
144
|
+
// integrality and fall back to the default otherwise — matching the
|
|
145
|
+
// root-level integer parser, which yields the default for a non-integer.
|
|
146
|
+
const n = `Number(${accessor})`;
|
|
147
|
+
return `(Number.isInteger(${n}) ? ${n} : ${defaultValue})`;
|
|
148
|
+
}
|
|
142
149
|
case 'boolean':
|
|
143
150
|
return `Boolean(${accessor})`;
|
|
144
151
|
case 'array':
|
|
@@ -50,6 +50,14 @@ export const getDefaultValue = (schema) => {
|
|
|
50
50
|
if (getMjstPrimitive(schema) === 'bigint') {
|
|
51
51
|
return '0n';
|
|
52
52
|
}
|
|
53
|
+
// Array-form `type` (e.g. `["string","null"]`): `hasType` is false for it, so
|
|
54
|
+
// without this a required-but-missing value would default to `undefined` —
|
|
55
|
+
// violating both `required` and the declared type. Derive the default from the
|
|
56
|
+
// first listed type so the fallback is a valid member.
|
|
57
|
+
const rawType = schema['type'];
|
|
58
|
+
if (Array.isArray(rawType) && rawType.length > 0 && typeof rawType[0] === 'string') {
|
|
59
|
+
return getDefaultValue({ ...schema, type: rawType[0] });
|
|
60
|
+
}
|
|
53
61
|
if (!hasType(schema)) {
|
|
54
62
|
return 'undefined';
|
|
55
63
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/generate-parsers",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.3",
|
|
4
4
|
"description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
},
|
|
43
43
|
"exports": {
|
|
44
44
|
".": {
|
|
45
|
-
"
|
|
46
|
-
"
|
|
45
|
+
"types": "./dist/index.d.ts",
|
|
46
|
+
"default": "./dist/index.js"
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"json-schema-typed": "^8.0.1",
|
|
51
|
-
"@amritk/helpers": "0.13.
|
|
52
|
-
"@amritk/generate-markdown": "0.4.
|
|
51
|
+
"@amritk/helpers": "0.13.2",
|
|
52
|
+
"@amritk/generate-markdown": "0.4.2"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@sinclair/typebox": "^0.34.49",
|