🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@amritk/adapters

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@amritk/adapters - npm Package Compare versions

Comparing version
0.2.10
to
0.2.11
+2
-1
dist/effect-to-json-schema.d.ts

@@ -9,4 +9,5 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';

* pass that representation straight through — it accurately reflects what Effect
* expects on the wire — only stripping the dialect marker.
* expects on the wire — only stripping the dialect marker. A top-level
* `BigIntFromSelf` / `DateFromSelf` is rescued into an `x-mjst` hint.
*/
export declare const effectToJsonSchema: (source: unknown) => Promise<JSONSchema>;

@@ -0,1 +1,2 @@

import { MJST_EXTENSION_KEY } from '@amritk/helpers/mjst-extension';
/**

@@ -20,3 +21,27 @@ * Resolves `effect`'s `JSONSchema.make` from a runtime import, throwing a clear

};
// The annotation key Effect stamps an identifier under (e.g. `"DateFromSelf"`).
const IDENTIFIER_ANNOTATION = Symbol.for('effect/annotation/Identifier');
/** Reads `node.ast`, the AST that backs every Effect `Schema`. */
const astOf = (source) => {
const ast = source?.ast;
return ast && typeof ast === 'object' ? ast : undefined;
};
/**
* Effect models `bigint` and a runtime `Date` as types with no JSON Schema
* representation, so `JSONSchema.make` throws on `Schema.BigIntFromSelf` /
* `Schema.DateFromSelf`. To stay consistent with the Zod, Valibot, and TypeBox
* adapters — which map those to the shared `x-mjst` hint — we rescue them here:
* `bigint` (`BigIntKeyword`) → `primitive: 'bigint'`, and the `DateFromSelf`
* declaration → `instanceOf: 'Date'`. (`Schema.Date` / `Schema.BigInt` decode
* from a string and already convert to a `string` schema, so they are untouched.)
*/
const rescueXMjst = (ast) => {
if (ast._tag === 'BigIntKeyword')
return { [MJST_EXTENSION_KEY]: { primitive: 'bigint' } };
if (ast._tag === 'Declaration' && ast.annotations?.[IDENTIFIER_ANNOTATION] === 'DateFromSelf') {
return { [MJST_EXTENSION_KEY]: { instanceOf: 'Date' } };
}
return undefined;
};
/**
* Converts an Effect `Schema` into a JSON Schema via `JSONSchema.make`.

@@ -28,3 +53,4 @@ *

* pass that representation straight through — it accurately reflects what Effect
* expects on the wire — only stripping the dialect marker.
* expects on the wire — only stripping the dialect marker. A top-level
* `BigIntFromSelf` / `DateFromSelf` is rescued into an `x-mjst` hint.
*/

@@ -37,2 +63,8 @@ export const effectToJsonSchema = async (source) => {

}
const ast = astOf(source);
if (ast) {
const rescued = rescueXMjst(ast);
if (rescued)
return rescued;
}
const make = await loadMake();

@@ -44,3 +76,8 @@ let json;

catch (error) {
throw new Error(`Effect adapter failed to convert the schema. Is it a valid Effect Schema?\n${String(error)}`);
// `make` throws on an unrepresentable type with no `jsonSchema` annotation —
// most often a nested `BigIntFromSelf` / `DateFromSelf`. Point the user at the
// fix rather than surfacing Effect's opaque "Missing annotation" message.
throw new Error(`Effect adapter failed to convert the schema. A nested bigint or runtime Date (BigIntFromSelf / DateFromSelf) ` +
`has no JSON Schema representation — use the string-encoded Schema.BigInt / Schema.Date, or add a ` +
`\`jsonSchema\` annotation to that field.\n${String(error)}`);
}

@@ -47,0 +84,0 @@ delete json['$schema'];

import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
/**
* Converts a Zod schema into a Draft 2020-12 JSON Schema using Zod 4's native
* `toJSONSchema`.
*
* Zod's `z.date()` has no JSON Schema representation and would otherwise throw.
* We pass `unrepresentable: 'any'` so conversion never fails on it, then use the
* `override` hook to rewrite date schemas into an `x-mjst` instanceOf hint — the
* same extension TypeBox dates use — so generated types and runtime checks treat
* them as `Date`.
*/
export declare const zodToJsonSchema: (source: unknown) => Promise<JSONSchema>;

