feat: Reinitialize frontend with SvelteKit and TypeScript

- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
This commit is contained in:
2026-02-17 16:19:59 -05:00
parent 54df6018f5
commit de2d83092e
28274 changed files with 3816354 additions and 90 deletions

View File

@@ -0,0 +1,62 @@
import { CastableBase, type Fn } from "@ark/util";
import type { BaseNode } from "../node.ts";
import type { NodeId } from "../parse.ts";
import type { TraversalKind } from "./traversal.ts";
export type CoercibleValue = string | number | boolean | null | undefined;
export declare class CompiledFunction<compiledSignature = (...args: unknown[]) => unknown, args extends readonly string[] = readonly string[]> extends CastableBase<{
[k in args[number]]: k;
}> {
readonly argNames: args;
readonly body = "";
constructor(...args: args);
indentation: number;
indent(): this;
dedent(): this;
prop(key: PropertyKey, optional?: boolean): string;
index(key: string | number, optional?: boolean): string;
line(statement: string): this;
const(identifier: string, expression: CoercibleValue): this;
let(identifier: string, expression: CoercibleValue): this;
set(identifier: string, expression: CoercibleValue): this;
if(condition: string, then: (self: this) => this): this;
elseIf(condition: string, then: (self: this) => this): this;
else(then: (self: this) => this): this;
/** Current index is "i" */
for(until: string, body: (self: this) => this, initialValue?: CoercibleValue): this;
/** Current key is "k" */
forIn(object: string, body: (self: this) => this): this;
block(prefix: string, contents: (self: this) => this, suffix?: string): this;
return(expression?: CoercibleValue): this;
write(name?: string, indent?: number): string;
compile(): compiledSignature;
}
export declare const compileSerializedValue: (value: unknown) => string;
export declare const compileLiteralPropAccess: (key: PropertyKey, optional?: boolean) => string;
export declare const serializeLiteralKey: (key: PropertyKey) => string;
export declare const indexPropAccess: (key: string, optional?: boolean) => string;
export interface InvokeOptions extends ReferenceOptions {
arg?: string;
}
export interface ReferenceOptions {
kind?: TraversalKind;
bind?: string;
}
export declare namespace NodeCompiler {
interface Context {
kind: TraversalKind;
optimistic?: true;
}
}
export declare class NodeCompiler extends CompiledFunction<Fn, ["data", "ctx"]> {
traversalKind: TraversalKind;
optimistic: boolean;
constructor(ctx: NodeCompiler.Context);
invoke(node: BaseNode | NodeId, opts?: InvokeOptions): string;
referenceToId(id: NodeId, opts?: ReferenceOptions): string;
requiresContextFor(node: BaseNode): boolean;
initializeErrorCount(): this;
returnIfFail(): this;
returnIfFailFast(): this;
traverseKey(keyExpression: string, accessExpression: string, node: BaseNode): this;
check(node: BaseNode, opts?: InvokeOptions): this;
}

145
frontend/node_modules/@ark/schema/out/shared/compile.js generated vendored Normal file
View File

@@ -0,0 +1,145 @@
import { CastableBase, DynamicFunction, hasDomain, isDotAccessible, serializePrimitive } from "@ark/util";
import { registeredReference } from "./registry.js";
export class CompiledFunction extends CastableBase {
argNames;
body = "";
constructor(...args) {
super();
this.argNames = args;
for (const arg of args) {
if (arg in this) {
throw new Error(`Arg name '${arg}' would overwrite an existing property on FunctionBody`);
}
;
this[arg] = arg;
}
}
indentation = 0;
indent() {
this.indentation += 4;
return this;
}
dedent() {
this.indentation -= 4;
return this;
}
prop(key, optional = false) {
return compileLiteralPropAccess(key, optional);
}
index(key, optional = false) {
return indexPropAccess(`${key}`, optional);
}
line(statement) {
;
this.body += `${" ".repeat(this.indentation)}${statement}\n`;
return this;
}
const(identifier, expression) {
this.line(`const ${identifier} = ${expression}`);
return this;
}
let(identifier, expression) {
return this.line(`let ${identifier} = ${expression}`);
}
set(identifier, expression) {
return this.line(`${identifier} = ${expression}`);
}
if(condition, then) {
return this.block(`if (${condition})`, then);
}
elseIf(condition, then) {
return this.block(`else if (${condition})`, then);
}
else(then) {
return this.block("else", then);
}
/** Current index is "i" */
for(until, body, initialValue = 0) {
return this.block(`for (let i = ${initialValue}; ${until}; i++)`, body);
}
/** Current key is "k" */
forIn(object, body) {
return this.block(`for (const k in ${object})`, body);
}
block(prefix, contents, suffix = "") {
this.line(`${prefix} {`);
this.indent();
contents(this);
this.dedent();
return this.line(`}${suffix}`);
}
return(expression = "") {
return this.line(`return ${expression}`);
}
write(name = "anonymous", indent = 0) {
return `${name}(${this.argNames.join(", ")}) { ${indent ?
this.body
.split("\n")
.map(l => " ".repeat(indent) + `${l}`)
.join("\n")
: this.body} }`;
}
compile() {
return new DynamicFunction(...this.argNames, this.body);
}
}
export const compileSerializedValue = (value) => hasDomain(value, "object") || typeof value === "symbol" ?
registeredReference(value)
: serializePrimitive(value);
export const compileLiteralPropAccess = (key, optional = false) => {
if (typeof key === "string" && isDotAccessible(key))
return `${optional ? "?" : ""}.${key}`;
return indexPropAccess(serializeLiteralKey(key), optional);
};
export const serializeLiteralKey = (key) => typeof key === "symbol" ? registeredReference(key) : JSON.stringify(key);
export const indexPropAccess = (key, optional = false) => `${optional ? "?." : ""}[${key}]`;
export class NodeCompiler extends CompiledFunction {
traversalKind;
optimistic;
constructor(ctx) {
super("data", "ctx");
this.traversalKind = ctx.kind;
this.optimistic = ctx.optimistic === true;
}
invoke(node, opts) {
const arg = opts?.arg ?? this.data;
const requiresContext = typeof node === "string" ? true : this.requiresContextFor(node);
const id = typeof node === "string" ? node : node.id;
if (requiresContext)
return `${this.referenceToId(id, opts)}(${arg}, ${this.ctx})`;
return `${this.referenceToId(id, opts)}(${arg})`;
}
referenceToId(id, opts) {
const invokedKind = opts?.kind ?? this.traversalKind;
const base = `this.${id}${invokedKind}`;
return opts?.bind ? `${base}.bind(${opts?.bind})` : base;
}
requiresContextFor(node) {
return this.traversalKind === "Apply" || node.allowsRequiresContext;
}
initializeErrorCount() {
return this.const("errorCount", "ctx.currentErrorCount");
}
returnIfFail() {
return this.if("ctx.currentErrorCount > errorCount", () => this.return());
}
returnIfFailFast() {
return this.if("ctx.failFast && ctx.currentErrorCount > errorCount", () => this.return());
}
traverseKey(keyExpression, accessExpression, node) {
const requiresContext = this.requiresContextFor(node);
if (requiresContext)
this.line(`${this.ctx}.path.push(${keyExpression})`);
this.check(node, {
arg: accessExpression
});
if (requiresContext)
this.line(`${this.ctx}.path.pop()`);
return this;
}
check(node, opts) {
return this.traversalKind === "Allows" ?
this.if(`!${this.invoke(node, opts)}`, () => this.return(false))
: this.line(this.invoke(node, opts));
}
}

View File

