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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/bin/forge.js +1050 -0
  4. package/bin/host-commands.js +344 -0
  5. package/bin/platform-commands.js +570 -0
  6. package/package.json +71 -0
  7. package/shared/auth.js +475 -0
  8. package/src/core/DirectMessageBus.js +364 -0
  9. package/src/core/EndpointResolver.js +247 -0
  10. package/src/core/ForgeContext.js +2227 -0
  11. package/src/core/ForgeHost.js +122 -0
  12. package/src/core/ForgePlatform.js +145 -0
  13. package/src/core/Ingress.js +768 -0
  14. package/src/core/Interceptors.js +420 -0
  15. package/src/core/MessageBus.js +310 -0
  16. package/src/core/Prometheus.js +305 -0
  17. package/src/core/RequestContext.js +413 -0
  18. package/src/core/RoutingStrategy.js +316 -0
  19. package/src/core/Supervisor.js +1306 -0
  20. package/src/core/ThreadAllocator.js +196 -0
  21. package/src/core/WorkerChannelManager.js +879 -0
  22. package/src/core/config.js +624 -0
  23. package/src/core/host-config.js +311 -0
  24. package/src/core/network-utils.js +166 -0
  25. package/src/core/platform-config.js +308 -0
  26. package/src/decorators/ServiceProxy.js +899 -0
  27. package/src/decorators/index.js +571 -0
  28. package/src/deploy/NginxGenerator.js +865 -0
  29. package/src/deploy/PlatformManifestGenerator.js +96 -0
  30. package/src/deploy/RouteManifestGenerator.js +112 -0
  31. package/src/deploy/index.js +984 -0
  32. package/src/frontend/FrontendDevLifecycle.js +65 -0
  33. package/src/frontend/FrontendPluginOrchestrator.js +187 -0
  34. package/src/frontend/SiteResolver.js +63 -0
  35. package/src/frontend/StaticMountRegistry.js +90 -0
  36. package/src/frontend/index.js +5 -0
  37. package/src/frontend/plugins/index.js +2 -0
  38. package/src/frontend/plugins/viteFrontend.js +79 -0
  39. package/src/frontend/types.js +35 -0
  40. package/src/index.js +56 -0
  41. package/src/internals.js +31 -0
  42. package/src/plugins/PluginManager.js +537 -0
  43. package/src/plugins/ScopedPostgres.js +192 -0
  44. package/src/plugins/ScopedRedis.js +142 -0
  45. package/src/plugins/index.js +1729 -0
  46. package/src/registry/ServiceRegistry.js +796 -0
  47. package/src/scaling/ScaleAdvisor.js +442 -0
  48. package/src/services/Service.js +195 -0
  49. package/src/services/worker-bootstrap.js +676 -0
  50. package/src/templates/auth-service.js +65 -0
  51. package/src/templates/identity-service.js +75 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Platform Manifest Generator
