|
| 1 | +--- |
| 2 | +"@objectstack/spec": major |
| 3 | +--- |
| 4 | + |
| 5 | +feat(spec)!: one driver vocabulary — `mongo` → `mongodb`, `turso` gets a config contract (#6345) |
| 6 | + |
| 7 | +`packages/spec` has owned the driver alias table since #4410, for one reason |
| 8 | +stated in its own module comment: two tables would let the id that SELECTS a |
| 9 | +driver and the id that selects that driver's CONFIG CONTRACT disagree. That |
| 10 | +argument was right and the table was right; it just never reached the two boot |
| 11 | +hosts. Measured on `main` before this change, driving the real entry points: |
| 12 | + |
| 13 | +| | `os start` | `os migrate` | |
| 14 | +| :-- | :-- | :-- | |
| 15 | +| `OS_DATABASE_DRIVER=pg` | accepted (`postgres`) | **refused by name** | |
| 16 | +| `OS_DATABASE_DRIVER=libsql` | accepted (`turso`) | **refused by name** | |
| 17 | + |
| 18 | +**10 of 21 spellings disagreed.** Three prior cards (#3276, #5820, #6265) each |
| 19 | +fixed one spelling on one side, each with a green pin — and every pin drove |
| 20 | +exactly one host, which is why the fork survived all three. |
| 21 | + |
| 22 | +**What this changeset changes in `@objectstack/spec`.** |
| 23 | + |
| 24 | +The flat `Record<string, BuiltinDriverId>` becomes one table with a row per |
| 25 | +driver carrying `id`, `aliases`, `contractOnlyAliases` and `hasLocalDefault`. |
| 26 | +`BUILTIN_DRIVER_IDS`, `DRIVER_ID_ALIASES` and `resolveDriverId` are projections |
| 27 | +of it — `BUILTIN_DRIVER_IDS` keeps its exact tuple type, so the api-surface delta |
| 28 | +for this PR is purely additive (10 new exports, nothing removed or renamed). |
| 29 | + |
| 30 | +Three faces are new, and they are what the two hosts consume: |
| 31 | +`resolveDatabaseDriverId()` (the selection face), `driverHasLocalDefault()` (does |
| 32 | +this driver have anything to fall back on with no URL) and |
| 33 | +`DATABASE_DRIVER_SELECTION_ALIASES` (what a refusal message enumerates). |
| 34 | + |
| 35 | +**BREAKING — the canonical mongo id is `mongodb`.** `resolveDriverId('mongo')` |
| 36 | +now returns `'mongodb'`; `BuiltinDriverId` no longer includes `'mongo'`; |
| 37 | +`DRIVER_CONFIG_SCHEMAS` and `MongoDriverSpec.id` follow. The old canon was the |
| 38 | +one string on the platform that said `mongo` while both hosts, the npm package |
| 39 | +(`@objectstack/driver-mongodb`) and every URL scheme said `mongodb`, and the |
| 40 | +maintainer's ruling renames it rather than adding a mapping layer, so that |
| 41 | +selection canon and contract canon are one string. |
| 42 | + |
| 43 | +`mongo` **stays an accepted alias**, deliberately: nothing that authored it |
| 44 | +breaks, and a deployment that never replays the conversion still resolves the |
| 45 | +same contract and builds the same driver. What needs migrating is the STORED |
| 46 | +value, because the canonical id is published as `DRIVER_CATALOG.id` — what Studio |
| 47 | +writes into `datasource.driver` — so after the rename the form emits `mongodb` |
| 48 | +while older rows carry `mongo`, and a reader matching stored rows against the |
| 49 | +catalog id silently misses them. The ADR-0087 D2 conversion |
| 50 | +`datasource-driver-mongo-to-mongodb` converges them at every rehydration seam. |
| 51 | + |
| 52 | +**`turso`/libSQL becomes a complete builtin.** It was the mirror image of the |
| 53 | +mongo problem: both hosts dispatched it while spec shipped no contract, so |
| 54 | +`validateDriverConfig('turso', …)` answered `{ known: false }` and a libSQL |
| 55 | +`config` was the one connection block on the platform with no gate — `{ token }` |
| 56 | +(the wrong key; it is `authToken`) was accepted in silence and the connection |
| 57 | +attempted unauthenticated. `TursoConfigSchema` closes that. The keys are drawn |
| 58 | +from what `TursoDriverConfig` actually READS, not from what libSQL supports, so |
| 59 | +the fix does not open a new inert slot: `client` (a live object, unauthorable), |
| 60 | +`pool` and `schemaMode`/`readOnly` (datasource-level) are deliberately absent. |
| 61 | + |
| 62 | +**Consumers of the `{ known: false }` answer, and what the flip does to each** — |
| 63 | +established before making it, since a consumer depending on the negative answer |
| 64 | +would have been a stop condition: |
| 65 | + |
| 66 | +1. `DatasourceSchema`'s `reportDriverConfigIssues` — was a no-op for turso, now |
| 67 | + parses. An authored turso `config` gains a real verdict. |
| 68 | +2. `service-datasource`'s `assertValidConfig` (the Setup wizard's door) — same |
| 69 | + flip, same reason. |
| 70 | +3. `DRIVER_CATALOG` — turso is deliberately NOT curated into the connection form, |
| 71 | + the same call `sqlite-wasm` has carried since #4410. No visible change. |
| 72 | +4. `driverReadsDeclaredPool` — answers `true` for turso before AND after (via the |
| 73 | + unknown-id branch before, the not-rejected branch now). Verdict unchanged. |
| 74 | + |
| 75 | +**`sql` and `wasm` join the selection face; `sqlite3`, `better-sqlite3`, |
| 76 | +`mariadb` and `inmemory` do not.** The ruling fixes the selection face as the |
| 77 | +union of what the two hosts accepted, and those four were accepted by neither — |
| 78 | +so they stay `contractOnlyAliases`: they keep resolving a config contract |
| 79 | +(dropping that would silently un-validate a stored `driver: 'sqlite3'` row) while |
| 80 | +`resolveDatabaseDriverId` refuses them, because converging two hosts is not a |
| 81 | +licence to widen a boot flag on no ruling. That distinction is the thing the flat |
| 82 | +`Record` could not express and is why the table has two alias columns. |
| 83 | + |
| 84 | +**Why `major` and not `minor`.** The alias widening alone would be `minor` — it |
| 85 | +only accepts more. The rename is what forces `major`: `BuiltinDriverId` loses a |
| 86 | +member, so every TypeScript consumer that switches on it or types a variable as |
| 87 | +it fails to compile, and `DRIVER_CONFIG_SCHEMAS['mongo']` is gone. That is a |
| 88 | +compile-time break even though the runtime behaviour is compatible, and pricing |
| 89 | +it as `minor` because "nothing breaks at run time" would be exactly the |
| 90 | +half-truth a consumer discovers at build time. |
| 91 | + |
| 92 | +<!-- adr-0087: registered datasource-driver-mongo-to-mongodb --> |
0 commit comments