- 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
76 lines
2.8 KiB
JavaScript
76 lines
2.8 KiB
JavaScript
import { hasDomain } from "./domain.js";
|
|
import { noSuggest } from "./errors.js";
|
|
import { ancestorsOf } from "./objectKinds.js";
|
|
import { NoopBase } from "./records.js";
|
|
// even though the value we attach will be identical, we use this so classes
|
|
// won't be treated as instanceof a Trait
|
|
const implementedTraits = noSuggest("implementedTraits");
|
|
export const hasTrait = (traitClass) => (o) => {
|
|
if (!hasDomain(o, "object"))
|
|
return false;
|
|
if (implementedTraits in o.constructor &&
|
|
o.constructor[implementedTraits].includes(traitClass))
|
|
return true;
|
|
// emulate standard instanceof behavior
|
|
return ancestorsOf(o).includes(traitClass);
|
|
};
|
|
/** @ts-ignore required to extend NoopBase */
|
|
export class Trait extends NoopBase {
|
|
static get [Symbol.hasInstance]() {
|
|
return hasTrait(this);
|
|
}
|
|
traitsOf() {
|
|
return implementedTraits in this.constructor ?
|
|
this.constructor[implementedTraits]
|
|
: [];
|
|
}
|
|
}
|
|
const collectPrototypeDescriptors = (trait) => {
|
|
let proto = trait.prototype;
|
|
let result = {};
|
|
do {
|
|
// ensure prototypes are sorted from lowest to highest precedence
|
|
result = Object.assign(Object.getOwnPropertyDescriptors(proto), result);
|
|
proto = Object.getPrototypeOf(proto);
|
|
} while (proto !== Object.prototype && proto !== null);
|
|
return result;
|
|
};
|
|
export const compose = ((...traits) => {
|
|
const base = function (...args) {
|
|
for (const trait of traits) {
|
|
const instance = Reflect.construct(trait, args, this.constructor);
|
|
Object.assign(this, instance);
|
|
}
|
|
};
|
|
const flatImplementedTraits = [];
|
|
for (const trait of traits) {
|
|
// copy static properties
|
|
Object.assign(base, trait);
|
|
// flatten and copy prototype
|
|
Object.defineProperties(base.prototype, collectPrototypeDescriptors(trait));
|
|
if (implementedTraits in trait) {
|
|
// add any ancestor traits from which the current trait was composed
|
|
for (const innerTrait of trait[implementedTraits]) {
|
|
if (!flatImplementedTraits.includes(innerTrait))
|
|
flatImplementedTraits.push(innerTrait);
|
|
}
|
|
}
|
|
if (!flatImplementedTraits.includes(trait))
|
|
flatImplementedTraits.push(trait);
|
|
}
|
|
Object.defineProperty(base, implementedTraits, {
|
|
value: flatImplementedTraits,
|
|
enumerable: false
|
|
});
|
|
return base;
|
|
});
|
|
export const implement = (...args) => {
|
|
if (args[args.length - 1] instanceof Trait)
|
|
return compose(...args);
|
|
const implementation = args[args.length - 1];
|
|
const base = compose(...args.slice(0, -1));
|
|
// copy implementation last since it overrides traits
|
|
Object.defineProperties(base.prototype, Object.getOwnPropertyDescriptors(implementation));
|
|
return base;
|
|
};
|