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
JavaScript
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
};