Replace project with current server release and Windows setup

Import the 2026-10-09 source release, Fastify/SQLite backend, restored source
assets, and analytics-enabled server build. Replace files from the previous
release while retaining the existing repository history.

Make builds portable across Windows and Linux, configure local startup,
normalize Windows static asset paths, and fix test fixture cleanup and file
URL handling. Exclude local environment settings, databases, dependencies,
validation output, and TypeScript build cache from version control.

Validation: npm ci, static and server builds, 106 arrival tests, and actual
local HTTP checks passed. The full suite passed 467 of 470 tests; three tests
still require QA fixtures omitted from the supplied release, documented in
README-서버실행.md.
This commit is contained in:
2026-10-09 16:43:35 +09:00
parent 71c8d3b7ff
commit e358e24436
642 changed files with 35202 additions and 17642 deletions
+397
View File
@@ -0,0 +1,397 @@
import assert from 'node:assert/strict';
import { readFile, mkdtemp, writeFile, rm } from 'node:fs/promises';
import { test } from 'node:test';
import vm from 'node:vm';
import { pathToFileURL } from 'node:url';
import ts from 'typescript';
const gpu = { device: 'webgpu', dtype: 'fp32' };
const wasm = { device: 'wasm', dtype: 'q8' };
async function providerHarness(capture) {
const sourceUrl = new URL('../src/depth/AiDepthProvider.ts', import.meta.url);
const directory = await mkdtemp(new URL('../.ai-depth-provider-test-', import.meta.url));
const source = (await readFile(sourceUrl, 'utf8')).replace("'./DepthProcessor'", JSON.stringify(new URL('../src/depth/DepthProcessor.ts', import.meta.url).href));
const { outputText } = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.ESNext } });
await writeFile(`${directory}/provider.mjs`, outputText);
const originalWorker = globalThis.Worker;
const originalCapture = globalThis.createImageBitmap;
const workers = [];
const bitmaps = [];
globalThis.Worker = class {
constructor() { this.messages = []; this.terminated = false; workers.push(this); }
postMessage(message) { this.messages.push(message); }
terminate() { this.terminated = true; }
};
globalThis.createImageBitmap = capture ?? (async () => { const bitmap = { width: 10, height: 10, closed: false, close() { this.closed = true; } }; bitmaps.push(bitmap); return bitmap; });
const { AiDepthProvider } = await import(pathToFileURL(`${directory}/provider.mjs`).href);
const statuses = [];
const provider = new AiDepthProvider((status) => statuses.push(status));
return { provider, workers, statuses, bitmaps, async cleanup() { provider.cancel(); globalThis.Worker = originalWorker; globalThis.createImageBitmap = originalCapture; await rm(directory, { recursive: true, force: true }); } };
}
const photo = { naturalWidth: 100, naturalHeight: 50 };
const tick = async () => { await Promise.resolve(); await Promise.resolve(); };
function replyWithResult(worker, run) {
const id = worker.messages.at(-1).id;
worker.onmessage({ data: { type: 'result', id, width: 1, height: 1, values: Uint8Array.of(255).buffer, normalizedValues: Float32Array.of(1).buffer, run } });
}
test('provider sends a fixed-model request and preserves measured execution metadata', async () => {
const h = await providerHarness();
try {
const pending = h.provider.getDepthMap(photo);
await tick();
const worker = h.workers[0];
const requestKeys = Object.keys(worker.messages[0]).sort();
replyWithResult(worker, { model: 'small', ...gpu, preparationMs: 5, inferenceMs: 7, totalMs: 12 });
const result = await pending;
assert.deepEqual(requestKeys, ['id', 'image', 'type']);
assert.equal(result.userData.aiDepthRun.model, 'small');
assert.equal(result.userData.aiDepthRun.device, 'webgpu');
assert.equal(result.userData.aiDepthRun.dtype, 'fp32');
assert.equal(result.userData.aiDepthRun.preparationMs, 5);
assert.equal(result.userData.aiDepthRun.inferenceMs, 7);
assert.ok(result.userData.aiDepthRun.totalMs >= 0);
assert.equal(result.userData.normalizedDepth[0], 1);
assert.equal(worker.terminated, true, 'finished inference must release its runtime before scene rendering resumes');
result.dispose();
} finally { await h.cleanup(); }
});
test('provider cancellation is an AbortError and late captures are closed without spawning workers', async () => {
let resolveCapture;
const bitmap = { width: 10, height: 5, closed: false, close() { this.closed = true; } };
const h = await providerHarness(() => new Promise((resolve) => { resolveCapture = resolve; }));
try {
const pending = h.provider.getDepthMap(photo);
const rejected = assert.rejects(pending, { name: 'AbortError' });
h.provider.cancel();
resolveCapture(bitmap);
await rejected;
await tick();
assert.equal(bitmap.closed, true);
assert.equal(h.workers.length, 0);
} finally { await h.cleanup(); }
});
test('orphaned worker errors and messages cannot reject or settle a replacement request', async () => {
const h = await providerHarness();
try {
const first = h.provider.getDepthMap(photo);
const firstRejected = assert.rejects(first);
await tick();
const oldWorker = h.workers[0];
h.provider.cancel(); await firstRejected;
const second = h.provider.getDepthMap(photo);
// Attach immediately so a failing implementation does not create an unhandled rejection.
const settled = second.then((value) => ({ value }), (error) => ({ error }));
await tick();
const worker = h.workers[1];
oldWorker.onerror({ message: 'late old worker failure' });
oldWorker.onmessageerror({});
oldWorker.onmessage({ data: { type: 'status', id: worker.messages[0].id, message: 'orphaned status' } });
replyWithResult(worker, { model: 'small', ...wasm, preparationMs: 1, inferenceMs: 2, totalMs: 3 });
const result = await settled;
assert.equal(result.error, undefined, 'orphaned callbacks must be ignored');
assert.equal(h.statuses.includes('orphaned status'), false);
assert.equal(worker.terminated, true, 'the valid completed worker is released');
result.value.dispose();
} finally { await h.cleanup(); }
});
test('completed inference releases only its own worker and late replies cannot terminate a newer request', async () => {
const h = await providerHarness();
try {
const first = h.provider.getDepthMap(photo); await tick();
const old = h.workers[0];
replyWithResult(old, { model: 'small', ...gpu, preparationMs: 1, inferenceMs: 2, totalMs: 3 });
const firstTexture = await first;
assert.equal(old.terminated, true);
const next = h.provider.getDepthMap(photo); await tick();
const current = h.workers[1];
replyWithResult(old, { model: 'small', ...gpu, preparationMs: 1, inferenceMs: 2, totalMs: 3 });
old.onerror({ message: 'late old failure' });
assert.equal(current.terminated, false);
replyWithResult(current, { model: 'small', ...wasm, preparationMs: 1, inferenceMs: 2, totalMs: 3 });
const texture = await next;
assert.equal(current.terminated, true); assert.equal(texture.userData.aiDepthRun.device, 'wasm');
firstTexture.dispose(); texture.dispose();
} finally { await h.cleanup(); }
});
async function workerHarness({ hasGpu = true, adapter = {}, adapterError = false, missingAdapterMethod = false, failLoad = () => false, failInference = () => false, beforeLoad = async () => {} } = {}) {
const source = await readFile(new URL('../src/depth/aiDepth.worker.ts', import.meta.url), 'utf8');
const { outputText } = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS } });
const constants = await import(new URL('../src/constants/depth.ts', import.meta.url));
let now = 0;
let listener;
let liveSessions = 0;
let adapterProbes = 0;
let maximumSessions = 0;
const loads = [];
const sessions = [];
const messages = [];
const terminals = new Map();
const pipeline = async (_task, model, options) => {
loads.push({ model, device: options.device, dtype: options.dtype });
now += 5;
options.progress_callback({ status: 'initiate' });
options.progress_callback({ status: 'progress', progress: 50 });
await beforeLoad(options);
if (failLoad(model, options)) throw new Error('load failure');
liveSessions++;
maximumSessions = Math.max(maximumSessions, liveSessions);
const session = async () => {
now += 7;
if (failInference(model, options)) throw new Error('inference failure');
const output = { dims: [1, 1, 2], data: Float32Array.of(1, 3), dispose() { session.tensorDisposals++; } };
return { predicted_depth: output };
};
session.processor = { image_processor: { size: { width: 518, height: 518 } } };
session.disposals = 0;
session.tensorDisposals = 0;
session.dispose = async () => { now += 2; session.disposals++; liveSessions--; };
sessions.push(session);
return session;
};
vm.runInNewContext(outputText, {
exports: {}, Error,
require(name) {
if (name === '@huggingface/transformers') return { pipeline };
if (name === '../constants/depth') return constants;
throw new Error(`unexpected worker dependency ${name}`);
},
performance: { now: () => now },
navigator: hasGpu ? { gpu: missingAdapterMethod ? {} : { async requestAdapter() { adapterProbes++; if (adapterError) throw new Error('adapter unavailable'); return adapter; } } } : {},
OffscreenCanvas: class { getContext() { return { drawImage() {} }; } },
self: {
addEventListener(_type, callback) { listener = callback; },
postMessage(message) { messages.push(message); if (message.type !== 'status') terminals.get(message.id)?.(message); },
},
});
let requestId = 0;
return {
loads, sessions, messages,
get maximumSessions() { return maximumSessions; },
get adapterProbes() { return adapterProbes; },
request(legacyFields = {}) {
const id = ++requestId;
const bitmap = { width: 10, height: 5, closed: 0, close() { this.closed++; } };
return new Promise((resolve) => {
terminals.set(id, (message) => { terminals.delete(id); resolve({ message, bitmap }); });
listener({ data: { type: 'infer', id, image: bitmap, ...legacyFields } });
});
},
};
}
test('worker uses Small 770 with WASM/q8 when WebGPU is absent', async () => {
const h = await workerHarness({ hasGpu: false });
const { message, bitmap } = await h.request();
assert.equal(message.type, 'result');
assert.deepEqual(h.loads, [{ model: 'onnx-community/depth-anything-v2-small-ONNX', ...wasm }]);
assert.deepEqual({ ...message.run }, { model: 'small', ...wasm, preparationMs: 5, inferenceMs: 7, totalMs: 12 });
assert.deepEqual({ ...h.sessions[0].processor.image_processor.size }, { width: 770, height: 770 });
assert.deepEqual([...new Uint8Array(message.values)], [0, 255]);
assert.deepEqual([...new Float32Array(message.normalizedValues)], [0, 1]);
assert.equal(h.sessions[0].tensorDisposals, 1);
assert.equal(bitmap.closed, 1);
});
test('a stale model-selection field cannot load another model or override execution', async () => {
const h = await workerHarness();
const { message } = await h.request({ model: 'base', preset: wasm });
assert.equal(message.type, 'result');
assert.deepEqual(h.loads, [{ model: 'onnx-community/depth-anything-v2-small-ONNX', ...gpu }]);
assert.equal(message.run.model, 'small');
});
test('worker reuses the fixed Small session on repeated requests', async () => {
const h = await workerHarness();
await h.request();
const reused = await h.request();
assert.equal(h.loads.length, 1);
assert.equal(h.adapterProbes, 1, 'cached session does not repeatedly probe GPU');
assert.equal(h.maximumSessions, 1);
assert.equal(h.sessions[0].disposals, 0);
assert.equal(reused.message.run.preparationMs, 0);
assert.equal(reused.message.run.inferenceMs, 7);
});
test('GPU inference failure disposes and requests an isolated WASM retry', async () => {
const h = await workerHarness({ failInference: (_model, preset) => preset.device === 'webgpu' });
const { message, bitmap } = await h.request();
assert.equal(message.type, 'fallback');
assert.deepEqual(h.loads, [{ model: 'onnx-community/depth-anything-v2-small-ONNX', ...gpu }]);
assert.equal(h.sessions[0].disposals, 1);
assert.equal(message.preparationMs, 7); assert.equal(message.inferenceMs, 7);
assert.equal(bitmap.closed, 1);
});
test('GPU loading failure requests fresh worker and retains failed preparation time', async () => {
const h = await workerHarness({ failLoad: (_model, preset) => preset.device === 'webgpu' });
const { message } = await h.request();
assert.equal(message.type, 'fallback');
assert.equal(message.preparationMs, 5); assert.equal(message.inferenceMs, 0);
assert.equal(h.loads.length, 1, 'must not retry inside a poisoned worker');
});
test('a present GPU API with null or rejected adapter selects WASM directly', async () => {
for (const options of [{ adapter: null }, { adapterError: true }, { missingAdapterMethod: true }]) {
const h = await workerHarness(options); const { message } = await h.request();
assert.equal(message.type, 'result'); assert.equal(message.run.device, 'wasm');
assert.deepEqual(h.loads, [{ model: 'onnx-community/depth-anything-v2-small-ONNX', ...wasm }]);
}
});
test('forced fallback worker bypasses GPU and retains Small 770', async () => {
const h = await workerHarness(); const { message } = await h.request({ forceWasm: true });
assert.equal(message.type, 'result'); assert.equal(message.run.device, 'wasm');
assert.deepEqual(h.loads, [{ model: 'onnx-community/depth-anything-v2-small-ONNX', ...wasm }]);
assert.equal(h.sessions[0].processor.image_processor.size.width, 770);
assert.equal(h.adapterProbes, 0, 'forced CPU retry cannot touch GPU again');
});
test('terminal WASM failure closes the input and returns an error without more retries', async () => {
const h = await workerHarness({ hasGpu: false, failInference: () => true });
const { message, bitmap } = await h.request();
assert.equal(message.type, 'error');
assert.equal(message.message, 'inference failure');
assert.equal(h.loads.length, 1);
assert.equal(bitmap.closed, 1);
});
test('worker labels file progress without implying a cold download', async () => {
const h = await workerHarness();
await h.request();
const statuses = h.messages.filter(({ type }) => type === 'status').map(({ message }) => message);
assert.ok(statuses.includes('출발 준비 중 · 50%'));
assert.ok(statuses.includes('내비게이션으로 길을 찾는 중…'));
assert.ok(statuses.every((message) => !/AI|깊이|모델|WebGPU|CPU|계산/i.test(message)));
assert.ok(statuses.every((message) => !/다운로드|download/i.test(message)));
});
test('provider retries once in a fresh worker and ignores orphan messages', async () => {
const h = await providerHarness();
try {
const pending = h.provider.getDepthMap(photo); const settled = pending.then(value => ({ value }), error => ({ error }));
await tick(); const old = h.workers[0], id = old.messages[0].id;
old.onmessage({ data: { type: 'fallback', id, preparationMs: 5, inferenceMs: 7 } });
await tick();
assert.equal(h.statuses.at(-1), '다른 길을 찾고 있어요. 조금만 더 기다려 주세요…');
assert.equal(h.workers.length, 2, 'fallback requires a new JS realm');
const fallback = h.workers[1]; assert.equal(old.terminated, true); assert.equal(fallback.messages[0].forceWasm, true);
old.onmessage({ data: { type: 'error', id, message: 'late GPU failure' } });
old.onerror({ message: 'late crash' });
replyWithResult(fallback, { model: 'small', ...wasm, preparationMs: 10, inferenceMs: 20, totalMs: 30 });
const result = await settled; assert.equal(result.error, undefined);
assert.equal(result.value.userData.aiDepthRun.preparationMs, 15); assert.equal(result.value.userData.aiDepthRun.inferenceMs, 27);
assert.equal(h.workers.length, 2); result.value.dispose();
} finally { await h.cleanup(); }
});
test('fallback cannot loop and terminal failures discard the poisoned runtime', async () => {
const h = await providerHarness();
try {
const pending = h.provider.getDepthMap(photo); const settled = pending.then(value => ({ value }), error => ({ error }));
await tick(); const old = h.workers[0], id = old.messages[0].id;
old.onmessage({ data: { type: 'fallback', id, preparationMs: 5, inferenceMs: 0 } }); await tick();
assert.equal(h.workers.length, 2);
const fallback = h.workers[1]; fallback.onmessage({ data: { type: 'fallback', id, preparationMs: 5, inferenceMs: 0 } });
const result = await settled; assert.ok(result.error); assert.equal(h.workers.length, 2); assert.equal(fallback.terminated, true);
} finally { await h.cleanup(); }
});
test('photo replacement during fallback capture closes stale bitmap without replacing new worker', async () => {
let captures = 0, release;
const h = await providerHarness(async () => {
captures++;
if (captures === 2) return new Promise(resolve => { release = resolve; });
return { width: 10, height: 5, close() {} };
});
try {
const pending = h.provider.getDepthMap(photo); const rejected = assert.rejects(pending, { name: 'AbortError' });
await tick(); const old = h.workers[0], id = old.messages[0].id;
old.onmessage({ data: { type: 'fallback', id, preparationMs: 5, inferenceMs: 0 } }); await tick();
assert.equal(typeof release, 'function', 'fallback recaptures the requested photo');
h.provider.cancel(); await rejected;
const replacement = h.provider.getDepthMap({ naturalWidth: 50, naturalHeight: 100 }); await tick();
assert.equal(h.workers.length, 2); const current = h.workers[1];
const stale = { width: 10, height: 5, closed: 0, close() { this.closed++; } }; release(stale); await tick();
assert.equal(stale.closed, 1); assert.equal(h.workers.length, 2); assert.equal(old.terminated, true);
replyWithResult(current, { model: 'small', ...wasm, preparationMs: 1, inferenceMs: 2, totalMs: 3 }); (await replacement).dispose();
} finally { await h.cleanup(); }
});
test('serialized terminal error also discards runtime so manual retry starts fresh', async () => {
const h = await providerHarness();
try {
const pending = h.provider.getDepthMap(photo); const rejected = assert.rejects(pending, /WASM failed/);
await tick(); const old = h.workers[0], id = old.messages[0].id;
old.onmessage({ data: { type: 'error', id, message: 'WASM failed' } }); await rejected;
assert.equal(old.terminated, true);
const retry = h.provider.getDepthMap(photo); await tick(); assert.equal(h.workers.length, 2);
replyWithResult(h.workers[1], { model: 'small', ...wasm, preparationMs: 1, inferenceMs: 2, totalMs: 3 });
(await retry).dispose();
} finally { await h.cleanup(); }
});
// Execute the installed dependency's actual initialization chain, replacing only ORT's
// slow hardware boundary. A rejected GPU load prevents same-realm WASM from running.
async function dependencyRuntime() {
const source = await readFile(new URL('../node_modules/@huggingface/transformers/src/backends/onnx.js', import.meta.url), 'utf8');
const start = source.indexOf('export async function createInferenceSession(');
assert.ok(start >= 0);
const body = source.slice(start, source.indexOf('\n/**', start)).replace('export ', '');
const calls = [];
const context = {
ensureWasmLoaded: async () => {}, getOnnxLogSeverityLevel: () => 2, env: {}, LogLevel: { WARNING: 2 }, apis: { IS_WEB_ENV: true },
InferenceSession: { async create(_buffer, options) {
const device = options.executionProviders[0]; calls.push(device);
if (device === 'webgpu') throw new Error('Failed to get GPU adapter');
return {};
} },
};
vm.createContext(context);
vm.runInContext('let webInitChain = Promise.resolve();' + body + ';this.create = createInferenceSession;', context);
return { calls, create: preset => context.create(new Uint8Array(), { executionProviders: [preset.device] }, {}) };
}
test('real dependency rejected chain is isolated before WASM fallback', async () => {
const poisoned = await dependencyRuntime();
const gpuWorker = await workerHarness({ beforeLoad: poisoned.create });
const failed = await gpuWorker.request(); assert.equal(failed.message.type, 'fallback');
assert.deepEqual(poisoned.calls, ['webgpu']);
await assert.rejects(poisoned.create(wasm), /Failed to get GPU adapter/);
assert.deepEqual(poisoned.calls, ['webgpu'], 'same-realm fallback is skipped by the dependency, matching the reported mechanism');
const clean = await dependencyRuntime();
const cpuWorker = await workerHarness({ beforeLoad: clean.create });
const recovered = await cpuWorker.request({ forceWasm: true });
assert.equal(recovered.message.type, 'result'); assert.equal(recovered.message.run.device, 'wasm');
assert.deepEqual(clean.calls, ['wasm']);
await cpuWorker.request(); assert.deepEqual(clean.calls, ['wasm'], 'successful WASM session is reused');
});
test('duplicate and stale fallback replies cannot replace the active CPU worker', async () => {
const h = await providerHarness();
try {
const pending = h.provider.getDepthMap(photo); await tick(); const old = h.workers[0], id = old.messages[0].id;
const fallbackReply = { data: { type: 'fallback', id, preparationMs: 5, inferenceMs: 0 } };
old.onmessage(fallbackReply); old.onmessage(fallbackReply); await tick();
assert.equal(h.workers.length, 2); const current = h.workers[1];
current.onmessage({ data: { type: 'fallback', id: id - 1, preparationMs: 0, inferenceMs: 0 } });
assert.equal(current.terminated, false); assert.equal(h.workers.length, 2);
replyWithResult(current, { model: 'small', ...wasm, preparationMs: 1, inferenceMs: 2, totalMs: 3 }); (await pending).dispose();
} finally { await h.cleanup(); }
});
test('Worker constructor failure closes the captured bitmap', async () => {
const h = await providerHarness();
try {
globalThis.Worker = class { constructor() { throw new Error('worker unavailable'); } };
await assert.rejects(h.provider.getDepthMap(photo), /worker unavailable/);
assert.equal(h.bitmaps[0].closed, true);
} finally { await h.cleanup(); }
});