diff --git a/.changeset/many-snails-strive.md b/.changeset/many-snails-strive.md new file mode 100644 index 000000000..68da09cfd --- /dev/null +++ b/.changeset/many-snails-strive.md @@ -0,0 +1,5 @@ +--- +"@tanstack/db": patch +--- + +Fix ordering of ts update overloads & fix a lot of type errors in tests diff --git a/eslint.config.mjs b/eslint.config.mjs index 0a381dd12..834075224 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -15,6 +15,18 @@ export default [ "prettier/prettier": `error`, "stylistic/quotes": [`error`, `backtick`], ...prettierConfig.rules, + "@typescript-eslint/no-unused-vars": [ + `error`, + { argsIgnorePattern: `^_`, varsIgnorePattern: `^_` }, + ], + "@typescript-eslint/naming-convention": [ + "error", + { + selector: "typeParameter", + format: ["PascalCase"], + leadingUnderscore: `allow`, + }, + ], }, }, ] diff --git a/examples/react/todo/src/App.tsx b/examples/react/todo/src/App.tsx index 793b7253f..9c1a85b1c 100644 --- a/examples/react/todo/src/App.tsx +++ b/examples/react/todo/src/App.tsx @@ -130,7 +130,7 @@ const collectionsCache = new Map() // Function to create the appropriate todo collection based on type const createTodoCollection = (type: CollectionType) => { if (collectionsCache.has(`todo`)) { - return collectionsCache.get(`todo`) + return collectionsCache.get(`todo`) as Collection } else { let newCollection: Collection if (type === CollectionType.Electric) { @@ -147,7 +147,7 @@ const createTodoCollection = (type: CollectionType) => { timestamptz: (date: string) => new Date(date), }, }, - getKey: (item) => item.id, + getKey: (item) => item.id!, schema: updateTodoSchema, onInsert: async ({ transaction }) => { const modified = transaction.mutations[0].modified @@ -201,7 +201,7 @@ const createTodoCollection = (type: CollectionType) => { : undefined, })) }, - getKey: (item: UpdateTodo) => String(item.id), + getKey: (item: UpdateTodo) => item.id!, schema: updateTodoSchema, queryClient, onInsert: async ({ transaction }) => { @@ -220,7 +220,7 @@ const createTodoCollection = (type: CollectionType) => { return await Promise.all( transaction.mutations.map(async (mutation) => { const { original } = mutation - const response = await api.todos.delete(original.id) + await api.todos.delete(original.id) }) ) }, @@ -254,7 +254,7 @@ const createConfigCollection = (type: CollectionType) => { }, }, }, - getKey: (item: UpdateConfig) => item.id, + getKey: (item: UpdateConfig) => item.id!, schema: updateConfigSchema, onInsert: async ({ transaction }) => { const modified = transaction.mutations[0].modified @@ -346,7 +346,7 @@ export default function App() { // Always call useLiveQuery hooks const { data: todos } = useLiveQuery((q) => q - .from({ todoCollection: todoCollection as Collection }) + .from({ todoCollection: todoCollection }) .orderBy(`@created_at`) .select(`@id`, `@created_at`, `@text`, `@completed`) ) @@ -462,6 +462,7 @@ export default function App() { } const toggleTodo = (todo: UpdateTodo) => { + console.log(todoCollection) todoCollection.update(todo.id, (draft) => { draft.completed = !draft.completed }) diff --git a/packages/db-collections/src/electric.ts b/packages/db-collections/src/electric.ts index 9a59bed1e..5691bd244 100644 --- a/packages/db-collections/src/electric.ts +++ b/packages/db-collections/src/electric.ts @@ -187,7 +187,13 @@ export function electricCollectionOptions>( : undefined // Extract standard Collection config properties - const { shapeOptions, onInsert, onUpdate, onDelete, ...restConfig } = config + const { + shapeOptions: _shapeOptions, + onInsert: _onInsert, + onUpdate: _onUpdate, + onDelete: _onDelete, + ...restConfig + } = config return { ...restConfig, diff --git a/packages/db/src/collection.ts b/packages/db/src/collection.ts index 33247b23b..f48173085 100644 --- a/packages/db/src/collection.ts +++ b/packages/db/src/collection.ts @@ -903,20 +903,34 @@ export class CollectionImpl< * // Update with metadata * update("todo-1", { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" }) */ + // Overload 1: Update multiple items with a callback update( - key: TKey, - configOrCallback: ((draft: TItem) => void) | OperationConfig, - maybeCallback?: (draft: TItem) => void + key: Array, + callback: (drafts: Array) => void + ): TransactionType + + // Overload 2: Update multiple items with config and a callback + update( + keys: Array, + config: OperationConfig, + callback: (drafts: Array) => void + ): TransactionType + + // Overload 3: Update a single item with a callback + update( + id: TKey | unknown, + callback: (draft: TItem) => void ): TransactionType + // Overload 4: Update a single item with config and a callback update( - keys: Array, - configOrCallback: ((draft: Array) => void) | OperationConfig, - maybeCallback?: (draft: Array) => void + id: TKey | unknown, + config: OperationConfig, + callback: (draft: TItem) => void ): TransactionType update( - keys: TKey | Array, + keys: (TKey | unknown) | Array, configOrCallback: ((draft: TItem | Array) => void) | OperationConfig, maybeCallback?: (draft: TItem | Array) => void ) { diff --git a/packages/db/src/proxy.ts b/packages/db/src/proxy.ts index 7599223ad..0ca947a26 100644 --- a/packages/db/src/proxy.ts +++ b/packages/db/src/proxy.ts @@ -642,7 +642,7 @@ export function createChangeProxy< return value }, - set(sobj, prop, value) { + set(_sobj, prop, value) { const currentValue = changeTracker.copy_[prop as keyof T] debugLog( `set called for property ${String(prop)}, current:`, @@ -716,7 +716,7 @@ export function createChangeProxy< return true }, - defineProperty(ptarget, prop, descriptor) { + defineProperty(_ptarget, prop, descriptor) { // const result = Reflect.defineProperty( // changeTracker.copy_, // prop, diff --git a/packages/db/src/query/schema.ts b/packages/db/src/query/schema.ts index 000869b5d..8bc818187 100644 --- a/packages/db/src/query/schema.ts +++ b/packages/db/src/query/schema.ts @@ -197,7 +197,7 @@ export type SelectCallback = ( context: TContext extends { schema: infer S } ? S : any ) => any -export type As = string +export type As<_TContext extends Context = Context> = string export type From = InputReference<{ baseSchema: TContext[`baseSchema`] @@ -219,9 +219,9 @@ export type GroupBy = | PropertyReference | Array> -export type Limit = number +export type Limit<_TContext extends Context = Context> = number -export type Offset = number +export type Offset<_TContext extends Context = Context> = number export interface BaseQuery { // The select clause is an array of either plain strings or objects mapping alias names diff --git a/packages/db/tests/collection-getters.test.ts b/packages/db/tests/collection-getters.test.ts index 6a21773bf..6d9460436 100644 --- a/packages/db/tests/collection-getters.test.ts +++ b/packages/db/tests/collection-getters.test.ts @@ -14,7 +14,6 @@ describe(`Collection getters`, () => { commit: () => void }) => void } - let mockMutationFn: { persist: () => Promise } let config: CollectionConfig beforeEach(() => { @@ -34,10 +33,6 @@ describe(`Collection getters`, () => { }), } - mockMutationFn = { - persist: vi.fn().mockResolvedValue(undefined), - } - config = { id: `test-collection`, getKey: (val) => val.id as string, diff --git a/packages/db/tests/collection-subscribe-changes.test.ts b/packages/db/tests/collection-subscribe-changes.test.ts index 498790b3a..371995eb7 100644 --- a/packages/db/tests/collection-subscribe-changes.test.ts +++ b/packages/db/tests/collection-subscribe-changes.test.ts @@ -5,9 +5,8 @@ import { createTransaction } from "../src/transactions" import type { ChangeMessage, ChangesPayload, + MutationFn, PendingMutation, - Transaction, - TransactionConfig, } from "../src/types" // Helper function to wait for changes to be processed @@ -226,7 +225,9 @@ describe(`Collection.subscribeChanges`, () => { updated?: boolean }>({ id: `optimistic-changes-test`, - getKey: (item) => item.id, + getKey: (item) => { + return item.id + }, sync: { sync: ({ begin, write, commit }) => { // Listen for sync events @@ -246,11 +247,7 @@ describe(`Collection.subscribeChanges`, () => { }, }) - const mutationFn = async ({ - transaction, - }: { - transaction: Transaction - }) => { + const mutationFn: MutationFn = async ({ transaction }) => { emitter.emit(`sync`, transaction.mutations) return Promise.resolve() } @@ -376,11 +373,7 @@ describe(`Collection.subscribeChanges`, () => { }, }) - const mutationFn = async ({ - transaction, - }: { - transaction: Transaction - }) => { + const mutationFn: MutationFn = async ({ transaction }) => { emitter.emit(`sync`, transaction.mutations) return Promise.resolve() } @@ -545,11 +538,7 @@ describe(`Collection.subscribeChanges`, () => { }, }, }) - const mutationFn = async ({ - transaction, - }: { - transaction: Transaction - }) => { + const mutationFn: MutationFn = async ({ transaction }) => { emitter.emit(`sync`, transaction.mutations) return Promise.resolve() } diff --git a/packages/db/tests/collection.test-d.ts b/packages/db/tests/collection.test-d.ts new file mode 100644 index 000000000..d198bc301 --- /dev/null +++ b/packages/db/tests/collection.test-d.ts @@ -0,0 +1,43 @@ +import { assertType, describe, expectTypeOf, it } from "vitest" +import type { CollectionImpl } from "../src/collection" +import type { OperationConfig } from "../src/types" + +describe(`Collection.update type tests`, () => { + type TypeTestItem = { id: string; value: number; optional?: boolean } + + const updateMethod: CollectionImpl[`update`] = (() => {}) as any // Dummy assignment for type checking + + it(`should correctly type drafts for multi-item update with callback (Overload 1)`, () => { + updateMethod([`id1`, `id2`], (drafts) => { + expectTypeOf(drafts).toEqualTypeOf>() + // @ts-expect-error - This line should error because drafts is an array, not a single item. + assertType(drafts) + }) + }) + + it(`should correctly type drafts for multi-item update with config and callback (Overload 2)`, () => { + const config: OperationConfig = { metadata: { test: true } } + updateMethod([`id1`, `id2`], config, (drafts) => { + expectTypeOf(drafts).toEqualTypeOf>() + // @ts-expect-error - This line should error. + assertType(drafts) + }) + }) + + it(`should correctly type draft for single-item update with callback (Overload 3)`, () => { + updateMethod(`id1`, (draft) => { + expectTypeOf(draft).toEqualTypeOf() + // @ts-expect-error - This line should error because draft is a single item, not an array. + assertType>(draft) + }) + }) + + it(`should correctly type draft for single-item update with config and callback (Overload 4)`, () => { + const config: OperationConfig = { metadata: { test: true } } + updateMethod(`id1`, config, (draft) => { + expectTypeOf(draft).toEqualTypeOf() + // @ts-expect-error - This line should error. + assertType>(draft) + }) + }) +}) diff --git a/packages/db/tests/collection.test.ts b/packages/db/tests/collection.test.ts index 2753e00d9..614ada66d 100644 --- a/packages/db/tests/collection.test.ts +++ b/packages/db/tests/collection.test.ts @@ -7,6 +7,7 @@ import type { ChangeMessage, MutationFn, PendingMutation } from "../src/types" describe(`Collection`, () => { it(`should throw if there's no sync config`, () => { + // @ts-expect-error we're testing for throwing when there's no config passed in expect(() => createCollection()).toThrow(`Collection requires a config`) }) @@ -200,7 +201,7 @@ describe(`Collection`, () => { tx.mutate(() => collection.insert(data)) // @ts-expect-error possibly undefined is ok in test - const insertedKey = tx.mutations[0].key + const insertedKey = tx.mutations[0].key as string // The merged value should immediately contain the new insert expect(collection.state).toEqual( @@ -410,7 +411,7 @@ describe(`Collection`, () => { // Test bulk delete const tx9 = createTransaction({ mutationFn }) - tx9.mutate(() => collection.delete([keys[2], keys[3]])) + tx9.mutate(() => collection.delete([keys[2]!, keys[3]!])) // @ts-expect-error possibly undefined is ok in test expect(collection.state.has(keys[2])).toBe(false) // @ts-expect-error possibly undefined is ok in test @@ -520,9 +521,6 @@ describe(`Collection`, () => { tx2.mutate(() => collection.delete(`non-existent-id`)) ).not.toThrow() - // Should throw when trying to delete an invalid type - const tx3 = createTransaction({ mutationFn }) - // Should not throw when deleting by string key (even if key doesn't exist) const tx4 = createTransaction({ mutationFn }) expect(() => @@ -614,7 +612,7 @@ describe(`Collection`, () => { ) // Test delete handler - tx.mutate(() => collection.delete(1)) + tx.mutate(() => collection.delete(`1`)) // Convert number to string to match expected type // Verify the handler functions were defined correctly // We're not testing actual invocation since that would require modifying the Collection class @@ -625,18 +623,18 @@ describe(`Collection`, () => { it(`should execute operations outside of explicit transactions using handlers`, async () => { // Create handler functions that resolve after a short delay to simulate async operations - const onInsertMock = vi.fn().mockImplementation(async (tx) => { + const onInsertMock = vi.fn().mockImplementation(async () => { // Wait a bit to simulate an async operation await new Promise((resolve) => setTimeout(resolve, 10)) return { success: true, operation: `insert` } }) - const onUpdateMock = vi.fn().mockImplementation(async (tx) => { + const onUpdateMock = vi.fn().mockImplementation(async () => { await new Promise((resolve) => setTimeout(resolve, 10)) return { success: true, operation: `update` } }) - const onDeleteMock = vi.fn().mockImplementation(async (tx) => { + const onDeleteMock = vi.fn().mockImplementation(async () => { await new Promise((resolve) => setTimeout(resolve, 10)) return { success: true, operation: `delete` } }) @@ -676,7 +674,7 @@ describe(`Collection`, () => { expect(onUpdateMock).toHaveBeenCalledTimes(1) // Test direct delete operation - const deleteTx = collection.delete(1) + const deleteTx = collection.delete(`1`) // Convert number to string to match expected type expect(deleteTx).toBeDefined() expect(onDeleteMock).toHaveBeenCalledTimes(1) @@ -731,7 +729,7 @@ describe(`Collection`, () => { // Test delete without handler expect(() => { - collection.delete(1) + collection.delete(`1`) // Convert number to string to match expected type }).toThrow( `Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.` ) diff --git a/packages/db/tests/proxy.test.ts b/packages/db/tests/proxy.test.ts index 07998882c..f8ed6f8d3 100644 --- a/packages/db/tests/proxy.test.ts +++ b/packages/db/tests/proxy.test.ts @@ -137,10 +137,10 @@ describe(`Proxy Library`, () => { // @ts-expect-error ignore for test obj.self = obj // Create circular reference - // @ts-expect-error ignore for test - const { proxy, getChanges } = createChangeProxy(obj) + const { proxy, getChanges } = createChangeProxy( + obj as Record + ) - // @ts-expect-error ignore for test proxy.name = `Jane` expect(getChanges()).toEqual({ diff --git a/packages/db/tests/query/in-operator.test.ts b/packages/db/tests/query/in-operator.test.ts index 36b020348..e48247287 100644 --- a/packages/db/tests/query/in-operator.test.ts +++ b/packages/db/tests/query/in-operator.test.ts @@ -265,9 +265,9 @@ describe(`Query - IN Operator`, () => { graph.run() - const dataMessages = messages.filter((m) => m.type === MessageType.DATA) - const results = - dataMessages[0]?.data.collection.getInner().map(([data]) => data[1]) || [] + // const dataMessages = messages.filter((m) => m.type === MessageType.DATA) + // const results = + // dataMessages[0]?.data.collection.getInner().map(([data]) => data[1]) || [] // TODO: Finish this test! }) @@ -352,9 +352,9 @@ describe(`Query - IN Operator`, () => { graph.run() - const dataMessages = messages.filter((m) => m.type === MessageType.DATA) - const results = - dataMessages[0]?.data.collection.getInner().map(([data]) => data[1]) || [] + // const dataMessages = messages.filter((m) => m.type === MessageType.DATA) + // const results = + // dataMessages[0]?.data.collection.getInner().map(([data]) => data[1]) || [] // TODO: Finish this test! }) diff --git a/packages/db/tests/query/query-builder/select-functions.test.ts b/packages/db/tests/query/query-builder/select-functions.test.ts index 205ffec91..99b7257fd 100644 --- a/packages/db/tests/query/query-builder/select-functions.test.ts +++ b/packages/db/tests/query/query-builder/select-functions.test.ts @@ -80,7 +80,8 @@ describe(`QueryBuilder.select with function calls`, () => { const builtQuery = query._query expect(builtQuery.select).toHaveLength(2) - expect(builtQuery.select[1]).toHaveProperty(`json_value`) + // Non-null assertion since we've already checked the length + expect(builtQuery.select![1]).toHaveProperty(`json_value`) }) it(`validates and filters out invalid function calls`, () => { @@ -123,11 +124,12 @@ describe(`QueryBuilder.select with function calls`, () => { const builtQuery = query._query expect(builtQuery.select).toHaveLength(4) - expect(builtQuery.select[0]).toBe(`@e.id`) - expect(builtQuery.select[1]).toBe(`@e.name`) - expect(builtQuery.select[2]).toBe(`@d.name`) - expect(builtQuery.select[3]).toHaveProperty(`dept_budget`) - expect(builtQuery.select[3]).toHaveProperty(`sum_salary`) - expect(builtQuery.select[3]).toHaveProperty(`upper_name`) + // Non-null assertions since we've already checked the length + expect(builtQuery.select![0]).toBe(`@e.id`) + expect(builtQuery.select![1]).toBe(`@e.name`) + expect(builtQuery.select![2]).toBe(`@d.name`) + expect(builtQuery.select![3]).toHaveProperty(`dept_budget`) + expect(builtQuery.select![3]).toHaveProperty(`sum_salary`) + expect(builtQuery.select![3]).toHaveProperty(`upper_name`) }) }) diff --git a/packages/db/tests/query/query-collection.test.ts b/packages/db/tests/query/query-collection.test.ts index 831e92de6..77746b0f4 100644 --- a/packages/db/tests/query/query-collection.test.ts +++ b/packages/db/tests/query/query-collection.test.ts @@ -218,7 +218,7 @@ describe(`Query Collections`, () => { const query = queryBuilder() .from({ person: collection }) - .where(({ person }) => person.age > 30) + .where(({ person }) => (person.age ?? 0) > 30) const compiledQuery = compileQuery(query) @@ -1137,13 +1137,20 @@ describe(`Query Collections`, () => { const query = queryBuilder() .from({ collection }) - .select(({ collection: result }) => ({ - displayName: `${result.name} (Age: ${result.age})`, - status: result.isActive ? `Active` : `Inactive`, - ageGroup: - result.age < 30 ? `Young` : result.age < 40 ? `Middle` : `Senior`, - emailDomain: result.email.split(`@`)[1], - })) + .select(({ collection: result }) => { + return { + displayName: `${result.name} (Age: ${result.age})`, + status: result.isActive ? `Active` : `Inactive`, + ageGroup: result.age + ? result.age < 30 + ? `Young` + : result.age < 40 + ? `Middle` + : `Senior` + : `missing age`, + emailDomain: result.email.split(`@`)[1], + } + }) const compiledQuery = compileQuery(query) diff --git a/packages/db/tests/query/query-types.test.ts b/packages/db/tests/query/query-types.test.ts index f93bac5a9..2a8e0355d 100644 --- a/packages/db/tests/query/query-types.test.ts +++ b/packages/db/tests/query/query-types.test.ts @@ -40,26 +40,30 @@ describe(`Query Type System`, () => { // These won't run at runtime but will cause TypeScript errors if the types don't match // Valid basic query -const basicQuery = { +// @ts-expect-error - Unused variable for type checking +const _basicQuery = { select: [`@id`, `@name`], from: `users`, } satisfies Query // Valid query with aliased columns -const aliasedQuery = { +// @ts-expect-error - Unused variable for type checking +const _aliasedQuery = { select: [`@id`, { full_name: `@name` }], from: `users`, } satisfies Query // Valid query with simple WHERE condition -const simpleWhereQuery = { +// @ts-expect-error - Unused variable for type checking +const _simpleWhereQuery = { select: [`@id`, `@name`], from: `users`, where: [[`@age`, `>`, 18] as SimpleCondition], } satisfies Query // Valid query with flat composite WHERE condition -const compositeWhereQuery = { +// @ts-expect-error - Unused variable for type checking +const _compositeWhereQuery = { select: [`@id`, `@name`], from: `users`, where: [ @@ -76,7 +80,8 @@ const compositeWhereQuery = { } satisfies Query // Full query with all optional properties -const fullQuery = { +// @ts-expect-error - Unused variable for type checking +const _fullQuery = { select: [`@id`, `@name`, { age_years: `@age` }], as: `user_data`, from: `users`, @@ -90,7 +95,8 @@ const fullQuery = { // Condition type checking const simpleCondition: SimpleCondition = [`@age`, `>`, 18] -const simpleCond: Condition = simpleCondition +// @ts-expect-error - Unused variable for type checking +const _simpleCond: Condition = simpleCondition // Flat composite condition const flatCompositeCondition: FlatCompositeCondition = [ @@ -102,7 +108,8 @@ const flatCompositeCondition: FlatCompositeCondition = [ `=`, true, ] -const flatCompCond: Condition = flatCompositeCondition +// @ts-expect-error - Unused variable for type checking +const _flatCompCond: Condition = flatCompositeCondition // Nested composite condition const nestedCompositeCondition = [ @@ -110,33 +117,56 @@ const nestedCompositeCondition = [ `and` as LogicalOperator, [`@active`, `=`, true] as SimpleCondition, ] as [SimpleCondition, LogicalOperator, SimpleCondition] -const nestedCompCond: Condition = nestedCompositeCondition +// @ts-expect-error - Unused variable for type checking +const _nestedCompCond: Condition = nestedCompositeCondition // The code below demonstrates type compatibility for ConditionOperand // If TypeScript compiles this file, then these assignments work -const operand1: ConditionOperand = `string literal` -const operand2: ConditionOperand = 42 -const operand3: ConditionOperand = true -const operand4: ConditionOperand = null -const operand5: ConditionOperand = undefined -const operand6: ConditionOperand = `@department` -const operand7: ConditionOperand = { col: `department` } -const operand8: ConditionOperand = { value: { nested: `object` } } +// These variables are intentionally unused as they're just for type checking +// @ts-expect-error - Unused variable for type checking +const _operand1: ConditionOperand = `string literal` +// @ts-expect-error - Unused variable for type checking +const _operand2: ConditionOperand = 42 +// @ts-expect-error - Unused variable for type checking +const _operand3: ConditionOperand = true +// @ts-expect-error - Unused variable for type checking +const _operand4: ConditionOperand = null +// @ts-expect-error - Unused variable for type checking +const _operand5: ConditionOperand = undefined +// @ts-expect-error - Unused variable for type checking +const _operand6: ConditionOperand = `@department` +// @ts-expect-error - Unused variable for type checking +const _operand7: ConditionOperand = { col: `department` } +// @ts-expect-error - Unused variable for type checking +const _operand8: ConditionOperand = { value: { nested: `object` } } // The code below demonstrates type compatibility for Comparator // If TypeScript compiles this file, then these assignments work -const comp1: Comparator = `=` -const comp2: Comparator = `!=` -const comp3: Comparator = `<` -const comp4: Comparator = `<=` -const comp5: Comparator = `>` -const comp6: Comparator = `>=` -const comp7: Comparator = `like` -const comp8: Comparator = `not like` -const comp9: Comparator = `in` -const comp10: Comparator = `not in` -const comp11: Comparator = `is` -const comp12: Comparator = `is not` +// These variables are intentionally unused as they're just for type checking +// @ts-expect-error - Unused variable for type checking +const _comp1: Comparator = `=` +// @ts-expect-error - Unused variable for type checking +const _comp2: Comparator = `!=` +// @ts-expect-error - Unused variable for type checking +const _comp3: Comparator = `<` +// @ts-expect-error - Unused variable for type checking +const _comp4: Comparator = `<=` +// @ts-expect-error - Unused variable for type checking +const _comp5: Comparator = `>` +// @ts-expect-error - Unused variable for type checking +const _comp6: Comparator = `>=` +// @ts-expect-error - Unused variable for type checking +const _comp7: Comparator = `like` +// @ts-expect-error - Unused variable for type checking +const _comp8: Comparator = `not like` +// @ts-expect-error - Unused variable for type checking +const _comp9: Comparator = `in` +// @ts-expect-error - Unused variable for type checking +const _comp10: Comparator = `not in` +// @ts-expect-error - Unused variable for type checking +const _comp11: Comparator = `is` +// @ts-expect-error - Unused variable for type checking +const _comp12: Comparator = `is not` // The following lines would fail type checking if uncommented: diff --git a/packages/db/tests/query/types.test.ts b/packages/db/tests/query/types.test-d.ts similarity index 100% rename from packages/db/tests/query/types.test.ts rename to packages/db/tests/query/types.test-d.ts diff --git a/packages/db/tests/transaction-types.test.ts b/packages/db/tests/transaction-types.test.ts index 8d9dc6578..0996d2db6 100644 --- a/packages/db/tests/transaction-types.test.ts +++ b/packages/db/tests/transaction-types.test.ts @@ -1,41 +1,49 @@ import { describe, expect, it } from "vitest" +import type { Collection } from "../src/collection" import type { - MutationFactoryConfig, + MutationFn, PendingMutation, Transaction, TransactionConfig, - TransactionState, } from "../src/types" describe(`Transaction Types`, () => { - it(`should validate PendingMutation structure with collectionId`, () => { + it(`should validate PendingMutation structure with collection`, () => { + // Create a mock collection + const mockCollection = {} as Collection<{ id: number; name: string }> + // Type assertion test - this will fail at compile time if the type is incorrect - const pendingMutation: PendingMutation = { + const pendingMutation: PendingMutation<{ id: number; name: string }> = { mutationId: `test-mutation-1`, original: { id: 1, name: `Original` }, modified: { id: 1, name: `Modified` }, changes: { name: `Modified` }, key: `1`, + globalKey: `1`, type: `update`, metadata: null, syncMetadata: {}, createdAt: new Date(), updatedAt: new Date(), - collectionId: `users`, // New required field + collection: mockCollection, } - expect(pendingMutation.collectionId).toBe(`users`) + expect(pendingMutation.collection).toBe(mockCollection) expect(pendingMutation.type).toBe(`update`) }) it(`should validate TransactionConfig structure`, () => { - // Empty config is valid - const minimalConfig: TransactionConfig = {} + // Minimal config with required mutationFn + const mockMutationFn: MutationFn = async () => Promise.resolve() + const minimalConfig: TransactionConfig = { + mutationFn: mockMutationFn, + } expect(minimalConfig).toBeDefined() // Full config const fullConfig: TransactionConfig = { id: `custom-transaction-id`, + mutationFn: mockMutationFn, metadata: { source: `user-form` }, } @@ -44,36 +52,36 @@ describe(`Transaction Types`, () => { }) it(`should validate Transaction structure`, () => { - const mockToObject = () => ({ - id: `test-transaction`, - state: `pending` as TransactionState, - createdAt: new Date(), - updatedAt: new Date(), - mutations: [], - metadata: {}, - }) + // Create a mock Transaction object with all required properties + const mockMutationFn: MutationFn = async () => Promise.resolve() - const transaction: Transaction = { + // Create a complete mock Transaction object with all required methods + const transaction = { id: `test-transaction`, state: `pending`, createdAt: new Date(), - updatedAt: new Date(), mutations: [], metadata: {}, - toObject: mockToObject, - } + mutationFn: mockMutationFn, + isPersisted: false, + autoCommit: false, + setState: () => {}, + commit: async () => Promise.resolve(), + rollback: async () => Promise.resolve(), + reset: () => {}, + addMutation: () => {}, + // Add missing methods + mutate: async () => Promise.resolve(), + applyMutations: () => {}, + touchCollection: () => {}, + } as unknown as Transaction expect(transaction.id).toBe(`test-transaction`) - - // Test toObject method - const plainObject = transaction.toObject() - expect(plainObject.id).toBe(`test-transaction`) - expect(plainObject).not.toHaveProperty(`toObject`) }) - it(`should validate MutationFactoryConfig structure`, () => { + it(`should validate TransactionConfig with metadata`, () => { // Minimal config with just the required mutationFn - const minimalConfig: MutationFactoryConfig = { + const minimalConfig: TransactionConfig = { mutationFn: async () => { return Promise.resolve({ success: true }) }, @@ -82,7 +90,7 @@ describe(`Transaction Types`, () => { expect(typeof minimalConfig.mutationFn).toBe(`function`) // Full config with metadata - const fullConfig: MutationFactoryConfig = { + const fullConfig: TransactionConfig = { mutationFn: async () => { return Promise.resolve({ success: true }) }, diff --git a/packages/db/tests/transactions.test.ts b/packages/db/tests/transactions.test.ts index 03846e728..f1b466eaf 100644 --- a/packages/db/tests/transactions.test.ts +++ b/packages/db/tests/transactions.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest" import { createTransaction } from "../src/transactions" -import { Collection, createCollection } from "../src/collection" +import { createCollection } from "../src/collection" describe(`Transactions`, () => { it(`calling createTransaction creates a transaction`, () => { diff --git a/packages/db/tests/utility-exposure.test.ts b/packages/db/tests/utility-exposure.test.ts index 0aa55c7a6..8317ec7b7 100644 --- a/packages/db/tests/utility-exposure.test.ts +++ b/packages/db/tests/utility-exposure.test.ts @@ -12,10 +12,10 @@ describe(`Utility exposure pattern`, () => { test(`exposes utilities at top level and under .utils namespace`, () => { // Create mock utility functions const testFn = (input: string) => `processed: ${input}` - const asyncFn = (input: number) => input * 2 + const asyncFn = (input: number) => Promise.resolve(input * 2) // Create a mock sync config - const mockSync: SyncConfig = { + const mockSync: SyncConfig<{ id: string }> = { sync: () => { return { unsubscribe: () => {}, @@ -25,7 +25,7 @@ describe(`Utility exposure pattern`, () => { // Create collection options with utilities const options: CollectionConfig<{ id: string }> & { utils: TestUtils } = { - getId: (item) => item.id, + getKey: (item) => item.id, sync: mockSync, utils: { testFn, @@ -45,7 +45,7 @@ describe(`Utility exposure pattern`, () => { test(`supports collections without utilities`, () => { // Create a mock sync config - const mockSync: SyncConfig = { + const mockSync: SyncConfig<{ id: string }> = { sync: () => { return { unsubscribe: () => {}, @@ -55,7 +55,7 @@ describe(`Utility exposure pattern`, () => { // Create collection without utilities const collection = createCollection({ - getId: (item: { id: string }) => item.id, + getKey: (item: { id: string }) => item.id, sync: mockSync, }) @@ -74,21 +74,15 @@ describe(`Utility exposure pattern`, () => { // Create mock utility functions const testFn = (input: string) => `processed: ${input}` - const asyncFn = (input: number) => input * 2 - - // Create a mock sync config - const mockSync: SyncConfig = { - sync: () => { - return { - unsubscribe: () => {}, - } - }, - } + // eslint-disable-next-line + const asyncFn = async (input: number) => input * 2 // Create collection options with utilities const options: CollectionConfig & { utils: TestUtils } = { - getId: (item) => item.id, - sync: mockSync, + getKey: (item) => item.id, + sync: { + sync: () => {}, + }, utils: { testFn, asyncFn, @@ -96,10 +90,9 @@ describe(`Utility exposure pattern`, () => { } // Create collection with utilities - const collection = createCollection(options) - - // Create an item for the collection - const testItem: TestItem = { id: `1`, name: `Test`, value: 42 } + const collection = createCollection( + options + ) // Let's verify utilities work with the collection expect(collection.utils.testFn(`test`)).toBe(`processed: test`) diff --git a/packages/vue-db/tests/useLiveQuery.test.ts b/packages/vue-db/tests/useLiveQuery.test.ts index 5c5d622a4..f24b4574b 100644 --- a/packages/vue-db/tests/useLiveQuery.test.ts +++ b/packages/vue-db/tests/useLiveQuery.test.ts @@ -110,11 +110,7 @@ describe(`Query Collections`, () => { })) ) - const { - state, - data, - collection: qColl, - } = useLiveQuery((q) => + const { state, data } = useLiveQuery((q) => q .from({ collection }) .where(`@age`, `>`, 30) @@ -298,7 +294,7 @@ describe(`Query Collections`, () => { })) ) - const { state, collection: qColl } = useLiveQuery((q) => + const { state } = useLiveQuery((q) => q .from({ issues: issueCollection }) .join({ @@ -429,7 +425,7 @@ describe(`Query Collections`, () => { const minAge = ref(30) - const { state, collection: qColl } = useLiveQuery((q) => { + const { state } = useLiveQuery((q) => { return q .from({ collection }) .where(`@age`, `>`, minAge.value) @@ -751,7 +747,7 @@ describe(`Query Collections`, () => { ) // Render the hook with a query that joins persons and issues - const { state, collection: qColl } = useLiveQuery((q) => + const { state } = useLiveQuery((q) => q .from({ issues: issueCollection }) .join({ diff --git a/tsconfig.json b/tsconfig.json index c48c73fd9..7814866aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,7 @@ "noImplicitReturns": true, "noUncheckedIndexedAccess": true, "noUnusedLocals": true, - "noUnusedParameters": true, + "noUnusedParameters": false, // Let ESLint handle this "resolveJsonModule": true, "skipLibCheck": true, "strict": true,