@nylorun/runtime 0.10.0-beta → 0.11.0-beta

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.
Files changed (190) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +53 -51
  3. package/dist/adapters/execution/restate.d.ts +130 -0
  4. package/dist/adapters/execution/restate.js +442 -0
  5. package/dist/adapters/sandbox/virtual.js +1 -1
  6. package/dist/adapters/streams/s2.d.ts +18 -0
  7. package/dist/adapters/streams/s2.js +361 -0
  8. package/dist/core/executors.d.ts +4 -1
  9. package/dist/core/executors.js +20 -1
  10. package/dist/core/flow-host.d.ts +93 -132
  11. package/dist/core/flow-host.js +282 -547
  12. package/dist/core/sandbox-routes.d.ts +8 -3
  13. package/dist/core/sandbox-routes.js +10 -10
  14. package/dist/execution/memory.d.ts +54 -0
  15. package/dist/execution/memory.js +217 -0
  16. package/dist/execution/types.d.ts +125 -0
  17. package/dist/execution/types.js +52 -0
  18. package/dist/host/config-for.d.ts +2 -1
  19. package/dist/host/config-for.js +3 -21
  20. package/dist/host/config.d.ts +0 -8
  21. package/dist/host/create-host.d.ts +36 -1
  22. package/dist/host/create-host.js +48 -24
  23. package/dist/host/execution.d.ts +61 -0
  24. package/dist/host/execution.js +69 -0
  25. package/dist/host/http.d.ts +4 -0
  26. package/dist/host/http.js +6 -0
  27. package/dist/host/index.d.ts +3 -1
  28. package/dist/host/index.js +2 -0
  29. package/dist/host/main.js +106 -47
  30. package/dist/host/stack-config.d.ts +72 -0
  31. package/dist/host/stack-config.js +193 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/infra/database.d.ts +16 -0
  34. package/dist/infra/database.js +31 -0
  35. package/dist/infra/execution.d.ts +39 -0
  36. package/dist/infra/execution.js +68 -0
  37. package/dist/infra/index.d.ts +42 -0
  38. package/dist/infra/index.js +56 -0
  39. package/dist/infra/readiness.d.ts +33 -0
  40. package/dist/infra/readiness.js +53 -0
  41. package/dist/infra/streams.d.ts +17 -0
  42. package/dist/infra/streams.js +33 -0
  43. package/dist/infra/workers.d.ts +39 -0
  44. package/dist/infra/workers.js +31 -0
  45. package/dist/model/pi-model.d.ts +2 -2
  46. package/dist/model/pi-model.js +5 -5
  47. package/dist/sandbox/manager.d.ts +26 -8
  48. package/dist/sandbox/manager.js +95 -47
  49. package/dist/sandbox/select.d.ts +1 -1
  50. package/dist/sandbox/select.js +4 -5
  51. package/dist/sandbox/types.d.ts +2 -2
  52. package/dist/store/canonical.d.ts +5 -0
  53. package/dist/store/canonical.js +17 -0
  54. package/dist/store/cursor.d.ts +9 -0
  55. package/dist/store/cursor.js +18 -0
  56. package/dist/store/memory.d.ts +57 -0
  57. package/dist/store/memory.js +643 -0
  58. package/dist/store/ownership.d.ts +19 -0
  59. package/dist/store/ownership.js +33 -0
  60. package/dist/store/postgres/connect.d.ts +25 -0
  61. package/dist/store/postgres/connect.js +25 -0
  62. package/dist/store/postgres/locking.d.ts +16 -0
  63. package/dist/store/postgres/locking.js +22 -0
  64. package/dist/store/postgres/migrations/001_initial.d.ts +27 -0
  65. package/dist/store/postgres/migrations/001_initial.js +196 -0
  66. package/dist/store/postgres/migrations/index.d.ts +49 -0
  67. package/dist/store/postgres/migrations/index.js +75 -0
  68. package/dist/store/postgres/names.d.ts +11 -0
  69. package/dist/store/postgres/names.js +42 -0
  70. package/dist/store/postgres/store.d.ts +11 -0
  71. package/dist/store/postgres/store.js +790 -0
  72. package/dist/store/postgres/tenants.d.ts +90 -0
  73. package/dist/store/postgres/tenants.js +207 -0
  74. package/dist/store/types.d.ts +440 -0
  75. package/dist/store/types.js +10 -0
  76. package/dist/streams/basin.d.ts +23 -0
  77. package/dist/streams/basin.js +51 -0
  78. package/dist/streams/memory.d.ts +25 -0
  79. package/dist/streams/memory.js +124 -0
  80. package/dist/streams/relay.d.ts +37 -0
  81. package/dist/streams/relay.js +254 -0
  82. package/dist/streams/types.d.ts +115 -0
  83. package/dist/streams/types.js +86 -0
  84. package/dist/tenant/actions.d.ts +153 -0
  85. package/dist/tenant/actions.js +191 -0
  86. package/dist/tenant/advance.d.ts +15 -0
  87. package/dist/tenant/advance.js +439 -0
  88. package/dist/tenant/auth.d.ts +11 -0
  89. package/dist/tenant/auth.js +34 -0
  90. package/dist/tenant/commands.d.ts +3 -0
  91. package/dist/tenant/commands.js +384 -0
  92. package/dist/tenant/context.d.ts +140 -0
  93. package/dist/tenant/context.js +40 -0
  94. package/dist/tenant/effects.d.ts +20 -0
  95. package/dist/tenant/effects.js +539 -0
  96. package/dist/tenant/envelope.d.ts +0 -3
  97. package/dist/tenant/envelope.js +2 -34
  98. package/dist/tenant/ephemeral.d.ts +15 -4
  99. package/dist/tenant/ephemeral.js +106 -38
  100. package/dist/tenant/http.d.ts +32 -0
  101. package/dist/tenant/http.js +51 -0
  102. package/dist/tenant/live.d.ts +92 -0
  103. package/dist/tenant/live.js +290 -0
  104. package/dist/tenant/model-setting.d.ts +16 -0
  105. package/dist/tenant/model-setting.js +12 -0
  106. package/dist/tenant/module.d.ts +29 -8
  107. package/dist/tenant/module.js +162 -158
  108. package/dist/tenant/paths.d.ts +12 -8
  109. package/dist/tenant/paths.js +38 -12
  110. package/dist/tenant/principals.d.ts +9 -13
  111. package/dist/tenant/principals.js +31 -38
  112. package/dist/tenant/quarantine-error.d.ts +1 -1
  113. package/dist/tenant/quarantine-error.js +2 -8
  114. package/dist/tenant/quarantine.d.ts +0 -8
  115. package/dist/tenant/quarantine.js +8 -30
  116. package/dist/tenant/reset.d.ts +7 -6
  117. package/dist/tenant/reset.js +7 -51
  118. package/dist/tenant/routes-tenant.d.ts +12 -0
  119. package/dist/tenant/routes-tenant.js +126 -0
  120. package/dist/tenant/routes.d.ts +9 -0
  121. package/dist/tenant/routes.js +142 -0
  122. package/dist/tenant/runtime.d.ts +59 -58
  123. package/dist/tenant/runtime.js +243 -2201
  124. package/dist/tenant/scheduler.d.ts +25 -0
  125. package/dist/tenant/scheduler.js +84 -0
  126. package/dist/tenant/session.d.ts +60 -0
  127. package/dist/tenant/session.js +94 -0
  128. package/dist/tenant/sessions.d.ts +34 -0
  129. package/dist/tenant/sessions.js +149 -0
  130. package/dist/tenant/status.d.ts +13 -4
  131. package/dist/tenant/status.js +76 -41
  132. package/dist/tenant/store-memory.d.ts +9 -12
  133. package/dist/tenant/store-memory.js +79 -68
  134. package/dist/tenant/store-pg.d.ts +15 -0
  135. package/dist/tenant/store-pg.js +140 -0
  136. package/dist/tenant/streams.d.ts +126 -0
  137. package/dist/tenant/streams.js +419 -0
  138. package/dist/tenant/sweep.d.ts +15 -0
  139. package/dist/tenant/sweep.js +122 -0
  140. package/dist/tenant/types.d.ts +95 -15
  141. package/dist/tenant/types.js +23 -1
  142. package/dist/tenant/worker.d.ts +84 -0
  143. package/dist/tenant/worker.js +189 -0
  144. package/dist/vault/service.d.ts +37 -31
  145. package/dist/vault/service.js +439 -359
  146. package/dist/version.d.ts +1 -1
  147. package/dist/version.js +1 -1
  148. package/package.json +13 -13
  149. package/dist/adapters/sandbox/microsandbox.d.ts +0 -2
  150. package/dist/adapters/sandbox/microsandbox.js +0 -238
  151. package/dist/core/store.d.ts +0 -32
  152. package/dist/core/store.js +0 -143
  153. package/dist/launcher/commands.d.ts +0 -24
  154. package/dist/launcher/commands.js +0 -260
  155. package/dist/launcher/ensure-config.d.ts +0 -16
  156. package/dist/launcher/ensure-config.js +0 -79
  157. package/dist/launcher/errors.d.ts +0 -12
  158. package/dist/launcher/errors.js +0 -22
  159. package/dist/launcher/host-config.d.ts +0 -17
  160. package/dist/launcher/host-config.js +0 -64
  161. package/dist/launcher/host-state.d.ts +0 -17
  162. package/dist/launcher/host-state.js +0 -61
  163. package/dist/launcher/lifecycle.d.ts +0 -66
  164. package/dist/launcher/lifecycle.js +0 -407
  165. package/dist/launcher/locks.d.ts +0 -26
  166. package/dist/launcher/locks.js +0 -117
  167. package/dist/launcher/logs.d.ts +0 -18
  168. package/dist/launcher/logs.js +0 -117
  169. package/dist/launcher/main.d.ts +0 -2
  170. package/dist/launcher/main.js +0 -66
  171. package/dist/launcher/output.d.ts +0 -11
  172. package/dist/launcher/output.js +0 -68
  173. package/dist/launcher/paths.d.ts +0 -20
  174. package/dist/launcher/paths.js +0 -36
  175. package/dist/launcher/probe.d.ts +0 -23
  176. package/dist/launcher/probe.js +0 -72
  177. package/dist/launcher/protocol.d.ts +0 -62
  178. package/dist/launcher/protocol.js +0 -1
  179. package/dist/launcher/schema-guard.d.ts +0 -6
  180. package/dist/launcher/schema-guard.js +0 -36
  181. package/dist/launcher/spawn.d.ts +0 -22
  182. package/dist/launcher/spawn.js +0 -85
  183. package/dist/launcher/status.d.ts +0 -6
  184. package/dist/launcher/status.js +0 -65
  185. package/dist/tenant/migration.d.ts +0 -16
  186. package/dist/tenant/migration.js +0 -116
  187. package/dist/tenant/schema.d.ts +0 -23
  188. package/dist/tenant/schema.js +0 -151
  189. package/dist/tenant/store-fs.d.ts +0 -16
  190. package/dist/tenant/store-fs.js +0 -205