@@ -0,0 +1,80 @@
import type { merge, show } from "@ark/util";
import type { UnknownErrorConfigs } from "../config.ts";
import type { nodeOfKind, reducibleKindOf } from "../kinds.ts";
import type { Disjoint } from "./disjoint.ts";
import type { ArkErrors } from "./errors.ts";
import type { NarrowedAttachments, NodeKind } from "./implement.ts";
import type { JsonSchema } from "./jsonSchema.ts";
type withMetaPrefixedKeys<o> = {
[k in keyof o as k extends string ? `meta.${k}` : never]: o[k];
};
export interface DefaultArkEnv {
meta(): {};
onFail(errors: ArkErrors): ArkErrors;
}
interface NodeMeta extends JsonSchema.UniversalMeta, UnknownErrorConfigs {
alias?: string;
onFail?: ArkErrors.Handler;
}
declare global {
export interface ArkEnv extends DefaultArkEnv {
}
export namespace ArkEnv {
type meta = show<NodeMeta & ReturnType<ArkEnv["meta"]>>;
type onFail = ReturnType<ArkEnv["onFail"]>;
}
}
export type TypeMeta = Omit<ArkEnv.meta, "onFail">;
export declare namespace TypeMeta {
type Collapsible<meta extends TypeMeta = TypeMeta> = meta | string;
type Mapper<meta extends TypeMeta = TypeMeta> = (existing: Readonly<meta>) => meta;
type MappableInput<meta extends TypeMeta = TypeMeta> = Collapsible<meta> | Mapper<meta>;
namespace MappableInput {
type Internal = MappableInput<ArkEnv.meta>;
}
}
export interface BaseNormalizedSchema extends withMetaPrefixedKeys<TypeMeta> {
readonly meta?: ArkEnv.meta | string;
}
interface DeclarationInput {
kind: NodeKind;
schema: unknown;
normalizedSchema: BaseNormalizedSchema;
inner: object;
errorContext?: BaseErrorContext;
reducibleTo?: NodeKind;
intersectionIsOpen?: true;
prerequisite?: unknown;
childKind?: NodeKind;
}
export interface BaseErrorContext<kind extends NodeKind = NodeKind> {
readonly description?: string;
readonly code: kind;
readonly meta: ArkEnv.meta;
}
export type defaultErrorContext<d extends DeclarationInput> = show<BaseErrorContext<d["kind"]> & d["inner"]>;
export type declareNode<d extends {
[k in keyof d]: k extends keyof DeclarationInput ? DeclarationInput[k] : never;
} & DeclarationInput> = merge<{
intersectionIsOpen: false;
prerequisite: prerequisiteOf<d>;
childKind: never;
reducibleTo: d["kind"];
errorContext: null;
}, d>;
type prerequisiteOf<d extends DeclarationInput> = "prerequisite" extends keyof d ? d["prerequisite"] : unknown;
export type attachmentsOf<d extends BaseNodeDeclaration> = NarrowedAttachments<d> & attachedInner<d>;
type attachedInner<d extends BaseNodeDeclaration> = "intersection" & d["kind"] extends never ? d["inner"] : {};
export interface BaseNodeDeclaration {
kind: NodeKind;
schema: unknown;
normalizedSchema: BaseNormalizedSchema;
inner: {};
reducibleTo: NodeKind;
prerequisite: any;
intersectionIsOpen: boolean;
childKind: NodeKind;
errorContext: BaseErrorContext | null;
}
export type ownIntersectionResult<d extends BaseNodeDeclaration> = nodeOfKind<reducibleKindOf<d["kind"]>> | Disjoint;
export {};

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,41 @@
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";
export interface DisjointEntry<kind extends DisjointKind = DisjointKind> {
kind: kind;
l: OperandsByDisjointKind[kind];
r: OperandsByDisjointKind[kind];
path: Key[];
optional: boolean;
}
type OperandsByDisjointKind = {
domain: nodeOfKind<"domain"> | Domain.Enumerable;
unit: nodeOfKind<"unit">;
proto: nodeOfKind<"proto">;
presence: BaseRoot;
range: nodeOfKind<BoundKind>;
assignability: BaseNode;
union: readonly BaseRoot[];
};
export type DisjointEntryContext = {
path?: Key[];
optional?: true;
};
export declare class Disjoint extends Array<DisjointEntry> {
static init<kind extends DisjointKind>(kind: kind, l: OperandsByDisjointKind[kind], r: OperandsByDisjointKind[kind], ctx?: DisjointEntryContext): Disjoint;
add<kind extends DisjointKind>(kind: kind, l: OperandsByDisjointKind[kind], r: OperandsByDisjointKind[kind], ctx?: DisjointEntryContext): Disjoint;
get summary(): string;
describeReasons(): string;
throw(): never;
invert(): Disjoint;
withPrefixKey(key: PropertyKey, kind: Prop.Kind): Disjoint;
toNeverIfDisjoint(): BaseRoot;
}
export type DisjointKind = keyof OperandsByDisjointKind;
export declare const writeUnsatisfiableExpressionError: <expression extends string>(expression: expression) => writeUnsatisfiableExpressionError<expression>;
export type writeUnsatisfiableExpressionError<expression extends string> = `${expression} results in an unsatisfiable type`;
export {};

View File

@@ -0,0 +1,65 @@
import { isArray, stringifyPath, throwParseError } from "@ark/util";
import { $ark } from "./registry.js";
import { isNode } from "./utils.js";
export class Disjoint extends Array {
static init(kind, l, r, ctx) {
return new Disjoint({
kind,
l,
r,
path: ctx?.path ?? [],
optional: ctx?.optional ?? false
});
}
add(kind, l, r, ctx) {
this.push({
kind,
l,
r,
path: ctx?.path ?? [],
optional: ctx?.optional ?? false
});
return this;
}
get summary() {
return this.describeReasons();
}
describeReasons() {
if (this.length === 1) {
const { path, l, r } = this[0];
const pathString = stringifyPath(path);
return writeUnsatisfiableExpressionError(`Intersection${pathString && ` at ${pathString}`} of ${describeReasons(l, r)}`);
}
return `The following intersections result in unsatisfiable types:\n${this.map(({ path, l, r }) => `${path}: ${describeReasons(l, r)}`).join("\n• ")}`;
}
throw() {
return throwParseError(this.describeReasons());
}
invert() {
const result = this.map(entry => ({
...entry,
l: entry.r,
r: entry.l
}));
// Workaround for Static Hermes, which doesn't preserve the Array subclass here
// https://github.com/arktypeio/arktype/issues/1027
if (!(result instanceof Disjoint))
return new Disjoint(...result);
return result;
}
withPrefixKey(key, kind) {
return this.map(entry => ({
...entry,
path: [key, ...entry.path],
optional: entry.optional || kind === "optional"
}));
}
toNeverIfDisjoint() {
return $ark.intrinsic.never;
}
}
const describeReasons = (l, r) => `${describeReason(l)} and ${describeReason(r)}`;
const describeReason = (value) => isNode(value) ? value.expression
: isArray(value) ? value.map(describeReason).join(" | ") || "never"
: String(value);
export const writeUnsatisfiableExpressionError = (expression) => `${expression} results in an unsatisfiable type`;

View File

@@ -0,0 +1,145 @@
import { CastableBase, ReadonlyArray, ReadonlyPath, type JsonArray, type JsonObject, type array, type merge, type propwiseXor, type show } from "@ark/util";
import type { Prerequisite, errorContext } from "../kinds.ts";
import type { NodeKind } from "./implement.ts";
import type { StandardSchemaV1 } from "./standardSchema.ts";
import type { Traversal } from "./traversal.ts";
import { arkKind } from "./utils.ts";
export type ArkErrorResult = ArkError | ArkErrors;
export declare class ArkError<code extends ArkErrorCode = ArkErrorCode> extends CastableBase<ArkErrorContextInput<code>> {
readonly [arkKind] = "error";
path: ReadonlyPath;
data: Prerequisite<code>;
private nodeConfig;
protected input: ArkErrorContextInput<code>;
protected ctx: Traversal;
constructor(input: ArkErrorContextInput<code>, ctx: Traversal);
transform(f: (input: ArkErrorContextInput<code>) => ArkErrorContextInput): ArkError;
hasCode<code extends ArkErrorCode>(code: code): this is ArkError<code>;
get propString(): string;
get expected(): string;
get actual(): string;
get problem(): string;
get message(): string;
get flat(): ArkError[];
toJSON(): JsonObject;
toString(): string;
throw(): never;
}
export declare namespace ArkErrors {
type Handler<returns = unknown> = (errors: ArkErrors) => returns;
}
/**
* A ReadonlyArray of `ArkError`s returned by a Type on invalid input.
*
* Subsequent errors added at an existing path are merged into an
* ArkError intersection.
*/
export declare class ArkErrors extends ReadonlyArray<ArkError> implements StandardSchemaV1.FailureResult {
readonly [arkKind] = "errors";
protected ctx: Traversal;
constructor(ctx: Traversal);
/**
* Errors by a pathString representing their location.
*/
byPath: Record<string, ArkError>;
/**
* {@link byPath} flattened so that each value is an array of ArkError instances at that path.
*
* ✅ Since "intersection" errors will be flattened to their constituent `.errors`,
* they will never be directly present in this representation.
*/
get flatByPath(): Record<string, ArkError[]>;
/**
* {@link byPath} flattened so that each value is an array of problem strings at that path.
*/
get flatProblemsByPath(): Record<string, string[]>;
/**
* All pathStrings at which errors are present mapped to the errors occuring
* at that path or any nested path within it.
*/
byAncestorPath: Record<string, ArkError[]>;
count: number;
private mutable;
/**
* Throw a TraversalError based on these errors.
*/
throw(): never;
/**
* Converts ArkErrors to TraversalError, a subclass of `Error` suitable for throwing with nice
* formatting.
*/
toTraversalError(): TraversalError;
/**
* Append an ArkError to this array, ignoring duplicates.
*/
add(error: ArkError): void;
transform(f: (e: ArkError) => ArkError): ArkErrors;
/**
* Add all errors from an ArkErrors instance, ignoring duplicates and
* prefixing their paths with that of the current Traversal.
*/
merge(errors: ArkErrors): void;
/**
* @internal
*/
affectsPath(path: ReadonlyPath): boolean;
/**
* A human-readable summary of all errors.
*/
get summary(): string;
/**
* Alias of this ArkErrors instance for StandardSchema compatibility.
*/
get issues(): this;
toJSON(): JsonArray;
toString(): string;
private addAncestorPaths;
}
export declare class TraversalError extends Error {
readonly name = "TraversalError";
arkErrors: ArkErrors;
constructor(errors: ArkErrors);
}
export interface DerivableErrorContext<code extends ArkErrorCode = ArkErrorCode> {
expected: string;
actual: string;
problem: string;
message: string;
data: Prerequisite<code>;
path: array<PropertyKey>;
propString: string;
}
export type DerivableErrorContextInput<code extends ArkErrorCode = ArkErrorCode> = Partial<DerivableErrorContext<code>> & propwiseXor<{
path?: array<PropertyKey>;
}, {
relativePath?: array<PropertyKey>;
prefixPath?: array<PropertyKey>;
}>;
export type ArkErrorCode = {
[kind in NodeKind]: errorContext<kind> extends null ? never : kind;
}[NodeKind];
type ArkErrorContextInputsByCode = {
[code in ArkErrorCode]: errorContext<code> & DerivableErrorContextInput<code>;
};
export type ArkErrorContextInput<code extends ArkErrorCode = ArkErrorCode> = merge<ArkErrorContextInputsByCode[code], {
meta?: ArkEnv.meta;
}>;
export type NodeErrorContextInput<code extends ArkErrorCode = ArkErrorCode> = ArkErrorContextInputsByCode[code] & {
meta: ArkEnv.meta;
};
export type MessageContext<code extends ArkErrorCode = ArkErrorCode> = Omit<ArkError<code>, "message">;
export type ProblemContext<code extends ArkErrorCode = ArkErrorCode> = Omit<MessageContext<code>, "problem">;
export type CustomErrorInput = show<{
code?: undefined;
} & DerivableErrorContextInput>;
export type ArkErrorInput = string | ArkErrorContextInput | CustomErrorInput;
export type ProblemConfig<code extends ArkErrorCode = ArkErrorCode> = string | ProblemWriter<code>;
export type ProblemWriter<code extends ArkErrorCode = ArkErrorCode> = (context: ProblemContext<code>) => string;
export type MessageConfig<code extends ArkErrorCode = ArkErrorCode> = string | MessageWriter<code>;
export type MessageWriter<code extends ArkErrorCode = ArkErrorCode> = (context: MessageContext<code>) => string;
export type getAssociatedDataForError<code extends ArkErrorCode> = code extends NodeKind ? Prerequisite<code> : unknown;
export type ExpectedConfig<code extends ArkErrorCode = ArkErrorCode> = string | ExpectedWriter<code>;
export type ExpectedWriter<code extends ArkErrorCode = ArkErrorCode> = (source: errorContext<code>) => string;
export type ActualConfig<code extends ArkErrorCode = ArkErrorCode> = string | ActualWriter<code>;
export type ActualWriter<code extends ArkErrorCode = ArkErrorCode> = (data: getAssociatedDataForError<code>) => string;
export {};

