threadforge 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/LICENSE +21 -0
- package/README.md +152 -0
- package/bin/forge.js +1050 -0
- package/bin/host-commands.js +344 -0
- package/bin/platform-commands.js +570 -0
- package/package.json +71 -0
- package/shared/auth.js +475 -0
- package/src/core/DirectMessageBus.js +364 -0
- package/src/core/EndpointResolver.js +247 -0
- package/src/core/ForgeContext.js +2227 -0
- package/src/core/ForgeHost.js +122 -0
- package/src/core/ForgePlatform.js +145 -0
- package/src/core/Ingress.js +768 -0
- package/src/core/Interceptors.js +420 -0
- package/src/core/MessageBus.js +310 -0
- package/src/core/Prometheus.js +305 -0
- package/src/core/RequestContext.js +413 -0
- package/src/core/RoutingStrategy.js +316 -0
- package/src/core/Supervisor.js +1306 -0
- package/src/core/ThreadAllocator.js +196 -0
- package/src/core/WorkerChannelManager.js +879 -0
- package/src/core/config.js +624 -0
- package/src/core/host-config.js +311 -0
- package/src/core/network-utils.js +166 -0
- package/src/core/platform-config.js +308 -0
- package/src/decorators/ServiceProxy.js +899 -0
- package/src/decorators/index.js +571 -0
- package/src/deploy/NginxGenerator.js +865 -0
- package/src/deploy/PlatformManifestGenerator.js +96 -0
- package/src/deploy/RouteManifestGenerator.js +112 -0
- package/src/deploy/index.js +984 -0
- package/src/frontend/FrontendDevLifecycle.js +65 -0
- package/src/frontend/FrontendPluginOrchestrator.js +187 -0
- package/src/frontend/SiteResolver.js +63 -0
- package/src/frontend/StaticMountRegistry.js +90 -0
- package/src/frontend/index.js +5 -0
- package/src/frontend/plugins/index.js +2 -0
- package/src/frontend/plugins/viteFrontend.js +79 -0
- package/src/frontend/types.js +35 -0
- package/src/index.js +56 -0
- package/src/internals.js +31 -0
- package/src/plugins/PluginManager.js +537 -0
- package/src/plugins/ScopedPostgres.js +192 -0
- package/src/plugins/ScopedRedis.js +142 -0
- package/src/plugins/index.js +1729 -0
- package/src/registry/ServiceRegistry.js +796 -0
- package/src/scaling/ScaleAdvisor.js +442 -0
- package/src/services/Service.js +195 -0
- package/src/services/worker-bootstrap.js +676 -0
- package/src/templates/auth-service.js +65 -0
- package/src/templates/identity-service.js +75 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ForgeHost — Multi-Project Hosting Platform
|
|
3
|
+
*
|
|
4
|
+
* Wraps Supervisor via composition to manage multiple projects
|
|
5
|
+
* sharing one machine, one Postgres, one Redis, and one auth system.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
*
|
|
9
|
+
* import { ForgeHost, defineHost } from 'threadforge';
|
|
10
|
+
*
|
|
11
|
+
* const hostConfig = defineHost({ ... });
|
|
12
|
+
* const host = new ForgeHost(hostConfig);
|
|
13
|
+
* await host.start();
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Supervisor } from "./Supervisor.js";
|
|
17
|
+
import { resolveAndDefine, resolveHostConfig } from "./host-config.js";
|
|
18
|
+
|
|
19
|
+
export class ForgeHost {
|
|
20
|
+
/**
|
|
21
|
+
* @param {Object} hostConfig - Output from defineHost() or raw host config
|
|
22
|
+
* @param {Object} [options] - Supervisor options override
|
|
23
|
+
*/
|
|
24
|
+
constructor(hostConfig, options = {}) {
|
|
25
|
+
this._hostConfig = hostConfig;
|
|
26
|
+
this._options = options;
|
|
27
|
+
this._supervisor = null;
|
|
28
|
+
this._resolvedConfig = null;
|
|
29
|
+
this._hostMeta = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve all project configs, merge into one flat config,
|
|
34
|
+
* and start the Supervisor.
|
|
35
|
+
*/
|
|
36
|
+
async start() {
|
|
37
|
+
// Resolve host config → flat service map → defineServices()
|
|
38
|
+
this._resolvedConfig = await resolveAndDefine(this._hostConfig);
|
|
39
|
+
this._hostMeta = this._resolvedConfig._hostMeta;
|
|
40
|
+
|
|
41
|
+
// Store host meta as JSON for workers to read
|
|
42
|
+
this._resolvedConfig._hostMetaJSON = JSON.stringify(this._hostMeta);
|
|
43
|
+
|
|
44
|
+
// Create and start the Supervisor with the merged config
|
|
45
|
+
this._supervisor = new Supervisor(this._resolvedConfig, this._options);
|
|
46
|
+
await this._supervisor.start();
|
|
47
|
+
|
|
48
|
+
this._printHostInfo();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gracefully shut down the entire host.
|
|
53
|
+
*/
|
|
54
|
+
async shutdown() {
|
|
55
|
+
if (this._supervisor) {
|
|
56
|
+
await this._supervisor.shutdown();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get status with per-project breakdown.
|
|
62
|
+
*/
|
|
63
|
+
status() {
|
|
64
|
+
if (!this._supervisor || !this._hostMeta) return null;
|
|
65
|
+
|
|
66
|
+
const base = this._supervisor._status();
|
|
67
|
+
const projects = {};
|
|
68
|
+
|
|
69
|
+
for (const [projectId, meta] of Object.entries(this._hostMeta)) {
|
|
70
|
+
const projectGroups = base.processGroups.filter((pg) =>
|
|
71
|
+
pg.services.some((s) => meta.services.includes(s.name)),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
projects[projectId] = {
|
|
75
|
+
domain: meta.domain,
|
|
76
|
+
services: meta.services.length,
|
|
77
|
+
workers: projectGroups.reduce((sum, pg) => sum + pg.workers, 0),
|
|
78
|
+
schema: meta.schema,
|
|
79
|
+
keyPrefix: meta.keyPrefix,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
...base,
|
|
85
|
+
hostMode: true,
|
|
86
|
+
domain: this._resolvedConfig._hostDomain,
|
|
87
|
+
projects,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the list of project IDs.
|
|
93
|
+
*/
|
|
94
|
+
get projectIds() {
|
|
95
|
+
return Object.keys(this._hostMeta ?? {});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get metadata for a specific project.
|
|
100
|
+
*/
|
|
101
|
+
getProject(projectId) {
|
|
102
|
+
return this._hostMeta?.[projectId] ?? null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_printHostInfo() {
|
|
106
|
+
console.log(`\n ForgeHost — Multi-Project Platform`);
|
|
107
|
+
console.log(` ─────────────────────────────────────`);
|
|
108
|
+
if (this._resolvedConfig._hostDomain) {
|
|
109
|
+
console.log(` Domain: ${this._resolvedConfig._hostDomain}`);
|
|
110
|
+
}
|
|
111
|
+
console.log(` Projects: ${Object.keys(this._hostMeta).length}`);
|
|
112
|
+
console.log("");
|
|
113
|
+
|
|
114
|
+
for (const [projectId, meta] of Object.entries(this._hostMeta)) {
|
|
115
|
+
const domain = meta.domain ? ` (${meta.domain})` : "";
|
|
116
|
+
console.log(` ${projectId}${domain}`);
|
|
117
|
+
console.log(` Services: ${meta.services.join(", ")}`);
|
|
118
|
+
console.log(` Schema: ${meta.schema}`);
|
|
119
|
+
}
|
|
120
|
+
console.log("");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ForgePlatform — Platform Mode Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Wraps ForgeHost to provide a platform-level lifecycle with
|
|
5
|
+
* domain-based multi-app routing, shared services, and
|
|
6
|
+
* platform-specific banner output.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
*
|
|
10
|
+
* import { ForgePlatform, definePlatform } from 'threadforge';
|
|
11
|
+
*
|
|
12
|
+
* const config = definePlatform({ ... });
|
|
13
|
+
* const platform = new ForgePlatform(config);
|
|
14
|
+
* await platform.start();
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { ForgeHost } from "./ForgeHost.js";
|
|
18
|
+
import { resolveAppDomains, transformToHostConfig } from "./platform-config.js";
|
|
19
|
+
|
|
20
|
+
export class ForgePlatform {
|
|
21
|
+
/**
|
|
22
|
+
* @param {Object} platformConfig - Output from definePlatform() or raw platform config
|
|
23
|
+
* @param {Object} [options] - Supervisor options override
|
|
24
|
+
*/
|
|
25
|
+
constructor(platformConfig, options = {}) {
|
|
26
|
+
this._platformConfig = platformConfig;
|
|
27
|
+
this._options = options;
|
|
28
|
+
this._host = null;
|
|
29
|
+
this._resolvedConfig = null;
|
|
30
|
+
this._hostMeta = null;
|
|
31
|
+
this._platformApps = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Start the platform: resolve config, create ForgeHost, start.
|
|
36
|
+
*/
|
|
37
|
+
async start() {
|
|
38
|
+
const raw = this._platformConfig._isPlatformConfig
|
|
39
|
+
? this._platformConfig._raw
|
|
40
|
+
: this._platformConfig;
|
|
41
|
+
|
|
42
|
+
const hostConfig = transformToHostConfig(raw);
|
|
43
|
+
this._platformApps = raw.platform.apps;
|
|
44
|
+
|
|
45
|
+
this._host = new ForgeHost(hostConfig, this._options);
|
|
46
|
+
await this._host.start();
|
|
47
|
+
|
|
48
|
+
this._resolvedConfig = this._host._resolvedConfig;
|
|
49
|
+
this._hostMeta = this._host._resolvedConfig._hostMeta;
|
|
50
|
+
this._resolvedConfig._isPlatformMode = true;
|
|
51
|
+
|
|
52
|
+
this._printPlatformBanner();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Gracefully shut down the platform.
|
|
57
|
+
*/
|
|
58
|
+
async shutdown() {
|
|
59
|
+
if (this._host) {
|
|
60
|
+
await this._host.shutdown();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the apps map: appId → { domains, services, schema }
|
|
66
|
+
*/
|
|
67
|
+
get apps() {
|
|
68
|
+
if (!this._platformApps || !this._hostMeta) return {};
|
|
69
|
+
|
|
70
|
+
const result = {};
|
|
71
|
+
for (const [appId, app] of Object.entries(this._platformApps)) {
|
|
72
|
+
const meta = this._hostMeta[appId];
|
|
73
|
+
result[appId] = {
|
|
74
|
+
domains: resolveAppDomains(app),
|
|
75
|
+
services: meta?.services ?? [],
|
|
76
|
+
schema: meta?.schema ?? appId,
|
|
77
|
+
static: app.static ?? null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the host metadata (from ForgeHost).
|
|
85
|
+
*/
|
|
86
|
+
get hostMeta() {
|
|
87
|
+
return this._hostMeta ?? {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get platform status with per-app breakdown.
|
|
92
|
+
*/
|
|
93
|
+
status() {
|
|
94
|
+
if (!this._host) return null;
|
|
95
|
+
|
|
96
|
+
const base = this._host.status();
|
|
97
|
+
return {
|
|
98
|
+
...base,
|
|
99
|
+
platformMode: true,
|
|
100
|
+
apps: this.apps,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Collect all service names that belong to a project (not shared).
|
|
106
|
+
*/
|
|
107
|
+
_collectProjectServiceNames() {
|
|
108
|
+
const names = new Set();
|
|
109
|
+
for (const meta of Object.values(this._hostMeta ?? {})) {
|
|
110
|
+
for (const svc of meta.services) names.add(svc);
|
|
111
|
+
}
|
|
112
|
+
return names;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_printPlatformBanner() {
|
|
116
|
+
console.log(`\n ForgePlatform — Multi-App Platform Mode`);
|
|
117
|
+
console.log(` ──────────────────────────────────────────`);
|
|
118
|
+
|
|
119
|
+
const apps = this._platformApps;
|
|
120
|
+
if (!apps) return;
|
|
121
|
+
|
|
122
|
+
console.log(` Apps: ${Object.keys(apps).length}`);
|
|
123
|
+
console.log("");
|
|
124
|
+
|
|
125
|
+
for (const [appId, app] of Object.entries(apps)) {
|
|
126
|
+
const domains = resolveAppDomains(app);
|
|
127
|
+
const meta = this._hostMeta?.[appId];
|
|
128
|
+
console.log(` ${appId}`);
|
|
129
|
+
console.log(` Domains: ${domains.join(", ")}`);
|
|
130
|
+
if (meta) {
|
|
131
|
+
console.log(` Services: ${meta.services.join(", ")}`);
|
|
132
|
+
console.log(` Schema: ${meta.schema}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const projectServices = this._collectProjectServiceNames();
|
|
137
|
+
const allServices = Object.keys(this._resolvedConfig?.services ?? {});
|
|
138
|
+
const sharedServices = allServices.filter((s) => !projectServices.has(s));
|
|
139
|
+
|
|
140
|
+
if (sharedServices.length > 0) {
|
|
141
|
+
console.log(`\n Shared: ${sharedServices.join(", ")}`);
|
|
142
|
+
}
|
|
143
|
+
console.log("");
|
|
144
|
+
}
|
|
145
|
+
}
|