@arktype/schema
Advanced tools
Comparing version 0.1.5 to 0.1.6
@@ -26,3 +26,2 @@ export * from "./ast.js"; | ||
export * from "./refinements/regex.js"; | ||
export * from "./roots/discriminate.js"; | ||
export * from "./roots/intersection.js"; | ||
@@ -29,0 +28,0 @@ export * from "./roots/morph.js"; |
@@ -26,3 +26,2 @@ export * from "./ast.js"; | ||
export * from "./refinements/regex.js"; | ||
export * from "./roots/discriminate.js"; | ||
export * from "./roots/intersection.js"; | ||
@@ -29,0 +28,0 @@ export * from "./roots/morph.js"; |
@@ -20,3 +20,7 @@ import { append, appendUnique, capitalize, isArray, throwInternalError, throwParseError } from "@arktype/util"; | ||
compile(js) { | ||
js.compilePrimitive(this); | ||
if (js.traversalKind === "Allows") | ||
js.return(this.compiledCondition); | ||
else { | ||
js.if(this.compiledNegation, () => js.line(`${js.ctx}.error(${this.compiledErrorContext})`)); | ||
} | ||
} | ||
@@ -23,0 +27,0 @@ get errorContext() { |
@@ -28,3 +28,3 @@ import type { Constructor, ErrorMessage, NonEnumerableDomain, array, conform, describe, inferDomain, instanceOf, isAny } from "@arktype/util"; | ||
type validateRootBranch<schema, $> = schema extends BaseNode ? schema : "morphs" extends keyof schema ? validateMorphRoot<schema, $> : validateMorphChild<schema, $>; | ||
type inferRootBranch<schema, $> = schema extends type.cast<infer to> ? to : schema extends MorphSchema ? (In: schema["in"] extends {} ? inferMorphChild<schema["in"], $> : unknown) => schema["out"] extends {} ? Out<inferMorphChild<schema["out"], $>> : schema["morphs"] extends infer morph extends Morph ? Out<inferMorphOut<morph>> : schema["morphs"] extends (readonly [...unknown[], infer morph extends Morph]) ? Out<inferMorphOut<morph>> : never : schema extends MorphInputSchema ? inferMorphChild<schema, $> : unknown; | ||
type inferRootBranch<schema, $> = schema extends type.cast<infer to> ? to : schema extends MorphSchema ? (In: schema["in"] extends {} ? inferMorphChild<schema["in"], $> : unknown) => schema["morphs"] extends infer morph extends Morph ? Out<inferMorphOut<morph>> : schema["morphs"] extends (readonly [...unknown[], infer morph extends Morph]) ? Out<inferMorphOut<morph>> : never : schema extends MorphInputSchema ? inferMorphChild<schema, $> : unknown; | ||
type NonIntersectableBasisRoot = NonEnumerableDomain | Constructor | UnitSchema; | ||
@@ -31,0 +31,0 @@ type validateMorphChild<schema, $> = [ |
import type { Key } from "@arktype/util"; | ||
import type { SchemaModule } from "../module.js"; | ||
import "./tsKeywords.js"; | ||
export interface internalKeywordExports { | ||
lengthBoundable: string | unknown[]; | ||
propertyKey: Key; | ||
nonNegativeIntegerString: string; | ||
} | ||
export type internalKeywords = SchemaModule<internalKeywordExports>; | ||
export declare const internalKeywords: internalKeywords; |
import { root, schemaScope } from "../scope.js"; | ||
// these are needed to create some internal types | ||
import { arrayIndexMatcher } from "../structure/shared.js"; | ||
import "./tsKeywords.js"; | ||
export const internalKeywords = schemaScope({ | ||
lengthBoundable: ["string", Array], | ||
propertyKey: ["string", "symbol"] | ||
propertyKey: ["string", "symbol"], | ||
nonNegativeIntegerString: { domain: "string", regex: arrayIndexMatcher } | ||
}, { | ||
@@ -6,0 +10,0 @@ prereducedAliases: true, |
@@ -9,5 +9,5 @@ /// <reference types="node" resolution-mode="require"/> | ||
date: (In: string) => Out<Date>; | ||
json: (In: string) => Out<unknown>; | ||
json: (In: string) => Out<object>; | ||
}; | ||
export type parsing = SchemaModule<parsingExports>; | ||
export declare const parsing: parsing; |
import { isWellFormedInteger, wellFormedIntegerMatcher, wellFormedNumberMatcher } from "@arktype/util"; | ||
import { root, schemaScope } from "../scope.js"; | ||
import { tryParseDatePattern } from "./utils/date.js"; | ||
import { defineRegex } from "./utils/regex.js"; | ||
const number = root.defineRoot({ | ||
in: { | ||
domain: "string", | ||
regex: wellFormedNumberMatcher, | ||
description: "a well-formed numeric string" | ||
}, | ||
in: defineRegex(wellFormedNumberMatcher, "a well-formed numeric string"), | ||
morphs: (s) => Number.parseFloat(s) | ||
}); | ||
const integer = root.defineRoot({ | ||
in: { | ||
domain: "string", | ||
regex: wellFormedIntegerMatcher | ||
}, | ||
in: defineRegex(wellFormedIntegerMatcher, "a well-formed integer string"), | ||
morphs: (s, ctx) => { | ||
@@ -25,6 +19,3 @@ if (!isWellFormedInteger(s)) | ||
const url = root.defineRoot({ | ||
in: { | ||
domain: "string", | ||
description: "a valid URL" | ||
}, | ||
in: "string", | ||
morphs: (s, ctx) => { | ||
@@ -40,7 +31,11 @@ try { | ||
const json = root.defineRoot({ | ||
in: { | ||
domain: "string", | ||
description: "a JSON-parsable string" | ||
}, | ||
morphs: (s) => JSON.parse(s) | ||
in: "string", | ||
morphs: (s, ctx) => { | ||
try { | ||
return JSON.parse(s); | ||
} | ||
catch { | ||
return ctx.error("a valid JSON string"); | ||
} | ||
} | ||
}); | ||
@@ -47,0 +42,0 @@ const date = root.defineRoot({ |
@@ -12,2 +12,3 @@ import type { SchemaModule } from "../module.js"; | ||
semver: string; | ||
ip: string; | ||
integer: number; | ||
@@ -14,0 +15,0 @@ } |
import { root, schemaScope } from "../scope.js"; | ||
import { creditCardMatcher, isLuhnValid } from "./utils/creditCard.js"; | ||
import { ip } from "./utils/ip.js"; | ||
import { defineRegex } from "./utils/regex.js"; | ||
// Non-trivial expressions should have an explanation or attribution | ||
@@ -21,27 +23,9 @@ const url = root.defineRoot({ | ||
const emailMatcher = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/; | ||
const email = root.defineRoot({ | ||
domain: "string", | ||
regex: { | ||
rule: emailMatcher.source, | ||
description: "a valid email" | ||
} | ||
}); | ||
const email = defineRegex(emailMatcher, "a valid email"); | ||
const uuidMatcher = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/; | ||
// https://github.com/validatorjs/validator.js/blob/master/src/lib/isUUID.js | ||
const uuid = root.defineRoot({ | ||
domain: "string", | ||
regex: { | ||
rule: uuidMatcher.source, | ||
description: "a valid UUID" | ||
} | ||
}); | ||
const uuid = defineRegex(uuidMatcher, "a valid UUID"); | ||
const semverMatcher = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; | ||
// https://semver.org/ | ||
const semver = root.defineRoot({ | ||
domain: "string", | ||
regex: { | ||
rule: semverMatcher.source, | ||
description: "a valid semantic version (see https://semver.org/)" | ||
} | ||
}); | ||
const semver = defineRegex(semverMatcher, "a valid semantic version (see https://semver.org/)"); | ||
const creditCard = root.defineRoot({ | ||
@@ -59,28 +43,6 @@ domain: "string", | ||
export const validation = schemaScope({ | ||
alpha: { | ||
domain: "string", | ||
regex: /^[A-Za-z]*$/, | ||
description: "only letters" | ||
}, | ||
alphanumeric: { | ||
domain: "string", | ||
regex: { | ||
rule: /^[A-Za-z\d]*$/.source, | ||
description: "only letters and digits" | ||
} | ||
}, | ||
lowercase: { | ||
domain: "string", | ||
regex: { | ||
rule: /^[a-z]*$/.source, | ||
description: "only lowercase letters" | ||
} | ||
}, | ||
uppercase: { | ||
domain: "string", | ||
regex: { | ||
rule: /^[A-Za-z]*$/.source, | ||
description: "only uppercase letters" | ||
} | ||
}, | ||
alpha: defineRegex(/^[A-Za-z]*$/, "only letters"), | ||
alphanumeric: defineRegex(/^[A-Za-z\d]*$/, "only letters and digits"), | ||
lowercase: defineRegex(/^[a-z]*$/, "only lowercase letters"), | ||
uppercase: defineRegex(/^[A-Z]*$/, "only uppercase letters"), | ||
creditCard, | ||
@@ -91,2 +53,3 @@ email, | ||
semver, | ||
ip, | ||
integer: { | ||
@@ -93,0 +56,0 @@ domain: "number", |
@@ -1,2 +0,2 @@ | ||
import { Callable, type Guardable, type Json, type conform } from "@arktype/util"; | ||
import { Callable, type Guardable, type Json, type Key, type conform } from "@arktype/util"; | ||
import type { BaseConstraint } from "./constraint.js"; | ||
@@ -25,6 +25,4 @@ import type { Inner, Node, reducibleKindOf } from "./kinds.js"; | ||
readonly allowsRequiresContext: boolean; | ||
readonly referencesByName: Record<string, BaseNode>; | ||
readonly references: readonly BaseNode[]; | ||
readonly contributesReferencesById: Record<string, BaseNode>; | ||
readonly contributesReferences: readonly BaseNode[]; | ||
readonly referencesById: Record<string, BaseNode>; | ||
get references(): BaseNode[]; | ||
readonly precedence: number; | ||
@@ -59,12 +57,18 @@ jit: boolean; | ||
firstReferenceOfKindOrThrow<kind extends NodeKind>(kind: kind): Node<kind>; | ||
transform(mapper: DeepNodeTransformation, shouldTransform: ShouldTransformFn): Node<reducibleKindOf<this["kind"]>>; | ||
private _transform; | ||
transform<mapper extends DeepNodeTransformation>(mapper: mapper, opts?: DeepNodeTransformOptions): Node<reducibleKindOf<this["kind"]>> | Extract<ReturnType<mapper>, null>; | ||
protected _transform(mapper: DeepNodeTransformation, ctx: DeepNodeTransformationContext): BaseNode | null; | ||
configureShallowDescendants(configOrDescription: BaseMeta | string): this; | ||
} | ||
export type DeepNodeTransformOptions = { | ||
shouldTransform: ShouldTransformFn; | ||
}; | ||
export type ShouldTransformFn = (node: BaseNode, ctx: DeepNodeTransformationContext) => boolean; | ||
export type DeepNodeTransformationContext = { | ||
/** a literal key or a node representing the key of an index signature */ | ||
path: Array<Key | BaseNode>; | ||
seen: { | ||
[originalId: string]: (() => BaseNode) | undefined; | ||
[originalId: string]: (() => BaseNode | undefined) | undefined; | ||
}; | ||
shouldTransform: ShouldTransformFn; | ||
}; | ||
export type DeepNodeTransformation = <kind extends NodeKind>(kind: kind, inner: Inner<kind>, ctx: DeepNodeTransformationContext) => Inner<kind>; | ||
export type DeepNodeTransformation = <kind extends NodeKind>(kind: kind, inner: Inner<kind>, ctx: DeepNodeTransformationContext) => Inner<kind> | null; |
@@ -1,2 +0,2 @@ | ||
import { Callable, flatMorph, includes, isArray, shallowClone, throwError } from "@arktype/util"; | ||
import { Callable, flatMorph, includes, isArray, isEmptyObject, shallowClone, throwError } from "@arktype/util"; | ||
import { basisKinds, constraintKinds, precedenceOfKind, refinementKinds, rootKinds } from "./shared/implement.js"; | ||
@@ -7,3 +7,6 @@ import { TraversalContext } from "./shared/traversal.js"; | ||
constructor(attachments) { | ||
super((data) => { | ||
super( | ||
// pipedFromCtx allows us internally to reuse TraversalContext | ||
// through pipes and keep track of piped paths. It is not exposed | ||
(data, pipedFromCtx) => { | ||
if (!this.includesMorph && | ||
@@ -13,2 +16,4 @@ !this.allowsRequiresContext && | ||
return data; | ||
if (pipedFromCtx) | ||
return this.traverseApply(data, pipedFromCtx); | ||
const ctx = new TraversalContext(data, this.$.resolvedConfig); | ||
@@ -19,7 +24,2 @@ this.traverseApply(data, ctx); | ||
this.attachments = attachments; | ||
this.contributesReferencesById = | ||
this.id in this.referencesByName ? | ||
this.referencesByName | ||
: { ...this.referencesByName, [this.id]: this }; | ||
this.contributesReferences = Object.values(this.contributesReferencesById); | ||
} | ||
@@ -36,6 +36,6 @@ qualifiedId = `${this.$.id}${this.id}`; | ||
this.children.some(child => child.allowsRequiresContext); | ||
referencesByName = this.children.reduce((result, child) => Object.assign(result, child.contributesReferencesById), {}); | ||
references = Object.values(this.referencesByName); | ||
contributesReferencesById; | ||
contributesReferences; | ||
referencesById = this.children.reduce((result, child) => Object.assign(result, child.referencesById), { [this.id]: this }); | ||
get references() { | ||
return Object.values(this.referencesById); | ||
} | ||
precedence = precedenceOfKind(this.kind); | ||
@@ -77,6 +77,6 @@ jit = false; | ||
for (const [k, v] of this.entries) { | ||
const keySchemainition = this.impl.keys[k]; | ||
if (keySchemainition.meta) | ||
const keySchemaImplementation = this.impl.keys[k]; | ||
if (keySchemaImplementation.meta) | ||
continue; | ||
if (keySchemainition.child) { | ||
if (keySchemaImplementation.child) { | ||
const childValue = v; | ||
@@ -132,3 +132,3 @@ ioInner[k] = | ||
firstReference(filter) { | ||
return this.references.find(filter); | ||
return this.references.find(n => n !== this && filter(n)); | ||
} | ||
@@ -146,6 +146,10 @@ firstReferenceOrThrow(filter) { | ||
} | ||
transform(mapper, shouldTransform) { | ||
return this._transform(mapper, shouldTransform, { seen: {} }); | ||
transform(mapper, opts) { | ||
return this._transform(mapper, { | ||
seen: {}, | ||
path: [], | ||
shouldTransform: opts?.shouldTransform ?? (() => true) | ||
}); | ||
} | ||
_transform(mapper, shouldTransform, ctx) { | ||
_transform(mapper, ctx) { | ||
if (ctx.seen[this.id]) | ||
@@ -156,16 +160,38 @@ // TODO: remove cast by making lazilyResolve more flexible | ||
return this.$.lazilyResolve(ctx.seen[this.id]); | ||
if (!shouldTransform(this, ctx)) | ||
if (!ctx.shouldTransform(this, ctx)) | ||
return this; | ||
ctx.seen[this.id] = () => node; | ||
const innerWithTransformedChildren = flatMorph(this.inner, (k, v) => [ | ||
k, | ||
this.impl.keys[k].child ? | ||
isArray(v) ? | ||
v.map(node => node._transform(mapper, shouldTransform, ctx)) | ||
: v._transform(mapper, shouldTransform, ctx) | ||
: v | ||
]); | ||
let transformedNode; | ||
ctx.seen[this.id] = () => transformedNode; | ||
const innerWithTransformedChildren = flatMorph(this.inner, (k, v) => { | ||
if (!this.impl.keys[k].child) | ||
return [k, v]; | ||
const children = v; | ||
if (!isArray(children)) { | ||
const transformed = children._transform(mapper, ctx); | ||
return transformed ? [k, transformed] : []; | ||
} | ||
const transformed = children.flatMap(n => { | ||
const transformedChild = n._transform(mapper, ctx); | ||
return transformedChild ?? []; | ||
}); | ||
return transformed.length ? [k, transformed] : []; | ||
}); | ||
delete ctx.seen[this.id]; | ||
const node = this.$.node(this.kind, mapper(this.kind, innerWithTransformedChildren, ctx)); | ||
return node; | ||
const transformedInner = mapper(this.kind, innerWithTransformedChildren, ctx); | ||
if (transformedInner === null) | ||
return null; | ||
// TODO: more robust checks for pruned inner | ||
if (isEmptyObject(transformedInner)) | ||
return null; | ||
if ((this.kind === "required" || | ||
this.kind === "optional" || | ||
this.kind === "index") && | ||
!("value" in transformedInner)) | ||
return null; | ||
if (this.kind === "morph") { | ||
; | ||
transformedInner.in ??= this.$.keywords | ||
.unknown; | ||
} | ||
return (transformedNode = this.$.node(this.kind, transformedInner)); | ||
} | ||
@@ -176,4 +202,6 @@ configureShallowDescendants(configOrDescription) { | ||
: configOrDescription; | ||
return this.transform((kind, inner) => ({ ...inner, ...config }), node => node.kind !== "structure"); | ||
return this.transform((kind, inner) => ({ ...inner, ...config }), { | ||
shouldTransform: node => node.kind !== "structure" | ||
}); | ||
} | ||
} |
@@ -17,3 +17,3 @@ import { RawPrimitiveConstraint } from "../constraint.js"; | ||
exactLength: (l, r, ctx) => new Disjoint({ | ||
"[length]": { | ||
'["length"]': { | ||
unit: { | ||
@@ -20,0 +20,0 @@ l: ctx.$.node("unit", { unit: l.rule }), |
@@ -18,4 +18,8 @@ import { compileErrorContext } from "../shared/implement.js"; | ||
compile(js) { | ||
js.compilePrimitive(this); | ||
if (js.traversalKind === "Allows") | ||
js.return(this.compiledCondition); | ||
else { | ||
js.if(this.compiledNegation, () => js.line(`${js.ctx}.error(${this.compiledErrorContext})`)); | ||
} | ||
} | ||
} |
@@ -227,5 +227,3 @@ import { flatMorph, hasDomain, isEmptyObject, isKeyOf, omit, pick, throwParseError } from "@arktype/util"; | ||
intersections: { | ||
intersection: (l, r, ctx) => { | ||
return intersectIntersections(l, r, ctx); | ||
}, | ||
intersection: (l, r, ctx) => intersectIntersections(l, r, ctx), | ||
...defineRightwardIntersections("intersection", (l, r, ctx) => { | ||
@@ -232,0 +230,0 @@ // if l is unknown, return r |
import { type BuiltinObjectKind, type BuiltinObjects, type Primitive, type anyOrNever, type array, type listable, type show } from "@arktype/util"; | ||
import type { of } from "../ast.js"; | ||
import type { type } from "../inference.js"; | ||
import type { Node, NodeSchema, RootSchema } from "../kinds.js"; | ||
import type { Node, NodeSchema } from "../kinds.js"; | ||
import type { StaticArkOption } from "../scope.js"; | ||
@@ -13,3 +13,3 @@ import type { NodeCompiler } from "../shared/compile.js"; | ||
import type { DefaultableAst } from "../structure/optional.js"; | ||
import { BaseRoot, type schemaKindRightOf } from "./root.js"; | ||
import { BaseRoot, type Root, type schemaKindRightOf } from "./root.js"; | ||
export type MorphInputKind = schemaKindRightOf<"morph">; | ||
@@ -23,9 +23,7 @@ export type MorphInputNode = Node<MorphInputKind>; | ||
readonly in: MorphInputNode; | ||
readonly out?: BaseRoot; | ||
readonly morphs: readonly Morph[]; | ||
readonly morphs: array<Morph | Root>; | ||
} | ||
export interface MorphSchema extends BaseMeta { | ||
readonly in: MorphInputSchema; | ||
readonly out?: RootSchema | undefined; | ||
readonly morphs: listable<Morph>; | ||
readonly morphs: listable<Morph | Root>; | ||
} | ||
@@ -44,5 +42,2 @@ export interface MorphDeclaration extends declareNode<{ | ||
compiledMorphs: string; | ||
outValidator: TraverseApply | null; | ||
private queueArgs; | ||
private queueArgsReference; | ||
traverseAllows: TraverseAllows; | ||
@@ -53,2 +48,3 @@ traverseApply: TraverseApply; | ||
get in(): BaseRoot; | ||
get validatedOut(): BaseRoot | undefined; | ||
get out(): BaseRoot; | ||
@@ -55,0 +51,0 @@ rawKeyOf(): BaseRoot; |
@@ -5,2 +5,3 @@ import { arrayFrom, registeredReference, throwParseError } from "@arktype/util"; | ||
import { intersectNodes } from "../shared/intersections.js"; | ||
import { hasArkKind } from "../shared/utils.js"; | ||
import { BaseRoot } from "./root.js"; | ||
@@ -22,17 +23,5 @@ import { defineRightwardIntersections } from "./utils.js"; | ||
}, | ||
out: { | ||
child: true, | ||
parse: (schema, ctx) => { | ||
if (schema === undefined) | ||
return; | ||
const out = ctx.$.schema(schema); | ||
return out.kind === "intersection" && out.children.length === 0 ? | ||
// ignore unknown as an output validator | ||
undefined | ||
: out; | ||
} | ||
}, | ||
morphs: { | ||
parse: arrayFrom, | ||
serialize: morphs => morphs.map(registeredReference) | ||
serialize: morphs => morphs.map(m => hasArkKind(m, "root") ? m.json : registeredReference(m)) | ||
} | ||
@@ -52,9 +41,2 @@ }, | ||
return inTersection; | ||
const out = l.out ? | ||
r.out ? | ||
intersectNodes(l.out, r.out, ctx) | ||
: l.out | ||
: r.out; | ||
if (out instanceof Disjoint) | ||
return out; | ||
// in case from is a union, we need to distribute the branches | ||
@@ -64,4 +46,3 @@ // to can be a union as any schema is allowed | ||
morphs: l.morphs, | ||
in: inBranch, | ||
out | ||
in: inBranch | ||
}))); | ||
@@ -85,13 +66,7 @@ }, | ||
export class MorphNode extends BaseRoot { | ||
serializedMorphs = this.json.morphs; | ||
serializedMorphs = this.morphs.map(registeredReference); | ||
compiledMorphs = `[${this.serializedMorphs}]`; | ||
outValidator = this.inner.out?.traverseApply ?? null; | ||
queueArgs = [ | ||
this.morphs, | ||
this.outValidator ? { outValidator: this.outValidator } : {} | ||
]; | ||
queueArgsReference = registeredReference(this.queueArgs); | ||
traverseAllows = (data, ctx) => this.in.traverseAllows(data, ctx); | ||
traverseApply = (data, ctx) => { | ||
ctx.queueMorphs(...this.queueArgs); | ||
ctx.queueMorphs(this.morphs); | ||
this.in.traverseApply(data, ctx); | ||
@@ -105,3 +80,3 @@ }; | ||
} | ||
js.line(`ctx.queueMorphs(...${this.queueArgsReference})`); | ||
js.line(`ctx.queueMorphs(${this.compiledMorphs})`); | ||
js.line(js.invoke(this.in)); | ||
@@ -112,4 +87,10 @@ } | ||
} | ||
get validatedOut() { | ||
const lastMorph = this.inner.morphs.at(-1); | ||
return hasArkKind(lastMorph, "root") ? | ||
lastMorph?.out | ||
: undefined; | ||
} | ||
get out() { | ||
return this.inner.out?.out ?? this.$.keywords.unknown.raw; | ||
return this.validatedOut ?? this.$.keywords.unknown.raw; | ||
} | ||
@@ -116,0 +97,0 @@ rawKeyOf() { |
@@ -31,3 +31,3 @@ import { builtinConstructors, constructorExtends, getExactBuiltinConstructorName, objectKindDescriptions, objectKindOrDomainOf, prototypeKeysOf } from "@arktype/util"; | ||
proto | ||
: Disjoint.from("domain", ctx.$.keywords.object, domain) | ||
: Disjoint.from("domain", ctx.$.keywords.object.raw, domain) | ||
} | ||
@@ -34,0 +34,0 @@ }); |
@@ -119,4 +119,4 @@ import { includes, omit, throwParseError } from "@arktype/util"; | ||
: { ...inner, undeclared } | ||
: inner, node => !includes(structuralKinds, node.kind)); | ||
: inner, { shouldTransform: node => !includes(structuralKinds, node.kind) }); | ||
} | ||
} |
@@ -0,1 +1,2 @@ | ||
import { type Domain, type Json, type SerializedPrimitive, type show } from "@arktype/util"; | ||
import type { Node, NodeSchema } from "../kinds.js"; | ||
@@ -8,3 +9,5 @@ import type { NodeCompiler } from "../shared/compile.js"; | ||
import type { TraverseAllows, TraverseApply } from "../shared/traversal.js"; | ||
import type { TraversalPath } from "../shared/utils.js"; | ||
import { BaseRoot, type schemaKindRightOf } from "./root.js"; | ||
import type { UnitNode } from "./unit.js"; | ||
export type UnionChildKind = schemaKindRightOf<"union"> | "alias"; | ||
@@ -38,3 +41,5 @@ export type UnionChildSchema = NodeSchema<UnionChildKind>; | ||
isBoolean: boolean; | ||
discriminant: null; | ||
unitBranches: UnitNode[]; | ||
discriminant: Discriminant<DiscriminantKind> | null; | ||
discriminantJson: Json | null; | ||
expression: string; | ||
@@ -44,6 +49,23 @@ traverseAllows: TraverseAllows; | ||
compile(js: NodeCompiler): void; | ||
private compileIndiscriminable; | ||
rawKeyOf(): BaseRoot; | ||
get nestableExpression(): string; | ||
discriminate(): Discriminant | null; | ||
} | ||
export declare const intersectBranches: (l: readonly UnionChildNode[], r: readonly UnionChildNode[], ctx: IntersectionContext) => readonly UnionChildNode[] | Disjoint; | ||
export declare const reduceBranches: ({ branches, ordered }: UnionInner) => readonly UnionChildNode[]; | ||
export type CaseKey<kind extends DiscriminantKind = DiscriminantKind> = DiscriminantKind extends kind ? string : DiscriminantKinds[kind] | "default"; | ||
export type Discriminant<kind extends DiscriminantKind = DiscriminantKind> = { | ||
path: string[]; | ||
kind: kind; | ||
cases: DiscriminatedCases<kind>; | ||
}; | ||
export type DiscriminatedCases<kind extends DiscriminantKind = DiscriminantKind> = { | ||
[caseKey in CaseKey<kind>]: BaseRoot | true; | ||
}; | ||
export type DiscriminantKinds = { | ||
domain: Domain; | ||
unit: SerializedPrimitive; | ||
}; | ||
export type DiscriminantKind = show<keyof DiscriminantKinds>; | ||
export declare const pruneDiscriminant: (discriminantKind: DiscriminantKind, path: TraversalPath, branch: BaseRoot) => BaseRoot | null; |
@@ -1,2 +0,36 @@ | ||
import { appendUnique, groupBy, isArray } from "@arktype/util"; | ||
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { | ||
var useValue = arguments.length > 2; | ||
for (var i = 0; i < initializers.length; i++) { | ||
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); | ||
} | ||
return useValue ? value : void 0; | ||
}; | ||
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { | ||
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } | ||
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; | ||
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; | ||
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); | ||
var _, done = false; | ||
for (var i = decorators.length - 1; i >= 0; i--) { | ||
var context = {}; | ||
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; | ||
for (var p in contextIn.access) context.access[p] = contextIn.access[p]; | ||
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; | ||
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); | ||
if (kind === "accessor") { | ||
if (result === void 0) continue; | ||
if (result === null || typeof result !== "object") throw new TypeError("Object expected"); | ||
if (_ = accept(result.get)) descriptor.get = _; | ||
if (_ = accept(result.set)) descriptor.set = _; | ||
if (_ = accept(result.init)) initializers.unshift(_); | ||
} | ||
else if (_ = accept(result)) { | ||
if (kind === "field") initializers.unshift(_); | ||
else descriptor[key] = _; | ||
} | ||
} | ||
if (target) Object.defineProperty(target, contextIn.name, descriptor); | ||
done = true; | ||
}; | ||
import { appendUnique, cached, compileLiteralPropAccess, domainDescriptions, entriesOf, flatMorph, groupBy, isArray, isKeyOf, printable, throwInternalError } from "@arktype/util"; | ||
import { Disjoint } from "../shared/disjoint.js"; | ||
@@ -40,5 +74,3 @@ import { implementNode, schemaKindsRightOf } from "../shared/implement.js"; | ||
defaults: { | ||
description: node => { | ||
return describeBranches(node.branches.map(branch => branch.description)); | ||
}, | ||
description: node => describeBranches(node.branches.map(branch => branch.description)), | ||
expected: ctx => { | ||
@@ -53,5 +85,7 @@ const byPath = groupBy(ctx.errors, "propString"); | ||
const expected = describeBranches(branchesAtPath); | ||
const actual = errors.reduce((acc, e) => e.actual && !acc.includes(e.actual) ? | ||
`${acc && `${acc}, `}${e.actual}` | ||
: acc, ""); | ||
// if there are multiple actual descriptions that differ, | ||
// just fall back to printable, which is the most specific | ||
const actual = errors.every(e => e.actual === errors[0].actual) ? | ||
errors[0].actual | ||
: printable(errors[0].data); | ||
return `${path && `${path} `}must be ${expected}${actual && ` (was ${actual})`}`; | ||
@@ -99,47 +133,198 @@ }); | ||
}); | ||
export class UnionNode extends BaseRoot { | ||
isNever = this.branches.length === 0; | ||
isBoolean = this.branches.length === 2 && | ||
this.branches[0].hasUnit(false) && | ||
this.branches[1].hasUnit(true); | ||
discriminant = null; | ||
expression = this.isNever ? "never" | ||
: this.isBoolean ? "boolean" | ||
: this.branches.map(branch => branch.nestableExpression).join(" | "); | ||
traverseAllows = (data, ctx) => this.branches.some(b => b.traverseAllows(data, ctx)); | ||
traverseApply = (data, ctx) => { | ||
const errors = []; | ||
for (let i = 0; i < this.branches.length; i++) { | ||
ctx.pushBranch(); | ||
this.branches[i].traverseApply(data, ctx); | ||
if (!ctx.hasError()) | ||
return ctx.queuedMorphs.push(...ctx.popBranch().queuedMorphs); | ||
errors.push(ctx.popBranch().error); | ||
let UnionNode = (() => { | ||
let _classSuper = BaseRoot; | ||
let _instanceExtraInitializers = []; | ||
let _discriminate_decorators; | ||
return class UnionNode extends _classSuper { | ||
static { | ||
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0; | ||
_discriminate_decorators = [cached]; | ||
__esDecorate(this, null, _discriminate_decorators, { kind: "method", name: "discriminate", static: false, private: false, access: { has: obj => "discriminate" in obj, get: obj => obj.discriminate }, metadata: _metadata }, null, _instanceExtraInitializers); | ||
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); | ||
} | ||
ctx.error({ code: "union", errors }); | ||
}; | ||
compile(js) { | ||
if (js.traversalKind === "Apply") { | ||
js.const("errors", "[]"); | ||
this.branches.forEach(branch => js | ||
.line("ctx.pushBranch()") | ||
.line(js.invoke(branch)) | ||
.if("!ctx.hasError()", () => js.return("ctx.queuedMorphs.push(...ctx.popBranch().queuedMorphs)")) | ||
.line("errors.push(ctx.popBranch().error)")); | ||
js.line(`ctx.error({ code: "union", errors })`); | ||
isNever = (__runInitializers(this, _instanceExtraInitializers), this.branches.length === 0); | ||
isBoolean = this.branches.length === 2 && | ||
this.branches[0].hasUnit(false) && | ||
this.branches[1].hasUnit(true); | ||
unitBranches = this.branches.filter((n) => n.hasKind("unit")); | ||
discriminant = this.discriminate(); | ||
discriminantJson = this.discriminant ? discriminantToJson(this.discriminant) : null; | ||
expression = this.isNever ? "never" | ||
: this.isBoolean ? "boolean" | ||
: this.branches.map(branch => branch.nestableExpression).join(" | "); | ||
traverseAllows = (data, ctx) => this.branches.some(b => b.traverseAllows(data, ctx)); | ||
traverseApply = (data, ctx) => { | ||
const errors = []; | ||
for (let i = 0; i < this.branches.length; i++) { | ||
ctx.pushBranch(); | ||
this.branches[i].traverseApply(data, ctx); | ||
if (!ctx.hasError()) | ||
return ctx.queuedMorphs.push(...ctx.popBranch().queuedMorphs); | ||
errors.push(ctx.popBranch().error); | ||
} | ||
ctx.error({ code: "union", errors }); | ||
}; | ||
compile(js) { | ||
if (!this.discriminant || | ||
// if we have a union of two units like `boolean`, the | ||
// undiscriminated compilation will be just as fast | ||
(this.unitBranches.length === this.branches.length && | ||
this.branches.length === 2)) | ||
return this.compileIndiscriminable(js); | ||
// we need to access the path as optional so we don't throw if it isn't present | ||
const condition = this.discriminant.path.reduce((acc, segment) => acc + compileLiteralPropAccess(segment, true), this.discriminant.kind === "domain" ? "typeof data" : "data"); | ||
const cases = this.discriminant.cases; | ||
const caseKeys = Object.keys(cases); | ||
js.block(`switch(${condition})`, () => { | ||
for (const k in cases) { | ||
const v = cases[k]; | ||
const caseCondition = k === "default" ? "default" : `case ${k}`; | ||
js.line(`${caseCondition}: return ${v === true ? v : js.invoke(v)}`); | ||
} | ||
return js; | ||
}); | ||
if (js.traversalKind === "Allows") { | ||
js.return(false); | ||
return; | ||
} | ||
const expected = describeBranches(this.discriminant.kind === "domain" ? | ||
caseKeys.map(k => domainDescriptions[k.slice(1, -1)]) | ||
: caseKeys); | ||
js.line(`ctx.error({ | ||
expected: ${JSON.stringify(expected)}, | ||
actual: ${condition}, | ||
relativePath: ${JSON.stringify(this.discriminant.path)} | ||
})`); | ||
} | ||
else { | ||
this.branches.forEach(branch => js.if(`${js.invoke(branch)}`, () => js.return(true))); | ||
js.return(false); | ||
compileIndiscriminable(js) { | ||
if (js.traversalKind === "Apply") { | ||
js.const("errors", "[]"); | ||
this.branches.forEach(branch => js | ||
.line("ctx.pushBranch()") | ||
.line(js.invoke(branch)) | ||
.if("!ctx.hasError()", () => js.return("ctx.queuedMorphs.push(...ctx.popBranch().queuedMorphs)")) | ||
.line("errors.push(ctx.popBranch().error)")); | ||
js.line(`ctx.error({ code: "union", errors })`); | ||
} | ||
else { | ||
this.branches.forEach(branch => js.if(`${js.invoke(branch)}`, () => js.return(true))); | ||
js.return(false); | ||
} | ||
} | ||
} | ||
rawKeyOf() { | ||
return this.branches.reduce((result, branch) => result.and(branch.rawKeyOf()), this.$.keywords.unknown.raw); | ||
} | ||
get nestableExpression() { | ||
// avoid adding unnecessary parentheses around boolean since it's | ||
// already collapsed to a single keyword | ||
return this.isBoolean ? "boolean" : super.nestableExpression; | ||
} | ||
} | ||
rawKeyOf() { | ||
return this.branches.reduce((result, branch) => result.and(branch.rawKeyOf()), this.$.keywords.unknown.raw); | ||
} | ||
get nestableExpression() { | ||
// avoid adding unnecessary parentheses around boolean since it's | ||
// already collapsed to a single keyword | ||
return this.isBoolean ? "boolean" : super.nestableExpression; | ||
} | ||
discriminate() { | ||
if (this.branches.length < 2) | ||
return null; | ||
if (this.unitBranches.length === this.branches.length) { | ||
const cases = flatMorph(this.unitBranches, (i, unit) => [ | ||
`${unit.serializedValue}`, | ||
true | ||
]); | ||
return { | ||
path: [], | ||
kind: "unit", | ||
cases | ||
}; | ||
} | ||
const casesBySpecifier = {}; | ||
for (let lIndex = 0; lIndex < this.branches.length - 1; lIndex++) { | ||
const l = this.branches[lIndex]; | ||
for (let rIndex = lIndex + 1; rIndex < this.branches.length; rIndex++) { | ||
const r = this.branches[rIndex]; | ||
const result = intersectNodesRoot(l, r, l.$); | ||
if (!(result instanceof Disjoint)) | ||
continue; | ||
for (const { path, kind, disjoint } of result.flat) { | ||
if (!isKeyOf(kind, discriminantKinds)) | ||
continue; | ||
const qualifiedDiscriminant = `${path}${kind}`; | ||
let lSerialized; | ||
let rSerialized; | ||
if (kind === "domain") { | ||
lSerialized = `"${disjoint.l.domain}"`; | ||
rSerialized = `"${disjoint.r.domain}"`; | ||
} | ||
else if (kind === "unit") { | ||
lSerialized = disjoint.l.serializedValue; | ||
rSerialized = disjoint.r.serializedValue; | ||
} | ||
else { | ||
return throwInternalError(`Unexpected attempt to discriminate disjoint kind '${kind}'`); | ||
} | ||
if (!casesBySpecifier[qualifiedDiscriminant]) { | ||
casesBySpecifier[qualifiedDiscriminant] = { | ||
[lSerialized]: [l], | ||
[rSerialized]: [r] | ||
}; | ||
continue; | ||
} | ||
const cases = casesBySpecifier[qualifiedDiscriminant]; | ||
if (!isKeyOf(lSerialized, cases)) | ||
cases[lSerialized] = [l]; | ||
else if (!cases[lSerialized].includes(l)) | ||
cases[lSerialized].push(l); | ||
if (!isKeyOf(rSerialized, cases)) | ||
cases[rSerialized] = [r]; | ||
else if (!cases[rSerialized].includes(r)) | ||
cases[rSerialized].push(r); | ||
} | ||
} | ||
} | ||
const bestDiscriminantEntry = entriesOf(casesBySpecifier) | ||
.sort((a, b) => Object.keys(a[1]).length - Object.keys(b[1]).length) | ||
.at(-1); | ||
if (!bestDiscriminantEntry) | ||
return null; | ||
const [specifier, bestCases] = bestDiscriminantEntry; | ||
const [path, kind] = parseDiscriminantKey(specifier); | ||
let defaultBranches = [...this.branches]; | ||
const cases = flatMorph(bestCases, (k, caseBranches) => { | ||
const prunedBranches = []; | ||
defaultBranches = defaultBranches.filter(n => !caseBranches.includes(n)); | ||
for (const branch of caseBranches) { | ||
const pruned = pruneDiscriminant(kind, path, branch); | ||
// if any branch of the union has no constraints (i.e. is unknown) | ||
// return it right away | ||
if (pruned === null) | ||
return [k, true]; | ||
prunedBranches.push(pruned); | ||
} | ||
const caseNode = prunedBranches.length === 1 ? | ||
prunedBranches[0] | ||
: this.$.node("union", prunedBranches); | ||
Object.assign(this.referencesById, caseNode.referencesById); | ||
return [k, caseNode]; | ||
}); | ||
if (defaultBranches.length) { | ||
cases.default = this.$.node("union", defaultBranches, { | ||
prereduced: true | ||
}); | ||
Object.assign(this.referencesById, cases.default.referencesById); | ||
} | ||
return { | ||
kind, | ||
path, | ||
cases | ||
}; | ||
} | ||
}; | ||
})(); | ||
export { UnionNode }; | ||
const discriminantToJson = (discriminant) => ({ | ||
kind: discriminant.kind, | ||
path: discriminant.path, | ||
cases: flatMorph(discriminant.cases, (k, node) => [ | ||
k, | ||
node === true ? node | ||
: node.hasKind("union") && node.discriminantJson ? node.discriminantJson | ||
: node.json | ||
]) | ||
}); | ||
const describeBranches = (descriptions) => { | ||
@@ -164,77 +349,2 @@ if (descriptions.length === 0) | ||
}; | ||
// private static compileDiscriminatedLiteral(cases: DiscriminatedCases) { | ||
// // TODO: error messages for traversal | ||
// const caseKeys = Object.keys(cases) | ||
// if (caseKeys.length === 2) { | ||
// return `if( ${this.argName} !== ${caseKeys[0]} && ${this.argName} !== ${caseKeys[1]}) { | ||
// return false | ||
// }` | ||
// } | ||
// // for >2 literals, we fall through all cases, breaking on the last | ||
// const compiledCases = | ||
// caseKeys.map((k) => ` case ${k}:`).join("\n") + " break" | ||
// // if none of the cases are met, the check fails (this is optimal for perf) | ||
// return `switch(${this.argName}) { | ||
// ${compiledCases} | ||
// default: | ||
// return false | ||
// }` | ||
// } | ||
// private static compileIndiscriminable( | ||
// branches: readonly BranchNode[], | ||
// ctx: CompilationContext | ||
// ) { | ||
// if (branches.length === 0) { | ||
// return compileFailureResult("custom", "nothing", ctx) | ||
// } | ||
// if (branches.length === 1) { | ||
// return branches[0].compile(ctx) | ||
// } | ||
// return branches | ||
// .map( | ||
// (branch) => `(() => { | ||
// ${branch.compile(ctx)} | ||
// return true | ||
// })()` | ||
// ) | ||
// .join(" || ") | ||
// } | ||
// private static compileDiscriminant( | ||
// discriminant: Discriminant, | ||
// ctx: CompilationContext | ||
// ) { | ||
// if (discriminant.isPureRootLiteral) { | ||
// // TODO: ctx? | ||
// return this.compileDiscriminatedLiteral(discriminant.cases) | ||
// } | ||
// let compiledPath = this.argName | ||
// for (const segment of discriminant.path) { | ||
// // we need to access the path as optional so we don't throw if it isn't present | ||
// compiledPath += compilePropAccess(segment, true) | ||
// } | ||
// const condition = | ||
// discriminant.kind === "domain" ? `typeof ${compiledPath}` : compiledPath | ||
// let compiledCases = "" | ||
// for (const k in discriminant.cases) { | ||
// const caseCondition = k === "default" ? "default" : `case ${k}` | ||
// const caseBranches = discriminant.cases[k] | ||
// ctx.discriminants.push(discriminant) | ||
// const caseChecks = isArray(caseBranches) | ||
// ? this.compileIndiscriminable(caseBranches, ctx) | ||
// : this.compileDiscriminant(caseBranches, ctx) | ||
// ctx.discriminants.pop() | ||
// compiledCases += `${caseCondition}: { | ||
// ${caseChecks ? `${caseChecks}\n break` : "break"} | ||
// }` | ||
// } | ||
// if (!discriminant.cases.default) { | ||
// // TODO: error message for traversal | ||
// compiledCases += `default: { | ||
// return false | ||
// }` | ||
// } | ||
// return `switch(${condition}) { | ||
// ${compiledCases} | ||
// }` | ||
// } | ||
export const intersectBranches = (l, r, ctx) => { | ||
@@ -329,1 +439,38 @@ // If the corresponding r branch is identified as a subtype of an l branch, the | ||
}; | ||
const discriminantKinds = { | ||
domain: 1, | ||
unit: 1 | ||
}; | ||
const parseDiscriminantKey = (key) => { | ||
const lastPathIndex = key.lastIndexOf("]"); | ||
const parsedPath = JSON.parse(key.slice(0, lastPathIndex + 1)); | ||
const parsedKind = key.slice(lastPathIndex + 1); | ||
return [parsedPath, parsedKind]; | ||
}; | ||
export const pruneDiscriminant = (discriminantKind, path, branch) => branch.transform((nodeKind, inner, ctx) => { | ||
// if we've already checked a path at least as long as the current one, | ||
// we don't need to revalidate that we're in an object | ||
if (nodeKind === "domain" && | ||
inner.domain === "object" && | ||
path.length > ctx.path.length) | ||
return null; | ||
// if the discriminant has already checked the domain at the current path | ||
// (or an exact value, implying a domain), we don't need to recheck it | ||
if ((discriminantKind === nodeKind || | ||
(nodeKind === "domain" && ctx.path.length === path.length)) && | ||
ctx.path.length === path.length && | ||
ctx.path.every((segment, i) => segment === path[i])) | ||
return null; | ||
return inner; | ||
}, { | ||
shouldTransform: node => node.children.length !== 0 || | ||
node.kind === "domain" || | ||
node.kind === "unit" | ||
}); | ||
// // TODO: if deeply includes morphs? | ||
// const writeUndiscriminableMorphUnionMessage = <path extends string>( | ||
// path: path | ||
// ) => | ||
// `${ | ||
// path === "/" ? "A" : `At ${path}, a` | ||
// } union including one or more morphs must be discriminable` as const |
@@ -19,7 +19,10 @@ import { domainOf, printable, prototypeKeysOf } from "@arktype/util"; | ||
defaults: { | ||
description: node => printable(node.unit) | ||
description: node => printable(node.unit), | ||
problem: ({ expected, actual }) => `${expected === actual ? `must be reference equal to ${expected} (serialized to the same value)` : `must be ${expected} (was ${actual})`}` | ||
}, | ||
intersections: { | ||
unit: (l, r) => Disjoint.from("unit", l, r), | ||
...defineRightwardIntersections("unit", (l, r) => r.allows(l.unit) ? l : Disjoint.from("assignability", l.unit, r)) | ||
...defineRightwardIntersections("unit", (l, r) => r.allows(l.unit) ? l : (Disjoint.from("assignability", l, r.hasKind("intersection") ? | ||
r.children.find(rConstraint => !rConstraint.allows(l.unit)) | ||
: r))) | ||
} | ||
@@ -26,0 +29,0 @@ }); |
@@ -35,3 +35,3 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { | ||
}; | ||
import { CompiledFunction, DynamicBase, bound, flatMorph, hasDomain, isArray, printable, throwInternalError, throwParseError } from "@arktype/util"; | ||
import { CompiledFunction, DynamicBase, bound, envHasCsp, flatMorph, hasDomain, isArray, printable, throwInternalError, throwParseError } from "@arktype/util"; | ||
import { globalConfig, mergeConfigs } from "./config.js"; | ||
@@ -49,3 +49,3 @@ import { validateUninstantiatedGenericNode } from "./generic.js"; | ||
]), { | ||
jitless: false, | ||
jitless: envHasCsp(), | ||
registerKeywords: false, | ||
@@ -222,3 +222,3 @@ prereducedAliases: false | ||
if (!this.resolvedConfig.jitless) | ||
bindCompiledScope(node.contributesReferences); | ||
bindCompiledScope(node.references); | ||
} | ||
@@ -228,3 +228,3 @@ else { | ||
// add the node as a reference | ||
Object.assign(this.referencesById, node.contributesReferencesById); | ||
Object.assign(this.referencesById, node.referencesById); | ||
} | ||
@@ -395,15 +395,13 @@ return node; | ||
}; | ||
const compileScope = (references) => { | ||
return new CompiledFunction() | ||
.block("return", js => { | ||
references.forEach(node => { | ||
const allowsCompiler = new NodeCompiler("Allows").indent(); | ||
node.compile(allowsCompiler); | ||
const applyCompiler = new NodeCompiler("Apply").indent(); | ||
node.compile(applyCompiler); | ||
js.line(`${allowsCompiler.writeMethod(`${node.id}Allows`)},`).line(`${applyCompiler.writeMethod(`${node.id}Apply`)},`); | ||
}); | ||
return js; | ||
}) | ||
.compile()(); | ||
}; | ||
const compileScope = (references) => new CompiledFunction() | ||
.block("return", js => { | ||
references.forEach(node => { | ||
const allowsCompiler = new NodeCompiler("Allows").indent(); | ||
node.compile(allowsCompiler); | ||
const applyCompiler = new NodeCompiler("Apply").indent(); | ||
node.compile(applyCompiler); | ||
js.line(`${allowsCompiler.writeMethod(`${node.id}Allows`)},`).line(`${applyCompiler.writeMethod(`${node.id}Apply`)},`); | ||
}); | ||
return js; | ||
}) | ||
.compile()(); |
import { CompiledFunction } from "@arktype/util"; | ||
import type { Node } from "../kinds.js"; | ||
import type { BaseNode } from "../node.js"; | ||
import type { Discriminant } from "../roots/discriminate.js"; | ||
import type { PrimitiveKind } from "./implement.js"; | ||
import type { Discriminant } from "../roots/union.js"; | ||
import type { TraversalKind } from "./traversal.js"; | ||
@@ -27,4 +25,3 @@ export interface InvokeOptions extends ReferenceOptions { | ||
check(node: BaseNode, opts?: InvokeOptions): this; | ||
compilePrimitive(node: Node<PrimitiveKind>): this; | ||
writeMethod(name: string): string; | ||
} |
@@ -49,24 +49,2 @@ import { CompiledFunction } from "@arktype/util"; | ||
} | ||
compilePrimitive(node) { | ||
const pathString = this.path.join(); | ||
if (node.kind === "domain" && | ||
node.domain === "object" && | ||
this.discriminants.some(d => d.path.join().startsWith(pathString))) { | ||
// if we've already checked a path at least as long as the current one, | ||
// we don't need to revalidate that we're in an object | ||
return this; | ||
} | ||
if ((node.kind === "domain" || node.kind === "unit") && | ||
this.discriminants.some(d => d.path.join() === pathString && | ||
(node.kind === "domain" ? | ||
d.kind === "domain" || d.kind === "value" | ||
: d.kind === "value"))) { | ||
// if the discriminant has already checked the domain at the current path | ||
// (or an exact value, implying a domain), we don't need to recheck it | ||
return this; | ||
} | ||
if (this.traversalKind === "Allows") | ||
return this.return(node.compiledCondition); | ||
return this.if(node.compiledNegation, () => this.line(`${this.ctx}.error(${node.compiledErrorContext})`)); | ||
} | ||
writeMethod(name) { | ||
@@ -73,0 +51,0 @@ return `${name}(${this.argNames.join(", ")}){\n${this.body} }\n`; |
import { type entryOf } from "@arktype/util"; | ||
import type { Node } from "../kinds.js"; | ||
import type { BaseNode } from "../node.js"; | ||
import type { BaseRoot } from "../roots/root.js"; | ||
import type { BoundKind, PrimitiveKind } from "./implement.js"; | ||
import type { BoundKind } from "./implement.js"; | ||
type DisjointKinds = { | ||
@@ -27,7 +28,4 @@ domain?: { | ||
assignability?: { | ||
l: unknown; | ||
r: Node<PrimitiveKind>; | ||
} | { | ||
l: Node<PrimitiveKind>; | ||
r: unknown; | ||
l: BaseNode; | ||
r: BaseNode; | ||
}; | ||
@@ -34,0 +32,0 @@ union?: { |
@@ -65,9 +65,15 @@ import { Hkt } from "@arktype/util"; | ||
export const pipeFromMorph = (from, to, ctx) => { | ||
const out = from?.out ? intersectNodes(from.out, to, ctx) : to; | ||
if (out instanceof Disjoint) | ||
return out; | ||
const morphs = [...from.morphs]; | ||
if (from.validatedOut) { | ||
// still piped from context, so allows appending additional morphs | ||
const outIntersection = intersectNodes(from.validatedOut, to, ctx); | ||
if (outIntersection instanceof Disjoint) | ||
return outIntersection; | ||
morphs[morphs.length - 1] = outIntersection; | ||
} | ||
else | ||
morphs.push(to); | ||
return ctx.$.node("morph", { | ||
morphs: from.morphs, | ||
in: from.in, | ||
out | ||
morphs, | ||
in: from.in | ||
}); | ||
@@ -81,5 +87,4 @@ }; | ||
morphs: to.morphs, | ||
in: result, | ||
out: to.out | ||
in: result | ||
}); | ||
}; |
@@ -9,3 +9,2 @@ import type { array } from "@arktype/util"; | ||
morphs: array<Morph>; | ||
to?: TraverseApply; | ||
}; | ||
@@ -16,5 +15,2 @@ export type BranchTraversalContext = { | ||
}; | ||
export type QueueMorphOptions = { | ||
outValidator?: TraverseApply; | ||
}; | ||
export declare class TraversalContext { | ||
@@ -32,3 +28,3 @@ root: unknown; | ||
get currentBranch(): BranchTraversalContext | undefined; | ||
queueMorphs(morphs: array<Morph>, opts?: QueueMorphOptions): void; | ||
queueMorphs(morphs: array<Morph>): void; | ||
finalize(): unknown; | ||
@@ -35,0 +31,0 @@ get currentErrorCount(): number; |
@@ -17,3 +17,3 @@ import { ArkError, ArkErrors } from "./errors.js"; | ||
} | ||
queueMorphs(morphs, opts) { | ||
queueMorphs(morphs) { | ||
const input = { | ||
@@ -23,4 +23,2 @@ path: [...this.path], | ||
}; | ||
if (opts?.outValidator) | ||
input.to = opts?.outValidator; | ||
this.currentBranch?.queuedMorphs.push(input) ?? | ||
@@ -35,47 +33,31 @@ this.queuedMorphs.push(input); | ||
for (let i = 0; i < this.queuedMorphs.length; i++) { | ||
const { path, morphs, to } = this.queuedMorphs[i]; | ||
if (path.length === 0) { | ||
this.path = []; | ||
// if the morph applies to the root, just assign to it directly | ||
for (const morph of morphs) { | ||
const result = morph(out, this); | ||
if (result instanceof ArkErrors) | ||
return result; | ||
if (this.hasError()) | ||
return this.errors; | ||
if (result instanceof ArkError) { | ||
// if an ArkTypeError was returned but wasn't added to these | ||
// errors, add it then return | ||
this.error(result); | ||
return this.errors; | ||
} | ||
out = result; | ||
} | ||
} | ||
else { | ||
const { path, morphs } = this.queuedMorphs[i]; | ||
const key = path.at(-1); | ||
let parent; | ||
if (key !== undefined) { | ||
// find the object on which the key to be morphed exists | ||
let parent = out; | ||
parent = out; | ||
for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) | ||
parent = parent[path[pathIndex]]; | ||
// apply the morph function and assign the result to the corresponding property | ||
const key = path.at(-1); | ||
this.path = path; | ||
for (const morph of morphs) { | ||
const result = morph(parent[key], this); | ||
if (result instanceof ArkErrors) | ||
return result; | ||
if (this.hasError()) | ||
return this.errors; | ||
if (result instanceof ArkError) { | ||
this.error(result); | ||
return this.errors; | ||
} | ||
} | ||
this.path = path; | ||
for (const morph of morphs) { | ||
const result = morph(parent === undefined ? out : parent[key], this); | ||
if (result instanceof ArkErrors) | ||
return result; | ||
if (this.hasError()) | ||
return this.errors; | ||
if (result instanceof ArkError) { | ||
// if an ArkError was returned but wasn't added to these | ||
// errors, add it then return | ||
this.error(result); | ||
return this.errors; | ||
} | ||
// apply the morph function and assign the result to the | ||
// corresponding property, or to root if path is empty | ||
if (parent === undefined) | ||
out = result; | ||
else | ||
parent[key] = result; | ||
} | ||
} | ||
if (to) { | ||
const toCtx = new TraversalContext(out, this.config); | ||
to(out, toCtx); | ||
return toCtx.finalize(); | ||
} | ||
} | ||
@@ -82,0 +64,0 @@ } |
import { BaseConstraint } from "../constraint.js"; | ||
import type { Node, RootSchema } from "../kinds.js"; | ||
import type { DeepNodeTransformation, DeepNodeTransformationContext } from "../node.js"; | ||
import type { BaseRoot } from "../roots/root.js"; | ||
@@ -33,2 +34,3 @@ import type { BaseMeta, declareNode } from "../shared/declare.js"; | ||
traverseApply: TraverseApply<object>; | ||
protected _transform(mapper: DeepNodeTransformation, ctx: DeepNodeTransformationContext): import("../node.js").BaseNode<import("../shared/declare.js").RawNodeDeclaration> | null; | ||
compile(): void; | ||
@@ -35,0 +37,0 @@ } |
@@ -74,2 +74,8 @@ import { printable, stringAndSymbolicEntriesOf, throwParseError } from "@arktype/util"; | ||
}); | ||
_transform(mapper, ctx) { | ||
ctx.path.push(this.signature); | ||
const result = super._transform(mapper, ctx); | ||
ctx.path.pop(); | ||
return result; | ||
} | ||
compile() { | ||
@@ -76,0 +82,0 @@ // this is currently handled by StructureNode |
import { type Key } from "@arktype/util"; | ||
import { BaseConstraint } from "../constraint.js"; | ||
import type { Node, RootSchema } from "../kinds.js"; | ||
import type { DeepNodeTransformation, DeepNodeTransformationContext } from "../node.js"; | ||
import type { BaseRoot } from "../roots/root.js"; | ||
@@ -33,2 +34,3 @@ import type { NodeCompiler } from "../shared/compile.js"; | ||
compiledKey: string; | ||
protected _transform(mapper: DeepNodeTransformation, ctx: DeepNodeTransformationContext): import("../node.js").BaseNode<import("../shared/declare.js").RawNodeDeclaration> | null; | ||
private defaultValueMorphs; | ||
@@ -35,0 +37,0 @@ private defaultValueMorphsReference; |
@@ -43,2 +43,8 @@ import { compileSerializedValue, printable, registeredReference, throwParseError, unset } from "@arktype/util"; | ||
compiledKey = typeof this.key === "string" ? this.key : this.serializedKey; | ||
_transform(mapper, ctx) { | ||
ctx.path.push(this.key); | ||
const result = super._transform(mapper, ctx); | ||
ctx.path.pop(); | ||
return result; | ||
} | ||
defaultValueMorphs = [ | ||
@@ -45,0 +51,0 @@ data => { |
import { type array, type satisfy } from "@arktype/util"; | ||
import { BaseConstraint } from "../constraint.js"; | ||
import type { RootSchema } from "../kinds.js"; | ||
import type { DeepNodeTransformation, DeepNodeTransformationContext } from "../node.js"; | ||
import type { MaxLengthNode } from "../refinements/maxLength.js"; | ||
@@ -54,2 +55,3 @@ import type { MinLengthNode } from "../refinements/minLength.js"; | ||
compile(js: NodeCompiler): void; | ||
protected _transform(mapper: DeepNodeTransformation, ctx: DeepNodeTransformationContext): import("../node.js").BaseNode<import("../shared/declare.js").RawNodeDeclaration> | null; | ||
tuple: SequenceTuple; | ||
@@ -56,0 +58,0 @@ expression: string; |
@@ -206,2 +206,8 @@ import { append, throwInternalError, throwParseError } from "@arktype/util"; | ||
} | ||
_transform(mapper, ctx) { | ||
ctx.path.push(this.$.keywords.nonNegativeIntegerString.raw); | ||
const result = super._transform(mapper, ctx); | ||
ctx.path.pop(); | ||
return result; | ||
} | ||
tuple = sequenceInnerToTuple(this.inner); | ||
@@ -208,0 +214,0 @@ // this depends on tuple so needs to come after it |
{ | ||
"name": "@arktype/schema", | ||
"version": "0.1.5", | ||
"license": "MIT", | ||
"author": { | ||
"name": "David Blass", | ||
"email": "david@arktype.io", | ||
"url": "https://arktype.io" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/arktypeio/arktype.git" | ||
}, | ||
"type": "module", | ||
"main": "./out/api.js", | ||
"types": "./out/api.d.ts", | ||
"exports": { | ||
".": "./out/api.js", | ||
"./config": "./out/config.js", | ||
"./internal/*": "./out/*" | ||
}, | ||
"files": [ | ||
"out" | ||
], | ||
"scripts": { | ||
"build": "tsx ../repo/build.ts", | ||
"bench": "tsx ./__tests__/comparison.bench.ts", | ||
"test": "tsx ../repo/testPackage.ts", | ||
"tnt": "tsx ../repo/testPackage.ts --skipTypes" | ||
}, | ||
"dependencies": { | ||
"@arktype/util": "0.0.42" | ||
} | ||
} | ||
"name": "@arktype/schema", | ||
"version": "0.1.6", | ||
"license": "MIT", | ||
"author": { | ||
"name": "David Blass", | ||
"email": "david@arktype.io", | ||
"url": "https://arktype.io" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/arktypeio/arktype.git" | ||
}, | ||
"type": "module", | ||
"main": "./out/api.js", | ||
"types": "./out/api.d.ts", | ||
"exports": { | ||
".": "./out/api.js", | ||
"./config": "./out/config.js", | ||
"./internal/*": "./out/*" | ||
}, | ||
"files": [ | ||
"out" | ||
], | ||
"dependencies": { | ||
"@arktype/util": "0.0.44" | ||
}, | ||
"scripts": { | ||
"build": "tsx ../repo/build.ts", | ||
"bench": "tsx ./__tests__/comparison.bench.ts", | ||
"test": "tsx ../repo/testPackage.ts", | ||
"tnt": "tsx ../repo/testPackage.ts --skipTypes" | ||
} | ||
} |
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
308875
121
6957
+ Added@arktype/util@0.0.44(transitive)
- Removed@arktype/util@0.0.42(transitive)
Updated@arktype/util@0.0.44