252
frontend/node_modules/@ark/schema/out/shared/errors.js generated vendored Normal file
View File

@@ -0,0 +1,252 @@
import { CastableBase, ReadonlyArray, ReadonlyPath, append, conflatenateAll, defineProperties, flatMorph, stringifyPath } from "@ark/util";
import { arkKind } from "./utils.js";
export class ArkError extends CastableBase {
[arkKind] = "error";
path;
data;
nodeConfig;
input;
ctx;
// TS gets confused by <code>, so internally we just use the base type for input
constructor({ prefixPath, relativePath, ...input }, ctx) {
super();
this.input = input;
this.ctx = ctx;
defineProperties(this, input);
const data = ctx.data;
if (input.code === "union") {
input.errors = input.errors.flatMap(innerError => {
// flatten union errors to avoid repeating context like "foo must be foo must be"...
const flat = innerError.hasCode("union") ? innerError.errors : [innerError];
if (!prefixPath && !relativePath)
return flat;
return flat.map(e => e.transform(e => ({
...e,
path: conflatenateAll(prefixPath, e.path, relativePath)
})));
});
}
this.nodeConfig = ctx.config[this.code];
const basePath = [...(input.path ?? ctx.path)];
if (relativePath)
basePath.push(...relativePath);
if (prefixPath)
basePath.unshift(...prefixPath);
this.path = new ReadonlyPath(...basePath);
this.data = "data" in input ? input.data : data;
}
transform(f) {
return new ArkError(f({
data: this.data,
path: this.path,
...this.input
}), this.ctx);
}
hasCode(code) {
return this.code === code;
}
get propString() {
return stringifyPath(this.path);
}
get expected() {
if (this.input.expected)
return this.input.expected;
const config = this.meta?.expected ?? this.nodeConfig.expected;
return typeof config === "function" ? config(this.input) : config;
}
get actual() {
if (this.input.actual)
return this.input.actual;
const config = this.meta?.actual ?? this.nodeConfig.actual;
return typeof config === "function" ? config(this.data) : config;
}
get problem() {
if (this.input.problem)
return this.input.problem;
const config = this.meta?.problem ?? this.nodeConfig.problem;
return typeof config === "function" ? config(this) : config;
}
get message() {
if (this.input.message)
return this.input.message;
const config = this.meta?.message ?? this.nodeConfig.message;
return typeof config === "function" ? config(this) : config;
}
get flat() {
return this.hasCode("intersection") ? [...this.errors] : [this];
}
toJSON() {
return {
data: this.data,
path: this.path,
...this.input,
expected: this.expected,
actual: this.actual,
problem: this.problem,
message: this.message
};
}
toString() {
return this.message;
}
throw() {
throw this;
}
}
/**
* A ReadonlyArray of `ArkError`s returned by a Type on invalid input.
*
* Subsequent errors added at an existing path are merged into an
* ArkError intersection.
*/
export class ArkErrors extends ReadonlyArray {
[arkKind] = "errors";
ctx;
constructor(ctx) {
super();
this.ctx = ctx;
}
/**
* Errors by a pathString representing their location.
*/
byPath = Object.create(null);
/**
* {@link byPath} flattened so that each value is an array of ArkError instances at that path.
*
* ✅ Since "intersection" errors will be flattened to their constituent `.errors`,
* they will never be directly present in this representation.
*/
get flatByPath() {
return flatMorph(this.byPath, (k, v) => [k, v.flat]);
}
/**
* {@link byPath} flattened so that each value is an array of problem strings at that path.
*/
get flatProblemsByPath() {
return flatMorph(this.byPath, (k, v) => [k, v.flat.map(e => e.problem)]);
}
/**
* All pathStrings at which errors are present mapped to the errors occuring
* at that path or any nested path within it.
*/
byAncestorPath = Object.create(null);
count = 0;
mutable = this;
/**
* Throw a TraversalError based on these errors.
*/
throw() {
throw this.toTraversalError();
}
/**
* Converts ArkErrors to TraversalError, a subclass of `Error` suitable for throwing with nice
* formatting.
*/
toTraversalError() {
return new TraversalError(this);
}
/**
* Append an ArkError to this array, ignoring duplicates.
*/
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++;
}
transform(f) {
const result = new ArkErrors(this.ctx);
for (const e of this)
result.add(f(e));
return result;
}
/**
* Add all errors from an ArkErrors instance, ignoring duplicates and
* prefixing their paths with that of the current Traversal.
*/
merge(errors) {
for (const e of errors) {
this.add(new ArkError({ ...e, path: [...this.ctx.path, ...e.path] }, this.ctx));
}
}
/**
* @internal
*/
affectsPath(path) {
if (this.length === 0)
return false;
return (
// this would occur if there is an existing error at a prefix of path
// e.g. the path is ["foo", "bar"] and there is an error at ["foo"]
path.stringifyAncestors().some(s => s in this.byPath) ||
// this would occur if there is an existing error at a suffix of path
// e.g. the path is ["foo"] and there is an error at ["foo", "bar"]
path.stringify() in this.byAncestorPath);
}
/**
* A human-readable summary of all errors.
*/
get summary() {
return this.toString();
}
/**
* Alias of this ArkErrors instance for StandardSchema compatibility.
*/
get issues() {
return this;
}
toJSON() {
return [...this.map(e => e.toJSON())];
}
toString() {
return this.join("\n");
}
addAncestorPaths(error) {
for (const propString of error.path.stringifyAncestors()) {
this.byAncestorPath[propString] = append(this.byAncestorPath[propString], error);
}
}
}
export class TraversalError extends Error {
name = "TraversalError";
constructor(errors) {
if (errors.length === 1)
super(errors.summary);
else
super("\n" + errors.map(error => `${indent(error)}`).join("\n"));
Object.defineProperty(this, "arkErrors", {
value: errors,
enumerable: false
});
}
}
const indent = (error) => error.toString().split("\n").join("\n ");

View File

