threadforge 0.1.1 → 0.2.1
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/README.md +52 -20
- package/bin/forge.js +2 -1058
- package/bin/host-commands.d.ts +2 -0
- package/bin/host-commands.d.ts.map +1 -0
- package/bin/host-commands.js +7 -8
- package/bin/platform-commands.d.ts +2 -0
- package/bin/platform-commands.d.ts.map +1 -0
- package/bin/platform-commands.js +118 -36
- package/dist/cli/base-command.d.ts +12 -0
- package/dist/cli/base-command.d.ts.map +1 -0
- package/dist/cli/base-command.js +25 -0
- package/dist/cli/base-command.js.map +1 -0
- package/dist/cli/commands/build.d.ts +10 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +110 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/deploy.d.ts +12 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -0
- package/dist/cli/commands/deploy.js +143 -0
- package/dist/cli/commands/deploy.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +10 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +138 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +10 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +76 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/host.d.ts +8 -0
- package/dist/cli/commands/host.d.ts.map +1 -0
- package/dist/cli/commands/host.js +20 -0
- package/dist/cli/commands/host.js.map +1 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +246 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/platform.d.ts +8 -0
- package/dist/cli/commands/platform.d.ts.map +1 -0
- package/dist/cli/commands/platform.js +20 -0
- package/dist/cli/commands/platform.js.map +1 -0
- package/dist/cli/commands/restart.d.ts +8 -0
- package/dist/cli/commands/restart.d.ts.map +1 -0
- package/dist/cli/commands/restart.js +13 -0
- package/dist/cli/commands/restart.js.map +1 -0
- package/dist/cli/commands/scaffold/frontend.d.ts +10 -0
- package/dist/cli/commands/scaffold/frontend.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/frontend.js +130 -0
- package/dist/cli/commands/scaffold/frontend.js.map +1 -0
- package/dist/cli/commands/scaffold/react.d.ts +7 -0
- package/dist/cli/commands/scaffold/react.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/react.js +12 -0
- package/dist/cli/commands/scaffold/react.js.map +1 -0
- package/dist/cli/commands/scale.d.ts +8 -0
- package/dist/cli/commands/scale.d.ts.map +1 -0
- package/dist/cli/commands/scale.js +13 -0
- package/dist/cli/commands/scale.js.map +1 -0
- package/dist/cli/commands/start.d.ts +10 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +71 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.d.ts +11 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +60 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/stop.d.ts +10 -0
- package/dist/cli/commands/stop.d.ts.map +1 -0
- package/dist/cli/commands/stop.js +89 -0
- package/dist/cli/commands/stop.js.map +1 -0
- package/dist/cli/util/config-discovery.d.ts +8 -0
- package/dist/cli/util/config-discovery.d.ts.map +1 -0
- package/dist/cli/util/config-discovery.js +70 -0
- package/dist/cli/util/config-discovery.js.map +1 -0
- package/dist/cli/util/config-patcher.d.ts +17 -0
- package/dist/cli/util/config-patcher.d.ts.map +1 -0
- package/dist/cli/util/config-patcher.js +439 -0
- package/dist/cli/util/config-patcher.js.map +1 -0
- package/dist/cli/util/frontend-dev.d.ts +8 -0
- package/dist/cli/util/frontend-dev.d.ts.map +1 -0
- package/dist/cli/util/frontend-dev.js +117 -0
- package/dist/cli/util/frontend-dev.js.map +1 -0
- package/dist/cli/util/process.d.ts +5 -0
- package/dist/cli/util/process.d.ts.map +1 -0
- package/dist/cli/util/process.js +17 -0
- package/dist/cli/util/process.js.map +1 -0
- package/dist/cli/util/templates.d.ts +10 -0
- package/dist/cli/util/templates.d.ts.map +1 -0
- package/dist/cli/util/templates.js +157 -0
- package/dist/cli/util/templates.js.map +1 -0
- package/dist/core/AlertSink.d.ts +83 -0
- package/dist/core/AlertSink.d.ts.map +1 -0
- package/dist/core/AlertSink.js +126 -0
- package/dist/core/AlertSink.js.map +1 -0
- package/dist/core/DirectMessageBus.d.ts +88 -0
- package/dist/core/DirectMessageBus.d.ts.map +1 -0
- package/dist/core/DirectMessageBus.js +352 -0
- package/dist/core/DirectMessageBus.js.map +1 -0
- package/dist/core/EndpointResolver.d.ts +111 -0
- package/dist/core/EndpointResolver.d.ts.map +1 -0
- package/dist/core/EndpointResolver.js +336 -0
- package/dist/core/EndpointResolver.js.map +1 -0
- package/dist/core/ForgeContext.d.ts +221 -0
- package/dist/core/ForgeContext.d.ts.map +1 -0
- package/dist/core/ForgeContext.js +1169 -0
- package/dist/core/ForgeContext.js.map +1 -0
- package/dist/core/ForgeEndpoints.d.ts +71 -0
- package/dist/core/ForgeEndpoints.d.ts.map +1 -0
- package/dist/core/ForgeEndpoints.js +442 -0
- package/dist/core/ForgeEndpoints.js.map +1 -0
- package/dist/core/ForgeHost.d.ts +82 -0
- package/dist/core/ForgeHost.d.ts.map +1 -0
- package/dist/core/ForgeHost.js +107 -0
- package/dist/core/ForgeHost.js.map +1 -0
- package/dist/core/ForgePlatform.d.ts +96 -0
- package/dist/core/ForgePlatform.d.ts.map +1 -0
- package/dist/core/ForgePlatform.js +136 -0
- package/dist/core/ForgePlatform.js.map +1 -0
- package/dist/core/ForgeWebSocket.d.ts +56 -0
- package/dist/core/ForgeWebSocket.d.ts.map +1 -0
- package/dist/core/ForgeWebSocket.js +415 -0
- package/dist/core/ForgeWebSocket.js.map +1 -0
- package/dist/core/Ingress.d.ts +329 -0
- package/dist/core/Ingress.d.ts.map +1 -0
- package/dist/core/Ingress.js +694 -0
- package/dist/core/Ingress.js.map +1 -0
- package/dist/core/Interceptors.d.ts +134 -0
- package/dist/core/Interceptors.d.ts.map +1 -0
- package/dist/core/Interceptors.js +416 -0
- package/dist/core/Interceptors.js.map +1 -0
- package/dist/core/Logger.d.ts +20 -0
- package/dist/core/Logger.d.ts.map +1 -0
- package/dist/core/Logger.js +77 -0
- package/dist/core/Logger.js.map +1 -0
- package/dist/core/MessageBus.d.ts +15 -0
- package/dist/core/MessageBus.d.ts.map +1 -0
- package/dist/core/MessageBus.js +18 -0
- package/dist/core/MessageBus.js.map +1 -0
- package/dist/core/Prometheus.d.ts +80 -0
- package/dist/core/Prometheus.d.ts.map +1 -0
- package/dist/core/Prometheus.js +332 -0
- package/dist/core/Prometheus.js.map +1 -0
- package/dist/core/RequestContext.d.ts +214 -0
- package/dist/core/RequestContext.d.ts.map +1 -0
- package/dist/core/RequestContext.js +556 -0
- package/dist/core/RequestContext.js.map +1 -0
- package/dist/core/Router.d.ts +45 -0
- package/dist/core/Router.d.ts.map +1 -0
- package/dist/core/Router.js +285 -0
- package/dist/core/Router.js.map +1 -0
- package/dist/core/RoutingStrategy.d.ts +116 -0
- package/dist/core/RoutingStrategy.d.ts.map +1 -0
- package/dist/core/RoutingStrategy.js +306 -0
- package/dist/core/RoutingStrategy.js.map +1 -0
- package/dist/core/RpcConfig.d.ts +72 -0
- package/dist/core/RpcConfig.d.ts.map +1 -0
- package/dist/core/RpcConfig.js +127 -0
- package/dist/core/RpcConfig.js.map +1 -0
- package/dist/core/SignatureCache.d.ts +81 -0
- package/dist/core/SignatureCache.d.ts.map +1 -0
- package/dist/core/SignatureCache.js +172 -0
- package/dist/core/SignatureCache.js.map +1 -0
- package/dist/core/StaticFileServer.d.ts +34 -0
- package/dist/core/StaticFileServer.d.ts.map +1 -0
- package/dist/core/StaticFileServer.js +497 -0
- package/dist/core/StaticFileServer.js.map +1 -0
- package/dist/core/Supervisor.d.ts +198 -0
- package/dist/core/Supervisor.d.ts.map +1 -0
- package/dist/core/Supervisor.js +1418 -0
- package/dist/core/Supervisor.js.map +1 -0
- package/dist/core/ThreadAllocator.d.ts +52 -0
- package/dist/core/ThreadAllocator.d.ts.map +1 -0
- package/dist/core/ThreadAllocator.js +174 -0
- package/dist/core/ThreadAllocator.js.map +1 -0
- package/dist/core/WorkerChannelManager.d.ts +130 -0
- package/dist/core/WorkerChannelManager.d.ts.map +1 -0
- package/dist/core/WorkerChannelManager.js +956 -0
- package/dist/core/WorkerChannelManager.js.map +1 -0
- package/dist/core/config-enums.d.ts +41 -0
- package/dist/core/config-enums.d.ts.map +1 -0
- package/dist/core/config-enums.js +59 -0
- package/dist/core/config-enums.js.map +1 -0
- package/dist/core/config.d.ts +159 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +694 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/host-config.d.ts +146 -0
- package/dist/core/host-config.d.ts.map +1 -0
- package/dist/core/host-config.js +312 -0
- package/dist/core/host-config.js.map +1 -0
- package/dist/core/ipc-errors.d.ts +27 -0
- package/dist/core/ipc-errors.d.ts.map +1 -0
- package/dist/core/ipc-errors.js +36 -0
- package/dist/core/ipc-errors.js.map +1 -0
- package/dist/core/network-utils.d.ts +35 -0
- package/dist/core/network-utils.d.ts.map +1 -0
- package/dist/core/network-utils.js +145 -0
- package/dist/core/network-utils.js.map +1 -0
- package/dist/core/platform-config.d.ts +142 -0
- package/dist/core/platform-config.d.ts.map +1 -0
- package/dist/core/platform-config.js +299 -0
- package/dist/core/platform-config.js.map +1 -0
- package/dist/decorators/ServiceProxy.d.ts +175 -0
- package/dist/decorators/ServiceProxy.d.ts.map +1 -0
- package/dist/decorators/ServiceProxy.js +969 -0
- package/dist/decorators/ServiceProxy.js.map +1 -0
- package/dist/decorators/index.d.ts +146 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +545 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/deploy/NginxGenerator.d.ts +165 -0
- package/dist/deploy/NginxGenerator.d.ts.map +1 -0
- package/dist/deploy/NginxGenerator.js +781 -0
- package/dist/deploy/NginxGenerator.js.map +1 -0
- package/dist/deploy/PlatformManifestGenerator.d.ts +43 -0
- package/dist/deploy/PlatformManifestGenerator.d.ts.map +1 -0
- package/dist/deploy/PlatformManifestGenerator.js +80 -0
- package/dist/deploy/PlatformManifestGenerator.js.map +1 -0
- package/dist/deploy/RouteManifestGenerator.d.ts +42 -0
- package/dist/deploy/RouteManifestGenerator.d.ts.map +1 -0
- package/dist/deploy/RouteManifestGenerator.js +105 -0
- package/dist/deploy/RouteManifestGenerator.js.map +1 -0
- package/dist/deploy/index.d.ts +210 -0
- package/dist/deploy/index.d.ts.map +1 -0
- package/dist/deploy/index.js +918 -0
- package/dist/deploy/index.js.map +1 -0
- package/dist/frontend/FrontendDevLifecycle.d.ts +26 -0
- package/dist/frontend/FrontendDevLifecycle.d.ts.map +1 -0
- package/dist/frontend/FrontendDevLifecycle.js +60 -0
- package/dist/frontend/FrontendDevLifecycle.js.map +1 -0
- package/dist/frontend/FrontendPluginOrchestrator.d.ts +64 -0
- package/dist/frontend/FrontendPluginOrchestrator.d.ts.map +1 -0
- package/dist/frontend/FrontendPluginOrchestrator.js +167 -0
- package/dist/frontend/FrontendPluginOrchestrator.js.map +1 -0
- package/dist/frontend/SiteResolver.d.ts +33 -0
- package/dist/frontend/SiteResolver.d.ts.map +1 -0
- package/dist/frontend/SiteResolver.js +53 -0
- package/dist/frontend/SiteResolver.js.map +1 -0
- package/dist/frontend/StaticMountRegistry.d.ts +36 -0
- package/dist/frontend/StaticMountRegistry.d.ts.map +1 -0
- package/dist/frontend/StaticMountRegistry.js +94 -0
- package/dist/frontend/StaticMountRegistry.js.map +1 -0
- package/dist/frontend/index.d.ts +7 -0
- package/dist/frontend/index.d.ts.map +1 -0
- package/{src → dist}/frontend/index.js +4 -2
- package/dist/frontend/index.js.map +1 -0
- package/dist/frontend/pathUtils.d.ts +8 -0
- package/dist/frontend/pathUtils.d.ts.map +1 -0
- package/dist/frontend/pathUtils.js +17 -0
- package/dist/frontend/pathUtils.js.map +1 -0
- package/dist/frontend/plugins/index.d.ts +2 -0
- package/dist/frontend/plugins/index.d.ts.map +1 -0
- package/{src → dist}/frontend/plugins/index.js +1 -1
- package/dist/frontend/plugins/index.js.map +1 -0
- package/dist/frontend/plugins/viteFrontend.d.ts +51 -0
- package/dist/frontend/plugins/viteFrontend.d.ts.map +1 -0
- package/dist/frontend/plugins/viteFrontend.js +134 -0
- package/dist/frontend/plugins/viteFrontend.js.map +1 -0
- package/dist/frontend/types.d.ts +25 -0
- package/dist/frontend/types.d.ts.map +1 -0
- package/dist/frontend/types.js +2 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/internals.d.ts +21 -0
- package/dist/internals.d.ts.map +1 -0
- package/{src → dist}/internals.js +12 -14
- package/dist/internals.js.map +1 -0
- package/dist/plugins/PluginManager.d.ts +209 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -0
- package/dist/plugins/PluginManager.js +365 -0
- package/dist/plugins/PluginManager.js.map +1 -0
- package/dist/plugins/ScopedPostgres.d.ts +78 -0
- package/dist/plugins/ScopedPostgres.d.ts.map +1 -0
- package/dist/plugins/ScopedPostgres.js +190 -0
- package/dist/plugins/ScopedPostgres.js.map +1 -0
- package/dist/plugins/ScopedRedis.d.ts +88 -0
- package/dist/plugins/ScopedRedis.d.ts.map +1 -0
- package/dist/plugins/ScopedRedis.js +169 -0
- package/dist/plugins/ScopedRedis.js.map +1 -0
- package/dist/plugins/index.d.ts +289 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +1942 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/types.d.ts +59 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +2 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/registry/ServiceRegistry.d.ts +305 -0
- package/dist/registry/ServiceRegistry.d.ts.map +1 -0
- package/dist/registry/ServiceRegistry.js +735 -0
- package/dist/registry/ServiceRegistry.js.map +1 -0
- package/dist/scaling/ScaleAdvisor.d.ts +214 -0
- package/dist/scaling/ScaleAdvisor.d.ts.map +1 -0
- package/dist/scaling/ScaleAdvisor.js +526 -0
- package/dist/scaling/ScaleAdvisor.js.map +1 -0
- package/dist/services/Service.d.ts +164 -0
- package/dist/services/Service.d.ts.map +1 -0
- package/dist/services/Service.js +106 -0
- package/dist/services/Service.js.map +1 -0
- package/dist/services/worker-bootstrap.d.ts +15 -0
- package/dist/services/worker-bootstrap.d.ts.map +1 -0
- package/dist/services/worker-bootstrap.js +744 -0
- package/dist/services/worker-bootstrap.js.map +1 -0
- package/dist/templates/auth-service.d.ts +42 -0
- package/dist/templates/auth-service.d.ts.map +1 -0
- package/dist/templates/auth-service.js +54 -0
- package/dist/templates/auth-service.js.map +1 -0
- package/dist/templates/identity-service.d.ts +50 -0
- package/dist/templates/identity-service.d.ts.map +1 -0
- package/dist/templates/identity-service.js +62 -0
- package/dist/templates/identity-service.js.map +1 -0
- package/dist/types/contract.d.ts +120 -0
- package/dist/types/contract.d.ts.map +1 -0
- package/dist/types/contract.js +69 -0
- package/dist/types/contract.js.map +1 -0
- package/package.json +78 -20
- package/src/core/DirectMessageBus.js +0 -364
- package/src/core/EndpointResolver.js +0 -259
- package/src/core/ForgeContext.js +0 -2236
- package/src/core/ForgeHost.js +0 -122
- package/src/core/ForgePlatform.js +0 -145
- package/src/core/Ingress.js +0 -768
- package/src/core/Interceptors.js +0 -420
- package/src/core/MessageBus.js +0 -321
- package/src/core/Prometheus.js +0 -305
- package/src/core/RequestContext.js +0 -413
- package/src/core/RoutingStrategy.js +0 -330
- package/src/core/Supervisor.js +0 -1349
- package/src/core/ThreadAllocator.js +0 -196
- package/src/core/WorkerChannelManager.js +0 -879
- package/src/core/config.js +0 -637
- package/src/core/host-config.js +0 -311
- package/src/core/network-utils.js +0 -166
- package/src/core/platform-config.js +0 -308
- package/src/decorators/ServiceProxy.js +0 -904
- package/src/decorators/index.js +0 -571
- package/src/deploy/NginxGenerator.js +0 -865
- package/src/deploy/PlatformManifestGenerator.js +0 -96
- package/src/deploy/RouteManifestGenerator.js +0 -112
- package/src/deploy/index.js +0 -984
- package/src/frontend/FrontendDevLifecycle.js +0 -65
- package/src/frontend/FrontendPluginOrchestrator.js +0 -187
- package/src/frontend/SiteResolver.js +0 -63
- package/src/frontend/StaticMountRegistry.js +0 -90
- package/src/frontend/plugins/viteFrontend.js +0 -79
- package/src/frontend/types.js +0 -35
- package/src/index.js +0 -58
- package/src/plugins/PluginManager.js +0 -537
- package/src/plugins/ScopedPostgres.js +0 -192
- package/src/plugins/ScopedRedis.js +0 -142
- package/src/plugins/index.js +0 -1756
- package/src/registry/ServiceRegistry.js +0 -797
- package/src/scaling/ScaleAdvisor.js +0 -442
- package/src/services/Service.js +0 -195
- package/src/services/worker-bootstrap.js +0 -679
- package/src/templates/auth-service.js +0 -65
- package/src/templates/identity-service.js +0 -75
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scoped PostgreSQL Plugin
|
|
3
|
-
*
|
|
4
|
-
* Wraps the standard postgres() plugin to provide per-project
|
|
5
|
-
* schema isolation. Each project gets its own Postgres schema
|
|
6
|
-
* (project_{projectId}), and every connection checkout runs
|
|
7
|
-
* SET search_path to route queries to the correct schema.
|
|
8
|
-
*
|
|
9
|
-
* If ctx._projectId is null (shared services like auth),
|
|
10
|
-
* returns the raw pool unscoped.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { postgres } from "./index.js";
|
|
14
|
-
|
|
15
|
-
const SCHEMA_RE = /^[a-z_][a-z0-9_]{0,62}$/;
|
|
16
|
-
|
|
17
|
-
/** C-PLUGIN-3: Validate schema name at every usage point */
|
|
18
|
-
function validateSchemaName(schemaName) {
|
|
19
|
-
if (!SCHEMA_RE.test(schemaName)) {
|
|
20
|
-
throw new Error(`Invalid schema name: "${schemaName}". Must match /^[a-z_][a-z0-9_]{0,62}$/`);
|
|
21
|
-
}
|
|
22
|
-
if (Buffer.byteLength(schemaName, 'utf8') > 63) {
|
|
23
|
-
throw new Error(`Invalid schema name: "${schemaName}". Exceeds 63 byte limit`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** C-PLUGIN-3: Quote identifier with proper double-quote escaping */
|
|
28
|
-
function quoteIdent(schemaName) {
|
|
29
|
-
return `"${schemaName.replace(/"/g, '""')}"`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Create a ScopedPool that wraps a pg.Pool and sets the search_path
|
|
34
|
-
* on every connection checkout.
|
|
35
|
-
*/
|
|
36
|
-
class ScopedPool {
|
|
37
|
-
constructor(pool, schemaName) {
|
|
38
|
-
validateSchemaName(schemaName);
|
|
39
|
-
this._pool = pool;
|
|
40
|
-
this._schemaName = schemaName;
|
|
41
|
-
this._schemaCreated = false;
|
|
42
|
-
|
|
43
|
-
// Set search_path once when a new connection is created in the pool
|
|
44
|
-
const quoted = quoteIdent(this._schemaName);
|
|
45
|
-
this._pool.on('connect', (client) => {
|
|
46
|
-
client.query(`SET search_path TO ${quoted}, public`).catch((err) => {
|
|
47
|
-
console.error(`[ScopedPostgres] Failed to set search_path for ${this._schemaName}:`, err.message);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Ensure the project schema exists (idempotent).
|
|
54
|
-
*/
|
|
55
|
-
async _ensureSchema() {
|
|
56
|
-
if (this._schemaCreated) return;
|
|
57
|
-
// C-PLUGIN-3: Re-validate at every usage point
|
|
58
|
-
validateSchemaName(this._schemaName);
|
|
59
|
-
const client = await this._pool.connect();
|
|
60
|
-
try {
|
|
61
|
-
// Advisory lock prevents race when multiple workers create the same schema
|
|
62
|
-
const hash = Buffer.from(this._schemaName).reduce((h, b) => ((h << 5) - h + b) | 0, 5381);
|
|
63
|
-
const lockId = Math.abs(hash) * 65536 + this._schemaName.length;
|
|
64
|
-
await client.query("SELECT pg_advisory_lock($1)", [lockId]);
|
|
65
|
-
try {
|
|
66
|
-
const quoted = quoteIdent(this._schemaName);
|
|
67
|
-
await client.query(`CREATE SCHEMA IF NOT EXISTS ${quoted}`);
|
|
68
|
-
this._schemaCreated = true;
|
|
69
|
-
} finally {
|
|
70
|
-
await client.query("SELECT pg_advisory_unlock($1)", [lockId]);
|
|
71
|
-
}
|
|
72
|
-
} finally {
|
|
73
|
-
client.release();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get a scoped connection from the pool.
|
|
79
|
-
* Sets search_path before returning the client.
|
|
80
|
-
*/
|
|
81
|
-
async connect() {
|
|
82
|
-
// C-PLUGIN-3: Re-validate at every usage point
|
|
83
|
-
validateSchemaName(this._schemaName);
|
|
84
|
-
const client = await this._pool.connect();
|
|
85
|
-
try {
|
|
86
|
-
const quoted = quoteIdent(this._schemaName);
|
|
87
|
-
await client.query(`SET search_path TO ${quoted}, public`);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
client.release(err);
|
|
90
|
-
throw err;
|
|
91
|
-
}
|
|
92
|
-
const originalRelease = client.release.bind(client);
|
|
93
|
-
client.release = (err) => {
|
|
94
|
-
if (!err) {
|
|
95
|
-
client.query("SET search_path TO public").then(
|
|
96
|
-
() => originalRelease(),
|
|
97
|
-
(queryErr) => {
|
|
98
|
-
// Log the failure but release cleanly to avoid destroying the pooled client
|
|
99
|
-
console.warn(`[ScopedPostgres] Failed to reset search_path on release: ${queryErr.message}`);
|
|
100
|
-
originalRelease();
|
|
101
|
-
}
|
|
102
|
-
);
|
|
103
|
-
} else {
|
|
104
|
-
originalRelease(err);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
return client;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Run a query with the scoped search_path.
|
|
112
|
-
* search_path is set automatically on connection creation via pool 'connect' event.
|
|
113
|
-
*/
|
|
114
|
-
async query(text, params) {
|
|
115
|
-
return this._pool.query(text, params);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
get totalCount() { return this._pool.totalCount; }
|
|
119
|
-
get idleCount() { return this._pool.idleCount; }
|
|
120
|
-
get waitingCount() { return this._pool.waitingCount; }
|
|
121
|
-
|
|
122
|
-
async end() { return this._pool.end(); }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Create a scoped Postgres plugin.
|
|
127
|
-
*
|
|
128
|
-
* @param {Object} [options] - Same options as postgres() plugin
|
|
129
|
-
* @returns {Object} Plugin descriptor
|
|
130
|
-
*/
|
|
131
|
-
export function scopedPostgres(options = {}) {
|
|
132
|
-
const basePlugin = postgres(options);
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
name: "postgres",
|
|
136
|
-
version: "1.0.0",
|
|
137
|
-
inject: "pg",
|
|
138
|
-
|
|
139
|
-
validate: basePlugin.validate,
|
|
140
|
-
env: basePlugin.env,
|
|
141
|
-
|
|
142
|
-
async connect(ctx) {
|
|
143
|
-
// Get the raw pool from the base plugin
|
|
144
|
-
const pool = await basePlugin.connect(ctx);
|
|
145
|
-
|
|
146
|
-
// If no project ID, return unscoped pool (shared services)
|
|
147
|
-
const projectId = ctx._projectId;
|
|
148
|
-
if (!projectId) return pool;
|
|
149
|
-
|
|
150
|
-
const rawSchema = ctx._projectSchema ?? (projectId ? `project_${projectId}` : null);
|
|
151
|
-
if (!rawSchema) return pool;
|
|
152
|
-
if (!/^[a-z_][a-z0-9_]{0,62}$/.test(rawSchema)) {
|
|
153
|
-
throw new Error(`Invalid schema name: "${rawSchema}"`);
|
|
154
|
-
}
|
|
155
|
-
const schemaName = rawSchema;
|
|
156
|
-
const scoped = new ScopedPool(pool, schemaName);
|
|
157
|
-
|
|
158
|
-
// Create schema on first connect
|
|
159
|
-
await scoped._ensureSchema();
|
|
160
|
-
|
|
161
|
-
ctx.logger.info("PostgreSQL scoped", { schema: schemaName, project: projectId });
|
|
162
|
-
return scoped;
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
async healthCheck(pool) {
|
|
166
|
-
// ScopedPool or raw pool — both support query()
|
|
167
|
-
if (pool.query) {
|
|
168
|
-
try {
|
|
169
|
-
await pool.query("SELECT 1");
|
|
170
|
-
return { status: "ok", total: pool.totalCount, idle: pool.idleCount };
|
|
171
|
-
} catch (e) {
|
|
172
|
-
return { status: "error", error: e.message };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return basePlugin.healthCheck(pool);
|
|
176
|
-
},
|
|
177
|
-
|
|
178
|
-
async disconnect(pool) {
|
|
179
|
-
await pool.end();
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
metrics(pool) {
|
|
183
|
-
return {
|
|
184
|
-
pool_total: pool.totalCount ?? 0,
|
|
185
|
-
pool_idle: pool.idleCount ?? 0,
|
|
186
|
-
pool_waiting: pool.waitingCount ?? 0,
|
|
187
|
-
};
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
nginx: basePlugin.nginx,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scoped Redis Plugin
|
|
3
|
-
*
|
|
4
|
-
* Wraps the standard redis() plugin to provide per-project
|
|
5
|
-
* key isolation. Each project's keys are prefixed with {projectId}:
|
|
6
|
-
* so project A can't see project B's data.
|
|
7
|
-
*
|
|
8
|
-
* If ioredis is available, uses its built-in keyPrefix option.
|
|
9
|
-
* Otherwise, wraps the built-in RESP client with a PrefixedRedisClient.
|
|
10
|
-
*
|
|
11
|
-
* If ctx._projectId is null (shared services like auth),
|
|
12
|
-
* returns the unprefixed client.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { redis } from "./index.js";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Wraps the built-in RESP client to prefix all key operations.
|
|
19
|
-
*/
|
|
20
|
-
class PrefixedRedisClient {
|
|
21
|
-
constructor(client, prefix) {
|
|
22
|
-
this._client = client;
|
|
23
|
-
this._prefix = prefix;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
_pk(key) {
|
|
27
|
-
return `${this._prefix}${key}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
get status() {
|
|
31
|
-
return this._client.status;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
get(k) { return this._client.get(this._pk(k)); }
|
|
35
|
-
set(...a) { a[0] = this._pk(a[0]); return this._client.set(...a); }
|
|
36
|
-
del(...keys) { return this._client.del(...keys.map((k) => this._pk(k))); }
|
|
37
|
-
incr(k) { return this._client.incr(this._pk(k)); }
|
|
38
|
-
decr(k) { return this._client.decr(this._pk(k)); }
|
|
39
|
-
expire(k, s) { return this._client.expire(this._pk(k), s); }
|
|
40
|
-
ttl(k) { return this._client.ttl(this._pk(k)); }
|
|
41
|
-
|
|
42
|
-
hget(k, f) { return this._client.hget(this._pk(k), f); }
|
|
43
|
-
hset(k, f, v) { return this._client.hset(this._pk(k), f, v); }
|
|
44
|
-
hgetall(k) { return this._client.hgetall(this._pk(k)); }
|
|
45
|
-
|
|
46
|
-
lpush(k, ...v) { return this._client.lpush(this._pk(k), ...v); }
|
|
47
|
-
rpush(k, ...v) { return this._client.rpush(this._pk(k), ...v); }
|
|
48
|
-
lrange(k, s, e) { return this._client.lrange(this._pk(k), s, e); }
|
|
49
|
-
|
|
50
|
-
sadd(k, ...m) { return this._client.sadd(this._pk(k), ...m); }
|
|
51
|
-
smembers(k) { return this._client.smembers(this._pk(k)); }
|
|
52
|
-
|
|
53
|
-
keys(p) { return this._client.keys(this._pk(p)); }
|
|
54
|
-
publish(ch, m) { return this._client.publish(this._pk(ch), m); }
|
|
55
|
-
|
|
56
|
-
ping() { return this._client.ping(); }
|
|
57
|
-
quit() { return this._client.quit(); }
|
|
58
|
-
disconnect() { return this._client.disconnect?.(); }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Build the Redis URL from options, falling back to localhost defaults.
|
|
63
|
-
*/
|
|
64
|
-
function buildRedisUrl(options) {
|
|
65
|
-
return options.url ?? `redis://${options.host ?? "127.0.0.1"}:${options.port ?? 6379}/${options.db ?? 0}`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Create a scoped Redis plugin.
|
|
70
|
-
*
|
|
71
|
-
* @param {Object} [options] - Same options as redis() plugin
|
|
72
|
-
* @returns {Object} Plugin descriptor
|
|
73
|
-
*/
|
|
74
|
-
export function scopedRedis(options = {}) {
|
|
75
|
-
return {
|
|
76
|
-
name: "redis",
|
|
77
|
-
version: "1.0.0",
|
|
78
|
-
inject: "redis",
|
|
79
|
-
|
|
80
|
-
validate() {
|
|
81
|
-
const url = buildRedisUrl(options);
|
|
82
|
-
try {
|
|
83
|
-
new URL(url);
|
|
84
|
-
} catch {
|
|
85
|
-
throw new Error(`Invalid Redis URL: ${url}`);
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
env() {
|
|
90
|
-
return { FORGE_REDIS_URL: buildRedisUrl(options) };
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
async connect(ctx) {
|
|
94
|
-
const projectId = ctx._projectId;
|
|
95
|
-
const prefix = projectId ? `${projectId}:` : "";
|
|
96
|
-
const url = buildRedisUrl(options);
|
|
97
|
-
|
|
98
|
-
// Try ioredis first — it has built-in keyPrefix support
|
|
99
|
-
try {
|
|
100
|
-
const R = (await import("ioredis")).default;
|
|
101
|
-
const c = new R(url, {
|
|
102
|
-
keyPrefix: prefix,
|
|
103
|
-
maxRetriesPerRequest: 3,
|
|
104
|
-
lazyConnect: true,
|
|
105
|
-
});
|
|
106
|
-
await c.connect();
|
|
107
|
-
if (prefix) {
|
|
108
|
-
ctx.logger.info("Redis scoped (ioredis)", { prefix, project: projectId });
|
|
109
|
-
}
|
|
110
|
-
return c;
|
|
111
|
-
} catch {
|
|
112
|
-
// Fall through to built-in RESP client
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Built-in RESP client — need manual prefix wrapping
|
|
116
|
-
const { _mkRedis } = await import("./index.js");
|
|
117
|
-
const client = await _mkRedis(url, ctx);
|
|
118
|
-
|
|
119
|
-
if (!prefix) return client;
|
|
120
|
-
|
|
121
|
-
ctx.logger.info("Redis scoped (built-in)", { prefix, project: projectId });
|
|
122
|
-
return new PrefixedRedisClient(client, prefix);
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
async healthCheck(c) {
|
|
126
|
-
const inner = c._client ?? c;
|
|
127
|
-
try {
|
|
128
|
-
return { status: (await inner.ping()) === "PONG" ? "ok" : "degraded" };
|
|
129
|
-
} catch (e) {
|
|
130
|
-
return { status: "error", error: e.message };
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
|
|
134
|
-
async disconnect(c) {
|
|
135
|
-
await (c._client ?? c).quit?.();
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
metrics(c) {
|
|
139
|
-
return { connected: (c._client ?? c).status === "ready" ? 1 : 0 };
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
}
|