@arktype/schema
Advanced tools
Comparing version 0.15.0 to 0.16.0
@@ -5,3 +5,3 @@ import { append, appendUnique, capitalize, isArray, throwInternalError, throwParseError } from "@ark/util"; | ||
import { compileErrorContext, constraintKeys } from "./shared/implement.js"; | ||
import { intersectNodes, intersectNodesRoot } from "./shared/intersections.js"; | ||
import { intersectNodesRoot, intersectOrPipeNodes } from "./shared/intersections.js"; | ||
import { $ark } from "./shared/registry.js"; | ||
@@ -67,3 +67,3 @@ import { arkKind } from "./shared/utils.js"; | ||
return result; | ||
result = intersectNodes(root, result, s.ctx); | ||
result = intersectOrPipeNodes(root, result, s.ctx); | ||
} | ||
@@ -74,3 +74,3 @@ return result; | ||
for (let i = 0; i < s.l.length; i++) { | ||
const result = intersectNodes(s.l[i], head, s.ctx); | ||
const result = intersectOrPipeNodes(s.l[i], head, s.ctx); | ||
if (result === null) | ||
@@ -77,0 +77,0 @@ continue; |
@@ -27,8 +27,5 @@ import { Callable, appendUnique, flatMorph, includes, isArray, isEmptyObject, throwError } from "@ark/util"; | ||
withMeta(meta) { | ||
const newMeta = typeof meta === "function" ? | ||
meta({ ...this.meta }) | ||
: { ...this.meta, ...meta }; | ||
return this.$.node(this.kind, { | ||
...this.inner, | ||
meta: newMeta | ||
meta: typeof meta === "function" ? meta({ ...this.meta }) : meta | ||
}); | ||
@@ -35,0 +32,0 @@ } |
@@ -5,3 +5,3 @@ import { append, domainDescriptions, printable, throwInternalError, throwParseError } from "@ark/util"; | ||
import { implementNode } from "../shared/implement.js"; | ||
import { intersectNodes } from "../shared/intersections.js"; | ||
import { intersectOrPipeNodes } from "../shared/intersections.js"; | ||
import { writeCyclicJsonSchemaMessage } from "../shared/jsonSchema.js"; | ||
@@ -29,3 +29,3 @@ import { $ark } from "../shared/registry.js"; | ||
intersections: { | ||
alias: (l, r, ctx) => ctx.$.lazilyResolve(() => neverIfDisjoint(intersectNodes(l.resolution, r.resolution, ctx)), `${l.reference}${ctx.pipe ? "=>" : "&"}${r.reference}`), | ||
alias: (l, r, ctx) => ctx.$.lazilyResolve(() => neverIfDisjoint(intersectOrPipeNodes(l.resolution, r.resolution, ctx)), `${l.reference}${ctx.pipe ? "=>" : "&"}${r.reference}`), | ||
...defineRightwardIntersections("alias", (l, r, ctx) => { | ||
@@ -36,3 +36,3 @@ if (r.isUnknown()) | ||
return r; | ||
return ctx.$.lazilyResolve(() => neverIfDisjoint(intersectNodes(l.resolution, r, ctx)), `${l.reference}${ctx.pipe ? "=>" : "&"}${r.id}`); | ||
return ctx.$.lazilyResolve(() => neverIfDisjoint(intersectOrPipeNodes(l.resolution, r, ctx)), `${l.reference}${ctx.pipe ? "=>" : "&"}${r.id}`); | ||
}) | ||
@@ -39,0 +39,0 @@ } |
@@ -5,3 +5,3 @@ import { flatMorph, hasDomain, isEmptyObject, isKeyOf, throwParseError } from "@ark/util"; | ||
import { implementNode, structureKeys } from "../shared/implement.js"; | ||
import { intersectNodes } from "../shared/intersections.js"; | ||
import { intersectOrPipeNodes } from "../shared/intersections.js"; | ||
import { hasArkKind, isNode } from "../shared/utils.js"; | ||
@@ -123,3 +123,3 @@ import { BaseRoot } from "./root.js"; | ||
return r; | ||
const basis = l.basis ? intersectNodes(l.basis, r, ctx) : r; | ||
const basis = l.basis ? intersectOrPipeNodes(l.basis, r, ctx) : r; | ||
return (basis instanceof Disjoint ? basis | ||
@@ -241,3 +241,3 @@ : l?.basis?.equals(basis) ? | ||
rBasis ? | ||
intersectNodes(lBasis, rBasis, ctx) | ||
intersectOrPipeNodes(lBasis, rBasis, ctx) | ||
: lBasis | ||
@@ -244,0 +244,0 @@ : rBasis; |
import { arrayEquals, liftArray, throwParseError } from "@ark/util"; | ||
import { Disjoint } from "../shared/disjoint.js"; | ||
import { implementNode } from "../shared/implement.js"; | ||
import { intersectNodes } from "../shared/intersections.js"; | ||
import { intersectOrPipeNodes } from "../shared/intersections.js"; | ||
import { writeJsonSchemaMorphMessage } from "../shared/jsonSchema.js"; | ||
@@ -40,3 +40,3 @@ import { $ark, registeredReference } from "../shared/registry.js"; | ||
} | ||
const inTersection = intersectNodes(l.in, r.in, ctx); | ||
const inTersection = intersectOrPipeNodes(l.in, r.in, ctx); | ||
if (inTersection instanceof Disjoint) | ||
@@ -48,3 +48,3 @@ return inTersection; | ||
if (l.declaredIn || r.declaredIn) { | ||
const declaredIn = intersectNodes(l.in, r.in, ctx); | ||
const declaredIn = intersectOrPipeNodes(l.in, r.in, ctx); | ||
// we can't treat this as a normal Disjoint since it's just declared | ||
@@ -58,3 +58,3 @@ // it should only happen if someone's essentially trying to create a broken type | ||
if (l.declaredOut || r.declaredOut) { | ||
const declaredOut = intersectNodes(l.out, r.out, ctx); | ||
const declaredOut = intersectOrPipeNodes(l.out, r.out, ctx); | ||
if (declaredOut instanceof Disjoint) | ||
@@ -73,3 +73,3 @@ return declaredOut.throw(); | ||
...defineRightwardIntersections("morph", (l, r, ctx) => { | ||
const inTersection = intersectNodes(l.in, r, ctx); | ||
const inTersection = intersectOrPipeNodes(l.in, r, ctx); | ||
return inTersection instanceof Disjoint ? inTersection : (inTersection.distribute(branch => ({ | ||
@@ -76,0 +76,0 @@ ...l.inner, |
@@ -6,2 +6,3 @@ import { builtinConstructors, constructorExtends, getBuiltinNameOfConstructor, objectKindDescriptions, objectKindOrDomainOf, throwParseError } from "@ark/util"; | ||
import { $ark } from "../shared/registry.js"; | ||
import { isNode } from "../shared/utils.js"; | ||
import { InternalBasis } from "./basis.js"; | ||
@@ -18,3 +19,5 @@ const implementation = implementNode({ | ||
normalize: schema => typeof schema === "string" ? { proto: builtinConstructors[schema] } | ||
: typeof schema === "function" ? { proto: schema } | ||
: typeof schema === "function" ? | ||
isNode(schema) ? schema | ||
: { proto: schema } | ||
: typeof schema.proto === "string" ? | ||
@@ -21,0 +24,0 @@ { ...schema, proto: builtinConstructors[schema.proto] } |
@@ -34,2 +34,3 @@ import { inferred, type array } from "@ark/util"; | ||
get defaultMeta(): unknown; | ||
withoutOptionalOrDefaultMeta(): this; | ||
as(): this; | ||
@@ -36,0 +37,0 @@ brand(name: string): this; |
@@ -34,2 +34,17 @@ import { includes, inferred, omit, throwInternalError, throwParseError, unset } from "@ark/util"; | ||
} | ||
withoutOptionalOrDefaultMeta() { | ||
if (!this.optionalMeta && this.defaultMeta === unset) | ||
return this; | ||
const meta = { ...this.meta }; | ||
delete meta.default; | ||
delete meta.optional; | ||
if (!this.hasKind("morph") || | ||
(!this.in.optionalMeta && this.in.defaultMeta === unset)) | ||
return this.withMeta(meta); | ||
return this.$.node("morph", { | ||
...this.inner, | ||
in: this.in.withoutOptionalOrDefaultMeta(), | ||
meta | ||
}); | ||
} | ||
as() { | ||
@@ -36,0 +51,0 @@ return this; |
@@ -5,3 +5,3 @@ import { appendUnique, arrayEquals, domainDescriptions, flatMorph, groupBy, isArray, jsTypeOfDescriptions, printable, throwParseError } from "@ark/util"; | ||
import { implementNode } from "../shared/implement.js"; | ||
import { intersectNodes, intersectNodesRoot } from "../shared/intersections.js"; | ||
import { intersectNodesRoot, intersectOrPipeNodes } from "../shared/intersections.js"; | ||
import { $ark, registeredReference } from "../shared/registry.js"; | ||
@@ -394,3 +394,3 @@ import { hasArkKind } from "../shared/utils.js"; | ||
} | ||
const branchIntersection = intersectNodes(l[lIndex], r[rIndex], ctx); | ||
const branchIntersection = intersectOrPipeNodes(l[lIndex], r[rIndex], ctx); | ||
if (branchIntersection instanceof Disjoint) { | ||
@@ -397,0 +397,0 @@ // Doesn't tell us anything useful about their relationships |
@@ -30,2 +30,3 @@ import { CastableBase, ReadonlyArray, type propwiseXor, type show } from "@ark/util"; | ||
add(error: ArkError): void; | ||
private _add; | ||
merge(errors: ArkErrors): void; | ||
@@ -32,0 +33,0 @@ get summary(): string; |
@@ -59,2 +59,7 @@ import { CastableBase, ReadonlyArray, defineProperties } from "@ark/util"; | ||
add(error) { | ||
if (this.includes(error)) | ||
return; | ||
this._add(error); | ||
} | ||
_add(error) { | ||
const existing = this.byPath[error.propString]; | ||
@@ -83,3 +88,7 @@ if (existing) { | ||
merge(errors) { | ||
errors.forEach(e => this.add(new ArkError({ ...e, path: [...e.path, ...this.ctx.path] }, this.ctx))); | ||
errors.forEach(e => { | ||
if (this.includes(e)) | ||
return; | ||
this._add(new ArkError({ ...e, path: [...e.path, ...this.ctx.path] }, this.ctx)); | ||
}); | ||
} | ||
@@ -86,0 +95,0 @@ get summary() { |
@@ -9,3 +9,3 @@ import type { BaseNode } from "../node.ts"; | ||
export declare const pipeNodesRoot: InternalNodeIntersection<BaseScope>; | ||
export declare const intersectNodes: InternalNodeIntersection<IntersectionContext>; | ||
export declare const intersectOrPipeNodes: InternalNodeIntersection<IntersectionContext>; | ||
export {}; |
@@ -5,3 +5,3 @@ import { Disjoint } from "./disjoint.js"; | ||
const intersectionCache = {}; | ||
export const intersectNodesRoot = (l, r, $) => intersectNodes(l, r, { | ||
export const intersectNodesRoot = (l, r, $) => intersectOrPipeNodes(l, r, { | ||
$, | ||
@@ -11,3 +11,3 @@ invert: false, | ||
}); | ||
export const pipeNodesRoot = (l, r, $) => intersectNodes(l, r, { | ||
export const pipeNodesRoot = (l, r, $) => intersectOrPipeNodes(l, r, { | ||
$, | ||
@@ -17,3 +17,3 @@ invert: false, | ||
}); | ||
export const intersectNodes = (l, r, ctx) => { | ||
export const intersectOrPipeNodes = ((l, r, ctx) => { | ||
const operator = ctx.pipe ? "|>" : "&"; | ||
@@ -36,7 +36,18 @@ const lrCacheKey = `${l.hash}${operator}${r.hash}`; | ||
} | ||
if (l.equals(r)) | ||
const isPureIntersection = !ctx.pipe || (!l.includesMorph && !r.includesMorph); | ||
if (isPureIntersection && l.equals(r)) | ||
return l; | ||
let result = ctx.pipe && l.hasKindIn(...rootKinds) && r.hasKindIn(...rootKinds) ? | ||
_pipeNodes(l, r, ctx) | ||
: _intersectNodes(l, r, ctx); | ||
let result; | ||
if (isPureIntersection) { | ||
if (l.equals(r)) | ||
return l; | ||
result = _intersectNodes(l, r, ctx); | ||
} | ||
else { | ||
result = | ||
l.hasKindIn(...rootKinds) ? | ||
// if l is a RootNode, r will be as well | ||
_pipeNodes(l, r, ctx) | ||
: _intersectNodes(l, r, ctx); | ||
} | ||
if (isNode(result)) { | ||
@@ -52,3 +63,3 @@ // if the result equals one of the operands, preserve its metadata by | ||
return result; | ||
}; | ||
}); | ||
const _intersectNodes = (l, r, ctx) => { | ||
@@ -72,11 +83,7 @@ const leftmostKind = l.precedence < r.precedence ? l.kind : r.kind; | ||
}; | ||
const _pipeNodes = (l, r, ctx) => l.includesMorph ? | ||
const _pipeNodes = (l, r, ctx) => l.includesMorph || r.includesMorph ? | ||
ctx.invert ? | ||
pipeMorphed(r, l, ctx) | ||
: pipeMorphed(l, r, ctx) | ||
: r.includesMorph ? | ||
ctx.invert ? | ||
pipeMorphed(r, l, ctx) | ||
: pipeMorphed(l, r, ctx) | ||
: _intersectNodes(l, r, ctx); | ||
: _intersectNodes(l, r, ctx); | ||
const pipeMorphed = (from, to, ctx) => from.distribute(fromBranch => _pipeMorphed(fromBranch, to, ctx), results => { | ||
@@ -86,5 +93,7 @@ const viableBranches = results.filter(isNode); | ||
return Disjoint.init("union", from.branches, to.branches); | ||
// if the input type has changed, create a new node without preserving metadata | ||
if (viableBranches.length < from.branches.length || | ||
!from.branches.every((branch, i) => branch.in.equals(viableBranches[i].in))) | ||
return ctx.$.parseSchema(viableBranches); | ||
// otherwise, the input has not changed so preserve metadata | ||
let meta; | ||
@@ -117,3 +126,3 @@ if ("default" in from.meta) | ||
// still piped from context, so allows appending additional morphs | ||
const outIntersection = intersectNodes(from.lastMorphIfNode, to, ctx); | ||
const outIntersection = intersectOrPipeNodes(from.lastMorphIfNode, to, ctx); | ||
if (outIntersection instanceof Disjoint) | ||
@@ -131,3 +140,3 @@ return outIntersection; | ||
if (to.hasKind("morph")) { | ||
const inTersection = intersectNodes(from, to.in, ctx); | ||
const inTersection = intersectOrPipeNodes(from, to.in, ctx); | ||
if (inTersection instanceof Disjoint) | ||
@@ -134,0 +143,0 @@ return inTersection; |
@@ -28,4 +28,7 @@ import type { array } from "@ark/util"; | ||
finalize(): unknown; | ||
private applyQueuedMorphs; | ||
private applyMorphsAtPath; | ||
get currentErrorCount(): number; | ||
hasError(): boolean; | ||
pathHasError(path: TraversalPath): boolean; | ||
get failFast(): boolean; | ||
@@ -32,0 +35,0 @@ error<input extends ArkErrorInput>(input: input): ArkError<input extends { |
import { ArkError, ArkErrors } from "./errors.js"; | ||
import { pathToPropString } from "./utils.js"; | ||
import { isNode, pathToPropString } from "./utils.js"; | ||
export class TraversalContext { | ||
@@ -35,2 +35,6 @@ path = []; | ||
this.root = this.config.clone(this.root); | ||
this.applyQueuedMorphs(); | ||
return this.hasError() ? this.errors : this.root; | ||
} | ||
applyQueuedMorphs() { | ||
// invoking morphs that are Nodes will reuse this context, potentially | ||
@@ -40,40 +44,52 @@ // adding additional morphs, so we have to continue looping until | ||
while (this.queuedMorphs.length) { | ||
const { path, morphs } = this.queuedMorphs.shift(); | ||
// even if we already have an error, apply morphs that are not at a path | ||
// with errors to capture potential validation errors | ||
if (this.hasError()) { | ||
const morphPropString = pathToPropString(path); | ||
if (this.errors.some(e => morphPropString.startsWith(e.propString))) | ||
const queuedMorphs = this.queuedMorphs; | ||
this.queuedMorphs = []; | ||
for (const { path, morphs } of queuedMorphs) { | ||
// even if we already have an error, apply morphs that are not at a path | ||
// with errors to capture potential validation errors | ||
if (this.pathHasError(path)) | ||
continue; | ||
this.applyMorphsAtPath(path, morphs); | ||
} | ||
const key = path.at(-1); | ||
let parent; | ||
if (key !== undefined) { | ||
// find the object on which the key to be morphed exists | ||
parent = this.root; | ||
for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) | ||
parent = parent[path[pathIndex]]; | ||
} | ||
} | ||
applyMorphsAtPath(path, morphs) { | ||
const key = path.at(-1); | ||
let parent; | ||
if (key !== undefined) { | ||
// find the object on which the key to be morphed exists | ||
parent = this.root; | ||
for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) | ||
parent = parent[path[pathIndex]]; | ||
} | ||
this.path = path; | ||
for (const morph of morphs) { | ||
const morphIsNode = isNode(morph); | ||
const result = morph(parent === undefined ? this.root : parent[key], this); | ||
if (result instanceof ArkError) { | ||
// if an ArkError was returned, ensure it has been added to errors | ||
this.errors.add(result); | ||
// skip any remaining morphs at the current path | ||
break; | ||
} | ||
this.path = path; | ||
for (const morph of morphs) { | ||
const result = morph(parent === undefined ? this.root : parent[key], this); | ||
if (result instanceof ArkError) { | ||
// if an ArkError was returned but wasn't added to these | ||
// errors, add it | ||
if (!this.errors.includes(result)) | ||
this.error(result); | ||
if (result instanceof ArkErrors) { | ||
// if the morph was a direct reference to another node, | ||
// errors will have been added directly via this piped context | ||
if (!morphIsNode) { | ||
// otherwise, we have to ensure each error has been added | ||
this.errors.merge(result); | ||
} | ||
else if (!(result instanceof ArkErrors)) { | ||
// if the morph was successful, assign the result to the | ||
// corresponding property, or to root if path is empty | ||
if (parent === undefined) | ||
this.root = result; | ||
else | ||
parent[key] = result; | ||
} | ||
// if ArkErrors was returned, the morph itself was likely a type | ||
// and the errors will have been added directly via this piped context | ||
// skip any remaining morphs at the current path | ||
break; | ||
} | ||
// if the morph was successful, assign the result to the | ||
// corresponding property, or to root if path is empty | ||
if (parent === undefined) | ||
this.root = result; | ||
else | ||
parent[key] = result; | ||
// if the current morph queued additional morphs, | ||
// applying them before subsequent morphs | ||
this.applyQueuedMorphs(); | ||
} | ||
return this.hasError() ? this.errors : this.root; | ||
} | ||
@@ -90,2 +106,8 @@ get currentErrorCount() { | ||
} | ||
pathHasError(path) { | ||
if (!this.hasError()) | ||
return false; | ||
const propString = pathToPropString(path); | ||
return this.errors.some(e => propString.startsWith(e.propString)); | ||
} | ||
get failFast() { | ||
@@ -92,0 +114,0 @@ return this.branches.length !== 0; |
@@ -6,3 +6,3 @@ import { append, printable, stringAndSymbolicEntriesOf, throwParseError } from "@ark/util"; | ||
import { implementNode } from "../shared/implement.js"; | ||
import { intersectNodes } from "../shared/intersections.js"; | ||
import { intersectOrPipeNodes } from "../shared/intersections.js"; | ||
import { $ark } from "../shared/registry.js"; | ||
@@ -40,3 +40,3 @@ const implementation = implementNode({ | ||
if (l.signature.equals(r.signature)) { | ||
const valueIntersection = intersectNodes(l.value, r.value, ctx); | ||
const valueIntersection = intersectOrPipeNodes(l.value, r.value, ctx); | ||
const value = valueIntersection instanceof Disjoint ? | ||
@@ -43,0 +43,0 @@ $ark.intrinsic.never.internal |
@@ -6,3 +6,3 @@ import { append, printable, throwParseError, unset } from "@ark/util"; | ||
import { Disjoint } from "../shared/disjoint.js"; | ||
import { intersectNodes } from "../shared/intersections.js"; | ||
import { intersectOrPipeNodes } from "../shared/intersections.js"; | ||
import { $ark } from "../shared/registry.js"; | ||
@@ -13,3 +13,3 @@ export const intersectProps = (l, r, ctx) => { | ||
const key = l.key; | ||
let value = intersectNodes(l.value, r.value, ctx); | ||
let value = intersectOrPipeNodes(l.value, r.value, ctx); | ||
const kind = l.required || r.required ? "required" : "optional"; | ||
@@ -16,0 +16,0 @@ if (value instanceof Disjoint) { |
@@ -6,3 +6,3 @@ import { append, conflatenate, throwInternalError, throwParseError } from "@ark/util"; | ||
import { implementNode } from "../shared/implement.js"; | ||
import { intersectNodes } from "../shared/intersections.js"; | ||
import { intersectOrPipeNodes } from "../shared/intersections.js"; | ||
import { writeUnsupportedJsonSchemaTypeMessage } from "../shared/jsonSchema.js"; | ||
@@ -323,3 +323,3 @@ import { $ark } from "../shared/registry.js"; | ||
} | ||
const result = intersectNodes(lHead.node, rHead.node, s.ctx); | ||
const result = intersectOrPipeNodes(lHead.node, rHead.node, s.ctx); | ||
if (result instanceof Disjoint) { | ||
@@ -326,0 +326,0 @@ if (kind === "prefix" || kind === "postfix") { |
@@ -277,6 +277,12 @@ import { append, conflatenate, flatMorph, printable, spliterate, throwParseError } from "@ark/util"; | ||
...inner, | ||
required: this.props.map(prop => prop.hasKind("optional") ? | ||
// don't include keys like default that don't exist on required | ||
this.$.node("required", { key: prop.key, value: prop.value }) | ||
: prop) | ||
required: this.props.map(prop => { | ||
if (prop.hasKind("required")) | ||
return prop; | ||
// strip default/optional meta from the value so that it | ||
// isn't reduced back to an optional prop | ||
return this.$.node("required", { | ||
key: prop.key, | ||
value: prop.value.withoutOptionalOrDefaultMeta() | ||
}); | ||
}) | ||
}); | ||
@@ -283,0 +289,0 @@ } |
{ | ||
"name": "@arktype/schema", | ||
"version": "0.15.0", | ||
"version": "0.16.0", | ||
"license": "MIT", | ||
@@ -32,3 +32,3 @@ "author": { | ||
"dependencies": { | ||
"@ark/util": "0.15.0" | ||
"@ark/util": "0.16.0" | ||
}, | ||
@@ -35,0 +35,0 @@ "publishConfig": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
350839
8203
+ Added@ark/util@0.16.0(transitive)
- Removed@ark/util@0.15.0(transitive)
Updated@ark/util@0.16.0