Skip to content

Commit cb604a5

Browse files
committed
feat: Update environment variable handling for multi-project support and refine documentation
1 parent cf52c38 commit cb604a5

8 files changed

Lines changed: 40 additions & 49 deletions

File tree

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ Ordered by dependency. Items higher in the list unblock those below them.
160160
明确 ObjectOS 启动输入 = **Artifact**(不可变、可缓存的元数据信封)+ **Deployment Config**(业务 DB 坐标、凭据、项目身份、密钥;不进 artifact)。详见 [north-star.mdx §6.3](content/docs/concepts/north-star.mdx)
161161

162162
- [x] north-star.mdx §6.3 增补 Runtime Inputs 节(含本地单 project env 表 + 反模式说明)
163-
- [x] 实现本地单 project env 路径:`OBJECTSTACK_PROJECT_ID` / `OBJECTSTACK_DATABASE_URL` / `OBJECTSTACK_DATABASE_DRIVER` / `OBJECTSTACK_ARTIFACT_PATH`(默认 `./dist/objectstack.json`)/ `OBJECTSTACK_CLOUD_URL`(可选,留空即离线)/ `AUTH_SECRET`
163+
- [x] 实现本地 single / multi-project env 路径:`OBJECTSTACK_MULTI_PROJECT` / `OBJECTSTACK_PROJECT_ID` / `OBJECTSTACK_DATABASE_URL` / `OBJECTSTACK_DATABASE_DRIVER` / `OBJECTSTACK_ARTIFACT_PATH`(默认 `./dist/objectstack.json`)/ `AUTH_SECRET`
164164
- [x] 修复 Drift:`ProjectKernelFactory` 不再直连控制面 DB 读 `sys_project` / `sys_project_credential`,改走 Artifact API + Deployment Config 注入(`localProject` 分支)
165165
- [x] [apps/server/objectstack.config.ts](apps/server/objectstack.config.ts) 的 env 命名收敛到 `OBJECTSTACK_*` 前缀,`isLocalMode` 分流本地/云端路径
166166

apps/server/objectstack.config.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
* ## Boot modes
99
*
10-
* ### Local mode (`OBJECTSTACK_CLOUD_URL` is unset)
10+
* ### Single-project mode (`OBJECTSTACK_MULTI_PROJECT` is unset or false)
1111
*
1212
* Single-project, offline-first. No control-plane DB is required.
1313
* Required env vars:
@@ -22,7 +22,7 @@
2222
* TURSO_DATABASE_URL — libsql:// or https:// Turso URL (fallback alias for OBJECTSTACK_DATABASE_URL)
2323
* TURSO_AUTH_TOKEN — Turso auth token (fallback alias for OBJECTSTACK_DATABASE_AUTH_TOKEN)
2424
*
25-
* ### Cloud mode (`OBJECTSTACK_CLOUD_URL` is set)
25+
* ### Multi-project mode (`OBJECTSTACK_MULTI_PROJECT=true`)
2626
*
2727
* Multi-project, control-plane connected.
2828
* Required env vars:
@@ -31,7 +31,7 @@
3131
* AUTH_SECRET / NEXT_PUBLIC_BASE_URL — same as local
3232
*
3333
* The control-plane driver URL accepts:
34-
* - unset / `file:<path>` → SQLite (better-sqlite3) [default: .objectstack/data/control.db]
34+
* - unset / `file:<path>` → local SQLite (better-sqlite3) [default: .objectstack/data/control.db]
3535
* - `libsql://…` → libSQL / Turso
3636
* - `http(s)://…` → libSQL / sqld over HTTP
3737
*/
@@ -50,8 +50,12 @@ import { templateRegistry } from './server/templates/registry.js';
5050

5151
type IDataDriver = Contracts.IDataDriver;
5252

53-
// ── Discriminator ─────────────────────────────────────────────────────────────
54-
const isLocalMode = !process.env.OBJECTSTACK_CLOUD_URL;
53+
function envFlag(name: string): boolean {
54+
return ['1', 'true', 'yes', 'on'].includes((process.env[name] ?? '').trim().toLowerCase());
55+
}
56+
57+
// ── Boot mode ─────────────────────────────────────────────────────────────────
58+
const isLocalMode = !envFlag('OBJECTSTACK_MULTI_PROJECT');
5559

