Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ark/schema/constraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export abstract class InternalPrimitiveConstraint<
if (js.traversalKind === "Allows") js.return(this.compiledCondition)
else {
js.if(this.compiledNegation, () =>
js.line(`${js.ctx}.errorFromNodeContext(${this.compiledErrorContext})`)
js.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`)
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion ark/schema/roots/basis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export abstract class InternalBasis<
if (js.traversalKind === "Allows") js.return(this.compiledCondition)
else {
js.if(this.compiledNegation, () =>
js.line(`${js.ctx}.errorFromNodeContext(${this.compiledErrorContext})`)
js.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`)
)
}
}
Expand Down
27 changes: 14 additions & 13 deletions ark/schema/roots/union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,18 +382,19 @@ export class UnionNode extends BaseRoot<Union.Declaration> {
for (const k in cases) {
const v = cases[k]
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: string
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}`)
}
return js
})

Expand Down Expand Up @@ -460,8 +461,8 @@ export class UnionNode extends BaseRoot<Union.Declaration> {
js.return(
optimistic ?
branch.contextFreeMorph ?
`${registeredReference(branch.contextFreeMorph)}(${js.data})`
: js.data
`${registeredReference(branch.contextFreeMorph)}(data)`
: "data"
: true
)
)
Expand Down
8 changes: 5 additions & 3 deletions ark/schema/shared/traversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ export class Traversal {
}

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(
Expand All @@ -269,6 +269,7 @@ export class Traversal {
this.errors.merge(result)
}
// skip any remaining morphs at the current path
this.queuedMorphs = []
break
}

Expand Down Expand Up @@ -301,9 +302,10 @@ export const traverseKey = <result>(
export type TraversalMethodsByKind<input = unknown> = {
Allows: TraverseAllows<input>
Apply: TraverseApply<input>
Optimistic: TraverseApply<input>
}

export type TraversalKind = keyof TraversalMethodsByKind
export type TraversalKind = keyof TraversalMethodsByKind & {}

export type TraverseAllows<data = unknown> = (
data: data,
Expand Down
4 changes: 2 additions & 2 deletions ark/schema/structure/sequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ export class SequenceNode extends BaseConstraint<Sequence.Declaration> {

for (const [i, node] of this.defaultablesAndOptionals.entries()) {
const dataIndex = `${i + this.prefixLength}`
js.if(`${dataIndex} >= ${js.data}.length`, () =>
js.if(`${dataIndex} >= data.length`, () =>
js.traversalKind === "Allows" ? js.return(true) : js.return()
)
js.traverseKey(dataIndex, `data[${dataIndex}]`, node)
Expand All @@ -442,7 +442,7 @@ export class SequenceNode extends BaseConstraint<Sequence.Declaration> {
if (this.postfix) {
js.const(
"firstPostfixIndex",
`${js.data}.length${this.postfix ? `- ${this.postfix.length}` : ""}`
`data.length${this.postfix ? `- ${this.postfix.length}` : ""}`
)
}
js.for(
Expand Down
128 changes: 128 additions & 0 deletions ark/type/__tests__/pipe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1072,4 +1072,132 @@ Right: { foo: (In: string) => Out<{ [string]: $jsonObject | number | string | fa
})
).throws.snap("TraversalError: _ must be present (was missing)")
})

// https://github.com/arktypeio/arktype/pull/1464
it("branched optimistic pipe union", () => {
class TypeA {
type = "typeA"
constructor() {}
}

class TypeB {
type = "typeB"
constructor() {}
}

const typeA = new TypeA()

const Thing = type.or(
type.instanceOf(TypeB),
type.string.pipe(_value => new TypeB()),
type.instanceOf(TypeA).pipe(_value => new TypeB())
)

const out = Thing.assert(typeA)
attest(out).instanceOf(TypeB)
})

// https://github.com/arktypeio/arktype/pull/1464
it("complex pipes", () => {
const inputData = [
{
OuterKey: [
{
MiddleKey: [
{
InnerKey: []
}
]
}
]
}
]

const genericSchema = type("Record<string, unknown>[]")
.pipe.try(arr =>
arr.map(item => {
const [kind, value] = Object.entries(item)[0]
return { kind, value }
})
)
.pipe(
type({ kind: "string", value: "unknown" })
.pipe(item => ({ kind: item.kind, value: item.value }))
.array(),
arr =>
arr.reduce<Record<string, { value: unknown }>>(
(acc, { kind, value }) => {
acc[kind] = { value }
return acc
},
{}
),
type({
OuterKey: {
value: type({
MiddleKey: type({ InnerKey: type("object") })
.array()
.pipe(v => v[0])
}).array()
}
})
)

const result = genericSchema(inputData)
attest(result).equals({
OuterKey: {
value: [
{
MiddleKey: { InnerKey: [] }
}
]
}
})
})

// https://github.com/arktypeio/arktype/pull/1464
it("nested pipes", () => {
const parseFirstElementToNumber = type("string[]")
.pipe(v => v[0])
.to("string.numeric.parse")

const extractAndParseFirstElement = type({
Value: parseFirstElementToNumber
})
.array()
.pipe(v => v[0])

const Item = type({
SubItem: extractAndParseFirstElement,
Meta: {}
})

const T = type({
Item: Item.array()
})

const data = {
Item: [
{
SubItem: [
{
Value: ["0"]
}
]
},
{
SubItem: [
{
Value: ["0"]
}
]
}
]
}

const result = T(data)

attest(result.toString()).snap(`Item[0].Meta must be an object (was missing)
Item[1].Meta must be an object (was missing)`)
})
})