@@ -35,2 +35,93 @@ import { MJST_EXTENSION_KEY } from '@amritk/helpers/mjst-extension';

*/
/**
* Zod 4's `toJSONSchema` emits a fixed tuple as a bare `prefixItems` array with
* no length bound, so the result accepts a too-short array (positions past the
* end are simply unconstrained) and a too-long one (nothing forbids extra
* items). A Zod tuple requires exactly its fixed elements, so we restore that:
* `minItems` forces the fixed elements to be present, and — when the tuple has
* no `.rest(...)` (no `items`) — `items: false` forbids extras. Applied to every
* `prefixItems` node in the tree. Existing tighter bounds are never loosened.
*/
const enforceTupleLength = (node) => {
if (node === null || typeof node !== 'object')
return;
if (Array.isArray(node)) {
for (const item of node)
enforceTupleLength(item);
return;
}
const obj = node;
if (Array.isArray(obj['prefixItems'])) {
const fixed = obj['prefixItems'].length;
const min = typeof obj['minItems'] === 'number' ? obj['minItems'] : 0;
if (min < fixed)
obj['minItems'] = fixed;
// No `items` keyword means no rest element: the array may not exceed the
// fixed tuple, so forbid additional items.
if (!('items' in obj))
obj['items'] = false;
}
for (const value of Object.values(obj))
enforceTupleLength(value);
};
// Keys a branch may carry and still be treated as a plain "closed object" that
// is safe to merge. Anything else (patternProperties, a nested allOf, $ref, a
// conditional, …) makes the merge unsafe, so we leave such an allOf alone.
const CLOSED_OBJECT_KEYS = new Set(['type', 'properties', 'required', 'additionalProperties']);
const isClosedObject = (node) => {
if (node === null || typeof node !== 'object' || Array.isArray(node))
return false;
const obj = node;
if (obj['type'] !== 'object' || obj['additionalProperties'] !== false)
return false;
return Object.keys(obj).every((key) => CLOSED_OBJECT_KEYS.has(key));
};
/**
* Zod emits an intersection of objects as an `allOf` where every branch carries
* `additionalProperties: false`. That is unsatisfiable — each branch rejects the
* keys the others contribute — even though the Zod intersection accepts the
* combined object. When every `allOf` branch is a closed object we merge them
* into one: properties are unioned (a key in several branches becomes an `allOf`
* of its schemas), `required` is unioned, and `additionalProperties: false` is
* kept over the combined key set. `allOf`s with a non-object branch (e.g. two
* refined strings) are left untouched — those already combine correctly.
*/
const mergeClosedObjectAllOf = (node) => {
if (node === null || typeof node !== 'object')
return node;
if (Array.isArray(node))
return node.map(mergeClosedObjectAllOf);
const obj = node;
const out = {};
for (const [key, value] of Object.entries(obj))
out[key] = mergeClosedObjectAllOf(value);
const allOf = out['allOf'];
if (Array.isArray(allOf) && allOf.length > 1 && allOf.every(isClosedObject)) {
const properties = {};
const required = new Set();
for (const branch of allOf) {
const props = (branch['properties'] ?? {});
for (const [prop, schema] of Object.entries(props)) {
const bucket = properties[prop];
if (bucket)
bucket.push(schema);
else
properties[prop] = [schema];
}
for (const r of (branch['required'] ?? []))
required.add(r);
}
const mergedProps = {};
for (const [prop, schemas] of Object.entries(properties)) {
mergedProps[prop] = schemas.length === 1 ? schemas[0] : { allOf: schemas };
}
delete out['allOf'];
out['type'] = 'object';
out['properties'] = mergedProps;
if (required.size > 0)
out['required'] = [...required];
out['additionalProperties'] = false;
}
return out;
};
export const zodToJsonSchema = async (source) => {

@@ -77,3 +168,6 @@ if (typeof source !== 'object' || source === null) {

delete json['$schema'];
return json;
// Zod under-constrains fixed tuples (bare `prefixItems`); restore their length.
enforceTupleLength(json);
// Collapse an unsatisfiable `allOf` of closed objects (object intersections).
return mergeClosedObjectAllOf(json);
};
{
"name": "@amritk/adapters",
"version": "0.2.10",
"version": "0.2.11",
"description": "Convert schemas from external libraries (TypeBox, Zod, ...) into JSON Schema for mjst.",

@@ -5,0 +5,0 @@ "type": "module",