@amalgm/apps 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/PURPOSE.md +98 -0
- package/README.md +61 -0
- package/dist/apps.d.ts +58 -0
- package/dist/apps.js +274 -0
- package/dist/apps.js.map +1 -0
- package/dist/bin/apps.d.ts +2 -0
- package/dist/bin/apps.js +6 -0
- package/dist/bin/apps.js.map +1 -0
- package/dist/bin/mcp.d.ts +2 -0
- package/dist/bin/mcp.js +15 -0
- package/dist/bin/mcp.js.map +1 -0
- package/dist/boundaries.d.ts +11 -0
- package/dist/boundaries.js +55 -0
- package/dist/boundaries.js.map +1 -0
- package/dist/changes.d.ts +14 -0
- package/dist/changes.js +23 -0
- package/dist/changes.js.map +1 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.d.ts +3 -0
- package/dist/command.js +40 -0
- package/dist/command.js.map +1 -0
- package/dist/faults.d.ts +21 -0
- package/dist/faults.js +59 -0
- package/dist/faults.js.map +1 -0
- package/dist/generations.d.ts +28 -0
- package/dist/generations.js +83 -0
- package/dist/generations.js.map +1 -0
- package/dist/health.d.ts +35 -0
- package/dist/health.js +116 -0
- package/dist/health.js.map +1 -0
- package/dist/identity.d.ts +10 -0
- package/dist/identity.js +59 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/lease.d.ts +30 -0
- package/dist/lease.js +82 -0
- package/dist/lease.js.map +1 -0
- package/dist/logs.d.ts +11 -0
- package/dist/logs.js +40 -0
- package/dist/logs.js.map +1 -0
- package/dist/manifest.d.ts +6 -0
- package/dist/manifest.js +105 -0
- package/dist/manifest.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.js +65 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.js +171 -0
- package/dist/mcp.js.map +1 -0
- package/dist/operations.d.ts +10 -0
- package/dist/operations.js +31 -0
- package/dist/operations.js.map +1 -0
- package/dist/ownership.d.ts +11 -0
- package/dist/ownership.js +75 -0
- package/dist/ownership.js.map +1 -0
- package/dist/package.d.ts +20 -0
- package/dist/package.js +204 -0
- package/dist/package.js.map +1 -0
- package/dist/presentation.d.ts +9 -0
- package/dist/presentation.js +63 -0
- package/dist/presentation.js.map +1 -0
- package/dist/process.d.ts +10 -0
- package/dist/process.js +68 -0
- package/dist/process.js.map +1 -0
- package/dist/reconciler.d.ts +27 -0
- package/dist/reconciler.js +103 -0
- package/dist/reconciler.js.map +1 -0
- package/dist/releases.d.ts +26 -0
- package/dist/releases.js +108 -0
- package/dist/releases.js.map +1 -0
- package/dist/restarts.d.ts +40 -0
- package/dist/restarts.js +137 -0
- package/dist/restarts.js.map +1 -0
- package/dist/runtime.d.ts +19 -0
- package/dist/runtime.js +57 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema.d.ts +3 -0
- package/dist/schema.js +80 -0
- package/dist/schema.js.map +1 -0
- package/dist/source-import.d.ts +4 -0
- package/dist/source-import.js +77 -0
- package/dist/source-import.js.map +1 -0
- package/dist/store.d.ts +29 -0
- package/dist/store.js +221 -0
- package/dist/store.js.map +1 -0
- package/dist/supervisor.d.ts +29 -0
- package/dist/supervisor.js +279 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/types.d.ts +264 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/ENGINE_INTEGRATION.md +68 -0
- package/docs/SUPERVISION.md +73 -0
- package/package.json +59 -0
package/dist/changes.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { observe, report } from './boundaries.js';
|
|
2
|
+
class Changes {
|
|
3
|
+
options;
|
|
4
|
+
publishRoutes;
|
|
5
|
+
queue = Promise.resolve();
|
|
6
|
+
constructor(options, publishRoutes) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.publishRoutes = publishRoutes;
|
|
9
|
+
}
|
|
10
|
+
publish(change) {
|
|
11
|
+
this.queue = this.queue
|
|
12
|
+
.then(async () => {
|
|
13
|
+
await observe(this.options.onChange, [change], this.options.onError, 'Apps onChange callback failed');
|
|
14
|
+
await this.publishRoutes();
|
|
15
|
+
})
|
|
16
|
+
.catch((error) => report(this.options.onError, error, 'Apps change queue failed'));
|
|
17
|
+
}
|
|
18
|
+
settle() {
|
|
19
|
+
return this.queue;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export { Changes };
|
|
23
|
+
//# sourceMappingURL=changes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changes.js","sourceRoot":"","sources":["../src/changes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAKlD,MAAM,OAAO;IACM,OAAO,CAAc;IACrB,aAAa,CAAyB;IAC/C,KAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEjD,YAAY,OAAoB,EAAE,aAAqC;QACrE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACrC,CAAC;IAED,OAAO,CAAC,MAAiB;QACvB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK;aACpB,IAAI,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,OAAO,CACX,IAAI,CAAC,OAAO,CAAC,QAAQ,EACrB,CAAC,MAAM,CAAC,EACR,IAAI,CAAC,OAAO,CAAC,OAAO,EACpB,+BAA+B,CAChC,CAAC;YACF,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,0BAA0B,CAAC,CAAC,CAAC;IACvF,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Writable } from 'node:stream';
|
|
2
|
+
import { Apps } from './apps.js';
|
|
3
|
+
declare const HELP = "Usage: amalgm-apps [--state-dir DIR] <command>\n\nCommands:\n register <cwd> [--port PORT] [--no-dns] [--no-autostart] [--no-keep-alive]\n validate <cwd>\n list\n show <app-id>\n deploy <app-id> [cwd]\n versions <app-id>\n deployments <app-id>\n rollback <app-id> [release-id]\n rename <app-id> <name>\n start <app-id>\n stop <app-id>\n connect-dns <app-id>\n disconnect-dns <app-id>\n routes\n delete <app-id>";
|
|
4
|
+
interface CliOptions {
|
|
5
|
+
stateDir?: string;
|
|
6
|
+
port?: number;
|
|
7
|
+
connectDns?: boolean;
|
|
8
|
+
autostart?: boolean;
|
|
9
|
+
keepAlive?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface ParsedCli {
|
|
12
|
+
command: string | undefined;
|
|
13
|
+
args: string[];
|
|
14
|
+
options: CliOptions;
|
|
15
|
+
}
|
|
16
|
+
interface CliIo {
|
|
17
|
+
stdout?: Writable;
|
|
18
|
+
stderr?: Writable;
|
|
19
|
+
apps?: Apps;
|
|
20
|
+
}
|
|
21
|
+
declare function parse(argv: string[]): ParsedCli;
|
|
22
|
+
declare function runCli(argv: string[], io?: CliIo): Promise<number>;
|
|
23
|
+
export { HELP, parse, runCli };
|
|
24
|
+
export type { CliIo, CliOptions, ParsedCli };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Apps } from './apps.js';
|
|
2
|
+
const HELP = `Usage: amalgm-apps [--state-dir DIR] <command>
|
|
3
|
+
|
|
4
|
+
Commands:
|
|
5
|
+
register <cwd> [--port PORT] [--no-dns] [--no-autostart] [--no-keep-alive]
|
|
6
|
+
validate <cwd>
|
|
7
|
+
list
|
|
8
|
+
show <app-id>
|
|
9
|
+
deploy <app-id> [cwd]
|
|
10
|
+
versions <app-id>
|
|
11
|
+
deployments <app-id>
|
|
12
|
+
rollback <app-id> [release-id]
|
|
13
|
+
rename <app-id> <name>
|
|
14
|
+
start <app-id>
|
|
15
|
+
stop <app-id>
|
|
16
|
+
connect-dns <app-id>
|
|
17
|
+
disconnect-dns <app-id>
|
|
18
|
+
routes
|
|
19
|
+
delete <app-id>`;
|
|
20
|
+
function parse(argv) {
|
|
21
|
+
const args = [...argv];
|
|
22
|
+
const options = {};
|
|
23
|
+
for (let index = 0; index < args.length;) {
|
|
24
|
+
const value = args[index];
|
|
25
|
+
if (value === '--state-dir') {
|
|
26
|
+
options.stateDir = args[index + 1];
|
|
27
|
+
args.splice(index, 2);
|
|
28
|
+
}
|
|
29
|
+
else if (value === '--port') {
|
|
30
|
+
options.port = Number(args[index + 1]);
|
|
31
|
+
args.splice(index, 2);
|
|
32
|
+
}
|
|
33
|
+
else if (value === '--no-dns') {
|
|
34
|
+
options.connectDns = false;
|
|
35
|
+
args.splice(index, 1);
|
|
36
|
+
}
|
|
37
|
+
else if (value === '--no-autostart') {
|
|
38
|
+
options.autostart = false;
|
|
39
|
+
args.splice(index, 1);
|
|
40
|
+
}
|
|
41
|
+
else if (value === '--no-keep-alive') {
|
|
42
|
+
options.keepAlive = false;
|
|
43
|
+
args.splice(index, 1);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
index += 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { command: args.shift(), args, options };
|
|
50
|
+
}
|
|
51
|
+
function required(value, label) {
|
|
52
|
+
if (!value)
|
|
53
|
+
throw new Error(`${label} is required`);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
async function runCli(argv, io = {}) {
|
|
57
|
+
const stdout = io.stdout || process.stdout;
|
|
58
|
+
const stderr = io.stderr || process.stderr;
|
|
59
|
+
const parsed = parse(argv);
|
|
60
|
+
if (!parsed.command || ['help', '--help', '-h'].includes(parsed.command)) {
|
|
61
|
+
stdout.write(`${HELP}\n`);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
const apps = io.apps || new Apps({ stateDir: parsed.options.stateDir });
|
|
65
|
+
let result;
|
|
66
|
+
try {
|
|
67
|
+
const [first, second, ...rest] = parsed.args;
|
|
68
|
+
switch (parsed.command) {
|
|
69
|
+
case 'register':
|
|
70
|
+
result = await apps.register({
|
|
71
|
+
cwd: required(first, 'cwd'),
|
|
72
|
+
port: parsed.options.port,
|
|
73
|
+
connectDns: parsed.options.connectDns,
|
|
74
|
+
autostart: parsed.options.autostart,
|
|
75
|
+
keepAlive: parsed.options.keepAlive,
|
|
76
|
+
});
|
|
77
|
+
break;
|
|
78
|
+
case 'validate':
|
|
79
|
+
result = await apps.validate(required(first, 'cwd'));
|
|
80
|
+
break;
|
|
81
|
+
case 'list':
|
|
82
|
+
result = await apps.list();
|
|
83
|
+
break;
|
|
84
|
+
case 'show':
|
|
85
|
+
result = await apps.get(required(first, 'app-id'));
|
|
86
|
+
if (!result)
|
|
87
|
+
throw new Error(`App not found: ${first}`);
|
|
88
|
+
break;
|
|
89
|
+
case 'deploy':
|
|
90
|
+
result = await apps.deploy(required(first, 'app-id'), { cwd: second });
|
|
91
|
+
break;
|
|
92
|
+
case 'versions':
|
|
93
|
+
result = await apps.versions(required(first, 'app-id'));
|
|
94
|
+
break;
|
|
95
|
+
case 'deployments':
|
|
96
|
+
result = await apps.deployments(required(first, 'app-id'));
|
|
97
|
+
break;
|
|
98
|
+
case 'rollback':
|
|
99
|
+
result = await apps.rollback(required(first, 'app-id'), { releaseId: second });
|
|
100
|
+
break;
|
|
101
|
+
case 'rename':
|
|
102
|
+
result = await apps.rename(required(first, 'app-id'), required([second, ...rest].filter(Boolean).join(' '), 'name'));
|
|
103
|
+
break;
|
|
104
|
+
case 'start':
|
|
105
|
+
result = await apps.start(required(first, 'app-id'));
|
|
106
|
+
break;
|
|
107
|
+
case 'stop':
|
|
108
|
+
result = await apps.stop(required(first, 'app-id'));
|
|
109
|
+
break;
|
|
110
|
+
case 'connect-dns':
|
|
111
|
+
result = await apps.connectDns(required(first, 'app-id'));
|
|
112
|
+
break;
|
|
113
|
+
case 'disconnect-dns':
|
|
114
|
+
result = await apps.disconnectDns(required(first, 'app-id'));
|
|
115
|
+
break;
|
|
116
|
+
case 'routes':
|
|
117
|
+
result = await apps.routes();
|
|
118
|
+
break;
|
|
119
|
+
case 'delete':
|
|
120
|
+
result = await apps.delete(required(first, 'app-id'));
|
|
121
|
+
break;
|
|
122
|
+
default:
|
|
123
|
+
throw new Error(`Unknown command: ${parsed.command}`);
|
|
124
|
+
}
|
|
125
|
+
stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
if (!io.apps)
|
|
134
|
+
await apps.close();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export { HELP, parse, runCli };
|
|
138
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/command.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
function runCommand(command, options = {}) {
|
|
3
|
+
if (!command)
|
|
4
|
+
return Promise.resolve({ output: '' });
|
|
5
|
+
const timeoutMs = options.timeoutMs || 10 * 60_000;
|
|
6
|
+
const maxOutputBytes = options.maxOutputBytes || 64 * 1024;
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const child = spawn(command, {
|
|
9
|
+
cwd: options.cwd,
|
|
10
|
+
env: options.env || process.env,
|
|
11
|
+
shell: true,
|
|
12
|
+
windowsHide: true,
|
|
13
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
14
|
+
});
|
|
15
|
+
let output = '';
|
|
16
|
+
const append = (chunk) => {
|
|
17
|
+
output = `${output}${chunk}`.slice(-maxOutputBytes);
|
|
18
|
+
};
|
|
19
|
+
child.stdout.on('data', append);
|
|
20
|
+
child.stderr.on('data', append);
|
|
21
|
+
const timer = setTimeout(() => {
|
|
22
|
+
child.kill('SIGKILL');
|
|
23
|
+
reject(new Error(`Command timed out after ${timeoutMs}ms\n${output.trim()}`));
|
|
24
|
+
}, timeoutMs);
|
|
25
|
+
timer.unref?.();
|
|
26
|
+
child.once('error', (error) => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
reject(error);
|
|
29
|
+
});
|
|
30
|
+
child.once('exit', (code, signal) => {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
if (code === 0)
|
|
33
|
+
resolve({ output });
|
|
34
|
+
else
|
|
35
|
+
reject(new Error(output.trim() || `Command exited with ${signal || `code ${code}`}`));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export { runCommand };
|
|
40
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C,SAAS,UAAU,CAAC,OAAe,EAAE,UAA0B,EAAE;IAC/D,IAAI,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,GAAG,MAAM,CAAC;IACnD,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,EAAE,GAAG,IAAI,CAAC;IAE3D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE;YAC3B,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;YAC/B,KAAK,EAAE,IAAI;YACX,WAAW,EAAE,IAAI;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,CAAC,KAAsB,EAAE,EAAE;YACxC,MAAM,GAAG,GAAG,MAAM,GAAG,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,CAAC;QACtD,CAAC,CAAC;QACF,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,SAAS,OAAO,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAChF,CAAC,EAAE,SAAS,CAAC,CAAC;QACd,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC5B,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAClC,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;;gBAC/B,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,uBAAuB,MAAM,IAAI,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAC7F,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/faults.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Store } from './store.js';
|
|
2
|
+
import type { AppRecord } from './types.js';
|
|
3
|
+
interface FaultOptions {
|
|
4
|
+
store: Store;
|
|
5
|
+
changed?: (app: AppRecord) => unknown;
|
|
6
|
+
onError?: (error: Error) => unknown;
|
|
7
|
+
}
|
|
8
|
+
declare class Faults {
|
|
9
|
+
private readonly store;
|
|
10
|
+
private readonly changed;
|
|
11
|
+
private readonly onError;
|
|
12
|
+
private readonly apps;
|
|
13
|
+
constructor(options: FaultOptions);
|
|
14
|
+
degrade(appId: string | null, error: unknown, context?: string): AppRecord | null;
|
|
15
|
+
guard<TResult>(appId: string | null, context: string, operation: () => TResult): TResult | undefined;
|
|
16
|
+
guardAsync<TResult>(appId: string | null, context: string, operation: () => TResult | Promise<TResult>): Promise<TResult | undefined>;
|
|
17
|
+
clear(appId: string): void;
|
|
18
|
+
get(appId: string): Error | null;
|
|
19
|
+
}
|
|
20
|
+
export { Faults };
|
|
21
|
+
export type { FaultOptions };
|
package/dist/faults.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { report } from './boundaries.js';
|
|
2
|
+
class Faults {
|
|
3
|
+
store;
|
|
4
|
+
changed;
|
|
5
|
+
onError;
|
|
6
|
+
apps = new Map();
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.store = options.store;
|
|
9
|
+
this.changed = options.changed || (() => { });
|
|
10
|
+
this.onError = options.onError;
|
|
11
|
+
}
|
|
12
|
+
degrade(appId, error, context = 'Apps supervision failed') {
|
|
13
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
14
|
+
if (appId)
|
|
15
|
+
this.apps.set(appId, failure);
|
|
16
|
+
report(this.onError, failure, context);
|
|
17
|
+
if (!appId)
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
const app = this.store.updateApp(appId, {
|
|
21
|
+
status: 'degraded',
|
|
22
|
+
error: failure.message,
|
|
23
|
+
});
|
|
24
|
+
if (app)
|
|
25
|
+
this.changed(app);
|
|
26
|
+
return app;
|
|
27
|
+
}
|
|
28
|
+
catch (storeError) {
|
|
29
|
+
report(this.onError, storeError, 'Could not persist degraded app state');
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
guard(appId, context, operation) {
|
|
34
|
+
try {
|
|
35
|
+
return operation();
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
this.degrade(appId, error, context);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async guardAsync(appId, context, operation) {
|
|
43
|
+
try {
|
|
44
|
+
return await operation();
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
this.degrade(appId, error, context);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
clear(appId) {
|
|
52
|
+
this.apps.delete(appId);
|
|
53
|
+
}
|
|
54
|
+
get(appId) {
|
|
55
|
+
return this.apps.get(appId) || null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export { Faults };
|
|
59
|
+
//# sourceMappingURL=faults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faults.js","sourceRoot":"","sources":["../src/faults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAUzC,MAAM,MAAM;IACO,KAAK,CAAQ;IACb,OAAO,CAA8B;IACrC,OAAO,CAA0C;IACjD,IAAI,GAAG,IAAI,GAAG,EAAiB,CAAC;IAEjD,YAAY,OAAqB;QAC/B,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;IAED,OAAO,CAAC,KAAoB,EAAE,KAAc,EAAE,OAAO,GAAG,yBAAyB;QAC/E,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1E,IAAI,KAAK;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE;gBACtC,MAAM,EAAE,UAAU;gBAClB,KAAK,EAAE,OAAO,CAAC,OAAO;aACvB,CAAC,CAAC;YACH,IAAI,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC3B,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,OAAO,UAAU,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,sCAAsC,CAAC,CAAC;YACzE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAU,KAAoB,EAAE,OAAe,EAAE,SAAwB;QAC5E,IAAI,CAAC;YACH,OAAO,SAAS,EAAE,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YACpC,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CACd,KAAoB,EACpB,OAAe,EACf,SAA2C;QAE3C,IAAI,CAAC;YACH,OAAO,MAAM,SAAS,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YACpC,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAa;QACjB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,GAAG,CAAC,KAAa;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;IACtC,CAAC;CACF;AAED,OAAO,EAAE,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { GenerationStatus, ProcessGeneration } from './types.js';
|
|
3
|
+
interface NewGeneration {
|
|
4
|
+
id: string;
|
|
5
|
+
appId: string;
|
|
6
|
+
deploymentId: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
}
|
|
9
|
+
interface GenerationOutcome {
|
|
10
|
+
status?: GenerationStatus;
|
|
11
|
+
exitedAt?: string;
|
|
12
|
+
exitCode?: number | null;
|
|
13
|
+
exitSignal?: NodeJS.Signals | null;
|
|
14
|
+
error?: string | null;
|
|
15
|
+
}
|
|
16
|
+
declare class Generations {
|
|
17
|
+
private readonly db;
|
|
18
|
+
constructor(db: Database.Database);
|
|
19
|
+
create(generation: NewGeneration): ProcessGeneration;
|
|
20
|
+
get(id: string): ProcessGeneration | null;
|
|
21
|
+
latest(appId: string): ProcessGeneration | null;
|
|
22
|
+
failuresSince(appId: string, since: string): ProcessGeneration[];
|
|
23
|
+
attach(id: string, pid: number, processIdentity: string, now?: string): ProcessGeneration;
|
|
24
|
+
stable(id: string, now?: string): ProcessGeneration;
|
|
25
|
+
finish(id: string, outcome?: GenerationOutcome): ProcessGeneration | null;
|
|
26
|
+
}
|
|
27
|
+
export { Generations };
|
|
28
|
+
export type { GenerationOutcome, NewGeneration };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
function row(value) {
|
|
3
|
+
if (!value)
|
|
4
|
+
return null;
|
|
5
|
+
return {
|
|
6
|
+
sequence: value.sequence,
|
|
7
|
+
id: value.id,
|
|
8
|
+
appId: value.app_id,
|
|
9
|
+
deploymentId: value.deployment_id,
|
|
10
|
+
pid: value.pid,
|
|
11
|
+
processIdentity: value.process_identity,
|
|
12
|
+
status: value.status,
|
|
13
|
+
createdAt: value.created_at,
|
|
14
|
+
startedAt: value.started_at,
|
|
15
|
+
stableAt: value.stable_at,
|
|
16
|
+
exitedAt: value.exited_at,
|
|
17
|
+
exitCode: value.exit_code,
|
|
18
|
+
exitSignal: value.exit_signal,
|
|
19
|
+
error: value.error,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
class Generations {
|
|
23
|
+
db;
|
|
24
|
+
constructor(db) {
|
|
25
|
+
this.db = db;
|
|
26
|
+
}
|
|
27
|
+
create(generation) {
|
|
28
|
+
this.db.prepare(`
|
|
29
|
+
INSERT INTO process_generations
|
|
30
|
+
(id, app_id, deployment_id, status, created_at)
|
|
31
|
+
VALUES (@id, @appId, @deploymentId, 'starting', @createdAt)
|
|
32
|
+
`).run(generation);
|
|
33
|
+
return this.get(generation.id);
|
|
34
|
+
}
|
|
35
|
+
get(id) {
|
|
36
|
+
return row(this.db.prepare('SELECT * FROM process_generations WHERE id = ?').get(id));
|
|
37
|
+
}
|
|
38
|
+
latest(appId) {
|
|
39
|
+
return row(this.db.prepare(`
|
|
40
|
+
SELECT * FROM process_generations
|
|
41
|
+
WHERE app_id = ? ORDER BY sequence DESC LIMIT 1
|
|
42
|
+
`).get(appId));
|
|
43
|
+
}
|
|
44
|
+
failuresSince(appId, since) {
|
|
45
|
+
return this.db.prepare(`
|
|
46
|
+
SELECT * FROM process_generations
|
|
47
|
+
WHERE app_id = ? AND status IN ('crashed', 'failed') AND exited_at >= ?
|
|
48
|
+
ORDER BY sequence DESC
|
|
49
|
+
`).all(appId, since).map((value) => row(value)).filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
attach(id, pid, processIdentity, now = new Date().toISOString()) {
|
|
52
|
+
this.db.prepare(`
|
|
53
|
+
UPDATE process_generations
|
|
54
|
+
SET pid = ?, process_identity = ?, status = 'running', started_at = ?
|
|
55
|
+
WHERE id = ?
|
|
56
|
+
`).run(pid, processIdentity, now, id);
|
|
57
|
+
return this.get(id);
|
|
58
|
+
}
|
|
59
|
+
stable(id, now = new Date().toISOString()) {
|
|
60
|
+
this.db.prepare(`
|
|
61
|
+
UPDATE process_generations SET stable_at = ? WHERE id = ?
|
|
62
|
+
`).run(now, id);
|
|
63
|
+
return this.get(id);
|
|
64
|
+
}
|
|
65
|
+
finish(id, outcome = {}) {
|
|
66
|
+
this.db.prepare(`
|
|
67
|
+
UPDATE process_generations SET
|
|
68
|
+
status = @status, exited_at = @exitedAt, exit_code = @exitCode,
|
|
69
|
+
exit_signal = @exitSignal, error = @error
|
|
70
|
+
WHERE id = @id
|
|
71
|
+
`).run({
|
|
72
|
+
id,
|
|
73
|
+
status: outcome.status || 'exited',
|
|
74
|
+
exitedAt: outcome.exitedAt || new Date().toISOString(),
|
|
75
|
+
exitCode: outcome.exitCode ?? null,
|
|
76
|
+
exitSignal: outcome.exitSignal || null,
|
|
77
|
+
error: outcome.error || null,
|
|
78
|
+
});
|
|
79
|
+
return this.get(id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export { Generations };
|
|
83
|
+
//# sourceMappingURL=generations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generations.js","sourceRoot":"","sources":["../src/generations.ts"],"names":[],"mappings":"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"}
|
package/dist/health.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Faults } from './faults.js';
|
|
2
|
+
import type { Store } from './store.js';
|
|
3
|
+
import type { AppHealth, AppRecord, HealthPolicy } from './types.js';
|
|
4
|
+
declare const POLICY: HealthPolicy;
|
|
5
|
+
interface HealthResult {
|
|
6
|
+
accepted: boolean;
|
|
7
|
+
reason?: string;
|
|
8
|
+
health?: AppHealth | null;
|
|
9
|
+
}
|
|
10
|
+
interface HealthOptions {
|
|
11
|
+
store: Store;
|
|
12
|
+
changed?: (app: AppRecord) => unknown;
|
|
13
|
+
faults: Faults;
|
|
14
|
+
policy?: Partial<HealthPolicy>;
|
|
15
|
+
}
|
|
16
|
+
declare class Health {
|
|
17
|
+
private readonly store;
|
|
18
|
+
private readonly changed;
|
|
19
|
+
private readonly faults;
|
|
20
|
+
private readonly policy;
|
|
21
|
+
private readonly enrolled;
|
|
22
|
+
private timer;
|
|
23
|
+
constructor(options: HealthOptions);
|
|
24
|
+
snapshot(appId: string, now?: number): AppHealth | null;
|
|
25
|
+
ready(appId: string, options?: {
|
|
26
|
+
heartbeatIntervalMs?: number;
|
|
27
|
+
}, now?: number): HealthResult;
|
|
28
|
+
heartbeat(appId: string, now?: number): HealthResult;
|
|
29
|
+
forget(appId: string): void;
|
|
30
|
+
sweep(now?: number): string[];
|
|
31
|
+
start(): void;
|
|
32
|
+
close(): void;
|
|
33
|
+
}
|
|
34
|
+
export { Health, POLICY };
|
|
35
|
+
export type { HealthOptions, HealthResult };
|
package/dist/health.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { APP_HEALTH_POLICY, healthSignalRejection, heartbeatRecovery, snapshotEntry, } from '@amalgm/core/supervision';
|
|
2
|
+
const POLICY = { ...APP_HEALTH_POLICY };
|
|
3
|
+
// The kernel's clampIntervalMs with the policy injectable; under the
|
|
4
|
+
// default POLICY the numbers and semantics are the kernel's.
|
|
5
|
+
function interval(value, policy = POLICY) {
|
|
6
|
+
const number = Number(value);
|
|
7
|
+
if (!Number.isFinite(number))
|
|
8
|
+
return policy.defaultIntervalMs;
|
|
9
|
+
return Math.min(Math.max(number, policy.minIntervalMs), policy.maxIntervalMs);
|
|
10
|
+
}
|
|
11
|
+
class Health {
|
|
12
|
+
store;
|
|
13
|
+
changed;
|
|
14
|
+
faults;
|
|
15
|
+
policy;
|
|
16
|
+
enrolled = new Map();
|
|
17
|
+
timer = null;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.store = options.store;
|
|
20
|
+
this.changed = options.changed || (() => { });
|
|
21
|
+
this.faults = options.faults;
|
|
22
|
+
this.policy = { ...POLICY, ...options.policy };
|
|
23
|
+
}
|
|
24
|
+
snapshot(appId, now = Date.now()) {
|
|
25
|
+
const entry = this.enrolled.get(appId);
|
|
26
|
+
if (!entry)
|
|
27
|
+
return null;
|
|
28
|
+
return snapshotEntry(appId, entry, now);
|
|
29
|
+
}
|
|
30
|
+
ready(appId, options = {}, now = Date.now()) {
|
|
31
|
+
const app = this.store.getApp(appId);
|
|
32
|
+
const rejection = healthSignalRejection(app);
|
|
33
|
+
if (rejection || !app)
|
|
34
|
+
return { accepted: false, reason: rejection ?? 'not_found' };
|
|
35
|
+
const entry = {
|
|
36
|
+
readyAt: now,
|
|
37
|
+
lastHeartbeatAt: now,
|
|
38
|
+
intervalMs: interval(options.heartbeatIntervalMs, this.policy),
|
|
39
|
+
};
|
|
40
|
+
this.enrolled.set(appId, entry);
|
|
41
|
+
this.changed(this.store.updateApp(appId, {
|
|
42
|
+
status: 'running',
|
|
43
|
+
error: null,
|
|
44
|
+
readyAt: new Date(now).toISOString(),
|
|
45
|
+
lastHeartbeatAt: new Date(now).toISOString(),
|
|
46
|
+
heartbeatIntervalMs: entry.intervalMs,
|
|
47
|
+
}));
|
|
48
|
+
return { accepted: true, health: this.snapshot(appId, now) };
|
|
49
|
+
}
|
|
50
|
+
heartbeat(appId, now = Date.now()) {
|
|
51
|
+
const app = this.store.getApp(appId);
|
|
52
|
+
const rejection = healthSignalRejection(app);
|
|
53
|
+
if (rejection || !app)
|
|
54
|
+
return { accepted: false, reason: rejection ?? 'not_found' };
|
|
55
|
+
let entry = this.enrolled.get(appId);
|
|
56
|
+
if (!entry) {
|
|
57
|
+
entry = {
|
|
58
|
+
readyAt: null,
|
|
59
|
+
lastHeartbeatAt: now,
|
|
60
|
+
intervalMs: interval(app.heartbeatIntervalMs, this.policy),
|
|
61
|
+
};
|
|
62
|
+
this.enrolled.set(appId, entry);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
entry.lastHeartbeatAt = now;
|
|
66
|
+
}
|
|
67
|
+
const recovery = heartbeatRecovery(app, now);
|
|
68
|
+
if (recovery)
|
|
69
|
+
this.changed(this.store.updateApp(appId, recovery.update));
|
|
70
|
+
return { accepted: true, health: this.snapshot(appId, now) };
|
|
71
|
+
}
|
|
72
|
+
forget(appId) {
|
|
73
|
+
this.enrolled.delete(appId);
|
|
74
|
+
}
|
|
75
|
+
sweep(now = Date.now()) {
|
|
76
|
+
const transitioned = [];
|
|
77
|
+
for (const [appId, entry] of this.enrolled) {
|
|
78
|
+
this.faults.guard(appId, 'Health sweep failed', () => {
|
|
79
|
+
const overdue = now - entry.lastHeartbeatAt > entry.intervalMs * this.policy.missedBeats;
|
|
80
|
+
if (!overdue)
|
|
81
|
+
return;
|
|
82
|
+
const app = this.store.getApp(appId);
|
|
83
|
+
if (!app || app.desiredState !== 'running') {
|
|
84
|
+
this.enrolled.delete(appId);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (app.status !== 'running')
|
|
88
|
+
return;
|
|
89
|
+
const seconds = Math.round((now - entry.lastHeartbeatAt) / 1000);
|
|
90
|
+
this.changed(this.store.updateApp(appId, {
|
|
91
|
+
status: 'unresponsive',
|
|
92
|
+
error: `No heartbeat for ${seconds}s`,
|
|
93
|
+
lastHeartbeatAt: new Date(entry.lastHeartbeatAt).toISOString(),
|
|
94
|
+
}));
|
|
95
|
+
transitioned.push(appId);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return transitioned;
|
|
99
|
+
}
|
|
100
|
+
start() {
|
|
101
|
+
if (this.timer)
|
|
102
|
+
return;
|
|
103
|
+
this.timer = setInterval(() => {
|
|
104
|
+
this.faults.guard(null, 'Health timer failed', () => this.sweep());
|
|
105
|
+
}, this.policy.sweepIntervalMs);
|
|
106
|
+
this.timer.unref?.();
|
|
107
|
+
}
|
|
108
|
+
close() {
|
|
109
|
+
if (this.timer)
|
|
110
|
+
clearInterval(this.timer);
|
|
111
|
+
this.timer = null;
|
|
112
|
+
this.enrolled.clear();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export { Health, POLICY };
|
|
116
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { APP_REF_PATTERN } from '@amalgm/core/transport';
|
|
2
|
+
import type { AppRecord } from './types.js';
|
|
3
|
+
interface AppReader {
|
|
4
|
+
listApps(): AppRecord[];
|
|
5
|
+
}
|
|
6
|
+
declare function defaultStateDir(env?: NodeJS.ProcessEnv, scope?: unknown): string;
|
|
7
|
+
declare function allocatePort(store: AppReader, preferred: number | undefined, minimum: number, maximum: number): number;
|
|
8
|
+
declare function allocateRef(store: AppReader): string;
|
|
9
|
+
declare function assertAppRef(value: unknown): string;
|
|
10
|
+
export { APP_REF_PATTERN, allocatePort, allocateRef, assertAppRef, defaultStateDir, };
|