@@ -0,0 +1,151 @@
import { type Entry, type Json, type JsonStructure, type KeySet, type arrayIndexOf, type keySetOf, type listable, type requireKeys, type show } from "@ark/util";
import type { NodeConfig, ResolvedUnknownNodeConfig } from "../config.ts";
import type { Declaration, Inner, errorContext, nodeOfKind } from "../kinds.ts";
import type { BaseNode } from "../node.ts";
import type { NodeId, NodeParseContext } from "../parse.ts";
import type { BaseRoot, schemaKindOrRightOf, schemaKindRightOf } from "../roots/root.ts";
import type { BaseScope, ResolvedScopeConfig } from "../scope.ts";
import type { Structure } from "../structure/structure.ts";
import type { BaseErrorContext, BaseNodeDeclaration, BaseNormalizedSchema } from "./declare.ts";
import type { Disjoint } from "./disjoint.ts";
import { type makeRootAndArrayPropertiesMutable } from "./utils.ts";
export declare const basisKinds: readonly ["unit", "proto", "domain"];
export type BasisKind = (typeof basisKinds)[number];
export declare const structuralKinds: readonly ["required", "optional", "index", "sequence"];
export type StructuralKind = (typeof structuralKinds)[number];
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];
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];
export declare const rootKinds: readonly ["alias", "union", "morph", "unit", "intersection", "proto", "domain"];
export type RootKind = (typeof rootKinds)[number];
export type NodeKind = RootKind | ConstraintKind;
type orderedNodeKinds = [...typeof rootKinds, ...typeof constraintKinds];
export declare const nodeKinds: orderedNodeKinds;
export type OpenNodeKind = {
[k in NodeKind]: Declaration<k>["intersectionIsOpen"] extends true ? k : never;
}[NodeKind];
export type ClosedNodeKind = Exclude<NodeKind, OpenNodeKind>;
export type PrimitiveKind = Exclude<RefinementKind | BasisKind, "structure">;
export type CompositeKind = Exclude<NodeKind, PrimitiveKind>;
export type OrderedNodeKinds = typeof nodeKinds;
export declare const constraintKeys: KeySet<ConstraintKind>;
export declare const structureKeys: keySetOf<Structure.Inner>;
type RightsByKind = accumulateRightKinds<OrderedNodeKinds, {}>;
export type kindOrRightOf<kind extends NodeKind> = kind | kindRightOf<kind>;
export type kindLeftOf<kind extends NodeKind> = Exclude<NodeKind, kindOrRightOf<kind>>;
export type kindOrLeftOf<kind extends NodeKind> = kind | kindLeftOf<kind>;
type accumulateRightKinds<remaining extends readonly NodeKind[], result> = remaining extends (readonly [infer head extends NodeKind, ...infer tail extends NodeKind[]]) ? accumulateRightKinds<tail, result & {
[k in head]: tail[number];
}> : result;
export interface InternalIntersectionOptions {
pipe: boolean;
}
export interface IntersectionContext extends InternalIntersectionOptions {
$: BaseScope;
invert: boolean;
}
export type ConstraintIntersection<lKind extends ConstraintKind, rKind extends kindOrRightOf<lKind>> = (l: nodeOfKind<lKind>, r: nodeOfKind<rKind>, ctx: IntersectionContext) => BaseNode | Disjoint | null;
export type ConstraintIntersectionMap<kind extends ConstraintKind> = show<{
[_ in kind]: ConstraintIntersection<kind, kind>;
} & {
[rKind in kindRightOf<kind>]?: ConstraintIntersection<kind, rKind>;
}>;
export type RootIntersection<lKind extends RootKind, rKind extends schemaKindOrRightOf<lKind>> = (l: nodeOfKind<lKind>, r: nodeOfKind<rKind>, ctx: IntersectionContext) => BaseRoot | Disjoint;
export type TypeIntersectionMap<kind extends RootKind> = {
[rKind in schemaKindOrRightOf<kind>]: RootIntersection<kind, rKind>;
};
export type IntersectionMap<kind extends NodeKind> = kind extends RootKind ? TypeIntersectionMap<kind> : ConstraintIntersectionMap<kind & ConstraintKind>;
export type UnknownIntersectionMap = {
[k in NodeKind]?: (l: BaseNode, r: BaseNode, ctx: IntersectionContext) => UnknownIntersectionResult;
};
export type UnknownIntersectionResult = BaseNode | Disjoint | null;
type PrecedenceByKind = {
[i in arrayIndexOf<OrderedNodeKinds> as OrderedNodeKinds[i]]: i;
};
export declare const precedenceByKind: PrecedenceByKind;
export declare const isNodeKind: (value: unknown) => value is NodeKind;
export declare function assertNodeKind<kind extends NodeKind>(value: BaseNode, kind: kind): asserts value is nodeOfKind<kind>;
export type precedenceOfKind<kind extends NodeKind> = PrecedenceByKind[kind];
export declare const precedenceOfKind: <kind extends NodeKind>(kind: kind) => precedenceOfKind<kind>;
export type kindRightOf<kind extends NodeKind> = RightsByKind[kind];
export declare const schemaKindsRightOf: <kind extends RootKind>(kind: kind) => schemaKindRightOf<kind>[];
export declare const unionChildKinds: readonly [...("intersection" | "morph" | "unit" | "proto" | "domain")[], "alias"];
export type UnionChildKind = (typeof unionChildKinds)[number];
export declare const morphChildKinds: readonly [...("intersection" | "unit" | "proto" | "domain")[], "alias"];
export type MorphChildKind = (typeof morphChildKinds)[number];
export type keySchemaDefinitions<d extends BaseNodeDeclaration> = {
[k in keyRequiringSchemaDefinition<d>]: NodeKeyImplementation<d, k>;
};
type keyRequiringSchemaDefinition<d extends BaseNodeDeclaration> = Exclude<keyof d["normalizedSchema"], keyof BaseNormalizedSchema>;
export declare const defaultValueSerializer: (v: unknown) => Json;
export type NodeKeyImplementation<d extends BaseNodeDeclaration, k extends keyof d["normalizedSchema"], instantiated = k extends keyof d["inner"] ? Exclude<d["inner"][k], undefined> : never> = requireKeys<{
preserveUndefined?: true;
child?: boolean | ((value: instantiated) => BaseNode[]);
serialize?: (schema: instantiated) => Json;
reduceIo?: (ioKind: "in" | "out", inner: makeRootAndArrayPropertiesMutable<d["inner"]>, value: d["inner"][k]) => void;
parse?: (schema: Exclude<d["normalizedSchema"][k], undefined>, ctx: NodeParseContext<d["kind"]>) => instantiated | undefined;
}, (d["normalizedSchema"][k] extends instantiated | undefined ? never : "parse") | ([instantiated] extends [listable<BaseNode>] ? "child" : never)>;
interface CommonNodeImplementationInput<d extends BaseNodeDeclaration> {
kind: d["kind"];
keys: keySchemaDefinitions<d>;
normalize: (schema: d["schema"], $: BaseScope) => d["normalizedSchema"];
applyConfig?: (schema: d["normalizedSchema"], config: ResolvedScopeConfig) => d["normalizedSchema"];
hasAssociatedError: d["errorContext"] extends null ? false : true;
finalizeInnerJson?: (json: {
[k in keyof d["inner"]]: Json;
}) => JsonStructure;
collapsibleKey?: keyof d["inner"];
reduce?: (inner: d["inner"], $: BaseScope) => nodeOfKind<d["reducibleTo"]> | Disjoint | undefined;
obviatesBasisDescription?: d["kind"] extends RefinementKind ? true : never;
obviatesBasisExpression?: d["kind"] extends RefinementKind ? true : never;
}
export interface UnknownNodeImplementation extends CommonNodeImplementationInput<BaseNodeDeclaration> {
defaults: ResolvedUnknownNodeConfig;
intersectionIsOpen: boolean;
intersections: UnknownIntersectionMap;
keys: Record<string, NodeKeyImplementation<any, any>>;
}
export declare const compileObjectLiteral: (ctx: object) => string;
export type nodeImplementationOf<d extends BaseNodeDeclaration> = nodeImplementationInputOf<d> & {
intersections: IntersectionMap<d["kind"]>;
intersectionIsOpen: d["intersectionIsOpen"];
defaults: Required<NodeConfig<d["kind"]>>;
};
export type nodeImplementationInputOf<d extends BaseNodeDeclaration> = CommonNodeImplementationInput<d> & {
intersections: IntersectionMap<d["kind"]>;
defaults: nodeSchemaaultsImplementationInputFor<d["kind"]>;
} & (d["intersectionIsOpen"] extends true ? {
intersectionIsOpen: true;
} : {}) & (d["reducibleTo"] extends d["kind"] ? {} : {
reduce: {};
});
type nodeSchemaaultsImplementationInputFor<kind extends NodeKind> = requireKeys<NodeConfig<kind>, "description" | (Inner<kind> extends (Omit<errorContext<kind>, keyof BaseErrorContext | "description">) ? never : "expected" & keyof NodeConfig<kind>)>;
export type DescriptionWriter<kind extends NodeKind = NodeKind> = (node: nodeOfKind<kind>) => string;
export interface UnknownAttachments {
readonly kind: NodeKind;
readonly impl: UnknownNodeImplementation;
readonly id: NodeId;
readonly inner: Record<string, any>;
readonly innerEntries: readonly Entry<string>[];
readonly innerJson: object;
readonly innerHash: string;
readonly meta: ArkEnv.meta;
readonly metaJson: object;
readonly json: object;
readonly hash: string;
readonly collapsibleJson: Json;
readonly children: BaseNode[];
}
export interface NarrowedAttachments<d extends BaseNodeDeclaration> extends UnknownAttachments {
kind: d["kind"];
inner: d["inner"];
json: JsonStructure;
innerJson: JsonStructure;
collapsibleJson: Json;
children: nodeOfKind<d["childKind"]>[];
}
export declare const implementNode: <d extends BaseNodeDeclaration = never>(_: nodeImplementationInputOf<d>) => nodeImplementationOf<d>;
export {};

