okf-catalog 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +87 -0
- package/dist/bundle/cat-file.js +94 -0
- package/dist/bundle/cat-file.js.map +1 -0
- package/dist/bundle/contract.js +60 -0
- package/dist/bundle/contract.js.map +1 -0
- package/dist/bundle/frontmatter.js +106 -0
- package/dist/bundle/frontmatter.js.map +1 -0
- package/dist/bundle/git-tree.js +109 -0
- package/dist/bundle/git-tree.js.map +1 -0
- package/dist/bundle/index-file.js +90 -0
- package/dist/bundle/index-file.js.map +1 -0
- package/dist/bundle/links.js +57 -0
- package/dist/bundle/links.js.map +1 -0
- package/dist/bundle/load.js +363 -0
- package/dist/bundle/load.js.map +1 -0
- package/dist/bundle/manifest.js +95 -0
- package/dist/bundle/manifest.js.map +1 -0
- package/dist/bundle/markdown.js +210 -0
- package/dist/bundle/markdown.js.map +1 -0
- package/dist/bundle/model.js +7 -0
- package/dist/bundle/model.js.map +1 -0
- package/dist/bundle/page.js +374 -0
- package/dist/bundle/page.js.map +1 -0
- package/dist/bundle/paths.js +36 -0
- package/dist/bundle/paths.js.map +1 -0
- package/dist/bundle/reserved.js +56 -0
- package/dist/bundle/reserved.js.map +1 -0
- package/dist/bundle/timestamp.js +49 -0
- package/dist/bundle/timestamp.js.map +1 -0
- package/dist/catalog/model.js +32 -0
- package/dist/catalog/model.js.map +1 -0
- package/dist/catalog/nearest.js +56 -0
- package/dist/catalog/nearest.js.map +1 -0
- package/dist/catalog/outputs.js +438 -0
- package/dist/catalog/outputs.js.map +1 -0
- package/dist/catalog/provenance.js +37 -0
- package/dist/catalog/provenance.js.map +1 -0
- package/dist/catalog/runtime.js +2 -0
- package/dist/catalog/runtime.js.map +1 -0
- package/dist/catalog/text.js +140 -0
- package/dist/catalog/text.js.map +1 -0
- package/dist/cli.js +81 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/check.js +155 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/pack.js +182 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/serve.js +452 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/config/company-config.js +272 -0
- package/dist/config/company-config.js.map +1 -0
- package/dist/derive/derived-document.js +42 -0
- package/dist/derive/derived-document.js.map +1 -0
- package/dist/engine/qmd-render.js +54 -0
- package/dist/engine/qmd-render.js.map +1 -0
- package/dist/engine/qmd.js +241 -0
- package/dist/engine/qmd.js.map +1 -0
- package/dist/fs/cache-dir.js +140 -0
- package/dist/fs/cache-dir.js.map +1 -0
- package/dist/fs/company-lock.js +162 -0
- package/dist/fs/company-lock.js.map +1 -0
- package/dist/fs/walk.js +168 -0
- package/dist/fs/walk.js.map +1 -0
- package/dist/log.js +65 -0
- package/dist/log.js.map +1 -0
- package/dist/mcp/server.js +30 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.js +45 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools.js +249 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/report/report.js +43 -0
- package/dist/report/report.js.map +1 -0
- package/dist/search/engine.js +2 -0
- package/dist/search/engine.js.map +1 -0
- package/dist/search/query.js +91 -0
- package/dist/search/query.js.map +1 -0
- package/dist/search/search.js +224 -0
- package/dist/search/search.js.map +1 -0
- package/dist/search/snippet.js +95 -0
- package/dist/search/snippet.js.map +1 -0
- package/dist/serve/poller.js +119 -0
- package/dist/serve/poller.js.map +1 -0
- package/dist/serve/runtime.js +390 -0
- package/dist/serve/runtime.js.map +1 -0
- package/dist/source/git-runner.js +232 -0
- package/dist/source/git-runner.js.map +1 -0
- package/dist/source/git.js +457 -0
- package/dist/source/git.js.map +1 -0
- package/dist/source/local.js +20 -0
- package/dist/source/local.js.map +1 -0
- package/dist/source/source.js +2 -0
- package/dist/source/source.js.map +1 -0
- package/npm-shrinkwrap.json +5482 -0
- package/package.json +80 -0
- package/plugin/claude-code/.claude-plugin/plugin.json +15 -0
- package/plugin/claude-code/.mcp.json +10 -0
- package/plugin/claude-code/skills/okf-catalog/SKILL.md +15 -0
- package/recipes/publish/README.md +32 -0
- package/recipes/publish/checkers.lock +435 -0
- package/recipes/publish/checkers.txt +5 -0
- package/recipes/publish/pack.sh +60 -0
- package/recipes/publish/publish.yml +88 -0
- package/recipes/publish/push.sh +63 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asks the source whether the remote moved, one whole tick at a time on a chained timer, and refreshes when it
|
|
3
|
+
* did or when the first load has not succeeded yet (D44). It runs from its timer and never from a lease, backs
|
|
4
|
+
* off no further than its interval, never keeps the process alive, and is stopped before the runtime drains.
|
|
5
|
+
*/
|
|
6
|
+
export function createPoller(deps) {
|
|
7
|
+
let timer;
|
|
8
|
+
let inFlight;
|
|
9
|
+
let stopped = false;
|
|
10
|
+
let lastTick;
|
|
11
|
+
let lastOutcome;
|
|
12
|
+
let goneLogged = false;
|
|
13
|
+
async function run() {
|
|
14
|
+
const started = performance.now();
|
|
15
|
+
let outcome = "failed";
|
|
16
|
+
let error;
|
|
17
|
+
let detail;
|
|
18
|
+
try {
|
|
19
|
+
const status = deps.runtime.status();
|
|
20
|
+
const source = deps.source();
|
|
21
|
+
let shouldRefresh = !status.loaded ||
|
|
22
|
+
status.fatal === true ||
|
|
23
|
+
status.refusing !== undefined ||
|
|
24
|
+
source === undefined;
|
|
25
|
+
if (!shouldRefresh && source?.changed !== undefined) {
|
|
26
|
+
const change = await source.changed();
|
|
27
|
+
if (change === "gone")
|
|
28
|
+
outcome = "gone";
|
|
29
|
+
else
|
|
30
|
+
shouldRefresh = change === "moved";
|
|
31
|
+
}
|
|
32
|
+
if (stopped)
|
|
33
|
+
return "skipped";
|
|
34
|
+
if (outcome !== "gone") {
|
|
35
|
+
if (shouldRefresh) {
|
|
36
|
+
const result = await deps.runtime.refresh();
|
|
37
|
+
outcome = result.outcome === "swapped" ? "refreshed" : "failed";
|
|
38
|
+
if (result.outcome === "failed")
|
|
39
|
+
error = result.error;
|
|
40
|
+
if (result.outcome === "fatal")
|
|
41
|
+
error = result.report.fatal?.rule;
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
outcome = "unchanged";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (caught) {
|
|
48
|
+
outcome = "failed";
|
|
49
|
+
error = caught.message;
|
|
50
|
+
const carried = caught.detail;
|
|
51
|
+
if (typeof carried === "string")
|
|
52
|
+
detail = carried;
|
|
53
|
+
}
|
|
54
|
+
lastTick = deps.clock();
|
|
55
|
+
lastOutcome = outcome;
|
|
56
|
+
const fields = {
|
|
57
|
+
outcome,
|
|
58
|
+
ms: Math.round(performance.now() - started),
|
|
59
|
+
...(error === undefined ? {} : { error }),
|
|
60
|
+
...(detail === undefined ? {} : { detail }),
|
|
61
|
+
};
|
|
62
|
+
if (outcome === "gone") {
|
|
63
|
+
if (goneLogged)
|
|
64
|
+
deps.log.debug("poller.tick", fields);
|
|
65
|
+
else
|
|
66
|
+
deps.log.warn("poller.tick", fields);
|
|
67
|
+
goneLogged = true;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
goneLogged = false;
|
|
71
|
+
if (outcome === "failed")
|
|
72
|
+
deps.log.warn("poller.tick", fields);
|
|
73
|
+
else
|
|
74
|
+
deps.log.info("poller.tick", fields);
|
|
75
|
+
}
|
|
76
|
+
return outcome;
|
|
77
|
+
}
|
|
78
|
+
function tick() {
|
|
79
|
+
if (stopped || inFlight !== undefined)
|
|
80
|
+
return Promise.resolve("skipped");
|
|
81
|
+
inFlight = run().finally(() => {
|
|
82
|
+
inFlight = undefined;
|
|
83
|
+
});
|
|
84
|
+
return inFlight;
|
|
85
|
+
}
|
|
86
|
+
function arm(delay) {
|
|
87
|
+
if (stopped)
|
|
88
|
+
return;
|
|
89
|
+
timer = setTimeout(() => {
|
|
90
|
+
timer = undefined;
|
|
91
|
+
void tick().finally(() => arm(deps.intervalMs));
|
|
92
|
+
}, delay);
|
|
93
|
+
timer.unref();
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
start: () => {
|
|
97
|
+
if (timer !== undefined || stopped)
|
|
98
|
+
return;
|
|
99
|
+
arm(deps.immediate === true ? 0 : deps.intervalMs);
|
|
100
|
+
},
|
|
101
|
+
stop: async () => {
|
|
102
|
+
stopped = true;
|
|
103
|
+
if (timer !== undefined)
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
timer = undefined;
|
|
106
|
+
deps.source()?.abort?.();
|
|
107
|
+
if (inFlight !== undefined)
|
|
108
|
+
await inFlight.catch(() => undefined);
|
|
109
|
+
},
|
|
110
|
+
tick,
|
|
111
|
+
timerHasRef: () => timer?.hasRef(),
|
|
112
|
+
state: () => ({
|
|
113
|
+
intervalMs: deps.intervalMs,
|
|
114
|
+
...(lastTick === undefined ? {} : { lastTick }),
|
|
115
|
+
...(lastOutcome === undefined ? {} : { lastOutcome }),
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=poller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poller.js","sourceRoot":"","sources":["../../src/serve/poller.ts"],"names":[],"mappings":"AA0BA;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAgB;IAC3C,IAAI,KAAiC,CAAC;IACtC,IAAI,QAA4C,CAAC;IACjD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,QAA0B,CAAC;IAC/B,IAAI,WAAsC,CAAC;IAC3C,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,KAAK,UAAU,GAAG;QAChB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAClC,IAAI,OAAO,GAAkB,QAAQ,CAAC;QACtC,IAAI,KAAyB,CAAC;QAC9B,IAAI,MAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC7B,IAAI,aAAa,GACf,CAAC,MAAM,CAAC,MAAM;gBACd,MAAM,CAAC,KAAK,KAAK,IAAI;gBACrB,MAAM,CAAC,QAAQ,KAAK,SAAS;gBAC7B,MAAM,KAAK,SAAS,CAAC;YACvB,IAAI,CAAC,aAAa,IAAI,MAAM,EAAE,OAAO,KAAK,SAAS,EAAE,CAAC;gBACpD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;gBACtC,IAAI,MAAM,KAAK,MAAM;oBAAE,OAAO,GAAG,MAAM,CAAC;;oBACnC,aAAa,GAAG,MAAM,KAAK,OAAO,CAAC;YAC1C,CAAC;YACD,IAAI,OAAO;gBAAE,OAAO,SAAS,CAAC;YAC9B,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;gBACvB,IAAI,aAAa,EAAE,CAAC;oBAClB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;oBAC5C,OAAO,GAAG,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC;oBAChE,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ;wBAAE,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;oBACtD,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO;wBAAE,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;gBACpE,CAAC;;oBAAM,OAAO,GAAG,WAAW,CAAC;YAC/B,CAAC;QACH,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,OAAO,GAAG,QAAQ,CAAC;YACnB,KAAK,GAAI,MAAgB,CAAC,OAAO,CAAC;YAClC,MAAM,OAAO,GAAI,MAA+B,CAAC,MAAM,CAAC;YACxD,IAAI,OAAO,OAAO,KAAK,QAAQ;gBAAE,MAAM,GAAG,OAAO,CAAC;QACpD,CAAC;QACD,QAAQ,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QACxB,WAAW,GAAG,OAAO,CAAC;QACtB,MAAM,MAAM,GAAG;YACb,OAAO;YACP,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;YAC3C,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;YACzC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;SAC5C,CAAC;QACF,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACvB,IAAI,UAAU;gBAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;;gBACjD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YAC1C,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,KAAK,CAAC;YACnB,IAAI,OAAO,KAAK,QAAQ;gBAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;;gBAC1D,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,SAAS,IAAI;QACX,IAAI,OAAO,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACzE,QAAQ,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC5B,QAAQ,GAAG,SAAS,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,SAAS,GAAG,CAAC,KAAa;QACxB,IAAI,OAAO;YAAE,OAAO;QACpB,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,KAAK,GAAG,SAAS,CAAC;YAClB,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAClD,CAAC,EAAE,KAAK,CAAC,CAAC;QACV,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAED,OAAO;QACL,KAAK,EAAE,GAAG,EAAE;YACV,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO;gBAAE,OAAO;YAC3C,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,KAAK,KAAK,SAAS;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YAC7C,KAAK,GAAG,SAAS,CAAC;YAClB,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC;YACzB,IAAI,QAAQ,KAAK,SAAS;gBAAE,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACpE,CAAC;QACD,IAAI;QACJ,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE;QAClC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YACZ,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;YAC/C,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;SACtD,CAAC;KACH,CAAC;AACJ,CAAC","sourcesContent":["import type { PollerOutcome, PollerStatus, Runtime } from \"../catalog/runtime.js\";\nimport type { Log } from \"../log.js\";\nimport type { Source } from \"../source/source.js\";\n\nexport interface PollerDeps {\n runtime: Runtime;\n /** The source, once `prepare()` has built it; undefined until then, when every tick is a retry through the runtime. */\n source: () => Source | undefined;\n intervalMs: number;\n log: Log;\n clock: () => Date;\n /** Run the first tick at once instead of one interval after `start()` (the first load came from disk). */\n immediate?: boolean;\n}\n\nexport interface Poller {\n start(): void;\n /** Clears the timer, aborts the source's transport and waits for the tick in flight. */\n stop(): Promise<void>;\n /** One tick, for tests and for `start()`: never two at once. */\n tick(): Promise<PollerOutcome>;\n state(): PollerStatus;\n /** Whether the armed timer holds the process; undefined when none is armed. For tests. */\n timerHasRef(): boolean | undefined;\n}\n\n/**\n * Asks the source whether the remote moved, one whole tick at a time on a chained timer, and refreshes when it\n * did or when the first load has not succeeded yet (D44). It runs from its timer and never from a lease, backs\n * off no further than its interval, never keeps the process alive, and is stopped before the runtime drains.\n */\nexport function createPoller(deps: PollerDeps): Poller {\n let timer: NodeJS.Timeout | undefined;\n let inFlight: Promise<PollerOutcome> | undefined;\n let stopped = false;\n let lastTick: Date | undefined;\n let lastOutcome: PollerOutcome | undefined;\n let goneLogged = false;\n\n async function run(): Promise<PollerOutcome> {\n const started = performance.now();\n let outcome: PollerOutcome = \"failed\";\n let error: string | undefined;\n let detail: string | undefined;\n try {\n const status = deps.runtime.status();\n const source = deps.source();\n let shouldRefresh =\n !status.loaded ||\n status.fatal === true ||\n status.refusing !== undefined ||\n source === undefined;\n if (!shouldRefresh && source?.changed !== undefined) {\n const change = await source.changed();\n if (change === \"gone\") outcome = \"gone\";\n else shouldRefresh = change === \"moved\";\n }\n if (stopped) return \"skipped\";\n if (outcome !== \"gone\") {\n if (shouldRefresh) {\n const result = await deps.runtime.refresh();\n outcome = result.outcome === \"swapped\" ? \"refreshed\" : \"failed\";\n if (result.outcome === \"failed\") error = result.error;\n if (result.outcome === \"fatal\") error = result.report.fatal?.rule;\n } else outcome = \"unchanged\";\n }\n } catch (caught) {\n outcome = \"failed\";\n error = (caught as Error).message;\n const carried = (caught as { detail?: unknown }).detail;\n if (typeof carried === \"string\") detail = carried;\n }\n lastTick = deps.clock();\n lastOutcome = outcome;\n const fields = {\n outcome,\n ms: Math.round(performance.now() - started),\n ...(error === undefined ? {} : { error }),\n ...(detail === undefined ? {} : { detail }),\n };\n if (outcome === \"gone\") {\n if (goneLogged) deps.log.debug(\"poller.tick\", fields);\n else deps.log.warn(\"poller.tick\", fields);\n goneLogged = true;\n } else {\n goneLogged = false;\n if (outcome === \"failed\") deps.log.warn(\"poller.tick\", fields);\n else deps.log.info(\"poller.tick\", fields);\n }\n return outcome;\n }\n\n function tick(): Promise<PollerOutcome> {\n if (stopped || inFlight !== undefined) return Promise.resolve(\"skipped\");\n inFlight = run().finally(() => {\n inFlight = undefined;\n });\n return inFlight;\n }\n\n function arm(delay: number): void {\n if (stopped) return;\n timer = setTimeout(() => {\n timer = undefined;\n void tick().finally(() => arm(deps.intervalMs));\n }, delay);\n timer.unref();\n }\n\n return {\n start: () => {\n if (timer !== undefined || stopped) return;\n arm(deps.immediate === true ? 0 : deps.intervalMs);\n },\n stop: async () => {\n stopped = true;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n deps.source()?.abort?.();\n if (inFlight !== undefined) await inFlight.catch(() => undefined);\n },\n tick,\n timerHasRef: () => timer?.hasRef(),\n state: () => ({\n intervalMs: deps.intervalMs,\n ...(lastTick === undefined ? {} : { lastTick }),\n ...(lastOutcome === undefined ? {} : { lastOutcome }),\n }),\n };\n}\n"]}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { loadBundle } from "../bundle/load.js";
|
|
2
|
+
import { deriveDocument } from "../derive/derived-document.js";
|
|
3
|
+
const EMPTY_INDEX = {
|
|
4
|
+
documents: 0,
|
|
5
|
+
indexed: 0,
|
|
6
|
+
updated: 0,
|
|
7
|
+
unchanged: 0,
|
|
8
|
+
removed: 0,
|
|
9
|
+
skipped: 0,
|
|
10
|
+
notIndexed: [],
|
|
11
|
+
collisions: [],
|
|
12
|
+
encodedFolders: [],
|
|
13
|
+
};
|
|
14
|
+
const yieldToLoop = () => new Promise((resolve) => setImmediate(resolve));
|
|
15
|
+
/**
|
|
16
|
+
* The serving runtime (decisions D28, D38, D39). The first load begins on `start()` or the first `ready()`,
|
|
17
|
+
* never before, so a probing connection costs nothing. Loading yields to the event loop between its phases (walk,
|
|
18
|
+
* load, derive, index), so the handshake is answered between them; each phase itself runs without yielding
|
|
19
|
+
* (measured on 736 pages: the load about 1.1 s, the engine's update about 0.7 s). Tool calls hold a lease
|
|
20
|
+
* on the generation they read; a refresh prepares the next generation while leases run, then blocks new leases,
|
|
21
|
+
* waits for the running ones, indexes, swaps catalog and index together, and releases. A refused reload keeps
|
|
22
|
+
* the previous generation; a reload that throws keeps it too and re-aligns the index with it. A refused first
|
|
23
|
+
* load is published as a generation whose report carries the refusal, so `status` can show it.
|
|
24
|
+
*/
|
|
25
|
+
export function createRuntime(deps) {
|
|
26
|
+
let engine;
|
|
27
|
+
let lock = "exclusive";
|
|
28
|
+
let resetOnOpen;
|
|
29
|
+
let source = deps.source;
|
|
30
|
+
let prepared;
|
|
31
|
+
let current;
|
|
32
|
+
let firstLoad;
|
|
33
|
+
let firstLoadFailed = false;
|
|
34
|
+
let refreshing;
|
|
35
|
+
let closing;
|
|
36
|
+
let closed = false;
|
|
37
|
+
let refusing;
|
|
38
|
+
let lastAttempt;
|
|
39
|
+
let lastRefusal;
|
|
40
|
+
/** Leases requested but not yet reading (waiting for the first load or the gate): shutdown waits for them. */
|
|
41
|
+
let requested = 0;
|
|
42
|
+
/** Leases reading the current generation: a swap waits for them. */
|
|
43
|
+
let active = 0;
|
|
44
|
+
let drainWaiters = [];
|
|
45
|
+
let requestWaiters = [];
|
|
46
|
+
let gate;
|
|
47
|
+
let openGate;
|
|
48
|
+
const integrity = deps.load.integrity === "require-manifest" ? "checked" : "skipped";
|
|
49
|
+
const counts = (report, index) => ({
|
|
50
|
+
documents: index.documents,
|
|
51
|
+
notIndexed: index.notIndexed.length,
|
|
52
|
+
collisions: index.collisions.length,
|
|
53
|
+
encodedFolders: index.encodedFolders,
|
|
54
|
+
admitted: report.admitted,
|
|
55
|
+
refused: report.refusals.length,
|
|
56
|
+
degraded: report.degradations.length,
|
|
57
|
+
});
|
|
58
|
+
/** `prepare()` once, kept once it succeeds; a failure is forgotten so the next attempt runs it again. */
|
|
59
|
+
function prepare() {
|
|
60
|
+
if (prepared === undefined) {
|
|
61
|
+
prepared = deps.prepare().then((result) => {
|
|
62
|
+
engine = result.engine;
|
|
63
|
+
lock = result.lock;
|
|
64
|
+
if (result.resetOnOpen !== undefined)
|
|
65
|
+
resetOnOpen = result.resetOnOpen;
|
|
66
|
+
if (result.source !== undefined)
|
|
67
|
+
source = result.source;
|
|
68
|
+
return result;
|
|
69
|
+
}, (error) => {
|
|
70
|
+
prepared = undefined;
|
|
71
|
+
throw error;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return prepared;
|
|
75
|
+
}
|
|
76
|
+
/** Walk, load and derive, yielding between the phases; the engine is not touched here. */
|
|
77
|
+
async function prepareDocs() {
|
|
78
|
+
const loaded = await source.load();
|
|
79
|
+
const prepared = await prepareFrom(loaded);
|
|
80
|
+
if (prepared.kind === "fatal")
|
|
81
|
+
noteRefusal(prepared, loaded);
|
|
82
|
+
return prepared;
|
|
83
|
+
}
|
|
84
|
+
/** A refusal is kept for `status`; a tree reused from disk that the loader refuses is discarded, so the next load extracts it again. */
|
|
85
|
+
function noteRefusal(prepared, loaded) {
|
|
86
|
+
const fatal = prepared.report.fatal;
|
|
87
|
+
if (fatal !== undefined) {
|
|
88
|
+
lastRefusal = {
|
|
89
|
+
rule: fatal.rule,
|
|
90
|
+
path: fatal.path,
|
|
91
|
+
detail: fatal.detail,
|
|
92
|
+
...(prepared.published === undefined ? {} : { commit: prepared.published.commit }),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (loaded.fresh === false && loaded.published !== undefined)
|
|
96
|
+
source.discard?.(loaded.published.commit);
|
|
97
|
+
}
|
|
98
|
+
async function prepareFrom(loaded) {
|
|
99
|
+
const walked = loaded.walk;
|
|
100
|
+
const published = loaded.published;
|
|
101
|
+
await yieldToLoop();
|
|
102
|
+
const options = {
|
|
103
|
+
...deps.load,
|
|
104
|
+
walkRefusals: walked.refusals,
|
|
105
|
+
hiddenPaths: walked.hidden,
|
|
106
|
+
hiddenFolders: walked.hiddenFolders,
|
|
107
|
+
};
|
|
108
|
+
if (walked.fatal !== undefined)
|
|
109
|
+
options.walkFatal = walked.fatal;
|
|
110
|
+
const now = deps.clock();
|
|
111
|
+
const { catalog, report } = loadBundle(deps.company, walked.files, options, now);
|
|
112
|
+
await yieldToLoop();
|
|
113
|
+
const withPublished = {
|
|
114
|
+
...(published === undefined ? {} : { published }),
|
|
115
|
+
...(loaded.fresh === undefined ? {} : { fresh: loaded.fresh }),
|
|
116
|
+
};
|
|
117
|
+
if (report.fatal !== undefined)
|
|
118
|
+
return { kind: "fatal", catalog, report, now, ...withPublished };
|
|
119
|
+
const docs = [...catalog.pages.values()].map(deriveDocument);
|
|
120
|
+
await yieldToLoop();
|
|
121
|
+
return { kind: "docs", catalog, report, docs, now, ...withPublished };
|
|
122
|
+
}
|
|
123
|
+
const whenDrained = () => active === 0 ? Promise.resolve() : new Promise((resolve) => drainWaiters.push(resolve));
|
|
124
|
+
const whenNoneRequested = () => requested === 0 ? Promise.resolve() : new Promise((resolve) => requestWaiters.push(resolve));
|
|
125
|
+
function closeGate() {
|
|
126
|
+
gate = new Promise((resolve) => {
|
|
127
|
+
openGate = resolve;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function releaseGate() {
|
|
131
|
+
const open = openGate;
|
|
132
|
+
gate = undefined;
|
|
133
|
+
openGate = undefined;
|
|
134
|
+
open?.();
|
|
135
|
+
}
|
|
136
|
+
/** Under the gate: no new lease starts, running ones finish, then the index is rebuilt and the generation swapped. */
|
|
137
|
+
async function commit(prepared) {
|
|
138
|
+
const live = engine;
|
|
139
|
+
if (live === undefined)
|
|
140
|
+
throw new Error("the engine is not open");
|
|
141
|
+
closeGate();
|
|
142
|
+
try {
|
|
143
|
+
await whenDrained();
|
|
144
|
+
let index;
|
|
145
|
+
try {
|
|
146
|
+
index = await live.index(prepared.docs);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// The engine may now hold the new tree while the old catalog stays served: put them back together. If
|
|
150
|
+
// that fails too, nothing may be served until a refresh succeeds (D39).
|
|
151
|
+
if (current !== undefined) {
|
|
152
|
+
try {
|
|
153
|
+
await live.index([...current.catalog.pages.values()].map(deriveDocument));
|
|
154
|
+
}
|
|
155
|
+
catch (again) {
|
|
156
|
+
refusing = `the index could not be re-aligned with the served pages after a failed refresh (${again.message}); nothing is served until a refresh succeeds`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
const generation = {
|
|
162
|
+
catalog: prepared.catalog,
|
|
163
|
+
report: prepared.report,
|
|
164
|
+
index,
|
|
165
|
+
loadedAt: prepared.now,
|
|
166
|
+
dev: deps.load.dev,
|
|
167
|
+
integrity,
|
|
168
|
+
...(prepared.published === undefined ? {} : { published: prepared.published }),
|
|
169
|
+
};
|
|
170
|
+
current = generation;
|
|
171
|
+
// A tree reused from disk (the first-load fallback) does not answer the refusal that made it necessary.
|
|
172
|
+
if (prepared.fresh !== false)
|
|
173
|
+
lastRefusal = undefined;
|
|
174
|
+
if (generation.published !== undefined)
|
|
175
|
+
source.served?.(generation.published.commit);
|
|
176
|
+
return generation;
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
releaseGate();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const fatalGeneration = (next) => ({
|
|
183
|
+
catalog: next.catalog,
|
|
184
|
+
report: next.report,
|
|
185
|
+
index: EMPTY_INDEX,
|
|
186
|
+
loadedAt: next.now,
|
|
187
|
+
dev: deps.load.dev,
|
|
188
|
+
integrity,
|
|
189
|
+
...(next.published === undefined ? {} : { published: next.published }),
|
|
190
|
+
});
|
|
191
|
+
/** The first load: prepare, load, publish. A loader refusal falls back to the tree the source last served (D43). */
|
|
192
|
+
async function firstLoadBody() {
|
|
193
|
+
await prepare();
|
|
194
|
+
const next = await prepareDocs();
|
|
195
|
+
if (next.kind === "fatal") {
|
|
196
|
+
lastAttempt = { at: deps.clock(), outcome: "fatal" };
|
|
197
|
+
deps.log.error("load.fatal", {
|
|
198
|
+
rule: next.report.fatal?.rule,
|
|
199
|
+
path: next.report.fatal?.path,
|
|
200
|
+
detail: next.report.fatal?.detail,
|
|
201
|
+
});
|
|
202
|
+
const servedOnDisk = source.loadServed === undefined ? undefined : await source.loadServed();
|
|
203
|
+
if (servedOnDisk !== undefined) {
|
|
204
|
+
const served = await prepareFrom(servedOnDisk);
|
|
205
|
+
if (served.kind === "docs") {
|
|
206
|
+
const generation = await commit(served);
|
|
207
|
+
deps.log.warn("load.served-previous", {
|
|
208
|
+
commit: generation.published?.commit,
|
|
209
|
+
refused: next.published?.commit,
|
|
210
|
+
});
|
|
211
|
+
return generation;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
current = fatalGeneration(next);
|
|
215
|
+
return current;
|
|
216
|
+
}
|
|
217
|
+
const generation = await commit(next);
|
|
218
|
+
lastAttempt = { at: deps.clock(), outcome: "swapped" };
|
|
219
|
+
deps.log.info("load.done", counts(generation.report, generation.index));
|
|
220
|
+
return generation;
|
|
221
|
+
}
|
|
222
|
+
function start() {
|
|
223
|
+
if (firstLoad !== undefined || closed)
|
|
224
|
+
return;
|
|
225
|
+
firstLoad = (async () => {
|
|
226
|
+
try {
|
|
227
|
+
return await firstLoadBody();
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
refusing = error.message;
|
|
231
|
+
firstLoadFailed = true;
|
|
232
|
+
lastAttempt = { at: deps.clock(), outcome: "failed" };
|
|
233
|
+
const detail = error.detail;
|
|
234
|
+
deps.log.error("serve.refusing", {
|
|
235
|
+
problem: refusing,
|
|
236
|
+
...(typeof detail === "string" ? { detail } : {}),
|
|
237
|
+
});
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
})();
|
|
241
|
+
// The handshake starts this load with nobody waiting on it; a failure is kept for `ready()` and `status()`
|
|
242
|
+
// and must not surface as an unhandled rejection, which would end the process.
|
|
243
|
+
firstLoad.catch(() => undefined);
|
|
244
|
+
}
|
|
245
|
+
async function ready() {
|
|
246
|
+
if (refusing !== undefined)
|
|
247
|
+
throw new Error(refusing);
|
|
248
|
+
start();
|
|
249
|
+
if (firstLoad === undefined)
|
|
250
|
+
throw new Error("the runtime is shut down");
|
|
251
|
+
return firstLoad;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* A lease is counted from the moment it is requested, so a shutdown that follows the request waits for it; it
|
|
255
|
+
* becomes active only once it reads a generation, so a swap waits for readers and never for a call that is
|
|
256
|
+
* itself waiting for the first load.
|
|
257
|
+
*/
|
|
258
|
+
function lease(fn) {
|
|
259
|
+
if (closed)
|
|
260
|
+
return Promise.reject(new Error("the runtime is shut down"));
|
|
261
|
+
requested += 1;
|
|
262
|
+
let reading = false;
|
|
263
|
+
const release = () => {
|
|
264
|
+
if (reading) {
|
|
265
|
+
active -= 1;
|
|
266
|
+
if (active === 0) {
|
|
267
|
+
const waiters = drainWaiters;
|
|
268
|
+
drainWaiters = [];
|
|
269
|
+
for (const resolve of waiters)
|
|
270
|
+
resolve();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
requested -= 1;
|
|
274
|
+
if (requested === 0) {
|
|
275
|
+
const waiters = requestWaiters;
|
|
276
|
+
requestWaiters = [];
|
|
277
|
+
for (const resolve of waiters)
|
|
278
|
+
resolve();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
return (async () => {
|
|
282
|
+
try {
|
|
283
|
+
await ready();
|
|
284
|
+
while (gate !== undefined)
|
|
285
|
+
await gate;
|
|
286
|
+
const generation = current;
|
|
287
|
+
const live = engine;
|
|
288
|
+
if (generation === undefined || live === undefined) {
|
|
289
|
+
throw new Error("no generation is published");
|
|
290
|
+
}
|
|
291
|
+
reading = true;
|
|
292
|
+
active += 1;
|
|
293
|
+
return await fn(generation, live);
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
release();
|
|
297
|
+
}
|
|
298
|
+
})();
|
|
299
|
+
}
|
|
300
|
+
function refresh() {
|
|
301
|
+
if (closed)
|
|
302
|
+
return Promise.reject(new Error("the runtime is shut down"));
|
|
303
|
+
if (refreshing !== undefined)
|
|
304
|
+
return refreshing;
|
|
305
|
+
refreshing = (async () => {
|
|
306
|
+
try {
|
|
307
|
+
if (firstLoad === undefined)
|
|
308
|
+
throw new Error("the first load has not started");
|
|
309
|
+
if (firstLoadFailed) {
|
|
310
|
+
// A first load that failed (the network, the cache, the lock) is tried again from the start.
|
|
311
|
+
firstLoadFailed = false;
|
|
312
|
+
firstLoad = undefined;
|
|
313
|
+
start();
|
|
314
|
+
if (firstLoad === undefined)
|
|
315
|
+
throw new Error("the runtime is shut down");
|
|
316
|
+
const generation = await firstLoad;
|
|
317
|
+
refusing = undefined;
|
|
318
|
+
return generation.report.fatal === undefined
|
|
319
|
+
? { outcome: "swapped", generation }
|
|
320
|
+
: { outcome: "fatal", report: generation.report };
|
|
321
|
+
}
|
|
322
|
+
await firstLoad;
|
|
323
|
+
const next = await prepareDocs();
|
|
324
|
+
if (next.kind === "fatal") {
|
|
325
|
+
lastAttempt = { at: deps.clock(), outcome: "fatal" };
|
|
326
|
+
deps.log.error("refresh.fatal", {
|
|
327
|
+
rule: next.report.fatal?.rule,
|
|
328
|
+
path: next.report.fatal?.path,
|
|
329
|
+
detail: next.report.fatal?.detail,
|
|
330
|
+
});
|
|
331
|
+
return { outcome: "fatal", report: next.report };
|
|
332
|
+
}
|
|
333
|
+
const generation = await commit(next);
|
|
334
|
+
refusing = undefined;
|
|
335
|
+
lastAttempt = { at: deps.clock(), outcome: "swapped" };
|
|
336
|
+
deps.log.info("refresh.done", counts(generation.report, generation.index));
|
|
337
|
+
return { outcome: "swapped", generation };
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const message = error.message;
|
|
341
|
+
lastAttempt = { at: deps.clock(), outcome: "failed" };
|
|
342
|
+
const detail = error.detail;
|
|
343
|
+
deps.log.error("refresh.failed", {
|
|
344
|
+
error: message,
|
|
345
|
+
...(typeof detail === "string" ? { detail } : {}),
|
|
346
|
+
});
|
|
347
|
+
return { outcome: "failed", error: message };
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
refreshing = undefined;
|
|
351
|
+
}
|
|
352
|
+
})();
|
|
353
|
+
return refreshing;
|
|
354
|
+
}
|
|
355
|
+
function status() {
|
|
356
|
+
const result = {
|
|
357
|
+
lock,
|
|
358
|
+
loaded: current !== undefined,
|
|
359
|
+
fatal: current?.report.fatal !== undefined,
|
|
360
|
+
...(deps.extra?.() ?? {}),
|
|
361
|
+
};
|
|
362
|
+
if (lastAttempt !== undefined)
|
|
363
|
+
result.lastAttempt = lastAttempt;
|
|
364
|
+
if (lastRefusal !== undefined)
|
|
365
|
+
result.lastRefusal = lastRefusal;
|
|
366
|
+
if (refusing !== undefined)
|
|
367
|
+
result.refusing = refusing;
|
|
368
|
+
if (resetOnOpen !== undefined)
|
|
369
|
+
result.resetOnOpen = resetOnOpen;
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
function shutdown() {
|
|
373
|
+
if (closing !== undefined)
|
|
374
|
+
return closing;
|
|
375
|
+
closed = true;
|
|
376
|
+
closing = (async () => {
|
|
377
|
+
source.abort?.();
|
|
378
|
+
if (firstLoad !== undefined)
|
|
379
|
+
await firstLoad.catch(() => undefined);
|
|
380
|
+
if (refreshing !== undefined)
|
|
381
|
+
await refreshing.catch(() => undefined);
|
|
382
|
+
await whenNoneRequested();
|
|
383
|
+
if (engine !== undefined)
|
|
384
|
+
await engine.close();
|
|
385
|
+
})();
|
|
386
|
+
return closing;
|
|
387
|
+
}
|
|
388
|
+
return { start, ready, lease, refresh, status, shutdown };
|
|
389
|
+
}
|
|
390
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/serve/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { loadBundle } from \"../bundle/load.js\";\nimport type { LoadOptions, Report } from \"../bundle/model.js\";\nimport type { Catalog } from \"../catalog/model.js\";\nimport type {\n Generation,\n LastRefusal,\n PublishedInfo,\n RefreshOutcome,\n Runtime,\n RuntimeStatus,\n} from \"../catalog/runtime.js\";\nimport { type DerivedDocument, deriveDocument } from \"../derive/derived-document.js\";\nimport type { Log } from \"../log.js\";\nimport type { Engine, IndexResult } from \"../search/engine.js\";\nimport type { Loaded, Source } from \"../source/source.js\";\n\nexport interface PrepareResult {\n engine: Engine;\n lock: \"exclusive\" | \"private\";\n /** Why the engine rebuilt its store, when it did. */\n resetOnOpen?: string;\n /** A source that could only be built once the work folder was known (the repository source). */\n source?: Source;\n}\n\nexport interface RuntimeDeps {\n company: string;\n source: Source;\n /** Runs at the start of the first load, once it succeeds: the cache folder, the lock and the engine. Never for a probe. A failure is retried by `refresh()`. */\n prepare: () => Promise<PrepareResult>;\n load: LoadOptions;\n clock: () => Date;\n log: Log;\n /** Status fields the command owns: the lock's holder and the poller. */\n extra?: () => Pick<RuntimeStatus, \"lockOwner\" | \"poller\">;\n}\n\nexport interface ServingRuntime extends Runtime {\n start(): void;\n}\n\nconst EMPTY_INDEX: IndexResult = {\n documents: 0,\n indexed: 0,\n updated: 0,\n unchanged: 0,\n removed: 0,\n skipped: 0,\n notIndexed: [],\n collisions: [],\n encodedFolders: [],\n};\n\nconst yieldToLoop = (): Promise<void> => new Promise((resolve) => setImmediate(resolve));\n\ntype Prepared =\n | {\n kind: \"docs\";\n catalog: Catalog;\n report: Report;\n docs: DerivedDocument[];\n now: Date;\n published?: PublishedInfo;\n /** False when the tree was reused from disk (the first-load fallback), which keeps a refusal visible. */\n fresh?: boolean;\n }\n | {\n kind: \"fatal\";\n catalog: Catalog;\n report: Report;\n now: Date;\n published?: PublishedInfo;\n fresh?: boolean;\n };\n\n/**\n * The serving runtime (decisions D28, D38, D39). The first load begins on `start()` or the first `ready()`,\n * never before, so a probing connection costs nothing. Loading yields to the event loop between its phases (walk,\n * load, derive, index), so the handshake is answered between them; each phase itself runs without yielding\n * (measured on 736 pages: the load about 1.1 s, the engine's update about 0.7 s). Tool calls hold a lease\n * on the generation they read; a refresh prepares the next generation while leases run, then blocks new leases,\n * waits for the running ones, indexes, swaps catalog and index together, and releases. A refused reload keeps\n * the previous generation; a reload that throws keeps it too and re-aligns the index with it. A refused first\n * load is published as a generation whose report carries the refusal, so `status` can show it.\n */\nexport function createRuntime(deps: RuntimeDeps): ServingRuntime {\n let engine: Engine | undefined;\n let lock: \"exclusive\" | \"private\" = \"exclusive\";\n let resetOnOpen: string | undefined;\n let source: Source = deps.source;\n let prepared: Promise<PrepareResult> | undefined;\n let current: Generation | undefined;\n let firstLoad: Promise<Generation> | undefined;\n let firstLoadFailed = false;\n let refreshing: Promise<RefreshOutcome> | undefined;\n let closing: Promise<void> | undefined;\n let closed = false;\n let refusing: string | undefined;\n let lastAttempt: RuntimeStatus[\"lastAttempt\"];\n let lastRefusal: LastRefusal | undefined;\n /** Leases requested but not yet reading (waiting for the first load or the gate): shutdown waits for them. */\n let requested = 0;\n /** Leases reading the current generation: a swap waits for them. */\n let active = 0;\n let drainWaiters: Array<() => void> = [];\n let requestWaiters: Array<() => void> = [];\n let gate: Promise<void> | undefined;\n let openGate: (() => void) | undefined;\n\n const integrity = deps.load.integrity === \"require-manifest\" ? \"checked\" : \"skipped\";\n\n const counts = (report: Report, index: IndexResult) => ({\n documents: index.documents,\n notIndexed: index.notIndexed.length,\n collisions: index.collisions.length,\n encodedFolders: index.encodedFolders,\n admitted: report.admitted,\n refused: report.refusals.length,\n degraded: report.degradations.length,\n });\n\n /** `prepare()` once, kept once it succeeds; a failure is forgotten so the next attempt runs it again. */\n function prepare(): Promise<PrepareResult> {\n if (prepared === undefined) {\n prepared = deps.prepare().then(\n (result) => {\n engine = result.engine;\n lock = result.lock;\n if (result.resetOnOpen !== undefined) resetOnOpen = result.resetOnOpen;\n if (result.source !== undefined) source = result.source;\n return result;\n },\n (error: unknown) => {\n prepared = undefined;\n throw error;\n },\n );\n }\n return prepared;\n }\n\n /** Walk, load and derive, yielding between the phases; the engine is not touched here. */\n async function prepareDocs(): Promise<Prepared> {\n const loaded = await source.load();\n const prepared = await prepareFrom(loaded);\n if (prepared.kind === \"fatal\") noteRefusal(prepared, loaded);\n return prepared;\n }\n\n /** A refusal is kept for `status`; a tree reused from disk that the loader refuses is discarded, so the next load extracts it again. */\n function noteRefusal(prepared: Prepared & { kind: \"fatal\" }, loaded: Loaded): void {\n const fatal = prepared.report.fatal;\n if (fatal !== undefined) {\n lastRefusal = {\n rule: fatal.rule,\n path: fatal.path,\n detail: fatal.detail,\n ...(prepared.published === undefined ? {} : { commit: prepared.published.commit }),\n };\n }\n if (loaded.fresh === false && loaded.published !== undefined)\n source.discard?.(loaded.published.commit);\n }\n\n async function prepareFrom(loaded: Loaded): Promise<Prepared> {\n const walked = loaded.walk;\n const published = loaded.published;\n await yieldToLoop();\n const options: LoadOptions = {\n ...deps.load,\n walkRefusals: walked.refusals,\n hiddenPaths: walked.hidden,\n hiddenFolders: walked.hiddenFolders,\n };\n if (walked.fatal !== undefined) options.walkFatal = walked.fatal;\n const now = deps.clock();\n const { catalog, report } = loadBundle(deps.company, walked.files, options, now);\n await yieldToLoop();\n const withPublished = {\n ...(published === undefined ? {} : { published }),\n ...(loaded.fresh === undefined ? {} : { fresh: loaded.fresh }),\n };\n if (report.fatal !== undefined)\n return { kind: \"fatal\", catalog, report, now, ...withPublished };\n const docs = [...catalog.pages.values()].map(deriveDocument);\n await yieldToLoop();\n return { kind: \"docs\", catalog, report, docs, now, ...withPublished };\n }\n\n const whenDrained = (): Promise<void> =>\n active === 0 ? Promise.resolve() : new Promise((resolve) => drainWaiters.push(resolve));\n const whenNoneRequested = (): Promise<void> =>\n requested === 0 ? Promise.resolve() : new Promise((resolve) => requestWaiters.push(resolve));\n\n function closeGate(): void {\n gate = new Promise((resolve) => {\n openGate = resolve;\n });\n }\n function releaseGate(): void {\n const open = openGate;\n gate = undefined;\n openGate = undefined;\n open?.();\n }\n\n /** Under the gate: no new lease starts, running ones finish, then the index is rebuilt and the generation swapped. */\n async function commit(prepared: Prepared & { kind: \"docs\" }): Promise<Generation> {\n const live = engine;\n if (live === undefined) throw new Error(\"the engine is not open\");\n closeGate();\n try {\n await whenDrained();\n let index: IndexResult;\n try {\n index = await live.index(prepared.docs);\n } catch (error) {\n // The engine may now hold the new tree while the old catalog stays served: put them back together. If\n // that fails too, nothing may be served until a refresh succeeds (D39).\n if (current !== undefined) {\n try {\n await live.index([...current.catalog.pages.values()].map(deriveDocument));\n } catch (again) {\n refusing = `the index could not be re-aligned with the served pages after a failed refresh (${(again as Error).message}); nothing is served until a refresh succeeds`;\n }\n }\n throw error;\n }\n const generation: Generation = {\n catalog: prepared.catalog,\n report: prepared.report,\n index,\n loadedAt: prepared.now,\n dev: deps.load.dev,\n integrity,\n ...(prepared.published === undefined ? {} : { published: prepared.published }),\n };\n current = generation;\n // A tree reused from disk (the first-load fallback) does not answer the refusal that made it necessary.\n if (prepared.fresh !== false) lastRefusal = undefined;\n if (generation.published !== undefined) source.served?.(generation.published.commit);\n return generation;\n } finally {\n releaseGate();\n }\n }\n\n const fatalGeneration = (next: Prepared & { kind: \"fatal\" }): Generation => ({\n catalog: next.catalog,\n report: next.report,\n index: EMPTY_INDEX,\n loadedAt: next.now,\n dev: deps.load.dev,\n integrity,\n ...(next.published === undefined ? {} : { published: next.published }),\n });\n\n /** The first load: prepare, load, publish. A loader refusal falls back to the tree the source last served (D43). */\n async function firstLoadBody(): Promise<Generation> {\n await prepare();\n const next = await prepareDocs();\n if (next.kind === \"fatal\") {\n lastAttempt = { at: deps.clock(), outcome: \"fatal\" };\n deps.log.error(\"load.fatal\", {\n rule: next.report.fatal?.rule,\n path: next.report.fatal?.path,\n detail: next.report.fatal?.detail,\n });\n const servedOnDisk = source.loadServed === undefined ? undefined : await source.loadServed();\n if (servedOnDisk !== undefined) {\n const served = await prepareFrom(servedOnDisk);\n if (served.kind === \"docs\") {\n const generation = await commit(served);\n deps.log.warn(\"load.served-previous\", {\n commit: generation.published?.commit,\n refused: next.published?.commit,\n });\n return generation;\n }\n }\n current = fatalGeneration(next);\n return current;\n }\n const generation = await commit(next);\n lastAttempt = { at: deps.clock(), outcome: \"swapped\" };\n deps.log.info(\"load.done\", counts(generation.report, generation.index));\n return generation;\n }\n\n function start(): void {\n if (firstLoad !== undefined || closed) return;\n firstLoad = (async () => {\n try {\n return await firstLoadBody();\n } catch (error) {\n refusing = (error as Error).message;\n firstLoadFailed = true;\n lastAttempt = { at: deps.clock(), outcome: \"failed\" };\n const detail = (error as { detail?: unknown }).detail;\n deps.log.error(\"serve.refusing\", {\n problem: refusing,\n ...(typeof detail === \"string\" ? { detail } : {}),\n });\n throw error;\n }\n })();\n // The handshake starts this load with nobody waiting on it; a failure is kept for `ready()` and `status()`\n // and must not surface as an unhandled rejection, which would end the process.\n firstLoad.catch(() => undefined);\n }\n\n async function ready(): Promise<Generation> {\n if (refusing !== undefined) throw new Error(refusing);\n start();\n if (firstLoad === undefined) throw new Error(\"the runtime is shut down\");\n return firstLoad;\n }\n\n /**\n * A lease is counted from the moment it is requested, so a shutdown that follows the request waits for it; it\n * becomes active only once it reads a generation, so a swap waits for readers and never for a call that is\n * itself waiting for the first load.\n */\n function lease<T>(fn: (generation: Generation, engine: Engine) => Promise<T>): Promise<T> {\n if (closed) return Promise.reject(new Error(\"the runtime is shut down\"));\n requested += 1;\n let reading = false;\n const release = (): void => {\n if (reading) {\n active -= 1;\n if (active === 0) {\n const waiters = drainWaiters;\n drainWaiters = [];\n for (const resolve of waiters) resolve();\n }\n }\n requested -= 1;\n if (requested === 0) {\n const waiters = requestWaiters;\n requestWaiters = [];\n for (const resolve of waiters) resolve();\n }\n };\n return (async () => {\n try {\n await ready();\n while (gate !== undefined) await gate;\n const generation = current;\n const live = engine;\n if (generation === undefined || live === undefined) {\n throw new Error(\"no generation is published\");\n }\n reading = true;\n active += 1;\n return await fn(generation, live);\n } finally {\n release();\n }\n })();\n }\n\n function refresh(): Promise<RefreshOutcome> {\n if (closed) return Promise.reject(new Error(\"the runtime is shut down\"));\n if (refreshing !== undefined) return refreshing;\n refreshing = (async (): Promise<RefreshOutcome> => {\n try {\n if (firstLoad === undefined) throw new Error(\"the first load has not started\");\n if (firstLoadFailed) {\n // A first load that failed (the network, the cache, the lock) is tried again from the start.\n firstLoadFailed = false;\n firstLoad = undefined;\n start();\n if (firstLoad === undefined) throw new Error(\"the runtime is shut down\");\n const generation: Generation = await firstLoad;\n refusing = undefined;\n return generation.report.fatal === undefined\n ? { outcome: \"swapped\", generation }\n : { outcome: \"fatal\", report: generation.report };\n }\n await firstLoad;\n const next = await prepareDocs();\n if (next.kind === \"fatal\") {\n lastAttempt = { at: deps.clock(), outcome: \"fatal\" };\n deps.log.error(\"refresh.fatal\", {\n rule: next.report.fatal?.rule,\n path: next.report.fatal?.path,\n detail: next.report.fatal?.detail,\n });\n return { outcome: \"fatal\", report: next.report };\n }\n const generation = await commit(next);\n refusing = undefined;\n lastAttempt = { at: deps.clock(), outcome: \"swapped\" };\n deps.log.info(\"refresh.done\", counts(generation.report, generation.index));\n return { outcome: \"swapped\", generation };\n } catch (error) {\n const message = (error as Error).message;\n lastAttempt = { at: deps.clock(), outcome: \"failed\" };\n const detail = (error as { detail?: unknown }).detail;\n deps.log.error(\"refresh.failed\", {\n error: message,\n ...(typeof detail === \"string\" ? { detail } : {}),\n });\n return { outcome: \"failed\", error: message };\n } finally {\n refreshing = undefined;\n }\n })();\n return refreshing;\n }\n\n function status(): RuntimeStatus {\n const result: RuntimeStatus = {\n lock,\n loaded: current !== undefined,\n fatal: current?.report.fatal !== undefined,\n ...(deps.extra?.() ?? {}),\n };\n if (lastAttempt !== undefined) result.lastAttempt = lastAttempt;\n if (lastRefusal !== undefined) result.lastRefusal = lastRefusal;\n if (refusing !== undefined) result.refusing = refusing;\n if (resetOnOpen !== undefined) result.resetOnOpen = resetOnOpen;\n return result;\n }\n\n function shutdown(): Promise<void> {\n if (closing !== undefined) return closing;\n closed = true;\n closing = (async () => {\n source.abort?.();\n if (firstLoad !== undefined) await firstLoad.catch(() => undefined);\n if (refreshing !== undefined) await refreshing.catch(() => undefined);\n await whenNoneRequested();\n if (engine !== undefined) await engine.close();\n })();\n return closing;\n }\n\n return { start, ready, lease, refresh, status, shutdown };\n}\n"]}
|