@@ -1,407 +0,0 @@
1
- import { existsSync, renameSync, statSync } from "node:fs";
2
- import { readFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { compareVersions, PROTOCOL_HEADER, PROTOCOL_VERSION, } from "@nylorun/core/compatibility";
5
- import { baselineEnvironment, hostProcessEnvironment, } from "../host/environment.js";
6
- import { LauncherError } from "./errors.js";
7
- import { ensureHostConfig } from "./ensure-config.js";
8
- import { writeHostConfig } from "./host-config.js";
9
- import { clearHostState, ensureHostCredentials, readHostCredentials, readHostState, writeHostState, } from "./host-state.js";
10
- import { LIFECYCLE_LOCK_WAIT_MS, processAlive, withProcessLock, } from "./locks.js";
11
- import { hostUrl } from "./paths.js";
12
- import { probeHostHealth, probeReady } from "./probe.js";
13
- import { assertSchemaGuard } from "./schema-guard.js";
14
- import { forceKillHost, spawnHostProcess } from "./spawn.js";
15
- import { status } from "./status.js";
16
- const READY_TIMEOUT_MS = 30_000;
17
- const LOG_LIMIT_BYTES = 10 * 1024 * 1024;
18
- const DOWN_KILL_WAIT_MS = 30_000;
19
- const WAIT_IDLE_DEFAULT_MS = 300_000;
20
- function emitProgress(ctx, phase, message) {
21
- ctx.emit?.({
22
- type: "progress",
23
- phase,
24
- ...(message ? { message } : {}),
25
- });
26
- }
27
- function rotateLog(path) {
28
- try {
29
- if (statSync(path).size > LOG_LIMIT_BYTES) {
30
- renameSync(path, `${path}.1`);
31
- }
32
- }
33
- catch {
34
- /* no log yet */
35
- }
36
- }
37
- async function logTail(path, lines = 40) {
38
- try {
39
- const text = await readFile(path, "utf8");
40
- return text.trimEnd().split("\n").slice(-lines).join("\n");
41
- }
42
- catch {
43
- return "";
44
- }
45
- }
46
- /**
47
- * Refuse to start an older Runtime than the one that last ran this Host root,
48
- * unless the caller confirmed the downgrade.
49
- */
50
- function assertNotDowngrade(recorded, target, allowDowngrade) {
51
- if (!recorded || allowDowngrade)
52
- return;
53
- if (compareVersions(target, recorded) >= 0)
54
- return;
55
- throw new LauncherError("downgrade_refused", `This Host root last ran Runtime ${recorded}; the installed Runtime is ${target}.`, `Install ${recorded} or newer (npm install --global @nylorun/runtime@${recorded}), or pass --allow-downgrade after confirming Tenants can open on the older schema.`);
56
- }
57
- async function awaitHostReady(url, logPath, childPid) {
58
- const deadline = Date.now() + READY_TIMEOUT_MS;
59
- while (Date.now() < deadline) {
60
- if (childPid !== undefined && !processAlive(childPid)) {
61
- const tail = await logTail(logPath);
62
- throw new LauncherError("host_start_failed", `The Runtime Host exited before becoming ready.${tail ? `\n${tail}` : ""}`, 'Inspect "nylorun-runtime logs" and retry up.', { logExcerpt: tail });
63
- }
64
- if (await probeReady(url, 500))
65
- return;
66
- const probe = await probeHostHealth(url, 500);
67
- if (probe.health)
68
- return;
69
- await new Promise((r) => setTimeout(r, 200));
70
- }
71
- const tail = await logTail(logPath);
72
- throw new LauncherError("host_start_failed", `The Runtime Host did not become ready within 30 seconds.${tail ? `\n${tail}` : ""}`, 'Inspect "nylorun-runtime logs" and retry up.', { logExcerpt: tail });
73
- }
74
- function adminHeaders(adminKey) {
75
- return {
76
- Authorization: `Bearer ${adminKey}`,
77
- [PROTOCOL_HEADER]: String(PROTOCOL_VERSION),
78
- Accept: "application/json",
79
- };
80
- }
81
- async function fetchAdminAggregate(url, adminKey, fetchImpl) {
82
- try {
83
- const response = await fetchImpl(`${url}/v1/admin/status`, {
84
- headers: adminHeaders(adminKey),
85
- signal: AbortSignal.timeout(5_000),
86
- });
87
- if (!response.ok)
88
- return undefined;
89
- const body = (await response.json());
90
- return body.aggregate;
91
- }
92
- catch {
93
- return undefined;
94
- }
95
- }
96
- async function requestShutdown(url, adminKey, fetchImpl) {
97
- try {
98
- await fetchImpl(`${url}/v1/admin/host/shutdown`, {
99
- method: "POST",
100
- headers: adminHeaders(adminKey),
101
- signal: AbortSignal.timeout(5_000),
102
- });
103
- }
104
- catch {
105
- /* fall through to signal/kill */
106
- }
107
- }
108
- /**
109
- * Start the local Host (D§9.6). Never restarts a running Host.
110
- */
111
- export async function up(ctx, options = {}) {
112
- return withProcessLock(ctx.paths.lifecycleLock, ctx.lock?.waitMs ?? LIFECYCLE_LOCK_WAIT_MS, async () => upLocked(ctx, options), ctx.lock);
113
- }
114
- async function upLocked(ctx, options) {
115
- const { config } = await ensureHostConfig(ctx.paths, {
116
- ...(options.port !== undefined ? { port: options.port } : {}),
117
- });
118
- await ensureHostCredentials(ctx.paths);
119
- const before = await status(ctx.paths, ctx.runtimeVersion);
120
- if (before.state === "running" && !options.foreground) {
121
- return {
122
- url: before.url,
123
- hostId: before.hostId ?? config.hostId,
124
- pid: before.pid,
125
- version: before.version ?? before.runtimeVersion ?? "unknown",
126
- started: false,
127
- };
128
- }
129
- if (before.state === "foreign-port") {
130
- throw new LauncherError("foreign_port", `Port ${config.port} on ${config.host} is in use by another process.`, 'Choose a free port with "nylorun-runtime up --port <n>", or stop the other process.');
131
- }
132
- if (before.state === "unresponsive") {
133
- throw new LauncherError("host_unresponsive", `A Host process is running (pid ${before.pid}) but is not answering ${before.url}.`, 'Inspect it with "nylorun-runtime logs", then stop it with "nylorun-runtime down".', { pid: before.pid });
134
- }
135
- // Clear stale state before start.
136
- const stateFile = await readHostState(ctx.paths);
137
- if (stateFile && !processAlive(stateFile.pid)) {
138
- await clearHostState(ctx.paths);
139
- }
140
- const target = ctx.runtimeVersion;
141
- assertNotDowngrade(typeof config.runtimeVersion === "string" ? config.runtimeVersion : undefined, target, options.allowDowngrade);
142
- assertSchemaGuard(ctx.paths, ctx.tenantSchemaMax);
143
- const entry = ctx.hostEntry;
144
- const nodeBinary = ctx.nodeBinary;
145
- if (!existsSync(entry)) {
146
- throw new LauncherError("host_start_failed", `The installed Runtime has no Host entry at ${entry}.`, `Reinstall it: npm install --global @nylorun/runtime@${target}`);
147
- }
148
- const baseline = baselineEnvironment(ctx.baselineEnv);
149
- const environment = hostProcessEnvironment(baseline, config, {
150
- root: ctx.paths.root,
151
- home: ctx.paths.home,
152
- tmp: ctx.paths.tmp,
153
- });
154
- const devModel = ctx.baselineEnv.NYLORUN_DEV_MODEL?.trim();
155
- if (devModel)
156
- environment.NYLORUN_DEV_MODEL = devModel;
157
- const spawnFn = ctx.spawnHost ?? spawnHostProcess;
158
- const url = hostUrl(config);
159
- if (options.foreground) {
160
- emitProgress(ctx, "start");
161
- const child = spawnFn({
162
- nodeBinary,
163
- entry,
164
- environment,
165
- cwd: ctx.paths.home,
166
- detached: false,
167
- platform: ctx.platform,
168
- });
169
- await awaitHostReady(url, ctx.paths.log, child.pid);
170
- await recordReady(ctx.paths, config, child.pid, target, entry);
171
- return {
172
- url,
173
- hostId: config.hostId,
174
- pid: child.pid,
175
- version: target,
176
- started: true,
177
- };
178
- }
179
- rotateLog(ctx.paths.log);
180
- emitProgress(ctx, "start");
181
- const child = spawnFn({
182
- nodeBinary,
183
- entry,
184
- environment,
185
- cwd: ctx.paths.home,
186
- detached: true,
187
- logPath: ctx.paths.log,
188
- platform: ctx.platform,
189
- });
190
- try {
191
- await awaitHostReady(url, ctx.paths.log, child.pid);
192
- }
193
- catch (error) {
194
- if (child.pid)
195
- forceKillHost(child.pid, ctx.platform);
196
- throw error;
197
- }
198
- child.unref();
199
- await recordReady(ctx.paths, config, child.pid, target, entry);
200
- return {
201
- url,
202
- hostId: config.hostId,
203
- pid: child.pid,
204
- version: target,
205
- started: true,
206
- };
207
- }
208
- async function recordReady(paths, config, pid, version, entry) {
209
- const url = hostUrl(config);
210
- await writeHostState(paths, {
211
- pid,
212
- startedAt: new Date().toISOString(),
213
- version,
214
- entry,
215
- url,
216
- });
217
- // runtimeVersion changes only after ready (D§9.6 / D§9.8).
218
- await writeHostConfig(paths, {
219
- ...config,
220
- format: 1,
221
- runtimeVersion: version,
222
- });
223
- }
224
- /**
225
- * Stop the local Host (D§9.7).
226
- */
227
- export async function down(ctx, options = {}) {
228
- return withProcessLock(ctx.paths.lifecycleLock, ctx.lock?.waitMs ?? LIFECYCLE_LOCK_WAIT_MS, async () => downLocked(ctx, options), ctx.lock);
229
- }
230
- async function downLocked(ctx, options) {
231
- const current = await status(ctx.paths, ctx.runtimeVersion);
232
- const stateFile = await readHostState(ctx.paths);
233
- const pid = current.pid ?? stateFile?.pid;
234
- if (current.state === "absent" ||
235
- current.state === "stopped" ||
236
- (!pid && current.state !== "running" && current.state !== "unresponsive")) {
237
- await clearHostState(ctx.paths);
238
- return { stopped: false };
239
- }
240
- if (current.state === "foreign-port") {
241
- throw new LauncherError("foreign_port", `Port is serving a foreign process; refusing to stop it.`, "Stop the other process manually, or use a different --port.");
242
- }
243
- const fetchImpl = ctx.fetchImpl ?? fetch;
244
- const credentials = await readHostCredentials(ctx.paths);
245
- const url = current.url ?? stateFile?.url;
246
- if (!options.force && credentials && url && current.state === "running") {
247
- emitProgress(ctx, "wait");
248
- let aggregate = await fetchAdminAggregate(url, credentials.adminKey, fetchImpl);
249
- if (aggregate &&
250
- (aggregate.runningSessions > 0 || aggregate.connectedExecutors > 0)) {
251
- if (!options.wait) {
252
- throw new LauncherError("active_work", `Host has ${aggregate.runningSessions} running session(s) and ${aggregate.connectedExecutors} connected executor(s).`, 'Pass --wait to drain, or --force to stop immediately.', { aggregate });
253
- }
254
- const timeoutMs = options.timeoutMs ?? WAIT_IDLE_DEFAULT_MS;
255
- const deadline = Date.now() + timeoutMs;
256
- while (Date.now() < deadline) {
257
- aggregate = await fetchAdminAggregate(url, credentials.adminKey, fetchImpl);
258
- if (!aggregate ||
259
- (aggregate.runningSessions === 0 &&
260
- aggregate.connectedExecutors === 0)) {
261
- break;
262
- }
263
- await new Promise((r) => setTimeout(r, 1000));
264
- }
265
- if (aggregate &&
266
- (aggregate.runningSessions > 0 || aggregate.connectedExecutors > 0)) {
267
- throw new LauncherError("active_work", `Timed out waiting for the Host to become idle.`, "Retry with a longer --timeout, or pass --force.", { aggregate });
268
- }
269
- }
270
- }
271
- emitProgress(ctx, "stop");
272
- if (credentials && url) {
273
- await requestShutdown(url, credentials.adminKey, fetchImpl);
274
- }
275
- if (pid) {
276
- try {
277
- process.kill(pid, "SIGTERM");
278
- }
279
- catch (error) {
280
- if (error.code !== "ESRCH")
281
- throw error;
282
- }
283
- const deadline = Date.now() + DOWN_KILL_WAIT_MS;
284
- while (Date.now() < deadline) {
285
- if (!processAlive(pid)) {
286
- await clearHostState(ctx.paths);
287
- return { stopped: true };
288
- }
289
- await new Promise((r) => setTimeout(r, 100));
290
- }
291
- forceKillHost(pid, ctx.platform);
292
- }
293
- await clearHostState(ctx.paths);
294
- return { stopped: true };
295
- }
296
- /**
297
- * Check the installed Runtime can take over, stop, start (D§9.8).
298
- * runtimeVersion updates only after ready.
299
- */
300
- export async function restart(ctx, options = {}) {
301
- return withProcessLock(ctx.paths.lifecycleLock, ctx.lock?.waitMs ?? LIFECYCLE_LOCK_WAIT_MS, async () => {
302
- const config = (await ensureHostConfig(ctx.paths, {
303
- ...(options.port !== undefined ? { port: options.port } : {}),
304
- })).config;
305
- const target = ctx.runtimeVersion;
306
- // Check before touching the running Host.
307
- assertNotDowngrade(typeof config.runtimeVersion === "string"
308
- ? config.runtimeVersion
309
- : undefined, target, options.allowDowngrade);
310
- assertSchemaGuard(ctx.paths, ctx.tenantSchemaMax);
311
- await downLocked(ctx, {
312
- wait: options.wait,
313
- force: options.force ?? true,
314
- ...(options.timeoutMs !== undefined
315
- ? { timeoutMs: options.timeoutMs }
316
- : {}),
317
- });
318
- try {
319
- return await upLocked(ctx, {
320
- allowDowngrade: true,
321
- ...(options.port !== undefined ? { port: options.port } : {}),
322
- });
323
- }
324
- catch (error) {
325
- if (error instanceof LauncherError && error.code === "host_start_failed") {
326
- const tail = typeof error.details === "object" &&
327
- error.details &&
328
- "logExcerpt" in error.details
329
- ? String(error.details.logExcerpt ?? "")
330
- : await logTail(ctx.paths.log);
331
- throw new LauncherError("upgrade_failed", `Failed to start Runtime ${target} after stop.${tail ? `\n${tail}` : ""}`, "The previous Host is stopped. Fix the issue, or reinstall the last good version (npm install --global @nylorun/runtime@<version>) and run up.", { version: target, logExcerpt: tail });
332
- }
333
- throw error;
334
- }
335
- }, ctx.lock);
336
- }
337
- /**
338
- * Foreground Host: print up result once ready; stop on SIGINT/SIGTERM (D§9.3).
339
- */
340
- export async function run(ctx, options = {}) {
341
- return withProcessLock(ctx.paths.lifecycleLock, ctx.lock?.waitMs ?? LIFECYCLE_LOCK_WAIT_MS, async () => {
342
- const result = await upLocked(ctx, {
343
- port: options.port,
344
- allowDowngrade: options.allowDowngrade,
345
- foreground: true,
346
- });
347
- options.onReady?.(result);
348
- const state = await readHostState(ctx.paths);
349
- const pid = state?.pid ?? result.pid;
350
- await new Promise((resolvePromise) => {
351
- let settled = false;
352
- const finish = async () => {
353
- if (settled)
354
- return;
355
- settled = true;
356
- cleanup();
357
- clearInterval(poll);
358
- try {
359
- if (pid)
360
- process.kill(pid, "SIGTERM");
361
- }
362
- catch {
363
- /* gone */
364
- }
365
- const deadline = Date.now() + DOWN_KILL_WAIT_MS;
366
- while (Date.now() < deadline && pid && processAlive(pid)) {
367
- await new Promise((r) => setTimeout(r, 100));
368
- }
369
- if (pid && processAlive(pid))
370
- forceKillHost(pid, ctx.platform);
371
- await clearHostState(ctx.paths);
372
- resolvePromise();
373
- };
374
- const handler = () => {
375
- void finish();
376
- };
377
- let remove;
378
- if (ctx.onSignal) {
379
- remove = ctx.onSignal(handler);
380
- }
381
- else {
382
- process.on("SIGINT", handler);
383
- process.on("SIGTERM", handler);
384
- remove = () => {
385
- process.off("SIGINT", handler);
386
- process.off("SIGTERM", handler);
387
- };
388
- }
389
- const cleanup = () => remove();
390
- const poll = setInterval(() => {
391
- if (pid && !processAlive(pid)) {
392
- void (async () => {
393
- if (settled)
394
- return;
395
- settled = true;
396
- cleanup();
397
- clearInterval(poll);
398
- await clearHostState(ctx.paths);
399
- resolvePromise();
400
- })();
401
- }
402
- }, 250);
403
- poll.unref();
404
- });
405
- return result;
406
- }, ctx.lock);
407
- }
@@ -1,26 +0,0 @@
1
- export interface ProcessLock {
2
- pid: number;
3
- startTime: string;
4
- }
5
- export interface LockOptions {
6
- /** Poll interval while waiting (ms). */
7
- pollMs?: number;
8
- /** Max wait for another holder (ms). */
9
- waitMs?: number;
10
- }
11
- declare function processStartTime(pid: number): string | undefined;
12
- export declare function processAlive(pid: number): boolean;
13
- declare function tryAcquireLock(path: string): boolean;
14
- declare function releaseLock(path: string): void;
15
- declare function recoverStaleLock(path: string): void;
16
- /**
17
- * Hold a PID+start-identity lock. Stale when the holder process is gone.
18
- * Times out with `lock_timeout`.
19
- */
20
- export declare function withProcessLock<T>(path: string, waitMs: number, body: () => Promise<T>, options?: LockOptions): Promise<T>;
21
- export declare const LIFECYCLE_LOCK_WAIT_MS = 60000;
22
- export declare function readProcessLock(path: string): ProcessLock | undefined;
23
- export declare function lockFileExists(path: string): boolean;
24
- /** Test helper: force-write a lock as if held by `pid` with optional startTime. */
25
- export declare function writeProcessLockForTest(path: string, lock: ProcessLock): void;
26
- export { processStartTime, recoverStaleLock, releaseLock, tryAcquireLock, };
@@ -1,117 +0,0 @@
1
- import { closeSync, existsSync, openSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
2
- import { LauncherError } from "./errors.js";
3
- function processStartTime(pid) {
4
- try {
5
- const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
6
- const closeParen = stat.lastIndexOf(")");
7
- if (closeParen < 0)
8
- return undefined;
9
- const fields = stat.slice(closeParen + 2).split(" ");
10
- return fields[19];
11
- }
12
- catch {
13
- return undefined;
14
- }
15
- }
16
- export function processAlive(pid) {
17
- try {
18
- process.kill(pid, 0);
19
- return true;
20
- }
21
- catch (error) {
22
- return error.code !== "ESRCH";
23
- }
24
- }
25
- function readLock(path) {
26
- try {
27
- const value = JSON.parse(readFileSync(path, "utf8"));
28
- if (typeof value?.pid === "number" && typeof value.startTime === "string") {
29
- return value;
30
- }
31
- return undefined;
32
- }
33
- catch {
34
- return undefined;
35
- }
36
- }
37
- function lockIsLive(lock) {
38
- if (!processAlive(lock.pid))
39
- return false;
40
- const start = processStartTime(lock.pid);
41
- // When /proc is unavailable, treat a live PID as holding the lock.
42
- if (start === undefined)
43
- return true;
44
- return start === lock.startTime;
45
- }
46
- function tryAcquireLock(path) {
47
- try {
48
- const fd = openSync(path, "wx", 0o600);
49
- try {
50
- const startTime = processStartTime(process.pid) ?? String(Date.now());
51
- writeFileSync(fd, JSON.stringify({ pid: process.pid, startTime }));
52
- }
53
- finally {
54
- closeSync(fd);
55
- }
56
- return true;
57
- }
58
- catch (error) {
59
- if (error.code === "EEXIST")
60
- return false;
61
- throw error;
62
- }
63
- }
64
- function releaseLock(path) {
65
- try {
66
- unlinkSync(path);
67
- }
68
- catch {
69
- /* gone */
70
- }
71
- }
72
- function recoverStaleLock(path) {
73
- const lock = readLock(path);
74
- if (!lock || lockIsLive(lock))
75
- return;
76
- releaseLock(path);
77
- }
78
- /**
79
- * Hold a PID+start-identity lock. Stale when the holder process is gone.
80
- * Times out with `lock_timeout`.
81
- */
82
- export async function withProcessLock(path, waitMs, body, options = {}) {
83
- const pollMs = options.pollMs ?? 100;
84
- const deadline = Date.now() + waitMs;
85
- for (;;) {
86
- recoverStaleLock(path);
87
- if (tryAcquireLock(path)) {
88
- try {
89
- return await body();
90
- }
91
- finally {
92
- releaseLock(path);
93
- }
94
- }
95
- while (Date.now() < deadline) {
96
- const held = readLock(path);
97
- if (!held || !lockIsLive(held))
98
- break;
99
- await new Promise((r) => setTimeout(r, pollMs));
100
- }
101
- if (Date.now() >= deadline) {
102
- throw new LauncherError("lock_timeout", `Timed out waiting for lock ${path}.`, "Retry after the other launcher process finishes, or remove a stale lock if no launcher is running.", { path, waitMs });
103
- }
104
- }
105
- }
106
- export const LIFECYCLE_LOCK_WAIT_MS = 60_000;
107
- export function readProcessLock(path) {
108
- return readLock(path);
109
- }
110
- export function lockFileExists(path) {
111
- return existsSync(path);
112
- }
113
- /** Test helper: force-write a lock as if held by `pid` with optional startTime. */
114
- export function writeProcessLockForTest(path, lock) {
115
- writeFileSync(path, JSON.stringify(lock), { mode: 0o600 });
116
- }
117
- export { processStartTime, recoverStaleLock, releaseLock, tryAcquireLock, };
@@ -1,18 +0,0 @@
1
- import type { HostPaths } from "./paths.js";
2
- import type { LauncherEvent } from "./protocol.js";
3
- export interface LogsOptions {
4
- lines?: number;
5
- follow?: boolean;
6
- tenant?: string;
7
- all?: boolean;
8
- /** Called for each log line (JSON mode) or text line. */
9
- emit: (event: Extract<LauncherEvent, {
10
- type: "log";
11
- }>) => void;
12
- /** When set, follow until this aborts. */
13
- signal?: AbortSignal;
14
- }
15
- /**
16
- * Read Host and Tenant log files (D§9.3). File reads only; no Host calls.
17
- */
18
- export declare function logs(paths: HostPaths, options: LogsOptions): Promise<void>;
@@ -1,117 +0,0 @@
1
- import { existsSync, readdirSync, statSync } from "node:fs";
2
- import { open, readFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { LauncherError } from "./errors.js";
5
- function collectSources(paths, options) {
6
- const sources = [];
7
- if (!options.tenant) {
8
- sources.push({ path: paths.log, source: "host" });
9
- }
10
- if (options.tenant) {
11
- sources.push({
12
- path: join(paths.tenants, options.tenant, "logs", "tenant.log"),
13
- source: "tenant",
14
- tenantId: options.tenant,
15
- });
16
- return sources;
17
- }
18
- if (!options.all)
19
- return sources;
20
- let tenantDirs = [];
21
- try {
22
- tenantDirs = readdirSync(paths.tenants);
23
- }
24
- catch {
25
- return sources;
26
- }
27
- for (const id of tenantDirs) {
28
- if (id.startsWith("."))
29
- continue;
30
- const logPath = join(paths.tenants, id, "logs", "tenant.log");
31
- if (!existsSync(logPath))
32
- continue;
33
- sources.push({ path: logPath, source: "tenant", tenantId: id });
34
- }
35
- return sources;
36
- }
37
- /**
38
- * Read Host and Tenant log files (D§9.3). File reads only; no Host calls.
39
- */
40
- export async function logs(paths, options) {
41
- const lineCount = options.lines ?? 200;
42
- const sources = collectSources(paths, options);
43
- if (sources.length === 0 || !sources.some((s) => existsSync(s.path))) {
44
- throw new LauncherError("not_found", `No Runtime log under ${paths.root}.`, 'Start a Host with "nylorun-runtime up", then retry.');
45
- }
46
- for (const source of sources) {
47
- if (!existsSync(source.path))
48
- continue;
49
- const text = await readFile(source.path, "utf8");
50
- const slice = text.trimEnd().split("\n").slice(-lineCount);
51
- for (const line of slice) {
52
- options.emit({
53
- type: "log",
54
- source: source.source,
55
- ...(source.tenantId ? { tenantId: source.tenantId } : {}),
56
- line,
57
- });
58
- }
59
- }
60
- if (!options.follow)
61
- return;
62
- const cursors = new Map();
63
- for (const source of sources) {
64
- try {
65
- cursors.set(source.path, statSync(source.path).size);
66
- }
67
- catch {
68
- cursors.set(source.path, 0);
69
- }
70
- }
71
- await new Promise((resolvePromise) => {
72
- const stop = () => {
73
- clearInterval(poll);
74
- resolvePromise();
75
- };
76
- if (options.signal) {
77
- if (options.signal.aborted) {
78
- stop();
79
- return;
80
- }
81
- options.signal.addEventListener("abort", stop, { once: true });
82
- }
83
- const poll = setInterval(async () => {
84
- for (const source of collectSources(paths, options)) {
85
- let next = 0;
86
- try {
87
- next = statSync(source.path).size;
88
- }
89
- catch {
90
- continue;
91
- }
92
- const size = cursors.get(source.path) ?? 0;
93
- if (next <= size) {
94
- cursors.set(source.path, next);
95
- continue;
96
- }
97
- const handle = await open(source.path, "r");
98
- const buffer = Buffer.alloc(next - size);
99
- await handle.read(buffer, 0, buffer.length, size);
100
- await handle.close();
101
- cursors.set(source.path, next);
102
- const chunk = buffer.toString("utf8");
103
- for (const line of chunk.split("\n")) {
104
- if (line === "" && !chunk.endsWith("\n"))
105
- continue;
106
- options.emit({
107
- type: "log",
108
- source: source.source,
109
- ...(source.tenantId ? { tenantId: source.tenantId } : {}),
110
- line,
111
- });
112
- }
113
- }
114
- }, 500);
115
- poll.unref();
116
- });
117
- }
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export declare function main(argv?: readonly string[], env?: NodeJS.ProcessEnv): Promise<number>;