- 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
133 lines
5.0 KiB
JavaScript
133 lines
5.0 KiB
JavaScript
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
|
|
});
|
|
};
|