View File

@@ -0,0 +1,98 @@
import { flatMorph, printable, throwParseError } from "@ark/util";
import { compileSerializedValue } from "./compile.js";
import { isNode } from "./utils.js";
export const basisKinds = ["unit", "proto", "domain"];
export const structuralKinds = [
"required",
"optional",
"index",
"sequence"
];
export const prestructuralKinds = [
"pattern",
"divisor",
"exactLength",
"max",
"min",
"maxLength",
"minLength",
"before",
"after"
];
export const refinementKinds = [
...prestructuralKinds,
"structure",
"predicate"
];
export const constraintKinds = [...refinementKinds, ...structuralKinds];
export const rootKinds = [
"alias",
"union",
"morph",
"unit",
"intersection",
"proto",
"domain"
];
export const nodeKinds = [...rootKinds, ...constraintKinds];
export const constraintKeys = flatMorph(constraintKinds, (i, kind) => [kind, 1]);
export const structureKeys = flatMorph([...structuralKinds, "undeclared"], (i, k) => [k, 1]);
export const precedenceByKind = flatMorph(nodeKinds, (i, kind) => [kind, i]);
export const isNodeKind = (value) => typeof value === "string" && value in precedenceByKind;
export function assertNodeKind(value, kind) {
const valueIsNode = isNode(value);
if (!valueIsNode || value.kind !== kind) {
throwParseError(`Expected node of kind ${kind} (was ${valueIsNode ? `${value.kind} node` : printable(value)})`);
}
}
export const precedenceOfKind = (kind) => precedenceByKind[kind];
export const schemaKindsRightOf = (kind) => rootKinds.slice(precedenceOfKind(kind) + 1);
export const unionChildKinds = [
...schemaKindsRightOf("union"),
"alias"
];
export const morphChildKinds = [
...schemaKindsRightOf("morph"),
"alias"
];
export const defaultValueSerializer = (v) => {
if (typeof v === "string" || typeof v === "boolean" || v === null)
return v;
if (typeof v === "number") {
if (Number.isNaN(v))
return "NaN";
if (v === Number.POSITIVE_INFINITY)
return "Infinity";
if (v === Number.NEGATIVE_INFINITY)
return "-Infinity";
return v;
}
return compileSerializedValue(v);
};
export const compileObjectLiteral = (ctx) => {
let result = "{ ";
for (const [k, v] of Object.entries(ctx))
result += `${k}: ${compileSerializedValue(v)}, `;
return result + " }";
};
export const implementNode = (_) => {
const implementation = _;
if (implementation.hasAssociatedError) {
implementation.defaults.expected ??= ctx => "description" in ctx ?
ctx.description
: implementation.defaults.description(ctx);
implementation.defaults.actual ??= data => printable(data);
implementation.defaults.problem ??= ctx => `must be ${ctx.expected}${ctx.actual ? ` (was ${ctx.actual})` : ""}`;
implementation.defaults.message ??= ctx => {
if (ctx.path.length === 0)
return ctx.problem;
const problemWithLocation = `${ctx.propString} ${ctx.problem}`;
if (problemWithLocation[0] === "[") {
// clarify paths like [1], [0][1], and ["key!"] that could be confusing
return `value at ${problemWithLocation}`;
}
return problemWithLocation;
};
}
return implementation;
};

View File

@@ -0,0 +1,10 @@
import type { BaseNode } from "../node.ts";
import type { BaseRoot } from "../roots/root.ts";
import type { BaseScope } from "../scope.ts";
import { Disjoint } from "./disjoint.ts";
import { type IntersectionContext, type RootKind } from "./implement.ts";
type InternalNodeIntersection<ctx> = <l extends BaseNode, r extends BaseNode>(l: l, r: r, ctx: ctx) => l["kind"] | r["kind"] extends RootKind ? BaseRoot | Disjoint : BaseNode | Disjoint | null;
export declare const intersectNodesRoot: InternalNodeIntersection<BaseScope>;
export declare const pipeNodesRoot: InternalNodeIntersection<BaseScope>;
export declare const intersectOrPipeNodes: InternalNodeIntersection<IntersectionContext>;
export {};

View File

@@ -0,0 +1,132 @@
import { Disjoint } from "./disjoint.js";
import { rootKinds } from "./implement.js";
import { isNode } from "./utils.js";
const intersectionCache = {};
export const intersectNodesRoot = (l, r, $) => intersectOrPipeNodes(l, r, {
$,
invert: false,
pipe: false
});
export const pipeNodesRoot = (l, r, $) => intersectOrPipeNodes(l, r, {
$,
invert: false,
pipe: true
});
export const intersectOrPipeNodes = ((l, r, ctx) => {
const operator = ctx.pipe ? "|>" : "&";
const lrCacheKey = `${l.hash}${operator}${r.hash}`;
if (intersectionCache[lrCacheKey] !== undefined)
return intersectionCache[lrCacheKey];
if (!ctx.pipe) {
// we can only use this for the commutative & operator
const rlCacheKey = `${r.hash}${operator}${l.hash}`;
if (intersectionCache[rlCacheKey] !== undefined) {
// if the cached result was a Disjoint and the operands originally
// appeared in the opposite order, we need to invert it to match
const rlResult = intersectionCache[rlCacheKey];
const lrResult = rlResult instanceof Disjoint ? rlResult.invert() : rlResult;
// add the lr result to the cache directly to bypass this check in the future
intersectionCache[lrCacheKey] = lrResult;
return lrResult;
}
}
const isPureIntersection = !ctx.pipe || (!l.includesTransform && !r.includesTransform);
if (isPureIntersection && l.equals(r))
return l;
let result = isPureIntersection ? _intersectNodes(l, r, ctx)
: l.hasKindIn(...rootKinds) ?
// if l is a RootNode, r will be as well
_pipeNodes(l, r, ctx)
: _intersectNodes(l, r, ctx);
if (isNode(result)) {
// if the result equals one of the operands, preserve its metadata by
// returning the original reference
if (l.equals(result))
result = l;
else if (r.equals(result))
result = r;
}
intersectionCache[lrCacheKey] = result;
return result;
});
const _intersectNodes = (l, r, ctx) => {
const leftmostKind = l.precedence < r.precedence ? l.kind : r.kind;
const implementation = l.impl.intersections[r.kind] ?? r.impl.intersections[l.kind];
if (implementation === undefined) {
// should be two ConstraintNodes that have no relation
// this could also happen if a user directly intersects a Type and a ConstraintNode,
// but that is not allowed by the external function signature
return null;
}
else if (leftmostKind === l.kind)
return implementation(l, r, ctx);
else {
let result = implementation(r, l, { ...ctx, invert: !ctx.invert });
if (result instanceof Disjoint)
result = result.invert();
return result;
}
};
const _pipeNodes = (l, r, ctx) => l.includesTransform || r.includesTransform ?
ctx.invert ?
pipeMorphed(r, l, ctx)
: pipeMorphed(l, r, ctx)
: _intersectNodes(l, r, ctx);
const pipeMorphed = (from, to, ctx) => from.distribute(fromBranch => _pipeMorphed(fromBranch, to, ctx), results => {
const viableBranches = results.filter(isNode);
if (viableBranches.length === 0)
return Disjoint.init("union", from.branches, to.branches);
// if the input type has changed, create a new node without preserving metadata
if (viableBranches.length < from.branches.length ||
!from.branches.every((branch, i) => branch.rawIn.equals(viableBranches[i].rawIn)))
return ctx.$.parseSchema(viableBranches);
// otherwise, the input has not changed so preserve metadata
let meta;
if (viableBranches.length === 1) {
const onlyBranch = viableBranches[0];
if (!meta)
return onlyBranch;
return ctx.$.node("morph", {
...onlyBranch.inner,
in: onlyBranch.rawIn.configure(meta, "self")
});
}
const schema = {
branches: viableBranches
};
if (meta)
schema.meta = meta;
return ctx.$.parseSchema(schema);
});
const _pipeMorphed = (from, to, ctx) => {
const fromIsMorph = from.hasKind("morph");
if (fromIsMorph) {
const morphs = [...from.morphs];
if (from.lastMorphIfNode) {
// still piped from context, so allows appending additional morphs
const outIntersection = intersectOrPipeNodes(from.lastMorphIfNode, to, ctx);
if (outIntersection instanceof Disjoint)
return outIntersection;
morphs[morphs.length - 1] = outIntersection;
}
else
morphs.push(to);
return ctx.$.node("morph", {
morphs,
in: from.inner.in
});
}
if (to.hasKind("morph")) {
const inTersection = intersectOrPipeNodes(from, to.rawIn, ctx);
if (inTersection instanceof Disjoint)
return inTersection;
return ctx.$.node("morph", {
morphs: [to],
in: inTersection
});
}
return ctx.$.node("morph", {
morphs: [to],
in: from
});
};

