Skip to content

Commit ddade65

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-16304-clause2-sibling-declaration
2 parents 15c32e7 + 7f96e14 commit ddade65

14 files changed

Lines changed: 1455 additions & 28 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/driver-sql": minor
3+
---
4+
5+
`SqlDriver`'s object-definition parameters now DECLARE every key they read. `initObjects` accepts `lifecycle`, and the whole rotation chain — `rotateShards`, `ensureRotation`, `ensureShardTable` — accepts `tenancy` and `indexes`, spelled as a **fresh object literal** rather than only as a value bound to a variable first.
6+
7+
The driver read those keys off caller objects all along, through `(obj as any).<key>`, while the parameter's own inline type listed none of them. That is refused or accepted depending only on where the object is spelled: TypeScript's excess-property check fires on a fresh literal and not on one hoisted to a variable, so the same call compiles in one shape and is `TS2353` in the other. The loud outcome is the harmless one. The bad one is an author — or an AI reading the signature — concluding the key is not accepted and DROPPING it, at which point a declared UNIQUE is never synced and an ADR-0057 rotation policy is never armed, with nothing anywhere saying so.
8+
9+
This is the third instance of one class, not a third coincidence: `tenancy` (#4311) and `indexes` (#16570) were the first two, each fixed one key at a time. The class is now held by a gate — `scripts/check-object-def-param-keys.mjs` — that reads parameter lists as an AST and covers the shape no in-file check could see: a subclass in another published package overriding one of these methods with a narrower literal.
10+
11+
- **What widened.** `rotateShards(objectDef)` gains `tenancy?: any` and `indexes?: any[]`; `ensureRotation(…, obj, …)` gains the same two; `ensureShardTable(…, obj)` gains `indexes?: any[]`; `initObjects(objects)` gains `lifecycle?: any`. All three rotation links carry the keys, not just the leaf that reads them — declaring them only on the leaf would leave the two links above still narrowing the same value in flight, so a fresh literal handed to the public entry point would still have been refused.
12+
- **What did NOT widen, deliberately.** The accept set still has a boundary: a misspelling (`indexs`, `tenancyy`, `lifecycl`) on a fresh literal is still `TS2353`, pinned by `@ts-expect-error` in `src/sql-driver-16711-object-def-param-keys.test.ts`. A "fix" that relaxed these parameters to `any`, or gave them an index signature, would have turned every other assertion green while deleting the entire layer of protection.
13+
- **Four `as any` casts deleted**, including the residual one in `detectManagedDrift`, whose parameter had declared `indexes` all along. Behaviour is unchanged in every case — the keys were already being read.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@objectstack/driver-sqlite-wasm": minor
3+
---
4+
5+
`SqliteWasmDriver.initObjects` accepts `tenancy`, `indexes` and `lifecycle` in a **fresh object literal**, inherited from the widened `SqlDriver` — and that inheritance is now asserted rather than assumed.
6+
7+
This package overrides neither `initObjects` nor `registerObjectMetadata`, so its published `.d.ts` re-declares none of them and the door it exposes is `SqlDriver`'s, imported from `@objectstack/driver-sql`. Measured on the built declarations: zero re-declarations of `initObjects`, `registerObjectMetadata`, `rotateShards`, `ensureShardTable` or `registerManagedObjectMetadata`. That is the opposite direction of the defect the sibling packages carried — `TursoDriver` overrode `initObjects` with a narrower literal and shadowed a base-class fix for five weeks — and it is recorded here because a consumer reading only this package's changelog would otherwise never learn its accept set moved.
8+
9+
`src/sqlite-wasm-16711-inherited-object-def-keys.test.ts` pins the inheritance inside this package's own tsc program: the inherited parameter is not `any`, each key is present on the element type, a fresh literal carrying them compiles and is read at run time, and a misspelling is still `TS2353`. It goes red both ways — if the base narrows again, and if a future override here re-declares the door more narrowly.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/driver-turso": minor
3+
---
4+
5+
`TursoDriver.initObjects` now declares every key `SqlDriver.initObjects` declares — `tenancy`, `indexes` and `lifecycle` — so a caller of this package can spell them in a **fresh object literal** instead of hoisting the object to a variable to get past the type.
6+
7+
`TursoDriver` OVERRIDES `initObjects`, and an override does not inherit the base's parameter type. Its own literal read `Array<{ name: string; fields?: Record<string, any> }>`, which is what every consumer of `@objectstack/driver-turso` saw — so when #4311 declared `tenancy` on the base in August, that fix did not exist from outside this package, and stayed invisible for five weeks with nothing red anywhere. #16570's `indexes` fix would have escaped by the identical route.
8+
9+
The type face was the only thing refusing the keys. The remote arm forwards the whole object through as `schema`, and `registerRemoteFieldMetadata` reads `tenancy` straight back off it, so the runtime carried both keys the entire time. `tenancy.enabled: false` is the key that decides whether a UNIQUE partitions globally or per organization — an author who hit the refusal and dropped it silently got the other answer.
10+
11+
- `registerRemoteFieldMetadata(obj)` declares `tenancy?: any` and reads it directly; its `(obj as any).tenancy` cast is gone.
12+
- The boundary is intact: a misspelling on a fresh literal is still `TS2353`, pinned in `src/turso-driver-16711-init-objects-param.test.ts`.
13+
- `scripts/check-object-def-param-keys.mjs` now fails the build if this override — or any other subclass override in the workspace — declares fewer keys than the method it shadows, or erases the base's shape with an opaque type or an index signature.

.github/workflows/lint.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4386,6 +4386,29 @@ jobs:
43864386
- name: Check every driver runs the shared conformance cases
43874387
run: pnpm check:driver-conformance
43884388

4389+
# Object-definition parameter keys (#16711). `SqlDriver` takes object
4390+
# definitions as inline object-literal parameter types and reads keys off
4391+
# them that the literal does not list, through `(obj as any).<key>`. Three
4392+
# single-key cards fixed one key each (#4311 `tenancy`, #16570 `indexes`,
4393+
# #16711 `lifecycle`) before anyone called it a class. The escape is
4394+
# silent: TypeScript's excess-property check fires on a FRESH object
4395+
# literal and not on one bound to a variable first, so an author who hits
4396+
# the refusal drops the key — an unsynced UNIQUE, an unarmed ADR-0057
4397+
# rotation policy — and nothing says so.
4398+
#
4399+
# ⛔ NOT scoped to sql-driver.ts, which is the whole ruling: `TursoDriver`
4400+
# OVERRIDES `initObjects` in a separately published package, so #4311's
4401+
# fix was invisible from outside `@objectstack/driver-sql` for five weeks
4402+
# and #16570's would have escaped identically. The gate compares every
4403+
# subclass override's declared keys against the base's, across packages,
4404+
# and refuses the two edits that would make that vacuous (an index
4405+
# signature, an opaque replacement type). AST-based, because the signature
4406+
# this class hides behind wraps across lines and a single-line grep for it
4407+
# returns a silence that reads exactly like a negative.
4408+
# Reads source files only; no build, ~2s.
4409+
- name: Check object-definition parameters declare the keys they are read for
4410+
run: pnpm check:object-def-param-keys
4411+
43894412
# Stall-guard self-test (#4250). scripts/run-with-stall-guard.mjs is what
43904413
# turns a frozen Test Core into a labeled red; six jobs across five
43914414
# workflows now route their test steps through it. But it only executes its

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
"check:type-check-debt": "node scripts/check-type-check-coverage.mjs --self-test && node scripts/check-type-check-coverage.mjs --re-measure",
163163
"check:driver-conformance": "node scripts/check-driver-conformance.mjs --self-test && node scripts/check-driver-conformance.mjs",
164164
"check:driver-memory-census": "node scripts/check-driver-memory-census.mjs --self-test && node scripts/check-driver-memory-census.mjs",
165+
"check:object-def-param-keys": "node scripts/check-object-def-param-keys.mjs --self-test && node scripts/check-object-def-param-keys.mjs",
165166
"check:engine-double-contract": "node scripts/check-engine-double-contract.mjs --self-test && node scripts/check-engine-double-contract.mjs",
166167
"check:where-matcher": "node scripts/check-where-matcher-conformance.mjs --self-test && node scripts/check-where-matcher-conformance.mjs",
167168
"check:objectql-double-limit": "node scripts/check-objectql-double-limit.mjs --self-test && node scripts/check-objectql-double-limit.mjs",

packages/drivers/driver-sql/src/sql-driver-11794-richtext-text-family.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,19 @@ for (const liveCell of [PG_CELL, MYSQL_CELL]) {
322322
// it physically too. Both directions measured, boundary included.
323323
const KT = `${T}_keyed`;
324324
// Hoisted (not an inline literal) the way #11374's `boundedObject()`
325-
// is: `indexes` rides through `initObjects` beyond its narrow
326-
// parameter type, exactly as the platform objects declare it.
325+
// is, exactly as the platform objects declare it.
326+
//
327+
// ⚠️ The second half of what this comment used to say has EXPIRED and
328+
// is kept here as a dated record rather than deleted: it read
329+
// "`indexes` rides through `initObjects` beyond its narrow parameter
330+
// type", and that was true — the signature declared no `indexes` and
331+
// the driver read the key through an `as any` anyway. #16570 declared
332+
// it and #16711 closed the class, so the hoist is no longer LOAD-BEARING
333+
// here; an inline literal would compile today. It stays because
334+
// mirroring #11374's authoring shape is why it was written that way in
335+
// the first place, and because this suite is about column widths, not
336+
// about parameter types. The pin that must stay inline is
337+
// `sql-driver-16711-object-def-param-keys.test.ts`.
327338
const keyedObject = {
328339
name: KT,
329340
fields: {

packages/drivers/driver-sql/src/sql-driver-15479-shadow-plain-unique-duplicates.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,17 @@ declareDialectCell(MYSQL_CELL, 'hash-shadow plain unique over duplicates (#15479
125125
): Promise<{ logs: string[]; err: unknown }> => {
126126
driver = new SqlDriver(cell.config());
127127
const logs = spy();
128-
await driver.initObjects([{ ...meta, indexes: [] }] as any);
128+
// #16711: the `as any` that used to be on both of these calls was a
129+
// workaround for `initObjects` not declaring `indexes`. The signature
130+
// declares it now, so the cast is gone and these two calls are checked
131+
// like any other.
132+
await driver.initObjects([{ ...meta, indexes: [] }]);
129133
const knex = (driver as any).knex;
130134
await knex(meta.name).insert([
131135
{ id: 'a', ...row },
132136
{ id: 'b', ...row },
133137
]);
134-
const err: unknown = await driver.initObjects([meta] as any).then(
138+
const err: unknown = await driver.initObjects([meta]).then(
135139
() => null,
136140
(e) => e,
137141
);
@@ -228,7 +232,7 @@ declareDialectCell(MYSQL_CELL, 'hash-shadow plain unique over duplicates (#15479
228232
it('still creates and enforces the plain shadow unique over clean data', async () => {
229233
driver = new SqlDriver(cell.config());
230234
spy();
231-
await driver.initObjects([plainUniqueOn('os15479_clean')] as any);
235+
await driver.initObjects([plainUniqueOn('os15479_clean')]);
232236

233237
const { cols, idx } = await catalog('os15479_clean');
234238
const shadow = cols.find((c: any) => isHashShadowColumn(c.COLUMN_NAME));

0 commit comments

Comments
 (0)