You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit f22f0e8
Browse filesBrowse the repository at this point in the historyBrowse files
`DecisionConfigSchema` declares an optional `mode: 'exclusive' | 'inclusive'` — the contract half of the #15429 ruling. The author of a `decision` node that routes on its out-edges can now declare whether it takes only the first out-edge whose condition holds or every one of them — with taking every one as the value that must be written down, the way BPMN separates the exclusive gateway from the inclusive one and n8n's Switch keeps "send to all matching outputs" behind an off-by-default toggle (#19867).
6
+
7
+
Clause-②: yes (widening) — one new OPTIONAL key on a published, strict node-config schema, so the set of accepted configs grows. Nothing previously accepted is refused, no key is renamed or retired, and the parsed output of an existing config is unchanged (the key has no `.default()`).
8
+
9
+
-**`'exclusive'`** — only the first out-edge whose condition holds, in the order the edges are declared; this is what an omitted `mode` means. **`'inclusive'`** — every out-edge whose condition holds. Any other value is refused at `mode` with a prescription naming both members' meanings.
10
+
-**⚠️ Declared ahead of its enforcement, on purpose.** The ruling's split order lands this key first, then the engine semantics together with the `os migrate meta` conversion in one change, then the docs. Until that second step ships, nothing reads `mode`: an edge-branched decision still takes EVERY out-edge whose condition holds, whatever `mode` says. The key's own description says so, and the conversion that writes `mode: 'inclusive'` onto every decision relying on today's behaviour ships in the same change as the new traversal, so no flow changes behaviour silently.
11
+
-**A `conditions` list is unaffected**: it is ordered first-match on its own, and `mode` speaks about the out-edges.
12
+
-**Where it binds today**: `decision` config stays export-only (nothing parses it at run time), so the closed pair is enforced by `tsc`, by the published JSON Schema and by a direct parse — the same doors `conditions` has.
fix(driver-turso)!: a REMOTE `TursoDriver` answers or refuses every public `SqlDriver` method it inherited, instead of failing on a Knex connection it does not have (#20055)
6
+
7
+
Clause-②: yes (narrowing)
8
+
9
+
`TursoDriver` extends `SqlDriver`. Before this change, 23 public `SqlDriver` members had no remote-mode arm, so a remote driver ran their Knex implementations. Remote mode builds Knex with no connection, so those calls failed with knex's `Unable to acquire a connection`, which reads as a network fault, or they answered from state no remote schema sync fills. Each one is now answered on the remote database or refused with `NOT_IMPLEMENTED` / 501, and the driver's source lists every public `SqlDriver` member with its remote answer. A method added to `SqlDriver` later fails this package's type check until its remote answer is decided.
10
+
11
+
**BREAKING.** On a remote driver, six members that used to answer now refuse or answer differently. This narrows what a published driver accepts. It ships as `minor` under the repo's launch-window convention for breaking changes (`scripts/check-changeset-no-major.mjs`).
12
+
13
+
-`explain()` and `analyzeQuery()` used to resolve with the SQL the local compiler would build, plus an `error` field in place of a plan. They now reject with `NOT_IMPLEMENTED` / 501.
14
+
-`applyMigrationEntries()` used to resolve. Every entry came back `skipped`, including a destructive entry that `allowDestructive` permitted. It now rejects with `NOT_IMPLEMENTED` / 501, with or without entries.
15
+
-`getKnex()` used to return a Knex instance on which every statement failed. It now throws `NOT_IMPLEMENTED` / 501.
16
+
-`supportsRotation` used to read `true`. It now reads `false`. The lifecycle service reads it, and for an object that declares a rotation storage policy it now takes its age-based reap instead of calling `rotateShards()`, which failed.
17
+
-`setFileColumnsMovedResolver()` used to return `true` ("taken"), and then never asked the resolver. It now returns `false`. Remote mode keeps writing media columns in the JSON encoding, as before.
18
+
19
+
**Refused with a clearer error.** These calls already failed. They now reject with `NOT_IMPLEMENTED` / 501, and the message names the local or embedded-replica transport, or `execute()`, as the alternative:
20
+
21
+
-`introspectSchema()`. The datasource connection test still answers `ok: false`, and its error now says introspection is not supported in remote mode;
22
+
-`findWithWindowFunctions()`;
23
+
-`rotateShards()`;
24
+
-`distinct()` called with `options.tenantId` on an object that has a tenant column. No remote read applies the tenant scope, so an answer would list every organization's values.
25
+
26
+
**Now answered on the remote database.** These used to fail:
27
+
28
+
-`distinct()` without a tenant scope runs a `SELECT DISTINCT` and answers what local mode answers for the same rows. The filter and the value presentation match, and an unknown column is refused with `INVALID_FIELD` / 400.
29
+
-`reclaimSpace()` sends the statement local mode issues, `PRAGMA incremental_vacuum`, to the remote database. The lifecycle service's sweep now counts the datasource as reclaimed instead of logging a warning. The statement returns pages only on a database whose `auto_vacuum` mode is `INCREMENTAL`.
30
+
31
+
`RemoteTransport` gains one public method, `compileDistinct()`. It builds the statement behind a remote `distinct()`.
32
+
33
+
**Unchanged.** Local and embedded-replica modes run the inherited Knex members as before. On a remote driver, `commitTransaction()` and `rollbackTransaction()` still reject through the `commit()` / `rollback()` refusal. The deferred-DDL readers still report nothing deferred, and `getSchemaSyncStats()` still answers `{ created: 0, existing: 0 }`, which the `IDataDriver` contract reads as "cannot say". The bookkeeping and dialect members also answer as before.
34
+
35
+
<!-- adr-0087: not-required (no-migration-prescription) A narrowing of which calls a remote `TursoDriver` answers: no key, spec symbol, Zod schema, object definition or stored representation is added, removed or renamed. `TursoDriverConfig` and both `TursoConfigSchema` copies are untouched, and every `SqlDriver` member keeps its name and signature. What moves is only whether a remote driver answers a call or refuses it with NOT_IMPLEMENTED/501, so `objectstack migrate meta` has nothing to visit and there is no tombstone to mint. -->
|**mode**|`Enum<'exclusive' \| 'inclusive'>`| optional | Declares how many out-edges an edge-branched decision takes when more than one out-edge condition holds: 'exclusive' = only the first, in the order the edges are declared (what an omitted mode means); 'inclusive' = every one that holds. Declared ahead of the engine change that reads it: until that lands, an edge-branched decision takes every out-edge whose condition holds, whatever this says. A conditions list is first-match on its own. |
Each call below is refused in remote mode with `code: 'NOT_IMPLEMENTED'` and
125
125
`status: 501`: the call is valid, and the remote transport does not have the
126
-
capability. The check runs before the call reads or writes anything.
126
+
capability. The check runs before the call reads or writes anything. Every other
127
+
public `SqlDriver` method is answered in remote mode; the section after the
128
+
table lists the ones that answer differently from local mode.
127
129
128
130
| Operation | Refused call | Use instead |
129
131
|:---|:---|:---|
130
-
| Transactions |`beginTransaction()`, `commit()`, `rollback()`, and `options.transaction` passed to any `RemoteTransport` method in the tree above, to `aggregate()` or to `syncSchemasBatch()`| The local or embedded-replica transport, which run Knex transactions and honour `options.transaction`|
132
+
| Transactions |`beginTransaction()`, `commit()`, `rollback()` (and their deprecated aliases `commitTransaction()` and `rollbackTransaction()`), and `options.transaction` passed to any `RemoteTransport` method in the tree above, to `aggregate()` or to `syncSchemasBatch()`| The local or embedded-replica transport, which run Knex transactions and honour `options.transaction`|
131
133
| Record numbers |`create()`, `bulkCreate()`, and an `upsert()` with no `id`, `_id` or `conflictKeys`, when a row leaves an `autonumber` field empty (`undefined`, `null` or `''`) | The local or embedded-replica transport, which generate record numbers, or a value you supply, which is written unchanged |
132
134
| Deferring schema DDL |`setDeferredDdl(true)`, which `os migrate plan` calls. `setDeferredDdl(false)` is accepted | Run the command against a local SQLite copy of the database (a `file:` URL) |
133
135
| Schema drift detection |`detectManagedDrift()`|`os migrate plan` against a local SQLite copy of the database (a `file:` URL) |
134
136
| Planning the ADR-0104 media column move |`planMediaColumnMove()`, the column step of `os migrate files-to-references`| The local or embedded-replica transport, which plan it |
137
+
| Applying drift entries |`applyMigrationEntries()`, with or without entries | An ordinary boot against the datasource (`os serve` / `os start`) performs the additive schema sync |
138
+
| Schema introspection |`introspectSchema()`, which the datasource connection test and the federation validation sweep call | A datasource pointed at a local SQLite copy of the database (a `file:` URL) or at an embedded replica |
139
+
| Window-function reads |`findWithWindowFunctions()`| The local or embedded-replica transport, or the window statement sent through `execute()`|
140
+
| Query plans |`explain()`, `analyzeQuery()`| The same query against the local or embedded-replica transport |
141
+
| Shard rotation (ADR-0057) |`rotateShards()`| The local or embedded-replica transport. In remote mode the lifecycle service enforces the same window with an age-based reap (see below) |
142
+
| The Knex instance |`getKnex()`: remote mode builds Knex with no connection |`execute()`, or the libSQL client from `getLibsqlClient()`|
143
+
| Tenant-scoped distinct values |`distinct()` with `options.tenantId` on an object that has a tenant column | The same call without `tenantId`, or the local or embedded-replica transport |
0 commit comments