3
+ *
4
+ * Generates a `routes/platform.yaml` manifest for ForgeProxy
5
+ * that describes the platform topology: apps, domains, shared services.
6
+ *
7
+ * ForgeProxy already loads `routes/*.yaml` with `host` and `project_id`
8
+ * fields — this file is supplementary metadata for tooling.
9
+ */
10
+
11
+ import { resolveAppDomains } from "../core/platform-config.js";
12
+
13
+ function yamlString(value) {
14
+ const text = String(value ?? "");
15
+ return `'${text.replace(/'/g, "''")}'`;
16
+ }
17
+
18
+ function yamlArray(values = []) {
19
+ return `[${values.map((value) => yamlString(value)).join(", ")}]`;
20
+ }
21
+
22
+ /**
23
+ * Generate a platform.yaml manifest.
24
+ *
25
+ * @param {Object} platformConfig - The platform block from the user config
26
+ * @param {Object} hostMeta - From resolveHostConfig().hostMeta
27
+ * @param {Object} [sites] - From resolveHostConfig().sites
28
+ * @returns {string} YAML string
29
+ */
30
+ export function generatePlatformManifest(platformConfig, hostMeta, sites = {}) {
31
+ const apps = platformConfig.apps ?? {};
32
+ const globalAuth = platformConfig.globalAuth ?? false;
33
+
34
+ let yaml = `# Auto-generated by: forge platform generate\n`;
35
+ yaml += `# Do not edit — regenerate with: forge platform generate\n\n`;
36
+
37
+ yaml += `apps:\n`;
38
+ for (const [appId, app] of Object.entries(apps)) {
39
+ const domains = resolveAppDomains(app);
40
+ const meta = hostMeta?.[appId];
41
+
42
+ yaml += ` ${appId}:\n`;
43
+ yaml += ` domains: ${yamlArray(domains)}\n`;
44
+
45
+ // Reference per-service route manifests
46
+ if (meta?.services?.length > 0) {
47
+ const routeFiles = meta.services.map((s) => `routes/${s.replace(/:/g, "_")}.yaml`);
48
+ if (routeFiles.length === 1) {
49
+ yaml += ` routes: ${yamlString(routeFiles[0])}\n`;
50
+ } else {
51
+ yaml += ` routes:\n`;
52
+ for (const f of routeFiles) {
53
+ yaml += ` - ${yamlString(f)}\n`;
54
+ }
55
+ }
56
+ }
57
+
58
+ if (globalAuth) {
59
+ yaml += ` auth_required: true\n`;
60
+ yaml += ` public_paths: ${yamlArray(["/auth/*", "/public/*"])}\n`;
61
+ }
62
+
63
+ if (app.static) {
64
+ yaml += ` static: ${yamlString(app.static)}\n`;
65
+ }
66
+
67
+ const site = sites?.[appId];
68
+ if (site?.frontend) {
69
+ yaml += ` frontend:\n`;
70
+ yaml += ` plugin: ${yamlString(site.frontend.plugin)}\n`;
71
+ yaml += ` base_path: ${yamlString(site.frontend.basePath)}\n`;
72
+ yaml += ` out_dir: ${yamlString(site.frontend.outDir)}\n`;
73
+ yaml += ` spa_fallback: ${site.frontend.spaFallback ? "true" : "false"}\n`;
74
+ }
75
+ }
76
+
77
+ // Shared services section (auth + identity when globalAuth is enabled)
78
+ if (globalAuth) {
79
+ const sharedEntries = [
80
+ { name: "auth", prefix: "/auth", mountOnAll: true },
81
+ { name: "identity", prefix: "/identity", mountOnAll: false },
82
+ ];
83
+
84
+ yaml += `\nshared_services:\n`;
85
+ for (const entry of sharedEntries) {
86
+ yaml += ` ${entry.name}:\n`;
87
+ yaml += ` routes: ${yamlString(`routes/${entry.name}.yaml`)}\n`;
88
+ if (entry.mountOnAll) {
89
+ yaml += ` mount_on_all_apps: true\n`;
90
+ }
91
+ yaml += ` prefix: ${yamlString(entry.prefix)}\n`;
92
+ }
93
+ }
94
+
95
+ return yaml;
96
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Route Manifest Generator
3
+ *
4
+ * Reads service classes, extracts route + internal method declarations,
5
+ * and generates YAML route manifests for ForgeProxy.
6
+ *
7
+ * `forge generate` produces:
8
+ *
9
+ * # routes/users.yaml
10
+ * service: users
11
+ * prefix: /api/users
12
+ * auth: required
13
+ * rate_limit: 1000/min
14
+ *
15
+ * routes:
16
+ * - method: GET
17
+ * path: /:id
18
+ * handler: getUser
19
+ *
20
+ * # Backend-only methods (callable via IPC, not via HTTP)
21
+ * internal_methods:
22
+ * - name: validatePassword
23
+ * local_only: true # zero-copy, same-process only
24
+ */
25
+
26
+ import { getContract, getInternalMethods } from "../decorators/index.js";
27
+
28
+ /**
29
+ * Generate a route manifest for a single service.
30
+ */
31
+ export function generateRouteManifest(serviceName, ServiceClass, serviceConfig = {}) {
32
+ const contract = getContract(ServiceClass);
33
+ if (!contract || (contract.routes.length === 0 && contract.methods.size === 0)) return null;
34
+
35
+ const prefix = ServiceClass.prefix ?? serviceConfig.prefix ?? `/${serviceName}`;
36
+ const auth = ServiceClass.auth ?? serviceConfig.auth ?? "required";
37
+ const rateLimit = ServiceClass.rateLimit ?? serviceConfig.rateLimit ?? null;
38
+
39
+ let yaml = `# Auto-generated by: forge generate\n`;
40
+ yaml += `# Source: ${serviceName} service\n`;
41
+ yaml += `# Do not edit — regenerate with: forge generate\n\n`;
42
+ yaml += `service: '${serviceName}'\n`;
43
+ yaml += `prefix: '${prefix}'\n`;
44
+ yaml += `auth: '${auth}'\n`;
45
+
46
+ if (rateLimit) {
47
+ yaml += `rate_limit: '${rateLimit}'\n`;
48
+ }
49
+
50
+ // Multi-project host fields
51
+ if (serviceConfig._projectId) {
52
+ yaml += `project_id: '${serviceConfig._projectId}'\n`;
53
+ }
54
+ if (serviceConfig._hostDomain) {
55
+ yaml += `host: '${serviceConfig._hostDomain}'\n`;
56
+ }
57
+
58
+ // HTTP routes (ForgeProxy handles these)
59
+ if (contract.routes.length > 0) {
60
+ yaml += `\nroutes:\n`;
61
+ for (const route of contract.routes) {
62
+ if (ServiceClass.prototype && typeof ServiceClass.prototype[route.handlerName] !== "function") {
63
+ console.warn(
64
+ `[Deploy] Route handler "${route.handlerName}" for ${route.httpMethod} ${route.path} ` +
65
+ `does not exist on ${serviceName}. Check contract definition.`,
66
+ );
67
+ }
68
+
69
+ yaml += ` - method: ${route.httpMethod}\n`;
70
+ yaml += ` path: ${route.path}\n`;
71
+ yaml += ` handler: ${route.handlerName}\n`;
72
+
73
+ if (route.auth && route.auth !== auth) {
74
+ yaml += ` auth: ${route.auth}\n`;
75
+ }
76
+ if (route.rateLimit) {
77
+ yaml += ` rate_limit: ${route.rateLimit}\n`;
78
+ }
79
+ if (route.cacheSecs) {
80
+ yaml += ` cache_secs: ${route.cacheSecs}\n`;
81
+ }
82
+ }
83
+ }
84
+
85
+ // Internal methods (backend-only, not routed via HTTP)
86
+ const internalMethods = getInternalMethods(ServiceClass);
87
+ if (internalMethods.length > 0) {
88
+ yaml += `\n# Backend-only methods (callable via IPC, not via HTTP)\n`;
89
+ yaml += `internal_methods:\n`;
90
+ for (const method of internalMethods) {
91
+ const meta = contract.methods.get(method);
92
+ yaml += ` - name: ${method}\n`;
93
+ if (meta?.options?.localOnly) {
94
+ yaml += ` local_only: true\n`;
95
+ }
96
+ }
97
+ }
98
+
99
+ return yaml;
100
+ }
101
+
102
+ /**
103
+ * Generate route manifests for all services.
104
+ */
105
+ export function generateAllRouteManifests(serviceClasses, serviceConfigs = {}) {
106
+ const manifests = new Map();
107
+ for (const [name, ServiceClass] of Object.entries(serviceClasses)) {
108
+ const yaml = generateRouteManifest(name, ServiceClass, serviceConfigs[name] ?? {});
109
+ if (yaml) manifests.set(name, yaml);
110
+ }
111
+ return manifests;
112
+ }