zopia 0.5.0 โ 0.5.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/CHANGELOG.md +32 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/conversions/json-schema-to-zod.ts +10 -4
- package/src/conversions/manifest-writer.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.2] - 2026-09-30
|
|
11
|
+
|
|
12
|
+
### ๐ Fixed
|
|
13
|
+
- ๐ **`toJSON` property error in generated `index.ts` files (#5).** The
|
|
14
|
+
canonical-JSON helper emitted into schemas using `uniqueItems` (and object
|
|
15
|
+
`const`/`enum` comparisons) guarded against `toJSON` methods with
|
|
16
|
+
`typeof value.toJSON === 'function'`. Because the helper is invoked per
|
|
17
|
+
item with the item's own type, primitive element types (e.g.
|
|
18
|
+
`uniqueItems` on a string array) let TypeScript narrow `value` to `never`,
|
|
19
|
+
and editors reported `TS2339: Property 'toJSON' does not exist on type
|
|
20
|
+
'never'`. The guard now reads `typeof Object(value).toJSON === 'function'`
|
|
21
|
+
โ identical runtime behavior (short-circuited for non-objects,
|
|
22
|
+
`Object(obj) === obj` for objects), but valid TypeScript for every element
|
|
23
|
+
type. Generated code stays plain JavaScript (no type annotations were
|
|
24
|
+
added), and a new suite typechecks generated endpoints end-to-end under
|
|
25
|
+
`strict: true` with `ts.createProgram`, so generated files are now held to
|
|
26
|
+
the same compile-cleanly bar as the IntelliSense declarations.
|
|
27
|
+
|
|
28
|
+
## [0.5.1] - 2026-09-30
|
|
29
|
+
|
|
30
|
+
### ๐ Fixed
|
|
31
|
+
- ๐ **Duplicate `.int()` in generated Zod schemas for `int32` and `int64`
|
|
32
|
+
formats (#4).** Integer properties carrying an integer format emitted
|
|
33
|
+
`z.number().int().int()...` โ the `integer` type already appends `.int()`
|
|
34
|
+
and the format branch appended it again. Now every combination
|
|
35
|
+
(`integer`/`number` ร `int32`/`int64`/`uint32`/`uint64`) emits exactly one
|
|
36
|
+
`.int()`, with bounds (`int32` โ `-2147483648...2147483647`,
|
|
37
|
+
`uint32` โ `0...4294967295`) and `.nonnegative()` for the unsigned formats
|
|
38
|
+
unchanged. Runtime validation, warning codes, and reverse-conversion
|
|
39
|
+
overlays are untouched; covered by new unit and end-to-end regression
|
|
40
|
+
tests that assert the emitted code (not just parse behavior).
|
|
41
|
+
|
|
10
42
|
## [0.5.0] - 2026-09-29
|
|
11
43
|
|
|
12
44
|
### โจ Added
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
[](https://bun.sh/)
|
|
14
14
|
[](https://vitest.dev/)
|
|
15
15
|
|
|
16
|
-
โ
**Status โ Phase 3 complete ยท v0.5.
|
|
16
|
+
โ
**Status โ Phase 3 complete ยท v0.5.2 released**
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
|
package/package.json
CHANGED
|
@@ -310,7 +310,7 @@ function jsonSchemaToZodInternal(input: JsonSchema | string, options: JsonSchema
|
|
|
310
310
|
const normalized = JSON.parse(literal);
|
|
311
311
|
return JSON.stringify(normalized, (_key, item) => item && typeof item === 'object' && !Array.isArray(item) ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) : item);
|
|
312
312
|
};
|
|
313
|
-
const generatedCanonicalJson = `(value) => { if (value && typeof value === 'object' && typeof value.toJSON === 'function') return undefined; try { return JSON.stringify(value, (_key, item) => { if (item === null || typeof item === 'string' || typeof item === 'boolean') return item; if (typeof item === 'number') { if (!Number.isFinite(item)) throw new Error('non-JSON number'); return item; } if (!item || typeof item !== 'object' || Object.getOwnPropertySymbols(item).length || (!Array.isArray(item) && ![Object.prototype, null].includes(Object.getPrototypeOf(item)))) throw new Error('non-JSON value'); const children = Array.isArray(item) ? item : Object.values(item); if (children.some((child) => child && typeof child === 'object' && typeof child.toJSON === 'function')) throw new Error('non-JSON toJSON'); if (Array.isArray(item)) { if (Object.keys(item).length !== item.length || !item.every((_child, index) => Object.prototype.hasOwnProperty.call(item, index))) throw new Error('non-JSON array'); return item; } return Object.fromEntries(Object.entries(item).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)); }); } catch { return undefined; } }`;
|
|
313
|
+
const generatedCanonicalJson = `(value) => { if (value && typeof value === 'object' && typeof Object(value).toJSON === 'function') return undefined; try { return JSON.stringify(value, (_key, item) => { if (item === null || typeof item === 'string' || typeof item === 'boolean') return item; if (typeof item === 'number') { if (!Number.isFinite(item)) throw new Error('non-JSON number'); return item; } if (!item || typeof item !== 'object' || Object.getOwnPropertySymbols(item).length || (!Array.isArray(item) && ![Object.prototype, null].includes(Object.getPrototypeOf(item)))) throw new Error('non-JSON value'); const children = Array.isArray(item) ? item : Object.values(item); if (children.some((child) => child && typeof child === 'object' && typeof child.toJSON === 'function')) throw new Error('non-JSON toJSON'); if (Array.isArray(item)) { if (Object.keys(item).length !== item.length || !item.every((_child, index) => Object.prototype.hasOwnProperty.call(item, index))) throw new Error('non-JSON array'); return item; } return Object.fromEntries(Object.entries(item).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)); }); } catch { return undefined; } }`;
|
|
314
314
|
let source: JsonSchema;
|
|
315
315
|
try {
|
|
316
316
|
if (typeof input !== 'string') source = input;
|
|
@@ -684,11 +684,17 @@ function jsonSchemaToZodInternal(input: JsonSchema | string, options: JsonSchema
|
|
|
684
684
|
}
|
|
685
685
|
}
|
|
686
686
|
if ((node.format === 'int32' || node.format === 'int64') && (node.type === 'integer' || node.type === 'number')) {
|
|
687
|
-
|
|
688
|
-
|
|
687
|
+
// `integer` schemas already carry .int() from the type switch above โ appending it again
|
|
688
|
+
// duplicated the call in generated code (issue #4); only `number` schemas need it added here.
|
|
689
|
+
const alreadyInteger = node.type === 'integer';
|
|
690
|
+
const integer = alreadyInteger ? result.schema : (result.schema as any).int();
|
|
691
|
+
const code = alreadyInteger ? result.code : `${result.code}.int()`;
|
|
692
|
+
result = node.format === 'int32' ? { schema: (integer as any).min(-2147483648).max(2147483647), code: `${code}.min(-2147483648).max(2147483647)` } : { schema: integer, code };
|
|
689
693
|
}
|
|
690
694
|
if ((node.format === 'uint32' || node.format === 'uint64') && (node.type === 'integer' || node.type === 'number')) {
|
|
691
|
-
const
|
|
695
|
+
const alreadyInteger = node.type === 'integer';
|
|
696
|
+
const integer = alreadyInteger ? (result.schema as any).nonnegative() : (result.schema as any).int().nonnegative();
|
|
697
|
+
const code = alreadyInteger ? `${result.code}.nonnegative()` : `${result.code}.int().nonnegative()`;
|
|
692
698
|
result = node.format === 'uint32' ? { schema: integer.max(4294967295), code: `${code}.max(4294967295)` } : { schema: integer, code };
|
|
693
699
|
}
|
|
694
700
|
if (node.format && node.type === 'string') {
|
|
@@ -16,7 +16,7 @@ export const ZOPIA_MANIFEST_SCHEMA = 'zopia:manifest@1' as const;
|
|
|
16
16
|
export const ZOPIA_MANIFEST_FILE = '.zopia-manifest.json' as const;
|
|
17
17
|
|
|
18
18
|
/** Package version recorded by the current manifest writer. */
|
|
19
|
-
export const ZOPIA_VERSION = '0.5.
|
|
19
|
+
export const ZOPIA_VERSION = '0.5.2' as const;
|
|
20
20
|
|
|
21
21
|
/** Supported source dialect labels stored in a manifest. */
|
|
22
22
|
export type ZopiaManifestSourceKind = 'swagger-2.0' | 'openapi-3.0' | 'openapi-3.1';
|