View File

@@ -0,0 +1,93 @@
import type { array, autocomplete, JsonArray, JsonObject, listable } from "@ark/util";
export type JsonSchema = JsonSchema.NonBooleanBranch;
export type ListableJsonSchema = listable<JsonSchema>;
export type JsonSchemaOrBoolean = listable<JsonSchema.Branch>;
export declare namespace JsonSchema {
type TypeName = "string" | "integer" | "number" | "object" | "array" | "boolean" | "null";
/**
* a subset of JSON Schema's annotations, see:
* https://json-schema.org/understanding-json-schema/reference/annotations
**/
interface Meta<t = unknown> extends UniversalMeta<t> {
$schema?: string;
$defs?: Record<string, JsonSchema>;
}
type Format = autocomplete<"date-time" | "date" | "time" | "email" | "ipv4" | "ipv6" | "uri" | "uuid" | "regex">;
/**
* doesn't include root-only keys like $schema
*/
interface UniversalMeta<t = unknown> {
title?: string;
description?: string;
format?: Format;
deprecated?: true;
default?: t;
examples?: readonly t[];
}
type Composition = Union | OneOf | Intersection | Not;
type NonBooleanBranch = Constrainable | Const | Composition | Enum | String | Numeric | Object | Array | Ref;
type Branch = boolean | JsonSchema;
type RefString = `#/$defs/${string}`;
interface Ref extends Meta {
$ref: RefString;
type?: never;
}
interface Constrainable extends Meta {
type?: listable<TypeName>;
}
interface Intersection extends Meta {
allOf: readonly JsonSchema[];
}
interface Not extends Meta {
not: JsonSchema;
}
interface OneOf extends Meta {
oneOf: readonly JsonSchema[];
}
interface Union extends Meta {
anyOf: readonly JsonSchema[];
}
interface Const extends Meta {
const: unknown;
}
interface Enum extends Meta {
enum: array;
}
interface String extends Meta<string> {
type: "string";
minLength?: number;
maxLength?: number;
pattern?: string;
format?: string;
}
interface Numeric extends Meta<number> {
type: "number" | "integer";
multipleOf?: number;
minimum?: number;
exclusiveMinimum?: number;
maximum?: number;
exclusiveMaximum?: number;
}
interface Object extends Meta<JsonObject> {
type: "object";
properties?: Record<string, JsonSchema>;
required?: string[];
patternProperties?: Record<string, JsonSchema>;
additionalProperties?: JsonSchemaOrBoolean;
maxProperties?: number;
minProperties?: number;
propertyNames?: String;
}
interface Array extends Meta<JsonArray> {
type: "array";
additionalItems?: JsonSchemaOrBoolean;
contains?: JsonSchemaOrBoolean;
uniqueItems?: boolean;
minItems?: number;
maxItems?: number;
items?: JsonSchemaOrBoolean;
prefixItems?: readonly Branch[];
}
type LengthBoundable = String | Array;
type Structure = Object | Array;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,7 @@
import { type NonNegativeIntegerLiteral } from "@ark/util";
import type { ArkSchemaRegistry } from "../config.ts";
export declare const registryName: string;
export declare const $ark: ArkSchemaRegistry;
export declare const reference: (name: string) => RegisteredReference;
export declare const registeredReference: (value: object | symbol) => RegisteredReference;
export type RegisteredReference<to extends string = string> = `$ark${"" | NonNegativeIntegerLiteral}.${to}`;

View File

@@ -0,0 +1,10 @@
import { register, registry } from "@ark/util";
let _registryName = "$ark";
let suffix = 2;
while (_registryName in globalThis)
_registryName = `$ark${suffix++}`;
export const registryName = _registryName;
globalThis[registryName] = registry;
export const $ark = registry;
export const reference = (name) => `${registryName}.${name}`;
export const registeredReference = (value) => reference(register(value));

View File

@@ -0,0 +1,122 @@
/** From https://github.com/standard-schema/standard-schema */
/** The Standard Typed interface. This is a base type extended by other specs. */
export interface StandardTypedV1<Input = unknown, Output = Input> {
/** The Standard properties. */
readonly "~standard": StandardTypedV1.Props<Input, Output>;
}
export declare namespace StandardTypedV1 {
/** The Standard Typed properties interface. */
interface Props<Input = unknown, Output = Input> {
/** The version number of the standard. */
readonly version: 1;
/** The vendor name of the schema library. */
readonly vendor: string;
/** Inferred types associated with the schema. */
readonly types?: Types<Input, Output> | undefined;
}
/** The Standard Typed types interface. */
interface Types<Input = unknown, Output = Input> {
/** The input type of the schema. */
readonly input: Input;
/** The output type of the schema. */
readonly output: Output;
}
/** Infers the input type of a Standard Typed. */
type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
/** Infers the output type of a Standard Typed. */
type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
}
/** The Standard Schema interface. */
export interface StandardSchemaV1<Input = unknown, Output = Input> {
/** The Standard Schema properties. */
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
}
export declare namespace StandardSchemaV1 {
/** The Standard Schema properties interface. */
interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
/** Validates unknown input values. */
readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
}
/** The result interface of the validate function. */
type Result<Output> = SuccessResult<Output> | FailureResult;
/** The result interface if validation succeeds. */
interface SuccessResult<Output> {
/** The typed output value. */
readonly value: Output;
/** A falsy value for `issues` indicates success. */
readonly issues?: undefined;
}
interface Options {
/** Explicit support for additional vendor-specific parameters, if needed. */
readonly libraryOptions?: Record<string, unknown> | undefined;
}
/** The result interface if validation fails. */
interface FailureResult {
/** The issues of failed validation. */
readonly issues: ReadonlyArray<Issue>;
}
/** The issue interface of the failure output. */
interface Issue {
/** The error message of the issue. */
readonly message: string;
/** The path of the issue, if any. */
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
}
/** The path segment interface of the issue. */
interface PathSegment {
/** The key representing a path segment. */
readonly key: PropertyKey;
}
/** The Standard types interface. */
interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {
}
/** Infers the input type of a Standard. */
type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
/** Infers the output type of a Standard. */
type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
/** ArkType-specific properties that extend the standard schema with JSON Schema support. */
interface ArkTypeProps<Input = unknown, Output = Input> extends Props<Input, Output>, StandardJSONSchemaV1.Props<Input, Output> {
vendor: "arktype";
}
}
/** The Standard JSON Schema interface. */
export interface StandardJSONSchemaV1<Input = unknown, Output = Input> {
/** The Standard JSON Schema properties. */
readonly "~standard": StandardJSONSchemaV1.Props<Input, Output>;
}
export declare namespace StandardJSONSchemaV1 {
/** The Standard JSON Schema properties interface. */
interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
/** Methods for generating the input/output JSON Schema. */
readonly jsonSchema: StandardJSONSchemaV1.Converter;
}
/** The Standard JSON Schema converter interface. */
interface Converter {
/** Converts the input type to JSON Schema. May throw if conversion is not supported. */
readonly input: (options: StandardJSONSchemaV1.Options) => Record<string, unknown>;
/** Converts the output type to JSON Schema. May throw if conversion is not supported. */
readonly output: (options: StandardJSONSchemaV1.Options) => Record<string, unknown>;
}
/**
* The target version of the generated JSON Schema.
*
* It is *strongly recommended* that implementers support `"draft-2020-12"` and `"draft-07"`, as they are both in wide use. All other targets can be implemented on a best-effort basis. Libraries should throw if they don't support a specified target.
*
* The `"openapi-3.0"` target is intended as a standardized specifier for OpenAPI 3.0 which is a superset of JSON Schema `"draft-04"`.
*/
type Target = "draft-2020-12" | "draft-07" | "openapi-3.0" | ({} & string);
/** The options for the input/output methods. */
interface Options {
/** Specifies the target version of the generated JSON Schema. Support for all versions is on a best-effort basis. If a given version is not supported, the library should throw. */
readonly target: Target;
/** Explicit support for additional vendor-specific parameters, if needed. */
readonly libraryOptions?: Record<string, unknown> | undefined;
}
/** The Standard types interface. */
interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {
}
/** Infers the input type of a Standard. */
type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
/** Infers the output type of a Standard. */
type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
}

View File

@@ -0,0 +1,2 @@
/** From https://github.com/standard-schema/standard-schema */
export {};

View File

