From ecc6025081c36c6b14551a14784bd5e6841f0cd7 Mon Sep 17 00:00:00 2001 From: Dimava Date: Sat, 12 Oct 2024 01:07:38 +0300 Subject: [PATCH 1/5] fix performance regression on large amount of errors --- ark/schema/shared/traversal.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ark/schema/shared/traversal.ts b/ark/schema/shared/traversal.ts index 2ba1b958e..637aec697 100644 --- a/ark/schema/shared/traversal.ts +++ b/ark/schema/shared/traversal.ts @@ -149,8 +149,11 @@ 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)) + for (let i = 0; i <= path.length; i++) { + const partialPropString = pathToPropString(path.slice(0, i)) + if (Object.hasOwn(this.errors.byPath, partialPropString)) return true + } + return false } get failFast(): boolean { From 8595e0ce2fa6791ffb072957f7ff1f984685cd5e Mon Sep 17 00:00:00 2001 From: Dimava Date: Sat, 12 Oct 2024 22:55:03 +0300 Subject: [PATCH 2/5] reduce to O(N) for long paths --- ark/schema/shared/traversal.ts | 13 ++++++-- ark/schema/shared/utils.ts | 58 +++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/ark/schema/shared/traversal.ts b/ark/schema/shared/traversal.ts index 637aec697..1e9ff3e55 100644 --- a/ark/schema/shared/traversal.ts +++ b/ark/schema/shared/traversal.ts @@ -8,7 +8,12 @@ import { type ArkErrorContextInput, type ArkErrorInput } from "./errors.ts" -import { isNode, pathToPropString, type TraversalPath } from "./utils.ts" +import { + appendPropToPathString, + isNode, + pathToPropString, + type TraversalPath +} from "./utils.ts" export type MorphsAtPath = { path: TraversalPath @@ -149,8 +154,10 @@ export class TraversalContext { pathHasError(path: TraversalPath): boolean { if (!this.hasError()) return false - for (let i = 0; i <= path.length; i++) { - const partialPropString = pathToPropString(path.slice(0, i)) + let partialPropString: string = "" + if (Object.hasOwn(this.errors.byPath, partialPropString)) return true + for (let i = 0; i < path.length; i++) { + partialPropString = appendPropToPathString(partialPropString, path[i]) if (Object.hasOwn(this.errors.byPath, partialPropString)) return true } return false diff --git a/ark/schema/shared/utils.ts b/ark/schema/shared/utils.ts index ca2c47219..10d3d90f2 100644 --- a/ark/schema/shared/utils.ts +++ b/ark/schema/shared/utils.ts @@ -61,27 +61,49 @@ export type PathToPropStringFn = ( : NoInfer<[opts: PathToPropStringOptions]> ) => string -export const pathToPropString: PathToPropStringFn = (path, ...[opts]) => { +export type AppendPropToPathStringFn = ( + path: string, + prop: stringifiable, + ...[opts]: [stringifiable] extends [PropertyKey] ? + [opts?: PathToPropStringOptions] + : NoInfer<[opts: PathToPropStringOptions]> +) => string + +export const appendPropToPathString: AppendPropToPathStringFn = ( + path, + prop, + ...[opts] +) => { const stringifySymbol = opts?.stringifySymbol ?? printable - const propAccessChain = path.reduce((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((s, k) => appendPropToPathString(s, k, ...opts), "") + export type arkKind = typeof arkKind export const arkKind = noSuggest("arkKind") From 8ebea207e76fd349ef2025d44f534bf15834e48d Mon Sep 17 00:00:00 2001 From: Dimava Date: Sat, 12 Oct 2024 23:00:33 +0300 Subject: [PATCH 3/5] change this.errors.byPath to null prototyped --- ark/schema/shared/errors.ts | 2 +- ark/schema/shared/traversal.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ark/schema/shared/errors.ts b/ark/schema/shared/errors.ts index 0fe50527c..bc83d7a9b 100644 --- a/ark/schema/shared/errors.ts +++ b/ark/schema/shared/errors.ts @@ -82,7 +82,7 @@ export class ArkErrors extends ReadonlyArray { this.ctx = ctx } - byPath: Record = {} + byPath: Record = Object.create(null) count = 0 private mutable: ArkError[] = this as never diff --git a/ark/schema/shared/traversal.ts b/ark/schema/shared/traversal.ts index 1e9ff3e55..022a62f42 100644 --- a/ark/schema/shared/traversal.ts +++ b/ark/schema/shared/traversal.ts @@ -155,10 +155,11 @@ export class TraversalContext { if (!this.hasError()) return false let partialPropString: string = "" - if (Object.hasOwn(this.errors.byPath, partialPropString)) return true + // 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 (Object.hasOwn(this.errors.byPath, partialPropString)) return true + if (this.errors.byPath[partialPropString]) return true } return false } From 2cc9cd7e90a782de8fa1a657c26619bd3a60b673 Mon Sep 17 00:00:00 2001 From: Dimava Date: Sat, 12 Oct 2024 23:02:41 +0300 Subject: [PATCH 4/5] remove unneeded import --- ark/schema/shared/traversal.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ark/schema/shared/traversal.ts b/ark/schema/shared/traversal.ts index 022a62f42..44ef13b89 100644 --- a/ark/schema/shared/traversal.ts +++ b/ark/schema/shared/traversal.ts @@ -11,7 +11,6 @@ import { import { appendPropToPathString, isNode, - pathToPropString, type TraversalPath } from "./utils.ts" From 730edaf1c3125ace8e643010b8af6c26ef275137 Mon Sep 17 00:00:00 2001 From: Dimava Date: Sat, 12 Oct 2024 23:15:07 +0300 Subject: [PATCH 5/5] format --- ark/schema/shared/traversal.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ark/schema/shared/traversal.ts b/ark/schema/shared/traversal.ts index 44ef13b89..41861c63e 100644 --- a/ark/schema/shared/traversal.ts +++ b/ark/schema/shared/traversal.ts @@ -8,11 +8,7 @@ import { type ArkErrorContextInput, type ArkErrorInput } from "./errors.ts" -import { - appendPropToPathString, - isNode, - type TraversalPath -} from "./utils.ts" +import { appendPropToPathString, isNode, type TraversalPath } from "./utils.ts" export type MorphsAtPath = { path: TraversalPath