From c450f007dcaf6b6e4915f3886702c14292f80b30 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 10:55:09 +0000 Subject: [PATCH 1/3] =?UTF-8?q?refactor(drivers)!:=20=E4=BA=94=E4=B8=AA?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8=E7=9A=84=20query=20=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=B7=9F=E8=BF=9B=20DriverQuery=20(#6075)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #5181 (PR #6076) 把 IDataDriver 的六个 query 方法收窄为 DriverQuery (Omit),但五个驱动的实现仍声明 query: QueryAST (turso 侧是 query: any)。方法参数按双变比较所以它不红,可调用方现在 有权省略 object —— 实现的类型说 query.object 是 string,运行期却可能 是 undefined。这是休眠的类型谎言,不是活体缺陷:实测五个驱动没有一个 读 query.object。 把六个契约方法 (find/findOne/count/updateMany/deleteMany/explain) 的 query 参数统一为 DriverQuery,并跟进少量只转发 where/orderBy/groupBy 的私有辅助方法。收窄后「驱动读 query.object」成为编译错误 (TS2339)。 零运行时改动:非测试改动 100% 是类型注解,无逻辑、无行为、无 emit 差异。 测试面按 #5181 的迁移说明逐处重拼 —— 删掉调用字面量里冗余的 object 键; syncSchemasBatch 条目与 distinct 的 QueryInput 不在收窄范围,保持原样。 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx --- .../driver-query-signatures-follow-through.md | 29 +++++++ .../src/memory-datetime-storage.test.ts | 12 +-- ...ry-driver-calendar-day-upper-bound.test.ts | 6 +- .../src/memory-driver-document-not.test.ts | 2 +- ...ry-driver-filter-logic-conformance.test.ts | 7 +- .../driver-memory/src/memory-driver.test.ts | 63 ++++---------- .../driver-memory/src/memory-driver.ts | 14 ++-- .../src/memory-empty-field-constraint.test.ts | 2 +- .../src/memory-filter-ast-vocabulary.test.ts | 2 +- .../memory-filter-refusal-envelope.test.ts | 2 +- .../memory-filter-vocabulary-refusal.test.ts | 2 +- .../src/memory-null-comparand-refusal.test.ts | 1 - .../src/memory-pagination-conformance.test.ts | 2 +- .../src/memory-temporal-conformance.test.ts | 14 ++-- .../src/persistence/persistence.test.ts | 20 ++--- .../driver-mongodb/src/mongodb-driver.ts | 18 ++-- .../src/sql-driver-advanced.test.ts | 42 ++++------ ...l-driver-aggregate-temporal-output.test.ts | 2 +- .../src/sql-driver-array-fields.test.ts | 6 +- .../src/sql-driver-boolean-identity.test.ts | 1 - .../src/sql-driver-bulk-json.test.ts | 2 +- ...ql-driver-calendar-day-upper-bound.test.ts | 6 +- .../sql-driver-cross-field-reference.test.ts | 2 +- .../sql-driver-date-now-default-live.test.ts | 2 +- .../src/sql-driver-date-only.test.ts | 20 ++--- ...-driver-datetime-canonical-storage.test.ts | 6 +- ...river-datetime-filter-text-storage.test.ts | 19 +---- .../src/sql-driver-datetime-filter.test.ts | 7 -- .../sql-driver-datetime-mysql-storage.test.ts | 8 +- ...-driver-datetime-postgres-timezone.test.ts | 4 +- .../sql-driver-empty-field-constraint.test.ts | 1 - .../sql-driver-external-remote-name.test.ts | 16 ++-- .../sql-driver-filter-no-silent-drop.test.ts | 2 +- ...sql-driver-filter-refusal-envelope.test.ts | 2 +- .../src/sql-driver-index-drift.test.ts | 2 +- .../src/sql-driver-like-escape.test.ts | 7 +- .../src/sql-driver-nested-and-filter.test.ts | 2 +- .../src/sql-driver-not-null-safe.test.ts | 1 - .../src/sql-driver-null-operators.test.ts | 17 ++-- .../src/sql-driver-numeric-fidelity.test.ts | 12 +-- .../src/sql-driver-or-filter.test.ts | 4 +- ...river-out-of-contract-filter-input.test.ts | 1 - .../sql-driver-pagination-conformance.test.ts | 24 +++--- .../src/sql-driver-query-signature.test.ts | 82 +++++++++++++++++++ .../src/sql-driver-retention-prune.test.ts | 8 +- .../src/sql-driver-rotation.test.ts | 16 ++-- .../driver-sql/src/sql-driver-schema.test.ts | 12 +-- .../src/sql-driver-server-timing.test.ts | 2 +- .../src/sql-driver-tenant-scope.test.ts | 70 ++++++++-------- .../sql-driver-time-canonical-storage.test.ts | 9 +- .../src/sql-driver-time-live-dialects.test.ts | 13 ++- .../src/sql-driver-time-of-day.test.ts | 12 +-- .../src/sql-driver-timestamp-format.test.ts | 8 +- ...driver-undefined-comparand-refusal.test.ts | 1 - .../src/sql-driver-unique-tenancy.test.ts | 4 +- ...sql-driver-unknown-column-recovery.test.ts | 2 +- ...river-user-datetime-default-format.test.ts | 16 ++-- .../drivers/driver-sql/src/sql-driver.test.ts | 23 +++--- packages/drivers/driver-sql/src/sql-driver.ts | 20 ++--- .../src/sqlite-wasm-driver-advanced.test.ts | 42 ++++------ .../src/sqlite-wasm-driver-schema.test.ts | 6 +- .../sqlite-wasm-driver-tenant-scope.test.ts | 44 +++++----- .../src/sqlite-wasm-driver.test.ts | 22 +++-- .../sqlite-wasm-temporal-conformance.test.ts | 10 +-- .../remote-transport-boolean-identity.test.ts | 6 +- ...te-transport-node-operator-refusal.test.ts | 10 +-- .../src/remote-transport-not-operator.test.ts | 6 +- .../remote-transport-text-predicates.test.ts | 3 +- .../remote-transport-top-level-where.test.ts | 2 +- .../driver-turso/src/turso-driver.test.ts | 4 +- .../drivers/driver-turso/src/turso-driver.ts | 15 ++-- .../turso-filter-logic-conformance.test.ts | 4 +- .../turso-local-remote-null-parity.test.ts | 30 +++---- .../src/turso-pagination-conformance.test.ts | 2 +- .../src/turso-temporal-conformance.test.ts | 10 +-- 75 files changed, 477 insertions(+), 451 deletions(-) create mode 100644 .changeset/driver-query-signatures-follow-through.md create mode 100644 packages/drivers/driver-sql/src/sql-driver-query-signature.test.ts diff --git a/.changeset/driver-query-signatures-follow-through.md b/.changeset/driver-query-signatures-follow-through.md new file mode 100644 index 0000000000..bc6cfd8e4c --- /dev/null +++ b/.changeset/driver-query-signatures-follow-through.md @@ -0,0 +1,29 @@ +--- +"@objectstack/driver-memory": major +"@objectstack/driver-mongodb": major +"@objectstack/driver-sql": major +"@objectstack/driver-sqlite-wasm": major +"@objectstack/driver-turso": major +--- + +refactor(drivers)!: 五个驱动的 query 参数跟进 `DriverQuery`,休眠的类型谎言就此没有藏身处 (#6075) + +#5181(PR #6076)把 `IDataDriver.find/findOne/count/updateMany/deleteMany/explain` 的 query 参数收窄为 `DriverQuery`(`Omit`),并在同一条 changeset 里写明:「把驱动签名一并迁到 `DriverQuery` 是后续的机械收尾」。这就是那次收尾。 + +在此之前,五个驱动的实现仍旧声明 `query: QueryAST`(turso 侧是 `query: any`)。**它不红,也不会红** —— 方法参数按双变比较,实现声明得比契约宽照样满足契约。但调用方现在**有权**省略 `object`,于是这些实现的类型说 `query.object` 是 `string`,运行期却可能是 `undefined`:一句休眠的谎言,没有任何门拦得住下一个照着它写代码的人。 + +收尾之后,「驱动读 `query.object`」直接变成编译错误: + +```ts +// 收窄前:编译通过,运行期可能是 undefined —— 谎言 +// 收窄后:error TS2339: Property 'object' does not exist on type 'DriverQuery'. +const name = query.object; +``` + +**零运行时改动。** 本次改的全部是类型注解:五个驱动的六个契约方法签名,以及为让类型自洽而必须跟进的少量私有辅助方法参数(mongodb 的 `buildFindOptions` / `buildSortSpec`,sql 的 `findRows` / `orderKeysFor`,turso 的 `toRemoteQuery` / `toRemoteReadQuery`,memory 的 `performAggregation`)—— 它们都只转发或读取 `where` / `orderBy` / `groupBy` 这些字段,本来就不读 `object`。turso 的几处 `query: any` 一并收紧,多拿回一批本已放弃的检查。emit 无差异,测试全绿(memory 524、mongodb 206、sql 906、sqlite-wasm 254、turso 788)。 + +**迁移面:删掉驱动调用字面量里的 `object:` 键**,与 #5181 是同一句话,只是现在也覆盖了直接按具体驱动类(`SqlDriver` / `MemoryDriver` / …)而非按 `IDataDriver` 取类型的调用方。编译器会逐处指出来(TS2353 `'object' does not exist in type 'DriverQuery'`)。本仓下游 25 个包实测零处需要改动,改动只落在五个驱动自己的测试里。 + +标 major 的依据与 #5181 一致:**源码级破坏性**(调用点内联字面量),运行时行为零变化。`check:api-surface` 只记录导出的存在与否、不记录签名,因此这条说明同样是该变更唯一的下游载体。 + +`aggregate` / `distinct` / `syncSchemasBatch` 不在本次范围内 —— 它们不是 `IDataDriver` 收窄的那六个方法,其中 `syncSchemasBatch` 的条目里 `object` 是被真实读取的必填键,`expand` 条目里的 `object` 同理命名的是关联对象,都不是冗余。 diff --git a/packages/drivers/driver-memory/src/memory-datetime-storage.test.ts b/packages/drivers/driver-memory/src/memory-datetime-storage.test.ts index 2b721a5d54..700deb7862 100644 --- a/packages/drivers/driver-memory/src/memory-datetime-storage.test.ts +++ b/packages/drivers/driver-memory/src/memory-datetime-storage.test.ts @@ -61,7 +61,7 @@ describe('InMemoryDriver Field.datetime storage (#4047)', () => { it('stores every writer form as canonical UTC ISO text', async () => { await seedMixed(); - const raw = await driver.find('task', { object: 'task' }); + const raw = await driver.find('task', {}); for (const row of raw) { expect(typeof (row as any).created_at, `${(row as any).id} stored form`).toBe('string'); expect((row as any).created_at).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); @@ -93,10 +93,10 @@ describe('InMemoryDriver Field.datetime storage (#4047)', () => { it('keeps #3777/#4042 bound semantics on top of the converged storage', async () => { await seedMixed(); - const gte = await driver.find('task', { object: 'task', where: { created_at: { $gte: '2026-07-28' } } }); + const gte = await driver.find('task', { where: { created_at: { $gte: '2026-07-28' } } }); expect(ids(gte)).toEqual(['d_midnight', 'd_next_day', 's_evening', 's_morning']); - const lt = await driver.find('task', { object: 'task', where: { created_at: { $lt: '2026-07-28' } } }); + const lt = await driver.find('task', { where: { created_at: { $lt: '2026-07-28' } } }); expect(ids(lt)).toEqual(['d_yesterday', 's_old']); const instant = await driver.find('task', { @@ -150,7 +150,7 @@ describe('InMemoryDriver Field.datetime storage (#4047)', () => { // honest subject for an update-path assertion. await driver.create('task', { id: 'u1', due_at: '2026-04-19T10:00:00.000Z' }); await driver.update('task', 'u1', { due_at: new Date('2026-07-28T09:15:00Z') }); - const row: any = (await driver.find('task', { object: 'task', where: { id: 'u1' } }))[0]; + const row: any = (await driver.find('task', { where: { id: 'u1' } }))[0]; expect(row.due_at).toBe('2026-07-28T09:15:00.000Z'); // …and the converged value is reachable by a date window, which is the @@ -171,7 +171,7 @@ describe('InMemoryDriver Field.datetime storage (#4047)', () => { ] as const) { await driver.create('task', { id, created_on }); } - const all = await driver.find('task', { object: 'task' }); + const all = await driver.find('task', {}); for (const row of all) expect(typeof (row as any).created_on).toBe('string'); expect((all.find((r: any) => r.id === 'on_obj')).created_on).toBe('2026-07-28'); @@ -183,7 +183,7 @@ describe('InMemoryDriver Field.datetime storage (#4047)', () => { it('an undeclared object is left alone (no schema → no coercion)', async () => { await driver.create('freeform', { id: 'f1', when: new Date('2026-07-28T09:15:00Z') }); - const row: any = (await driver.find('freeform', { object: 'freeform' }))[0]; + const row: any = (await driver.find('freeform', {}))[0]; expect(row.when).toBeInstanceOf(Date); }); }); diff --git a/packages/drivers/driver-memory/src/memory-driver-calendar-day-upper-bound.test.ts b/packages/drivers/driver-memory/src/memory-driver-calendar-day-upper-bound.test.ts index c03219a08d..b4899e2587 100644 --- a/packages/drivers/driver-memory/src/memory-driver-calendar-day-upper-bound.test.ts +++ b/packages/drivers/driver-memory/src/memory-driver-calendar-day-upper-bound.test.ts @@ -59,13 +59,13 @@ describe('InMemoryDriver — bare-day $lte covers the whole day (#4042)', () => }); it('$gte / $gt / $lt keep their midnight anchoring', async () => { - const gte = await driver.find('task', { object: 'task', where: { created_at: { $gte: '2026-07-28' } } }); + const gte = await driver.find('task', { where: { created_at: { $gte: '2026-07-28' } } }); expect(ids(gte)).toEqual(['t_evening', 't_midnight', 't_morning']); - const gt = await driver.find('task', { object: 'task', where: { created_at: { $gt: '2026-07-28' } } }); + const gt = await driver.find('task', { where: { created_at: { $gt: '2026-07-28' } } }); expect(ids(gt)).toEqual(['t_evening', 't_midnight', 't_morning']); // string '…T00:00' > '2026-07-28' - const lt = await driver.find('task', { object: 'task', where: { created_at: { $lt: '2026-07-28' } } }); + const lt = await driver.find('task', { where: { created_at: { $lt: '2026-07-28' } } }); expect(ids(lt)).toEqual(['t_old', 't_yesterday']); }); diff --git a/packages/drivers/driver-memory/src/memory-driver-document-not.test.ts b/packages/drivers/driver-memory/src/memory-driver-document-not.test.ts index 22471911bc..a886d69ae3 100644 --- a/packages/drivers/driver-memory/src/memory-driver-document-not.test.ts +++ b/packages/drivers/driver-memory/src/memory-driver-document-not.test.ts @@ -86,7 +86,7 @@ describe('[#5324] InMemoryDriver.find compiles a document-level $not', () => { }); const idsFrom = async (driver: InMemoryDriver, where: unknown): Promise => { - const rows = await driver.find('deal', { object: 'deal', fields: ['id'], where: where as FilterCondition }); + const rows = await driver.find('deal', { fields: ['id'], where: where as FilterCondition }); return (rows as Array>).map((r) => String(r.id)).sort(); }; diff --git a/packages/drivers/driver-memory/src/memory-driver-filter-logic-conformance.test.ts b/packages/drivers/driver-memory/src/memory-driver-filter-logic-conformance.test.ts index 8459363f05..03f77f5611 100644 --- a/packages/drivers/driver-memory/src/memory-driver-filter-logic-conformance.test.ts +++ b/packages/drivers/driver-memory/src/memory-driver-filter-logic-conformance.test.ts @@ -146,7 +146,7 @@ describe('[#5324] InMemoryDriver.find — filter logic conformance (the LIVE que }); const ids = async (where: FilterCondition): Promise => { - const rows = await driver.find(TABLE, { object: TABLE, fields: ['id'], where }); + const rows = await driver.find(TABLE, { fields: ['id'], where }); return (rows as Array>).map((r) => String(r.id)).sort((x, y) => x.localeCompare(y)); }; @@ -369,7 +369,7 @@ describe('[#5373] comparand types — the analytics face against the live query const sorted = (ids: string[]): string[] => [...ids].sort((x, y) => x.localeCompare(y)); const findIds = async (where: FilterCondition): Promise => { - const rows = await driver.find(COMPARAND_TABLE, { object: COMPARAND_TABLE, fields: ['id'], where }); + const rows = await driver.find(COMPARAND_TABLE, { fields: ['id'], where }); return sorted((rows as Array>).map((r) => String(r.id))); }; @@ -418,7 +418,6 @@ describe('[#5373] comparand types — the analytics face against the live query */ it('the fixture stores real booleans and real nulls, not their stringified forms', async () => { const rows = (await driver.find(COMPARAND_TABLE, { - object: COMPARAND_TABLE, fields: ['id', 'is_active', 'closed_at', 'code'], })) as Array>; const one = rows.find((r) => r.id === '1')!; @@ -606,7 +605,7 @@ describe('[#5374] operator semantics — the analytics face against the live que const sorted = (ids: string[]): string[] => [...ids].sort((x, y) => x.localeCompare(y)); const findIds = async (where: FilterCondition): Promise => { - const rows = await driver.find(COMPARAND_TABLE, { object: COMPARAND_TABLE, fields: ['id'], where }); + const rows = await driver.find(COMPARAND_TABLE, { fields: ['id'], where }); return sorted((rows as Array>).map((r) => String(r.id))); }; diff --git a/packages/drivers/driver-memory/src/memory-driver.test.ts b/packages/drivers/driver-memory/src/memory-driver.test.ts index 880538e18e..a1d8af5a34 100644 --- a/packages/drivers/driver-memory/src/memory-driver.test.ts +++ b/packages/drivers/driver-memory/src/memory-driver.test.ts @@ -18,7 +18,7 @@ describe('InMemoryDriver', () => { it('should clear data on disconnect', async () => { await driver.create(testTable, { id: '1', name: 'test' }); await driver.disconnect(); - const results = await driver.find(testTable, { fields: ['id'], object: testTable }); + const results = await driver.find(testTable, { fields: ['id'] }); expect(results).toHaveLength(0); }); }); @@ -51,7 +51,6 @@ describe('InMemoryDriver', () => { const results = await driver.find(testTable, { fields: ['id', 'name', 'age'], - object: testTable }); expect(results).toHaveLength(1); @@ -70,7 +69,7 @@ describe('InMemoryDriver', () => { expect(updateResult.active).toBe(false); - const results = await driver.find(testTable, { fields: ['active'], object: testTable }); + const results = await driver.find(testTable, { fields: ['active'] }); expect(results[0].active).toBe(false); }); @@ -81,7 +80,7 @@ describe('InMemoryDriver', () => { const deleteResult = await driver.delete(testTable, '1'); expect(deleteResult).toBe(true); - const results = await driver.find(testTable, { fields: ['name'], object: testTable }); + const results = await driver.find(testTable, { fields: ['name'] }); expect(results).toHaveLength(1); expect(results[0].name).toBe('David'); }); @@ -90,7 +89,7 @@ describe('InMemoryDriver', () => { const created = await driver.create(testTable, { id: '1', name: 'Alice' }); created.name = 'Modified'; - const found = await driver.find(testTable, { object: testTable }); + const found = await driver.find(testTable, {}); expect(found[0].name).toBe('Alice'); }); @@ -112,7 +111,6 @@ describe('InMemoryDriver', () => { const results = await driver.find(testTable, { fields: ['id'], - object: testTable, where: { role: 'user' } }); @@ -126,7 +124,6 @@ describe('InMemoryDriver', () => { const results = await driver.find(testTable, { fields: ['id'], - object: testTable, limit: 2 }); @@ -138,7 +135,6 @@ describe('InMemoryDriver', () => { const results = await driver.find(testTable, { fields: ['name', 'age'], - object: testTable, }); expect(results).toHaveLength(1); @@ -165,11 +161,11 @@ describe('InMemoryDriver', () => { }); await driverWithData.connect(); - const users = await driverWithData.find('users', { object: 'users' }); + const users = await driverWithData.find('users', {}); expect(users).toHaveLength(2); expect(users[0].name).toBe('Alice'); - const posts = await driverWithData.find('posts', { object: 'posts' }); + const posts = await driverWithData.find('posts', {}); expect(posts).toHaveLength(1); }); @@ -182,7 +178,7 @@ describe('InMemoryDriver', () => { }); await driverWithData.connect(); - const items = await driverWithData.find('items', { object: 'items' }); + const items = await driverWithData.find('items', {}); expect(items).toHaveLength(1); expect(items[0].id).toBeDefined(); expect(typeof items[0].id).toBe('string'); @@ -227,7 +223,7 @@ describe('InMemoryDriver', () => { await driver.create(testTable, { id: '2', name: 'Bob' }); await driver.commit(tx); - const results = await driver.find(testTable, { object: testTable }); + const results = await driver.find(testTable, {}); expect(results).toHaveLength(2); }); @@ -238,13 +234,13 @@ describe('InMemoryDriver', () => { await driver.create(testTable, { id: '2', name: 'Bob' }); // Verify Bob exists before rollback - let results = await driver.find(testTable, { object: testTable }); + let results = await driver.find(testTable, {}); expect(results).toHaveLength(2); await driver.rollback(tx); // After rollback, Bob should be gone - results = await driver.find(testTable, { object: testTable }); + results = await driver.find(testTable, {}); expect(results).toHaveLength(1); expect(results[0].name).toBe('Alice'); }); @@ -256,7 +252,7 @@ describe('InMemoryDriver', () => { await driver.update(testTable, '1', { name: 'Alice Modified' }); await driver.rollback(tx); - const results = await driver.find(testTable, { object: testTable }); + const results = await driver.find(testTable, {}); expect(results[0].name).toBe('Alice'); }); @@ -324,14 +320,14 @@ describe('InMemoryDriver', () => { describe('Schema Management', () => { it('should create table on syncSchema', async () => { await driver.syncSchema('new_table', {}); - const results = await driver.find('new_table', { object: 'new_table' }); + const results = await driver.find('new_table', {}); expect(results).toHaveLength(0); }); it('should drop table', async () => { await driver.create(testTable, { id: '1', name: 'test' }); await driver.dropTable(testTable); - const results = await driver.find(testTable, { object: testTable }); + const results = await driver.find(testTable, {}); expect(results).toHaveLength(0); }); }); @@ -358,7 +354,6 @@ describe('InMemoryDriver', () => { expect(total).toBe(3); const userCount = await driver.count(testTable, { - object: testTable, where: { role: 'user' }, }); expect(userCount).toBe(2); @@ -486,7 +481,6 @@ describe('InMemoryDriver', () => { it('should filter with $gt operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { age: { $gt: 28 } }, }); expect(results).toHaveLength(2); @@ -495,7 +489,6 @@ describe('InMemoryDriver', () => { it('should filter with $in operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { name: { $in: ['Alice', 'Diana'] } }, }); expect(results).toHaveLength(2); @@ -503,7 +496,6 @@ describe('InMemoryDriver', () => { it('should filter with $and operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { $and: [{ age: { $gte: 28 } }, { score: { $gt: 80 } }] }, }); expect(results).toHaveLength(2); @@ -512,7 +504,6 @@ describe('InMemoryDriver', () => { it('should filter with $or operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { $or: [{ age: { $lt: 26 } }, { score: { $gte: 95 } }] }, }); expect(results).toHaveLength(2); @@ -521,7 +512,6 @@ describe('InMemoryDriver', () => { it('should filter with $regex operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { name: { $regex: /^[AB]/ } }, }); expect(results).toHaveLength(2); @@ -530,7 +520,6 @@ describe('InMemoryDriver', () => { it('should count with complex filter', async () => { const count = await driver.count(testTable, { - object: testTable, where: { age: { $gte: 30 } }, }); expect(count).toBe(2); @@ -566,7 +555,6 @@ describe('InMemoryDriver', () => { it('should handle AST comparison node filter', async () => { const results = await driver.find(testTable, { - object: testTable, where: { type: 'comparison', field: 'age', operator: '>', value: 28 }, }); expect(results).toHaveLength(2); @@ -574,7 +562,6 @@ describe('InMemoryDriver', () => { it('should handle AST logical node filter', async () => { const results = await driver.find(testTable, { - object: testTable, where: { type: 'logical', operator: 'or', @@ -589,7 +576,6 @@ describe('InMemoryDriver', () => { it('should handle empty where clause', async () => { const results = await driver.find(testTable, { - object: testTable, }); expect(results).toHaveLength(3); }); @@ -605,7 +591,6 @@ describe('InMemoryDriver', () => { it('should filter with $contains operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { name: { $contains: 'Evan' } }, }); expect(results).toHaveLength(1); @@ -614,7 +599,6 @@ describe('InMemoryDriver', () => { it('should filter with $contains case-insensitively', async () => { const results = await driver.find(testTable, { - object: testTable, where: { name: { $contains: 'alice' } }, }); expect(results).toHaveLength(1); @@ -623,7 +607,6 @@ describe('InMemoryDriver', () => { it('should filter with $notContains operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { email: { $notContains: 'example' } }, }); expect(results).toHaveLength(2); @@ -632,7 +615,6 @@ describe('InMemoryDriver', () => { it('should filter with $startsWith operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { name: { $startsWith: 'Ch' } }, }); expect(results).toHaveLength(1); @@ -641,7 +623,6 @@ describe('InMemoryDriver', () => { it('should filter with $endsWith operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { email: { $endsWith: '.com' } }, }); expect(results).toHaveLength(4); @@ -649,7 +630,6 @@ describe('InMemoryDriver', () => { it('should filter with $between operator', async () => { const results = await driver.find(testTable, { - object: testTable, where: { age: { $between: [26, 32] } }, }); expect(results).toHaveLength(2); @@ -658,7 +638,6 @@ describe('InMemoryDriver', () => { it('should filter with $null: true', async () => { const results = await driver.find(testTable, { - object: testTable, where: { bio: { $null: true } }, }); expect(results).toHaveLength(1); @@ -667,7 +646,6 @@ describe('InMemoryDriver', () => { it('should filter with $null: false', async () => { const results = await driver.find(testTable, { - object: testTable, where: { bio: { $null: false } }, }); expect(results).toHaveLength(3); @@ -675,7 +653,6 @@ describe('InMemoryDriver', () => { it('should handle $contains inside $and', async () => { const results = await driver.find(testTable, { - object: testTable, where: { $and: [{ name: { $contains: 'a' } }, { age: { $gte: 30 } }] }, }); expect(results).toHaveLength(2); @@ -684,7 +661,6 @@ describe('InMemoryDriver', () => { it('should handle $startsWith inside $or', async () => { const results = await driver.find(testTable, { - object: testTable, where: { $or: [{ name: { $startsWith: 'Al' } }, { name: { $startsWith: 'Ev' } }] }, }); expect(results).toHaveLength(2); @@ -693,7 +669,6 @@ describe('InMemoryDriver', () => { it('should handle AST-converted notcontains via convertConditionToMongo', async () => { const results = await driver.find(testTable, { - object: testTable, where: { type: 'comparison', field: 'name', operator: 'notcontains', value: 'Bob' }, }); expect(results).toHaveLength(3); @@ -701,7 +676,6 @@ describe('InMemoryDriver', () => { it('should handle combined $startsWith + $endsWith on same field', async () => { const results = await driver.find(testTable, { - object: testTable, where: { name: { $startsWith: 'A', $endsWith: 'son' } }, }); expect(results).toHaveLength(1); @@ -713,7 +687,6 @@ describe('InMemoryDriver', () => { // Records without bio field at all should also match $null: true await driver.create(testTable, { id: '5', name: 'Frank', age: 40, email: 'frank@test.com' }); const results = await driver.find(testTable, { - object: testTable, where: { bio: { $null: true } }, }); // Alice (bio: null), Frank (bio: missing) @@ -735,19 +708,19 @@ describe('InMemoryDriver', () => { }); it('find() returns copies — mutating a result does not change the store', async () => { - const first = await driver.find(testTable, { object: testTable, where: { id: '1' } }); + const first = await driver.find(testTable, { where: { id: '1' } }); (first[0] as any).secret_ref = '••••••••'; // simulate engine masking the row in place - const second = await driver.find(testTable, { object: testTable, where: { id: '1' } }); + const second = await driver.find(testTable, { where: { id: '1' } }); expect((second[0] as any).secret_ref).toBe('secret:abc123'); // store intact expect(second[0]).not.toBe(first[0]); // distinct object identity }); it('findOne() returns a copy — mutating it does not change the store', async () => { - const first = await driver.findOne(testTable, { object: testTable, where: { id: '1' } }); + const first = await driver.findOne(testTable, { where: { id: '1' } }); (first as any).secret_ref = '••••••••'; - const second = await driver.findOne(testTable, { object: testTable, where: { id: '1' } }); + const second = await driver.findOne(testTable, { where: { id: '1' } }); expect((second as any).secret_ref).toBe('secret:abc123'); expect(second).not.toBe(first); }); @@ -767,7 +740,6 @@ describe('InMemoryDriver', () => { it('accepts the engine AST ({groupBy, aggregations}) and returns grouped sums', async () => { const rows = await driver.aggregate(tbl, { - object: tbl, groupBy: ['category'], aggregations: [{ function: 'sum', field: 'amount', alias: 'amount' }], } as any); @@ -777,7 +749,6 @@ describe('InMemoryDriver', () => { it('AST with no groupBy returns a single total row; where filters first', async () => { const rows = await driver.aggregate(tbl, { - object: tbl, where: { category: 'travel' }, aggregations: [{ function: 'sum', field: 'amount', alias: 'total' }, { function: 'count', field: '*', alias: 'count' }], } as any); diff --git a/packages/drivers/driver-memory/src/memory-driver.ts b/packages/drivers/driver-memory/src/memory-driver.ts index c3e04a7ee4..258200ef73 100644 --- a/packages/drivers/driver-memory/src/memory-driver.ts +++ b/packages/drivers/driver-memory/src/memory-driver.ts @@ -2,7 +2,7 @@ import type { QueryAST, QueryInput, DriverOptions } from '@objectstack/spec/data'; import { canonicalAstOperator } from '@objectstack/spec/data'; -import type { IDataDriver } from '@objectstack/spec/contracts'; +import type { DriverQuery, IDataDriver } from '@objectstack/spec/contracts'; import { Logger, createLogger, nextUtcCalendarDay } from '@objectstack/core'; import { Query, Aggregator } from 'mingo'; import { getValueByPath } from './memory-matcher.js'; @@ -280,7 +280,7 @@ export class InMemoryDriver implements IDataDriver { // CRUD // =================================== - async find(object: string, query: QueryAST, options?: DriverOptions) { + async find(object: string, query: DriverQuery, options?: DriverOptions) { this.logger.debug('Find operation', { object, query }); const table = this.getTable(object); @@ -339,7 +339,7 @@ export class InMemoryDriver implements IDataDriver { // row — the whole table was already in memory before the first `yield`. Nothing // called it. Page through `find()` with `limit`/`offset`. - async findOne(object: string, query: QueryAST, options?: DriverOptions) { + async findOne(object: string, query: DriverQuery, options?: DriverOptions) { this.logger.debug('FindOne operation', { object, query }); const results = await this.find(object, { ...query, limit: 1 }, options); @@ -441,7 +441,7 @@ export class InMemoryDriver implements IDataDriver { return true; } - async count(object: string, query?: QueryAST, options?: DriverOptions) { + async count(object: string, query?: DriverQuery, options?: DriverOptions) { let records = this.getTable(object); if (query?.where) { const mongoQuery = this.convertToMongoQuery(query.where, object); @@ -466,7 +466,7 @@ export class InMemoryDriver implements IDataDriver { return results; } - async updateMany(object: string, query: QueryAST, data: Record, options?: DriverOptions): Promise { + async updateMany(object: string, query: DriverQuery, data: Record, options?: DriverOptions): Promise { this.logger.debug('UpdateMany operation', { object, query }); const table = this.getTable(object); @@ -499,7 +499,7 @@ export class InMemoryDriver implements IDataDriver { return count; } - async deleteMany(object: string, query: QueryAST, options?: DriverOptions): Promise { + async deleteMany(object: string, query: DriverQuery, options?: DriverOptions): Promise { this.logger.debug('DeleteMany operation', { object, query }); const table = this.getTable(object); @@ -1035,7 +1035,7 @@ export class InMemoryDriver implements IDataDriver { // Aggregation Logic // =================================== - private performAggregation(records: any[], query: QueryInput): any[] { + private performAggregation(records: any[], query: Omit): any[] { const { groupBy, aggregations } = query; const groups: Map = new Map(); diff --git a/packages/drivers/driver-memory/src/memory-empty-field-constraint.test.ts b/packages/drivers/driver-memory/src/memory-empty-field-constraint.test.ts index f5795dc36c..1e12184fbc 100644 --- a/packages/drivers/driver-memory/src/memory-empty-field-constraint.test.ts +++ b/packages/drivers/driver-memory/src/memory-empty-field-constraint.test.ts @@ -70,7 +70,7 @@ describe('[#5240] InMemoryDriver (live mingo path) refuses a zero-operator field }); const ids = async (where: unknown): Promise => { - const rows = await driver.find('deal', { object: 'deal', fields: ['id'], where: where as FilterCondition }); + const rows = await driver.find('deal', { fields: ['id'], where: where as FilterCondition }); return rows.map((r: any) => String(r.id)).sort(); }; diff --git a/packages/drivers/driver-memory/src/memory-filter-ast-vocabulary.test.ts b/packages/drivers/driver-memory/src/memory-filter-ast-vocabulary.test.ts index ed87159062..e5320fc75f 100644 --- a/packages/drivers/driver-memory/src/memory-filter-ast-vocabulary.test.ts +++ b/packages/drivers/driver-memory/src/memory-filter-ast-vocabulary.test.ts @@ -52,7 +52,7 @@ describe('InMemoryDriver filter vocabulary ↔ VALID_AST_OPERATORS', () => { // contract forbids, fed in to prove the driver throws instead of silently // dropping the condition. `unknown` is the honest parameter type. const find = (where: unknown) => - driver.find(TABLE, { object: TABLE, fields: ['id'], where: where as FilterCondition }); + driver.find(TABLE, { fields: ['id'], where: where as FilterCondition }); /** * The authored `FilterArray`, travelling the one route that exists (#5158): diff --git a/packages/drivers/driver-memory/src/memory-filter-refusal-envelope.test.ts b/packages/drivers/driver-memory/src/memory-filter-refusal-envelope.test.ts index cdab2a4cfc..bc0e7f02ce 100644 --- a/packages/drivers/driver-memory/src/memory-filter-refusal-envelope.test.ts +++ b/packages/drivers/driver-memory/src/memory-filter-refusal-envelope.test.ts @@ -48,7 +48,7 @@ describe('[#4436] InMemoryDriver filter refusals carry INVALID_FILTER and leak n }); const find = (where: unknown) => - driver.find('deal', { object: 'deal', fields: ['id'], where: where as FilterCondition }); + driver.find('deal', { fields: ['id'], where: where as FilterCondition }); const cases: Array<[string, unknown, string]> = [ ['unsupported operator in a condition array', [['stage', 'sounds_like', 'won']], 'sounds_like'], diff --git a/packages/drivers/driver-memory/src/memory-filter-vocabulary-refusal.test.ts b/packages/drivers/driver-memory/src/memory-filter-vocabulary-refusal.test.ts index 36ea620fa0..61a88da0b4 100644 --- a/packages/drivers/driver-memory/src/memory-filter-vocabulary-refusal.test.ts +++ b/packages/drivers/driver-memory/src/memory-filter-vocabulary-refusal.test.ts @@ -83,7 +83,7 @@ describe('[#5324/#5328] a filter this driver cannot evaluate is refused, not ans }); const ids = async (where: unknown): Promise => { - const rows = await driver.find('deal', { object: 'deal', fields: ['id'], where: where as FilterCondition }); + const rows = await driver.find('deal', { fields: ['id'], where: where as FilterCondition }); return (rows as Array>).map((r) => String(r.id)).sort(); }; diff --git a/packages/drivers/driver-memory/src/memory-null-comparand-refusal.test.ts b/packages/drivers/driver-memory/src/memory-null-comparand-refusal.test.ts index 050ed5d285..c71c9809a0 100644 --- a/packages/drivers/driver-memory/src/memory-null-comparand-refusal.test.ts +++ b/packages/drivers/driver-memory/src/memory-null-comparand-refusal.test.ts @@ -76,7 +76,6 @@ describe('[#5347] $null requires a boolean comparand, on both filter faces', () const findIds = async (where: unknown): Promise => { const rows = await driver.find('deal', { - object: 'deal', fields: ['id'], where: where as FilterCondition, }); diff --git a/packages/drivers/driver-memory/src/memory-pagination-conformance.test.ts b/packages/drivers/driver-memory/src/memory-pagination-conformance.test.ts index 9947a083da..a25b848d86 100644 --- a/packages/drivers/driver-memory/src/memory-pagination-conformance.test.ts +++ b/packages/drivers/driver-memory/src/memory-pagination-conformance.test.ts @@ -77,7 +77,7 @@ describe('InMemoryDriver — paged reads are a partition of the result set (obje paged.push(...page); } - const whole = await driver.find('ticket', { object: 'ticket', orderBy: [...testCase.orderBy] }); + const whole = await driver.find('ticket', { orderBy: [...testCase.orderBy] }); expect(paged.map((r) => r.id)).toEqual(whole.map((r: any) => r.id)); }); } diff --git a/packages/drivers/driver-memory/src/memory-temporal-conformance.test.ts b/packages/drivers/driver-memory/src/memory-temporal-conformance.test.ts index c8a415b1d1..7495b6a09b 100644 --- a/packages/drivers/driver-memory/src/memory-temporal-conformance.test.ts +++ b/packages/drivers/driver-memory/src/memory-temporal-conformance.test.ts @@ -103,14 +103,14 @@ describe('driver-memory — temporal conformance', () => { for (const c of TEMPORAL_CASES) { it(c.name, async () => { - const rows = await driver.find('conformance', { object: 'conformance', where: c.filter }); + const rows = await driver.find('conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); if (c.tokenFilter) { it(`${c.name} — via relative tokens`, async () => { - const rows = await driver.find('conformance', { object: 'conformance', where: resolveTokens(c.tokenFilter) }); + const rows = await driver.find('conformance', { where: resolveTokens(c.tokenFilter) }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); @@ -141,7 +141,7 @@ describe('driver-memory — Field.time conformance', () => { for (const c of TEMPORAL_TIME_CASES) { it(c.name, async () => { - const rows = await driver.find('time_conformance', { object: 'time_conformance', where: c.filter }); + const rows = await driver.find('time_conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); @@ -164,7 +164,7 @@ describe('driver-memory — temporal conformance on rows that predate the schema }); it('converged every pre-schema row to the storage canon (the premise, so the sweep cannot pass vacuously)', async () => { - const rows = await driver.find('conformance', { object: 'conformance' }); + const rows = await driver.find('conformance', {}); expect(rows).toHaveLength(TEMPORAL_ROWS.length); for (const row of rows as any[]) { const expected = TEMPORAL_ROWS.find((r) => r.id === row.id)!; @@ -179,7 +179,7 @@ describe('driver-memory — temporal conformance on rows that predate the schema // already swept above — a divergence here is a convergence bug by construction. for (const c of TEMPORAL_CASES) { it(c.name, async () => { - const rows = await driver.find('conformance', { object: 'conformance', where: c.filter }); + const rows = await driver.find('conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); @@ -199,7 +199,7 @@ describe('driver-memory — Field.time conformance on rows that predate the sche }); it('converged every pre-schema wall clock to the storage canon (the premise)', async () => { - const rows = await driver.find('time_conformance', { object: 'time_conformance' }); + const rows = await driver.find('time_conformance', {}); expect(rows).toHaveLength(TEMPORAL_TIME_ROWS.length); for (const row of rows as any[]) { const expected = TEMPORAL_TIME_ROWS.find((r) => r.id === row.id)!; @@ -209,7 +209,7 @@ describe('driver-memory — Field.time conformance on rows that predate the sche for (const c of TEMPORAL_TIME_CASES) { it(c.name, async () => { - const rows = await driver.find('time_conformance', { object: 'time_conformance', where: c.filter }); + const rows = await driver.find('time_conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); diff --git a/packages/drivers/driver-memory/src/persistence/persistence.test.ts b/packages/drivers/driver-memory/src/persistence/persistence.test.ts index 39ccc36123..f4c5433cb9 100644 --- a/packages/drivers/driver-memory/src/persistence/persistence.test.ts +++ b/packages/drivers/driver-memory/src/persistence/persistence.test.ts @@ -63,7 +63,7 @@ describe('InMemoryDriver Persistence', () => { }); await driver2.connect(); - const users = await driver2.find('users', { object: 'users' }); + const users = await driver2.find('users', {}); expect(users).toHaveLength(2); expect(users[0].name).toBe('Alice'); expect(users[1].name).toBe('Bob'); @@ -103,7 +103,7 @@ describe('InMemoryDriver Persistence', () => { }); await driver2.connect(); - const tasks = await driver2.find('tasks', { object: 'tasks' }); + const tasks = await driver2.find('tasks', {}); expect(tasks).toHaveLength(1); expect(tasks[0].id).toBe('1'); expect(tasks[0].done).toBe(true); @@ -117,7 +117,7 @@ describe('InMemoryDriver Persistence', () => { }); await driver.connect(); - const users = await driver.find('users', { object: 'users' }); + const users = await driver.find('users', {}); expect(users).toHaveLength(0); await driver.disconnect(); @@ -153,7 +153,7 @@ describe('InMemoryDriver Persistence', () => { persistence: { adapter: customAdapter }, }); await driver2.connect(); - const projects = await driver2.find('projects', { object: 'projects' }); + const projects = await driver2.find('projects', {}); expect(projects).toHaveLength(1); expect(projects[0].name).toBe('Alpha'); @@ -167,13 +167,13 @@ describe('InMemoryDriver Persistence', () => { await driver.connect(); await driver.create('items', { id: '1', name: 'Widget' }); - const items = await driver.find('items', { object: 'items' }); + const items = await driver.find('items', {}); expect(items).toHaveLength(1); await driver.disconnect(); // After disconnect, data is gone - const itemsAfter = await driver.find('items', { object: 'items' }); + const itemsAfter = await driver.find('items', {}); expect(itemsAfter).toHaveLength(0); }); }); @@ -209,7 +209,7 @@ describe('InMemoryDriver Persistence', () => { persistence: { type: 'auto', path: filePath, autoSaveInterval: 100 }, }); await driver2.connect(); - const users = await driver2.find('users', { object: 'users' }); + const users = await driver2.find('users', {}); expect(users).toHaveLength(1); expect(users[0].name).toBe('Alice'); await driver2.disconnect(); @@ -234,7 +234,7 @@ describe('InMemoryDriver Persistence', () => { persistence: { type: 'file', path: TEST_FILE_PATH, autoSaveInterval: 100 }, }); await driver2.connect(); - const items = await driver2.find('items', { object: 'items' }); + const items = await driver2.find('items', {}); expect(items).toHaveLength(3); await driver2.disconnect(); }); @@ -268,7 +268,7 @@ describe('InMemoryDriver Persistence', () => { { id: 'n1', title: 'first' }, { id: 'n2', title: 'second' }, ]); - const rows = await driver.find('ext_note', { object: 'ext_note' }); + const rows = await driver.find('ext_note', {}); expect(rows.map((r: any) => r.title).sort()).toEqual(['first', 'second']); await driver.disconnect(); } @@ -328,7 +328,7 @@ describe('InMemoryDriver Persistence', () => { await driver.create('items', { id: '1', name: 'Widget' }); // Should work as pure in-memory without errors - const items = await driver.find('items', { object: 'items' }); + const items = await driver.find('items', {}); expect(items).toHaveLength(1); await driver.disconnect(); diff --git a/packages/drivers/driver-mongodb/src/mongodb-driver.ts b/packages/drivers/driver-mongodb/src/mongodb-driver.ts index 70863646ae..062982468b 100644 --- a/packages/drivers/driver-mongodb/src/mongodb-driver.ts +++ b/packages/drivers/driver-mongodb/src/mongodb-driver.ts @@ -9,7 +9,7 @@ */ import type { QueryAST, DriverOptions } from '@objectstack/spec/data'; -import type { IDataDriver } from '@objectstack/spec/contracts'; +import type { DriverQuery, IDataDriver } from '@objectstack/spec/contracts'; import { MongoClient, Db, @@ -198,7 +198,7 @@ export class MongoDBDriver implements IDataDriver { * (#4484), which subsumes that divergence rather than fixing it. */ private buildFindOptions( - query: QueryAST, + query: DriverQuery, session: FindOptions['session'], opts?: { singleRowLookup?: boolean }, ): FindOptions { @@ -233,7 +233,7 @@ export class MongoDBDriver implements IDataDriver { return findOptions; } - async find(object: string, query: QueryAST, options?: DriverOptions): Promise[]> { + async find(object: string, query: DriverQuery, options?: DriverOptions): Promise[]> { const collection = this.getCollection(object); const session = this.getSession(options); @@ -245,7 +245,7 @@ export class MongoDBDriver implements IDataDriver { return results as Record[]; } - async findOne(object: string, query: QueryAST, options?: DriverOptions): Promise | null> { + async findOne(object: string, query: DriverQuery, options?: DriverOptions): Promise | null> { const collection = this.getCollection(object); const session = this.getSession(options); @@ -363,7 +363,7 @@ export class MongoDBDriver implements IDataDriver { return result.deletedCount > 0; } - async count(object: string, query?: QueryAST, options?: DriverOptions): Promise { + async count(object: string, query?: DriverQuery, options?: DriverOptions): Promise { const collection = this.getCollection(object); const session = this.getSession(options); @@ -434,7 +434,7 @@ export class MongoDBDriver implements IDataDriver { ); } - async updateMany(object: string, query: QueryAST, data: Record, options?: DriverOptions): Promise { + async updateMany(object: string, query: DriverQuery, data: Record, options?: DriverOptions): Promise { const collection = this.getCollection(object); const session = this.getSession(options); @@ -452,7 +452,7 @@ export class MongoDBDriver implements IDataDriver { return result.modifiedCount; } - async deleteMany(object: string, query: QueryAST, options?: DriverOptions): Promise { + async deleteMany(object: string, query: DriverQuery, options?: DriverOptions): Promise { const collection = this.getCollection(object); const session = this.getSession(options); @@ -541,7 +541,7 @@ export class MongoDBDriver implements IDataDriver { // Query Plan Analysis // =========================================================================== - async explain(object: string, query: QueryAST, _options?: DriverOptions): Promise { + async explain(object: string, query: DriverQuery, _options?: DriverOptions): Promise { const collection = this.getCollection(object); const filter = translateFilter(query.where, this.temporalKindFor(object)); const explanation = await collection.find(filter).explain('executionStats'); @@ -620,7 +620,7 @@ export class MongoDBDriver implements IDataDriver { * is still honoured, tie-breaker and all — that is the half this driver used * to drop entirely (objectstack#4419). */ - private buildSortSpec(query: QueryAST, opts?: { singleRowLookup?: boolean }): Document | undefined { + private buildSortSpec(query: DriverQuery, opts?: { singleRowLookup?: boolean }): Document | undefined { const sort: Document = {}; let lastDirection: 1 | -1 = 1; if (Array.isArray(query.orderBy)) { diff --git a/packages/drivers/driver-sql/src/sql-driver-advanced.test.ts b/packages/drivers/driver-sql/src/sql-driver-advanced.test.ts index c1f8d80b9e..1dbe61e86d 100644 --- a/packages/drivers/driver-sql/src/sql-driver-advanced.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-advanced.test.ts @@ -139,33 +139,33 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { expect(result).toBeDefined(); expect(result.length).toBe(3); - const count = await driver.count('orders', { object: 'orders' }); + const count = await driver.count('orders', {}); expect(count).toBe(8); }); it('should update many records', async () => { - const result = await driver.updateMany('orders', { object: 'orders', where: { status: 'pending' } }, { status: 'processing' }); + const result = await driver.updateMany('orders', { where: { status: 'pending' } }, { status: 'processing' }); expect(result).toBeGreaterThan(0); - const results = await driver.find('orders', { object: 'orders', where: { status: 'processing' } }); + const results = await driver.find('orders', { where: { status: 'processing' } }); expect(results.length).toBe(1); }); it('should delete many records', async () => { - const result = await driver.deleteMany('orders', { object: 'orders', where: { status: 'cancelled' } }); + const result = await driver.deleteMany('orders', { where: { status: 'cancelled' } }); expect(result).toBe(1); - const remaining = await driver.count('orders', { object: 'orders' }); + const remaining = await driver.count('orders', {}); expect(remaining).toBe(4); }); it('should handle empty bulk update and delete', async () => { - const result = await driver.updateMany('orders', { object: 'orders', where: { status: 'nonexistent' } }, { status: 'updated' }); + const result = await driver.updateMany('orders', { where: { status: 'nonexistent' } }, { status: 'updated' }); expect(result).toBe(0); - const deleteResult = await driver.deleteMany('orders', { object: 'orders', where: { id: 'nonexistent' } }); + const deleteResult = await driver.deleteMany('orders', { where: { id: 'nonexistent' } }); expect(deleteResult).toBe(0); }); }); @@ -190,7 +190,7 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { await driver.commitTransaction(trx); - const result = await driver.findOne('orders', { object: 'orders', where: { id: 'trx1' } }); + const result = await driver.findOne('orders', { where: { id: 'trx1' } }); expect(result).toBeDefined(); expect(result.customer).toBe('TxUser'); } catch (e) { @@ -218,7 +218,7 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { await driver.rollbackTransaction(trx); - const result = await driver.findOne('orders', { object: 'orders', where: { id: 'trx2' } }); + const result = await driver.findOne('orders', { where: { id: 'trx2' } }); expect(result).toBeNull(); } catch (e) { await driver.rollbackTransaction(trx); @@ -249,13 +249,13 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { await driver.commitTransaction(trx); - const created = await driver.findOne('orders', { object: 'orders', where: { id: 'trx3' } }); + const created = await driver.findOne('orders', { where: { id: 'trx3' } }); expect(created).toBeDefined(); - const updated = await driver.findOne('orders', { object: 'orders', where: { id: '1' } }); + const updated = await driver.findOne('orders', { where: { id: '1' } }); expect(updated.status).toBe('shipped'); - const deleted = await driver.findOne('orders', { object: 'orders', where: { id: '5' } }); + const deleted = await driver.findOne('orders', { where: { id: '5' } }); expect(deleted).toBeNull(); } catch (e) { await driver.rollbackTransaction(trx); @@ -266,12 +266,12 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { describe('Edge Cases and Error Handling', () => { it('should handle empty filters gracefully', async () => { - const results = await driver.find('orders', { object: 'orders', where: {} }); + const results = await driver.find('orders', { where: {} }); expect(results.length).toBe(5); }); it('should handle undefined query parameters', async () => { - const results = await driver.find('orders', { object: 'orders' }); + const results = await driver.find('orders', {}); expect(results.length).toBe(5); }); @@ -284,7 +284,7 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { await driver.create('nullable_test', { id: '1', name: null, value: null }); - const result = await driver.findOne('nullable_test', { object: 'nullable_test', where: { id: '1' } }); + const result = await driver.findOne('nullable_test', { where: { id: '1' } }); expect(result).toBeDefined(); expect(result.name).toBeNull(); expect(result.value).toBeNull(); @@ -292,7 +292,6 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { it('should handle pagination with offset and limit', async () => { const page1 = await driver.find('orders', { - object: 'orders', orderBy: [{ field: 'id', order: 'asc' }], offset: 0, limit: 2, @@ -301,7 +300,6 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { expect(page1[0].id).toBe('1'); const page2 = await driver.find('orders', { - object: 'orders', orderBy: [{ field: 'id', order: 'asc' }], offset: 2, limit: 2, @@ -311,13 +309,12 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { }); it('should handle offset beyond total records', async () => { - const results = await driver.find('orders', { object: 'orders', offset: 100, limit: 10 }); + const results = await driver.find('orders', { offset: 100, limit: 10 }); expect(results.length).toBe(0); }); it('should handle complex nested filters', async () => { const results = await driver.find('orders', { - object: 'orders', where: { $or: [ { $and: [{ status: 'completed' }, { amount: { $gt: 100 } }] }, @@ -338,7 +335,6 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { it('should handle contains filter', async () => { const results = await driver.find('orders', { - object: 'orders', where: { product: { $contains: 'top' } }, }); @@ -348,7 +344,6 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { it('should handle in filter', async () => { const results = await driver.find('orders', { - object: 'orders', where: { status: { $in: ['completed', 'pending'] } }, }); @@ -357,7 +352,6 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { it('should handle nin (not in) filter', async () => { const results = await driver.find('orders', { - object: 'orders', where: { status: { $nin: ['cancelled'] } }, }); @@ -365,14 +359,14 @@ describe('SqlDriver Advanced Operations (SQLite)', () => { }); it('should handle findOne with query parameter', async () => { - const result = await driver.findOne('orders', { object: 'orders', where: { customer: 'Charlie' } }); + const result = await driver.findOne('orders', { where: { customer: 'Charlie' } }); expect(result).toBeDefined(); expect(result.customer).toBe('Charlie'); }); it('should return null for non-existent record', async () => { - const result = await driver.findOne('orders', { object: 'orders', where: { id: 'nonexistent' } }); + const result = await driver.findOne('orders', { where: { id: 'nonexistent' } }); expect(result).toBeNull(); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-aggregate-temporal-output.test.ts b/packages/drivers/driver-sql/src/sql-driver-aggregate-temporal-output.test.ts index 0ccfc8866e..76a3512c92 100644 --- a/packages/drivers/driver-sql/src/sql-driver-aggregate-temporal-output.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-aggregate-temporal-output.test.ts @@ -74,7 +74,7 @@ describe('temporal values leaving aggregate()/distinct() (#3797)', () => { // `orderBy: [['id', 'asc']]` until #4311. The driver reads `item.field`, // which a tuple does not have, so the sort was silently dropped — this // helper had been reading whatever order the rows came back in. - const rows = await driver.find(TABLE, { object: TABLE, orderBy: [{ field: 'id', order: 'asc' }] }); + const rows = await driver.find(TABLE, { orderBy: [{ field: 'id', order: 'asc' }] }); return rows.map((r: any) => r[field]); }; diff --git a/packages/drivers/driver-sql/src/sql-driver-array-fields.test.ts b/packages/drivers/driver-sql/src/sql-driver-array-fields.test.ts index a1e01edda9..eb030fe5e2 100644 --- a/packages/drivers/driver-sql/src/sql-driver-array-fields.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-array-fields.test.ts @@ -63,7 +63,7 @@ describe('SqlDriver array/object field persistence', () => { }, { bypassTenantAudit: true }, ); - const row = await driver.findOne('zoo', { object: 'zoo', where: { id: 'z1' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('zoo', { where: { id: 'z1' } }, { bypassTenantAudit: true }); expect(row.tags).toEqual(['x', 'y']); expect(row.ms).toEqual(['red', 'green']); expect(row.cbs).toEqual(['email', 'push']); @@ -76,13 +76,13 @@ describe('SqlDriver array/object field persistence', () => { it('updates an array field to a new array', async () => { await driver.create('zoo', { id: 'z2', name: 'B', tags: ['a'] }, { bypassTenantAudit: true }); await driver.update('zoo', 'z2', { tags: ['a', 'b', 'c'] }, { bypassTenantAudit: true }); - const row = await driver.findOne('zoo', { object: 'zoo', where: { id: 'z2' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('zoo', { where: { id: 'z2' } }, { bypassTenantAudit: true }); expect(row.tags).toEqual(['a', 'b', 'c']); }); it('does not crash on an empty array', async () => { await driver.create('zoo', { id: 'z3', name: 'C', ms: [] }, { bypassTenantAudit: true }); - const row = await driver.findOne('zoo', { object: 'zoo', where: { id: 'z3' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('zoo', { where: { id: 'z3' } }, { bypassTenantAudit: true }); expect(row.ms).toEqual([]); }); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-boolean-identity.test.ts b/packages/drivers/driver-sql/src/sql-driver-boolean-identity.test.ts index 4dc834319e..500746efd3 100644 --- a/packages/drivers/driver-sql/src/sql-driver-boolean-identity.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-boolean-identity.test.ts @@ -98,7 +98,6 @@ describe('[#5134] SqlDriver compiles empty $and/$or/$not to their boolean identi // boolean algebra says rather than by accident of what Knex renders. const ids = async (where: unknown): Promise => { const rows = await driver.find('deal', { - object: 'deal', fields: ['id'], where: where as FilterCondition, }); diff --git a/packages/drivers/driver-sql/src/sql-driver-bulk-json.test.ts b/packages/drivers/driver-sql/src/sql-driver-bulk-json.test.ts index d613e270b3..14ed9b13a7 100644 --- a/packages/drivers/driver-sql/src/sql-driver-bulk-json.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-bulk-json.test.ts @@ -50,7 +50,7 @@ describe('SqlDriver bulkCreate JSON marshaling (#2735)', () => { expect(result).toHaveLength(2); // Read-back parity: JSON columns decode to objects, same as single insert. - const v1 = await driver.findOne('venue', { object: 'venue', where: { id: 'v1' } }); + const v1 = await driver.findOne('venue', { where: { id: 'v1' } }); expect(v1.location).toEqual({ lat: 47.6062, lng: -122.3321 }); expect(v1.tags).toEqual(['a', 'b']); expect(v1.meta).toEqual({ tier: 1 }); diff --git a/packages/drivers/driver-sql/src/sql-driver-calendar-day-upper-bound.test.ts b/packages/drivers/driver-sql/src/sql-driver-calendar-day-upper-bound.test.ts index 37d0e25c69..9ddc23c0c9 100644 --- a/packages/drivers/driver-sql/src/sql-driver-calendar-day-upper-bound.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-calendar-day-upper-bound.test.ts @@ -102,13 +102,13 @@ describe('bare-day $lte on Field.datetime — the #3777 repro', () => { }); it('$gte / $gt / $lt keep their midnight anchoring (the issue-table rows marked correct)', async () => { - const gte = await driver.find('task', { object: 'task', where: { created_at: { $gte: '2026-07-28' } } }); + const gte = await driver.find('task', { where: { created_at: { $gte: '2026-07-28' } } }); expect(ids(gte)).toEqual(['t_evening', 't_midnight', 't_morning']); - const gt = await driver.find('task', { object: 'task', where: { created_at: { $gt: '2026-07-28' } } }); + const gt = await driver.find('task', { where: { created_at: { $gt: '2026-07-28' } } }); expect(ids(gt)).toEqual(['t_evening', 't_morning']); // excludes the exact-midnight row - const lt = await driver.find('task', { object: 'task', where: { created_at: { $lt: '2026-07-28' } } }); + const lt = await driver.find('task', { where: { created_at: { $lt: '2026-07-28' } } }); expect(ids(lt)).toEqual(['t_old', 't_yesterday']); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-cross-field-reference.test.ts b/packages/drivers/driver-sql/src/sql-driver-cross-field-reference.test.ts index 66e3b682e7..7018bd2e0c 100644 --- a/packages/drivers/driver-sql/src/sql-driver-cross-field-reference.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-cross-field-reference.test.ts @@ -78,7 +78,7 @@ describe('[#5041] SqlDriver refuses `$field` cross-field comparison in the ADR-0 }); const find = (where: unknown) => - driver.find('deal', { object: 'deal', fields: ['id'], where: where as FilterCondition }); + driver.find('deal', { fields: ['id'], where: where as FilterCondition }); it('the issue repro — `{ amount: { $gt: { $field: "budget" } } }` — carries the full envelope', async () => { const err = await refusalOf(() => find({ amount: { $gt: { $field: 'budget' } } })); diff --git a/packages/drivers/driver-sql/src/sql-driver-date-now-default-live.test.ts b/packages/drivers/driver-sql/src/sql-driver-date-now-default-live.test.ts index 7b1fa32839..3313b0b6dd 100644 --- a/packages/drivers/driver-sql/src/sql-driver-date-now-default-live.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-date-now-default-live.test.ts @@ -73,7 +73,7 @@ function suite(dialect: 'pg' | 'mysql', url: string | undefined) { it('a defaulted insert round-trips a valid canonical calendar day', async () => { await driver.create(TABLE, { id: 'x', label: 'x' }, { bypassTenantAudit: true }); - const row: any = await driver.findOne(TABLE, { object: TABLE, where: { id: 'x' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne(TABLE, { where: { id: 'x' } }, { bypassTenantAudit: true }); expect(String(row.due_on)).toMatch(/^\d{4}-\d{2}-\d{2}$/); // The UTC calendar day only ever differs from this probe's own UTC clock // across a midnight boundary — accept today or the day either side rather diff --git a/packages/drivers/driver-sql/src/sql-driver-date-only.test.ts b/packages/drivers/driver-sql/src/sql-driver-date-only.test.ts index b486adb174..ae84410e56 100644 --- a/packages/drivers/driver-sql/src/sql-driver-date-only.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-date-only.test.ts @@ -49,7 +49,7 @@ describe('SqlDriver Field.date is a tz-naive calendar day (ADR-0053 Phase 1)', ( { id: 'd1', name: 'A', close_date: new Date('2026-07-15T17:24:56.533Z') }, { bypassTenantAudit: true }, ); - const row = await driver.findOne('deal', { object: 'deal', where: { id: 'd1' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('deal', { where: { id: 'd1' } }, { bypassTenantAudit: true }); expect(row.close_date).toBe('2026-07-15'); }); @@ -59,7 +59,7 @@ describe('SqlDriver Field.date is a tz-naive calendar day (ADR-0053 Phase 1)', ( { id: 'd2', name: 'B', close_date: '2026-07-15T17:24:56.533Z' }, { bypassTenantAudit: true }, ); - const row = await driver.findOne('deal', { object: 'deal', where: { id: 'd2' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('deal', { where: { id: 'd2' } }, { bypassTenantAudit: true }); expect(row.close_date).toBe('2026-07-15'); }); @@ -69,7 +69,7 @@ describe('SqlDriver Field.date is a tz-naive calendar day (ADR-0053 Phase 1)', ( { id: 'd3', name: 'C', close_date: '2026-07-15' }, { bypassTenantAudit: true }, ); - const row = await driver.findOne('deal', { object: 'deal', where: { id: 'd3' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('deal', { where: { id: 'd3' } }, { bypassTenantAudit: true }); expect(row.close_date).toBe('2026-07-15'); }); @@ -79,7 +79,7 @@ describe('SqlDriver Field.date is a tz-naive calendar day (ADR-0053 Phase 1)', ( { id: 'd4', name: 'D', signed_at: new Date('2026-03-20T12:34:56.000Z') }, { bypassTenantAudit: true }, ); - const row = await driver.findOne('deal', { object: 'deal', where: { id: 'd4' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('deal', { where: { id: 'd4' } }, { bypassTenantAudit: true }); // datetime must retain its wall-clock time — never sliced to YYYY-MM-DD. expect(new Date(row.signed_at).toISOString()).toBe('2026-03-20T12:34:56.000Z'); }); @@ -92,7 +92,7 @@ describe('SqlDriver Field.date is a tz-naive calendar day (ADR-0053 Phase 1)', ( { id: 'd5', name: 'E', close_date: new Date('2026-07-15T17:24:56.533Z') }, { bypassTenantAudit: true }, ); - const rows = await driver.find('deal', { object: 'deal', where: { close_date: '2026-07-15' } }); + const rows = await driver.find('deal', { where: { close_date: '2026-07-15' } }); expect(rows.map((r: any) => r.id)).toEqual(['d5']); }); @@ -100,7 +100,7 @@ describe('SqlDriver Field.date is a tz-naive calendar day (ADR-0053 Phase 1)', ( await driver.create('deal', { id: 'd6', name: 'F', close_date: new Date('2026-07-15T08:00:00Z') }, { bypassTenantAudit: true }); await driver.create('deal', { id: 'd7', name: 'G', close_date: '2026-07-16' }, { bypassTenantAudit: true }); await driver.create('deal', { id: 'd8', name: 'H', close_date: '2026-07-17' }, { bypassTenantAudit: true }); - const rows = await driver.find('deal', { object: 'deal', where: { close_date: { $in: ['2026-07-15', '2026-07-17'] } } }); + const rows = await driver.find('deal', { where: { close_date: { $in: ['2026-07-15', '2026-07-17'] } } }); expect(rows.map((r: any) => r.id).sort()).toEqual(['d6', 'd8']); }); @@ -108,7 +108,7 @@ describe('SqlDriver Field.date is a tz-naive calendar day (ADR-0053 Phase 1)', ( await driver.create('deal', { id: 'r1', close_date: '2025-01-15' }, { bypassTenantAudit: true }); await driver.create('deal', { id: 'r2', close_date: '2026-03-20' }, { bypassTenantAudit: true }); await driver.create('deal', { id: 'r3', close_date: '2026-05-25' }, { bypassTenantAudit: true }); - const rows = await driver.find('deal', { object: 'deal', where: { close_date: { $gte: '2026-01-01', $lt: '2026-05-01' } } }); + const rows = await driver.find('deal', { where: { close_date: { $gte: '2026-01-01', $lt: '2026-05-01' } } }); expect(rows.map((r: any) => r.id)).toEqual(['r2']); }); @@ -119,7 +119,7 @@ describe('SqlDriver Field.date is a tz-naive calendar day (ADR-0053 Phase 1)', ( await (driver as any).knex('deal').insert({ id: 'legacy', name: 'L', close_date: '2026-08-15T17:24:56.533Z' }); // Read-side repair: the returned value is date-only with no migration. - const row = await driver.findOne('deal', { object: 'deal', where: { id: 'legacy' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('deal', { where: { id: 'legacy' } }, { bypassTenantAudit: true }); expect(row.close_date).toBe('2026-08-15'); // …but the value still stored in SQL keeps its time, so a SQL equality @@ -127,13 +127,13 @@ describe('SqlDriver Field.date is a tz-naive calendar day (ADR-0053 Phase 1)', ( // ADR-0053 calls out: read-repair fixes display/read, and an optional // one-time migration (or any write through the normalized path) rewrites // legacy rows at rest. - const beforeRewrite = await driver.find('deal', { object: 'deal', where: { close_date: '2026-08-15' } }); + const beforeRewrite = await driver.find('deal', { where: { close_date: '2026-08-15' } }); expect(beforeRewrite.map((r: any) => r.id)).toEqual([]); // Rewriting through the normalized write path (formatInput) collapses the // stored value to date-only, after which the equality filter matches. await driver.update('deal', 'legacy', { close_date: '2026-08-15' }, { bypassTenantAudit: true }); - const afterRewrite = await driver.find('deal', { object: 'deal', where: { close_date: '2026-08-15' } }); + const afterRewrite = await driver.find('deal', { where: { close_date: '2026-08-15' } }); expect(afterRewrite.map((r: any) => r.id)).toEqual(['legacy']); }); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-datetime-canonical-storage.test.ts b/packages/drivers/driver-sql/src/sql-driver-datetime-canonical-storage.test.ts index 7d32b25824..8ac02bb52b 100644 --- a/packages/drivers/driver-sql/src/sql-driver-datetime-canonical-storage.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-datetime-canonical-storage.test.ts @@ -100,7 +100,7 @@ describe('Field.datetime writes land in ONE canonical form (#3912)', () => { ); } - const rows = await driver.find('evt', { object: 'evt', orderBy: [{ field: 'at', order: 'asc' }] }); + const rows = await driver.find('evt', { orderBy: [{ field: 'at', order: 'asc' }] }); expect(rows.map((r: any) => r.id)).toEqual(instants.map(([id]) => id)); // …and the raw column sorts identically, i.e. the DB did the ordering. @@ -188,12 +188,12 @@ describe('backfillCanonicalDatetimes converges a legacy database (#3912)', () => // it fast. Both must agree, or the migration would be observable as a change // in results — which is exactly what it must never be. const window = { at: { $gte: '2026-03-20T00:00:00.000Z', $lte: '2026-03-21T00:00:00.000Z' } }; - const before = (await driver.find('evt', { object: 'evt', where: window })).map((r: any) => r.id).sort(); + const before = (await driver.find('evt', { where: window })).map((r: any) => r.id).sort(); expect(before).toEqual(['canon', 'epoch', 'naive', 'offset']); await (driver as any).backfillCanonicalDatetimes('evt', true); - const after = (await driver.find('evt', { object: 'evt', where: window })).map((r: any) => r.id).sort(); + const after = (await driver.find('evt', { where: window })).map((r: any) => r.id).sort(); expect(after).toEqual(before); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-datetime-filter-text-storage.test.ts b/packages/drivers/driver-sql/src/sql-driver-datetime-filter-text-storage.test.ts index 41eb215682..7f89c21948 100644 --- a/packages/drivers/driver-sql/src/sql-driver-datetime-filter-text-storage.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-datetime-filter-text-storage.test.ts @@ -69,7 +69,6 @@ describe('SqlDriver datetime filters on ISO-TEXT-stored columns (#3912)', () => it('matches $gte against an ISO date string', async () => { const rows = await driver.find('lead', { - object: 'lead', where: { created_date: { $gte: '2026-01-01' } }, orderBy: [{ field: 'id', order: 'asc' }], }); @@ -78,7 +77,6 @@ describe('SqlDriver datetime filters on ISO-TEXT-stored columns (#3912)', () => it('matches a $gte / $lt window — the dashboard dateRange shape', async () => { const rows = await driver.find('lead', { - object: 'lead', where: { created_date: { $gte: '2026-01-01', $lt: '2026-05-01' } }, }); expect(rows.map((r: any) => r.id)).toEqual(['l2']); @@ -86,13 +84,11 @@ describe('SqlDriver datetime filters on ISO-TEXT-stored columns (#3912)', () => it('matches a full ISO timestamp comparand, to the millisecond', async () => { const inclusive = await driver.find('lead', { - object: 'lead', where: { created_date: { $gte: '2026-05-25T08:30:15.250Z' } }, }); expect(inclusive.map((r: any) => r.id)).toEqual(['l3']); const exclusive = await driver.find('lead', { - object: 'lead', where: { created_date: { $gt: '2026-05-25T08:30:15.250Z' } }, }); expect(exclusive).toEqual([]); @@ -100,7 +96,6 @@ describe('SqlDriver datetime filters on ISO-TEXT-stored columns (#3912)', () => it('matches a JS Date comparand against a TEXT-stored row', async () => { const rows = await driver.find('lead', { - object: 'lead', where: { created_date: { $gte: new Date('2026-05-01T00:00:00Z') } }, }); expect(rows.map((r: any) => r.id)).toEqual(['l3']); @@ -108,7 +103,6 @@ describe('SqlDriver datetime filters on ISO-TEXT-stored columns (#3912)', () => it('matches equality on the exact stored instant', async () => { const rows = await driver.find('lead', { - object: 'lead', where: { created_date: '2026-03-20T12:00:00.000Z' }, }); expect(rows.map((r: any) => r.id)).toEqual(['l2']); @@ -116,20 +110,17 @@ describe('SqlDriver datetime filters on ISO-TEXT-stored columns (#3912)', () => it('matches $between, $in and $nin', async () => { const between = await driver.find('lead', { - object: 'lead', where: { created_date: { $between: ['2026-01-01', '2026-04-01'] } }, }); expect(between.map((r: any) => r.id)).toEqual(['l2']); const isIn = await driver.find('lead', { - object: 'lead', where: { created_date: { $in: ['2026-03-20T12:00:00.000Z', '2026-05-25T08:30:15.250Z'] } }, orderBy: [{ field: 'id', order: 'asc' }], }); expect(isIn.map((r: any) => r.id)).toEqual(['l2', 'l3']); const notIn = await driver.find('lead', { - object: 'lead', where: { created_date: { $nin: ['2026-03-20T12:00:00.000Z'] } }, orderBy: [{ field: 'id', order: 'asc' }], }); @@ -138,18 +129,16 @@ describe('SqlDriver datetime filters on ISO-TEXT-stored columns (#3912)', () => it('matches $ne and the null predicates', async () => { const ne = await driver.find('lead', { - object: 'lead', where: { created_date: { $ne: '2026-03-20T12:00:00.000Z' } }, orderBy: [{ field: 'id', order: 'asc' }], }); expect(ne.map((r: any) => r.id)).toEqual(['l1', 'l3']); await driver.create('lead', { id: 'l4', name: 'Undated' }, { bypassTenantAudit: true }); - const missing = await driver.find('lead', { object: 'lead', where: { created_date: { $null: true } } }); + const missing = await driver.find('lead', { where: { created_date: { $null: true } } }); expect(missing.map((r: any) => r.id)).toEqual(['l4']); const present = await driver.find('lead', { - object: 'lead', where: { created_date: { $null: false } }, orderBy: [{ field: 'id', order: 'asc' }], }); @@ -170,7 +159,6 @@ describe('SqlDriver datetime filters on ISO-TEXT-stored columns (#3912)', () => it('matches inside an $or branch', async () => { const rows = await driver.find('lead', { - object: 'lead', where: { $or: [{ created_date: { $lt: '2025-06-01' } }, { name: 'Newer' }] }, orderBy: [{ field: 'id', order: 'asc' }], }); @@ -179,7 +167,6 @@ describe('SqlDriver datetime filters on ISO-TEXT-stored columns (#3912)', () => it('leaves the Field.date column on its own YYYY-MM-DD rule', async () => { const rows = await driver.find('lead', { - object: 'lead', where: { closed_on: { $gte: '2026-01-01' } }, orderBy: [{ field: 'id', order: 'asc' }], }); @@ -228,7 +215,6 @@ describe('SqlDriver datetime filters on the created_at audit column (#3912)', () expect(stamped[0]).toEqual({ c: 'text', u: 'text' }); const matched = await driver.find('ticket', { - object: 'ticket', where: { created_at: { $gte: '2000-01-01' } }, }); expect(matched.map((r: any) => r.id)).toEqual(['t1']); @@ -236,7 +222,6 @@ describe('SqlDriver datetime filters on the created_at audit column (#3912)', () // …and a window that starts after the stamp excludes it, so the match above // is a real comparison rather than "the predicate was dropped". const future = await driver.find('ticket', { - object: 'ticket', where: { created_at: { $gte: '2999-01-01' } }, }); expect(future).toEqual([]); @@ -281,7 +266,6 @@ describe('SqlDriver datetime filters on a legacy MIXED-storage column (#3912)', it('returns rows of BOTH forms from one window filter', async () => { const rows = await driver.find('lead', { - object: 'lead', where: { created_date: { $gte: '2026-01-01' } }, orderBy: [{ field: 'id', order: 'asc' }], }); @@ -290,7 +274,6 @@ describe('SqlDriver datetime filters on a legacy MIXED-storage column (#3912)', it('excludes rows of BOTH forms that fall outside the window', async () => { const rows = await driver.find('lead', { - object: 'lead', where: { created_date: { $lt: '2026-01-01' } }, orderBy: [{ field: 'id', order: 'asc' }], }); diff --git a/packages/drivers/driver-sql/src/sql-driver-datetime-filter.test.ts b/packages/drivers/driver-sql/src/sql-driver-datetime-filter.test.ts index 3e3226365e..ff609a89fb 100644 --- a/packages/drivers/driver-sql/src/sql-driver-datetime-filter.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-datetime-filter.test.ts @@ -62,7 +62,6 @@ describe('SqlDriver datetime filter coercion', () => { it('matches datetime $gte against an ISO date string', async () => { const rows = await driver.find('publication', { - object: 'publication', where: { published_at: { $gte: '2026-01-01' } }, orderBy: [{ field: 'published_at', order: 'asc' }], }); @@ -71,7 +70,6 @@ describe('SqlDriver datetime filter coercion', () => { it('matches datetime range with $gte / $lt', async () => { const rows = await driver.find('publication', { - object: 'publication', where: { published_at: { $gte: '2026-01-01', $lt: '2026-05-01' } }, }); expect(rows.map((r: any) => r.id)).toEqual(['p2']); @@ -79,7 +77,6 @@ describe('SqlDriver datetime filter coercion', () => { it('accepts a full ISO timestamp', async () => { const rows = await driver.find('publication', { - object: 'publication', where: { published_at: { $gte: '2026-05-25T00:00:00.000Z' } }, }); expect(rows.map((r: any) => r.id)).toEqual(['p3']); @@ -87,7 +84,6 @@ describe('SqlDriver datetime filter coercion', () => { it('accepts a JS Date object', async () => { const rows = await driver.find('publication', { - object: 'publication', where: { published_at: { $gte: new Date('2026-01-01T00:00:00Z') } }, }); expect(rows.map((r: any) => r.id).sort()).toEqual(['p2', 'p3']); @@ -96,7 +92,6 @@ describe('SqlDriver datetime filter coercion', () => { it('accepts a numeric epoch millisecond value', async () => { const ms = Date.parse('2026-01-01T00:00:00Z'); const rows = await driver.find('publication', { - object: 'publication', where: { published_at: { $gte: ms } }, }); expect(rows.map((r: any) => r.id).sort()).toEqual(['p2', 'p3']); @@ -104,7 +99,6 @@ describe('SqlDriver datetime filter coercion', () => { it('still filters non-date columns normally', async () => { const rows = await driver.find('publication', { - object: 'publication', where: { views: { $gte: 200 } }, }); expect(rows.map((r: any) => r.id).sort()).toEqual(['p2', 'p3']); @@ -112,7 +106,6 @@ describe('SqlDriver datetime filter coercion', () => { it('matches date (YYYY-MM-DD) columns with ISO comparand', async () => { const rows = await driver.find('publication', { - object: 'publication', where: { period_start: { $gte: '2026-01-01' } }, }); expect(rows.map((r: any) => r.id).sort()).toEqual(['p2', 'p3']); diff --git a/packages/drivers/driver-sql/src/sql-driver-datetime-mysql-storage.test.ts b/packages/drivers/driver-sql/src/sql-driver-datetime-mysql-storage.test.ts index 71c8bbeba4..6f6525c53f 100644 --- a/packages/drivers/driver-sql/src/sql-driver-datetime-mysql-storage.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-datetime-mysql-storage.test.ts @@ -130,7 +130,7 @@ describe.skipIf(!URL)('Field.datetime on MySQL (#3942)', () => { it('anchors a bare calendar-day comparand to UTC midnight, like the other dialects', async () => { await driver.create(TABLE, { id: 'b1', label: 'x', at: BOUNDARY }, { bypassTenantAudit: true }); const day = async (from: string, to: string) => - (await driver.find(TABLE, { object: TABLE, where: { at: { $gte: from, $lt: to } } })).map((r: any) => r.id); + (await driver.find(TABLE, { where: { at: { $gte: from, $lt: to } } })).map((r: any) => r.id); // 20:00Z belongs to 2026-03-20 in UTC; on a `+08:00` server read as local // midnight it would fall on the 21st — the divergence #3912 measured on PG. @@ -140,7 +140,7 @@ describe.skipIf(!URL)('Field.datetime on MySQL (#3942)', () => { it('round-trips the instant through a read', async () => { await driver.create(TABLE, { id: 'r', label: 'r', at: MIDDAY }, { bypassTenantAudit: true }); - const row: any = await driver.findOne(TABLE, { object: TABLE, where: { id: 'r' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne(TABLE, { where: { id: 'r' } }, { bypassTenantAudit: true }); expect(new Date(row.at).toISOString()).toBe(MIDDAY); }); }); @@ -219,7 +219,7 @@ describe.skipIf(!URL)('MySQL TIMESTAMP → DATETIME(3) migration (#3942)', () => { name: LEGACY, fields: { label: { type: 'string' }, at: { type: 'datetime' } } }, ]); await driver.create(LEGACY, { id: 'fresh', label: 'f' }, { bypassTenantAudit: true }); - const row: any = await driver.findOne(LEGACY, { object: LEGACY, where: { id: 'fresh' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne(LEGACY, { where: { id: 'fresh' } }, { bypassTenantAudit: true }); expect(row.created_at ?? null, 'created_at must still default').not.toBeNull(); }); }); @@ -319,7 +319,7 @@ async function columnTypes(driver: SqlDriver, table: string): Promise { - const row: any = await driver.findOne(table, { object: table, where: { id } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne(table, { where: { id } }, { bypassTenantAudit: true }); return new Date(row.at).toISOString(); } diff --git a/packages/drivers/driver-sql/src/sql-driver-datetime-postgres-timezone.test.ts b/packages/drivers/driver-sql/src/sql-driver-datetime-postgres-timezone.test.ts index 0b18ce1ca8..c375dfcf92 100644 --- a/packages/drivers/driver-sql/src/sql-driver-datetime-postgres-timezone.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-datetime-postgres-timezone.test.ts @@ -104,7 +104,7 @@ describe.skipIf(!URL)('Field.datetime on Postgres is timezone-independent (#3912 await driver.create(TABLE, { id: 'b1', label: 'x', at: new Date(BOUNDARY) }, { bypassTenantAudit: true }); const day = async (from: string, to: string) => - (await driver.find(TABLE, { object: TABLE, where: { at: { $gte: from, $lt: to } } })).map((r: any) => r.id); + (await driver.find(TABLE, { where: { at: { $gte: from, $lt: to } } })).map((r: any) => r.id); // 20:00Z belongs to 2026-03-20 in UTC. Read against the server's local // midnight (Asia/Shanghai) it would fall on the 21st instead — which is @@ -115,7 +115,7 @@ describe.skipIf(!URL)('Field.datetime on Postgres is timezone-independent (#3912 it('presents the stored instant as canonical UTC on read', async () => { await driver.create(TABLE, { id: 'r', label: 'r', at: '2026-03-20 12:00:00' }, { bypassTenantAudit: true }); - const row: any = await driver.findOne(TABLE, { object: TABLE, where: { id: 'r' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne(TABLE, { where: { id: 'r' } }, { bypassTenantAudit: true }); expect(new Date(row.at).toISOString()).toBe(MIDDAY); }); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-empty-field-constraint.test.ts b/packages/drivers/driver-sql/src/sql-driver-empty-field-constraint.test.ts index 1178c8072b..a2034beab1 100644 --- a/packages/drivers/driver-sql/src/sql-driver-empty-field-constraint.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-empty-field-constraint.test.ts @@ -83,7 +83,6 @@ describe('[#5240] SqlDriver refuses a field constrained by zero operators', () = // lane's half of #5240 (#5239 / #5146 batch). const ids = async (where: unknown): Promise => { const rows = await driver.find('deal', { - object: 'deal', fields: ['id'], where: where as FilterCondition, }); diff --git a/packages/drivers/driver-sql/src/sql-driver-external-remote-name.test.ts b/packages/drivers/driver-sql/src/sql-driver-external-remote-name.test.ts index 7ff55c2eda..9f64b429ec 100644 --- a/packages/drivers/driver-sql/src/sql-driver-external-remote-name.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-external-remote-name.test.ts @@ -86,7 +86,7 @@ describe('SqlDriver external read path — remoteName resolution (ADR-0015)', () ).not.toThrow(); // The bug: this used to throw `no such table: ext_customer`. - const rows = await ext.find('ext_customer', { object: 'ext_customer' }); + const rows = await ext.find('ext_customer', {}); expect(rows).toHaveLength(2); const acme = rows.find((r: any) => r.name === 'Acme'); @@ -98,24 +98,24 @@ describe('SqlDriver external read path — remoteName resolution (ADR-0015)', () expect(typeof acme.amount).toBe('number'); // numeric scalar // count + findOne route to the remote table too. - expect(await ext.count('ext_customer', { object: 'ext_customer' })).toBe(2); - const one = await ext.findOne('ext_customer', { object: 'ext_customer', where: { name: 'Globex' } }); + expect(await ext.count('ext_customer', {})).toBe(2); + const one = await ext.findOne('ext_customer', { where: { name: 'Globex' } }); expect(one?.name).toBe('Globex'); // Filtered reads hit the remote table. - const filtered = await ext.find('ext_customer', { object: 'ext_customer', where: { name: 'Acme' } }); + const filtered = await ext.find('ext_customer', { where: { name: 'Acme' } }); expect(filtered).toHaveLength(1); expect(filtered[0].name).toBe('Acme'); // Date filter — guards the coercion re-keying (§3): coercion maps are keyed // by the OBJECT name even though the builder now targets the remote table. - const byDate = await ext.find('ext_customer', { object: 'ext_customer', where: { when: '2026-02-15' } }); + const byDate = await ext.find('ext_customer', { where: { when: '2026-02-15' } }); expect(byDate).toHaveLength(1); expect(byDate[0].name).toBe('Globex'); // Datetime filter — the SQLite epoch-affinity case the §3 trap would break // if coercion were keyed by the physical (remote) name instead of object. - const byDatetime = await ext.find('ext_customer', { object: 'ext_customer', where: { seen_at: '2026-01-02T10:00:00.000Z' } }); + const byDatetime = await ext.find('ext_customer', { where: { seen_at: '2026-01-02T10:00:00.000Z' } }); expect(byDatetime.map((r: any) => r.name)).toContain('Acme'); // No object-named table was ever created in the remote db (no DDL leaked). @@ -144,7 +144,7 @@ describe('SqlDriver external read path — remoteName resolution (ADR-0015)', () await ext.connect?.(); try { ext.registerExternalObject!({ name: 'remote_customers', external: {}, fields: FIELDS as any }); - const rows = await ext.find('remote_customers', { object: 'remote_customers' }); + const rows = await ext.find('remote_customers', {}); expect(rows.length).toBe(2); } finally { await ext.disconnect?.(); @@ -156,7 +156,7 @@ describe('SqlDriver external read path — remoteName resolution (ADR-0015)', () await ext.connect?.(); try { ext.registerExternalObject!({ name: 'ext_cust2', external: { remoteName: 'remote_customers', remoteSchema: 'mart' }, fields: FIELDS as any }); - const rows = await ext.find('ext_cust2', { object: 'ext_cust2' }); + const rows = await ext.find('ext_cust2', {}); expect(rows.length).toBe(2); } finally { await ext.disconnect?.(); diff --git a/packages/drivers/driver-sql/src/sql-driver-filter-no-silent-drop.test.ts b/packages/drivers/driver-sql/src/sql-driver-filter-no-silent-drop.test.ts index ab0d31d15a..c4faebfc04 100644 --- a/packages/drivers/driver-sql/src/sql-driver-filter-no-silent-drop.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-filter-no-silent-drop.test.ts @@ -77,7 +77,7 @@ describe('SqlDriver rejects an uncompilable filter instead of dropping it', () = // it — the type layer never excluded this input, which is exactly why the // runtime refusal has to. const find = (where: unknown) => - driver.find('deal', { object: 'deal', fields: ['id'], where: where as FilterCondition }); + driver.find('deal', { fields: ['id'], where: where as FilterCondition }); // ── the array dialect is gone: every array shape is refused ─────────── diff --git a/packages/drivers/driver-sql/src/sql-driver-filter-refusal-envelope.test.ts b/packages/drivers/driver-sql/src/sql-driver-filter-refusal-envelope.test.ts index f3455c1f94..e9e17a4937 100644 --- a/packages/drivers/driver-sql/src/sql-driver-filter-refusal-envelope.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-filter-refusal-envelope.test.ts @@ -70,7 +70,7 @@ describe('[#4436] SqlDriver filter refusals carry INVALID_FILTER and leak no dri }); const find = (where: unknown) => - driver.find('deal', { object: 'deal', fields: ['id'], where: where as FilterCondition }); + driver.find('deal', { fields: ['id'], where: where as FilterCondition }); // The issue's own repro, at the layer that produces the envelope. it('the $-object unsupported-operator branch — the exact shape #4436 reported', async () => { diff --git a/packages/drivers/driver-sql/src/sql-driver-index-drift.test.ts b/packages/drivers/driver-sql/src/sql-driver-index-drift.test.ts index 95fdc155af..4c363bae7e 100644 --- a/packages/drivers/driver-sql/src/sql-driver-index-drift.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-index-drift.test.ts @@ -293,7 +293,7 @@ describe('SqlDriver index drift (#3728)', () => { expect(Object.values(uniques)).toContainEqual(['COALESCE(organization_id)', 'code']); // Existing rows survived, and the cross-tenant insert the issue is about works. - expect(await driver.count('product', { object: 'product' })).toBe(2); + expect(await driver.count('product', {})).toBe(2); const b = await driver.create('product', { organization_id: 'org_b', code: 'PROD-00001' }); expect(b.code).toBe('PROD-00001'); diff --git a/packages/drivers/driver-sql/src/sql-driver-like-escape.test.ts b/packages/drivers/driver-sql/src/sql-driver-like-escape.test.ts index 00e820e138..04d80f1c44 100644 --- a/packages/drivers/driver-sql/src/sql-driver-like-escape.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-like-escape.test.ts @@ -112,17 +112,17 @@ describe('SqlDriver — contains escapes LIKE metacharacters (P0-3)', () => { }); it('a "%" value matches only rows containing a literal %, not every row', async () => { - const r = await driver.find('docs', { object: 'docs', where: { title: { $contains: '%' } } }); + const r = await driver.find('docs', { where: { title: { $contains: '%' } } }); expect(r.map((x: any) => x.id)).toEqual(['1']); }); it('a "_" value matches only rows containing a literal _, not any single char', async () => { - const r = await driver.find('docs', { object: 'docs', where: { title: { $contains: '_' } } }); + const r = await driver.find('docs', { where: { title: { $contains: '_' } } }); expect(r.map((x: any) => x.id)).toEqual(['3']); }); it('an ordinary substring still matches normally', async () => { - const r = await driver.find('docs', { object: 'docs', where: { title: { $contains: 'sale' } } }); + const r = await driver.find('docs', { where: { title: { $contains: 'sale' } } }); expect(r.map((x: any) => x.id)).toEqual(['1']); }); }); @@ -240,7 +240,6 @@ function declareLikeEscapeSweep(cell: DialectCell): void { for (const c of LIKE_ESCAPE_CASES) { it(c.name, async () => { const rows = await driver.find(LIKE_TABLE, { - object: LIKE_TABLE, where: { title: { $contains: c.value } }, }); const got = rows diff --git a/packages/drivers/driver-sql/src/sql-driver-nested-and-filter.test.ts b/packages/drivers/driver-sql/src/sql-driver-nested-and-filter.test.ts index 83850cfd1a..9dbd9fb262 100644 --- a/packages/drivers/driver-sql/src/sql-driver-nested-and-filter.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-nested-and-filter.test.ts @@ -68,7 +68,7 @@ describe('SqlDriver — field key alongside a nested $and (#3650)', () => { }); it('intersects every level on find()', async () => { - const rows = await driver.find('opportunity', { object: 'opportunity', where: NESTED_AND }); + const rows = await driver.find('opportunity', { where: NESTED_AND }); expect(rows.map((r: any) => r.id)).toEqual(['b']); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-not-null-safe.test.ts b/packages/drivers/driver-sql/src/sql-driver-not-null-safe.test.ts index f5f581a2cf..b5e740fd3b 100644 --- a/packages/drivers/driver-sql/src/sql-driver-not-null-safe.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-not-null-safe.test.ts @@ -94,7 +94,6 @@ describe('[#5146] SqlDriver compiles $not NULL-safely', () => { const ids = async (where: unknown): Promise => { const rows = await driver.find('deal', { - object: 'deal', fields: ['id'], where: where as FilterCondition, }); diff --git a/packages/drivers/driver-sql/src/sql-driver-null-operators.test.ts b/packages/drivers/driver-sql/src/sql-driver-null-operators.test.ts index 1b70734b4b..71d0c44a5a 100644 --- a/packages/drivers/driver-sql/src/sql-driver-null-operators.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-null-operators.test.ts @@ -55,7 +55,7 @@ describe('SqlDriver — null / empty operators (#2704)', () => { // reaches the driver. Feeding the array raw is what this driver no longer // does — pinned at the bottom of this block. const lowered = (where: unknown) => - driver.find('tasks', { object: 'tasks', where: parseFilterAST(where) as FilterCondition }); + driver.find('tasks', { where: parseFilterAST(where) as FilterCondition }); it('equals + null → IS NULL (baseline that already worked)', async () => { expect(ids(await lowered([['assignee', '=', null]]))).toEqual(['2', '4']); @@ -84,7 +84,6 @@ describe('SqlDriver — null / empty operators (#2704)', () => { it('count with is_null is scoped, not the whole table', async () => { const count = await driver.count('tasks', { - object: 'tasks', where: parseFilterAST([['assignee', 'isnull', true]]) as FilterCondition, }); expect(count).toBe(2); @@ -92,34 +91,34 @@ describe('SqlDriver — null / empty operators (#2704)', () => { it('the raw array is refused by the driver — the dialect is gone (#5158)', async () => { await expect( - driver.find('tasks', { object: 'tasks', where: [['assignee', 'isnull', true]] as any }), + driver.find('tasks', { where: [['assignee', 'isnull', true]] as any }), ).rejects.toThrow(/A filter ARRAY reached the driver/); }); }); describe('object-format where ($-operators)', () => { it('$null: true → IS NULL', async () => { - const rows = await driver.find('tasks', { object: 'tasks', where: { assignee: { $null: true } } }); + const rows = await driver.find('tasks', { where: { assignee: { $null: true } } }); expect(ids(rows)).toEqual(['2', '4']); }); it('$null: false → IS NOT NULL', async () => { - const rows = await driver.find('tasks', { object: 'tasks', where: { assignee: { $null: false } } }); + const rows = await driver.find('tasks', { where: { assignee: { $null: false } } }); expect(ids(rows)).toEqual(['1', '3']); }); it('$ne: null → IS NOT NULL', async () => { - const rows = await driver.find('tasks', { object: 'tasks', where: { assignee: { $ne: null } } }); + const rows = await driver.find('tasks', { where: { assignee: { $ne: null } } }); expect(ids(rows)).toEqual(['1', '3']); }); it('$startsWith → prefix LIKE', async () => { - const rows = await driver.find('tasks', { object: 'tasks', where: { assignee: { $startsWith: 'a' } } }); + const rows = await driver.find('tasks', { where: { assignee: { $startsWith: 'a' } } }); expect(ids(rows)).toEqual(['1']); }); it('$regex (better-auth contains) → substring LIKE, not exact match', async () => { - const rows = await driver.find('tasks', { object: 'tasks', where: { assignee: { $regex: 'aro' } } }); + const rows = await driver.find('tasks', { where: { assignee: { $regex: 'aro' } } }); expect(ids(rows)).toEqual(['3']); }); @@ -141,7 +140,7 @@ describe('SqlDriver — null / empty operators (#2704)', () => { it('unknown $-operator throws instead of a silent equality compare', async () => { await expect( - driver.find('tasks', { object: 'tasks', where: { assignee: { $bogus: 1 } } }), + driver.find('tasks', { where: { assignee: { $bogus: 1 } } }), ).rejects.toThrow(/Unsupported filter operator/); }); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-numeric-fidelity.test.ts b/packages/drivers/driver-sql/src/sql-driver-numeric-fidelity.test.ts index 2c9b9fc70e..410670d50b 100644 --- a/packages/drivers/driver-sql/src/sql-driver-numeric-fidelity.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-numeric-fidelity.test.ts @@ -75,7 +75,7 @@ describe('SqlDriver scalar type fidelity (rating/slider/toggle/progress)', () => }, { bypassTenantAudit: true }, ); - const row = await driver.findOne('zoo', { object: 'zoo', where: { id: 'z1' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('zoo', { where: { id: 'z1' } }, { bypassTenantAudit: true }); // control expect(row.f_number).toBe(42); @@ -95,8 +95,8 @@ describe('SqlDriver scalar type fidelity (rating/slider/toggle/progress)', () => await driver.create('zoo', { id: 'z2', name: 'B', f_boolean: true, f_toggle: true }, { bypassTenantAudit: true }); await driver.create('zoo', { id: 'z3', name: 'C', f_boolean: false, f_toggle: false }, { bypassTenantAudit: true }); - const on = await driver.findOne('zoo', { object: 'zoo', where: { id: 'z2' } }, { bypassTenantAudit: true }); - const off = await driver.findOne('zoo', { object: 'zoo', where: { id: 'z3' } }, { bypassTenantAudit: true }); + const on = await driver.findOne('zoo', { where: { id: 'z2' } }, { bypassTenantAudit: true }); + const off = await driver.findOne('zoo', { where: { id: 'z3' } }, { bypassTenantAudit: true }); // control expect(on.f_boolean).toBe(true); @@ -121,7 +121,7 @@ describe('SqlDriver scalar type fidelity (rating/slider/toggle/progress)', () => }, { bypassTenantAudit: true }, ); - const row = await driver.findOne('zoo', { object: 'zoo', where: { id: 'z4' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('zoo', { where: { id: 'z4' } }, { bypassTenantAudit: true }); expect(row.f_record).toEqual({ home: '+1', work: '+2' }); expect(row.f_video).toEqual({ url: 'https://cdn/v.mp4', duration: 12 }); @@ -187,7 +187,7 @@ describe('SqlDriver numeric read coercion repairs legacy TEXT columns', () => { { id: 'L1', name: 'old-row', f_rating: 4, f_slider: 25, f_progress: 60 }, { bypassTenantAudit: true }, ); - const row = await driver.findOne('legacy', { object: 'legacy', where: { id: 'L1' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('legacy', { where: { id: 'L1' } }, { bypassTenantAudit: true }); expect(typeof row.f_rating).toBe('number'); expect(row.f_rating).toBe(4); @@ -202,7 +202,7 @@ describe('SqlDriver numeric read coercion repairs legacy TEXT columns', () => { // Hand-write a row with a null and a non-numeric string straight to the // TEXT columns, bypassing the driver, to model messy legacy data. await knex('legacy').insert({ id: 'L2', name: 'messy', f_rating: null, f_slider: 'n/a', f_progress: '60' }); - const row = await driver.findOne('legacy', { object: 'legacy', where: { id: 'L2' } }, { bypassTenantAudit: true }); + const row = await driver.findOne('legacy', { where: { id: 'L2' } }, { bypassTenantAudit: true }); expect(row.f_rating).toBeNull(); // null stays null, not 0 expect(row.f_slider).toBe('n/a'); // non-numeric junk is preserved, not NaN diff --git a/packages/drivers/driver-sql/src/sql-driver-or-filter.test.ts b/packages/drivers/driver-sql/src/sql-driver-or-filter.test.ts index 2abecb3044..d22f30b70b 100644 --- a/packages/drivers/driver-sql/src/sql-driver-or-filter.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-or-filter.test.ts @@ -105,7 +105,7 @@ function declareFilterLogicSweep(cell: DialectCell): void { describe('shared conformance cases', () => { for (const c of FILTER_LOGIC_CASES) { it(c.name, async () => { - const rows = await driver.find(FILTER_TABLE, { object: FILTER_TABLE, where: c.filter }); + const rows = await driver.find(FILTER_TABLE, { where: c.filter }); const got = rows .map((r: any) => String(r.id)) .sort((x: string, y: string) => x.localeCompare(y)); @@ -147,7 +147,6 @@ function declareFilterLogicSweep(cell: DialectCell): void { it('matches only the rows inside the abutting windows', async () => { const rows = await driver.find(DATE_WINDOW_TABLE, { - object: DATE_WINDOW_TABLE, where: { $or: [ { end_date: { $gte: '2026-08-07', $lt: '2026-08-08' } }, @@ -160,7 +159,6 @@ function declareFilterLogicSweep(cell: DialectCell): void { it('keeps a window AND-ed with a sibling key in the same branch', async () => { const rows = await driver.find(DATE_WINDOW_TABLE, { - object: DATE_WINDOW_TABLE, where: { $or: [ { id: 'nope' }, diff --git a/packages/drivers/driver-sql/src/sql-driver-out-of-contract-filter-input.test.ts b/packages/drivers/driver-sql/src/sql-driver-out-of-contract-filter-input.test.ts index d937ba514b..290d9d2101 100644 --- a/packages/drivers/driver-sql/src/sql-driver-out-of-contract-filter-input.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-out-of-contract-filter-input.test.ts @@ -89,7 +89,6 @@ describe('[#5347/#5348] SqlDriver refuses out-of-contract filter input', () => { const ids = async (where: unknown): Promise => { const rows = await driver.find('deal', { - object: 'deal', fields: ['id'], where: where as FilterCondition, }); diff --git a/packages/drivers/driver-sql/src/sql-driver-pagination-conformance.test.ts b/packages/drivers/driver-sql/src/sql-driver-pagination-conformance.test.ts index e7ea4b9362..d168ff4191 100644 --- a/packages/drivers/driver-sql/src/sql-driver-pagination-conformance.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-pagination-conformance.test.ts @@ -194,7 +194,7 @@ function declarePartitionSweep(cell: DialectCell): void { for (let offset = 0; offset < PAGINATION_ROWS.length; offset += pageSize) { const page = await driver.find( PAGED_TABLE, - { object: PAGED_TABLE, ...query, limit: pageSize, offset }, + { ...query, limit: pageSize, offset }, { bypassTenantAudit: true }, ); paged.push(...page); @@ -221,7 +221,7 @@ function declarePartitionSweep(cell: DialectCell): void { // user-facing half of the guarantee. const whole = await driver.find( PAGED_TABLE, - { object: PAGED_TABLE, orderBy: [...testCase.orderBy] }, + { orderBy: [...testCase.orderBy] }, { bypassTenantAudit: true }, ); expect(paged.map((r) => r.id)).toEqual(whole.map((r) => r.id)); @@ -253,7 +253,7 @@ function declarePartitionSweep(cell: DialectCell): void { // imposed ORDER BY would only change plan selection. const rows = await driver.find( PAGED_TABLE, - { object: PAGED_TABLE }, + {}, { bypassTenantAudit: true }, ); expect([...rows.map((r) => String(r.id))].sort()).toEqual([...PAGINATION_ALL_IDS].sort()); @@ -287,7 +287,7 @@ function declareClauseSweep(cell: DialectCell): void { }); // One warm-up read before the recorder is installed: a live dialect may // introspect on first use, and that statement is not the one under test. - await driver.find(CLAUSE_TABLE, { object: CLAUSE_TABLE }, { bypassTenantAudit: true }); + await driver.find(CLAUSE_TABLE, {}, { bypassTenantAudit: true }); driver.captureStatements(); }); @@ -298,7 +298,7 @@ function declareClauseSweep(cell: DialectCell): void { const sqlOfFind = (query: Omit) => driver.sqlOf(CLAUSE_TABLE, () => - driver.find(CLAUSE_TABLE, { object: CLAUSE_TABLE, ...query }, { bypassTenantAudit: true }), + driver.find(CLAUSE_TABLE, { ...query }, { bypassTenantAudit: true }), ); it('appends `id` after a non-unique sort key', async () => { @@ -353,7 +353,7 @@ function declareClauseSweep(cell: DialectCell): void { const sql = await driver.sqlOf(CLAUSE_TABLE, () => driver.findOne( CLAUSE_TABLE, - { object: CLAUSE_TABLE, where: { status: 'open' } }, + { where: { status: 'open' } }, { bypassTenantAudit: true }, ), ); @@ -365,7 +365,7 @@ function declareClauseSweep(cell: DialectCell): void { const sql = await driver.sqlOf(CLAUSE_TABLE, () => driver.findOne( CLAUSE_TABLE, - { object: CLAUSE_TABLE, orderBy: [{ field: 'status', order: 'desc' }] }, + { orderBy: [{ field: 'status', order: 'desc' }] }, { bypassTenantAudit: true }, ), ); @@ -382,7 +382,6 @@ function declareClauseSweep(cell: DialectCell): void { expect(driver['paginationTieBreaker']('some_remote_table')).toBeNull(); expect( driver['orderKeysFor']('some_remote_table', { - object: 'some_remote_table', limit: 5, offset: 5, }), @@ -402,8 +401,8 @@ function declareClauseSweep(cell: DialectCell): void { const remote = 'warned_remote_table'; const warn = vi.spyOn(driver['logger'], 'warn').mockImplementation(() => {}); try { - driver['orderKeysFor'](remote, { object: remote, limit: 5, offset: 5 }); - driver['orderKeysFor'](remote, { object: remote, limit: 5, offset: 10 }); + driver['orderKeysFor'](remote, { limit: 5, offset: 5 }); + driver['orderKeysFor'](remote, { limit: 5, offset: 10 }); expect(warn, 'once per object, not per query').toHaveBeenCalledTimes(1); const message = warn.mock.calls[0]![0]; expect(message, 'names the object').toContain(remote); @@ -411,11 +410,10 @@ function declareClauseSweep(cell: DialectCell): void { expect(message, 'names a remedy').toMatch(/orderBy/); // A managed table keeps the guarantee, so it must stay quiet. - driver['orderKeysFor'](CLAUSE_TABLE, { object: CLAUSE_TABLE, limit: 5, offset: 5 }); + driver['orderKeysFor'](CLAUSE_TABLE, { limit: 5, offset: 5 }); // So must an unpaged read, and a sorted one: neither is the silent case. - driver['orderKeysFor'](remote, { object: remote }); + driver['orderKeysFor'](remote, {}); driver['orderKeysFor'](remote, { - object: remote, orderBy: [{ field: 'status', order: 'asc' }], limit: 5, }); diff --git a/packages/drivers/driver-sql/src/sql-driver-query-signature.test.ts b/packages/drivers/driver-sql/src/sql-driver-query-signature.test.ts new file mode 100644 index 0000000000..870787336f --- /dev/null +++ b/packages/drivers/driver-sql/src/sql-driver-query-signature.test.ts @@ -0,0 +1,82 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * The driver side of #5181's `DriverQuery` narrowing (#6075). + * + * `packages/spec/src/contracts/data-driver.test.ts` already pins the CONTRACT: + * `IDataDriver`'s six query-taking methods declare `DriverQuery`. That pin says + * nothing about the implementations, and it cannot: method parameters are + * compared bivariantly, so an implementation declaring the wider `QueryAST` + * satisfies the narrower contract and every gate stays green. That is exactly + * how five drivers kept a stale signature through a full `pnpm typecheck` + * (125/125) after #6076 merged. + * + * The cost of the gap was a dormant lie rather than a live defect: a caller is + * now free to omit `object`, so an implementation declaring `query: QueryAST` + * promised a `string` where the runtime value could be `undefined`. No driver + * read it — a repo-wide grep for `query.object` across every driver source + * tree came back empty — so nothing was broken, and nothing stopped the next + * reader from writing the line that would break. + * + * These pins close that. They resolve in tsc, not in vitest: widening any of + * the six signatures back to `QueryAST` turns the `@ts-expect-error` below into + * an unused directive, which is itself an error, and `DropsObject` resolves to + * `never` per method so the failure names which one moved. The `expect()` calls + * only give the assertions a home the runner will execute. + * + * `SqlDriver` is the base `SqliteWasmDriver` and `TursoDriver` extend, so these + * pins cover their inherited signatures too; `TursoDriver`'s own overrides and + * the memory / mongodb implementations carry the same narrowing, held by their + * own suites' call-site literals, which no longer spell `object` and therefore + * no longer compile against a re-widened parameter. + */ + +import { describe, it, expect } from 'vitest'; +import type { DriverQuery } from '@objectstack/spec/contracts'; +import { SqlDriver } from './sql-driver.js'; + +/** Resolves to `'dropped'` only while `T` has no `object` key; `never` otherwise. */ +type DropsObject = 'object' extends keyof T ? never : 'dropped'; + +describe('SqlDriver query signatures follow the DriverQuery contract (#6075)', () => { + it('declares a query parameter without `object` on all six contract methods', () => { + // Read off the CLASS, not off the alias: a revert that puts `QueryAST` back + // on one implementation while `DriverQuery` stays imported would sail past + // any alias-scoped assertion. Here that slot resolves to `never`. + const perMethod: [ + DropsObject[1]>, + DropsObject[1]>, + DropsObject[1]>>, + DropsObject[1]>, + DropsObject[1]>, + DropsObject[1]>, + ] = ['dropped', 'dropped', 'dropped', 'dropped', 'dropped', 'dropped']; + expect(perMethod).toHaveLength(6); + }); + + it('makes a driver reading `query.object` a compile error — the whole point', () => { + // This is the line the narrowing exists to reject. Before it, the read + // compiled and typed as `string` while the runtime value could be + // `undefined`; after it, tsc refuses. Nothing else in this PR is worth + // anything if this directive ever stops being needed. + const readsObject = (query: Parameters[1]) => + // @ts-expect-error - Property 'object' does not exist on type 'DriverQuery' + query.object; + expect(typeof readsObject).toBe('function'); + }); + + it('rejects the redundant `object` key in a call-site literal', () => { + // The object name arrives as argument one. Spelling it again in the query + // is the redundancy #5181 removed, and the excess-property check is what + // keeps the two spellings from ever disagreeing. + // @ts-expect-error - 'object' does not exist in type 'DriverQuery' + const redundant: Parameters[1] = { object: 'account', where: { status: 'open' } }; + expect(redundant).toBeTruthy(); + }); + + it('accepts a query carrying only the parts a driver actually reads', () => { + const q: DriverQuery = { where: { status: 'open' }, orderBy: [{ field: 'id', order: 'asc' }], limit: 10 }; + const accepted: Parameters[1] = q; + expect(accepted.limit).toBe(10); + }); +}); diff --git a/packages/drivers/driver-sql/src/sql-driver-retention-prune.test.ts b/packages/drivers/driver-sql/src/sql-driver-retention-prune.test.ts index 958594eba1..0c43eae8d0 100644 --- a/packages/drivers/driver-sql/src/sql-driver-retention-prune.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-retention-prune.test.ts @@ -59,24 +59,24 @@ describe('SqlDriver retention prune on a builtin created_at timestamp column', ( const deleted = await driver.deleteMany( 'retention_probe', - { object: 'retention_probe', where: { created_at: { $lt: cutoffIso } } }, + { where: { created_at: { $lt: cutoffIso } } }, { bypassTenantAudit: true }, ); expect(deleted).toBe(2); - const remaining = await driver.find('retention_probe', { object: 'retention_probe' }); + const remaining = await driver.find('retention_probe', {}); expect(remaining.map((r: any) => r.id)).toEqual(['new1']); }); it('an ISO-8601 cutoff before every row deletes nothing', async () => { const deleted = await driver.deleteMany( 'retention_probe', - { object: 'retention_probe', where: { created_at: { $lt: '2020-01-01T00:00:00.000Z' } } }, + { where: { created_at: { $lt: '2020-01-01T00:00:00.000Z' } } }, { bypassTenantAudit: true }, ); expect(deleted).toBe(0); - const remaining = await driver.find('retention_probe', { object: 'retention_probe' }); + const remaining = await driver.find('retention_probe', {}); expect(remaining.map((r: any) => r.id).sort()).toEqual(['new1', 'old1', 'old2']); }); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-rotation.test.ts b/packages/drivers/driver-sql/src/sql-driver-rotation.test.ts index 7791d77e1a..00a5bf5e5c 100644 --- a/packages/drivers/driver-sql/src/sql-driver-rotation.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-rotation.test.ts @@ -71,8 +71,8 @@ describe('SqlDriver rotation (ADR-0057 P2)', () => { // The row is physically in the shard and visible through the view. const inShard = (await driver.execute(`SELECT id FROM "${currentShard}"`)) as any[]; expect(inShard.map((r) => r.id)).toEqual(['a']); - expect(await driver.count('rot_event', { object: 'rot_event' })).toBe(1); - const found = await driver.findOne('rot_event', { object: 'rot_event', where: { id: 'a' } }); + expect(await driver.count('rot_event', {})).toBe(1); + const found = await driver.findOne('rot_event', { where: { id: 'a' } }); expect(found?.payload).toBe('x'); }); @@ -86,7 +86,7 @@ describe('SqlDriver rotation (ADR-0057 P2)', () => { expect(day1.current).toBe('rot_event__r20360802'); expect(day1.dropped).toEqual([]); await driver.create('rot_event', { id: 'd1', payload: 'day1', created_at: new Date(T0 + DAY_MS) }, { bypassTenantAudit: true }); - expect(await driver.count('rot_event', { object: 'rot_event' })).toBe(2); + expect(await driver.count('rot_event', {})).toBe(2); // Day 3 (shards=3, window = [day1 .. day3]): the day-0 shard falls out — // one O(1) DROP, its rows gone from the view, newer rows intact. @@ -96,7 +96,7 @@ describe('SqlDriver rotation (ADR-0057 P2)', () => { const names = await tableNames(driver); expect(names['rot_event__r20360801']).toBeUndefined(); - const remaining = await driver.find('rot_event', { object: 'rot_event' }); + const remaining = await driver.find('rot_event', {}); expect(remaining.map((r: any) => r.id).sort()).toEqual(['d1']); }); @@ -112,7 +112,7 @@ describe('SqlDriver rotation (ADR-0057 P2)', () => { const names = await tableNames(driver); expect(names['rot_event']).toBe('view'); - const rows = await driver.find('rot_event', { object: 'rot_event' }); + const rows = await driver.find('rot_event', {}); expect(rows.map((r: any) => r.id)).toEqual(['legacy']); }); @@ -136,11 +136,11 @@ describe('SqlDriver rotation (ADR-0057 P2)', () => { await driver.create('rot_event', { id: 'old2', payload: 'p2', created_at: new Date(T0 - 10 * DAY_MS) }, { bypassTenantAudit: true }); const deleted = await driver.deleteMany( 'rot_event', - { object: 'rot_event', where: { created_at: { $lt: new Date(T0).toISOString() } } }, + { where: { created_at: { $lt: new Date(T0).toISOString() } } }, { bypassTenantAudit: true }, ); expect(deleted).toBe(1); - const rest = await driver.find('rot_event', { object: 'rot_event' }); + const rest = await driver.find('rot_event', {}); expect(rest.map((r: any) => r.id)).toEqual(['new']); }); @@ -151,6 +151,6 @@ describe('SqlDriver rotation (ADR-0057 P2)', () => { const second = await driver.rotateShards(ROTATED_OBJECT, T0 + 3_600_000); // +1h, same day expect(second.current).toBe(first.current); expect(second.dropped).toEqual([]); - expect(await driver.count('rot_event', { object: 'rot_event' })).toBe(1); + expect(await driver.count('rot_event', {})).toBe(1); }); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-schema.test.ts b/packages/drivers/driver-sql/src/sql-driver-schema.test.ts index c6556857af..6c596aa092 100644 --- a/packages/drivers/driver-sql/src/sql-driver-schema.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-schema.test.ts @@ -131,7 +131,7 @@ describe('SqlDriver Schema Sync (SQLite)', () => { users: ['u1', 'u2'], }); - const results = await driver.find('multi_test', { object: 'multi_test' }); + const results = await driver.find('multi_test', {}); const row = results[0]; expect(row.tags).toEqual(['a', 'b']); @@ -154,7 +154,7 @@ describe('SqlDriver Schema Sync (SQLite)', () => { expect(columns).toHaveProperty('completion'); await driver.create('percent_test', { completion: 0.85 }); - const res = await driver.find('percent_test', { object: 'percent_test' }); + const res = await driver.find('percent_test', {}); expect(res[0].completion).toBe(0.85); }); @@ -183,7 +183,7 @@ describe('SqlDriver Schema Sync (SQLite)', () => { owner: 'u-1', watchers: ['u-2', 'u-3'], }); - const res = await driver.find('ticket_user_test', { object: 'ticket_user_test' }); + const res = await driver.find('ticket_user_test', {}); expect(res[0].owner).toBe('u-1'); expect(res[0].watchers).toEqual(['u-2', 'u-3']); }); @@ -276,7 +276,7 @@ describe('SqlDriver Schema Sync (SQLite)', () => { work_hours: '09:00:00', }); - const res = await driver.find('new_types_test', { object: 'new_types_test' }); + const res = await driver.find('new_types_test', {}); const row = res[0]; expect(row.email).toBe('test@example.com'); @@ -382,7 +382,7 @@ describe('SqlDriver Schema Sync (SQLite)', () => { ).rejects.toThrow(/UNIQUE constraint failed|duplicate key value/); // Differing channel → allowed. await driver.create('idx_multi_obj', { notification_id: 'n1', recipient_id: 'r1', channel: 'email' }); - const rows = await driver.find('idx_multi_obj', { object: 'idx_multi_obj' }); + const rows = await driver.find('idx_multi_obj', {}); expect(rows.length).toBe(2); }); @@ -402,7 +402,7 @@ describe('SqlDriver Schema Sync (SQLite)', () => { // Two rows with no dedup_key (NULL) must both be insertable. await driver.create('idx_null_obj', {}); await driver.create('idx_null_obj', {}); - const rows = await driver.find('idx_null_obj', { object: 'idx_null_obj' }); + const rows = await driver.find('idx_null_obj', {}); expect(rows.length).toBe(2); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-server-timing.test.ts b/packages/drivers/driver-sql/src/sql-driver-server-timing.test.ts index 6bb01bbf47..84dc093eee 100644 --- a/packages/drivers/driver-sql/src/sql-driver-server-timing.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-server-timing.test.ts @@ -44,7 +44,7 @@ describe('SqlDriver Server-Timing db span', () => { it('records a db mark with a query count for a real find()', async () => { const t = new PerfTiming(); const rows = await runWithPerfTiming(t, () => - driver.find('orders', { object: 'orders', where: { status: 'open' } }), + driver.find('orders', { where: { status: 'open' } }), ); expect(rows).toHaveLength(1); const mark = dbMark(t); diff --git a/packages/drivers/driver-sql/src/sql-driver-tenant-scope.test.ts b/packages/drivers/driver-sql/src/sql-driver-tenant-scope.test.ts index 74ecc4e15c..c6cfb4ca44 100644 --- a/packages/drivers/driver-sql/src/sql-driver-tenant-scope.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-tenant-scope.test.ts @@ -50,22 +50,22 @@ describe('SqlDriver tenant scope (organization_id)', () => { describe('find', () => { it('returns only the caller tenant rows when tenantId is set', async () => { - const rowsA = await driver.find('account', { object: 'account' }, { tenantId: 'org_a' }); - const rowsB = await driver.find('account', { object: 'account' }, { tenantId: 'org_b' }); + const rowsA = await driver.find('account', {}, { tenantId: 'org_a' }); + const rowsB = await driver.find('account', {}, { tenantId: 'org_b' }); expect(rowsA.map(r => r.id).sort()).toEqual(['a1', 'a2']); expect(rowsB.map(r => r.id).sort()).toEqual(['b1', 'b2']); }); it('is unscoped when no tenantId (admin path)', async () => { - const all = await driver.find('account', { object: 'account' }); + const all = await driver.find('account', {}); expect(all).toHaveLength(4); }); }); describe('findOne by id', () => { it('cannot read across tenants', async () => { - const own = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }, { tenantId: 'org_a' }); - const cross = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }, { tenantId: 'org_b' }); + const own = await driver.findOne('account', { where: { id: 'a1' } }, { tenantId: 'org_a' }); + const cross = await driver.findOne('account', { where: { id: 'a1' } }, { tenantId: 'org_b' }); expect(own?.id).toBe('a1'); expect(cross).toBeNull(); }); @@ -75,13 +75,13 @@ describe('SqlDriver tenant scope (organization_id)', () => { it('refuses to update a row owned by another tenant', async () => { // org_b tries to update org_a's a1 → no-op await driver.update('account', 'a1', { tier: 'compromised' }, { tenantId: 'org_b' }); - const a1 = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }); + const a1 = await driver.findOne('account', { where: { id: 'a1' } }); expect(a1.tier).toBe('gold'); }); it('updates own rows fine', async () => { await driver.update('account', 'a1', { tier: 'platinum' }, { tenantId: 'org_a' }); - const a1 = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }); + const a1 = await driver.findOne('account', { where: { id: 'a1' } }); expect(a1.tier).toBe('platinum'); }); }); @@ -89,15 +89,15 @@ describe('SqlDriver tenant scope (organization_id)', () => { describe('delete', () => { it('refuses to delete a row owned by another tenant', async () => { await driver.delete('account', 'a1', { tenantId: 'org_b' }); - const a1 = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }); + const a1 = await driver.findOne('account', { where: { id: 'a1' } }); expect(a1).not.toBeNull(); }); }); describe('count / aggregate', () => { it('count is scoped', async () => { - const a = await driver.count!('account', { object: 'account' }, { tenantId: 'org_a' }); - const b = await driver.count!('account', { object: 'account' }, { tenantId: 'org_b' }); + const a = await driver.count!('account', {}, { tenantId: 'org_a' }); + const b = await driver.count!('account', {}, { tenantId: 'org_b' }); expect(a).toBe(2); expect(b).toBe(2); }); @@ -114,7 +114,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { const visibleToB = await driver.findOne( 'account', - { object: 'account', where: { id: 'a3' } }, + { where: { id: 'a3' } }, { tenantId: 'org_b' }, ); expect(visibleToB).toBeNull(); @@ -139,8 +139,8 @@ describe('SqlDriver tenant scope (organization_id)', () => { }); it('a scoped read still sees the org-less row (any tenant)', async () => { - const rowsA = await driver.find('account', { object: 'account' }, { tenantId: 'org_a' }); - const rowsB = await driver.find('account', { object: 'account' }, { tenantId: 'org_b' }); + const rowsA = await driver.find('account', {}, { tenantId: 'org_a' }); + const rowsB = await driver.find('account', {}, { tenantId: 'org_b' }); expect(rowsA.map((r: any) => r.id)).toContain('g1'); expect(rowsB.map((r: any) => r.id)).toContain('g1'); // …while cross-tenant rows stay hidden exactly as before. @@ -148,12 +148,12 @@ describe('SqlDriver tenant scope (organization_id)', () => { }); it('a scoped by-id read resolves the global row', async () => { - const row = await driver.findOne('account', { object: 'account', where: { id: 'g1' } }, { tenantId: 'org_a' }); + const row = await driver.findOne('account', { where: { id: 'g1' } }, { tenantId: 'org_a' }); expect(row?.id).toBe('g1'); }); it('a scoped count includes the global row', async () => { - const a = await driver.count!('account', { object: 'account' }, { tenantId: 'org_a' }); + const a = await driver.count!('account', {}, { tenantId: 'org_a' }); expect(a).toBe(3); // a1, a2, g1 — never org_b's rows }); }); @@ -162,11 +162,11 @@ describe('SqlDriver tenant scope (organization_id)', () => { it('updateMany only touches caller tenant rows', async () => { await driver.updateMany!( 'account', - { object: 'account', where: { tier: 'gold' } }, + { where: { tier: 'gold' } }, { tier: 'gold-upgraded' }, { tenantId: 'org_a' }, ); - const all = await driver.find('account', { object: 'account' }); + const all = await driver.find('account', {}); const byId = Object.fromEntries(all.map(r => [r.id, r.tier])); expect(byId.a1).toBe('gold-upgraded'); expect(byId.b1).toBe('gold'); // untouched @@ -175,10 +175,10 @@ describe('SqlDriver tenant scope (organization_id)', () => { it('deleteMany only deletes caller tenant rows', async () => { await driver.deleteMany!( 'account', - { object: 'account', where: { tier: 'gold' } }, + { where: { tier: 'gold' } }, { tenantId: 'org_a' }, ); - const remaining = await driver.find('account', { object: 'account' }); + const remaining = await driver.find('account', {}); const ids = remaining.map(r => r.id).sort(); expect(ids).toEqual(['a2', 'b1', 'b2']); }); @@ -194,7 +194,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { ], { tenantId: 'org_a' }, ); - const rows = await driver.find('account', { object: 'account', where: { id: { $in: ['bc1', 'bc2'] } } }); + const rows = await driver.find('account', { where: { id: { $in: ['bc1', 'bc2'] } } }); expect(rows.every(r => r.organization_id === 'org_a')).toBe(true); }); }); @@ -215,7 +215,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { }, ]); await driver.create('global_flag', { id: 'g1', name: 'G1' }); - const rows = await driver.find('global_flag', { object: 'global_flag' }, { tenantId: 'org_a' }); + const rows = await driver.find('global_flag', {}, { tenantId: 'org_a' }); expect(rows).toHaveLength(1); }); }); @@ -271,7 +271,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { // …and it actually isolates, rather than merely being recorded. await driver.create('ticket', { id: 't1', subject: 'A' }, { tenantId: 'org_a' }); await driver.create('ticket', { id: 't2', subject: 'B' }, { tenantId: 'org_b' }); - const rowsA = await driver.find('ticket', { object: 'ticket' }, { tenantId: 'org_a' }); + const rowsA = await driver.find('ticket', {}, { tenantId: 'org_a' }); expect(rowsA.map((r) => r.id)).toEqual(['t1']); expect(rowsA[0].organization_id).toBe('org_a'); }); @@ -298,7 +298,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { ]); await driver.create('workspace_item', { id: 'w1', name: 'W1' }, { tenantId: 'ws_a' }); await driver.create('workspace_item', { id: 'w2', name: 'W2' }, { tenantId: 'ws_b' }); - const rowsA = await driver.find('workspace_item', { object: 'workspace_item' }, { tenantId: 'ws_a' }); + const rowsA = await driver.find('workspace_item', {}, { tenantId: 'ws_a' }); expect(rowsA.map(r => r.id)).toEqual(['w1']); expect(rowsA[0].workspace_id).toBe('ws_a'); }); @@ -342,13 +342,13 @@ describe('SqlDriver tenant scope (organization_id)', () => { }); it('read is unscoped even when the caller passes tenantId (admin with active org sees all)', async () => { - const adminRead = await driver.find('sys_license', { object: 'sys_license' }, { tenantId: 'org_admin_active' }); + const adminRead = await driver.find('sys_license', {}, { tenantId: 'org_admin_active' }); expect(adminRead.map(r => r.id).sort()).toEqual(['lic_global', 'lic_org_b']); }); it('matches the unscoped (anonymous) read — no auth-dependent divergence', async () => { - const scoped = await driver.find('sys_license', { object: 'sys_license' }, { tenantId: 'org_admin_active' }); - const unscoped = await driver.find('sys_license', { object: 'sys_license' }); + const scoped = await driver.find('sys_license', {}, { tenantId: 'org_admin_active' }); + const unscoped = await driver.find('sys_license', {}); expect(scoped.map(r => r.id).sort()).toEqual(unscoped.map(r => r.id).sort()); }); @@ -369,7 +369,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { fields: platformGlobal[0].fields, }); expect((driver as any).tenantFieldByTable['sys_license']).toBeNull(); - const adminRead = await driver.find('sys_license', { object: 'sys_license' }, { tenantId: 'org_admin_active' }); + const adminRead = await driver.find('sys_license', {}, { tenantId: 'org_admin_active' }); expect(adminRead.map(r => r.id).sort()).toEqual(['lic_global', 'lic_org_b']); }); @@ -446,7 +446,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { it('find with tenantIds spans exactly the listed tenants', async () => { const rows = await driver.find( 'account', - { object: 'account' }, + {}, { tenantId: 'org_a', tenantIds: ['org_a', 'org_b'] } as any, ); expect(rows.map(r => r.id).sort()).toEqual(['a1', 'a2', 'b1', 'b2']); @@ -456,7 +456,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { await driver.create('account', { id: 'c1', organization_id: 'org_c', name: 'C1' }); const rows = await driver.find( 'account', - { object: 'account' }, + {}, { tenantId: 'org_a', tenantIds: ['org_a', 'org_b'] } as any, ); expect(rows.map(r => r.id)).not.toContain('c1'); @@ -466,7 +466,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { await driver.create('account', { id: 'g1', name: 'GLOBAL' }); const rows = await driver.find( 'account', - { object: 'account' }, + {}, { tenantId: 'org_a', tenantIds: ['org_a'] } as any, ); expect(rows.map(r => r.id).sort()).toEqual(['a1', 'a2', 'g1']); @@ -475,13 +475,13 @@ describe('SqlDriver tenant scope (organization_id)', () => { it('an empty or malformed tenantIds falls back to tenantId equality (fail toward isolation)', async () => { const empty = await driver.find( 'account', - { object: 'account' }, + {}, { tenantId: 'org_a', tenantIds: [] } as any, ); expect(empty.map(r => r.id).sort()).toEqual(['a1', 'a2']); const malformed = await driver.find( 'account', - { object: 'account' }, + {}, { tenantId: 'org_a', tenantIds: [null, ''] } as any, ); expect(malformed.map(r => r.id).sort()).toEqual(['a1', 'a2']); @@ -490,12 +490,12 @@ describe('SqlDriver tenant scope (organization_id)', () => { it('update/delete reach widens with the set — but only within it', async () => { const unionOpts = { tenantId: 'org_a', tenantIds: ['org_a', 'org_b'] } as any; await driver.update('account', 'b1', { tier: 'platinum' }, unionOpts); - const b1 = await driver.findOne('account', { object: 'account', where: { id: 'b1' } }); + const b1 = await driver.findOne('account', { where: { id: 'b1' } }); expect(b1.tier).toBe('platinum'); // A tenant OUTSIDE the set stays untouchable — the widened wall still walls. await driver.create('account', { id: 'c1', organization_id: 'org_c', name: 'C1', tier: 'gold' }); await driver.update('account', 'c1', { tier: 'compromised' }, unionOpts); - const c1 = await driver.findOne('account', { object: 'account', where: { id: 'c1' } }); + const c1 = await driver.findOne('account', { where: { id: 'c1' } }); expect(c1.tier).toBe('gold'); }); @@ -505,7 +505,7 @@ describe('SqlDriver tenant scope (organization_id)', () => { { id: 'n1', name: 'New' }, { tenantId: 'org_a', tenantIds: ['org_a', 'org_b'] } as any, ); - const row = await driver.findOne('account', { object: 'account', where: { id: 'n1' } }); + const row = await driver.findOne('account', { where: { id: 'n1' } }); expect(row?.organization_id).toBe('org_a'); }); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-time-canonical-storage.test.ts b/packages/drivers/driver-sql/src/sql-driver-time-canonical-storage.test.ts index 5b407844b6..2c0ff526ab 100644 --- a/packages/drivers/driver-sql/src/sql-driver-time-canonical-storage.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-time-canonical-storage.test.ts @@ -81,7 +81,6 @@ describe('Field.time canonical writes (#3994)', () => { } const hits = await driver.find('shift', { - object: 'shift', where: { starts_at: { $gte: '09:00:00', $lte: '18:00:00' } }, orderBy: [{ field: 'id', order: 'asc' }], }); @@ -96,11 +95,11 @@ describe('Field.time canonical writes (#3994)', () => { await driver.create('shift', { id: 'a', label: 'a', starts_at: '14:30' }, { bypassTenantAudit: true }); await driver.create('shift', { id: 'b', label: 'b', starts_at: '14:30:00' }, { bypassTenantAudit: true }); - const hits = await driver.find('shift', { object: 'shift', where: { starts_at: '14:30:00' } }); + const hits = await driver.find('shift', { where: { starts_at: '14:30:00' } }); expect(hits.map((r: any) => r.id).sort()).toEqual(['a', 'b']); // And the comparand is canonicalised too — the minutes-only spelling // matches the same two rows. - const hits2 = await driver.find('shift', { object: 'shift', where: { starts_at: '14:30' } }); + const hits2 = await driver.find('shift', { where: { starts_at: '14:30' } }); expect(hits2.map((r: any) => r.id).sort()).toEqual(['a', 'b']); }); @@ -110,7 +109,7 @@ describe('Field.time canonical writes (#3994)', () => { for (const [id, v] of WRITE_SHAPES) { await driver.create('shift', { id, label: id, starts_at: v }, { bypassTenantAudit: true }); } - const rows = await driver.find('shift', { object: 'shift', orderBy: [{ field: 'starts_at', order: 'asc' }] }); + const rows = await driver.find('shift', { orderBy: [{ field: 'starts_at', order: 'asc' }] }); // 08:00 first; the `.500` rows after their `14:30:00` flat siblings // (`.` sorts below every digit, so lexicographic == chronological). expect((rows[0] as any).id).toBe('s_early'); @@ -162,7 +161,6 @@ describe('Field.time legacy storage: backfill and read-side repair (#3994)', () await seedLegacy(driver); const hits = await driver.find('shift', { - object: 'shift', where: { starts_at: { $gte: '09:00:00', $lte: '18:00:00' } }, orderBy: [{ field: 'id', order: 'asc' }], }); @@ -190,7 +188,6 @@ describe('Field.time legacy storage: backfill and read-side repair (#3994)', () // Converged storage means the plain indexable comparison now works. const hits = await driver.find('shift', { - object: 'shift', where: { starts_at: { $gte: '09:00:00', $lte: '18:00:00' } }, orderBy: [{ field: 'id', order: 'asc' }], }); diff --git a/packages/drivers/driver-sql/src/sql-driver-time-live-dialects.test.ts b/packages/drivers/driver-sql/src/sql-driver-time-live-dialects.test.ts index de74903ce7..5b982f8018 100644 --- a/packages/drivers/driver-sql/src/sql-driver-time-live-dialects.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-time-live-dialects.test.ts @@ -88,7 +88,7 @@ function suite(dialect: 'pg' | 'mysql', url: string | undefined) { await driver.create(TABLE, { id, label: id, starts_at: v }, { bypassTenantAudit: true }); } for (const [id, , presented] of WRITES) { - const row: any = await driver.findOne(TABLE, { object: TABLE, where: { id } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne(TABLE, { where: { id } }, { bypassTenantAudit: true }); expect(row.starts_at, id).toBe(presented); } }); @@ -101,14 +101,14 @@ function suite(dialect: 'pg' | 'mysql', url: string | undefined) { { id: 'd', label: 'd', starts_at: new Date(Date.UTC(2026, 0, 15, 14, 30, 0, 500)) }, { bypassTenantAudit: true }, ); - const row: any = await driver.findOne(TABLE, { object: TABLE, where: { id: 'd' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne(TABLE, { where: { id: 'd' } }, { bypassTenantAudit: true }); expect(row.starts_at).toBe('14:30:00.500'); }); it('keeps milliseconds — no zero-precision rounding to the next second', async () => { // MySQL's bare TIME would ROUND '14:30:00.500' up to 14:30:01. await driver.create(TABLE, { id: 'ms', label: 'ms', starts_at: '14:30:00.500' }, { bypassTenantAudit: true }); - const row: any = await driver.findOne(TABLE, { object: TABLE, where: { id: 'ms' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne(TABLE, { where: { id: 'ms' } }, { bypassTenantAudit: true }); expect(row.starts_at).toBe('14:30:00.500'); }); @@ -119,7 +119,6 @@ function suite(dialect: 'pg' | 'mysql', url: string | undefined) { await driver.create(TABLE, { id: 'early', label: 'e', starts_at: '08:00:00' }, { bypassTenantAudit: true }); const hits = await driver.find(TABLE, { - object: TABLE, where: { starts_at: { $gte: '09:00:00', $lte: '18:00:00' } }, orderBy: [{ field: 'id', order: 'asc' }], }); @@ -128,7 +127,7 @@ function suite(dialect: 'pg' | 'mysql', url: string | undefined) { it("a NOW()-default time column records the UTC time-of-day, not the server's or session's", async () => { await driver.create(TABLE, { id: 'now', label: 'n' }, { bypassTenantAudit: true }); - const row: any = await driver.findOne(TABLE, { object: TABLE, where: { id: 'now' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne(TABLE, { where: { id: 'now' } }, { bypassTenantAudit: true }); // A leak of the +08:00 server zone is ~480 minutes; of the -04:00/-05:00 // process zone, ~240-300. Genuine clock skew is seconds. expect(minutesOffUtc(String(row.auto_at))).toBeLessThan(5); @@ -189,11 +188,11 @@ describe.skipIf(!MY_URL)('MySQL TIME → TIME(3) widening (#3994)', () => { const colType = String((list[0] as any).COLUMN_TYPE ?? (list[0] as any).column_type).toLowerCase(); expect(colType).toBe('time(3)'); - const old: any = await driver.findOne(LEGACY, { object: LEGACY, where: { id: 'old' } }, { bypassTenantAudit: true }); + const old: any = await driver.findOne(LEGACY, { where: { id: 'old' } }, { bypassTenantAudit: true }); expect(old.starts_at).toBe('14:30:00'); // the wall clock must not move await driver.create(LEGACY, { id: 'ms', label: 'm', starts_at: '14:30:00.500' }, { bypassTenantAudit: true }); - const ms: any = await driver.findOne(LEGACY, { object: LEGACY, where: { id: 'ms' } }, { bypassTenantAudit: true }); + const ms: any = await driver.findOne(LEGACY, { where: { id: 'ms' } }, { bypassTenantAudit: true }); expect(ms.starts_at).toBe('14:30:00.500'); // pre-widen this ROUNDED to 14:30:01 }); diff --git a/packages/drivers/driver-sql/src/sql-driver-time-of-day.test.ts b/packages/drivers/driver-sql/src/sql-driver-time-of-day.test.ts index 72882e3283..64ee8ea5c0 100644 --- a/packages/drivers/driver-sql/src/sql-driver-time-of-day.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-time-of-day.test.ts @@ -49,13 +49,13 @@ describe('Field.time canonical presentation (time-of-day, SQLite)', () => { // default (or any full timestamp that leaked into the column), bypassing the // driver write path. await raw('shift').insert({ id: 'legacy', label: 'L', starts_at: '2026-01-15 14:30:00' }); - const row: any = await driver.findOne('shift', { object: 'shift', where: { id: 'legacy' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('shift', { where: { id: 'legacy' } }, { bypassTenantAudit: true }); expect(row.starts_at).toBe('14:30:00'); }); it('repairs a full-ISO value (with Z) in a time column to its time-of-day', async () => { await raw('shift').insert({ id: 'iso', label: 'I', starts_at: '2026-01-15T14:30:00.500Z' }); - const row: any = await driver.findOne('shift', { object: 'shift', where: { id: 'iso' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('shift', { where: { id: 'iso' } }, { bypassTenantAudit: true }); expect(row.starts_at).toBe('14:30:00.500'); }); @@ -71,7 +71,7 @@ describe('Field.time canonical presentation (time-of-day, SQLite)', () => { ['d', '09:05:30.250', '09:05:30.250'], ] as const) { await driver.create('shift', { id, label: id, starts_at: written }, { bypassTenantAudit: true }); - const row: any = await driver.findOne('shift', { object: 'shift', where: { id } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('shift', { where: { id } }, { bypassTenantAudit: true }); expect(row.starts_at).toBe(presented); } }); @@ -79,7 +79,7 @@ describe('Field.time canonical presentation (time-of-day, SQLite)', () => { it('a NOW()-default time column reads back a time-of-day, not a full timestamp', async () => { // `auto_at` omitted → the DDL default fires. await driver.create('shift', { id: 'd', label: 'D' }, { bypassTenantAudit: true }); - const row: any = await driver.findOne('shift', { object: 'shift', where: { id: 'd' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('shift', { where: { id: 'd' } }, { bypassTenantAudit: true }); expect(row.auto_at).toMatch(TIME_OF_DAY); expect(row.auto_at).not.toContain('-'); // not a `YYYY-MM-DD …` timestamp }); @@ -87,7 +87,7 @@ describe('Field.time canonical presentation (time-of-day, SQLite)', () => { it('find() (list path) normalizes time identically to findOne()', async () => { await raw('shift').insert({ id: 'l1', label: 'L1', starts_at: '2026-02-02 08:15:00' }); await driver.create('shift', { id: 'l2', label: 'L2', starts_at: '08:15:00' }, { bypassTenantAudit: true }); - const rows = await driver.find('shift', { object: 'shift', orderBy: [{ field: 'id', order: 'asc' }] }); + const rows = await driver.find('shift', { orderBy: [{ field: 'id', order: 'asc' }] }); const byId = Object.fromEntries(rows.map((r: any) => [r.id, r])); expect(byId.l1.starts_at).toBe('08:15:00'); // legacy full-timestamp repaired expect(byId.l2.starts_at).toBe('08:15:00'); // canonical write round-trips @@ -95,7 +95,7 @@ describe('Field.time canonical presentation (time-of-day, SQLite)', () => { it('leaves null untouched', async () => { await driver.create('shift', { id: 'n', label: 'N', starts_at: null }, { bypassTenantAudit: true }); - const row: any = await driver.findOne('shift', { object: 'shift', where: { id: 'n' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('shift', { where: { id: 'n' } }, { bypassTenantAudit: true }); expect(row.starts_at).toBeNull(); }); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-timestamp-format.test.ts b/packages/drivers/driver-sql/src/sql-driver-timestamp-format.test.ts index 8559f4de66..88e094a5b7 100644 --- a/packages/drivers/driver-sql/src/sql-driver-timestamp-format.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-timestamp-format.test.ts @@ -143,7 +143,7 @@ describe('SqlDriver canonical audit-timestamp format (SQLite)', () => { // Simulate a row written by the OLD update stamp / CURRENT_TIMESTAMP default, // bypassing the driver write path entirely. await raw('thing').insert({ id: 'legacy', name: 'L', created_at: '2026-01-15 08:30:00', updated_at: '2026-01-15 08:30:00.246' }); - const row: any = await driver.findOne('thing', { object: 'thing', where: { id: 'legacy' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('thing', { where: { id: 'legacy' } }, { bypassTenantAudit: true }); expect(row.created_at).toBe('2026-01-15T08:30:00.000Z'); expect(row.updated_at).toBe('2026-01-15T08:30:00.246Z'); }); @@ -151,14 +151,14 @@ describe('SqlDriver canonical audit-timestamp format (SQLite)', () => { it('REGRESSION (freshness probe): the repaired instant equals the UTC wall-clock, host-timezone-independent', async () => { // The zone-naive '2026-01-15 08:30:00' must mean 08:30 UTC, NOT 08:30 local. await raw('thing').insert({ id: 'fr', name: 'F', created_at: '2026-01-15 08:30:00', updated_at: '2026-01-15 08:30:00' }); - const row: any = await driver.findOne('thing', { object: 'thing', where: { id: 'fr' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('thing', { where: { id: 'fr' } }, { bypassTenantAudit: true }); expect(new Date(row.updated_at as string).getTime()).toBe(Date.parse('2026-01-15T08:30:00.000Z')); }); it('read-repair is idempotent: an already-canonical value is returned unchanged', async () => { const canonical = '2026-02-02T02:02:02.222Z'; await raw('thing').insert({ id: 'canon', name: 'C', created_at: canonical, updated_at: canonical }); - const row: any = await driver.findOne('thing', { object: 'thing', where: { id: 'canon' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('thing', { where: { id: 'canon' } }, { bypassTenantAudit: true }); expect(row.created_at).toBe(canonical); expect(row.updated_at).toBe(canonical); }); @@ -175,7 +175,7 @@ describe('SqlDriver canonical audit-timestamp format (SQLite)', () => { try { await dd.initObjects([{ name: 'evt', fields: { created_at: { type: 'datetime' }, label: { type: 'string' } } }]); await dd.create('evt', { id: 'e1', label: 'x', created_at: new Date('2026-04-04T04:04:04.004Z') }, { bypassTenantAudit: true }); - const row: any = await dd.findOne('evt', { object: 'evt', where: { id: 'e1' } }, { bypassTenantAudit: true }); + const row: any = await dd.findOne('evt', { where: { id: 'e1' } }, { bypassTenantAudit: true }); expect(typeof row.created_at).toBe('string'); expect(row.created_at).toBe('2026-04-04T04:04:04.004Z'); } finally { diff --git a/packages/drivers/driver-sql/src/sql-driver-undefined-comparand-refusal.test.ts b/packages/drivers/driver-sql/src/sql-driver-undefined-comparand-refusal.test.ts index 3a95c63fec..167eccd443 100644 --- a/packages/drivers/driver-sql/src/sql-driver-undefined-comparand-refusal.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-undefined-comparand-refusal.test.ts @@ -105,7 +105,6 @@ describe('[#6050] SqlDriver refuses an undefined comparand', () => { const ids = async (where: unknown): Promise => { const rows = await driver.find('deal', { - object: 'deal', fields: ['id'], where: where as FilterCondition, }); diff --git a/packages/drivers/driver-sql/src/sql-driver-unique-tenancy.test.ts b/packages/drivers/driver-sql/src/sql-driver-unique-tenancy.test.ts index 1adfa38ca1..78e85311b8 100644 --- a/packages/drivers/driver-sql/src/sql-driver-unique-tenancy.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-unique-tenancy.test.ts @@ -355,7 +355,7 @@ describe('SqlDriver unique × tenancy (#3696)', () => { expect(Object.values(uniques)).toContainEqual(['COALESCE(organization_id)', 'code']); // Existing data survived, and the cross-tenant insert now works. - expect(await driver.count('product', { object: 'product' })).toBe(2); + expect(await driver.count('product', {})).toBe(2); const b = await driver.create('product', { organization_id: 'org_b', code: 'PROD-00001' }); expect(b.code).toBe('PROD-00001'); }); @@ -458,7 +458,7 @@ describe('SqlDriver unique × tenancy (#3696)', () => { expect(Object.values(uniques)).not.toContainEqual(['organization_id', 'code']); // Data preserved, and cross-tenant reuse still works post-rebuild. - expect(await driver.count('product', { object: 'product' })).toBe(1); + expect(await driver.count('product', {})).toBe(1); const b = await driver.create('product', { organization_id: 'org_b', code: 'C1', note: 'n' }); expect(b.code).toBe('C1'); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-unknown-column-recovery.test.ts b/packages/drivers/driver-sql/src/sql-driver-unknown-column-recovery.test.ts index 33317e2a55..33702a60d1 100644 --- a/packages/drivers/driver-sql/src/sql-driver-unknown-column-recovery.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-unknown-column-recovery.test.ts @@ -93,6 +93,6 @@ describe('SqlDriver find() recovers from unknown columns (objectstack#3821)', () }); it('propagates errors that are not about an unknown column', async () => { - await expect(driver.find('no_such_table', { object: 'no_such_table' })).rejects.toThrow(); + await expect(driver.find('no_such_table', {})).rejects.toThrow(); }); }); diff --git a/packages/drivers/driver-sql/src/sql-driver-user-datetime-default-format.test.ts b/packages/drivers/driver-sql/src/sql-driver-user-datetime-default-format.test.ts index 040c66508d..e7ef7a60ff 100644 --- a/packages/drivers/driver-sql/src/sql-driver-user-datetime-default-format.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver-user-datetime-default-format.test.ts @@ -102,7 +102,7 @@ describe('User NOW()-default temporal fields — canonical format (SQLite)', () expect(typeof rawRow.starts_at).toBe('string'); expect(rawRow.starts_at).toBe('2026-03-20T12:34:56.789Z'); - const row: any = await driver.findOne('event', { object: 'event', where: { id: 'e3' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('event', { where: { id: 'e3' } }, { bypassTenantAudit: true }); expect(row.starts_at).toBe(rawRow.starts_at); }); @@ -123,7 +123,7 @@ describe('User NOW()-default temporal fields — canonical format (SQLite)', () expect(sorted.map((r: any) => r.id)).toEqual(['explicit', 'defaulted']); for (const id of ['explicit', 'defaulted']) { - const row: any = await driver.findOne('event', { object: 'event', where: { id } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('event', { where: { id } }, { bypassTenantAudit: true }); expect(row.starts_at).toMatch(ISO_Z); expect(Number.isNaN(new Date(row.starts_at).getTime())).toBe(false); } @@ -137,14 +137,14 @@ describe('User NOW()-default temporal fields — canonical format (SQLite)', () const rawRow = await raw('event').where('id', 'legacy').first(); expect(typeof rawRow.starts_at).toBe('number'); - const row: any = await driver.findOne('event', { object: 'event', where: { id: 'legacy' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('event', { where: { id: 'legacy' } }, { bypassTenantAudit: true }); expect(row.starts_at).toBe('2026-03-20T12:34:56.789Z'); }); it('an explicit ISO-8601-Z string is preserved (idempotent) on read', async () => { const iso = '2026-05-25T08:00:00.000Z'; await driver.create('event', { id: 'e4', label: 'D', starts_at: iso }, { bypassTenantAudit: true }); - const row: any = await driver.findOne('event', { object: 'event', where: { id: 'e4' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('event', { where: { id: 'e4' } }, { bypassTenantAudit: true }); expect(row.starts_at).toBe(iso); }); @@ -154,21 +154,21 @@ describe('User NOW()-default temporal fields — canonical format (SQLite)', () // A row written before this fix (or by a raw insert that took the OLD naive // `CURRENT_TIMESTAMP` default), bypassing the driver write path entirely. await raw('event').insert({ id: 'legacy', label: 'L', starts_at: '2026-01-15 08:30:00' }); - const row: any = await driver.findOne('event', { object: 'event', where: { id: 'legacy' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('event', { where: { id: 'legacy' } }, { bypassTenantAudit: true }); expect(row.starts_at).toBe('2026-01-15T08:30:00.000Z'); }); it('REGRESSION (host-timezone independence): the repaired instant equals the UTC wall-clock', async () => { // The zone-naive `2026-01-15 08:30:00` must mean 08:30 UTC, NOT 08:30 local. await raw('event').insert({ id: 'tz', label: 'T', starts_at: '2026-01-15 08:30:00' }); - const row: any = await driver.findOne('event', { object: 'event', where: { id: 'tz' } }, { bypassTenantAudit: true }); + const row: any = await driver.findOne('event', { where: { id: 'tz' } }, { bypassTenantAudit: true }); expect(new Date(row.starts_at).getTime()).toBe(Date.parse('2026-01-15T08:30:00.000Z')); }); it('find() (list path) normalizes datetime identically to findOne(), across mixed storage', async () => { await raw('event').insert({ id: 'list1', label: 'L1', starts_at: '2026-02-02 02:02:02.200' }); await driver.create('event', { id: 'list2', label: 'L2', starts_at: new Date('2026-02-02T02:02:02.200Z') }, { bypassTenantAudit: true }); - const rows = await driver.find('event', { object: 'event', orderBy: [{ field: 'id', order: 'asc' }] }); + const rows = await driver.find('event', { orderBy: [{ field: 'id', order: 'asc' }] }); const byId = Object.fromEntries(rows.map((r: any) => [r.id, r])); expect(byId.list1.starts_at).toBe('2026-02-02T02:02:02.200Z'); expect(byId.list2.starts_at).toBe('2026-02-02T02:02:02.200Z'); @@ -179,7 +179,7 @@ describe('User NOW()-default temporal fields — canonical format (SQLite)', () try { await d2.initObjects([{ name: 'evt2', fields: { dt: { type: 'datetime' }, label: { type: 'string' } } }]); await d2.create('evt2', { id: 'n1', label: 'N', dt: null }, { bypassTenantAudit: true }); - const row: any = await d2.findOne('evt2', { object: 'evt2', where: { id: 'n1' } }, { bypassTenantAudit: true }); + const row: any = await d2.findOne('evt2', { where: { id: 'n1' } }, { bypassTenantAudit: true }); expect(row.dt).toBeNull(); } finally { await d2.disconnect(); diff --git a/packages/drivers/driver-sql/src/sql-driver.test.ts b/packages/drivers/driver-sql/src/sql-driver.test.ts index 86c61ca640..14b79554c0 100644 --- a/packages/drivers/driver-sql/src/sql-driver.test.ts +++ b/packages/drivers/driver-sql/src/sql-driver.test.ts @@ -40,7 +40,6 @@ describe('SqlDriver (SQLite Integration)', () => { it('should find objects with filters', async () => { const results = await driver.find('users', { - object: 'users', fields: ['name', 'age'], where: { age: { $gt: 18 } }, orderBy: [{ field: 'name', order: 'asc' }], @@ -55,7 +54,6 @@ describe('SqlDriver (SQLite Integration)', () => { // image, ...) the object may not have. The unknown column must NOT zero // the whole result — the real rows still come back, minus the phantom field. const results = await driver.find('users', { - object: 'users', fields: ['id', 'name', 'status', 'due_date', 'image'], orderBy: [{ field: 'name', order: 'asc' }], }); @@ -68,13 +66,12 @@ describe('SqlDriver (SQLite Integration)', () => { it('still surfaces non-column errors (e.g. unknown table) instead of empty', async () => { await expect( - driver.find('no_such_table', { object: 'no_such_table', fields: ['id'] }), + driver.find('no_such_table', { fields: ['id'] }), ).rejects.toThrow(); }); it('should apply simple AND/OR logic', async () => { const results = await driver.find('users', { - object: 'users', where: { $or: [{ age: 17 }, { age: { $gt: 29 } }], }, @@ -84,10 +81,10 @@ describe('SqlDriver (SQLite Integration)', () => { }); it('should find one object by id', async () => { - const [alice] = await driver.find('users', { object: 'users', where: { name: 'Alice' } }); + const [alice] = await driver.find('users', { where: { name: 'Alice' } }); expect(alice).toBeDefined(); - const fetched = await driver.findOne('users', { object: 'users', where: { id: alice.id } }); + const fetched = await driver.findOne('users', { where: { id: alice.id } }); expect(fetched).toBeDefined(); expect(fetched.name).toBe('Alice'); }); @@ -95,29 +92,29 @@ describe('SqlDriver (SQLite Integration)', () => { it('should create an object', async () => { await driver.create('users', { name: 'Eve', age: 22 }); - const [eve] = await driver.find('users', { object: 'users', where: { name: 'Eve' } }); + const [eve] = await driver.find('users', { where: { name: 'Eve' } }); expect(eve).toBeDefined(); expect(eve.age).toBe(22); }); it('should update an object', async () => { - const [bob] = await driver.find('users', { object: 'users', where: { name: 'Bob' } }); + const [bob] = await driver.find('users', { where: { name: 'Bob' } }); await driver.update('users', bob.id, { age: 18 }); - const updated = await driver.findOne('users', { object: 'users', where: { id: bob.id } }); + const updated = await driver.findOne('users', { where: { id: bob.id } }); expect(updated.age).toBe(18); }); it('should delete an object', async () => { - const [charlie] = await driver.find('users', { object: 'users', where: { name: 'Charlie' } }); + const [charlie] = await driver.find('users', { where: { name: 'Charlie' } }); await driver.delete('users', charlie.id); - const deleted = await driver.findOne('users', { object: 'users', where: { id: charlie.id } }); + const deleted = await driver.findOne('users', { where: { id: charlie.id } }); expect(deleted).toBeNull(); }); it('should count objects', async () => { - const count = await driver.count('users', { object: 'users', where: { age: 17 } }); + const count = await driver.count('users', { where: { age: 17 } }); expect(count).toBe(2); }); @@ -125,7 +122,7 @@ describe('SqlDriver (SQLite Integration)', () => { const created = await driver.create('users', { _id: 'custom-id', name: 'Frank', age: 40 }); expect(created.id).toBe('custom-id'); - const fetched = await driver.findOne('users', { object: 'users', where: { id: 'custom-id' } }); + const fetched = await driver.findOne('users', { where: { id: 'custom-id' } }); expect(fetched).toBeDefined(); expect(fetched.name).toBe('Frank'); }); diff --git a/packages/drivers/driver-sql/src/sql-driver.ts b/packages/drivers/driver-sql/src/sql-driver.ts index 9b493fd8f9..85e716fb25 100644 --- a/packages/drivers/driver-sql/src/sql-driver.ts +++ b/packages/drivers/driver-sql/src/sql-driver.ts @@ -7,7 +7,7 @@ * Supports PostgreSQL, MySQL, SQLite, and other SQL databases. */ -import type { QueryAST, DriverOptions, SchemaMode } from '@objectstack/spec/data'; +import type { DriverOptions, SchemaMode } from '@objectstack/spec/data'; import { parseAutonumberFormat, renderAutonumber, missingFieldValues, isTenancyDisabled, type AutonumberToken } from '@objectstack/spec/data'; import { STRUCTURED_JSON_TYPES, FILE_REFERENCE_TYPES, MULTI_OPTION_TYPES, NUMERIC_VALUE_TYPES } from '@objectstack/spec/data'; // `defaultValue` runtime tokens (#4560). The DDL below asks the SPEC — not a @@ -15,7 +15,7 @@ import { STRUCTURED_JSON_TYPES, FILE_REFERENCE_TYPES, MULTI_OPTION_TYPES, NUMERI // so the engine and this driver can never disagree about what may become a // physical column DEFAULT. import { isNowDefaultToken, isRuntimeDefaultToken } from '@objectstack/spec/data'; -import type { IDataDriver } from '@objectstack/spec/contracts'; +import type { DriverQuery, IDataDriver } from '@objectstack/spec/contracts'; import { StandardErrorCode } from '@objectstack/spec/api'; import { StorageNameMapping } from '@objectstack/spec/system'; import { ExternalSchemaModeViolationError } from '@objectstack/spec/shared'; @@ -2293,7 +2293,7 @@ export class SqlDriver implements IDataDriver { // CRUD — IDataDriver core // =================================== - async find(object: string, query: QueryAST, options?: DriverOptions): Promise { + async find(object: string, query: DriverQuery, options?: DriverOptions): Promise { return this.findRows(object, query, options); } @@ -2326,7 +2326,7 @@ export class SqlDriver implements IDataDriver { */ private async findRows( object: string, - query: QueryAST, + query: DriverQuery, options?: DriverOptions, singleRowLookup = false, ): Promise { @@ -2453,7 +2453,7 @@ export class SqlDriver implements IDataDriver { * `singleRowLookup` ORDER BY decision). * Spell an id lookup as what it is: `{ object, where: { id } }`. */ - async findOne(object: string, query: QueryAST, options?: DriverOptions): Promise { + async findOne(object: string, query: DriverQuery, options?: DriverOptions): Promise { if (!query || typeof query !== 'object') return null; const results = await this.findRows(object, { ...query, limit: 1 }, options, true); return results[0] || null; @@ -3027,7 +3027,7 @@ export class SqlDriver implements IDataDriver { } } - async updateMany(object: string, query: QueryAST, data: any, options?: DriverOptions): Promise { + async updateMany(object: string, query: DriverQuery, data: any, options?: DriverOptions): Promise { this.auditMissingTenant(object, 'updateMany', options); let total = 0; for (const target of this.rotationShardsOf(object) ?? [object]) { @@ -3039,7 +3039,7 @@ export class SqlDriver implements IDataDriver { return total; } - async deleteMany(object: string, query: QueryAST, options?: DriverOptions): Promise { + async deleteMany(object: string, query: DriverQuery, options?: DriverOptions): Promise { this.auditMissingTenant(object, 'deleteMany', options); let total = 0; for (const target of this.rotationShardsOf(object) ?? [object]) { @@ -3078,7 +3078,7 @@ export class SqlDriver implements IDataDriver { return null; } - async count(object: string, query?: QueryAST, options?: DriverOptions): Promise { + async count(object: string, query?: DriverQuery, options?: DriverOptions): Promise { const builder = this.getBuilder(object, options); this.applyTenantScope(builder, object, options); @@ -3402,7 +3402,7 @@ export class SqlDriver implements IDataDriver { // =================================== /** IDataDriver standard: analyze query performance */ - async explain(object: string, query: any, options?: DriverOptions): Promise { + async explain(object: string, query: DriverQuery, options?: DriverOptions): Promise { return this.analyzeQuery(object, query, options); } @@ -6990,7 +6990,7 @@ export class SqlDriver implements IDataDriver { */ protected orderKeysFor( object: string, - query: QueryAST, + query: DriverQuery, opts?: { singleRowLookup?: boolean }, ): Array<{ field: string; direction: 'asc' | 'desc' }> { const keys: Array<{ field: string; direction: 'asc' | 'desc' }> = []; diff --git a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-advanced.test.ts b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-advanced.test.ts index 0af0b73bb0..402855c24b 100644 --- a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-advanced.test.ts +++ b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-advanced.test.ts @@ -135,33 +135,33 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { expect(result).toBeDefined(); expect(result.length).toBe(3); - const count = await driver.count('orders', { object: 'orders' }); + const count = await driver.count('orders', {}); expect(count).toBe(8); }); it('should update many records', async () => { - const result = await driver.updateMany('orders', { object: 'orders', where: { status: 'pending' } }, { status: 'processing' }); + const result = await driver.updateMany('orders', { where: { status: 'pending' } }, { status: 'processing' }); expect(result).toBeGreaterThan(0); - const results = await driver.find('orders', { object: 'orders', where: { status: 'processing' } }); + const results = await driver.find('orders', { where: { status: 'processing' } }); expect(results.length).toBe(1); }); it('should delete many records', async () => { - const result = await driver.deleteMany('orders', { object: 'orders', where: { status: 'cancelled' } }); + const result = await driver.deleteMany('orders', { where: { status: 'cancelled' } }); expect(result).toBe(1); - const remaining = await driver.count('orders', { object: 'orders' }); + const remaining = await driver.count('orders', {}); expect(remaining).toBe(4); }); it('should handle empty bulk update and delete', async () => { - const result = await driver.updateMany('orders', { object: 'orders', where: { status: 'nonexistent' } }, { status: 'updated' }); + const result = await driver.updateMany('orders', { where: { status: 'nonexistent' } }, { status: 'updated' }); expect(result).toBe(0); - const deleteResult = await driver.deleteMany('orders', { object: 'orders', where: { id: 'nonexistent' } }); + const deleteResult = await driver.deleteMany('orders', { where: { id: 'nonexistent' } }); expect(deleteResult).toBe(0); }); }); @@ -186,7 +186,7 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { await driver.commitTransaction(trx); - const result = await driver.findOne('orders', { object: 'orders', where: { id: 'trx1' } }); + const result = await driver.findOne('orders', { where: { id: 'trx1' } }); expect(result).toBeDefined(); expect(result.customer).toBe('TxUser'); } catch (e) { @@ -214,7 +214,7 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { await driver.rollbackTransaction(trx); - const result = await driver.findOne('orders', { object: 'orders', where: { id: 'trx2' } }); + const result = await driver.findOne('orders', { where: { id: 'trx2' } }); expect(result).toBeNull(); } catch (e) { await driver.rollbackTransaction(trx); @@ -245,13 +245,13 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { await driver.commitTransaction(trx); - const created = await driver.findOne('orders', { object: 'orders', where: { id: 'trx3' } }); + const created = await driver.findOne('orders', { where: { id: 'trx3' } }); expect(created).toBeDefined(); - const updated = await driver.findOne('orders', { object: 'orders', where: { id: '1' } }); + const updated = await driver.findOne('orders', { where: { id: '1' } }); expect(updated.status).toBe('shipped'); - const deleted = await driver.findOne('orders', { object: 'orders', where: { id: '5' } }); + const deleted = await driver.findOne('orders', { where: { id: '5' } }); expect(deleted).toBeNull(); } catch (e) { await driver.rollbackTransaction(trx); @@ -262,12 +262,12 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { describe('Edge Cases and Error Handling', () => { it('should handle empty filters gracefully', async () => { - const results = await driver.find('orders', { object: 'orders', where: {} }); + const results = await driver.find('orders', { where: {} }); expect(results.length).toBe(5); }); it('should handle undefined query parameters', async () => { - const results = await driver.find('orders', { object: 'orders' }); + const results = await driver.find('orders', {}); expect(results.length).toBe(5); }); @@ -280,7 +280,7 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { await driver.create('nullable_test', { id: '1', name: null, value: null }); - const result = await driver.findOne('nullable_test', { object: 'nullable_test', where: { id: '1' } }); + const result = await driver.findOne('nullable_test', { where: { id: '1' } }); expect(result).toBeDefined(); expect(result.name).toBeNull(); expect(result.value).toBeNull(); @@ -288,7 +288,6 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { it('should handle pagination with offset and limit', async () => { const page1 = await driver.find('orders', { - object: 'orders', orderBy: [{ field: 'id', order: 'asc' }], offset: 0, limit: 2, @@ -297,7 +296,6 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { expect(page1[0].id).toBe('1'); const page2 = await driver.find('orders', { - object: 'orders', orderBy: [{ field: 'id', order: 'asc' }], offset: 2, limit: 2, @@ -307,13 +305,12 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { }); it('should handle offset beyond total records', async () => { - const results = await driver.find('orders', { object: 'orders', offset: 100, limit: 10 }); + const results = await driver.find('orders', { offset: 100, limit: 10 }); expect(results.length).toBe(0); }); it('should handle complex nested filters', async () => { const results = await driver.find('orders', { - object: 'orders', where: { $or: [ { $and: [{ status: 'completed' }, { amount: { $gt: 100 } }] }, @@ -327,7 +324,6 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { it('should handle contains filter', async () => { const results = await driver.find('orders', { - object: 'orders', where: { product: { $contains: 'top' } }, }); @@ -337,7 +333,6 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { it('should handle in filter', async () => { const results = await driver.find('orders', { - object: 'orders', where: { status: { $in: ['completed', 'pending'] } }, }); @@ -346,7 +341,6 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { it('should handle nin (not in) filter', async () => { const results = await driver.find('orders', { - object: 'orders', where: { status: { $nin: ['cancelled'] } }, }); @@ -354,14 +348,14 @@ describe('SqliteWasmDriver Advanced Operations (SQLite)', () => { }); it('should handle findOne with query parameter', async () => { - const result = await driver.findOne('orders', { object: 'orders', where: { customer: 'Charlie' } }); + const result = await driver.findOne('orders', { where: { customer: 'Charlie' } }); expect(result).toBeDefined(); expect(result.customer).toBe('Charlie'); }); it('should return null for non-existent record', async () => { - const result = await driver.findOne('orders', { object: 'orders', where: { id: 'nonexistent' } }); + const result = await driver.findOne('orders', { where: { id: 'nonexistent' } }); expect(result).toBeNull(); }); diff --git a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-schema.test.ts b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-schema.test.ts index d5a270d1ac..9ae8c49634 100644 --- a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-schema.test.ts +++ b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-schema.test.ts @@ -127,7 +127,7 @@ describe('SqliteWasmDriver Schema Sync (SQLite)', () => { users: ['u1', 'u2'], }); - const results = await driver.find('multi_test', { object: 'multi_test' }); + const results = await driver.find('multi_test', {}); const row = results[0]; expect(row.tags).toEqual(['a', 'b']); @@ -150,7 +150,7 @@ describe('SqliteWasmDriver Schema Sync (SQLite)', () => { expect(columns).toHaveProperty('completion'); await driver.create('percent_test', { completion: 0.85 }); - const res = await driver.find('percent_test', { object: 'percent_test' }); + const res = await driver.find('percent_test', {}); expect(res[0].completion).toBe(0.85); }); @@ -244,7 +244,7 @@ describe('SqliteWasmDriver Schema Sync (SQLite)', () => { work_hours: '09:00:00', }); - const res = await driver.find('new_types_test', { object: 'new_types_test' }); + const res = await driver.find('new_types_test', {}); const row = res[0]; expect(row.email).toBe('test@example.com'); diff --git a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-tenant-scope.test.ts b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-tenant-scope.test.ts index 38a995e985..adee7049d4 100644 --- a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-tenant-scope.test.ts +++ b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-tenant-scope.test.ts @@ -45,22 +45,22 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { describe('find', () => { it('returns only the caller tenant rows when tenantId is set', async () => { - const rowsA = await driver.find('account', { object: 'account' }, { tenantId: 'org_a' }); - const rowsB = await driver.find('account', { object: 'account' }, { tenantId: 'org_b' }); + const rowsA = await driver.find('account', {}, { tenantId: 'org_a' }); + const rowsB = await driver.find('account', {}, { tenantId: 'org_b' }); expect(rowsA.map(r => r.id).sort()).toEqual(['a1', 'a2']); expect(rowsB.map(r => r.id).sort()).toEqual(['b1', 'b2']); }); it('is unscoped when no tenantId (admin path)', async () => { - const all = await driver.find('account', { object: 'account' }); + const all = await driver.find('account', {}); expect(all).toHaveLength(4); }); }); describe('findOne by id', () => { it('cannot read across tenants', async () => { - const own = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }, { tenantId: 'org_a' }); - const cross = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }, { tenantId: 'org_b' }); + const own = await driver.findOne('account', { where: { id: 'a1' } }, { tenantId: 'org_a' }); + const cross = await driver.findOne('account', { where: { id: 'a1' } }, { tenantId: 'org_b' }); expect(own?.id).toBe('a1'); expect(cross).toBeNull(); }); @@ -70,13 +70,13 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { it('refuses to update a row owned by another tenant', async () => { // org_b tries to update org_a's a1 → no-op await driver.update('account', 'a1', { tier: 'compromised' }, { tenantId: 'org_b' }); - const a1 = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }); + const a1 = await driver.findOne('account', { where: { id: 'a1' } }); expect(a1.tier).toBe('gold'); }); it('updates own rows fine', async () => { await driver.update('account', 'a1', { tier: 'platinum' }, { tenantId: 'org_a' }); - const a1 = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }); + const a1 = await driver.findOne('account', { where: { id: 'a1' } }); expect(a1.tier).toBe('platinum'); }); }); @@ -84,15 +84,15 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { describe('delete', () => { it('refuses to delete a row owned by another tenant', async () => { await driver.delete('account', 'a1', { tenantId: 'org_b' }); - const a1 = await driver.findOne('account', { object: 'account', where: { id: 'a1' } }); + const a1 = await driver.findOne('account', { where: { id: 'a1' } }); expect(a1).not.toBeNull(); }); }); describe('count / aggregate', () => { it('count is scoped', async () => { - const a = await driver.count!('account', { object: 'account' }, { tenantId: 'org_a' }); - const b = await driver.count!('account', { object: 'account' }, { tenantId: 'org_b' }); + const a = await driver.count!('account', {}, { tenantId: 'org_a' }); + const b = await driver.count!('account', {}, { tenantId: 'org_b' }); expect(a).toBe(2); expect(b).toBe(2); }); @@ -109,7 +109,7 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { const visibleToB = await driver.findOne( 'account', - { object: 'account', where: { id: 'a3' } }, + { where: { id: 'a3' } }, { tenantId: 'org_b' }, ); expect(visibleToB).toBeNull(); @@ -130,11 +130,11 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { it('updateMany only touches caller tenant rows', async () => { await driver.updateMany!( 'account', - { object: 'account', where: { tier: 'gold' } }, + { where: { tier: 'gold' } }, { tier: 'gold-upgraded' }, { tenantId: 'org_a' }, ); - const all = await driver.find('account', { object: 'account' }); + const all = await driver.find('account', {}); const byId = Object.fromEntries(all.map(r => [r.id, r.tier])); expect(byId.a1).toBe('gold-upgraded'); expect(byId.b1).toBe('gold'); // untouched @@ -143,10 +143,10 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { it('deleteMany only deletes caller tenant rows', async () => { await driver.deleteMany!( 'account', - { object: 'account', where: { tier: 'gold' } }, + { where: { tier: 'gold' } }, { tenantId: 'org_a' }, ); - const remaining = await driver.find('account', { object: 'account' }); + const remaining = await driver.find('account', {}); const ids = remaining.map(r => r.id).sort(); expect(ids).toEqual(['a2', 'b1', 'b2']); }); @@ -162,7 +162,7 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { ], { tenantId: 'org_a' }, ); - const rows = await driver.find('account', { object: 'account', where: { id: { $in: ['bc1', 'bc2'] } } }); + const rows = await driver.find('account', { where: { id: { $in: ['bc1', 'bc2'] } } }); expect(rows.every(r => r.organization_id === 'org_a')).toBe(true); }); }); @@ -179,7 +179,7 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { }, ]); await driver.create('global_flag', { id: 'g1', name: 'G1' }); - const rows = await driver.find('global_flag', { object: 'global_flag' }, { tenantId: 'org_a' }); + const rows = await driver.find('global_flag', {}, { tenantId: 'org_a' }); expect(rows).toHaveLength(1); }); }); @@ -201,7 +201,7 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { ]); await driver.create('workspace_item', { id: 'w1', name: 'W1' }, { tenantId: 'ws_a' }); await driver.create('workspace_item', { id: 'w2', name: 'W2' }, { tenantId: 'ws_b' }); - const rowsA = await driver.find('workspace_item', { object: 'workspace_item' }, { tenantId: 'ws_a' }); + const rowsA = await driver.find('workspace_item', {}, { tenantId: 'ws_a' }); expect(rowsA.map(r => r.id)).toEqual(['w1']); expect(rowsA[0].workspace_id).toBe('ws_a'); }); @@ -241,13 +241,13 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { }); it('read is unscoped even when the caller passes tenantId (admin with active org sees all)', async () => { - const adminRead = await driver.find('sys_license', { object: 'sys_license' }, { tenantId: 'org_admin_active' }); + const adminRead = await driver.find('sys_license', {}, { tenantId: 'org_admin_active' }); expect(adminRead.map(r => r.id).sort()).toEqual(['lic_global', 'lic_org_b']); }); it('matches the unscoped (anonymous) read — no auth-dependent divergence', async () => { - const scoped = await driver.find('sys_license', { object: 'sys_license' }, { tenantId: 'org_admin_active' }); - const unscoped = await driver.find('sys_license', { object: 'sys_license' }); + const scoped = await driver.find('sys_license', {}, { tenantId: 'org_admin_active' }); + const unscoped = await driver.find('sys_license', {}); expect(scoped.map(r => r.id).sort()).toEqual(unscoped.map(r => r.id).sort()); }); @@ -266,7 +266,7 @@ describe('SqliteWasmDriver tenant scope (organization_id)', () => { fields: platformGlobal[0].fields, }); expect((driver as any).tenantFieldByTable['sys_license']).toBeNull(); - const adminRead = await driver.find('sys_license', { object: 'sys_license' }, { tenantId: 'org_admin_active' }); + const adminRead = await driver.find('sys_license', {}, { tenantId: 'org_admin_active' }); expect(adminRead.map(r => r.id).sort()).toEqual(['lic_global', 'lic_org_b']); }); diff --git a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver.test.ts b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver.test.ts index 4ba1ac2d17..9c8bcccc71 100644 --- a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver.test.ts +++ b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver.test.ts @@ -40,7 +40,6 @@ describe('SqliteWasmDriver (in-memory)', () => { it('should find objects with filters', async () => { const results = await driver.find('users', { - object: 'users', fields: ['name', 'age'], where: { age: { $gt: 18 } }, orderBy: [{ field: 'name', order: 'asc' }], @@ -52,7 +51,6 @@ describe('SqliteWasmDriver (in-memory)', () => { it('should apply simple AND/OR logic', async () => { const results = await driver.find('users', { - object: 'users', where: { $or: [{ age: 17 }, { age: { $gt: 29 } }] }, }); const names = results.map((r: any) => r.name).sort(); @@ -60,34 +58,34 @@ describe('SqliteWasmDriver (in-memory)', () => { }); it('should find one object by id', async () => { - const [alice] = await driver.find('users', { object: 'users', where: { name: 'Alice' } }); + const [alice] = await driver.find('users', { where: { name: 'Alice' } }); expect(alice).toBeDefined(); - const fetched = await driver.findOne('users', { object: 'users', where: { id: alice.id } }); + const fetched = await driver.findOne('users', { where: { id: alice.id } }); expect(fetched.name).toBe('Alice'); }); it('should create an object', async () => { await driver.create('users', { name: 'Eve', age: 22 }); - const [eve] = await driver.find('users', { object: 'users', where: { name: 'Eve' } }); + const [eve] = await driver.find('users', { where: { name: 'Eve' } }); expect(eve.age).toBe(22); }); it('should update an object', async () => { - const [bob] = await driver.find('users', { object: 'users', where: { name: 'Bob' } }); + const [bob] = await driver.find('users', { where: { name: 'Bob' } }); await driver.update('users', bob.id, { age: 18 }); - const updated = await driver.findOne('users', { object: 'users', where: { id: bob.id } }); + const updated = await driver.findOne('users', { where: { id: bob.id } }); expect(updated.age).toBe(18); }); it('should delete an object', async () => { - const [charlie] = await driver.find('users', { object: 'users', where: { name: 'Charlie' } }); + const [charlie] = await driver.find('users', { where: { name: 'Charlie' } }); await driver.delete('users', charlie.id); - const deleted = await driver.findOne('users', { object: 'users', where: { id: charlie.id } }); + const deleted = await driver.findOne('users', { where: { id: charlie.id } }); expect(deleted).toBeNull(); }); it('should count objects', async () => { - const count = await driver.count('users', { object: 'users', where: { age: 17 } }); + const count = await driver.count('users', { where: { age: 17 } }); expect(count).toBe(2); }); }); @@ -116,7 +114,7 @@ describe('SqliteWasmDriver (file persistence)', () => { await d1.disconnect(); const d2 = new SqliteWasmDriver({ filename: dbPath, persist: 'on-disconnect' }); - const rows = await d2.find('items', { object: 'items' }); + const rows = await d2.find('items', {}); expect(rows.length).toBe(1); expect(rows[0].label).toBe('first'); await d2.disconnect(); @@ -134,7 +132,7 @@ describe('SqliteWasmDriver (file persistence)', () => { await d1.flush(); const d2 = new SqliteWasmDriver({ filename: dbPath, persist: 'on-write' }); - const rows = await d2.find('items', { object: 'items' }); + const rows = await d2.find('items', {}); expect(rows.length).toBe(1); await d1.disconnect(); await d2.disconnect(); diff --git a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-temporal-conformance.test.ts b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-temporal-conformance.test.ts index 3c459e17c2..31ee2b5934 100644 --- a/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-temporal-conformance.test.ts +++ b/packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-temporal-conformance.test.ts @@ -88,14 +88,14 @@ describe('driver-sqlite-wasm — temporal conformance', () => { for (const c of TEMPORAL_CASES) { it(c.name, async () => { - const rows = await driver.find('conformance', { object: 'conformance', where: c.filter }); + const rows = await driver.find('conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); if (c.tokenFilter) { it(`${c.name} — via relative tokens`, async () => { - const rows = await driver.find('conformance', { object: 'conformance', where: resolveTokens(c.tokenFilter) }); + const rows = await driver.find('conformance', { where: resolveTokens(c.tokenFilter) }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); @@ -132,7 +132,7 @@ describe('driver-sqlite-wasm — Field.time conformance', () => { for (const c of TEMPORAL_TIME_CASES) { it(c.name, async () => { - const rows = await driver.find('time_conformance', { object: 'time_conformance', where: c.filter }); + const rows = await driver.find('time_conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); @@ -174,7 +174,7 @@ describe('driver-sqlite-wasm — temporal conformance on un-backfilled legacy st // already swept above — a divergence here is a repair-path bug by construction. for (const c of TEMPORAL_CASES) { it(c.name, async () => { - const rows = await driver.find('conformance', { object: 'conformance', where: c.filter }); + const rows = await driver.find('conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); @@ -209,7 +209,7 @@ describe('driver-sqlite-wasm — Field.time conformance on un-backfilled legacy for (const c of TEMPORAL_TIME_CASES) { it(c.name, async () => { - const rows = await driver.find('time_conformance', { object: 'time_conformance', where: c.filter }); + const rows = await driver.find('time_conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); diff --git a/packages/drivers/driver-turso/src/remote-transport-boolean-identity.test.ts b/packages/drivers/driver-turso/src/remote-transport-boolean-identity.test.ts index 527b1d9576..0967b82096 100644 --- a/packages/drivers/driver-turso/src/remote-transport-boolean-identity.test.ts +++ b/packages/drivers/driver-turso/src/remote-transport-boolean-identity.test.ts @@ -326,7 +326,7 @@ describe('RemoteTransport $and/$or identity elements (#1073)', () => { describe('(f) the same answers through every WHERE-building entry point', () => { it('compiles `$or: []` to FALSE on count / deleteMany / updateMany', async () => { const { t, calls } = transportWithCapturingClient(); - await t.count('deal', { object: 'deal', where: { $or: [] } }); + await t.count('deal', { where: { $or: [] } }); await t.deleteMany('deal', { where: { $or: [] } } as any); await t.updateMany('deal', { where: { $or: [] } } as any, { stage: 'lost' }); // Pre-fix these were an unfiltered COUNT, a DELETE of the whole table and @@ -405,8 +405,8 @@ describe('TursoDriver remote — identity elements on real rows (#1073)', () => it('`count` with an absorbed `$or` counts every row and binds nothing', async () => { // Executed, not string-matched: a stray bind left over from the absorbed // disjunct makes better-sqlite3 reject the statement outright. - expect(await driver.count('deal', { object: 'deal', where: { $or: [{ stage: 'won' }, {}] } })).toBe(3); - expect(await driver.count('deal', { object: 'deal', where: { $or: [] } })).toBe(0); + expect(await driver.count('deal', { where: { $or: [{ stage: 'won' }, {}] } })).toBe(3); + expect(await driver.count('deal', { where: { $or: [] } })).toBe(0); }); it('a real two-branch `$or` is unchanged', async () => { diff --git a/packages/drivers/driver-turso/src/remote-transport-node-operator-refusal.test.ts b/packages/drivers/driver-turso/src/remote-transport-node-operator-refusal.test.ts index 779541fca7..3711c5db27 100644 --- a/packages/drivers/driver-turso/src/remote-transport-node-operator-refusal.test.ts +++ b/packages/drivers/driver-turso/src/remote-transport-node-operator-refusal.test.ts @@ -493,7 +493,7 @@ describe('[#5769] a refused node-position $-key touches no rows', () => { expect(await ids({})).toEqual(['d_lost', 'd_open', 'd_won']); expect(await ids({ $and: [] })).toEqual(['d_lost', 'd_open', 'd_won']); expect(await ids({ $or: [] })).toEqual([]); - expect(await driver.count('deal', { object: 'deal', where: { stage: 'won' } })).toBe(1); + expect(await driver.count('deal', { where: { stage: 'won' } })).toBe(1); }); }); @@ -552,10 +552,10 @@ describe('[#5769] local and remote give the same verdict', () => { for (const [label, where] of PARITY) { it(`${label}: both transports refuse with INVALID_FILTER / 400`, async () => { const l = (await local - .find('deal', { object: 'deal', where } as unknown as QueryAST) + .find('deal', { where } as unknown as QueryAST) .catch((e) => e)) as WireBearingError; const r = (await remote - .find('deal', { object: 'deal', where } as unknown as QueryAST) + .find('deal', { where } as unknown as QueryAST) .catch((e) => e)) as WireBearingError; expect(l, `local resolved ${label}`).toBeInstanceOf(Error); expect(r, `remote resolved ${label}`).toBeInstanceOf(Error); @@ -567,8 +567,8 @@ describe('[#5769] local and remote give the same verdict', () => { } it('and both still answer a well-formed filter identically', async () => { - const l = (await local.find('deal', { object: 'deal', where: { stage: 'won' } })) as any[]; - const r = (await remote.find('deal', { object: 'deal', where: { stage: 'won' } })) as any[]; + const l = (await local.find('deal', { where: { stage: 'won' } })) as any[]; + const r = (await remote.find('deal', { where: { stage: 'won' } })) as any[]; expect(l.map((x) => x.id)).toEqual(['d_won']); expect(r.map((x) => x.id)).toEqual(['d_won']); }); diff --git a/packages/drivers/driver-turso/src/remote-transport-not-operator.test.ts b/packages/drivers/driver-turso/src/remote-transport-not-operator.test.ts index 69a34f4d85..f727680863 100644 --- a/packages/drivers/driver-turso/src/remote-transport-not-operator.test.ts +++ b/packages/drivers/driver-turso/src/remote-transport-not-operator.test.ts @@ -582,7 +582,7 @@ describe('TursoDriver remote — $not on real rows (#1076)', () => { // Un-lowered, the transport (correctly) refuses `$between` and names a // lowering step that had been skipped for this depth. await expect( - driver.find('deal', { object: 'deal', where: { $not: { amount: { $between: [15, 35] } } } }), + driver.find('deal', { where: { $not: { amount: { $between: [15, 35] } } } }), ).resolves.toBeDefined(); expect(await ids({ $not: { amount: { $between: [15, 35] } } })).toEqual(['d_null', 'd_won']); }); @@ -591,9 +591,9 @@ describe('TursoDriver remote — $not on real rows (#1076)', () => { // Three since #5903 — `d_null` is inside the negation now, and a count that // disagreed with the list under it is exactly the local/remote split this // issue closed, wearing a total instead of a row set. - expect(await driver.count('deal', { object: 'deal', where: { $not: { stage: 'won' } } })).toBe(3); + expect(await driver.count('deal', { where: { $not: { stage: 'won' } } })).toBe(3); expect((await ids({ $not: { stage: 'won' } })).length).toBe(3); - expect(await driver.count('deal', { object: 'deal', where: { $not: {} } })).toBe(0); + expect(await driver.count('deal', { where: { $not: {} } })).toBe(0); }); it('`deleteMany` with `$not: {}` deletes NOTHING', async () => { diff --git a/packages/drivers/driver-turso/src/remote-transport-text-predicates.test.ts b/packages/drivers/driver-turso/src/remote-transport-text-predicates.test.ts index 78e9a183b7..e99070ce71 100644 --- a/packages/drivers/driver-turso/src/remote-transport-text-predicates.test.ts +++ b/packages/drivers/driver-turso/src/remote-transport-text-predicates.test.ts @@ -26,6 +26,7 @@ */ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'; +import type { DriverQuery } from '@objectstack/spec/contracts'; import { TursoDriver } from './turso-driver.js'; import { RemoteTransport } from './remote-transport.js'; import { makeLibsqlSqliteStub, type LibsqlSqliteStub } from './libsql-sqlite-stub.testkit.js'; @@ -60,7 +61,7 @@ async function makeRemoteDriver(schema: Record, rows: Record +const ids = async (driver: TursoDriver, object: string, where: DriverQuery['where']) => ((await driver.find(object, { where })) as any[]).map((r) => r.id).sort(); describe('TursoDriver remote — declared text predicates return rows', () => { diff --git a/packages/drivers/driver-turso/src/remote-transport-top-level-where.test.ts b/packages/drivers/driver-turso/src/remote-transport-top-level-where.test.ts index 58c0c4928b..45894df586 100644 --- a/packages/drivers/driver-turso/src/remote-transport-top-level-where.test.ts +++ b/packages/drivers/driver-turso/src/remote-transport-top-level-where.test.ts @@ -366,7 +366,7 @@ describe('TursoDriver remote — a refused top-level where touches no rows (#107 it('the legitimate whole-table spellings still work on rows', async () => { expect(await ids({})).toEqual(['d_lost', 'd_open', 'd_won']); expect(await ids(undefined)).toEqual(['d_lost', 'd_open', 'd_won']); - expect(await driver.count('deal', { object: 'deal', where: {} })).toBe(3); + expect(await driver.count('deal', { where: {} })).toBe(3); }); it('a well-formed filter still narrows to its rows', async () => { diff --git a/packages/drivers/driver-turso/src/turso-driver.test.ts b/packages/drivers/driver-turso/src/turso-driver.test.ts index a4fca76913..baf91247cd 100644 --- a/packages/drivers/driver-turso/src/turso-driver.test.ts +++ b/packages/drivers/driver-turso/src/turso-driver.test.ts @@ -151,7 +151,7 @@ describe('TursoDriver (SQLite Integration)', () => { }); it('should count records', async () => { - const count = await driver.count('users', { object: 'users', where: { age: 17 } }); + const count = await driver.count('users', { where: { age: 17 } }); expect(count).toBe(2); }); @@ -752,7 +752,7 @@ describe('TursoDriver Remote Mode (via @libsql/client)', () => { }); it('should count records with filter', async () => { - const count = await driver.count('users', { object: 'users', where: { age: 17 } }); + const count = await driver.count('users', { where: { age: 17 } }); expect(count).toBe(2); }); diff --git a/packages/drivers/driver-turso/src/turso-driver.ts b/packages/drivers/driver-turso/src/turso-driver.ts index 3007579fc6..67a6766d68 100644 --- a/packages/drivers/driver-turso/src/turso-driver.ts +++ b/packages/drivers/driver-turso/src/turso-driver.ts @@ -20,6 +20,7 @@ */ import { SqlDriver, type SqlDriverConfig } from '@objectstack/driver-sql'; +import type { DriverQuery } from '@objectstack/spec/contracts'; import type { Client } from '@libsql/client'; import { RemoteTransport } from './remote-transport.js'; import { @@ -503,12 +504,12 @@ export class TursoDriver extends SqlDriver { // CRUD (remote mode overrides) // =================================== - override async find(object: string, query: any, options?: any): Promise { + override async find(object: string, query: DriverQuery, options?: any): Promise { if (this.isRemote) return this.formatRemoteRows(object, await this.remoteTransport!.find(object, this.toRemoteReadQuery(object, query))); return super.find(object, query, options); } - override async findOne(object: string, query: any, options?: any): Promise { + override async findOne(object: string, query: DriverQuery, options?: any): Promise { if (this.isRemote) return this.formatRemoteRow(object, await this.remoteTransport!.findOne(object, this.toRemoteReadQuery(object, query, { singleRowLookup: true }))); return super.findOne(object, query, options); } @@ -539,7 +540,7 @@ export class TursoDriver extends SqlDriver { return super.delete(object, id, options); } - override async count(object: string, query?: any, options?: any): Promise { + override async count(object: string, query?: DriverQuery, options?: any): Promise { if (this.isRemote) return this.remoteTransport!.count(object, this.toRemoteQuery(object, query)); return super.count(object, query, options); } @@ -725,7 +726,7 @@ export class TursoDriver extends SqlDriver { } /** A query with its `where` compiled through {@link toRemoteFilter}. */ - private toRemoteQuery(object: string, query: any): any { + private toRemoteQuery(object: string, query?: DriverQuery): any { if (!query || typeof query !== 'object' || query.where == null) return query; return { ...query, where: this.toRemoteFilter(object, query.where) }; } @@ -765,7 +766,7 @@ export class TursoDriver extends SqlDriver { */ private toRemoteReadQuery( object: string, - query: any, + query: DriverQuery, opts?: { singleRowLookup?: boolean }, ): any { if (!query || typeof query !== 'object') return query; @@ -969,14 +970,14 @@ export class TursoDriver extends SqlDriver { return super.bulkDelete(object, ids, options); } - override async updateMany(object: string, query: any, data: any, options?: any): Promise { + override async updateMany(object: string, query: DriverQuery, data: any, options?: any): Promise { if (this.isRemote) { return this.remoteTransport!.updateMany(object, this.toRemoteQuery(object, query), this.toRemoteWriteForms(object, data)); } return super.updateMany(object, query, data, options); } - override async deleteMany(object: string, query: any, options?: any): Promise { + override async deleteMany(object: string, query: DriverQuery, options?: any): Promise { if (this.isRemote) return this.remoteTransport!.deleteMany(object, this.toRemoteQuery(object, query)); return super.deleteMany(object, query, options); } diff --git a/packages/drivers/driver-turso/src/turso-filter-logic-conformance.test.ts b/packages/drivers/driver-turso/src/turso-filter-logic-conformance.test.ts index 1f0f36c314..af2dfeb766 100644 --- a/packages/drivers/driver-turso/src/turso-filter-logic-conformance.test.ts +++ b/packages/drivers/driver-turso/src/turso-filter-logic-conformance.test.ts @@ -76,7 +76,7 @@ describe('TursoDriver — filter logic conformance (local mode)', () => { * seed failed must not read as a case that correctly excluded everything. */ it('the fixture really is all four rows', async () => { - const rows = await driver.find('conformance', { object: 'conformance' }); + const rows = await driver.find('conformance', {}); expect(ids(rows)).toEqual(['1', '2', '3', '4']); }); @@ -84,7 +84,7 @@ describe('TursoDriver — filter logic conformance (local mode)', () => { it(c.name, async () => { const rows = await driver.find( 'conformance', - { object: 'conformance', where: c.filter }, + { where: c.filter }, { bypassTenantAudit: true }, ); expect(ids(rows), c.note).toEqual([...c.expected]); diff --git a/packages/drivers/driver-turso/src/turso-local-remote-null-parity.test.ts b/packages/drivers/driver-turso/src/turso-local-remote-null-parity.test.ts index 3c31bc930e..dd8cb8438c 100644 --- a/packages/drivers/driver-turso/src/turso-local-remote-null-parity.test.ts +++ b/packages/drivers/driver-turso/src/turso-local-remote-null-parity.test.ts @@ -53,7 +53,7 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { FILTER_LOGIC_ROWS } from '@objectstack/spec/data'; import { TursoDriver } from './turso-driver.js'; import { makeLibsqlSqliteStub, type LibsqlSqliteStub } from './libsql-sqlite-stub.testkit.js'; -import type { QueryAST } from '@objectstack/spec/data'; +import type { DriverQuery } from '@objectstack/spec/contracts'; /** * The SHARED conformance fixture, not a private one. `d` is valued on rows 1-2 @@ -276,18 +276,18 @@ describe('[#5903] TursoDriver LOCAL and REMOTE answer the NULL family identicall * would turn every case below green for the wrong reason. */ it('both transports hold the same four rows, with `d` really NULL on 3 and 4', async () => { - expect(ids(await local.find('conformance', { object: 'conformance' }))).toEqual(['1', '2', '3', '4']); - expect(ids(await remote.find('conformance', { object: 'conformance' }))).toEqual(['1', '2', '3', '4']); + expect(ids(await local.find('conformance', {}))).toEqual(['1', '2', '3', '4']); + expect(ids(await remote.find('conformance', {}))).toEqual(['1', '2', '3', '4']); for (const driver of [local, remote]) { - expect(ids(await driver.find('conformance', { object: 'conformance', where: { d: { $null: true } } } as QueryAST))).toEqual(['3', '4']); - expect(ids(await driver.find('conformance', { object: 'conformance', where: { d: { $null: false } } } as QueryAST))).toEqual(['1', '2']); + expect(ids(await driver.find('conformance', { where: { d: { $null: true } } } as DriverQuery))).toEqual(['3', '4']); + expect(ids(await driver.find('conformance', { where: { d: { $null: false } } } as DriverQuery))).toEqual(['1', '2']); } }); for (const c of PARITY_CASES) { it(c.name, async () => { - const localIds = ids(await local.find('conformance', { object: 'conformance', where: c.filter } as QueryAST)); - const remoteIds = ids(await remote.find('conformance', { object: 'conformance', where: c.filter } as QueryAST)); + const localIds = ids(await local.find('conformance', { where: c.filter } as DriverQuery)); + const remoteIds = ids(await remote.find('conformance', { where: c.filter } as DriverQuery)); // Agreement first — this is the assertion #5903 is about. expect(remoteIds, `local/remote divergence. ${c.why}`).toEqual(localIds); // …and agreement on the RIGHT answer, so a shared regression cannot pass @@ -301,10 +301,10 @@ describe('[#5903] TursoDriver LOCAL and REMOTE answer the NULL family identicall // wearing a total instead of a row set — and remote builds its COUNT // statement separately from its SELECT. for (const c of PARITY_CASES) { - expect(await local.count('conformance', { object: 'conformance', where: c.filter } as QueryAST), `local ${c.name}`).toBe( + expect(await local.count('conformance', { where: c.filter } as DriverQuery), `local ${c.name}`).toBe( c.expected.length, ); - expect(await remote.count('conformance', { object: 'conformance', where: c.filter } as QueryAST), `remote ${c.name}`).toBe( + expect(await remote.count('conformance', { where: c.filter } as DriverQuery), `remote ${c.name}`).toBe( c.expected.length, ); } @@ -319,7 +319,7 @@ describe('[#5903] TursoDriver LOCAL and REMOTE answer the NULL family identicall const where = { d: { $exists: value } }; for (const [label, driver] of [['local', local], ['remote', remote]] as const) { const err = (await driver - .find('conformance', { where } as unknown as QueryAST) + .find('conformance', { where } as unknown as DriverQuery) .catch((e) => e)) as Error & { code?: string; status?: number }; expect(err, `${label} ${JSON.stringify(value) ?? 'undefined'}`).toBeInstanceOf(Error); expect(err.code, label).toBe('INVALID_FILTER'); @@ -333,8 +333,8 @@ describe('[#5903] TursoDriver LOCAL and REMOTE answer the NULL family identicall it('both transports still COMPILE the two booleans `$exists` declares', async () => { for (const driver of [local, remote]) { - expect(ids(await driver.find('conformance', { object: 'conformance', where: { d: { $exists: true } } } as QueryAST))).toEqual(['1', '2']); - expect(ids(await driver.find('conformance', { object: 'conformance', where: { d: { $exists: false } } } as QueryAST))).toEqual(['3', '4']); + expect(ids(await driver.find('conformance', { where: { d: { $exists: true } } } as DriverQuery))).toEqual(['1', '2']); + expect(ids(await driver.find('conformance', { where: { d: { $exists: false } } } as DriverQuery))).toEqual(['3', '4']); } }); @@ -346,7 +346,7 @@ describe('[#5903] TursoDriver LOCAL and REMOTE answer the NULL family identicall const errors: Record = {}; for (const [faceLabel, driver] of [['local', local], ['remote', remote]] as const) { const err = (await driver - .find('conformance', { object: 'conformance', where } as unknown as QueryAST) + .find('conformance', { where } as unknown as DriverQuery) .catch((e) => e)) as Error & { code?: string; status?: number }; expect(err, faceLabel).toBeInstanceOf(Error); // The ENVELOPE is half the fix (#1116 / #4436): LOCAL used to throw @@ -387,8 +387,8 @@ describe('[#5903] TursoDriver LOCAL and REMOTE answer the NULL family identicall [{ d: { $in: ['v1', null] } }, ['1']], ]; for (const [where, expected] of NULL_CASES) { - const localIds = ids(await local.find('conformance', { object: 'conformance', where } as QueryAST)); - const remoteIds = ids(await remote.find('conformance', { object: 'conformance', where } as QueryAST)); + const localIds = ids(await local.find('conformance', { where } as DriverQuery)); + const remoteIds = ids(await remote.find('conformance', { where } as DriverQuery)); expect(remoteIds, `divergence on ${JSON.stringify(where)}`).toEqual(localIds); expect(localIds, `wrong answer on ${JSON.stringify(where)}`).toEqual(expected); } diff --git a/packages/drivers/driver-turso/src/turso-pagination-conformance.test.ts b/packages/drivers/driver-turso/src/turso-pagination-conformance.test.ts index c34b522b88..4eeafb98cb 100644 --- a/packages/drivers/driver-turso/src/turso-pagination-conformance.test.ts +++ b/packages/drivers/driver-turso/src/turso-pagination-conformance.test.ts @@ -79,7 +79,7 @@ describe('TursoDriver — paged reads are a partition of the result set (local m it('the fixture really is all twelve rows', async () => { const rows: Array> = await driver.find( 'ticket', - { object: 'ticket' }, + {}, { bypassTenantAudit: true }, ); expect(rows.map((r) => String(r.id)).sort()).toEqual([...PAGINATION_ALL_IDS].sort()); diff --git a/packages/drivers/driver-turso/src/turso-temporal-conformance.test.ts b/packages/drivers/driver-turso/src/turso-temporal-conformance.test.ts index e6e5a9d904..4db69c946f 100644 --- a/packages/drivers/driver-turso/src/turso-temporal-conformance.test.ts +++ b/packages/drivers/driver-turso/src/turso-temporal-conformance.test.ts @@ -122,14 +122,14 @@ describe('TursoDriver — temporal conformance (local mode)', () => { for (const c of TEMPORAL_CASES) { it(c.name, async () => { - const rows = await driver.find('conformance', { object: 'conformance', where: c.filter }); + const rows = await driver.find('conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); if (c.tokenFilter) { it(`${c.name} — via relative tokens`, async () => { - const rows = await driver.find('conformance', { object: 'conformance', where: resolveTokens(c.tokenFilter) }); + const rows = await driver.find('conformance', { where: resolveTokens(c.tokenFilter) }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); @@ -164,7 +164,7 @@ describe('TursoDriver — Field.time conformance (local mode)', () => { for (const c of TEMPORAL_TIME_CASES) { it(c.name, async () => { - const rows = await driver.find('time_conformance', { object: 'time_conformance', where: c.filter }); + const rows = await driver.find('time_conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); @@ -209,7 +209,7 @@ describe('TursoDriver — temporal conformance on un-backfilled legacy storage', // already swept above — a divergence here is a repair-path bug by construction. for (const c of TEMPORAL_CASES) { it(c.name, async () => { - const rows = await driver.find('conformance', { object: 'conformance', where: c.filter }); + const rows = await driver.find('conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); @@ -252,7 +252,7 @@ describe('TursoDriver — Field.time conformance on un-backfilled legacy storage for (const c of TEMPORAL_TIME_CASES) { it(c.name, async () => { - const rows = await driver.find('time_conformance', { object: 'time_conformance', where: c.filter }); + const rows = await driver.find('time_conformance', { where: c.filter }); const got = (rows as any[]).map((r) => r.id).sort(); expect(got, c.note).toEqual([...c.expected].sort()); }); From ae9fe543e0b7c31fee90cf97e0852d6fb0586977 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 11:19:41 +0000 Subject: [PATCH 2/3] =?UTF-8?q?fix(qa):=20=E4=B8=8B=E6=B8=B8=E6=B6=88?= =?UTF-8?q?=E8=B4=B9=E8=80=85=E8=B7=9F=E8=BF=9B=20DriverQuery=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20dogfood=20=E5=88=A0=E6=8E=89=E5=86=97=E4=BD=99?= =?UTF-8?q?=E7=9A=84=20object=20=E9=94=AE=20(#6075)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一版的消费半径扫描用了 `--filter 'pkg...'`(后缀省略号 = 该包 + 它 依赖的包,上游闭包),方向扫反了:签名收窄打到的是**下游**消费者。 真正该用的是前缀形 `--filter '...pkg'`,或者直接以全仓 `pnpm typecheck` 为准(CI 跑的就是全仓)。@objectstack/dogfood 因此没进上一轮扫描面。 全仓复扫命中一处真实编译错误:storage-growth.dogfood.test.ts:260 的 `cold.count(...)`,`cold` 是真实的 SqlDriver,内联字面量里的冗余 `object` 被收窄后的 DriverQuery 拒绝。 同包另有 9 处同形调用未被 tsc 标出 —— 它们走本地结构替身 `DriverLike.count(object, query?: Record)`,任何键都收, 类型层看不见。一并按同一规矩删除,避免同一文件里两种写法并存。 删键用的是带反向引用的模式,只在 query 里的值与第一个实参**逐字相同** 时才匹配,等价性由模式本身保证;dogfood 包里其余 `{ object: ... }` (权限授予、端点策略入参、report 条目、类型声明)因此一处未被波及。 ⛔ 未放松任何驱动侧签名。 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx --- .../test/empty-group-bucket-parity.test.ts | 2 +- .../test/group-key-read-shape-parity.test.ts | 2 +- .../dogfood/test/storage-growth.dogfood.test.ts | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/qa/dogfood/test/empty-group-bucket-parity.test.ts b/packages/qa/dogfood/test/empty-group-bucket-parity.test.ts index f93e9f47e2..e5c3e67e89 100644 --- a/packages/qa/dogfood/test/empty-group-bucket-parity.test.ts +++ b/packages/qa/dogfood/test/empty-group-bucket-parity.test.ts @@ -89,7 +89,7 @@ describe.each(DRIVERS)('empty group bucket parity: $name', ({ make }) => { const pushedDown = await driver.aggregate(TABLE, ast); // The rows the in-memory path would see — the driver's own read output, which // is exactly what `engine.aggregate` feeds the fallback. - const inMemory = applyInMemoryAggregation(await driver.find(TABLE, { object: TABLE }), ast as never); + const inMemory = applyInMemoryAggregation(await driver.find(TABLE, {}), ast as never); expect(shape(pushedDown, field)).toEqual(shape(inMemory, field)); // …and both agree on real `null`, not on a sentinel they happen to share. diff --git a/packages/qa/dogfood/test/group-key-read-shape-parity.test.ts b/packages/qa/dogfood/test/group-key-read-shape-parity.test.ts index d0b9356bd7..73a6108a43 100644 --- a/packages/qa/dogfood/test/group-key-read-shape-parity.test.ts +++ b/packages/qa/dogfood/test/group-key-read-shape-parity.test.ts @@ -94,7 +94,7 @@ describe.each(DRIVERS)('group-key read-shape parity: $name', ({ make }) => { await driver.create(TABLE, { id: 'c', qty: 7, won: false, stage: 'lost', amount: 4 }, opts); // What `engine.aggregate` feeds the in-memory fallback, and the canonical // presentation both aggregate paths are measured against. - rows = await driver.find(TABLE, { object: TABLE }); + rows = await driver.find(TABLE, {}); }); afterEach(async () => { diff --git a/packages/qa/dogfood/test/storage-growth.dogfood.test.ts b/packages/qa/dogfood/test/storage-growth.dogfood.test.ts index cc973f2c11..d66f9c93f5 100644 --- a/packages/qa/dogfood/test/storage-growth.dogfood.test.ts +++ b/packages/qa/dogfood/test/storage-growth.dogfood.test.ts @@ -152,7 +152,7 @@ describe('objectstack verify LIFECYCLE (ADR-0057): declared policies bound growt }); } - const before = await driver.count('growth_probe_event', { object: 'growth_probe_event' }); + const before = await driver.count('growth_probe_event', {}); expect(before).toBe(40); const report = await lifecycle.sweep(); @@ -167,11 +167,11 @@ describe('objectstack verify LIFECYCLE (ADR-0057): declared policies bound growt // The telemetry table is now bounded by its declared 30d window: // rows at 5,10,15,20,25d survive (5 rows), everything older is gone. - const after = await driver.count('growth_probe_event', { object: 'growth_probe_event' }); + const after = await driver.count('growth_probe_event', {}); expect(after, 'telemetry rows past retention.maxAge must be reaped').toBe(5); // Record-class/business data is sacrosanct — same age, still alive. - const records = await driver.count('growth_probe_record', { object: 'growth_probe_record' }); + const records = await driver.count('growth_probe_record', {}); expect(records, 'record-class rows must NEVER be reaped').toBe(5); // The sweep reported the reap and reclaimed the datasource. @@ -209,12 +209,12 @@ describe('objectstack verify LIFECYCLE (ADR-0057): declared policies bound growt // sweep applies the 'rotation' policy without touching the rows inside // the window. await streamDriver.create('growth_probe_stream', { payload: 'tick' }); - expect(await streamDriver.count('growth_probe_stream', { object: 'growth_probe_stream' })).toBe(1); + expect(await streamDriver.count('growth_probe_stream', {})).toBe(1); const report = await lifecycle.sweep(); const entry = report.swept.find((e) => e.object === 'growth_probe_stream'); expect(entry?.policy).toBe('rotation'); - expect(await streamDriver.count('growth_probe_stream', { object: 'growth_probe_stream' })).toBe(1); + expect(await streamDriver.count('growth_probe_stream', {})).toBe(1); }); it('ARCHIVE SAFETY: an audit ledger with a declared archive is never hot-deleted unarchived', async () => { @@ -230,7 +230,7 @@ describe('objectstack verify LIFECYCLE (ADR-0057): declared policies bound growt // No archive datasource named 'archive_missing' exists ⇒ the rows are // RETAINED (today's behavior), not dropped. Compliance data cannot be // destroyed by declaring a lifecycle. - const ledger = await driver.count('growth_probe_ledger', { object: 'growth_probe_ledger' }); + const ledger = await driver.count('growth_probe_ledger', {}); expect(ledger, 'archive-declared audit rows must be retained until archived').toBe(3); expect(report.skipped).toContainEqual({ object: 'growth_probe_ledger', reason: 'archive-pending' }); }); @@ -255,9 +255,9 @@ describe('objectstack verify LIFECYCLE (ADR-0057): declared policies bound growt expect((entry as { archived?: number })?.archived).toBe(3); // Hot store drained, cold store holds the ledger. - const hot = await driver.count('growth_probe_ledger', { object: 'growth_probe_ledger' }); + const hot = await driver.count('growth_probe_ledger', {}); expect(hot, 'archived rows must leave the hot store').toBe(0); - const coldRows = await cold.count('growth_probe_ledger', { object: 'growth_probe_ledger' }); + const coldRows = await cold.count('growth_probe_ledger', {}); expect(coldRows, 'archived rows must land in the cold store').toBe(3); await cold.disconnect(); }); From 34fd976cd6c86d25bbd794817c8b7131919ca187 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 11:43:29 +0000 Subject: [PATCH 3/3] =?UTF-8?q?fix(plugin-auth):=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=B1=82=E7=9A=84=E5=86=97=E4=BD=99=20object=20=E9=94=AE?= =?UTF-8?q?=E8=B7=9F=E8=BF=9B=20DriverQuery=EF=BC=8C=E5=8F=B0=E8=B4=A6?= =?UTF-8?q?=E4=B8=8D=E6=8A=AC=20(#6075)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check:type-check-debt 的全仓 re-measure 报 plugin-auth 从 131 涨到 132 (+1)。plugin-auth 的 tsconfig 把 **/*.test.ts 排除在外,所以 per-package `pnpm typecheck` 看不见它,只有把排除掀掉的全仓 re-measure 量得到。 定位到的就是一处: src/auth-where-operator-coverage.test.ts(383,7): error TS2353: Object literal may only specify known properties, and 'object' does not exist in type 'DriverQuery'. `driver.find('sys_user', { object: 'sys_user', fields: ['id'] } satisfies QueryAST)` —— 删掉的值与第一个实参逐字相同,属重拼类。承载类型按第三类 处置改为 `satisfies DriverQuery`:该处上方注释明确写了「这里不能用 as any, 否则会被 check:query-options-erasure (#4674/#4918) 记一笔」,所以保留 satisfies、只换目标类型,正是它原本的意图。 ⛔ 未抬台账数字:门禁原话「the ledger is a ratchet and may only shrink」 (#5278),本单是自己引入的 +1,属可约。改完复测回到 131,与台账记录一致。 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx --- .../plugin-auth/src/auth-where-operator-coverage.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugins/plugin-auth/src/auth-where-operator-coverage.test.ts b/packages/plugins/plugin-auth/src/auth-where-operator-coverage.test.ts index 0aec55cc2a..dca988cf88 100644 --- a/packages/plugins/plugin-auth/src/auth-where-operator-coverage.test.ts +++ b/packages/plugins/plugin-auth/src/auth-where-operator-coverage.test.ts @@ -73,6 +73,7 @@ import { assertEngineDeleteDispatch } from '@objectstack/objectql'; import { whereOperators } from '@better-auth/core/db/adapter'; import { FILTER_OPERATORS } from '@objectstack/spec/data'; import type { QueryAST } from '@objectstack/spec/data'; +import type { DriverQuery } from '@objectstack/spec/contracts'; import type { IDataEngine } from '@objectstack/core'; import { createObjectQLAdapterFactory, @@ -380,9 +381,8 @@ describe('[#5813] the predicates really filter on a real backend', () => { // would be counted by `check:query-options-erasure` (#4674/#4918), and // nothing about this read is off-contract. const left = await driver.find('sys_user', { - object: 'sys_user', fields: ['id'], - } satisfies QueryAST); + } satisfies DriverQuery); expect(left.map((r: any) => r.id).sort()).toEqual(['u_abc1', 'u_abcz', 'u_xabc']); }); });