5660
const authSecret = process.env.AUTH_SECRET
5761
?? 'dev-secret-please-change-in-production-min-32-chars';

apps/server/test/multi-project-e2e.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/**
44
* Multi-Project End-to-End Smoke Test
55
*
6-
* Boots the Server in `multi-project-local` mode against an ephemeral
6+
* Boots the Server in `multi-project` mode against an ephemeral
77
* SQLite control DB, then exercises the complete Supabase-style flow:
88
*
99
* 1. Create an organization
@@ -74,6 +74,7 @@ function expect(actual: any) {
7474
const workdir = mkdtempSync(join(tmpdir(), 'objectstack-e2e-'));
7575
const controlDb = join(workdir, 'control.db');
7676

77+
process.env.OBJECTSTACK_MULTI_PROJECT = 'true';
7778
process.env.OBJECTSTACK_DATABASE_URL = `file:${controlDb}`;
7879
process.env.AUTH_SECRET = 'e2e-test-secret-must-be-at-least-32-characters-long-xxxx';
7980
process.env.PORT = '0';

content/docs/concepts/north-star.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ ObjectOS 启动时必须从外部注入:
406406
| `OBJECTSTACK_DATABASE_URL` | 业务 DB URL | 例:`file:./.data/app.db` |
407407
| `OBJECTSTACK_DATABASE_DRIVER` | 业务 DB driver | `turso` / `sqlite` / `memory` |
408408
| `OBJECTSTACK_ARTIFACT_PATH` | 本地 artifact 路径 | 默认 `./dist/objectstack.json`;填了即跳过 Artifact API |
409-
| `OBJECTSTACK_CLOUD_URL` | 控制面 URL | 留空即「纯本地离线」;填了即「本地 ObjectOS + 远端控制面」 |
409+
| `OBJECTSTACK_MULTI_PROJECT` | 多项目开关 | `true``OBJECTSTACK_DATABASE_URL` 解释为控制面 DB URL |
410410
| `AUTH_SECRET` | 进程级密钥 | |
411411

412412
本地模式可以用磁盘 `dist/objectstack.json` 直接喂内核(用 `commitId: 'local-dev'` 之类的占位 envelope),不需要控制面在场。

content/docs/guides/cloud-deployment.mdx

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
---
22
title: Deployment & Multi-Project
3-
description: Run a single ObjectStack Server process in single-kernel, multi-project (local sqlite) or multi-project (remote turso) shape. Covers env vars, hostname routing, and Fly.io / Docker deployment.
3+
description: Run a single ObjectStack Server process in single-kernel or multi-project shape. Covers control-plane DB URLs, hostname routing, and Fly.io / Docker deployment.
44
---
55

66
# Deployment & Multi-Project
77

8-
The same `apps/server` binary boots into one of three shapes. Which one you get
8+
The same `apps/server` binary boots into one of two shapes. Which one you get
99
depends only on the environment variables you set — the code and Docker image
1010
are identical.
1111

1212
| Shape | Control plane | Projects | Hostname routing | Typical use |
1313
| ---------------------------- | ----------------------- | ----------- | ---------------- | ------------------------------------ |
14-
| **single** | — (single kernel) | 1 || OSS single-project deploy, local dev |
15-
| **multi-project-local** | Local SQLite file | 1 – N || Self-hosted multi-project |
16-
| **multi-project-remote** | Remote Turso DB | 1 – N || SaaS (Supabase-style, many replicas) |
14+
| **single** | — (single kernel) | 1 || OSS single-project deploy |
15+
| **multi-project** | `OBJECTSTACK_DATABASE_URL` | 1 – N || Framework Studio dev, self-hosted multi-project, SaaS |
1716

18-
All three shapes reuse:
17+
Both shapes reuse:
1918

2019
- `KernelManager` + `DefaultProjectKernelFactory` for per-project kernels
2120
- `DefaultEnvironmentDriverRegistry` for hostname → project resolution
22-
- `createControlPlanePlugins()` (multi-project shapes only) to install
21+
- `createControlPlanePlugins()` (multi-project only) to install
2322
tenant / auth / security / audit / package plugins onto the control DB.
2423

2524
---
@@ -35,22 +34,24 @@ One `ObjectKernel`, every plugin from `apps/server/objectstack.config.ts`, no
3534
per-project isolation. This is what you want on a laptop or for an OSS
3635
single-app install.
3736

38-
Set no special env vars — the absence of `OBJECTSTACK_MULTI_PROJECT` and
39-
`OBJECTSTACK_CONTROL_PLANE_URL` selects this shape automatically.
37+
Leave `OBJECTSTACK_MULTI_PROJECT` unset (or set it to `false`) to use this
38+
shape.
4039

4140
---
4241

43-
## Shape 2 — multi-project-local
42+
## Shape 2 — multi-project
4443

4544
```bash
4645
OBJECTSTACK_MULTI_PROJECT=true \
47-
OBJECTSTACK_CONTROL_DB=./.objectstack/data/control.db \
46+
OBJECTSTACK_DATABASE_URL=file:./.objectstack/data/control.db \
4847
AUTH_SECRET=$(openssl rand -hex 32) \
49-
pnpm --filter @objectstack/server dev:node
48+
pnpm --filter @objectstack/server dev
5049
```
5150

5251
- Control-plane tables (`sys_project`, `sys_project_credential`, …) live in
53-
the SQLite file at `OBJECTSTACK_CONTROL_DB`.
52+
the database configured by `OBJECTSTACK_DATABASE_URL`. Use `file:...` for a
53+
local SQLite control plane, or `libsql://...` / `https://...` for a remote
54+
Turso/libSQL control plane.
5455
- Projects are created through Studio / `POST /api/v1/cloud/projects`. Each
5556
project binds its own driver (sqlite / libsql / turso / postgres).
5657
- Incoming requests are routed to per-project kernels by the `Host` header
@@ -64,22 +65,19 @@ registered drivers: `memory`, `sqlite`, `turso` / `libsql`, or `postgres`.
6465
The factory switch lives in
6566
`packages/runtime/src/project-kernel-factory.ts::createDriver`.
6667

67-
---
68-
69-
## Shape 3 — multi-project-remote
68+
For a remote shared control plane, only the URL changes:
7069

7170
```bash
72-
OBJECTSTACK_CONTROL_PLANE_URL=https://control.example.com \
73-
TURSO_DATABASE_URL=libsql://control.turso.io \
74-
TURSO_AUTH_TOKEN=... \
71+
OBJECTSTACK_MULTI_PROJECT=true \
72+
OBJECTSTACK_DATABASE_URL=libsql://control.turso.io \
73+
OBJECTSTACK_DATABASE_AUTH_TOKEN=... \
7574
AUTH_SECRET=$(openssl rand -hex 32) \
76-
pnpm --filter @objectstack/server dev:node
75+
pnpm --filter @objectstack/server dev
7776
```
7877

79-
Same code path as Shape 2, but the control-plane driver is a `TursoDriver`
80-
pointing at a shared remote DB. Multiple replicas of the Server can now share
81-
one sys_* schema; `DefaultEnvironmentDriverRegistry`'s per-replica cache is
82-
coherent within its TTL (5 min default), and credential rotation fires
78+
Multiple Server replicas can share the same remote `sys_*` schema;
79+
`DefaultEnvironmentDriverRegistry`'s per-replica cache is coherent within its
80+
TTL (5 min default), and credential rotation fires
8381
`envRegistry.invalidate(projectId)` so stale entries drop out within one TTL.
8482

8583
---
@@ -156,7 +154,7 @@ docker build -f apps/server/Dockerfile -t objectstack/server:local .
156154
docker run --rm -p 3000:3000 \
157155
-v $(pwd)/data:/data \
158156
-e OBJECTSTACK_MULTI_PROJECT=true \
159-
-e OBJECTSTACK_CONTROL_DB=/data/control.db \
157+
-e OBJECTSTACK_DATABASE_URL=file:/data/control.db \
160158
-e AUTH_SECRET=$(openssl rand -hex 32) \
161159
objectstack/server:local
162160

@@ -172,11 +170,9 @@ point a wildcard DNS (`*.example.com`) at it. The container does the rest.
172170

173171
| Variable | Shape(s) | Purpose |
174172
| --------------------------------- | ------------------------- | -------------------------------------------------------------------------------- |
175-
| `OBJECTSTACK_MULTI_PROJECT` | multi-project-local | `true` opts into the SQLite-backed control plane. |
176-
| `OBJECTSTACK_CONTROL_DB` | multi-project-local | Filesystem path for the local control DB (default `./.objectstack/data/control.db`). |
177-
| `OBJECTSTACK_CONTROL_PLANE_URL` | multi-project-remote | Presence opts into remote-Turso control plane. |
178-
| `TURSO_DATABASE_URL` | multi-project-remote | Turso URL for the control DB. |
179-
| `TURSO_AUTH_TOKEN` | multi-project-remote | Auth token for the control DB. |
173+
| `OBJECTSTACK_MULTI_PROJECT` | all | `true` enables the multi-project control plane. Unset/false runs single-project mode. |
174+
| `OBJECTSTACK_DATABASE_URL` | all | In `single`, the project business DB URL. In `multi-project`, the control-plane DB URL (`file:`, `libsql://`, or `https://`). |
175+
| `OBJECTSTACK_DATABASE_AUTH_TOKEN` | all | Auth token for `OBJECTSTACK_DATABASE_URL` when using libSQL/Turso. |
180176
| `TURSO_ORG_NAME` + `TURSO_API_TOKEN` | multi-project (any) | Used by `TursoProjectDatabaseAdapter` to provision per-project databases. |
181177
| `OBJECTSTACK_KERNEL_CACHE_SIZE` | multi-project (any) | LRU size for per-project kernels (default 32, fly.toml defaults to 50). |
182178
| `OBJECTSTACK_KERNEL_TTL_MS` | multi-project (any) | Idle eviction TTL in ms (default 900000, fly.toml defaults to 1800000). |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"description": "ObjectStack Protocol & Specification - Monorepo for TypeScript Interfaces, JSON Schemas, and Convention Configurations",
66
"scripts": {
77
"build": "turbo run build --filter=!@objectstack/docs",
8-
"dev": "pnpm --filter @objectstack/server dev",
8+
"dev": "OBJECTSTACK_MULTI_PROJECT=true pnpm --filter @objectstack/server dev",
99
"start": "pnpm --filter @objectstack/server start",
1010
"studio:dev": "pnpm --filter @objectstack/studio dev",
1111
"studio:start": "pnpm --filter @objectstack/studio start",

packages/cli/src/commands/dev.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,6 @@ export default class Dev extends Command {
6464
OBJECTSTACK_PROJECT_ID: process.env.OBJECTSTACK_PROJECT_ID ?? 'proj_local',
6565
OBJECTSTACK_ARTIFACT_PATH: process.env.OBJECTSTACK_ARTIFACT_PATH ?? artifactPath,
6666
};
67-
// Ensure OBJECTSTACK_CLOUD_URL is absent so apps/server boots in local mode.
68-
// Only delete if the user has not explicitly set it.
69-
if (!process.env.OBJECTSTACK_CLOUD_URL) {
70-
delete localEnv.OBJECTSTACK_CLOUD_URL;
71-
}
72-
7367
printKV('Project ID', localEnv.OBJECTSTACK_PROJECT_ID!, '🎯');
7468
printKV('Artifact', path.relative(process.cwd(), localEnv.OBJECTSTACK_ARTIFACT_PATH!), '📦');
7569

packages/cli/src/commands/start.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ export default class Start extends Command {
4040
...(flags.port ? { PORT: String(flags.port) } : {}),
4141
};
4242

43-
if (!process.env.OBJECTSTACK_CLOUD_URL) {
44-
delete localEnv.OBJECTSTACK_CLOUD_URL;
45-
}
46-
4743
printKV('Project ID', localEnv.OBJECTSTACK_PROJECT_ID!, '🎯');
4844

4945
const binPath = process.argv[1];

0 commit comments

Comments
 (0)