feat: Phase 1 — extract the client (web SPA + desktop) to dtoro/oikos-web
Problem: the hexagonal refactor churns the backend tree for nine more phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper, compose/web image) must move to its own repo first so doc/layout rewrites land once on a backend-only tree. Change: - New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/ (updateURL repointed to oikos-web releases), compose/, own CI (web + desktop jobs), own deploy script (CI-green gate, TOCTOU guard, version-tagged images, prune-to-3), own webhook receiver on :9798 + launchd unit, own compose project publishing the same 8091:80. - Cutover executed on mac-mini in order: oikos stack's web service stopped+removed, oikos-web project brought up on 8091; outer Caddy untouched (targets the published port) — serving + Authentik flow + /wails 404 quirk verified post-cutover. - Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install targets, the compose web service, oikos-web from deploy.sh's fallback prune list; wails + go-keyring dropped from go.mod, vendor synced. - README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now point at the new repo; mbse + mascot design docs carry a path note. Risk: production SPA serving depends on the new pipeline now; rollback is versioned-image re-up of the old web service from a pre-split checkout (port 8091). Desktop builds installed before the split still check dtoro/oikos releases — one manual reinstall, noted in the oikos-web release notes. Verification: go vet, make test (race), make generate-check, golangci (no new findings; baseline down 400→365); post-cutover curls — localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
This commit is contained in:
139
web/node_modules/runed/dist/utilities/resource/resource.svelte.js
generated
vendored
Normal file
139
web/node_modules/runed/dist/utilities/resource/resource.svelte.js
generated
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
import { watch } from "../watch/index.js";
|
||||
// Helper functions for debounce and throttle
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function debounce(fn, delay) {
|
||||
let timeoutId;
|
||||
let lastResolve = null;
|
||||
return (...args) => {
|
||||
return new Promise((resolve) => {
|
||||
if (lastResolve) {
|
||||
lastResolve(undefined);
|
||||
}
|
||||
lastResolve = resolve;
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(async () => {
|
||||
const result = await fn(...args);
|
||||
if (lastResolve) {
|
||||
lastResolve(result);
|
||||
lastResolve = null;
|
||||
}
|
||||
}, delay);
|
||||
});
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function throttle(fn, delay) {
|
||||
let lastRun = 0;
|
||||
let lastPromise = null;
|
||||
return (...args) => {
|
||||
const now = Date.now();
|
||||
if (lastRun && now - lastRun < delay) {
|
||||
return lastPromise ?? Promise.resolve(undefined);
|
||||
}
|
||||
lastRun = now;
|
||||
lastPromise = fn(...args);
|
||||
return lastPromise;
|
||||
};
|
||||
}
|
||||
function runResource(source, fetcher, options = {}, effectFn) {
|
||||
const { lazy = false, once = false, initialValue, debounce: debounceTime, throttle: throttleTime, } = options;
|
||||
// Create state
|
||||
let current = $state(initialValue);
|
||||
let loading = $state(false);
|
||||
let error = $state(undefined);
|
||||
let cleanupFns = $state([]);
|
||||
// Helper function to run cleanup functions
|
||||
const runCleanup = () => {
|
||||
cleanupFns.forEach((fn) => fn());
|
||||
cleanupFns = [];
|
||||
};
|
||||
// Helper function to register cleanup
|
||||
const onCleanup = (fn) => {
|
||||
cleanupFns = [...cleanupFns, fn];
|
||||
};
|
||||
// Create the base fetcher function
|
||||
const baseFetcher = async (value, previousValue, refetching = false) => {
|
||||
try {
|
||||
loading = true;
|
||||
error = undefined;
|
||||
runCleanup();
|
||||
// Create new AbortController for this fetch
|
||||
const controller = new AbortController();
|
||||
onCleanup(() => controller.abort());
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const result = await fetcher(value, previousValue, {
|
||||
data: current,
|
||||
refetching,
|
||||
onCleanup,
|
||||
signal: controller.signal,
|
||||
});
|
||||
current = result;
|
||||
return result;
|
||||
}
|
||||
catch (e) {
|
||||
if (!(e instanceof DOMException && e.name === "AbortError")) {
|
||||
error = e;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
finally {
|
||||
loading = false;
|
||||
}
|
||||
};
|
||||
// Apply debounce or throttle if specified
|
||||
const runFetcher = debounceTime
|
||||
? debounce(baseFetcher, debounceTime)
|
||||
: throttleTime
|
||||
? throttle(baseFetcher, throttleTime)
|
||||
: baseFetcher;
|
||||
// Setup effect
|
||||
const sources = Array.isArray(source) ? source : [source];
|
||||
let prevValues;
|
||||
effectFn((values, previousValues) => {
|
||||
// Skip if once and already ran
|
||||
if (once && prevValues) {
|
||||
return;
|
||||
}
|
||||
prevValues = values;
|
||||
runFetcher(Array.isArray(source) ? values : values[0], Array.isArray(source) ? previousValues : previousValues?.[0]);
|
||||
}, { lazy });
|
||||
return {
|
||||
get current() {
|
||||
return current;
|
||||
},
|
||||
get loading() {
|
||||
return loading;
|
||||
},
|
||||
get error() {
|
||||
return error;
|
||||
},
|
||||
mutate: (value) => {
|
||||
current = value;
|
||||
},
|
||||
refetch: (info) => {
|
||||
const values = sources.map((s) => s());
|
||||
return runFetcher(Array.isArray(source) ? values : values[0], Array.isArray(source) ? values : values[0], info ?? true);
|
||||
},
|
||||
};
|
||||
}
|
||||
// Implementation
|
||||
export function resource(source, fetcher, options) {
|
||||
return runResource(source, fetcher, options, (fn, options) => {
|
||||
const sources = Array.isArray(source) ? source : [source];
|
||||
const getters = () => sources.map((s) => s());
|
||||
watch(getters, (values, previousValues) => {
|
||||
fn(values, previousValues ?? []);
|
||||
}, options);
|
||||
});
|
||||
}
|
||||
// Implementation
|
||||
export function resourcePre(source, fetcher, options) {
|
||||
return runResource(source, fetcher, options, (fn, options) => {
|
||||
const sources = Array.isArray(source) ? source : [source];
|
||||
const getter = () => sources.map((s) => s());
|
||||
watch.pre(getter, (values, previousValues) => {
|
||||
fn(values, previousValues ?? []);
|
||||
}, options);
|
||||
});
|
||||
}
|
||||
resource.pre = resourcePre;
|
||||
Reference in New Issue
Block a user