@ark/schema
Advanced tools
@@ -31,3 +31,3 @@ import { append, appendUnique, capitalize, isArray, throwInternalError, throwParseError } from "@ark/util"; | ||
| else { | ||
| js.if(this.compiledNegation, () => js.line(`${js.ctx}.errorFromNodeContext(${this.compiledErrorContext})`)); | ||
| js.if(this.compiledNegation, () => js.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`)); | ||
| } | ||
@@ -83,8 +83,8 @@ } | ||
| return result; | ||
| if (result.isRoot()) { | ||
| s.roots.push(result); | ||
| s.l.splice(i); | ||
| return intersectConstraints(s); | ||
| } | ||
| if (!matched) { | ||
| if (result.isRoot()) { | ||
| s.roots.push(result); | ||
| s.l.splice(i); | ||
| return intersectConstraints(s); | ||
| } | ||
| s.l[i] = result; | ||
@@ -94,3 +94,3 @@ matched = true; | ||
| else if (!s.l.includes(result)) { | ||
| return throwInternalError(`Unexpectedly encountered multiple distinct intersection results for refinement ${result}`); | ||
| return throwInternalError(`Unexpectedly encountered multiple distinct intersection results for refinement ${head}`); | ||
| } | ||
@@ -97,0 +97,0 @@ } |
+1
-0
@@ -14,2 +14,3 @@ export * from "./config.ts"; | ||
| export * from "./refinements/exactLength.ts"; | ||
| export * from "./refinements/kinds.ts"; | ||
| export * from "./refinements/max.ts"; | ||
@@ -16,0 +17,0 @@ export * from "./refinements/maxLength.ts"; |
+1
-0
@@ -14,2 +14,3 @@ export * from "./config.js"; | ||
| export * from "./refinements/exactLength.js"; | ||
| export * from "./refinements/kinds.js"; | ||
| export * from "./refinements/max.js"; | ||
@@ -16,0 +17,0 @@ export * from "./refinements/maxLength.js"; |
+6
-7
@@ -15,3 +15,2 @@ import { Callable, type GuardablePredicate, type JsonStructure, type Key, type array, type conform, type listable, type mutable, type requireKeys } from "@ark/util"; | ||
| import { Traversal, type TraverseAllows, type TraverseApply } from "./shared/traversal.ts"; | ||
| import { type arkKind } from "./shared/utils.ts"; | ||
| import type { UndeclaredKeyHandling } from "./structure/structure.ts"; | ||
@@ -51,8 +50,8 @@ export declare abstract class BaseNode< | ||
| traverse(data: d["prerequisite"], pipedFromCtx?: Traversal): ArkErrors | {} | null | undefined; | ||
| get in(): this extends { | ||
| [arkKind]: "root"; | ||
| } ? BaseRoot : BaseNode; | ||
| get out(): this extends { | ||
| [arkKind]: "root"; | ||
| } ? BaseRoot : BaseNode; | ||
| /** rawIn should be used internally instead */ | ||
| get in(): unknown; | ||
| get rawIn(): BaseNode; | ||
| /** rawOut should be used internally instead */ | ||
| get out(): unknown; | ||
| get rawOut(): BaseNode; | ||
| getIo(ioKind: "in" | "out"): BaseNode; | ||
@@ -59,0 +58,0 @@ toJSON(): JsonStructure; |
+17
-5
@@ -40,3 +40,4 @@ import { Callable, appendUnique, flatMorph, includes, isArray, isEmptyObject, isKeyOf, liftArray, printable, stringifyPath, throwError, throwInternalError } from "@ark/util"; | ||
| this.hasKind("morph") || | ||
| (this.hasKind("structure") && this.structuralMorph !== undefined); | ||
| (this.hasKind("structure") && this.structuralMorph !== undefined) || | ||
| (this.hasKind("sequence") && this.inner.defaultables !== undefined); | ||
| // if a predicate accepts exactly one arg, we can safely skip passing context | ||
@@ -170,8 +171,18 @@ // technically, a predicate could be written like `(data, ...[ctx]) => ctx.mustBe("malicious")` | ||
| } | ||
| /** rawIn should be used internally instead */ | ||
| get in() { | ||
| return this.cacheGetter("in", this.getIo("in")); | ||
| // ensure the node has been finalized if in is being used externally | ||
| return this.cacheGetter("in", this.rawIn.isRoot() ? this.$.finalize(this.rawIn) : this.rawIn); | ||
| } | ||
| get rawIn() { | ||
| return this.cacheGetter("rawIn", this.getIo("in")); | ||
| } | ||
| /** rawOut should be used internally instead */ | ||
| get out() { | ||
| return this.cacheGetter("out", this.getIo("out")); | ||
| // ensure the node has been finalized if out is being used externally | ||
| return this.cacheGetter("out", this.rawOut.isRoot() ? this.$.finalize(this.rawOut) : this.rawOut); | ||
| } | ||
| get rawOut() { | ||
| return this.cacheGetter("rawOut", this.getIo("out")); | ||
| } | ||
| // Should be refactored to use transform | ||
@@ -191,4 +202,5 @@ // https://github.com/arktypeio/arktype/issues/1020 | ||
| isArray(childValue) ? | ||
| childValue.map(child => child[ioKind]) | ||
| : childValue[ioKind]; | ||
| childValue.map(child => ioKind === "in" ? child.rawIn : child.rawOut) | ||
| : ioKind === "in" ? childValue.rawIn | ||
| : childValue.rawOut; | ||
| } | ||
@@ -195,0 +207,0 @@ else |
| import type { BaseConstraint } from "../constraint.ts"; | ||
| import type { BoundKind, nodeImplementationOf } from "../shared/implement.ts"; | ||
| import type { nodeImplementationOf } from "../shared/implement.ts"; | ||
| import { After } from "./after.ts"; | ||
@@ -28,2 +28,4 @@ import { Before } from "./before.ts"; | ||
| } | ||
| export type BoundKind = keyof BoundDeclarations; | ||
| export type RangeKind = Exclude<BoundKind, "exactLength">; | ||
| export type boundImplementationsByKind = { | ||
@@ -30,0 +32,0 @@ [k in BoundKind]: nodeImplementationOf<BoundDeclarations[k]>; |
@@ -5,3 +5,4 @@ import { type array, type propValueOf, type satisfy } from "@ark/util"; | ||
| import type { BaseNodeDeclaration, BaseNormalizedSchema } from "../shared/declare.ts"; | ||
| import type { keySchemaDefinitions, RangeKind } from "../shared/implement.ts"; | ||
| import type { keySchemaDefinitions } from "../shared/implement.ts"; | ||
| import type { RangeKind } from "./kinds.ts"; | ||
| export interface BaseRangeDeclaration extends BaseNodeDeclaration { | ||
@@ -8,0 +9,0 @@ kind: RangeKind; |
@@ -23,5 +23,5 @@ import { compileObjectLiteral } from "../shared/implement.js"; | ||
| else { | ||
| js.if(this.compiledNegation, () => js.line(`${js.ctx}.errorFromNodeContext(${this.compiledErrorContext})`)); | ||
| js.if(this.compiledNegation, () => js.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`)); | ||
| } | ||
| } | ||
| } |
@@ -7,3 +7,3 @@ import { type array, type listable, type show } from "@ark/util"; | ||
| import type { ArkError } from "../shared/errors.ts"; | ||
| import { type ConstraintKind, type nodeImplementationOf, type OpenNodeKind, type RefinementKind, type StructuralKind } from "../shared/implement.ts"; | ||
| import { type ConstraintKind, type nodeImplementationOf, type OpenNodeKind, type PrestructuralKind, type RefinementKind, type StructuralKind } from "../shared/implement.ts"; | ||
| import type { JsonSchema } from "../shared/jsonSchema.ts"; | ||
@@ -20,3 +20,3 @@ import type { ToJsonSchema } from "../shared/toJsonSchema.ts"; | ||
| type BasisKind = "domain" | "proto"; | ||
| type ChildKind = BasisKind | RefinementKind | "predicate" | "structure"; | ||
| type ChildKind = BasisKind | RefinementKind; | ||
| type FlattenedChildKind = ChildKind | StructuralKind; | ||
@@ -60,2 +60,3 @@ type RefinementsInner = { | ||
| basis: nodeOfKind<Intersection.BasisKind> | null; | ||
| prestructurals: array<nodeOfKind<PrestructuralKind>>; | ||
| refinements: array<nodeOfKind<RefinementKind>>; | ||
@@ -62,0 +63,0 @@ structure: Structure.Node | undefined; |
@@ -1,5 +0,5 @@ | ||
| import { flatMorph, hasDomain, isEmptyObject, isKeyOf, throwParseError } from "@ark/util"; | ||
| import { flatMorph, hasDomain, includes, isEmptyObject, isKeyOf, throwParseError } from "@ark/util"; | ||
| import { constraintKeyParser, flattenConstraints, intersectConstraints } from "../constraint.js"; | ||
| import { Disjoint } from "../shared/disjoint.js"; | ||
| import { implementNode, structureKeys } from "../shared/implement.js"; | ||
| import { implementNode, prestructuralKinds, structureKeys } from "../shared/implement.js"; | ||
| import { intersectOrPipeNodes } from "../shared/intersections.js"; | ||
@@ -116,6 +116,6 @@ import { hasArkKind, isNode } from "../shared/utils.js"; | ||
| if (node.basis && | ||
| !node.refinements.some(r => r.impl.obviatesBasisDescription)) | ||
| !node.prestructurals.some(r => r.impl.obviatesBasisDescription)) | ||
| childDescriptions.push(node.basis.description); | ||
| if (node.refinements.length) { | ||
| const sortedRefinementDescriptions = node.refinements | ||
| if (node.prestructurals.length) { | ||
| const sortedRefinementDescriptions = node.prestructurals | ||
| // override alphabetization to describe min before max | ||
@@ -155,3 +155,11 @@ .toSorted((l, r) => (l.kind === "min" && r.kind === "max" ? -1 : 0)) | ||
| basis = this.inner.domain ?? this.inner.proto ?? null; | ||
| refinements = this.children.filter(node => node.isRefinement()); | ||
| prestructurals = []; | ||
| refinements = this.children.filter((node) => { | ||
| if (!node.isRefinement()) | ||
| return false; | ||
| if (includes(prestructuralKinds, node.kind)) | ||
| // mutation is fine during initialization | ||
| this.prestructurals.push(node); | ||
| return true; | ||
| }); | ||
| structure = this.inner.structure; | ||
@@ -182,9 +190,9 @@ expression = writeIntersectionExpression(this); | ||
| } | ||
| if (this.refinements.length) { | ||
| for (let i = 0; i < this.refinements.length - 1; i++) { | ||
| this.refinements[i].traverseApply(data, ctx); | ||
| if (this.prestructurals.length) { | ||
| for (let i = 0; i < this.prestructurals.length - 1; i++) { | ||
| this.prestructurals[i].traverseApply(data, ctx); | ||
| if (ctx.failFast && ctx.currentErrorCount > errorCount) | ||
| return; | ||
| } | ||
| this.refinements.at(-1).traverseApply(data, ctx); | ||
| this.prestructurals.at(-1).traverseApply(data, ctx); | ||
| if (ctx.currentErrorCount > errorCount) | ||
@@ -221,8 +229,8 @@ return; | ||
| } | ||
| if (this.refinements.length) { | ||
| for (let i = 0; i < this.refinements.length - 1; i++) { | ||
| js.check(this.refinements[i]); | ||
| if (this.prestructurals.length) { | ||
| for (let i = 0; i < this.prestructurals.length - 1; i++) { | ||
| js.check(this.prestructurals[i]); | ||
| js.returnIfFailFast(); | ||
| } | ||
| js.check(this.refinements.at(-1)); | ||
| js.check(this.prestructurals.at(-1)); | ||
| if (this.structure || this.inner.predicate) | ||
@@ -252,8 +260,15 @@ js.returnIfFail(); | ||
| const writeIntersectionExpression = (node) => { | ||
| let expression = node.structure?.expression || | ||
| `${node.basis && !node.refinements.some(n => n.impl.obviatesBasisExpression) ? node.basis.nestableExpression + " " : ""}${node.refinements.map(n => n.expression).join(" & ")}` || | ||
| "unknown"; | ||
| if (expression === "Array == 0") | ||
| expression = "[]"; | ||
| return expression; | ||
| if (node.structure?.expression) | ||
| return node.structure.expression; | ||
| const basisExpression = (node.basis && | ||
| !node.prestructurals.some(n => n.impl.obviatesBasisExpression)) ? | ||
| node.basis.nestableExpression | ||
| : ""; | ||
| const refinementsExpression = node.prestructurals | ||
| .map(n => n.expression) | ||
| .join(" & "); | ||
| const fullExpression = `${basisExpression}${basisExpression ? " " : ""}${refinementsExpression}`; | ||
| if (fullExpression === "Array == 0") | ||
| return "[]"; | ||
| return fullExpression || "unknown"; | ||
| }; | ||
@@ -260,0 +275,0 @@ const intersectIntersections = (l, r, ctx) => { |
@@ -34,9 +34,9 @@ import { type array, type listable } from "@ark/util"; | ||
| type Out<morph extends Morph> = morph extends Morph<never, infer o> ? o : never; | ||
| type ContextFree<i = any, o = unknown> = (In: i) => o; | ||
| type ContextFree<i = never, o = unknown> = (In: i) => o; | ||
| } | ||
| export type Morph<i = any, o = unknown> = (In: i, ctx: Traversal) => o; | ||
| export type Morph<i = never, o = unknown> = (In: i, ctx: Traversal) => o; | ||
| export declare class MorphNode extends BaseRoot<Morph.Declaration> { | ||
| serializedMorphs: string[]; | ||
| compiledMorphs: string; | ||
| lastMorph: BaseRoot<import("./root.ts").InternalRootDeclaration> | Morph<any, unknown> | undefined; | ||
| lastMorph: BaseRoot<import("./root.ts").InternalRootDeclaration> | Morph<never, unknown> | undefined; | ||
| lastMorphIfNode: BaseRoot | undefined; | ||
@@ -46,4 +46,4 @@ introspectableIn: BaseRoot | undefined; | ||
| get shallowMorphs(): array<Morph>; | ||
| get in(): BaseRoot; | ||
| get out(): BaseRoot; | ||
| get rawIn(): BaseRoot; | ||
| get rawOut(): BaseRoot; | ||
| declareIn(declaredIn: BaseRoot): MorphNode; | ||
@@ -50,0 +50,0 @@ declareOut(declaredOut: BaseRoot): MorphNode; |
+11
-11
@@ -32,3 +32,3 @@ import { arrayEquals, liftArray, throwParseError } from "@ark/util"; | ||
| defaults: { | ||
| description: node => `a morph from ${node.in.description} to ${node.out?.description ?? "unknown"}` | ||
| description: node => `a morph from ${node.rawIn.description} to ${node.rawOut?.description ?? "unknown"}` | ||
| }, | ||
@@ -40,3 +40,3 @@ intersections: { | ||
| } | ||
| const inTersection = intersectOrPipeNodes(l.in, r.in, ctx); | ||
| const inTersection = intersectOrPipeNodes(l.rawIn, r.rawIn, ctx); | ||
| if (inTersection instanceof Disjoint) | ||
@@ -48,3 +48,3 @@ return inTersection; | ||
| if (l.declaredIn || r.declaredIn) { | ||
| const declaredIn = intersectOrPipeNodes(l.in, r.in, ctx); | ||
| const declaredIn = intersectOrPipeNodes(l.rawIn, r.rawIn, 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 = intersectOrPipeNodes(l.out, r.out, ctx); | ||
| const declaredOut = intersectOrPipeNodes(l.rawOut, r.rawOut, ctx); | ||
| if (declaredOut instanceof Disjoint) | ||
@@ -91,3 +91,3 @@ return declaredOut.throw(); | ||
| Object.assign(this.referencesById, this.lastMorphIfNode.referencesById) && | ||
| this.lastMorphIfNode.out | ||
| this.lastMorphIfNode.rawOut | ||
| : undefined; | ||
@@ -100,6 +100,6 @@ get shallowMorphs() { | ||
| } | ||
| get in() { | ||
| return (this.declaredIn ?? this.inner.in?.in ?? $ark.intrinsic.unknown.internal); | ||
| get rawIn() { | ||
| return (this.declaredIn ?? this.inner.in?.rawIn ?? $ark.intrinsic.unknown.internal); | ||
| } | ||
| get out() { | ||
| get rawOut() { | ||
| return (this.declaredOut ?? | ||
@@ -121,5 +121,5 @@ this.introspectableOut ?? | ||
| } | ||
| expression = `(In: ${this.in.expression}) => ${this.lastMorphIfNode ? "To" : "Out"}<${this.out.expression}>`; | ||
| expression = `(In: ${this.rawIn.expression}) => ${this.lastMorphIfNode ? "To" : "Out"}<${this.rawOut.expression}>`; | ||
| get defaultShortDescription() { | ||
| return this.in.meta.description ?? this.in.defaultShortDescription; | ||
| return this.rawIn.meta.description ?? this.rawIn.defaultShortDescription; | ||
| } | ||
@@ -129,3 +129,3 @@ innerToJsonSchema(ctx) { | ||
| code: "morph", | ||
| base: this.in.toJsonSchemaRecurse(ctx), | ||
| base: this.rawIn.toJsonSchemaRecurse(ctx), | ||
| out: this.introspectableOut?.toJsonSchemaRecurse(ctx) ?? null | ||
@@ -132,0 +132,0 @@ }); |
@@ -31,2 +31,4 @@ import { inferred, type array } from "@ark/util"; | ||
| constructor(attachments: UnknownAttachments, $: BaseScope); | ||
| get rawIn(): BaseRoot; | ||
| get rawOut(): BaseRoot; | ||
| get internal(): this; | ||
@@ -33,0 +35,0 @@ get "~standard"(): StandardSchemaV1.ArkTypeProps; |
+14
-4
@@ -18,2 +18,10 @@ import { arrayEquals, flatMorph, includes, inferred, omit, throwInternalError, throwParseError } from "@ark/util"; | ||
| } | ||
| // doesn't seem possible to override this at a type-level (e.g. via declare) | ||
| // without TS complaining about getters | ||
| get rawIn() { | ||
| return super.rawIn; | ||
| } | ||
| get rawOut() { | ||
| return super.rawOut; | ||
| } | ||
| get internal() { | ||
@@ -37,4 +45,4 @@ return this; | ||
| if (opts.io === "input") | ||
| return this.in.toJsonSchema(); | ||
| return this.out.toJsonSchema(); | ||
| return this.rawIn.toJsonSchema(); | ||
| return this.rawOut.toJsonSchema(); | ||
| } | ||
@@ -178,3 +186,3 @@ }; | ||
| return this.$.node("intersection", { | ||
| ...branch.inner, | ||
| domain: "object", | ||
| structure: structure[structuralMethodName](...args) | ||
@@ -304,3 +312,5 @@ }); | ||
| } | ||
| const operand = io === "root" ? this : this[io]; | ||
| const operand = io === "root" ? this | ||
| : io === "in" ? this.rawIn | ||
| : this.rawOut; | ||
| if (operand.hasKind("morph") || | ||
@@ -307,0 +317,0 @@ (constraint.impliedBasis && !operand.extends(constraint.impliedBasis))) { |
+43
-23
@@ -34,3 +34,3 @@ import { appendUnique, arrayEquals, domainDescriptions, flatMorph, groupBy, hasKey, isArray, jsTypeOfDescriptions, printable, range, throwParseError, unset } from "@ark/util"; | ||
| ...matchingMorph.inner, | ||
| in: matchingMorph.in.rawOr(node.in) | ||
| in: matchingMorph.rawIn.rawOr(node.rawIn) | ||
| }); | ||
@@ -80,3 +80,9 @@ } | ||
| problem: ctx => ctx.expected, | ||
| message: ctx => ctx.problem | ||
| message: ctx => { | ||
| if (ctx.problem[0] === "[") { | ||
| // clarify paths like [1], [0][1], and ["key!"] that could be confusing | ||
| return `value at ${ctx.problem}`; | ||
| } | ||
| return ctx.problem; | ||
| } | ||
| }, | ||
@@ -140,3 +146,3 @@ intersections: { | ||
| } | ||
| unitBranches = this.branches.filter((n) => n.in.hasKind("unit")); | ||
| unitBranches = this.branches.filter((n) => n.rawIn.hasKind("unit")); | ||
| discriminant = this.discriminate(); | ||
@@ -228,8 +234,16 @@ discriminantJson = this.discriminant ? discriminantToJson(this.discriminant) : null; | ||
| const caseCondition = k === "default" ? k : `case ${k}`; | ||
| js.line(`${caseCondition}: return ${v === true ? | ||
| optimistic ? js.data | ||
| : v | ||
| : optimistic ? | ||
| `${js.invoke(v)} ? ${v.contextFreeMorph ? `${registeredReference(v.contextFreeMorph)}(${js.data})` : js.data} : "${unset}"` | ||
| : js.invoke(v)}`); | ||
| let caseResult; | ||
| if (v === true) | ||
| caseResult = optimistic ? "data" : "true"; | ||
| else if (optimistic) { | ||
| if (v.rootApplyStrategy === "branchedOptimistic") | ||
| caseResult = js.invoke(v, { kind: "Optimistic" }); | ||
| else if (v.contextFreeMorph) | ||
| caseResult = `${js.invoke(v)} ? ${registeredReference(v.contextFreeMorph)}(data) : "${unset}"`; | ||
| else | ||
| caseResult = `${js.invoke(v)} ? data : "${unset}"`; | ||
| } | ||
| else | ||
| caseResult = js.invoke(v); | ||
| js.line(`${caseCondition}: return ${caseResult}`); | ||
| } | ||
@@ -283,4 +297,4 @@ return js; | ||
| branch.contextFreeMorph ? | ||
| `${registeredReference(branch.contextFreeMorph)}(${js.data})` | ||
| : js.data | ||
| `${registeredReference(branch.contextFreeMorph)}(data)` | ||
| : "data" | ||
| : true)); | ||
@@ -301,3 +315,3 @@ } | ||
| const cases = flatMorph(this.unitBranches, (i, n) => [ | ||
| `${n.in.serializedValue}`, | ||
| `${n.rawIn.serializedValue}`, | ||
| n.hasKind("morph") ? n : true | ||
@@ -317,3 +331,3 @@ ]); | ||
| const r = this.branches[rIndex]; | ||
| const result = intersectNodesRoot(l.in, r.in, l.$); | ||
| const result = intersectNodesRoot(l.rawIn, r.rawIn, l.$); | ||
| if (!(result instanceof Disjoint)) | ||
@@ -378,6 +392,8 @@ continue; | ||
| } | ||
| const orderedCandidates = this.ordered ? orderCandidates(candidates, this.branches) : candidates; | ||
| if (!orderedCandidates.length) | ||
| const viableCandidates = this.ordered ? | ||
| viableOrderedCandidates(candidates, this.branches) | ||
| : candidates; | ||
| if (!viableCandidates.length) | ||
| return null; | ||
| const ctx = createCaseResolutionContext(orderedCandidates, this); | ||
| const ctx = createCaseResolutionContext(viableCandidates, this); | ||
| const cases = {}; | ||
@@ -419,4 +435,8 @@ for (const k in ctx.best.cases) { | ||
| } | ||
| const createCaseResolutionContext = (orderedCandidates, node) => { | ||
| const best = orderedCandidates.sort((l, r) => Object.keys(r.cases).length - Object.keys(l.cases).length)[0]; | ||
| const createCaseResolutionContext = (viableCandidates, node) => { | ||
| const ordered = viableCandidates.sort((l, r) => l.path.length === r.path.length ? | ||
| Object.keys(r.cases).length - Object.keys(l.cases).length | ||
| // prefer shorter paths first | ||
| : l.path.length - r.path.length); | ||
| const best = ordered[0]; | ||
| const location = { | ||
@@ -469,3 +489,3 @@ kind: best.kind, | ||
| else { | ||
| if (entry.branch.in.overlaps(discriminantNode)) { | ||
| if (entry.branch.rawIn.overlaps(discriminantNode)) { | ||
| // include cases where an object not including the | ||
@@ -485,3 +505,3 @@ // discriminant path might have that value present as an undeclared key | ||
| }; | ||
| const orderCandidates = (candidates, originalBranches) => { | ||
| const viableOrderedCandidates = (candidates, originalBranches) => { | ||
| const viableCandidates = candidates.filter(candidate => { | ||
@@ -647,3 +667,3 @@ const caseGroups = Object.values(candidate.cases).map(caseCtx => caseCtx.branchIndices); | ||
| } | ||
| const intersection = intersectNodesRoot(branches[i].in, branches[j].in, branches[0].$); | ||
| const intersection = intersectNodesRoot(branches[i].rawIn, branches[j].rawIn, branches[0].$); | ||
| if (intersection instanceof Disjoint) | ||
@@ -653,7 +673,7 @@ continue; | ||
| assertDeterminateOverlap(branches[i], branches[j]); | ||
| if (intersection.equals(branches[i].in)) { | ||
| if (intersection.equals(branches[i].rawIn)) { | ||
| // preserve ordered branches that are a subtype of a subsequent branch | ||
| uniquenessByIndex[i] = !!ordered; | ||
| } | ||
| else if (intersection.equals(branches[j].in)) | ||
| else if (intersection.equals(branches[j].rawIn)) | ||
| uniquenessByIndex[j] = false; | ||
@@ -660,0 +680,0 @@ } |
| import { type Key } from "@ark/util"; | ||
| import type { nodeOfKind } from "../kinds.ts"; | ||
| import type { BaseNode } from "../node.ts"; | ||
| import type { BoundKind } from "../refinements/kinds.ts"; | ||
| import type { Domain } from "../roots/domain.ts"; | ||
| import type { BaseRoot } from "../roots/root.ts"; | ||
| import type { Prop } from "../structure/prop.ts"; | ||
| import type { BoundKind } from "./implement.ts"; | ||
| export interface DisjointEntry<kind extends DisjointKind = DisjointKind> { | ||
@@ -9,0 +9,0 @@ kind: kind; |
@@ -84,2 +84,6 @@ import { CastableBase, ReadonlyArray, ReadonlyPath, type JsonArray, type JsonObject, type array, type merge, type propwiseXor, type show } from "@ark/util"; | ||
| /** | ||
| * @internal | ||
| */ | ||
| affectsPath(path: ReadonlyPath): boolean; | ||
| /** | ||
| * A human-readable summary of all errors. | ||
@@ -94,3 +98,2 @@ */ | ||
| toString(): string; | ||
| private _add; | ||
| private addAncestorPaths; | ||
@@ -97,0 +100,0 @@ } |
+34
-39
@@ -152,5 +152,35 @@ import { CastableBase, ReadonlyArray, ReadonlyPath, append, conflatenateAll, defineProperties, flatMorph, stringifyPath } from "@ark/util"; | ||
| add(error) { | ||
| if (this.includes(error)) | ||
| return; | ||
| this._add(error); | ||
| const existing = this.byPath[error.propString]; | ||
| if (existing) { | ||
| if (error === existing) | ||
| return; | ||
| // If the existing error is an error for a value constrained to "never", | ||
| // then we don't want to intersect the error messages. | ||
| if (existing.hasCode("union") && existing.errors.length === 0) | ||
| return; | ||
| // If the new error is an error for a value constrained to "never", | ||
| // then we want to override any existing errors. | ||
| const errorIntersection = error.hasCode("union") && error.errors.length === 0 ? | ||
| error | ||
| : new ArkError({ | ||
| code: "intersection", | ||
| errors: existing.hasCode("intersection") ? | ||
| [...existing.errors, error] | ||
| : [existing, error] | ||
| }, this.ctx); | ||
| const existingIndex = this.indexOf(existing); | ||
| this.mutable[existingIndex === -1 ? this.length : existingIndex] = | ||
| errorIntersection; | ||
| this.byPath[error.propString] = errorIntersection; | ||
| // add the original error here rather than the intersection | ||
| // since the intersection is reflected by the array of errors at | ||
| // this path | ||
| this.addAncestorPaths(error); | ||
| } | ||
| else { | ||
| this.byPath[error.propString] = error; | ||
| this.addAncestorPaths(error); | ||
| this.mutable.push(error); | ||
| } | ||
| this.count++; | ||
| } | ||
@@ -169,5 +199,3 @@ transform(f) { | ||
| for (const e of errors) { | ||
| if (this.includes(e)) | ||
| continue; | ||
| this._add(new ArkError({ ...e, path: [...this.ctx.path, ...e.path] }, this.ctx)); | ||
| this.add(new ArkError({ ...e, path: [...this.ctx.path, ...e.path] }, this.ctx)); | ||
| } | ||
@@ -207,35 +235,2 @@ } | ||
| } | ||
| _add(error) { | ||
| const existing = this.byPath[error.propString]; | ||
| if (existing) { | ||
| // If the existing error is an error for a value constrained to "never", | ||
| // then we don't want to intersect the error messages. | ||
| if (existing.hasCode("union") && existing.errors.length === 0) | ||
| return; | ||
| // If the new error is an error for a value constrained to "never", | ||
| // then we want to override any existing errors. | ||
| const errorIntersection = error.hasCode("union") && error.errors.length === 0 ? | ||
| error | ||
| : new ArkError({ | ||
| code: "intersection", | ||
| errors: existing.hasCode("intersection") ? | ||
| [...existing.errors, error] | ||
| : [existing, error] | ||
| }, this.ctx); | ||
| const existingIndex = this.indexOf(existing); | ||
| this.mutable[existingIndex === -1 ? this.length : existingIndex] = | ||
| errorIntersection; | ||
| this.byPath[error.propString] = errorIntersection; | ||
| // add the original error here rather than the intersection | ||
| // since the intersection is reflected by the array of errors at | ||
| // this path | ||
| this.addAncestorPaths(error); | ||
| } | ||
| else { | ||
| this.byPath[error.propString] = error; | ||
| this.addAncestorPaths(error); | ||
| this.mutable.push(error); | ||
| } | ||
| this.count++; | ||
| } | ||
| addAncestorPaths(error) { | ||
@@ -242,0 +237,0 @@ for (const propString of error.path.stringifyAncestors()) { |
@@ -16,13 +16,7 @@ import { type Entry, type Json, type JsonStructure, type KeySet, type arrayIndexOf, type keySetOf, type listable, type requireKeys, type show } from "@ark/util"; | ||
| export type StructuralKind = (typeof structuralKinds)[number]; | ||
| export type RangeKind = Exclude<BoundKind, "exactLength">; | ||
| export type BoundKind = Exclude<RefinementKind, "pattern" | "divisor">; | ||
| export declare const refinementKinds: readonly ["pattern", "divisor", "exactLength", "max", "min", "maxLength", "minLength", "before", "after"]; | ||
| export declare const prestructuralKinds: readonly ["pattern", "divisor", "exactLength", "max", "min", "maxLength", "minLength", "before", "after"]; | ||
| export type PrestructuralKind = (typeof prestructuralKinds)[number]; | ||
| export declare const refinementKinds: readonly ["pattern", "divisor", "exactLength", "max", "min", "maxLength", "minLength", "before", "after", "structure", "predicate"]; | ||
| export type RefinementKind = (typeof refinementKinds)[number]; | ||
| type orderedConstraintKinds = [ | ||
| ...typeof refinementKinds, | ||
| ...typeof structuralKinds, | ||
| "structure", | ||
| "predicate" | ||
| ]; | ||
| export declare const constraintKinds: orderedConstraintKinds; | ||
| export declare const constraintKinds: readonly ["pattern", "divisor", "exactLength", "max", "min", "maxLength", "minLength", "before", "after", "structure", "predicate", "required", "optional", "index", "sequence"]; | ||
| export type ConstraintKind = (typeof constraintKinds)[number]; | ||
@@ -38,3 +32,3 @@ export declare const rootKinds: readonly ["alias", "union", "morph", "unit", "intersection", "proto", "domain"]; | ||
| export type ClosedNodeKind = Exclude<NodeKind, OpenNodeKind>; | ||
| export type PrimitiveKind = RefinementKind | BasisKind | "predicate"; | ||
| export type PrimitiveKind = Exclude<RefinementKind | BasisKind, "structure">; | ||
| export type CompositeKind = Exclude<NodeKind, PrimitiveKind>; | ||
@@ -41,0 +35,0 @@ export type OrderedNodeKinds = typeof nodeKinds; |
@@ -11,3 +11,3 @@ import { flatMorph, printable, throwParseError } from "@ark/util"; | ||
| ]; | ||
| export const refinementKinds = [ | ||
| export const prestructuralKinds = [ | ||
| "pattern", | ||
@@ -23,8 +23,8 @@ "divisor", | ||
| ]; | ||
| export const constraintKinds = [ | ||
| ...refinementKinds, | ||
| ...structuralKinds, | ||
| export const refinementKinds = [ | ||
| ...prestructuralKinds, | ||
| "structure", | ||
| "predicate" | ||
| ]; | ||
| export const constraintKinds = [...refinementKinds, ...structuralKinds]; | ||
| export const rootKinds = [ | ||
@@ -31,0 +31,0 @@ "alias", |
@@ -81,3 +81,3 @@ import { Disjoint } from "./disjoint.js"; | ||
| if (viableBranches.length < from.branches.length || | ||
| !from.branches.every((branch, i) => branch.in.equals(viableBranches[i].in))) | ||
| !from.branches.every((branch, i) => branch.rawIn.equals(viableBranches[i].rawIn))) | ||
| return ctx.$.parseSchema(viableBranches); | ||
@@ -92,3 +92,3 @@ // otherwise, the input has not changed so preserve metadata | ||
| ...onlyBranch.inner, | ||
| in: onlyBranch.in.configure(meta, "self") | ||
| in: onlyBranch.rawIn.configure(meta, "self") | ||
| }); | ||
@@ -122,3 +122,3 @@ } | ||
| if (to.hasKind("morph")) { | ||
| const inTersection = intersectOrPipeNodes(from, to.in, ctx); | ||
| const inTersection = intersectOrPipeNodes(from, to.rawIn, ctx); | ||
| if (inTersection instanceof Disjoint) | ||
@@ -125,0 +125,0 @@ return inTersection; |
| import { ReadonlyPath, type array } from "@ark/util"; | ||
| import type { ResolvedConfig } from "../config.ts"; | ||
| import type { Morph } from "../roots/morph.ts"; | ||
| import { ArkError, ArkErrors, type ArkErrorCode, type ArkErrorInput } from "./errors.ts"; | ||
| import { ArkError, ArkErrors, type ArkErrorCode, type ArkErrorInput, type NodeErrorContextInput } from "./errors.ts"; | ||
| export type MorphsAtPath = { | ||
@@ -94,2 +94,13 @@ path: ReadonlyPath; | ||
| popBranch(): BranchTraversal | undefined; | ||
| /** | ||
| * @internal | ||
| * Convenience for casting from InternalTraversal to Traversal | ||
| * for cases where the extra methods on the external type are expected, e.g. | ||
| * a morph or predicate. | ||
| */ | ||
| get external(): this; | ||
| /** | ||
| * @internal | ||
| */ | ||
| errorFromNodeContext<input extends NodeErrorContextInput>(input: input): ArkError<input["code"]>; | ||
| private errorFromContext; | ||
@@ -103,5 +114,6 @@ private applyQueuedMorphs; | ||
| Apply: TraverseApply<input>; | ||
| Optimistic: TraverseApply<input>; | ||
| }; | ||
| export type TraversalKind = keyof TraversalMethodsByKind; | ||
| export type TraversalKind = keyof TraversalMethodsByKind & {}; | ||
| export type TraverseAllows<data = unknown> = (data: data, ctx: InternalTraversal) => boolean; | ||
| export type TraverseApply<data = unknown> = (data: data, ctx: InternalTraversal) => void; |
@@ -180,6 +180,7 @@ import { ReadonlyPath, stringifyPath } from "@ark/util"; | ||
| for (const morph of morphs) { | ||
| // applyMorphsAtPath may be called recursively, so we need to reset the path here | ||
| // ensure morphs are applied relative to the correct path | ||
| // in case previous operations modified this.path | ||
| this.path = [...path]; | ||
| const morphIsNode = isNode(morph); | ||
| const result = morph(parent === undefined ? this.root : parent[key], this); | ||
| const result = morph((parent === undefined ? this.root : parent[key]), this); | ||
| if (result instanceof ArkError) { | ||
@@ -199,2 +200,3 @@ // if an ArkError was returned, ensure it has been added to errors | ||
| // skip any remaining morphs at the current path | ||
| this.queuedMorphs = []; | ||
| break; | ||
@@ -201,0 +203,0 @@ } |
@@ -26,2 +26,3 @@ import { type requireKeys } from "@ark/util"; | ||
| constructor(...args: ConstructorParameters<typeof BaseProp>); | ||
| get rawIn(): OptionalNode; | ||
| get outProp(): Prop.Node; | ||
@@ -46,5 +47,5 @@ expression: string; | ||
| }; | ||
| export declare const computeDefaultValueMorph: (key: PropertyKey, value: BaseRoot, defaultInput: unknown) => Morph; | ||
| export declare const computeDefaultValueMorph: (key: PropertyKey, value: BaseRoot, defaultInput: unknown) => Morph<any>; | ||
| export declare const assertDefaultValueAssignability: (node: BaseRoot, value: unknown, key: PropertyKey | null) => unknown; | ||
| export type writeUnassignableDefaultValueMessage<baseDef extends string, defaultValue extends string> = `Default value ${defaultValue} must be assignable to ${baseDef}`; | ||
| export declare const writeNonPrimitiveNonFunctionDefaultValueMessage: (key: PropertyKey | null) => string; |
@@ -1,2 +0,2 @@ | ||
| import { hasDomain, isThunk, printable, throwParseError } from "@ark/util"; | ||
| import { hasDomain, isThunk, omit, printable, throwParseError } from "@ark/util"; | ||
| import { intrinsic } from "../intrinsic.js"; | ||
@@ -44,2 +44,10 @@ import { compileSerializedValue } from "../shared/compile.js"; | ||
| } | ||
| get rawIn() { | ||
| const baseIn = super.rawIn; | ||
| if (!this.hasDefault()) | ||
| return baseIn; | ||
| return this.$.node("optional", omit(baseIn.inner, { default: true }), { | ||
| prereduced: true | ||
| }); | ||
| } | ||
| get outProp() { | ||
@@ -99,2 +107,5 @@ if (!this.hasDefault()) | ||
| throwParseError(writeNonPrimitiveNonFunctionDefaultValueMessage(key)); | ||
| // if the node has a default value, finalize it and apply JIT optimizations | ||
| // if applicable to ensure behavior + error logging is externally consistent | ||
| // (using .in here insead of .rawIn triggers finalization) | ||
| const out = node.in(wrapped ? value() : value); | ||
@@ -101,0 +112,0 @@ if (out instanceof ArkErrors) { |
@@ -48,3 +48,11 @@ import { append, conflatenate, printable, throwInternalError, throwParseError } from "@ark/util"; | ||
| defaultValueSerializer(element[1]) | ||
| ]) | ||
| ]), | ||
| reduceIo: (ioKind, inner, defaultables) => { | ||
| if (ioKind === "in") { | ||
| inner.optionals = defaultables.map(d => d[0].rawIn); | ||
| return; | ||
| } | ||
| inner.prefix = defaultables.map(d => d[0].rawOut); | ||
| return; | ||
| } | ||
| }, | ||
@@ -271,3 +279,3 @@ variadic: { | ||
| const dataIndex = `${i + this.prefixLength}`; | ||
| js.if(`${dataIndex} >= ${js.data}.length`, () => js.traversalKind === "Allows" ? js.return(true) : js.return()); | ||
| js.if(`${dataIndex} >= data.length`, () => js.traversalKind === "Allows" ? js.return(true) : js.return()); | ||
| js.traverseKey(dataIndex, `data[${dataIndex}]`, node); | ||
@@ -277,3 +285,3 @@ } | ||
| if (this.postfix) { | ||
| js.const("firstPostfixIndex", `${js.data}.length${this.postfix ? `- ${this.postfix.length}` : ""}`); | ||
| js.const("firstPostfixIndex", `data.length${this.postfix ? `- ${this.postfix.length}` : ""}`); | ||
| } | ||
@@ -444,3 +452,7 @@ js.for(`i < ${this.postfix ? "firstPostfixIndex" : "data.length"}`, () => js.traverseKey("i", "data[i]", this.variadic), this.prevariadic.length); | ||
| // but not trivial to serialize postfix elements as keys | ||
| kind === "prefix" ? s.result.length : `-${lTail.length + 1}`, "required")); | ||
| kind === "prefix" ? s.result.length : `-${lTail.length + 1}`, | ||
| // both operands must be required for the disjoint to be considered required | ||
| elementIsRequired(lHead) && elementIsRequired(rHead) ? | ||
| "required" | ||
| : "optional")); | ||
| s.result = [...s.result, { kind, node: $ark.intrinsic.never.internal }]; | ||
@@ -499,1 +511,2 @@ } | ||
| }; | ||
| const elementIsRequired = (el) => el.kind === "prefix" || el.kind === "postfix"; |
@@ -63,3 +63,3 @@ import { append, conflatenate, flatMorph, printable, spliterate, throwInternalError, throwParseError } from "@ark/util"; | ||
| // ensure we don't overwrite nodes added by optional | ||
| inner.required = append(inner.required, nodes.map(node => node[ioKind])); | ||
| inner.required = append(inner.required, nodes.map(node => (ioKind === "in" ? node.rawIn : node.rawOut))); | ||
| return; | ||
@@ -73,7 +73,7 @@ } | ||
| if (ioKind === "in") { | ||
| inner.optional = nodes.map(node => node.in); | ||
| inner.optional = nodes.map(node => node.rawIn); | ||
| return; | ||
| } | ||
| for (const node of nodes) { | ||
| inner[node.outProp.kind] = append(inner[node.outProp.kind], node.outProp.out); | ||
| inner[node.outProp.kind] = append(inner[node.outProp.kind], node.outProp.rawOut); | ||
| } | ||
@@ -93,4 +93,6 @@ } | ||
| reduceIo: (ioKind, inner, value) => { | ||
| if (value !== "delete") | ||
| if (value === "reject") { | ||
| inner.undeclared = "reject"; | ||
| return; | ||
| } | ||
| // if base is "delete", undeclared keys are "ignore" (i.e. unconstrained) | ||
@@ -593,4 +595,4 @@ // on input and "reject" on output | ||
| code: "morph", | ||
| base: keyBranch.in.toJsonSchemaRecurse(ctx), | ||
| out: keyBranch.out.toJsonSchemaRecurse(ctx) | ||
| base: keyBranch.rawIn.toJsonSchemaRecurse(ctx), | ||
| out: keyBranch.rawOut.toJsonSchemaRecurse(ctx) | ||
| }); | ||
@@ -597,0 +599,0 @@ } |
+2
-2
| { | ||
| "name": "@ark/schema", | ||
| "version": "0.49.0", | ||
| "version": "0.50.0", | ||
| "license": "MIT", | ||
@@ -32,3 +32,3 @@ "author": { | ||
| "dependencies": { | ||
| "@ark/util": "0.49.0" | ||
| "@ark/util": "0.50.0" | ||
| }, | ||
@@ -35,0 +35,0 @@ "publishConfig": { |
431150
1.07%10091
0.97%+ Added
- Removed
Updated