Skip to content

Commit fdb41c0

Browse files
os-zhuangclaude
andauthored
feat(types)!: remove ObjectStack's own legacy env-var aliases (11.0, #2379) (#2383)
Removes the framework's own renamed env names; keeps ubiquitous ecosystem conventions working (now silent, since they're permanent — not deprecated). Removed (rename required): - OS_MULTI_TENANT → OS_MULTI_ORG_ENABLED (resolveMultiOrgEnabled) - OBJECTSTACK_METADATA_WRITABLE → OS_METADATA_WRITABLE (sys-metadata-repository) - OS_AUTH_BASE_URL / AUTH_BASE_URL → OS_AUTH_URL (cli serve) Reclassified to silent (kept, no more nag): DATABASE_URL, AUTH_SECRET, BETTER_AUTH_SECRET, BETTER_AUTH_URL, CORS_*, LOG_LEVEL, ROOT_DOMAIN, MCP_SERVER_*, PORT. Also updated now-stale comments + plugin-org-scoping / plugin-security READMEs + object.zod / protocol.zod describe text to the canonical names. The generic readEnvWithDeprecation helper + env.test.ts (synthetic names) are unchanged. Verified: types env (7), cli (436), objectql (718), driver-sql (234), plugin-auth (175), mcp (53), plugin-hono-server (40) all green; spec api-surface check unchanged; 53 build tasks green. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 80fe2ef commit fdb41c0

17 files changed

Lines changed: 59 additions & 41 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@objectstack/types": major
3+
"@objectstack/objectql": major
4+
"@objectstack/cli": major
5+
---
6+
7+
Remove ObjectStack's own legacy env-var aliases (11.0); ecosystem-standard names stay.
8+
9+
The framework's renamed env vars no longer accept their old ObjectStack names —
10+
rename them:
11+
12+
| removed legacy name | use |
13+
|---|---|
14+
| `OS_MULTI_TENANT` | `OS_MULTI_ORG_ENABLED` |
15+
| `OBJECTSTACK_METADATA_WRITABLE` | `OS_METADATA_WRITABLE` |
16+
| `OS_AUTH_BASE_URL`, `AUTH_BASE_URL` | `OS_AUTH_URL` |
17+
18+
**Ecosystem-standard names are NOT removed** — they remain accepted (and no longer
19+
emit a deprecation warning, since they are permanent conventions, not legacy):
20+
`DATABASE_URL`, `AUTH_SECRET`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `PORT`,
21+
`CORS_*`, `LOG_LEVEL`, `ROOT_DOMAIN`, `MCP_SERVER_*`. The generic
22+
`readEnvWithDeprecation` helper is unchanged.

