- 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
160 lines
6.2 KiB
JavaScript
160 lines
6.2 KiB
JavaScript
import { arrayEquals, liftArray, throwParseError } from "@ark/util";
|
|
import { Disjoint } from "../shared/disjoint.js";
|
|
import { implementNode } from "../shared/implement.js";
|
|
import { intersectOrPipeNodes } from "../shared/intersections.js";
|
|
import { $ark, registeredReference } from "../shared/registry.js";
|
|
import { hasArkKind } from "../shared/utils.js";
|
|
import { BaseRoot } from "./root.js";
|
|
import { defineRightwardIntersections } from "./utils.js";
|
|
const implementation = implementNode({
|
|
kind: "morph",
|
|
hasAssociatedError: false,
|
|
keys: {
|
|
in: {
|
|
child: true,
|
|
parse: (schema, ctx) => ctx.$.parseSchema(schema)
|
|
},
|
|
morphs: {
|
|
parse: liftArray,
|
|
serialize: morphs => morphs.map(m => hasArkKind(m, "root") ? m.json : registeredReference(m))
|
|
},
|
|
declaredIn: {
|
|
child: false,
|
|
serialize: node => node.json
|
|
},
|
|
declaredOut: {
|
|
child: false,
|
|
serialize: node => node.json
|
|
}
|
|
},
|
|
normalize: schema => schema,
|
|
defaults: {
|
|
description: node => `a morph from ${node.rawIn.description} to ${node.rawOut?.description ?? "unknown"}`
|
|
},
|
|
intersections: {
|
|
morph: (l, r, ctx) => {
|
|
if (!l.hasEqualMorphs(r)) {
|
|
return throwParseError(writeMorphIntersectionMessage(l.expression, r.expression));
|
|
}
|
|
const inTersection = intersectOrPipeNodes(l.rawIn, r.rawIn, ctx);
|
|
if (inTersection instanceof Disjoint)
|
|
return inTersection;
|
|
const baseInner = {
|
|
morphs: l.morphs
|
|
};
|
|
if (l.declaredIn || r.declaredIn) {
|
|
const declaredIn = intersectOrPipeNodes(l.rawIn, r.rawIn, ctx);
|
|
// we can't treat this as a normal Disjoint since it's just declared
|
|
// it should only happen if someone's essentially trying to create a broken type
|
|
if (declaredIn instanceof Disjoint)
|
|
return declaredIn.throw();
|
|
else
|
|
baseInner.declaredIn = declaredIn;
|
|
}
|
|
if (l.declaredOut || r.declaredOut) {
|
|
const declaredOut = intersectOrPipeNodes(l.rawOut, r.rawOut, ctx);
|
|
if (declaredOut instanceof Disjoint)
|
|
return declaredOut.throw();
|
|
else
|
|
baseInner.declaredOut = declaredOut;
|
|
}
|
|
// in case from is a union, we need to distribute the branches
|
|
// to can be a union as any schema is allowed
|
|
return inTersection.distribute(inBranch => ctx.$.node("morph", {
|
|
...baseInner,
|
|
in: inBranch
|
|
}), ctx.$.parseSchema);
|
|
},
|
|
...defineRightwardIntersections("morph", (l, r, ctx) => {
|
|
const inTersection = l.inner.in ? intersectOrPipeNodes(l.inner.in, r, ctx) : r;
|
|
return (inTersection instanceof Disjoint ? inTersection
|
|
: inTersection.equals(l.inner.in) ? l
|
|
: ctx.$.node("morph", {
|
|
...l.inner,
|
|
in: inTersection
|
|
}));
|
|
})
|
|
}
|
|
});
|
|
export class MorphNode extends BaseRoot {
|
|
serializedMorphs = this.morphs.map(registeredReference);
|
|
compiledMorphs = `[${this.serializedMorphs}]`;
|
|
lastMorph = this.inner.morphs[this.inner.morphs.length - 1];
|
|
lastMorphIfNode = hasArkKind(this.lastMorph, "root") ? this.lastMorph : undefined;
|
|
introspectableIn = this.inner.in;
|
|
introspectableOut = this.lastMorphIfNode ?
|
|
Object.assign(this.referencesById, this.lastMorphIfNode.referencesById) &&
|
|
this.lastMorphIfNode.rawOut
|
|
: undefined;
|
|
get shallowMorphs() {
|
|
// if the morph input is a union, it should not contain any other shallow morphs
|
|
return Array.isArray(this.inner.in?.shallowMorphs) ?
|
|
[...this.inner.in.shallowMorphs, ...this.morphs]
|
|
: this.morphs;
|
|
}
|
|
get rawIn() {
|
|
return (this.declaredIn ?? this.inner.in?.rawIn ?? $ark.intrinsic.unknown.internal);
|
|
}
|
|
get rawOut() {
|
|
return (this.declaredOut ??
|
|
this.introspectableOut ??
|
|
$ark.intrinsic.unknown.internal);
|
|
}
|
|
declareIn(declaredIn) {
|
|
return this.$.node("morph", {
|
|
...this.inner,
|
|
declaredIn
|
|
});
|
|
}
|
|
declareOut(declaredOut) {
|
|
return this.$.node("morph", {
|
|
...this.inner,
|
|
declaredOut
|
|
});
|
|
}
|
|
expression = `(In: ${this.rawIn.expression}) => ${this.lastMorphIfNode ? "To" : "Out"}<${this.rawOut.expression}>`;
|
|
get defaultShortDescription() {
|
|
return this.rawIn.meta.description ?? this.rawIn.defaultShortDescription;
|
|
}
|
|
innerToJsonSchema(ctx) {
|
|
return ctx.fallback.morph({
|
|
code: "morph",
|
|
base: this.rawIn.toJsonSchemaRecurse(ctx),
|
|
out: this.introspectableOut?.toJsonSchemaRecurse(ctx) ?? null
|
|
});
|
|
}
|
|
compile(js) {
|
|
if (js.traversalKind === "Allows") {
|
|
if (!this.introspectableIn)
|
|
return;
|
|
js.return(js.invoke(this.introspectableIn));
|
|
return;
|
|
}
|
|
if (this.introspectableIn)
|
|
js.line(js.invoke(this.introspectableIn));
|
|
js.line(`ctx.queueMorphs(${this.compiledMorphs})`);
|
|
}
|
|
traverseAllows = (data, ctx) => !this.introspectableIn || this.introspectableIn.traverseAllows(data, ctx);
|
|
traverseApply = (data, ctx) => {
|
|
if (this.introspectableIn)
|
|
this.introspectableIn.traverseApply(data, ctx);
|
|
ctx.queueMorphs(this.morphs);
|
|
};
|
|
/** Check if the morphs of r are equal to those of this node */
|
|
hasEqualMorphs(r) {
|
|
return arrayEquals(this.morphs, r.morphs, {
|
|
isEqual: (lMorph, rMorph) => lMorph === rMorph ||
|
|
(hasArkKind(lMorph, "root") &&
|
|
hasArkKind(rMorph, "root") &&
|
|
lMorph.equals(rMorph))
|
|
});
|
|
}
|
|
}
|
|
export const Morph = {
|
|
implementation,
|
|
Node: MorphNode
|
|
};
|
|
export const writeMorphIntersectionMessage = (lDescription, rDescription) => `The intersection of distinct morphs at a single path is indeterminate:
|
|
Left: ${lDescription}
|
|
Right: ${rDescription}`;
|