@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,103 @@
1
+ import crypto from 'node:crypto';
2
+ import { processIdentity } from './process.js';
3
+ class Reconciler {
4
+ store;
5
+ changed;
6
+ failed;
7
+ faults;
8
+ intervalMs;
9
+ adopted = new Map();
10
+ timer = null;
11
+ constructor(options) {
12
+ this.store = options.store;
13
+ this.changed = options.changed;
14
+ this.failed = options.failed;
15
+ this.faults = options.faults;
16
+ this.intervalMs = options.intervalMs;
17
+ }
18
+ async adopt(app) {
19
+ let generation = app.activeGenerationId
20
+ ? this.store.generations.get(app.activeGenerationId)
21
+ : null;
22
+ const identity = await processIdentity(app.pid);
23
+ if (!identity)
24
+ throw new Error(`Could not identify surviving process ${app.pid}`);
25
+ if (generation?.processIdentity && generation.processIdentity !== identity) {
26
+ const current = this.store.updateApp(app.id, {
27
+ status: 'error',
28
+ error: `Process identity changed for pid ${app.pid}`,
29
+ });
30
+ this.changed(current);
31
+ throw new Error(current.error);
32
+ }
33
+ if (!generation) {
34
+ generation = this.store.generations.create({
35
+ id: `gen-${crypto.randomUUID()}`,
36
+ appId: app.id,
37
+ deploymentId: app.activeDeploymentId,
38
+ createdAt: new Date().toISOString(),
39
+ });
40
+ this.store.generations.attach(generation.id, app.pid, identity);
41
+ this.store.generations.stable(generation.id);
42
+ }
43
+ this.adopted.set(app.id, {
44
+ generationId: generation.id,
45
+ identity,
46
+ pid: app.pid,
47
+ });
48
+ const current = this.store.updateApp(app.id, {
49
+ activeGenerationId: generation.id,
50
+ desiredState: 'running',
51
+ status: 'running',
52
+ error: null,
53
+ });
54
+ this.changed(current);
55
+ return current;
56
+ }
57
+ forget(appId) {
58
+ this.adopted.delete(appId);
59
+ }
60
+ start() {
61
+ if (this.timer)
62
+ return;
63
+ this.timer = setInterval(() => {
64
+ this.faults.guardAsync(null, 'Reconciliation timer failed', () => this.sweep());
65
+ }, this.intervalMs);
66
+ this.timer.unref?.();
67
+ }
68
+ stop() {
69
+ if (this.timer)
70
+ clearInterval(this.timer);
71
+ this.timer = null;
72
+ this.adopted.clear();
73
+ }
74
+ async sweep() {
75
+ for (const [appId, adopted] of this.adopted) {
76
+ await this.faults.guardAsync(appId, 'Adopted process reconciliation failed', async () => {
77
+ const identity = await processIdentity(adopted.pid);
78
+ if (identity === adopted.identity)
79
+ return;
80
+ this.adopted.delete(appId);
81
+ this.store.generations.finish(adopted.generationId, {
82
+ status: 'crashed',
83
+ error: 'Adopted process exited',
84
+ });
85
+ const app = this.store.getApp(appId);
86
+ if (!app)
87
+ return;
88
+ const current = this.store.updateApp(appId, {
89
+ activeGenerationId: null,
90
+ pid: null,
91
+ status: app.desiredState === 'running' && app.keepAlive ? 'restarting' : 'error',
92
+ error: 'Adopted process exited',
93
+ });
94
+ this.changed(current);
95
+ if (app.desiredState === 'running' && app.keepAlive) {
96
+ this.failed(appId, 'Adopted process exited');
97
+ }
98
+ });
99
+ }
100
+ }
101
+ }
102
+ export { Reconciler };
103
+ //# sourceMappingURL=reconciler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconciler.js","sourceRoot":"","sources":["../src/reconciler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { Store } from './store.js';
2
+ import type { AppManifest, AppRecord, Deployment, Release, RunCommand } from './types.js';
3
+ interface ReleasesOptions {
4
+ stateDir: string;
5
+ store: Store;
6
+ buildEnv?: (app: AppRecord, manifest?: AppManifest) => NodeJS.ProcessEnv;
7
+ runCommand?: RunCommand;
8
+ buildTimeoutMs?: number;
9
+ }
10
+ declare function releaseId(appId: string, digest: string): string;
11
+ declare class Releases {
12
+ readonly stateDir: string;
13
+ readonly store: Store;
14
+ readonly buildEnv: (app: AppRecord, manifest?: AppManifest) => NodeJS.ProcessEnv;
15
+ readonly run: RunCommand;
16
+ readonly buildTimeoutMs: number | undefined;
17
+ readonly releasesDir: string;
18
+ readonly deploymentsDir: string;
19
+ readonly tempDir: string;
20
+ constructor(options: ReleasesOptions);
21
+ prepare(app: AppRecord, sourceDir: string, manifest?: AppManifest): Promise<Release>;
22
+ createDeployment(app: AppRecord, release: Release, reason: string): Promise<Deployment>;
23
+ removeDeployment(deployment: Deployment | null | undefined): void;
24
+ }
25
+ export { Releases, releaseId };
26
+ export type { ReleasesOptions };
@@ -0,0 +1,108 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { collectPackage, digestPackage, materialize } from './package.js';
5
+ import { runCommand } from './command.js';
6
+ import { errorMessage } from './boundaries.js';
7
+ function releaseId(appId, digest) {
8
+ const identity = crypto.createHash('sha256').update(`${appId}\0${digest}`).digest('hex');
9
+ return `rel-${identity.slice(0, 24)}`;
10
+ }
11
+ class Releases {
12
+ stateDir;
13
+ store;
14
+ buildEnv;
15
+ run;
16
+ buildTimeoutMs;
17
+ releasesDir;
18
+ deploymentsDir;
19
+ tempDir;
20
+ constructor(options) {
21
+ this.stateDir = options.stateDir;
22
+ this.store = options.store;
23
+ this.buildEnv = options.buildEnv || (() => process.env);
24
+ this.run = options.runCommand || runCommand;
25
+ this.buildTimeoutMs = options.buildTimeoutMs;
26
+ this.releasesDir = path.join(this.stateDir, 'releases');
27
+ this.deploymentsDir = path.join(this.stateDir, 'deployments');
28
+ this.tempDir = path.join(this.stateDir, 'tmp');
29
+ for (const directory of [this.releasesDir, this.deploymentsDir, this.tempDir]) {
30
+ fs.mkdirSync(directory, { recursive: true });
31
+ }
32
+ }
33
+ async prepare(app, sourceDir, manifest) {
34
+ const pkg = collectPackage(sourceDir, { manifest });
35
+ const digest = digestPackage(pkg);
36
+ const id = releaseId(app.id, digest);
37
+ const directory = path.join(this.releasesDir, app.id, id);
38
+ const existing = this.store.getRelease(id);
39
+ if (existing && fs.existsSync(existing.directory))
40
+ return existing;
41
+ fs.mkdirSync(path.dirname(directory), { recursive: true });
42
+ if (!fs.existsSync(directory)) {
43
+ const staging = fs.mkdtempSync(path.join(this.tempDir, `${app.id}-`));
44
+ try {
45
+ materialize(pkg.files, staging);
46
+ if (pkg.manifest.buildCommand) {
47
+ await this.run(pkg.manifest.buildCommand.replace(/\{port\}/g, String(app.port)), {
48
+ cwd: staging,
49
+ env: this.buildEnv(app, pkg.manifest),
50
+ timeoutMs: this.buildTimeoutMs,
51
+ });
52
+ }
53
+ fs.writeFileSync(path.join(staging, '.amalgm-release.json'), `${JSON.stringify({
54
+ id,
55
+ appId: app.id,
56
+ digest,
57
+ manifest: pkg.manifest,
58
+ }, null, 2)}\n`);
59
+ try {
60
+ fs.renameSync(staging, directory);
61
+ }
62
+ catch (error) {
63
+ if (!fs.existsSync(directory))
64
+ throw error;
65
+ fs.rmSync(staging, { recursive: true, force: true });
66
+ }
67
+ }
68
+ catch (error) {
69
+ fs.rmSync(staging, { recursive: true, force: true });
70
+ throw new Error(`Release build failed: ${errorMessage(error)}`);
71
+ }
72
+ }
73
+ return this.store.addRelease({
74
+ id,
75
+ appId: app.id,
76
+ digest,
77
+ directory,
78
+ manifest: pkg.manifest,
79
+ createdAt: new Date().toISOString(),
80
+ });
81
+ }
82
+ async createDeployment(app, release, reason) {
83
+ const id = `dep-${crypto.randomUUID()}`;
84
+ const directory = path.join(this.deploymentsDir, app.id, id);
85
+ await fs.promises.mkdir(path.dirname(directory), { recursive: true });
86
+ await fs.promises.cp(release.directory, directory, {
87
+ recursive: true,
88
+ errorOnExist: true,
89
+ force: false,
90
+ mode: fs.constants.COPYFILE_FICLONE,
91
+ });
92
+ return this.store.createDeployment({
93
+ id,
94
+ appId: app.id,
95
+ releaseId: release.id,
96
+ directory,
97
+ reason,
98
+ createdAt: new Date().toISOString(),
99
+ });
100
+ }
101
+ removeDeployment(deployment) {
102
+ if (!deployment?.directory)
103
+ return;
104
+ fs.rmSync(deployment.directory, { recursive: true, force: true });
105
+ }
106
+ }
107
+ export { Releases, releaseId };
108
+ //# sourceMappingURL=releases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"releases.js","sourceRoot":"","sources":["../src/releases.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { Faults } from './faults.js';
2
+ import type { Store } from './store.js';
3
+ import type { AppRecord, ProcessGeneration, RestartPolicy } from './types.js';
4
+ declare const DEFAULT_RESTART_POLICY: RestartPolicy;
5
+ declare function backoffDelayMs(count: number, policy: RestartPolicy): number;
6
+ declare class RestartLimiter {
7
+ private readonly limit;
8
+ private active;
9
+ private readonly pending;
10
+ constructor(limit: number);
11
+ run<TResult>(operation: () => Promise<TResult> | TResult): Promise<TResult>;
12
+ private drain;
13
+ }
14
+ interface RestartsOptions {
15
+ store: Store;
16
+ changed: (app: AppRecord) => unknown;
17
+ restart: (appId: string) => Promise<AppRecord | null>;
18
+ faults: Faults;
19
+ policy?: Partial<RestartPolicy>;
20
+ }
21
+ declare class Restarts {
22
+ private readonly store;
23
+ private readonly changed;
24
+ private readonly restart;
25
+ private readonly faults;
26
+ private readonly policy;
27
+ private readonly limiter;
28
+ private readonly timers;
29
+ constructor(options: RestartsOptions);
30
+ failures(appId: string, now?: number): ProcessGeneration[];
31
+ latch(appId: string, count: number): AppRecord | null;
32
+ permit(appId: string): boolean;
33
+ afterFailure(appId: string, error: unknown): AppRecord | null;
34
+ schedule(appId: string, delay: number): void;
35
+ launch(appId: string): Promise<AppRecord | null>;
36
+ cancel(appId: string): void;
37
+ close(): void;
38
+ }
39
+ export { DEFAULT_RESTART_POLICY, RestartLimiter, Restarts, backoffDelayMs };
40
+ export type { RestartsOptions };
@@ -0,0 +1,137 @@
1
+ import { APP_RESTART_POLICY, restartDelayMs } from '@amalgm/core/supervision';
2
+ import { errorMessage } from './boundaries.js';
3
+ const DEFAULT_RESTART_POLICY = {
4
+ ...APP_RESTART_POLICY,
5
+ maxConcurrent: 2,
6
+ };
7
+ // The kernel's backoff, with the delays injectable for tests: under
8
+ // the default policy the delegation to restartDelayMs is literal, and
9
+ // an overridden policy runs the same law with its own numbers.
10
+ function backoffDelayMs(count, policy) {
11
+ if (policy.initialDelayMs === APP_RESTART_POLICY.initialDelayMs
12
+ && policy.maxDelayMs === APP_RESTART_POLICY.maxDelayMs)
13
+ return restartDelayMs(count);
14
+ return Math.min(policy.initialDelayMs * (2 ** Math.max(0, count - 1)), policy.maxDelayMs);
15
+ }
16
+ class RestartLimiter {
17
+ limit;
18
+ active = 0;
19
+ pending = [];
20
+ constructor(limit) {
21
+ this.limit = limit;
22
+ }
23
+ run(operation) {
24
+ return new Promise((resolve, reject) => {
25
+ this.pending.push({
26
+ operation,
27
+ reject,
28
+ resolve: resolve,
29
+ });
30
+ this.drain();
31
+ });
32
+ }
33
+ drain() {
34
+ while (this.active < this.limit && this.pending.length) {
35
+ const item = this.pending.shift();
36
+ this.active += 1;
37
+ Promise.resolve()
38
+ .then(item.operation)
39
+ .then(item.resolve, item.reject)
40
+ .finally(() => {
41
+ this.active -= 1;
42
+ this.drain();
43
+ });
44
+ }
45
+ }
46
+ }
47
+ class Restarts {
48
+ store;
49
+ changed;
50
+ restart;
51
+ faults;
52
+ policy;
53
+ limiter;
54
+ timers = new Map();
55
+ constructor(options) {
56
+ this.store = options.store;
57
+ this.changed = options.changed;
58
+ this.restart = options.restart;
59
+ this.faults = options.faults;
60
+ this.policy = { ...DEFAULT_RESTART_POLICY, ...options.policy };
61
+ this.limiter = new RestartLimiter(this.policy.maxConcurrent);
62
+ }
63
+ failures(appId, now = Date.now()) {
64
+ const since = new Date(now - this.policy.windowMs).toISOString();
65
+ return this.store.generations.failuresSince(appId, since);
66
+ }
67
+ latch(appId, count) {
68
+ const current = this.store.updateApp(appId, {
69
+ status: 'degraded',
70
+ activeGenerationId: null,
71
+ pid: null,
72
+ error: `Failed ${count} process generations; automatic restarts paused`,
73
+ });
74
+ if (current)
75
+ this.changed(current);
76
+ return current;
77
+ }
78
+ permit(appId) {
79
+ const count = this.failures(appId).length;
80
+ if (count < this.policy.maxCrashes)
81
+ return true;
82
+ this.latch(appId, count);
83
+ return false;
84
+ }
85
+ afterFailure(appId, error) {
86
+ const app = this.store.getApp(appId);
87
+ if (!app || app.desiredState !== 'running' || app.keepAlive === false)
88
+ return app;
89
+ const count = this.failures(appId).length;
90
+ if (count >= this.policy.maxCrashes)
91
+ return this.latch(appId, count);
92
+ const current = this.store.updateApp(appId, {
93
+ status: 'restarting',
94
+ activeGenerationId: null,
95
+ pid: null,
96
+ error: String(error || 'Process generation failed'),
97
+ });
98
+ if (current)
99
+ this.changed(current);
100
+ this.schedule(appId, backoffDelayMs(count, this.policy));
101
+ return current;
102
+ }
103
+ schedule(appId, delay) {
104
+ this.cancel(appId);
105
+ const timer = setTimeout(() => {
106
+ this.timers.delete(appId);
107
+ this.faults.guardAsync(appId, 'Automatic restart failed', async () => {
108
+ try {
109
+ await this.launch(appId);
110
+ }
111
+ catch (error) {
112
+ this.afterFailure(appId, errorMessage(error));
113
+ }
114
+ });
115
+ }, delay);
116
+ timer.unref?.();
117
+ this.timers.set(appId, timer);
118
+ }
119
+ async launch(appId) {
120
+ if (!this.permit(appId))
121
+ return this.store.getApp(appId);
122
+ return this.limiter.run(() => this.restart(appId));
123
+ }
124
+ cancel(appId) {
125
+ const timer = this.timers.get(appId);
126
+ if (timer)
127
+ clearTimeout(timer);
128
+ this.timers.delete(appId);
129
+ }
130
+ close() {
131
+ for (const timer of this.timers.values())
132
+ clearTimeout(timer);
133
+ this.timers.clear();
134
+ }
135
+ }
136
+ export { DEFAULT_RESTART_POLICY, RestartLimiter, Restarts, backoffDelayMs };
137
+ //# sourceMappingURL=restarts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restarts.js","sourceRoot":"","sources":["../src/restarts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { Faults } from './faults.js';
2
+ import { Health } from './health.js';
3
+ import { Operations } from './operations.js';
4
+ import { Ownership } from './ownership.js';
5
+ import { Releases } from './releases.js';
6
+ import { Store } from './store.js';
7
+ import { Supervisor } from './supervisor.js';
8
+ import type { Apps } from './apps.js';
9
+ import type { AppsOptions } from './types.js';
10
+ declare function createRuntime(service: Apps, options: AppsOptions): {
11
+ faults: Faults;
12
+ health: Health;
13
+ operations: Operations;
14
+ ownership: Ownership;
15
+ releases: Releases;
16
+ store: Store;
17
+ supervisor: Supervisor;
18
+ };
19
+ export { createRuntime };
@@ -0,0 +1,57 @@
1
+ import path from 'node:path';
2
+ import { invoke, invokeAsync } from './boundaries.js';
3
+ import { Faults } from './faults.js';
4
+ import { Health } from './health.js';
5
+ import { Operations } from './operations.js';
6
+ import { Ownership } from './ownership.js';
7
+ import { Releases } from './releases.js';
8
+ import { Store } from './store.js';
9
+ import { Supervisor } from './supervisor.js';
10
+ function createRuntime(service, options) {
11
+ const customRunCommand = options.runCommand;
12
+ const customSpawn = options.spawn;
13
+ const store = options.store || new Store(path.join(service.stateDir, 'apps.db'));
14
+ const ownership = options.ownership || new Ownership(service.stateDir);
15
+ const operations = new Operations(ownership, () => service.changes.settle());
16
+ const faults = new Faults({
17
+ store,
18
+ changed: (app) => service.changed(app),
19
+ onError: options.onError,
20
+ });
21
+ const releases = new Releases({
22
+ stateDir: service.stateDir,
23
+ store,
24
+ buildTimeoutMs: options.buildTimeoutMs,
25
+ runCommand: typeof customRunCommand === 'function'
26
+ ? (...args) => invokeAsync(customRunCommand, args, options.onError, 'Build command callback failed')
27
+ : undefined,
28
+ buildEnv: (app) => service.environment(app),
29
+ });
30
+ const changed = (app) => service.changed(app);
31
+ const supervisor = new Supervisor({
32
+ stateDir: service.stateDir,
33
+ store,
34
+ settleMs: options.settleMs,
35
+ stopGraceMs: options.stopGraceMs,
36
+ killGraceMs: options.killGraceMs,
37
+ maxLogBytes: options.maxLogBytes,
38
+ reconcileIntervalMs: options.reconcileIntervalMs,
39
+ restartPolicy: options.restartPolicy,
40
+ environment: (app) => service.environment(app),
41
+ restart: (appId) => operations.run(appId, 'restart', () => (service.supervisor.start(appId, { automatic: true }))),
42
+ changed,
43
+ faults,
44
+ spawn: typeof customSpawn === 'function'
45
+ ? (...args) => invoke(customSpawn, args, options.onError, 'Spawn callback failed')
46
+ : undefined,
47
+ });
48
+ const health = new Health({
49
+ store,
50
+ changed,
51
+ faults,
52
+ policy: options.healthPolicy,
53
+ });
54
+ return { faults, health, operations, ownership, releases, store, supervisor };
55
+ }
56
+ export { createRuntime };
57
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type Database from 'better-sqlite3';
2
+ declare function migrate(db: Database.Database): void;
3
+ export { migrate };
package/dist/schema.js ADDED
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+ function hasColumn(db, table, column) {
3
+ return db.pragma(`table_info(${table})`)
4
+ .some((entry) => entry.name === column);
5
+ }
6
+ function migrate(db) {
7
+ db.exec(`
8
+ CREATE TABLE IF NOT EXISTS apps (
9
+ id TEXT PRIMARY KEY,
10
+ name TEXT NOT NULL,
11
+ description TEXT NOT NULL DEFAULT '',
12
+ app_ref TEXT NOT NULL UNIQUE,
13
+ public_url TEXT NOT NULL,
14
+ port INTEGER NOT NULL UNIQUE,
15
+ source_dir TEXT NOT NULL,
16
+ data_dir TEXT NOT NULL,
17
+ active_release_id TEXT,
18
+ active_deployment_id TEXT,
19
+ active_generation_id TEXT,
20
+ desired_state TEXT NOT NULL DEFAULT 'running',
21
+ status TEXT NOT NULL DEFAULT 'registered',
22
+ pid INTEGER,
23
+ dns_connected INTEGER NOT NULL DEFAULT 1,
24
+ autostart INTEGER NOT NULL DEFAULT 1,
25
+ keep_alive INTEGER NOT NULL DEFAULT 1,
26
+ error TEXT,
27
+ ready_at TEXT,
28
+ last_heartbeat_at TEXT,
29
+ heartbeat_interval_ms INTEGER,
30
+ created_at TEXT NOT NULL,
31
+ updated_at TEXT NOT NULL
32
+ );
33
+ CREATE TABLE IF NOT EXISTS releases (
34
+ id TEXT PRIMARY KEY,
35
+ app_id TEXT NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
36
+ digest TEXT NOT NULL,
37
+ directory TEXT NOT NULL,
38
+ manifest_json TEXT NOT NULL,
39
+ created_at TEXT NOT NULL,
40
+ UNIQUE(app_id, digest)
41
+ );
42
+ CREATE TABLE IF NOT EXISTS deployments (
43
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
44
+ id TEXT NOT NULL UNIQUE,
45
+ app_id TEXT NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
46
+ release_id TEXT NOT NULL REFERENCES releases(id),
47
+ directory TEXT NOT NULL,
48
+ reason TEXT NOT NULL,
49
+ status TEXT NOT NULL,
50
+ error TEXT,
51
+ created_at TEXT NOT NULL,
52
+ activated_at TEXT
53
+ );
54
+ CREATE TABLE IF NOT EXISTS process_generations (
55
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
56
+ id TEXT NOT NULL UNIQUE,
57
+ app_id TEXT NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
58
+ deployment_id TEXT NOT NULL REFERENCES deployments(id) ON DELETE CASCADE,
59
+ pid INTEGER,
60
+ process_identity TEXT,
61
+ status TEXT NOT NULL,
62
+ created_at TEXT NOT NULL,
63
+ started_at TEXT,
64
+ stable_at TEXT,
65
+ exited_at TEXT,
66
+ exit_code INTEGER,
67
+ exit_signal TEXT,
68
+ error TEXT
69
+ );
70
+ CREATE INDEX IF NOT EXISTS deployments_app_sequence
71
+ ON deployments(app_id, sequence DESC);
72
+ CREATE INDEX IF NOT EXISTS generations_app_sequence
73
+ ON process_generations(app_id, sequence DESC);
74
+ `);
75
+ if (!hasColumn(db, 'apps', 'active_generation_id')) {
76
+ db.exec('ALTER TABLE apps ADD COLUMN active_generation_id TEXT');
77
+ }
78
+ }
79
+ export { migrate };
80
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAIb,SAAS,SAAS,CAAC,EAAqB,EAAE,KAAa,EAAE,MAAc;IACrE,OAAQ,EAAE,CAAC,MAAM,CAAC,cAAc,KAAK,GAAG,CAA6B;SAClE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,OAAO,CAAC,EAAqB;IACpC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEP,CAAC,CAAC;IACH,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,CAAC;QACnD,EAAE,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Apps } from './apps.js';
2
+ import type { AppView, ExistingAppInput } from './types.js';
3
+ declare function importExisting(apps: Apps, input: ExistingAppInput): Promise<AppView>;
4
+ export { importExisting };