- 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
137 lines
5.0 KiB
JavaScript
137 lines
5.0 KiB
JavaScript
import { append, appendUnique, capitalize, isArray, throwInternalError, throwParseError } from "@ark/util";
|
|
import { BaseNode } from "./node.js";
|
|
import { Disjoint } from "./shared/disjoint.js";
|
|
import { compileObjectLiteral, constraintKeys } from "./shared/implement.js";
|
|
import { intersectNodesRoot, intersectOrPipeNodes } from "./shared/intersections.js";
|
|
import { $ark } from "./shared/registry.js";
|
|
import { arkKind } from "./shared/utils.js";
|
|
export class BaseConstraint extends BaseNode {
|
|
constructor(attachments, $) {
|
|
super(attachments, $);
|
|
// define as a getter to avoid it being enumerable/spreadable
|
|
Object.defineProperty(this, arkKind, {
|
|
value: "constraint",
|
|
enumerable: false
|
|
});
|
|
}
|
|
impliedSiblings;
|
|
intersect(r) {
|
|
return intersectNodesRoot(this, r, this.$);
|
|
}
|
|
}
|
|
export class InternalPrimitiveConstraint extends BaseConstraint {
|
|
traverseApply = (data, ctx) => {
|
|
if (!this.traverseAllows(data, ctx))
|
|
ctx.errorFromNodeContext(this.errorContext);
|
|
};
|
|
compile(js) {
|
|
if (js.traversalKind === "Allows")
|
|
js.return(this.compiledCondition);
|
|
else {
|
|
js.if(this.compiledNegation, () => js.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`));
|
|
}
|
|
}
|
|
get errorContext() {
|
|
return {
|
|
code: this.kind,
|
|
description: this.description,
|
|
meta: this.meta,
|
|
...this.inner
|
|
};
|
|
}
|
|
get compiledErrorContext() {
|
|
return compileObjectLiteral(this.errorContext);
|
|
}
|
|
}
|
|
export const constraintKeyParser = (kind) => (schema, ctx) => {
|
|
if (isArray(schema)) {
|
|
if (schema.length === 0) {
|
|
// Omit empty lists as input
|
|
return;
|
|
}
|
|
const nodes = schema.map(schema => ctx.$.node(kind, schema));
|
|
// predicate order must be preserved to ensure inputs are narrowed
|
|
// and checked in the correct order
|
|
if (kind === "predicate")
|
|
return nodes;
|
|
return nodes.sort((l, r) => (l.hash < r.hash ? -1 : 1));
|
|
}
|
|
const child = ctx.$.node(kind, schema);
|
|
return (child.hasOpenIntersection() ? [child] : child);
|
|
};
|
|
export const intersectConstraints = (s) => {
|
|
const head = s.r.shift();
|
|
if (!head) {
|
|
let result = s.l.length === 0 && s.kind === "structure" ?
|
|
$ark.intrinsic.unknown.internal
|
|
: s.ctx.$.node(s.kind, Object.assign(s.baseInner, unflattenConstraints(s.l)), { prereduced: true });
|
|
for (const root of s.roots) {
|
|
if (result instanceof Disjoint)
|
|
return result;
|
|
result = intersectOrPipeNodes(root, result, s.ctx);
|
|
}
|
|
return result;
|
|
}
|
|
let matched = false;
|
|
for (let i = 0; i < s.l.length; i++) {
|
|
const result = intersectOrPipeNodes(s.l[i], head, s.ctx);
|
|
if (result === null)
|
|
continue;
|
|
if (result instanceof Disjoint)
|
|
return result;
|
|
if (result.isRoot()) {
|
|
s.roots.push(result);
|
|
s.l.splice(i);
|
|
return intersectConstraints(s);
|
|
}
|
|
if (!matched) {
|
|
s.l[i] = result;
|
|
matched = true;
|
|
}
|
|
else if (!s.l.includes(result)) {
|
|
return throwInternalError(`Unexpectedly encountered multiple distinct intersection results for refinement ${head}`);
|
|
}
|
|
}
|
|
if (!matched)
|
|
s.l.push(head);
|
|
if (s.kind === "intersection") {
|
|
if (head.impliedSiblings)
|
|
for (const node of head.impliedSiblings)
|
|
appendUnique(s.r, node);
|
|
}
|
|
return intersectConstraints(s);
|
|
};
|
|
export const flattenConstraints = (inner) => {
|
|
const result = Object.entries(inner)
|
|
.flatMap(([k, v]) => k in constraintKeys ? v : [])
|
|
.sort((l, r) => l.precedence < r.precedence ? -1
|
|
: l.precedence > r.precedence ? 1
|
|
// preserve order for predicates
|
|
: l.kind === "predicate" && r.kind === "predicate" ? 0
|
|
: l.hash < r.hash ? -1
|
|
: 1);
|
|
return result;
|
|
};
|
|
export const unflattenConstraints = (constraints) => {
|
|
const inner = {};
|
|
for (const constraint of constraints) {
|
|
if (constraint.hasOpenIntersection()) {
|
|
inner[constraint.kind] = append(inner[constraint.kind], constraint);
|
|
}
|
|
else {
|
|
if (inner[constraint.kind]) {
|
|
return throwInternalError(`Unexpected intersection of closed refinements of kind ${constraint.kind}`);
|
|
}
|
|
inner[constraint.kind] = constraint;
|
|
}
|
|
}
|
|
return inner;
|
|
};
|
|
export const throwInvalidOperandError = (...args) => throwParseError(writeInvalidOperandMessage(...args));
|
|
export const writeInvalidOperandMessage = (kind, expected, actual) => {
|
|
const actualDescription = actual.hasKind("morph") ? "a morph"
|
|
: actual.isUnknown() ? "unknown"
|
|
: actual.exclude(expected).defaultShortDescription;
|
|
return `${capitalize(kind)} operand must be ${expected.description} (was ${actualDescription})`;
|
|
};
|