Skip to content

Commit 9a4182a

Browse files
fix(spec,runtime,cli)!: the in-memory engine is no longer a boot store — every boot door refuses it and names SQLite instead (#21598)
Fixes #21492 Fixes #21572 Clause-②: yes (narrowing) ## What this lands One landing, as triage ruled (`5969348039`, (ii)): #21572's spec row and #21492's runtime and CLI halves. The in-memory (mingo) engine is retired as a **boot store**, at its declaration, on both hosts. It refuses every tenant-scoped read by design, so a boot on it signed the seeded admin in and then answered `503` to every data read. Every boot door now refuses it with one sentence that names the replacement: `os dev --fresh` for a throwaway database, or the URL `:memory:` for SQLite's own in-memory database. - **spec** (`packages/spec/src/data/driver/config-registry.zod.ts`, the row from `d480ef0d70` plus its stale doc comments): `memory`, `mingo` and `in-memory` move from the selection face to `contractOnlyAliases`, beside `inmemory`. `DATABASE_DRIVER_SELECTION_ALIASES`, `DATABASE_DRIVER_SELECTION_IDS`, the `--database-driver` allowlist and `--help` drop them by derivation. `resolveDriverId` is unchanged, so a stored `datasource.driver: memory` still parses. No artifact moves (`check:generated`: all 15 up to date). - **runtime**: - `resolveProjectDatabaseUrl`: the `memory-driver` rung becomes a refusal that stands **ahead of every rung**. `ProjectDatabaseUrlSource` loses `'memory-driver'`. - `resolveStandaloneDatabase`: the URL detection recognises `memory://` and `mingo://`, and `refuseRetiredMemoryEngine` refuses them. It names the source: the typed URL, `OS_DATABASE_URL`, or the project's declared default datasource. - `unsupportedDriverMessage` gives a spelling of the engine the retirement sentence, not the typo list. - `StandaloneDatabaseDriverSchema` now enumerates the selection face. The dispatch loses its `memory` arm, and `ResolvedStandaloneDatabase.driver` can no longer name it. - The sentence lives in one place, `retiredMemoryEngineMessage`, with the predicate `namesRetiredMemoryEngine`. Both are exported for the CLI. - **cli**: - `inferDriverTypeFromUrl` refuses `memory://` and `mingo://` with `UnsupportedDriverError` (`recognized: false`). - `resolveStorageDefinition` refuses the spellings with the same sentence. The `memory` definition arm is gone. - The `--database` help of `dev` and `start` offers `:memory:` instead of `memory://`. - The `storage-driver.ts` #3276 note and its module header are rewritten: the header now says which boots reach that file (legacy only). The parity pin's header, the `database-driver-flag.ts` doc and serve's comments are rewritten too. - **docs**: `deployment/cli.mdx`, `deployment/environment-variables.mdx`, `data-modeling/drivers.mdx`, `plugins/index.mdx`. - **QA** (`docs/qa/platform-checklist/areas/cli.json`, revisions bumped with history): - `cli.dev-boot-contract`: the memory tier becomes a REFUSED variant, with a negative and the `:memory:` variant added. - `cli.migrate-duplicates-inventory`: the `no_sql_seam` negative cites the seam-double pin. - devx's rider `5967974683`: `cli.build-own-contract`'s six live strings name `this.exit(2)`. Its two dated history entries keep their wording, because they record what was true when written. - **ledger**: `scripts/driver-memory-census.ledger.json`'s CLI row `why` no longer claims that a boot spelling routes to the driver. - **one changeset** with the narrowing kit: `fix(...)!`, BREAKING, `minor` for the three packages, and the ADR-0087 marker `not-required (no-migration-prescription)`. The `driver-memory` package is unchanged. A declared non-default `driver: 'memory'` datasource and a directly constructed `InMemoryDriver` still build: per the ruling, neither is a boot spelling. ## A1: reach, measured on both trees **Before**, on `origin/main` `901e7cf13a` with `examples/app-crm` (closure build exit 0, 60/60 tasks): | invocation | result | |---|---| | `os dev --fresh --database-driver memory` | sign-in 200; `GET /api/v1/data/crm_lead` **503** `SERVICE_UNAVAILABLE`; the banner shows `Database: file:/tmp/objectstack-dev-…/data/objectstack.db` beside `Driver: _InMemoryDriver → (in-memory)` (the banner finding) | | `os migrate plan --database-url memory://qa` | **exit 0**, `datasource 'default': connected (driver=memory …)` | | `os dev --fresh` (control) | sign-in 200; crm_lead **200**; `SqlDriver(better-sqlite3)` | **After**, on this branch (`133871a251`; the refusal code has not changed since): | door | result | |---|---| | `os dev --fresh --database-driver memory` | exit 2 at flag parse (oclif: `Expected --database-driver=memory to be one of: sqlite, sqlite-wasm, postgres, mysql, mongodb, turso`) | | `os start --database-driver memory` | exit 2, same | | `OS_DATABASE_DRIVER=memory os dev --fresh` (and `mingo`) | exit 1 before any `Database:` row, naming `--fresh` and `:memory:` | | `OS_DATABASE_DRIVER=in-memory os start` | exit 1, same sentence | | `os dev --fresh --database memory://x` | the parent prints `Database: memory://x`; the serve child refuses, exit 1 | | `os migrate plan --database-url memory://qa` / `mingo://qa` | exit 1, `[StandaloneStack] The database URL "…" selects the in-memory (mingo) engine …` | | `os migrate duplicates --database-url memory://qa` | exit 1, `{"error":"boot_failed", …}` with the sentence | | `OS_MODE=legacy OS_DATABASE_DRIVER=memory os serve --dev` | exit 1 (legacy CLI host) | | `OS_MODE=legacy OS_DATABASE_URL=mingo://x os serve --dev` | exit 1 (legacy CLI host, inference) | | `OS_ARTIFACT_PATH=… OS_DATABASE_URL=memory:// os serve --dev` (artifact path, empty cwd) | exit 1 | | `os dev --fresh` (control) | crm_lead **200**, `SqlDriver(better-sqlite3)` | | `os dev --database :memory:` (the replacement) | crm_lead **200**, `Database: :memory:`, `SqlDriver(better-sqlite3) → :memory:` | ## A2: which doors reach which resolver (symbol walk) - **Runtime host** (`resolveStandaloneDatabase`, which calls `resolveProjectDatabaseUrl`), reached by: - `os serve` over a config (`createStandaloneStack`) and over an artifact (`createDefaultHostConfig`, then `createStandaloneStack`); these are the serve children of `os dev` and `os start`; - every `bootSchemaStack` user: `os migrate` `apply`, `plan`, `meta`, `duplicates`, `account-issuer`, `audit-metadata-bodies`, `files-to-references`, `multi-value-columns`, `recorded-by`, `resume`, `summary-nulls` and `value-shapes`, plus `os meta resync`, `os secret orphans` / `rewrap` and `os storage orphans`; - `probeMigrationTarget`, the pre-boot probe of seven migrate subcommands; - every embedder. - **The shared resolver alone** (`resolveProjectDatabaseUrl`), reached by the `os dev` and `os start` parents (`resolveDevDatabase`, `resolveStartDatabase`) and by `os db clean`. - **CLI host** (`resolveDriverType` with `resolveStorageDefinition`), reached by `os serve`'s legacy path only: `OS_MODE=off|none|legacy`, `bootMode: 'off'`, or a host config (`shouldBootWithLibrary`). - One refusal per host, at the resolver. No command carries a copy. ## A3: refusal shape and code The refusals reuse each host's existing shape for an unrecognised driver or scheme: the runtime's plain `Error`, prefixed `[StandaloneStack]` inside the standalone stack, and the CLI's `UnsupportedDriverError`. No ledgered error code is involved, and the error-code ledger is not edited. The CLI flag door is oclif's own allowlist refusal, derived from the spec (see Acceptance notes). ## A4: parity pin `driver-vocabulary-parity.test.ts` is the pin, with no exemption: - The accept rows still iterate the selection face, so the three spellings leave them by derivation. - A new row asserts which spellings the table withdrew: contract face yes, selection face no, derived from `DRIVER_ID_ALIASES`. - New refuse rows: both hosts refuse `memory` / `mingo` / `in-memory`, in dev and in prod, and both name `--fresh` and `:memory:`. - New URL-door rows: both hosts refuse `memory://`, `memory://named`, `mingo://` and `MINGO://upper` with no driver named. - A control: both hosts accept `:memory:` as `sqlite`. - The `mingo://` cross-host divergence (the CLI accepted it, the runtime called it an unknown scheme) is closed: both hosts now recognise and refuse it. ## A5: the census ledger row `scripts/driver-memory-census.ledger.json`, gated by `scripts/check-driver-memory-census.mjs`. The file is under `scripts/`: it is not `packages/spec` and not a governed surface. The CLI manifest row's `why` is rewritten, its axis is unchanged, and `check:driver-memory-census` is green. ## A6: reverse verification Both legs were run through `scripts/ablation-replace.mjs` on the committed tree (`b0b835ba75`), in WRAP mode, with a trap that restores from `HEAD` by absolute path. - **Runtime leg.** Three sites were planted, each behind an `OS_ABLATION_RUNTIME_*` env marker: the rung refusal, `refuseRetiredMemoryEngine`, and the `unsupportedDriverMessage` branch. Each anchor went 1 to 0, and each blob changed. - Runtime rebuilt (exit 0); `ablation-dist-preflight` found all three markers in `dist/` (exit 0 each). - Runtime pins: **14 red** of 124, in 4 files. - CLI: `dev-default-db` 2 red; the parity pin 7 red, each failing on its **runtime-half** lines (`:231`, `:254`). - `storage-driver.test.ts` (the CLI host): **green**. - Restore: blob equals `HEAD`, `git diff HEAD` empty. Runtime rebuilt; `preflight --absent` exit 0 for all three markers. - **CLI leg.** Two sites were planted in `storage-driver.ts`, the inference refusal and the spelling refusal; the anchors went 1 to 0. The tests import that file from source, so no build was involved. - `storage-driver.test.ts`: **9 red**. The parity pin: 7 red, each failing on its **CLI-half** lines (`:225`, `:248`). - The other host stays **green**: runtime pins 124/124, and `dev-default-db` (runtime-backed). - Restore: blob equals `HEAD`, 0 markers left, `git diff HEAD` empty. ## Tests and gates (at `b0b835ba75`) - **Full suites:** - `@objectstack/runtime` (all projects): 319 files, 5210 passed and 19 skipped. - `@objectstack/cli` `--project unit`: 254 files, 3742 passed. - `@objectstack/spec`: 657 files, 18834 passed and 1 todo. - **CLI integration**, the 19 integration-tier files that boot `os migrate` / `bootSchemaStack` or spawn `serve` with a store flag or URL: 19 files, 210 passed and 1 skipped. `duplicates.null-seam.test.ts` now boots nothing, so the tier predicate files it under `unit`. - **Typecheck:** runtime, cli and spec typecheck all exit 0, test-typecheck debt held. - **Lint:** the full `pnpm lint` exits 0. - **Spec artifacts:** `@objectstack/spec check:generated` exits 0. - **Derived gates:** `dispatch-gates --commands` (no paths) derived 121 for the 31 changed paths, and all 121 exit 0. `--ran` reports 121 run and 0 NOT-MEASURED. Three of them (`check:skill-examples`, `check:dual-build-cjs-loads`, `check:i18n-coverage`) first exited 3 for a missing build. They were re-run after a full `turbo run build --filter=!@objectstack/docs` (exit 0) and exit 0. ## Acceptance notes - **The flag door names no replacement.** `--database-driver memory` is refused by oclif's derived allowlist, in oclif's wording. It lists the legal kinds, `sqlite` among them, but does not name `--fresh` or `:memory:`. Making it do so would mean dropping the derived `options:` or special-casing a value at the consumer. Both are forbidden by `database-driver-flag.ts` and by the ruling. Every other door names the replacement. - **The URL door refuses after the parent's banner.** `os dev --database memory://x` prints the parent's `Database: memory://x` row, and the serve child then refuses, naming `OS_DATABASE_URL` (the channel it received the URL on). This is the same shape every unsupported scheme already has. The driver spelling is refused before the banner. The banner finding (a `file:` row beside an in-memory child) cannot occur any more. - **The CLI's `@objectstack/driver-memory` dependency** now serves no CLI-owned path. The ledger row says so. It is kept, because removing a published dependency is a packaging decision the ruling did not take. Carrier: none. - `duplicates.ts`'s header still quotes the original `memory://qa` measurement. That is a dated record of the defect, not an instruction. --- _Generated by [Claude Code](https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b610eab commit 9a4182a

31 files changed

Lines changed: 1011 additions & 365 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/runtime': minor
4+
'@objectstack/cli': minor
5+
---
6+
7+
fix(spec,runtime,cli)!: the in-memory (mingo) engine is no longer a boot store — every boot door refuses it and names SQLite instead (#21492, #21572)
8+
9+
Clause-②: yes (narrowing)
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) no metadata body, authorable key or stored shape moves: the driver table keeps `memory`, `mingo`, `in-memory` and `inmemory` on its config-contract face, so `resolveDriverId` answers them exactly as before and a stored `datasource.driver: memory` still parses against `MemoryConfigSchema`; what narrows is the boot selection (`--database-driver`, `OS_DATABASE_DRIVER`, `databaseDriver`, a `memory://` or `mingo://` database URL, and a project's default datasource declared on the engine), which is host configuration that `objectstack migrate meta` does not rewrite — and no rewrite would be truthful, since the only replacement is a different engine the operator has to choose. The other categories are closed on facts: all three packages publish (not `unpublished`); no ADR-0087 id covers a boot selection (not `registered` / `already-registered`); and the change is runtime behaviour plus exported constant values, not an interface or a type alone (not `runtime-interface-only` / `type-surface-only`). -->
12+
13+
**BREAKING**: the in-memory (mingo) engine can no longer be selected as the store a server, a migration or an embedded stack boots on. It refuses every tenant-scoped read by design, so a boot on it signed a user in and then answered `503` to every data request; there was nothing working to keep. The retirement is made at the declaration: `@objectstack/spec`'s driver table withdrew `memory`, `mingo` and `in-memory` from its selection face (they stay on the config-contract face beside `inmemory`), and every boot door refuses the engine with one sentence that names the replacement.
14+
15+
- **`@objectstack/spec`** — `DATABASE_DRIVER_SELECTION_ALIASES` no longer lists `memory`, `mingo` or `in-memory`; `DATABASE_DRIVER_SELECTION_IDS` no longer lists `memory`; `resolveDatabaseDriverId` answers `undefined` for all four spellings. `resolveDriverId`, `DRIVER_ID_ALIASES`, `BUILTIN_DRIVER_IDS` and the `memory` config contract are unchanged.
16+
- **`@objectstack/cli`** — `--database-driver memory` is refused while the flags parse (`os dev`, `os start`); `OS_DATABASE_DRIVER=memory` / `mingo` / `in-memory` is refused before `os dev` or `os start` prints its Database row; `os serve`'s legacy path refuses the spellings and the `memory://` / `mingo://` schemes as a fatal boot error. The help no longer offers `memory://`.
17+
- **`@objectstack/runtime`** — `createStandaloneStack`, `createDefaultHostConfig` and `resolveStandaloneDatabase` (every ordinary `os dev` / `os start` / `os serve` boot and every `os migrate` subcommand) refuse the spellings, the `memory://` and `mingo://` schemes, and a project whose default datasource is declared with `driver: 'memory'`. `resolveProjectDatabaseUrl` refuses a retired driver selection ahead of every rung, and its `ProjectDatabaseUrlSource` type no longer has the `'memory-driver'` member. `ResolvedStandaloneDatabase.driver` never names `memory`. Two exports are added for hosts that refuse the engine themselves: `namesRetiredMemoryEngine` and `retiredMemoryEngineMessage`.
18+
- **Unchanged:** the `@objectstack/driver-memory` package; a declared non-default datasource with `driver: 'memory'` and a directly constructed `InMemoryDriver`, both still built; SQLite's dev step-down, whose last rung is still this driver.
19+
20+
Migration — one flag change:
21+
22+
- FROM `os dev --database-driver memory` (or `OS_DATABASE_DRIVER=memory`) TO `os dev --fresh` for a throwaway database deleted on exit.
23+
- FROM `OS_DATABASE_URL=memory://…` / `--database memory://…` / `databaseUrl: 'memory://…'` TO `:memory:` (SQLite's own in-memory database), e.g. `OS_DATABASE_URL=:memory:`.
24+
- FROM a default datasource declared `{ driver: 'memory' }` TO a SQLite one, e.g. `{ driver: 'sqlite', config: { filename: ':memory:' } }`.
25+
26+
No shipped example selects the engine. It ships as `minor` under the launch-window convention for accept-set narrowings.

‎content/docs/data-modeling/drivers.mdx‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ libSQL data stayed untouched. Pass the token with `--database-auth-token`
7979
| **SQLite (WASM)** | `@objectstack/driver-sqlite-wasm` | `SqliteWasmDriver` | `sqlite-wasm` \| `wasm-sqlite` \| `wasm` |
8080
| **MongoDB** | `@objectstack/driver-mongodb` | `MongoDBDriver` | `mongodb` \| `mongo` (single-tenant only — see [below](#multi-tenancy-not-supported)) |
8181
| **Turso / libSQL** | `@objectstack/driver-turso` (optional install — see the callout above) | `TursoDriver` | `turso` \| `libsql` |
82-
| **Memory** | `@objectstack/driver-memory` | `InMemoryDriver` | `memory` |
82+
| **Memory** | `@objectstack/driver-memory` | `InMemoryDriver` | none — not a boot store (see [Memory Driver](#memory-driver)) |
8383

8484
> All SQL flavours (PostgreSQL / MySQL / SQLite) are served by a single
8585
> `@objectstack/driver-sql` package — there is no separate
@@ -101,7 +101,7 @@ driver ships that contract as a zod schema, exported from `@objectstack/spec/dat
101101
| `sqlite` \| `sqlite3` | `SqliteConfigSchema` | `filename`, `autoMigrate` |
102102
| `sqlite-wasm` \| `wasm-sqlite` | `SqliteWasmConfigSchema` | `filename`, `persist` |
103103
| `mongo` \| `mongodb` | `MongoConfigSchema` | `url`, `host`, `port`, `database`, `username`, `password`, `authSource`, `options` |
104-
| `memory` \| `in-memory` | `MemoryConfigSchema` | `initialData`, `strictMode`, `persistence` |
104+
| `memory` \| `in-memory` \| `mingo` | `MemoryConfigSchema` | `initialData`, `strictMode`, `persistence` |
105105

106106
An unrecognised key is rejected with its correction, at authoring time and in the
107107
Setup → Datasources wizard alike:
@@ -798,13 +798,25 @@ exits unless persistence is explicitly requested.
798798
It is the **last-resort fallback** in dev mode: `objectstack dev` prefers native
799799
SQLite (`better-sqlite3`), falls back to the pure-JS WASM SQLite driver if the
800800
native binary is unavailable, and only drops to the in-memory driver if WASM also
801-
fails to load. Set `OS_DATABASE_DRIVER=memory` to select it explicitly.
801+
fails to load.
802+
803+
<Callout type="warn">
804+
**It cannot be selected as a boot store.** The driver has no row-level tenant
805+
isolation and refuses every tenant-scoped call, so a server booted on it signs a
806+
user in and then answers `503` to every data request. Every boot door refuses it,
807+
naming the replacement: `OS_DATABASE_DRIVER=memory` / `mingo` / `in-memory`,
808+
`--database-driver memory`, a `memory://` or `mingo://` database URL, and a
809+
project whose **default** datasource is declared with `driver: 'memory'`. For a
810+
throwaway database use `os dev --fresh`; for SQLite's own in-memory database use
811+
the URL `:memory:` (`OS_DATABASE_URL=:memory:`). The driver package itself is
812+
unchanged: the declared datasources and direct construction below still build it.
813+
</Callout>
802814

803815
**Both ways of building the driver are ephemeral by default** (#4083, #4065):
804816

805817
| How it is built | Persistence |
806818
| :--- | :--- |
807-
| A **declared datasource** — `{ driver: 'memory' }` in a stack/app config | **Ephemeral.** Nothing is written to disk unless the declaration sets `config.persistence` — and when it does, the destination is scoped per datasource, so two memory datasources never share one file. |
819+
| A **declared datasource** — `{ driver: 'memory' }` in a stack/app config (not as the project's default datasource, which the boot refuses) | **Ephemeral.** Nothing is written to disk unless the declaration sets `config.persistence` — and when it does, the destination is scoped per datasource, so two memory datasources never share one file. |
808820
| `new InMemoryDriver()` **constructed directly** | **Ephemeral.** Pass `persistence` to opt in (see below). Note the scoping above is the *factory's* job: two directly-constructed `'auto'` drivers in one process still share the single default path. |
809821

810822
```typescript

‎content/docs/deployment/cli.mdx‎

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ predicate/schema/binding mistakes that fail silently at runtime), and `os dev --
6969
</Callout>
7070

7171
<Callout type="tip">
72-
`os dev --ui` starts a dev server with the bundled Console UI, auto-loads ObjectQL, an in-memory driver when appropriate, and the Hono HTTP server.
72+
`os dev --ui` starts a dev server with the bundled Console UI, auto-loads ObjectQL, a SQLite database (a persistent project file by default, a throwaway one with `--fresh`), and the Hono HTTP server.
7373
</Callout>
7474

7575
## Commands
@@ -182,8 +182,8 @@ os dev --database file:./data/test.db --auth-secret $(openssl rand -hex 32)
182182
| Flag | Env equivalent | Purpose |
183183
|---|---|---|
184184
| `-a, --artifact <path\|url>` | `OS_ARTIFACT_URL` / `OS_ARTIFACT_PATH` | Boot a pre-built artifact directly; **skips auto-compile** |
185-
| `-d, --database <url>` | `OS_DATABASE_URL` | `file:…` / `libsql://` / `postgres://` / `mongodb://` / `memory://` |
186-
| `--database-driver <kind>` | `OS_DATABASE_DRIVER` | Force `sqlite` \| `sqlite-wasm` \| `turso` \| `postgres` \| `mysql` \| `mongodb` \| `memory` |
185+
| `-d, --database <url>` | `OS_DATABASE_URL` | `file:…` / `:memory:` / `libsql://` / `postgres://` / `mongodb://` |
186+
| `--database-driver <kind>` | `OS_DATABASE_DRIVER` | Force `sqlite` \| `sqlite-wasm` \| `turso` \| `postgres` \| `mysql` \| `mongodb` |
187187
| `--database-auth-token <t>` | `OS_DATABASE_AUTH_TOKEN` | libsql/Turso token |
188188
| `--auth-secret <s>` | `OS_AUTH_SECRET` | Override the dev-fallback secret |
189189
| `--environment-id <id>` | `OS_ENVIRONMENT_ID` | Environment identifier (default `env_local`) |
@@ -472,8 +472,8 @@ os start
472472
|---|---|---|
473473
| `-a, --artifact <path\|url>` | `OS_ARTIFACT_PATH` | File path or `http(s)://` URL to the compiled artifact |
474474
| — | `OS_ARTIFACT_URL` | Boot a published artifact **by reference**, optionally content-hash pinned via a `#sha256=` fragment. See [Artifact-pinned boot](/docs/deployment/self-hosting#artifact-pinned-boot-os_artifact_url) |
475-
| `-d, --database <url>` | `OS_DATABASE_URL` | `file:…` / `libsql://` / `postgres://` / `mongodb://` / `memory://` |
476-
| `--database-driver <kind>` | `OS_DATABASE_DRIVER` | Force `sqlite` \| `sqlite-wasm` \| `turso` \| `postgres` \| `mysql` \| `mongodb` \| `memory` when the URL is ambiguous |
475+
| `-d, --database <url>` | `OS_DATABASE_URL` | `file:…` / `:memory:` / `libsql://` / `postgres://` / `mongodb://` |
476+
| `--database-driver <kind>` | `OS_DATABASE_DRIVER` | Force `sqlite` \| `sqlite-wasm` \| `turso` \| `postgres` \| `mysql` \| `mongodb` when the URL is ambiguous |
477477
| `--database-auth-token <token>` | `OS_DATABASE_AUTH_TOKEN` | Auth token for libsql/Turso |
478478
| `--auth-secret <secret>` | `OS_AUTH_SECRET` / `AUTH_SECRET` | Secret for `@objectstack/plugin-auth`. If neither the flag nor the env var is set, `os start` **auto-generates one** and persists it at `<home>/auth-secret` |
479479
| `--home <dir>` | `OS_HOME` | Home directory for persistent state (default `<cwd>/.objectstack` when an `objectstack.config.ts` is present, otherwise `~/.objectstack`) |
@@ -502,10 +502,24 @@ The fall-through applies to the **conventional** locations only. Remote
502502
(`http(s)://`) sources cannot be checked up front and are validated when fetched.
503503
</Callout>
504504
505+
<Callout type="warn">
506+
**The in-memory (mingo) engine is not a boot store.** It refuses every
507+
tenant-scoped read, so a server booted on it signed you in and then answered
508+
`503` to every data request. Every boot command — `os dev`, `os start`,
509+
`os serve` and every `os migrate` subcommand — refuses it: the
510+
`--database-driver memory` flag value (rejected while the flags parse),
511+
`OS_DATABASE_DRIVER=memory` / `mingo` / `in-memory`, and a `memory://` or
512+
`mingo://` database URL, each with a message naming the replacement. Use SQLite
513+
instead: `os dev --fresh` for a throwaway database that is deleted on exit, or
514+
`--database :memory:` (`OS_DATABASE_URL=:memory:`) for SQLite's own in-memory
515+
database. A datasource you declare with `driver: 'memory'` still validates;
516+
as the project's **default** datasource it is refused at boot the same way.
517+
</Callout>
518+
505519
**What it boots:**
506520
- Reads the artifact's `manifest`, `objects`, `views`, `flows`, …
507521
- Auto-registers the platform services declared in `requires: [...]` (e.g. `ai`, `automation`, `analytics`, `auth`, `ui`). Declaring a **service** capability (`automation`, `analytics`, `ai`, `audit`, …) is a *requirement*: if its provider package isn't installed, boot **fails fast** with a clear error instead of silently starting without a capability you asked for. (`auth` and `ui` are tier-gated with their own opt-in rules — `auth`'s secret-gated skip is described below.)
508-
- Auto-detects the driver from the database URL scheme (`memory://` → in-memory, `libsql://`/`https://*.turso.*` → Turso — via the optional `@objectstack/driver-turso` package, and a loud failure with the install command when it is missing rather than a fallback to sqlite —, `postgres[ql]://`/`pg://` → pg, `mongodb[+srv]://` → MongoDB, otherwise sqlite)
522+
- Auto-detects the driver from the database URL scheme (`libsql://`/`https://*.turso.*` → Turso — via the optional `@objectstack/driver-turso` package, and a loud failure with the install command when it is missing rather than a fallback to sqlite —, `postgres[ql]://`/`pg://` → pg, `mongodb[+srv]://` → MongoDB, otherwise sqlite; `:memory:` is SQLite's own in-memory database). `memory://` and `mingo://` are refused — see the callout below.
509523
- Runs standalone boot mode with one active environment.
510524
511525
**Authentication:**

‎content/docs/deployment/environment-variables.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ read at startup unless noted otherwise. Boolean variables accept `true` / `false
4646

4747
| Variable | Type | Default | Description |
4848
|:---|:---|:---|:---|
49-
| `OS_DATABASE_URL` | url | — | Database connection string (e.g. `file:./data.sqlite`, `postgres://…`, `mysql://…`, `mongodb://…`, `memory://`). `libsql://` / `*.turso.io` (Turso) is inferred too, but its driver is an **optional** package: `npm install @objectstack/driver-turso`, otherwise the boot fails loudly with that command — it never falls back to SQLite. |
49+
| `OS_DATABASE_URL` | url | — | Database connection string (e.g. `file:./data.sqlite`, `:memory:` for SQLite's own in-memory database, `postgres://…`, `mysql://…`, `mongodb://…`). `memory://` and `mingo://` name the in-memory (mingo) engine, which is not a boot store, and are refused at boot with a message naming the replacement. `libsql://` / `*.turso.io` (Turso) is inferred too, but its driver is an **optional** package: `npm install @objectstack/driver-turso`, otherwise the boot fails loudly with that command — it never falls back to SQLite. |
5050
| `OS_DATABASE_AUTH_TOKEN` | string | — | Auth token for a libSQL/Turso connection (`--database-auth-token`). The vendor's own `TURSO_AUTH_TOKEN` is read as a fallback and is **not** renamed (see the third-party names note above). Ignored by every other driver — their credentials live in the URL. |
51-
| `OS_DATABASE_DRIVER` | enum | inferred | Force a specific driver when the URL is ambiguous. `memory` \| `sqlite` \| `sqlite-wasm` \| `postgres` \| `mysql` \| `mongodb` \| `turso`. `mysql` is a supported deployment target that carries three dialect caveats — two of them integrity guarantees MySQL cannot enforce at the database. Read [MySQL dialect caveats](/docs/data-modeling/drivers#mysql-dialect-caveats) before choosing it. |
51+
| `OS_DATABASE_DRIVER` | enum | inferred | Force a specific driver when the URL is ambiguous. `sqlite` \| `sqlite-wasm` \| `postgres` \| `mysql` \| `mongodb` \| `turso`. `memory` / `mingo` / `in-memory` are refused at boot: the in-memory engine cannot serve a tenant-scoped read, so use `os dev --fresh` or `OS_DATABASE_URL=:memory:` instead. `mysql` is a supported deployment target that carries three dialect caveats — two of them integrity guarantees MySQL cannot enforce at the database. Read [MySQL dialect caveats](/docs/data-modeling/drivers#mysql-dialect-caveats) before choosing it. |
5252
| `OS_DATABASE_SQLITE_JOURNAL_MODE` | enum | `wal` | Journal mode for **file-backed** SQLite. `wal` (default) lets a dev server and CLI commands share one file without blocking each other, and is what makes the `os migrate` occupancy check reliable. Set to `delete` for SQLite's rollback journal — required when the database lives on a **network filesystem** (NFS/SMB), where WAL cannot work. The setting is applied, not merely skipped: `delete` converts a database that already adopted WAL back. Ignored for `:memory:`, for the WASM SQLite driver, and for non-SQLite drivers. A per-datasource `sqliteJournalMode` in driver config outranks it. See [Journal mode](/docs/data-modeling/drivers#journal-mode-wal-and-cross-process-access). |
5353
| `OS_DATABASE_POOL_MAX` | integer | `5` | Maximum pooled connections **each replica** opens to `postgres` / `mysql`. The pool, not the database, is usually what caps authed throughput on a multi-replica deployment: at the default of 5 a 3-replica cluster reaches at most ~15 connections, which can plateau the data API while the database still has most of its `max_connections` free. Size it so **`replicas × OS_DATABASE_POOL_MAX` stays below the database's `max_connections`**, leaving headroom for migrations and admin connections — e.g. 3 replicas × 40 = 120 against `max_connections=200`. A per-datasource `pool.max` in the datasource's own definition outranks it, and with the variable unset nothing changes. A value that is not a positive integer refuses the boot rather than falling back to the default. Ignored by `memory` / `sqlite` / `sqlite-wasm` / `turso`, which open no pool — declaring a `pool` block on those is an error, not a silent drop. |
5454
| `OS_ALLOW_DRIVER_CONNECT_FAILURE` | boolean | `false` | Escape hatch for the driver-connect boot guard. By default a data driver that fails to connect at startup **refuses the boot** — a server that cannot reach its database must not report itself started and then fail every request. The same guard covers a **declared datasource** that objects bind to via `datasource: '…'`, or an `external` one with `validation.onMismatch: 'fail'`: those objects have no fallback datasource, so an unconnected one means they are all dead. Set to `1` to boot anyway, in an explicitly degraded state logged loudly at startup. There is **no reconnection**: whatever failed stays dead for the process lifetime and every query and schema sync routed to it fails. |

‎content/docs/plugins/index.mdx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,9 @@ Security features including field-level and row-level security.
222222
- Middleware-based security
223223

224224
### `@objectstack/driver-memory`
225-
In-memory (mingo) data driver for development and testing.
226-
- Opt in explicitly with `OS_DATABASE_DRIVER=memory`, `--database-driver memory`, or a `memory://` URL — when no driver is configured the dev default is SQLite, not this driver
225+
In-memory (mingo) data driver for testing and embedding.
226+
- Not a boot store: `OS_DATABASE_DRIVER=memory`, `--database-driver memory` and a `memory://` URL are refused at boot, because the driver refuses every tenant-scoped read. For a throwaway dev database use `os dev --fresh`; for SQLite's own in-memory database use `OS_DATABASE_URL=:memory:`
227+
- Still built for a declared `driver: 'memory'` datasource and by direct construction (`new InMemoryDriver()`)
227228

228229
---
229230

0 commit comments

Comments
 (0)