packages/adapters/hono/src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,15 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
124124
// OS_CORS_CREDENTIALS – "false" to disallow credentials (default: true)
125125
// OS_CORS_MAX_AGE – preflight cache seconds (default: 86400)
126126
// (legacy CORS_* names still honoured with a deprecation warning)
127-
const corsDisabledByEnv = readEnvWithDeprecation('OS_CORS_ENABLED', 'CORS_ENABLED') === 'false';
127+
const corsDisabledByEnv = readEnvWithDeprecation('OS_CORS_ENABLED', 'CORS_ENABLED', { silent: true }) === 'false';
128128
if (options.cors !== false && !corsDisabledByEnv) {
129129
const corsOpts = typeof options.cors === 'object' ? options.cors : {};
130130
const enabled = corsOpts.enabled ?? true;
131131

132132
if (enabled) {
133133
// Resolve origins: options > env > default '*'
134134
let configuredOrigin: string | string[];
135-
const corsOriginEnv = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN');
135+
const corsOriginEnv = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN', { silent: true });
136136
if (corsOpts.origin) {
137137
configuredOrigin = corsOpts.origin;
138138
} else if (corsOriginEnv) {
@@ -142,8 +142,8 @@ export function createHonoApp(options: ObjectStackHonoOptions): Hono {
142142
configuredOrigin = '*';
143143
}
144144

145-
const credentials = corsOpts.credentials ?? (readEnvWithDeprecation('OS_CORS_CREDENTIALS', 'CORS_CREDENTIALS') !== 'false');
146-
const maxAgeEnv = readEnvWithDeprecation('OS_CORS_MAX_AGE', 'CORS_MAX_AGE');
145+
const credentials = corsOpts.credentials ?? (readEnvWithDeprecation('OS_CORS_CREDENTIALS', 'CORS_CREDENTIALS', { silent: true }) !== 'false');
146+
const maxAgeEnv = readEnvWithDeprecation('OS_CORS_MAX_AGE', 'CORS_MAX_AGE', { silent: true });
147147
const maxAge = corsOpts.maxAge ?? (maxAgeEnv ? parseInt(maxAgeEnv, 10) : 86400);
148148

149149
// When credentials is true, browsers reject wildcard '*' for Access-Control-Allow-Origin.

packages/cli/src/commands/serve.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,7 @@ export default class Serve extends Command {
10991099

11001100
// In dev, fall back to a stable local secret so users don't have
11011101
// to set OS_AUTH_SECRET just to try the login/register flow.
1102-
const secret = readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'])
1102+
const secret = readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'], { silent: true })
11031103
?? (isDev ? 'dev-only-insecure-secret-change-me-in-production' : undefined);
11041104

11051105
// Guard: in cloud-connected runtime mode (e.g. objectos worker)
@@ -1128,7 +1128,7 @@ export default class Serve extends Command {
11281128
} else if (!secret) {
11291129
console.warn(chalk.yellow(' ⚠ AuthPlugin skipped — set OS_AUTH_SECRET to enable authentication in production'));
11301130
} else {
1131-
const baseUrl = readEnvWithDeprecation('OS_AUTH_URL', ['OS_AUTH_BASE_URL', 'AUTH_BASE_URL', 'BETTER_AUTH_URL'])
1131+
const baseUrl = readEnvWithDeprecation('OS_AUTH_URL', 'BETTER_AUTH_URL', { silent: true })
11321132
?? process.env.OS_BASE_URL
11331133
?? `http://localhost:${port}`;
11341134

@@ -1184,7 +1184,7 @@ export default class Serve extends Command {
11841184
// must be trusted by better-auth or sign-up/sign-in is
11851185
// rejected with "Invalid origin". Mirrors the OS_COOKIE_DOMAIN
11861186
// wildcard semantics — they are always set together.
1187-
const rootDomain = readEnvWithDeprecation('OS_ROOT_DOMAIN', 'ROOT_DOMAIN')?.trim();
1187+
const rootDomain = readEnvWithDeprecation('OS_ROOT_DOMAIN', 'ROOT_DOMAIN', { silent: true })?.trim();
11881188
if (rootDomain) {
11891189
const wildcard = `https://*.${rootDomain}`;
11901190
if (!trustedOrigins.includes(wildcard)) trustedOrigins.push(wildcard);

packages/cli/src/commands/start.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ export default class Start extends Command {
179179
// ── Database resolution ─────────────────────────────────────────
180180
// Priority: --database > $OS_DATABASE_URL > $DATABASE_URL (legacy) > file:<home>/data/objectstack.db
181181
const databaseUrl = flags.database
182-
?? readEnvWithDeprecation('OS_DATABASE_URL', 'DATABASE_URL')
182+
?? readEnvWithDeprecation('OS_DATABASE_URL', 'DATABASE_URL', { silent: true })
183183
?? `file:${path.join(homeDir, 'data', 'objectstack.db')}`;
184184

185185
const environmentId = flags['environment-id']
@@ -196,7 +196,7 @@ export default class Start extends Command {
196196
// Quick-start should "just work" without the user having to
197197
// export AUTH_SECRET.
198198
const authSecret = flags['auth-secret']
199-
?? readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'])
199+
?? readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'], { silent: true })
200200
?? readOrCreateAuthSecret(homeDir);
201201

202202
// ── Banner ──────────────────────────────────────────────────────

packages/cli/src/utils/log-level.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ export function resolveLogLevel(opts: {
5050
* environment, emitting the standard deprecation warning for the legacy name.
5151
*/
5252
export function readLogLevelEnv(): string | undefined {
53-
return readEnvWithDeprecation('OS_LOG_LEVEL', 'LOG_LEVEL');
53+
return readEnvWithDeprecation('OS_LOG_LEVEL', 'LOG_LEVEL', { silent: true });
5454
}

packages/mcp/src/plugin.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ export class MCPServerPlugin implements Plugin {
6464

6565
async init(ctx: PluginContext): Promise<void> {
6666
const config: MCPServerRuntimeConfig = {
67-
name: readEnvWithDeprecation('OS_MCP_SERVER_NAME', 'MCP_SERVER_NAME') ?? this.options.name ?? 'objectstack',
67+
name: readEnvWithDeprecation('OS_MCP_SERVER_NAME', 'MCP_SERVER_NAME', { silent: true }) ?? this.options.name ?? 'objectstack',
6868
version: this.options.version ?? '1.0.0',
69-
transport: (readEnvWithDeprecation('OS_MCP_SERVER_TRANSPORT', 'MCP_SERVER_TRANSPORT') as 'stdio' | 'http') ?? this.options.transport ?? 'stdio',
69+
transport: (readEnvWithDeprecation('OS_MCP_SERVER_TRANSPORT', 'MCP_SERVER_TRANSPORT', { silent: true }) as 'stdio' | 'http') ?? this.options.transport ?? 'stdio',
7070
instructions: this.options.instructions,
7171
logger: ctx.logger,
7272
};
@@ -118,7 +118,7 @@ export class MCPServerPlugin implements Plugin {
118118
}
119119

120120
// ── Auto-start if configured ──
121-
const shouldStart = this.options.autoStart || readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED') === 'true';
121+
const shouldStart = this.options.autoStart || readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', { silent: true }) === 'true';
122122
if (shouldStart) {
123123
await this.runtime.start();
124124
ctx.logger.info('[MCP] Server started automatically');

packages/objectql/src/registry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,10 @@ export interface SchemaRegistryOptions {
133133
* is additionally INDEXED — single-tenant stacks skip the index since
134134
* nothing ever filters by organization.
135135
*
136-
* Sourced from the `OS_MULTI_TENANT` env var when not explicitly set —
136+
* Sourced from the `OS_MULTI_ORG_ENABLED` env var when not explicitly set —
137137
* matches how the SecurityPlugin and CLI startup banner pick the mode.
138138
* Default is `false` (single-tenant) so local `dev`/`start` runs seed
139-
* demo data inline at boot; set `OS_MULTI_TENANT=true` for cloud /
139+
* demo data inline at boot; set `OS_MULTI_ORG_ENABLED=true` for cloud /
140140
* production multi-org deployments. Pass an explicit boolean to override
141141
* (useful in tests).
142142
*/

packages/objectql/src/sys-metadata-repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ const RUNTIME_CREATE_ALLOWED_TYPES: ReadonlySet<string> = new Set(
185185
let _envWritableMetadataTypes: Set<string> | null = null;
186186
function envWritableMetadataTypes(): ReadonlySet<string> {
187187
if (_envWritableMetadataTypes !== null) return _envWritableMetadataTypes;
188-
const raw = readEnvWithDeprecation('OS_METADATA_WRITABLE', 'OBJECTSTACK_METADATA_WRITABLE') || '';
188+
const raw = readEnvWithDeprecation('OS_METADATA_WRITABLE', []) || '';
189189
const set = new Set<string>();
190190
for (const tok of raw.split(',')) {
191191
const t = tok.trim();

packages/plugins/driver-sql/src/sql-driver.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2222,7 +2222,7 @@ export class SqlDriver implements IDataDriver {
22222222

22232223
/**
22242224
* Whether the host kernel runs in multi-tenant mode — read once from
2225-
* `OS_MULTI_ORG_ENABLED` (or the deprecated `OS_MULTI_TENANT`), matching how
2225+
* `OS_MULTI_ORG_ENABLED`, matching how
22262226
* the SchemaRegistry / SecurityPlugin pick the mode. Used to gate the
22272227
* tenant-audit warning: it's only meaningful where tenant isolation is
22282228
* actually enforced (org-scoping installed).
@@ -2231,8 +2231,7 @@ export class SqlDriver implements IDataDriver {
22312231
protected isMultiTenantMode(): boolean {
22322232
if (this._multiTenantMode === undefined) {
22332233
// Single source of truth (shared with auth/registry/CLI) — previously
2234-
// this read `process.env` inline and so never emitted the
2235-
// `OS_MULTI_TENANT` deprecation warning the other sites do.
2234+
// this read `process.env` inline instead of the shared resolver.
22362235
this._multiTenantMode = resolveMultiOrgEnabled();
22372236
}
22382237
return this._multiTenantMode;

packages/plugins/plugin-auth/src/auth-manager.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ export class AuthManager {
859859
...(() => {
860860
const origins: string[] = [...(this.config.trustedOrigins || [])];
861861
// Sync with OS_CORS_ORIGIN env var (comma-separated)
862-
const corsOrigin = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN');
862+
const corsOrigin = readEnvWithDeprecation('OS_CORS_ORIGIN', 'CORS_ORIGIN', { silent: true });
863863
if (corsOrigin && corsOrigin !== '*') {
864864
corsOrigin.split(',').map(s => s.trim()).filter(Boolean).forEach(o => {
865865
if (!origins.includes(o)) origins.push(o);
@@ -1137,9 +1137,8 @@ export class AuthManager {
11371137
// The plugin itself is always installed (so list/update/invite endpoints
11381138
// keep responding); only the `create` operation is denied when the
11391139
// deployment is provisioned in single-org mode. Resolution order:
1140-
// 1. explicit `OS_MULTI_ORG_ENABLED` (wins for backwards compat),
1141-
// 2. else `OS_MULTI_TENANT` (multi-tenant deployments are always
1142-
// multi-org), default `'false'` → single-org / per-env runtime.
1140+
// `OS_MULTI_ORG_ENABLED` (default `'false'` → single-org /
1141+
// per-env runtime).
11431142
beforeCreateOrganization: async () => {
11441143
if (!resolveMultiOrgEnabled()) {
11451144
const { APIError } = await import('better-auth/api');
@@ -1565,7 +1564,7 @@ export class AuthManager {
15651564
* Generate a secure secret if not provided
15661565
*/
15671566
private generateSecret(): string {
1568-
const envSecret = readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET']);
1567+
const envSecret = readEnvWithDeprecation('OS_AUTH_SECRET', ['AUTH_SECRET', 'BETTER_AUTH_SECRET'], { silent: true });
15691568
if (envSecret) return envSecret;
15701569

15711570
// No secret configured. In production this is FATAL: a predictable
@@ -1863,9 +1862,7 @@ export class AuthManager {
18631862
// Extract enabled features
18641863
const pluginConfig: Partial<AuthPluginConfig> = this.config.plugins ?? {};
18651864
// Multi-org capability (UI org-switcher, "create org" action, etc.).
1866-
// Resolution order: explicit `OS_MULTI_ORG_ENABLED` wins, else fall
1867-
// back to legacy `OS_MULTI_TENANT` (multi-tenant deployments are always
1868-
// multi-org); default `'false'` → single-org / per-env runtime.
1865+
// `OS_MULTI_ORG_ENABLED` (default `'false'` → single-org / per-env runtime).
18691866
const multiOrgEnabled = resolveMultiOrgEnabled();
18701867

18711868
// Legal links shown beneath the login / register cards. Defaults to

0 commit comments

Comments
 (0)