@@ -0,0 +1,139 @@
import { type Constructor, type Domain, type Json, type requireKeys, type satisfy } from "@ark/util";
import type { Predicate } from "../predicate.ts";
import type { ConstraintKind } from "./implement.ts";
import type { JsonSchema } from "./jsonSchema.ts";
import type { StandardJSONSchemaV1 } from "./standardSchema.ts";
declare class ToJsonSchemaError<code extends ToJsonSchema.Code = ToJsonSchema.Code> extends Error {
readonly name = "ToJsonSchemaError";
readonly code: code;
readonly context: ToJsonSchema.ContextByCode[code];
constructor(code: code, context: ToJsonSchema.ContextByCode[code]);
hasCode<code extends ToJsonSchema.Code>(code: code): this is ToJsonSchemaError<code>;
}
export declare const ToJsonSchema: {
Error: typeof ToJsonSchemaError;
throw: (code: keyof ToJsonSchema.ContextByCode, context: ToJsonSchema.MorphContext | ToJsonSchema.UnitContext | ToJsonSchema.ProtoContext | ToJsonSchema.DomainContext | ToJsonSchema.PredicateContext | ToJsonSchema.DateContext | ToJsonSchema.ArrayObjectContext | ToJsonSchema.ArrayPostfixContext | ToJsonSchema.DefaultValueContext | ToJsonSchema.PatternIntersectionContext | ToJsonSchema.SymbolKeyContext) => never;
throwInternalOperandError: (kind: ConstraintKind, schema: JsonSchema) => never;
defaultConfig: ToJsonSchema.Context;
};
export declare namespace ToJsonSchema {
type Unjsonifiable = object | symbol | bigint | undefined;
type Error = InstanceType<typeof ToJsonSchema.Error>;
interface BaseContext<code extends Code, base extends JsonSchema = JsonSchema> {
code: code;
base: base;
}
interface ArrayObjectContext extends BaseContext<"arrayObject", JsonSchema.Array> {
object: JsonSchema.Object;
}
interface ArrayPostfixContext extends BaseContext<"arrayPostfix", VariadicArraySchema> {
elements: readonly JsonSchema[];
}
interface DefaultValueContext extends BaseContext<"defaultValue", JsonSchema> {
value: Unjsonifiable;
}
interface DomainContext extends BaseContext<"domain", JsonSchema> {
domain: satisfy<Domain, "symbol" | "bigint" | "undefined">;
}
interface MorphContext extends BaseContext<"morph", JsonSchema> {
out: JsonSchema | null;
}
interface PatternIntersectionContext extends BaseContext<"patternIntersection", StringSchemaWithPattern> {
pattern: string;
}
interface PredicateContext extends BaseContext<"predicate", JsonSchema> {
predicate: Predicate;
}
interface ProtoContext extends BaseContext<"proto", JsonSchema> {
proto: Constructor;
}
type SymbolKeyContext = IndexSymbolKeyContext | RequiredSymbolKeyContext | OptionalSymbolKeyContext;
interface IndexSymbolKeyContext extends BaseContext<"symbolKey", JsonSchema.Object> {
key: null;
value: JsonSchema;
optional: false;
}
interface RequiredSymbolKeyContext extends BaseContext<"symbolKey", JsonSchema.Object> {
key: symbol;
value: JsonSchema;
optional: false;
}
interface OptionalSymbolKeyContext extends BaseContext<"symbolKey", JsonSchema.Object> {
key: symbol;
value: JsonSchema;
optional: true;
default?: Json;
}
interface UnitContext extends BaseContext<"unit", JsonSchema> {
unit: Unjsonifiable;
}
interface DateContext extends BaseContext<"date", JsonSchema> {
before?: Date;
after?: Date;
}
interface ContextByCode {
arrayObject: ArrayObjectContext;
arrayPostfix: ArrayPostfixContext;
defaultValue: DefaultValueContext;
domain: DomainContext;
morph: MorphContext;
patternIntersection: PatternIntersectionContext;
predicate: PredicateContext;
proto: ProtoContext;
symbolKey: SymbolKeyContext;
unit: UnitContext;
date: DateContext;
}
type Code = keyof ContextByCode;
type FallbackContext = ContextByCode[Code];
type HandlerByCode = satisfy<{
[code in Code]: (ctx: ContextByCode[code]) => unknown;
}, {
arrayObject: (ctx: ArrayObjectContext) => JsonSchema.Structure;
arrayPostfix: (ctx: ArrayPostfixContext) => VariadicArraySchema;
defaultValue: (ctx: DefaultValueContext) => JsonSchema;
domain: (ctx: DomainContext) => JsonSchema;
morph: (ctx: MorphContext) => JsonSchema;
patternIntersection: (ctx: PatternIntersectionContext) => JsonSchema.String;
predicate: (ctx: PredicateContext) => JsonSchema;
proto: (ctx: ProtoContext) => JsonSchema;
symbolKey: (ctx: SymbolKeyContext) => JsonSchema.Object;
unit: (ctx: UnitContext) => JsonSchema;
date: (ctx: DateContext) => JsonSchema;
}>;
type VariadicArraySchema = requireKeys<JsonSchema.Array, "items">;
type StringSchemaWithPattern = requireKeys<JsonSchema.String, "pattern">;
type UniversalFallback = (ctx: FallbackContext) => JsonSchema;
interface FallbackObject extends Partial<HandlerByCode> {
default?: UniversalFallback;
}
type FallbackOption = UniversalFallback | FallbackObject;
type Target = satisfy<StandardJSONSchemaV1.Target, "draft-2020-12" | "draft-07">;
interface Options {
/** value to assign to the generated $schema key
*
* - set to `null` to omit the `$schema` key
* - does not affect the contents of the generated schema
* - if `target` is also specified, `dialect` takes precedence
*
* @default "https://json-schema.org/draft/2020-12/schema"
*/
dialect?: string | null;
/**
* Shorthand for specifying the target JSON Schema version.
* Maps to the appropriate `dialect` URL.
*
* - "draft-2020-12" -> "https://json-schema.org/draft/2020-12/schema"
* - "draft-07" -> "http://json-schema.org/draft-07/schema#"
*
* If `dialect` is also specified, `dialect` takes precedence.
*/
target?: Target;
useRefs?: boolean;
fallback?: FallbackOption;
}
interface Context extends Required<Options> {
fallback: HandlerByCode;
}
}
export {};

View File

@@ -0,0 +1,40 @@
import { printable, throwInternalError } from "@ark/util";
class ToJsonSchemaError extends Error {
name = "ToJsonSchemaError";
code;
context;
constructor(code, context) {
super(printable(context, { quoteKeys: false, indent: 4 }));
this.code = code;
this.context = context;
}
hasCode(code) {
return this.code === code;
}
}
const defaultConfig = {
target: "draft-2020-12",
dialect: "https://json-schema.org/draft/2020-12/schema",
useRefs: false,
fallback: {
arrayObject: ctx => ToJsonSchema.throw("arrayObject", ctx),
arrayPostfix: ctx => ToJsonSchema.throw("arrayPostfix", ctx),
defaultValue: ctx => ToJsonSchema.throw("defaultValue", ctx),
domain: ctx => ToJsonSchema.throw("domain", ctx),
morph: ctx => ToJsonSchema.throw("morph", ctx),
patternIntersection: ctx => ToJsonSchema.throw("patternIntersection", ctx),
predicate: ctx => ToJsonSchema.throw("predicate", ctx),
proto: ctx => ToJsonSchema.throw("proto", ctx),
symbolKey: ctx => ToJsonSchema.throw("symbolKey", ctx),
unit: ctx => ToJsonSchema.throw("unit", ctx),
date: ctx => ToJsonSchema.throw("date", ctx)
}
};
export const ToJsonSchema = {
Error: ToJsonSchemaError,
throw: (...args) => {
throw new ToJsonSchema.Error(...args);
},
throwInternalOperandError: (kind, schema) => throwInternalError(`Unexpected JSON Schema input for ${kind}: ${printable(schema)}`),
defaultConfig
};

View File

@@ -0,0 +1,117 @@
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, type NodeErrorContextInput } from "./errors.ts";
export type MorphsAtPath = {
path: ReadonlyPath;
morphs: array<Morph>;
};
export type BranchTraversal = {
error: ArkError | undefined;
queuedMorphs: MorphsAtPath[];
};
export type InternalTraversal = Omit<Traversal, "error" | "mustBe" | "reject">;
export declare class Traversal {
/**
* #### the path being validated or morphed
*
* ✅ array indices represented as numbers
* ⚠️ mutated during traversal - use `path.slice(0)` to snapshot
* 🔗 use {@link propString} for a stringified version
*/
path: PropertyKey[];
/**
* #### {@link ArkErrors} that will be part of this traversal's finalized result
*
* ✅ will always be an empty array for a valid traversal
*/
errors: ArkErrors;
/**
* #### the original value being traversed
*/
root: unknown;
/**
* #### configuration for this traversal
*
* ✅ options can affect traversal results and error messages
* ✅ defaults < global config < scope config
* ✅ does not include options configured on individual types
*/
config: ResolvedConfig;
queuedMorphs: MorphsAtPath[];
branches: BranchTraversal[];
seen: {
[id in string]?: unknown[];
};
constructor(root: unknown, config: ResolvedConfig);
/**
* #### the data being validated or morphed
*
* ✅ extracted from {@link root} at {@link path}
*/
get data(): unknown;
/**
* #### a string representing {@link path}
*
* ✅ uses `.access` {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation | where allowed by JS}, falling back to `[indexAccess]`
* @example
* const path = ["key1", Symbol("key2"), "key3", 4, "~key5"]
* const propString = 'key1[Symbol(key2)].key3[4]["~key5"]'
*/
get propString(): string;
/**
* #### add an {@link ArkError} and return `false`
*
* ✅ useful for predicates like `.narrow`
*/
reject(input: ArkErrorInput): false;
/**
* #### add an {@link ArkError} from a description and return `false`
*
* ✅ useful for predicates like `.narrow`
* 🔗 equivalent to {@link reject}({ expected })
*/
mustBe(expected: string): false;
/**
* #### add and return an {@link ArkError}
*
* ✅ useful for morphs like `.pipe`
*/
error<input extends ArkErrorInput>(input: input): ArkError<input extends {
code: ArkErrorCode;
} ? input["code"] : "predicate">;
/**
* #### whether {@link currentBranch} (or the traversal root, outside a union) has one or more errors
*/
hasError(): boolean;
get currentBranch(): BranchTraversal | undefined;
queueMorphs(morphs: array<Morph>): void;
finalize(onFail?: ArkErrors.Handler | null): unknown;
get currentErrorCount(): number;
get failFast(): boolean;
pushBranch(): void;
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;
private applyQueuedMorphs;
private applyMorphsAtPath;
}
export declare const traverseKey: <result>(key: PropertyKey, fn: () => result, ctx: InternalTraversal | undefined) => result;
export type TraversalMethodsByKind<input = unknown> = {
Allows: TraverseAllows<input>;
Apply: TraverseApply<input>;
Optimistic: TraverseApply<input>;
};
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;

