55 *
66 * Booted by `objectstack dev` / `objectstack serve` (see `package.json`).
77 *
8- * The CLI loads this file, registers the plugins below on a fresh
9- * `ObjectKernel`, then auto-mounts the standard HTTP stack
10- * (HonoServerPlugin, Setup, RestAPI, Dispatcher, I18nService).
8+ * ## Boot modes
119 *
12- * Our plugin list:
13- * 1. `createControlPlanePlugins(...)` — ObjectQL + driver + tenant +
14- * system-project + auth/security/audit + metadata for `sys_*`.
15- * 2. `MultiProjectPlugin` — registers `env-registry`, `kernel-manager`
16- * and `template-seeder` on the control kernel so HttpDispatcher can
17- * route requests to per-project kernels via hostname / X-Project-Id.
10+ * ### Local mode (`OBJECTSTACK_CLOUD_URL` is unset)
1811 *
19- * The control-plane driver is selected from a single URL-style env var
20- * (Turso/Prisma convention):
12+ * Single-project, offline-first. No control-plane DB is required.
13+ * Required env vars:
14+ * OBJECTSTACK_PROJECT_ID — project identity (e.g. "proj_local")
15+ * OBJECTSTACK_DATABASE_URL — project business DB (file:./app.db, memory://mydb, libsql://…)
16+ * OBJECTSTACK_DATABASE_DRIVER — driver name: sqlite | memory | turso | postgres
17+ * OBJECTSTACK_ARTIFACT_PATH — path to compiled artifact (default: ./dist/objectstack.json)
18+ * AUTH_SECRET — JWT signing secret (≥32 chars)
2119 *
22- * OBJECTSTACK_DATABASE_URL
23- * - unset → `file:./.objectstack/data/control.db`
24- * - `file:<path>` / path → SQLite at that path (better-sqlite3)
25- * - `libsql://…` → libSQL/Turso
26- * - `http(s)://…` → libSQL/sqld over HTTP
20+ * ### Cloud mode (`OBJECTSTACK_CLOUD_URL` is set)
2721 *
28- * OBJECTSTACK_DATABASE_AUTH_TOKEN — optional, for libSQL/HTTP URLs.
22+ * Multi-project, control-plane connected.
23+ * Required env vars:
24+ * OBJECTSTACK_DATABASE_URL — control-plane DB URL
25+ * OBJECTSTACK_DATABASE_AUTH_TOKEN — optional, for libSQL/Turso URLs
26+ * AUTH_SECRET / NEXT_PUBLIC_BASE_URL — same as local
27+ *
28+ * The control-plane driver URL accepts:
29+ * - unset / `file:<path>` → SQLite (better-sqlite3) [default: .objectstack/data/control.db]
30+ * - `libsql://…` → libSQL / Turso
31+ * - `http(s)://…` → libSQL / sqld over HTTP
2932 */
3033
3134import { resolve as resolvePath } from 'node:path' ;
@@ -39,11 +42,58 @@ import { templateRegistry } from './server/templates/registry.js';
3942
4043type IDataDriver = Contracts . IDataDriver ;
4144
42- /**
43- * Resolve the control-plane driver from `OBJECTSTACK_DATABASE_URL`.
44- * Drivers are loaded via dynamic import() so esbuild does not bundle
45- * the database libraries (better-sqlite3, libsql) at parse time.
46- */
45+ // ── Discriminator ─────────────────────────────────────────────────────────────
46+ const isLocalMode = ! process . env . OBJECTSTACK_CLOUD_URL ;
47+
48+ const authSecret = process . env . AUTH_SECRET
49+ ?? 'dev-secret-please-change-in-production-min-32-chars' ;
50+ const baseUrl = process . env . NEXT_PUBLIC_BASE_URL
51+ ?? ( process . env . VERCEL_PROJECT_PRODUCTION_URL
52+ ? `https://${ process . env . VERCEL_PROJECT_PRODUCTION_URL } `
53+ : undefined )
54+ ?? ( process . env . VERCEL_URL
55+ ? `https://${ process . env . VERCEL_URL } `
56+ : undefined )
57+ ?? `http://localhost:${ process . env . PORT ?? 3000 } ` ;
58+
59+ // ── LOCAL MODE ────────────────────────────────────────────────────────────────
60+
61+ const localProjectId = process . env . OBJECTSTACK_PROJECT_ID ?? 'proj_local' ;
62+ const localDatabaseUrl = process . env . OBJECTSTACK_DATABASE_URL
63+ ?? `file:${ resolvePath ( process . cwd ( ) , '.objectstack/data/local.db' ) } ` ;
64+ const localDatabaseDriver = process . env . OBJECTSTACK_DATABASE_DRIVER ?? 'sqlite' ;
65+ const localArtifactPath = process . env . OBJECTSTACK_ARTIFACT_PATH
66+ ?? resolvePath ( process . cwd ( ) , 'dist/objectstack.json' ) ;
67+
68+ // Lazy-loading proxy for local boot: registers ObjectQL + MetadataPlugin(local-file) + Auth
69+ // on the kernel. No control-plane, no MultiProjectPlugin.
70+ const localBootPluginProxy : any = {
71+ name : 'com.objectstack.local-boot' ,
72+ version : '0.0.0' ,
73+ async init ( ctx : any ) {
74+ const { ObjectQLPlugin } = await import ( '@objectstack/objectql' ) ;
75+ const { MetadataPlugin } = await import ( '@objectstack/metadata' ) ;
76+ const { AuthPlugin } = await import ( '@objectstack/plugin-auth' ) ;
77+
78+ await ctx . kernel . use ( new ObjectQLPlugin ( { environmentId : localProjectId } ) ) ;
79+ await ctx . kernel . use ( new MetadataPlugin ( {
80+ watch : false ,
81+ environmentId : localProjectId ,
82+ artifactSource : { mode : 'local-file' , path : localArtifactPath } ,
83+ } ) ) ;
84+ await ctx . kernel . use ( new AuthPlugin ( { secret : authSecret , baseUrl } ) ) ;
85+
86+ ctx . logger ?. info ?.( '[LocalBoot] plugins registered' , {
87+ projectId : localProjectId ,
88+ databaseUrl : localDatabaseUrl ,
89+ databaseDriver : localDatabaseDriver ,
90+ artifactPath : localArtifactPath ,
91+ } ) ;
92+ } ,
93+ } ;
94+
95+ // ── CLOUD MODE ────────────────────────────────────────────────────────────────
96+
4797async function buildControlDriver ( ) : Promise < {
4898 driver : IDataDriver ;
4999 driverName : 'sqlite' | 'turso' ;
@@ -66,22 +116,7 @@ async function buildControlDriver(): Promise<{
66116 return { driver : driver as unknown as IDataDriver , driverName : 'sqlite' , databaseUrl : `file:${ filename } ` } ;
67117}
68118
69- const authSecret = process . env . AUTH_SECRET
70- ?? 'dev-secret-please-change-in-production-min-32-chars' ;
71- const baseUrl = process . env . NEXT_PUBLIC_BASE_URL
72- ?? ( process . env . VERCEL_PROJECT_PRODUCTION_URL
73- ? `https://${ process . env . VERCEL_PROJECT_PRODUCTION_URL } `
74- : undefined )
75- ?? ( process . env . VERCEL_URL
76- ? `https://${ process . env . VERCEL_URL } `
77- : undefined )
78- ?? `http://localhost:${ process . env . PORT ?? 3000 } ` ;
79-
80119// Per-project kernels share a minimal base. Loaded lazily on project provisioning.
81- // System plugins (Tenant, Auth, Security, Audit) are included so that sys_*
82- // objects are visible at project API endpoints. They declare
83- // `defaultDatasource: 'cloud'` so their queries route through the
84- // ControlPlaneProxyDriver (org-scoped) mounted by ProjectKernelFactory.
85120const basePlugins : BasePluginsFactory = async ( { projectId, project } ) => {
86121 const { ObjectQLPlugin } = await import ( '@objectstack/objectql' ) ;
87122 const { MetadataPlugin } = await import ( '@objectstack/metadata' ) ;
@@ -100,18 +135,14 @@ const basePlugins: BasePluginsFactory = async ({ projectId, project }) => {
100135 ] ;
101136} ;
102137
103- // Example bundles are seeded into a project at provisioning time via
104- // the template-seeder — not pre-installed into every project kernel.
105138const appBundles : AppBundleResolver = {
106139 async resolve ( ) { return [ ] ; } ,
107140} ;
108141
109142// Single shared promise — both control-plane plugins and MultiProjectPlugin
110- // use the same DB connection. buildControlDriver() is called only once.
143+ // use the same DB connection.
111144const controlDriverPromise = buildControlDriver ( ) ;
112145
113- // Lazy-loading wrapper for MultiProjectPlugin — the control driver is built
114- // asynchronously so it is not imported at module evaluation time.
115146const multiProjectPluginProxy : any = {
116147 name : 'com.objectstack.multi-project' ,
117148 version : '0.0.0' ,
@@ -138,19 +169,27 @@ const multiProjectPluginProxy: any = {
138169 } ,
139170} ;
140171
141- export default {
142- plugins : [
143- ...createControlPlanePlugins ( {
144- controlDriverPromise,
145- authSecret,
146- baseUrl,
147- } ) ,
148- multiProjectPluginProxy ,
149- ] ,
150- // Project-scoping config consumed by `createRestApiPlugin` and
151- // `createDispatcherPlugin` (auto-registered by `objectstack serve`).
152- api : {
153- enableProjectScoping : true ,
154- projectResolution : 'auto' as const ,
155- } ,
156- } ;
172+ // ── Export ────────────────────────────────────────────────────────────────────
173+
174+ export default isLocalMode
175+ ? {
176+ plugins : [ localBootPluginProxy ] ,
177+ api : {
178+ enableProjectScoping : false ,
179+ projectResolution : 'none' as const ,
180+ } ,
181+ }
182+ : {
183+ plugins : [
184+ ...createControlPlanePlugins ( {
185+ controlDriverPromise,
186+ authSecret,
187+ baseUrl,
188+ } ) ,
189+ multiProjectPluginProxy ,
190+ ] ,
191+ api : {
192+ enableProjectScoping : true ,
193+ projectResolution : 'auto' as const ,
194+ } ,
195+ } ;
0 commit comments