@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.
Files changed (98) hide show
  1. package/PURPOSE.md +98 -0
  2. package/README.md +61 -0
  3. package/dist/apps.d.ts +58 -0
  4. package/dist/apps.js +274 -0
  5. package/dist/apps.js.map +1 -0
  6. package/dist/bin/apps.d.ts +2 -0
  7. package/dist/bin/apps.js +6 -0
  8. package/dist/bin/apps.js.map +1 -0
  9. package/dist/bin/mcp.d.ts +2 -0
  10. package/dist/bin/mcp.js +15 -0
  11. package/dist/bin/mcp.js.map +1 -0
  12. package/dist/boundaries.d.ts +11 -0
  13. package/dist/boundaries.js +55 -0
  14. package/dist/boundaries.js.map +1 -0
  15. package/dist/changes.d.ts +14 -0
  16. package/dist/changes.js +23 -0
  17. package/dist/changes.js.map +1 -0
  18. package/dist/cli.d.ts +24 -0
  19. package/dist/cli.js +138 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/command.d.ts +3 -0
  22. package/dist/command.js +40 -0
  23. package/dist/command.js.map +1 -0
  24. package/dist/faults.d.ts +21 -0
  25. package/dist/faults.js +59 -0
  26. package/dist/faults.js.map +1 -0
  27. package/dist/generations.d.ts +28 -0
  28. package/dist/generations.js +83 -0
  29. package/dist/generations.js.map +1 -0
  30. package/dist/health.d.ts +35 -0
  31. package/dist/health.js +116 -0
  32. package/dist/health.js.map +1 -0
  33. package/dist/identity.d.ts +10 -0
  34. package/dist/identity.js +59 -0
  35. package/dist/identity.js.map +1 -0
  36. package/dist/index.d.ts +8 -0
  37. package/dist/index.js +8 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/lease.d.ts +30 -0
  40. package/dist/lease.js +82 -0
  41. package/dist/lease.js.map +1 -0
  42. package/dist/logs.d.ts +11 -0
  43. package/dist/logs.js +40 -0
  44. package/dist/logs.js.map +1 -0
  45. package/dist/manifest.d.ts +6 -0
  46. package/dist/manifest.js +105 -0
  47. package/dist/manifest.js.map +1 -0
  48. package/dist/mcp-server.d.ts +3 -0
  49. package/dist/mcp-server.js +65 -0
  50. package/dist/mcp-server.js.map +1 -0
  51. package/dist/mcp.d.ts +4 -0
  52. package/dist/mcp.js +171 -0
  53. package/dist/mcp.js.map +1 -0
  54. package/dist/operations.d.ts +10 -0
  55. package/dist/operations.js +31 -0
  56. package/dist/operations.js.map +1 -0
  57. package/dist/ownership.d.ts +11 -0
  58. package/dist/ownership.js +75 -0
  59. package/dist/ownership.js.map +1 -0
  60. package/dist/package.d.ts +20 -0
  61. package/dist/package.js +204 -0
  62. package/dist/package.js.map +1 -0
  63. package/dist/presentation.d.ts +9 -0
  64. package/dist/presentation.js +63 -0
  65. package/dist/presentation.js.map +1 -0
  66. package/dist/process.d.ts +10 -0
  67. package/dist/process.js +68 -0
  68. package/dist/process.js.map +1 -0
  69. package/dist/reconciler.d.ts +27 -0
  70. package/dist/reconciler.js +103 -0
  71. package/dist/reconciler.js.map +1 -0
  72. package/dist/releases.d.ts +26 -0
  73. package/dist/releases.js +108 -0
  74. package/dist/releases.js.map +1 -0
  75. package/dist/restarts.d.ts +40 -0
  76. package/dist/restarts.js +137 -0
  77. package/dist/restarts.js.map +1 -0
  78. package/dist/runtime.d.ts +19 -0
  79. package/dist/runtime.js +57 -0
  80. package/dist/runtime.js.map +1 -0
  81. package/dist/schema.d.ts +3 -0
  82. package/dist/schema.js +80 -0
  83. package/dist/schema.js.map +1 -0
  84. package/dist/source-import.d.ts +4 -0
  85. package/dist/source-import.js +77 -0
  86. package/dist/source-import.js.map +1 -0
  87. package/dist/store.d.ts +29 -0
  88. package/dist/store.js +221 -0
  89. package/dist/store.js.map +1 -0
  90. package/dist/supervisor.d.ts +29 -0
  91. package/dist/supervisor.js +279 -0
  92. package/dist/supervisor.js.map +1 -0
  93. package/dist/types.d.ts +264 -0
  94. package/dist/types.js +2 -0
  95. package/dist/types.js.map +1 -0
  96. package/docs/ENGINE_INTEGRATION.md +68 -0
  97. package/docs/SUPERVISION.md +73 -0
  98. package/package.json +59 -0
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CommandOptions, CommandResult } from './types.js';
2
+ declare function runCommand(command: string, options?: CommandOptions): Promise<CommandResult>;
3
+ export { runCommand };
@@ -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"}
@@ -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":"AAAA,YAAY,CAAC;AAqCb,SAAS,GAAG,CAAC,KAAgC;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,KAAK,EAAE,KAAK,CAAC,MAAM;QACnB,YAAY,EAAE,KAAK,CAAC,aAAa;QACjC,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,eAAe,EAAE,KAAK,CAAC,gBAAgB;QACvC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,KAAK,CAAC,UAAU;QAC3B,SAAS,EAAE,KAAK,CAAC,UAAU;QAC3B,QAAQ,EAAE,KAAK,CAAC,SAAS;QACzB,QAAQ,EAAE,KAAK,CAAC,SAAS;QACzB,QAAQ,EAAE,KAAK,CAAC,SAAS;QACzB,UAAU,EAAE,KAAK,CAAC,WAAW;QAC7B,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC;AACJ,CAAC;AAED,MAAM,WAAW;IACE,EAAE,CAAoB;IAEvC,YAAY,EAAqB;QAC/B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED,MAAM,CAAC,UAAyB;QAC9B,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;KAIf,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAE,CAAC;IAClC,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,gDAAgD,CAAC,CAAC,GAAG,CAAC,EAAE,CAA8B,CAAC,CAAC;IACrH,CAAC;IAED,MAAM,CAAC,KAAa;QAClB,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAG1B,CAAC,CAAC,GAAG,CAAC,KAAK,CAA8B,CAAC,CAAC;IAC9C,CAAC;IAED,aAAa,CAAC,KAAa,EAAE,KAAa;QACxC,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;KAItB,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,KAAsB,CAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpF,CAAC;IAED,MAAM,CAAC,EAAU,EAAE,GAAW,EAAE,eAAuB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrF,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;KAIf,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAE,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,EAAU,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC/C,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;KAEf,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAE,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,EAAU,EAAE,UAA6B,EAAE;QAChD,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;KAKf,CAAC,CAAC,GAAG,CAAC;YACL,EAAE;YACF,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,QAAQ;YAClC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACtD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;YAClC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;YACtC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;SAC7B,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtB,CAAC;CACF;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -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, };