Skip to content

Commit

Permalink
perf: optimize traversals with a very large number of errors (#1172)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dimava authored Oct 12, 2024
1 parent 7adbaf6 commit cadbf41
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 22 deletions.
2 changes: 1 addition & 1 deletion ark/schema/shared/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class ArkErrors extends ReadonlyArray<ArkError> {
this.ctx = ctx
}

byPath: Record<string, ArkError> = {}
byPath: Record<string, ArkError> = Object.create(null)
count = 0
private mutable: ArkError[] = this as never

Expand Down
12 changes: 9 additions & 3 deletions ark/schema/shared/traversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
type ArkErrorContextInput,
type ArkErrorInput
} from "./errors.ts"
import { isNode, pathToPropString, type TraversalPath } from "./utils.ts"
import { appendPropToPathString, isNode, type TraversalPath } from "./utils.ts"

export type MorphsAtPath = {
path: TraversalPath
Expand Down Expand Up @@ -149,8 +149,14 @@ export class TraversalContext {
pathHasError(path: TraversalPath): boolean {
if (!this.hasError()) return false

const propString = pathToPropString(path)
return this.errors.some(e => propString.startsWith(e.propString))
let partialPropString: string = ""
// this.errors.byPath is null prototyped so indexing by string is safe
if (this.errors.byPath[partialPropString]) return true
for (let i = 0; i < path.length; i++) {
partialPropString = appendPropToPathString(partialPropString, path[i])
if (this.errors.byPath[partialPropString]) return true
}
return false
}

get failFast(): boolean {
Expand Down
58 changes: 40 additions & 18 deletions ark/schema/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,49 @@ export type PathToPropStringFn = <stringifiable>(
: NoInfer<[opts: PathToPropStringOptions<stringifiable>]>
) => string

export const pathToPropString: PathToPropStringFn = (path, ...[opts]) => {
export type AppendPropToPathStringFn = <stringifiable>(
path: string,
prop: stringifiable,
...[opts]: [stringifiable] extends [PropertyKey] ?
[opts?: PathToPropStringOptions]
: NoInfer<[opts: PathToPropStringOptions<stringifiable>]>
) => string

export const appendPropToPathString: AppendPropToPathStringFn = (
path,
prop,
...[opts]
) => {
const stringifySymbol = opts?.stringifySymbol ?? printable
const propAccessChain = path.reduce<string>((s, k) => {
switch (typeof k) {
case "string":
return isDotAccessible(k) ? `${s}.${k}` : `${s}[${JSON.stringify(k)}]`
case "number":
return `${s}[${k}]`
case "symbol":
return `${s}[${stringifySymbol(k)}]`
default:
if (opts?.stringifyNonKey)
return `${s}[${opts.stringifyNonKey(k as never)}]`
throwParseError(
`${printable(k)} must be a PropertyKey or stringifyNonKey must be passed to options`
)
}
}, "")
return propAccessChain[0] === "." ? propAccessChain.slice(1) : propAccessChain
let propAccessChain: string = path
switch (typeof prop) {
case "string":
propAccessChain =
isDotAccessible(prop) ?
path === "" ?
prop
: `${path}.${prop}`
: `${path}[${JSON.stringify(prop)}]`
break
case "number":
propAccessChain = `${path}[${prop}]`
break
case "symbol":
propAccessChain = `${path}[${stringifySymbol(prop)}]`
break
default:
if (opts?.stringifyNonKey)
propAccessChain = `${path}[${opts.stringifyNonKey(prop as never)}]`
throwParseError(
`${printable(prop)} must be a PropertyKey or stringifyNonKey must be passed to options`
)
}
return propAccessChain
}

export const pathToPropString: PathToPropStringFn = (path, ...opts) =>
path.reduce<string>((s, k) => appendPropToPathString(s, k, ...opts), "")

export type arkKind = typeof arkKind

export const arkKind = noSuggest("arkKind")
Expand Down

0 comments on commit cadbf41

Please sign in to comment.