UNPKG

trellis

Version:

Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications

163 lines (160 loc) 4.89 kB
import { attachStandardMiddleware } from "./chunk-SZ3VAB5P.js"; import { SqliteKernelBackend } from "./chunk-VRFPXKFZ.js"; import { TrellisKernel } from "./chunk-PVOECISX.js"; // src/server/tenancy.ts import { existsSync, mkdirSync } from "fs"; import { join, resolve } from "path"; var DEFAULT_TENANT = "default"; var TENANTS_DIR = "tenants"; function resolvePoolBackendFromEnv() { const backend = process.env.TRELLIS_BACKEND; if (backend === "sqljs") return { backend: "sqljs" }; if (backend === "better-sqlite") return { backend: "better-sqlite" }; return void 0; } var TenantPool = class { pool = /* @__PURE__ */ new Map(); dbPath; agentId; backendOpts; constructor(dbPath, agentIdOrOpts = "trellis-db") { this.dbPath = resolve(dbPath); if (typeof agentIdOrOpts === "string") { this.agentId = agentIdOrOpts; this.backendOpts = void 0; } else { this.agentId = agentIdOrOpts.agentId ?? "trellis-db"; this.backendOpts = agentIdOrOpts.backend; } this._ensureDirs(); } // ------------------------------------------------------------------------- // Public API // ------------------------------------------------------------------------- /** * Get (or create) the kernel for a tenant. * Pass `null` or `undefined` to get the default tenant. * * Uses the synchronous bun:sqlite backend. To use an async backend * (better-sqlite3, sql.js) call `preload(tenantId)` first; subsequent * `get()` calls will return the preloaded kernel from the pool. */ get(tenantId) { const id = tenantId ?? DEFAULT_TENANT; if (!this.pool.has(id)) { const isBun = typeof process !== "undefined" && Boolean(process.versions?.bun); if (!isBun) { throw new Error( "TenantPool.get() on Node requires `await pool.preload()` first. `trellis db serve` preloads automatically; custom servers should call preload before get()." ); } this.pool.set(id, this._createKernelSync(id)); } return this.pool.get(id); } /** * Pre-create a tenant's kernel using the runtime-selected backend. * Required when running with an async-only backend (sql.js / WebContainer * / browser). Idempotent — safe to call multiple times. * * After `preload()`, subsequent `get(tenantId)` returns the same kernel * synchronously from the pool. */ async preload(tenantId) { const id = tenantId ?? DEFAULT_TENANT; const cached = this.pool.get(id); if (cached) return cached; const kernel = await this._createKernelAsync(id); this.pool.set(id, kernel); return kernel; } /** * Check whether a tenant has been initialized (kernel created). */ has(tenantId) { return this.pool.has(tenantId); } /** * List all active tenant IDs (those with open kernels). */ activeTenants() { return Array.from(this.pool.keys()); } /** * Close a specific tenant's kernel and remove it from the pool. */ close(tenantId) { const kernel = this.pool.get(tenantId); if (kernel) { kernel.close(); this.pool.delete(tenantId); } } /** * Close all kernels and clear the pool. */ closeAll() { for (const kernel of this.pool.values()) { kernel.close(); } this.pool.clear(); } /** Absolute path to the tenant data directory. */ dataPath() { return this.dbPath; } /** * Return the SQLite file path for a given tenant. */ dbFilePath(tenantId) { const id = tenantId ?? DEFAULT_TENANT; if (id === DEFAULT_TENANT) { return join(this.dbPath, "default.sqlite"); } return join(this.dbPath, TENANTS_DIR, `${id}.sqlite`); } // ------------------------------------------------------------------------- // Private // ------------------------------------------------------------------------- _createKernelSync(tenantId) { const sqlitePath = this.dbFilePath(tenantId); const backend = new SqliteKernelBackend(sqlitePath); return this._wrapKernel(backend); } async _createKernelAsync(tenantId) { const sqlitePath = this.dbFilePath(tenantId); const { createKernelBackend } = await import("./core/persist/factory.js"); const backend = await createKernelBackend(sqlitePath, this.backendOpts); return this._wrapKernel(backend); } _wrapKernel(backend) { const kernel = new TrellisKernel({ backend, agentId: this.agentId, snapshotThreshold: 1e3 }); kernel.boot(); attachStandardMiddleware(kernel); return kernel; } _ensureDirs() { if (!existsSync(this.dbPath)) { mkdirSync(this.dbPath, { recursive: true }); } const tenantsPath = join(this.dbPath, TENANTS_DIR); if (!existsSync(tenantsPath)) { mkdirSync(tenantsPath, { recursive: true }); } } }; export { DEFAULT_TENANT, resolvePoolBackendFromEnv, TenantPool };