View File

@@ -0,0 +1,226 @@
import { ReadonlyPath, stringifyPath } from "@ark/util";
import { ArkError, ArkErrors } from "./errors.js";
import { isNode } from "./utils.js";
export class Traversal {
/**
* #### the path being validated or morphed
*
* ✅ array indices represented as numbers
* ⚠️ mutated during traversal - use `path.slice(0)` to snapshot
* 🔗 use {@link propString} for a stringified version
*/
path = [];
/**
* #### {@link ArkErrors} that will be part of this traversal's finalized result
*
* ✅ will always be an empty array for a valid traversal
*/
errors = new ArkErrors(this);
/**
* #### the original value being traversed
*/
root;
/**
* #### configuration for this traversal
*
* ✅ options can affect traversal results and error messages
* ✅ defaults < global config < scope config
* ✅ does not include options configured on individual types
*/
config;
queuedMorphs = [];
branches = [];
seen = {};
constructor(root, config) {
this.root = root;
this.config = config;
}
/**
* #### the data being validated or morphed
*
* ✅ extracted from {@link root} at {@link path}
*/
get data() {
let result = this.root;
for (const segment of this.path)
result = result?.[segment];
return result;
}
/**
* #### a string representing {@link path}
*
* @propString
*/
get propString() {
return stringifyPath(this.path);
}
/**
* #### add an {@link ArkError} and return `false`
*
* ✅ useful for predicates like `.narrow`
*/
reject(input) {
this.error(input);
return false;
}
/**
* #### add an {@link ArkError} from a description and return `false`
*
* ✅ useful for predicates like `.narrow`
* 🔗 equivalent to {@link reject}({ expected })
*/
mustBe(expected) {
this.error(expected);
return false;
}
error(input) {
const errCtx = typeof input === "object" ?
input.code ?
input
: { ...input, code: "predicate" }
: { code: "predicate", expected: input };
return this.errorFromContext(errCtx);
}
/**
* #### whether {@link currentBranch} (or the traversal root, outside a union) has one or more errors
*/
hasError() {
return this.currentErrorCount !== 0;
}
get currentBranch() {
return this.branches[this.branches.length - 1];
}
queueMorphs(morphs) {
const input = {
path: new ReadonlyPath(...this.path),
morphs
};
if (this.currentBranch)
this.currentBranch.queuedMorphs.push(input);
else
this.queuedMorphs.push(input);
}
finalize(onFail) {
if (this.queuedMorphs.length) {
if (typeof this.root === "object" &&
this.root !== null &&
this.config.clone)
this.root = this.config.clone(this.root);
this.applyQueuedMorphs();
}
if (this.hasError())
return onFail ? onFail(this.errors) : this.errors;
return this.root;
}
get currentErrorCount() {
return (this.currentBranch ?
this.currentBranch.error ?
1
: 0
: this.errors.count);
}
get failFast() {
return this.branches.length !== 0;
}
pushBranch() {
this.branches.push({
error: undefined,
queuedMorphs: []
});
}
popBranch() {
return this.branches.pop();
}
/**
* @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() {
return this;
}
errorFromNodeContext(input) {
return this.errorFromContext(input);
}
errorFromContext(errCtx) {
const error = new ArkError(errCtx, this);
if (this.currentBranch)
this.currentBranch.error = error;
else
this.errors.add(error);
return error;
}
applyQueuedMorphs() {
// invoking morphs that are Nodes will reuse this context, potentially
// adding additional morphs, so we have to continue looping until
// queuedMorphs is empty rather than iterating over the list once
while (this.queuedMorphs.length) {
const queuedMorphs = this.queuedMorphs;
this.queuedMorphs = [];
for (const { path, morphs } of queuedMorphs) {
// even if we already have an error, apply morphs that are not at a path
// with errors to capture potential validation errors
if (this.errors.affectsPath(path))
continue;
this.applyMorphsAtPath(path, morphs);
}
}
}
applyMorphsAtPath(path, morphs) {
const key = path[path.length - 1];
let parent;
if (key !== undefined) {
// find the object on which the key to be morphed exists
parent = this.root;
for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++)
parent = parent[path[pathIndex]];
}
for (const morph of morphs) {
// 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);
if (result instanceof ArkError) {
// if an ArkError was returned, ensure it has been added to errors
// (it may have already been added via ctx.error() within the morph)
// Only add if it's not already in the errors collection
if (!this.errors.includes(result))
this.errors.add(result);
// skip any remaining morphs at the current path
break;
}
if (result instanceof ArkErrors) {
// if the morph was a direct reference to another node,
// errors will have been added directly via this piped context
if (!morphIsNode) {
// otherwise, we have to ensure each error has been added
this.errors.merge(result);
}
// skip any remaining morphs at the current path
this.queuedMorphs = [];
break;
}
// if the morph was successful, assign the result to the
// corresponding property, or to root if path is empty
if (parent === undefined)
this.root = result;
else
parent[key] = result;
// if the current morph queued additional morphs,
// applying them before subsequent morphs
this.applyQueuedMorphs();
}
}
}
export const traverseKey = (key, fn,
// ctx will be undefined if this node isn't context-dependent
ctx) => {
if (!ctx)
return fn();
ctx.path.push(key);
const result = fn();
ctx.path.pop();
return result;
};

View File

@@ -0,0 +1,34 @@
import { type array, type mutable, type show, type Thunk } from "@ark/util";
import type { BaseConstraint } from "../constraint.ts";
import type { GenericRoot } from "../generic.ts";
import type { InternalModule } from "../module.ts";
import type { BaseNode } from "../node.ts";
import type { BaseParseContext } from "../parse.ts";
import type { BaseRoot } from "../roots/root.ts";
import type { BaseScope } from "../scope.ts";
import type { ArkError, ArkErrors } from "./errors.ts";
export declare const makeRootAndArrayPropertiesMutable: <o extends object>(o: o) => makeRootAndArrayPropertiesMutable<o>;
export type makeRootAndArrayPropertiesMutable<inner> = {
-readonly [k in keyof inner]: inner[k] extends array | undefined ? mutable<inner[k]> : inner[k];
} & unknown;
export type internalImplementationOf<external, typeOnlyKey extends keyof external = never> = {
[k in Exclude<keyof external, typeOnlyKey>]: external[k] extends ((...args: infer args) => unknown) ? (...args: {
[i in keyof args]: never;
}) => unknown : unknown;
};
export type arkKind = typeof arkKind;
export declare const arkKind: " arkKind";
export interface ArkKinds {
constraint: BaseConstraint;
root: BaseRoot;
scope: BaseScope;
generic: GenericRoot;
module: InternalModule;
error: ArkError;
errors: ArkErrors;
context: BaseParseContext;
}
export type ArkKind = show<keyof ArkKinds>;
export declare const hasArkKind: <kind extends ArkKind>(value: unknown, kind: kind) => value is ArkKinds[kind];
export declare const isNode: (value: unknown) => value is BaseNode;
export type unwrapDefault<thunkableValue> = thunkableValue extends Thunk<infer returnValue> ? returnValue : thunkableValue;

View File

@@ -0,0 +1,8 @@
import { flatMorph, isArray, noSuggest } from "@ark/util";
export const makeRootAndArrayPropertiesMutable = (o) =>
// this cast should not be required, but it seems TS is referencing
// the wrong parameters here?
flatMorph(o, (k, v) => [k, isArray(v) ? [...v] : v]);
export const arkKind = noSuggest("arkKind");
export const hasArkKind = (value, kind) => value?.[arkKind] === kind;
export const isNode = (value) => hasArkKind(value, "root") || hasArkKind(value, "constraint");