diff --git a/packages/db/src/local-only.ts b/packages/db/src/local-only.ts index d6590c61..19725bf2 100644 --- a/packages/db/src/local-only.ts +++ b/packages/db/src/local-only.ts @@ -3,6 +3,7 @@ import type { DeleteMutationFnParams, InsertMutationFnParams, OperationType, + ResolveInsertInput, ResolveType, SyncConfig, UpdateMutationFnParams, @@ -26,7 +27,7 @@ import type { StandardSchemaV1 } from "@standard-schema/spec" * You should provide EITHER an explicit type OR a schema, but not both, as they would conflict. */ export interface LocalOnlyCollectionConfig< - TExplicit = unknown, + TExplicit extends object = Record, TSchema extends StandardSchemaV1 = never, TFallback extends Record = Record, TKey extends string | number = string | number, @@ -51,7 +52,7 @@ export interface LocalOnlyCollectionConfig< */ onInsert?: ( params: InsertMutationFnParams< - ResolveType, + ResolveInsertInput, TKey, LocalOnlyCollectionUtils > @@ -136,33 +137,56 @@ export interface LocalOnlyCollectionUtils extends UtilsRecord {} * ) */ export function localOnlyCollectionOptions< - TExplicit = unknown, + TExplicit extends object = Record, TSchema extends StandardSchemaV1 = never, TFallback extends Record = Record, TKey extends string | number = string | number, >( config: LocalOnlyCollectionConfig -): CollectionConfig, TKey> & { +): CollectionConfig< + ResolveType, + TKey, + TSchema, + ResolveInsertInput +> & { utils: LocalOnlyCollectionUtils } { - type ResolvedType = ResolveType + type TItem = ResolveType + type TInsertInput = ResolveInsertInput const { initialData, onInsert, onUpdate, onDelete, ...restConfig } = config // Create the sync configuration with transaction confirmation capability - const syncResult = createLocalOnlySync(initialData) + const syncResult = createLocalOnlySync( + initialData as Array | undefined + ) /** * Create wrapper handlers that call user handlers first, then confirm transactions * Wraps the user's onInsert handler to also confirm the transaction immediately */ const wrappedOnInsert = async ( - params: InsertMutationFnParams + params: InsertMutationFnParams ) => { // Call user handler first if provided let handlerResult if (onInsert) { - handlerResult = (await onInsert(params)) ?? {} + handlerResult = + (await ( + onInsert as ( + p: InsertMutationFnParams< + TInsertInput, + TKey, + LocalOnlyCollectionUtils + > + ) => Promise + )( + params as unknown as InsertMutationFnParams< + TInsertInput, + TKey, + LocalOnlyCollectionUtils + > + )) ?? {} } // Then synchronously confirm the transaction by looping through mutations @@ -175,7 +199,7 @@ export function localOnlyCollectionOptions< * Wrapper for onUpdate handler that also confirms the transaction immediately */ const wrappedOnUpdate = async ( - params: UpdateMutationFnParams + params: UpdateMutationFnParams ) => { // Call user handler first if provided let handlerResult @@ -193,7 +217,7 @@ export function localOnlyCollectionOptions< * Wrapper for onDelete handler that also confirms the transaction immediately */ const wrappedOnDelete = async ( - params: DeleteMutationFnParams + params: DeleteMutationFnParams ) => { // Call user handler first if provided let handlerResult diff --git a/packages/db/src/local-storage.ts b/packages/db/src/local-storage.ts index 43dfc5af..c1e63818 100644 --- a/packages/db/src/local-storage.ts +++ b/packages/db/src/local-storage.ts @@ -9,7 +9,9 @@ import { import type { CollectionConfig, DeleteMutationFnParams, + InsertMutationFn, InsertMutationFnParams, + ResolveInsertInput, ResolveType, SyncConfig, UpdateMutationFnParams, @@ -62,6 +64,7 @@ export interface LocalStorageCollectionConfig< TExplicit = unknown, TSchema extends StandardSchemaV1 = never, TFallback extends object = Record, + TKey extends string | number = string | number, > { /** * The key to use for storing the collection data in localStorage/sessionStorage @@ -85,8 +88,18 @@ export interface LocalStorageCollectionConfig< */ id?: string schema?: TSchema - getKey: CollectionConfig>[`getKey`] - sync?: CollectionConfig>[`sync`] + getKey: CollectionConfig< + ResolveType, + TKey, + TSchema, + ResolveInsertInput + >[`getKey`] + sync?: CollectionConfig< + ResolveType, + TKey, + TSchema, + ResolveInsertInput + >[`sync`] /** * Optional asynchronous handler function called before an insert operation @@ -94,7 +107,10 @@ export interface LocalStorageCollectionConfig< * @returns Promise resolving to any value */ onInsert?: ( - params: InsertMutationFnParams> + params: InsertMutationFnParams< + ResolveInsertInput, + TKey + > ) => Promise /** @@ -103,7 +119,10 @@ export interface LocalStorageCollectionConfig< * @returns Promise resolving to any value */ onUpdate?: ( - params: UpdateMutationFnParams> + params: UpdateMutationFnParams< + ResolveType, + TKey + > ) => Promise /** @@ -112,7 +131,10 @@ export interface LocalStorageCollectionConfig< * @returns Promise resolving to any value */ onDelete?: ( - params: DeleteMutationFnParams> + params: DeleteMutationFnParams< + ResolveType, + TKey + > ) => Promise } @@ -206,13 +228,23 @@ export function localStorageCollectionOptions< TExplicit = unknown, TSchema extends StandardSchemaV1 = never, TFallback extends object = Record, + TKey extends string | number = string | number, >( - config: LocalStorageCollectionConfig -): Omit>, `id`> & { + config: LocalStorageCollectionConfig +): Omit< + CollectionConfig< + ResolveType, + TKey, + TSchema, + ResolveInsertInput + >, + `id` +> & { id: string utils: LocalStorageCollectionUtils } { - type ResolvedType = ResolveType + type TItem = ResolveType + type TInsertInput = ResolveInsertInput // Validate required parameters if (!config.storageKey) { @@ -237,14 +269,14 @@ export function localStorageCollectionOptions< } // Track the last known state to detect changes - const lastKnownData = new Map>() + const lastKnownData = new Map>() // Create the sync configuration - const sync = createLocalStorageSync( + const sync = createLocalStorageSync( config.storageKey, storage, storageEventApi, - config.getKey, + config.getKey as (item: TItem) => TKey, lastKnownData ) @@ -263,11 +295,11 @@ export function localStorageCollectionOptions< * @param dataMap - Map of items with version tracking to save to storage */ const saveToStorage = ( - dataMap: Map> + dataMap: Map> ): void => { try { // Convert Map to object format for storage - const objectData: Record> = {} + const objectData: Record> = {} dataMap.forEach((storedItem, key) => { objectData[String(key)] = storedItem }) @@ -303,7 +335,7 @@ export function localStorageCollectionOptions< * Wraps the user's onInsert handler to also save changes to localStorage */ const wrappedOnInsert = async ( - params: InsertMutationFnParams + params: InsertMutationFnParams ) => { // Validate that all values in the transaction can be JSON serialized params.transaction.mutations.forEach((mutation) => { @@ -313,20 +345,20 @@ export function localStorageCollectionOptions< // Call the user handler BEFORE persisting changes (if provided) let handlerResult: any = {} if (config.onInsert) { - handlerResult = (await config.onInsert(params)) ?? {} + handlerResult = + (await (config.onInsert as InsertMutationFn)( + params as unknown as InsertMutationFnParams + )) ?? {} } // Always persist to storage // Load current data from storage - const currentData = loadFromStorage( - config.storageKey, - storage - ) + const currentData = loadFromStorage(config.storageKey, storage) // Add new items with version keys params.transaction.mutations.forEach((mutation) => { - const key = config.getKey(mutation.modified) - const storedItem: StoredItem = { + const key = (config.getKey as (item: TItem) => TKey)(mutation.modified) + const storedItem: StoredItem = { versionKey: generateUuid(), data: mutation.modified, } @@ -343,7 +375,7 @@ export function localStorageCollectionOptions< } const wrappedOnUpdate = async ( - params: UpdateMutationFnParams + params: UpdateMutationFnParams ) => { // Validate that all values in the transaction can be JSON serialized params.transaction.mutations.forEach((mutation) => { @@ -358,15 +390,12 @@ export function localStorageCollectionOptions< // Always persist to storage // Load current data from storage - const currentData = loadFromStorage( - config.storageKey, - storage - ) + const currentData = loadFromStorage(config.storageKey, storage) // Update items with new version keys params.transaction.mutations.forEach((mutation) => { - const key = config.getKey(mutation.modified) - const storedItem: StoredItem = { + const key = (config.getKey as (item: TItem) => TKey)(mutation.modified) + const storedItem: StoredItem = { versionKey: generateUuid(), data: mutation.modified, } @@ -383,7 +412,7 @@ export function localStorageCollectionOptions< } const wrappedOnDelete = async ( - params: DeleteMutationFnParams + params: DeleteMutationFnParams ) => { // Call the user handler BEFORE persisting changes (if provided) let handlerResult: any = {} @@ -393,15 +422,14 @@ export function localStorageCollectionOptions< // Always persist to storage // Load current data from storage - const currentData = loadFromStorage( - config.storageKey, - storage - ) + const currentData = loadFromStorage(config.storageKey, storage) // Remove items params.transaction.mutations.forEach((mutation) => { // For delete operations, mutation.original contains the full object - const key = config.getKey(mutation.original as ResolvedType) + const key = (config.getKey as (item: TItem) => TKey)( + mutation.original as TItem + ) currentData.delete(key) }) @@ -433,7 +461,10 @@ export function localStorageCollectionOptions< ...restConfig, id: collectionId, sync, - onInsert: wrappedOnInsert, + onInsert: wrappedOnInsert as unknown as InsertMutationFn< + TInsertInput, + TKey + >, onUpdate: wrappedOnUpdate, onDelete: wrappedOnDelete, utils: { @@ -506,14 +537,17 @@ function loadFromStorage( * @param lastKnownData - Map tracking the last known state for change detection * @returns Sync configuration with manual trigger capability */ -function createLocalStorageSync( +function createLocalStorageSync< + T extends object, + TKey extends string | number = string | number, +>( storageKey: string, storage: StorageApi, storageEventApi: StorageEventApi, - _getKey: (item: T) => string | number, + _getKey: (item: T) => TKey, lastKnownData: Map> -): SyncConfig & { manualTrigger?: () => void } { - let syncParams: Parameters[`sync`]>[0] | null = null +): SyncConfig & { manualTrigger?: () => void } { + let syncParams: Parameters[`sync`]>[0] | null = null /** * Compare two Maps to find differences using version keys @@ -588,8 +622,8 @@ function createLocalStorageSync( } } - const syncConfig: SyncConfig & { manualTrigger?: () => void } = { - sync: (params: Parameters[`sync`]>[0]) => { + const syncConfig: SyncConfig & { manualTrigger?: () => void } = { + sync: (params: Parameters[`sync`]>[0]) => { const { begin, write, commit, markReady } = params // Store sync params for later use diff --git a/packages/db/tests/collection.test-d.ts b/packages/db/tests/collection.test-d.ts index 0faa6834..01cca41a 100644 --- a/packages/db/tests/collection.test-d.ts +++ b/packages/db/tests/collection.test-d.ts @@ -2,7 +2,11 @@ import { assertType, describe, expectTypeOf, it } from "vitest" import { z } from "zod" import { createCollection } from "../src/collection" import type { CollectionImpl } from "../src/collection" -import type { OperationConfig, ResolveType } from "../src/types" +import type { + OperationConfig, + ResolveInsertInput, + ResolveType, +} from "../src/types" import type { StandardSchemaV1 } from "@standard-schema/spec" describe(`Collection.update type tests`, () => { @@ -195,3 +199,115 @@ describe(`Collection type resolution tests`, () => { expectTypeOf>().toEqualTypeOf() }) }) + +describe(`Schema Input/Output Type Distinction`, () => { + // Define schema with different input/output types + const userSchemaWithDefaults = z.object({ + id: z.string(), + name: z.string(), + email: z.string().email(), + created_at: z.date().default(() => new Date()), + updated_at: z.date().default(() => new Date()), + }) + + // Define schema with transformations + const userSchemaTransform = z.object({ + id: z.string(), + name: z.string(), + email: z.string().email(), + created_at: z.string().transform((val) => new Date(val)), + updated_at: z.string().transform((val) => new Date(val)), + }) + + it(`should handle schema with default values correctly`, () => { + const collection = createCollection({ + getKey: (item) => item.id, + sync: { sync: () => {} }, + schema: userSchemaWithDefaults, + }) + + type ExpectedOutputType = ResolveType< + unknown, + typeof userSchemaWithDefaults, + Record + > + type ExpectedInputType = ResolveInsertInput< + unknown, + typeof userSchemaWithDefaults, + Record + > + type InsertArg = Parameters[0] + + // Input type should not include defaulted fields + expectTypeOf().toEqualTypeOf<{ + id: string + name: string + email: string + created_at?: Date + updated_at?: Date + }>() + + // Output type should include all fields + expectTypeOf().toEqualTypeOf<{ + id: string + name: string + email: string + created_at: Date + updated_at: Date + }>() + + // Insert should accept ExpectedInputType or array thereof + expectTypeOf().toEqualTypeOf< + ExpectedInputType | Array + >() + + // Collection items should be ExpectedOutputType + expectTypeOf(collection.toArray).toEqualTypeOf>() + }) + + it(`should handle schema with transformations correctly`, () => { + const collection = createCollection({ + getKey: (item) => item.id, + sync: { sync: () => {} }, + schema: userSchemaTransform, + }) + + type ExpectedInputType = ResolveInsertInput< + unknown, + typeof userSchemaTransform, + Record + > + type ExpectedOutputType = ResolveType< + unknown, + typeof userSchemaTransform, + Record + > + type InsertArg = Parameters[0] + + // Input type should be the raw input (before transformation) + expectTypeOf().toEqualTypeOf<{ + id: string + name: string + email: string + created_at: string + updated_at: string + }>() + + // Output type should be the transformed output + expectTypeOf().toEqualTypeOf<{ + id: string + name: string + email: string + created_at: Date + updated_at: Date + }>() + + // Insert should accept ExpectedInputType or array thereof + expectTypeOf().toEqualTypeOf< + ExpectedInputType | Array + >() + + // Collection items should be ExpectedOutputType + expectTypeOf(collection.toArray).toEqualTypeOf>() + }) +}) diff --git a/packages/electric-db-collection/src/electric.ts b/packages/electric-db-collection/src/electric.ts index d80f46bc..1ddfbad1 100644 --- a/packages/electric-db-collection/src/electric.ts +++ b/packages/electric-db-collection/src/electric.ts @@ -286,7 +286,14 @@ export function electricCollectionOptions< TExplicit extends Row = Row, TSchema extends StandardSchemaV1 = never, TFallback extends Row = Row, ->(config: ElectricCollectionConfig) { +>( + config: ElectricCollectionConfig +): CollectionConfig< + ResolveType, + string | number, + TSchema, + ResolveType +> & { utils: ElectricCollectionUtils } { const seenTxids = new Store>(new Set([])) const sync = createElectricSync>( config.shapeOptions, diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index e886f0d2..38bf316a 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -3,7 +3,8 @@ "description": "TanStack Query collection for TanStack DB", "version": "0.2.1", "dependencies": { - "@tanstack/db": "workspace:*" + "@tanstack/db": "workspace:*", + "@standard-schema/spec": "^1.0.0" }, "devDependencies": { "@tanstack/query-core": "^5.0.5", diff --git a/packages/query-db-collection/src/query.ts b/packages/query-db-collection/src/query.ts index 1a3074fc..3c1d165d 100644 --- a/packages/query-db-collection/src/query.ts +++ b/packages/query-db-collection/src/query.ts @@ -19,11 +19,14 @@ import type { DeleteMutationFnParams, InsertMutationFn, InsertMutationFnParams, + ResolveInsertInput, + ResolveType, SyncConfig, UpdateMutationFn, UpdateMutationFnParams, UtilsRecord, } from "@tanstack/db" +import type { StandardSchemaV1 } from "@standard-schema/spec" // Re-export for external use export type { SyncOperation } from "./manual-sync" @@ -35,14 +38,19 @@ export type { SyncOperation } from "./manual-sync" * @template TQueryKey - The type of the query key */ export interface QueryCollectionConfig< - TItem extends object, + TExplicit extends object = Record, + TSchema extends StandardSchemaV1 = never, + TFallback extends object = Record, TError = unknown, TQueryKey extends QueryKey = QueryKey, + TKey extends string | number = string | number, > { /** The query key used by TanStack Query to identify this query */ queryKey: TQueryKey /** Function that fetches data from the server. Must return the complete collection state */ - queryFn: (context: QueryFunctionContext) => Promise> + queryFn: ( + context: QueryFunctionContext + ) => Promise>> /** The TanStack Query client instance */ queryClient: QueryClient @@ -50,31 +58,31 @@ export interface QueryCollectionConfig< /** Whether the query should automatically run (default: true) */ enabled?: boolean refetchInterval?: QueryObserverOptions< - Array, + Array>, TError, - Array, - Array, + Array>, + Array>, TQueryKey >[`refetchInterval`] retry?: QueryObserverOptions< - Array, + Array>, TError, - Array, - Array, + Array>, + Array>, TQueryKey >[`retry`] retryDelay?: QueryObserverOptions< - Array, + Array>, TError, - Array, - Array, + Array>, + Array>, TQueryKey >[`retryDelay`] staleTime?: QueryObserverOptions< - Array, + Array>, TError, - Array, - Array, + Array>, + Array>, TQueryKey >[`staleTime`] @@ -82,11 +90,16 @@ export interface QueryCollectionConfig< /** Unique identifier for the collection */ id?: string /** Function to extract the unique key from an item */ - getKey: CollectionConfig[`getKey`] + getKey: CollectionConfig< + ResolveType, + TKey + >[`getKey`] /** Schema for validating items */ - schema?: CollectionConfig[`schema`] - sync?: CollectionConfig[`sync`] - startSync?: CollectionConfig[`startSync`] + schema?: TSchema + sync?: CollectionConfig>[`sync`] + startSync?: CollectionConfig< + ResolveType + >[`startSync`] // Direct persistence handlers /** @@ -129,7 +142,10 @@ export interface QueryCollectionConfig< * } * } */ - onInsert?: InsertMutationFn + onInsert?: InsertMutationFn< + ResolveInsertInput, + TKey + > /** * Optional asynchronous handler function called before an update operation @@ -182,7 +198,7 @@ export interface QueryCollectionConfig< * return { refetch: false } // Skip automatic refetch since we handled it manually * } */ - onUpdate?: UpdateMutationFn + onUpdate?: UpdateMutationFn, TKey> /** * Optional asynchronous handler function called before a delete operation @@ -228,7 +244,7 @@ export interface QueryCollectionConfig< * return { refetch: false } // Skip automatic refetch since we handled it manually * } */ - onDelete?: DeleteMutationFn + onDelete?: DeleteMutationFn, TKey> // TODO type returning { refetch: boolean } /** @@ -324,16 +340,35 @@ export interface QueryCollectionUtils< * ) */ export function queryCollectionOptions< - TItem extends object, + TExplicit extends object = Record, + TSchema extends StandardSchemaV1 = never, + TFallback extends object = Record, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number, - TInsertInput extends object = TItem, >( - config: QueryCollectionConfig -): CollectionConfig & { - utils: QueryCollectionUtils + config: QueryCollectionConfig< + TExplicit, + TSchema, + TFallback, + TError, + TQueryKey, + TKey + > +): CollectionConfig< + ResolveType, + TKey, + TSchema, + ResolveInsertInput +> & { + utils: QueryCollectionUtils< + ResolveType, + TKey, + ResolveInsertInput + > } { + type TItem = ResolveType + type TInsertInput = ResolveInsertInput const { queryKey, queryFn, @@ -372,7 +407,7 @@ export function queryCollectionOptions< throw new GetKeyRequiredError() } - const internalSync: SyncConfig[`sync`] = (params) => { + const internalSync: SyncConfig[`sync`] = (params) => { const { begin, write, commit, markReady, collection } = params const observerOptions: QueryObserverOptions< @@ -418,7 +453,7 @@ export function queryCollectionOptions< } const currentSyncedItems = new Map(collection.syncedData) - const newItemsMap = new Map() + const newItemsMap = new Map() newItemsArray.forEach((item) => { const key = getKey(item) newItemsMap.set(key, item) @@ -512,12 +547,12 @@ export function queryCollectionOptions< queryKey: Array getKey: (item: TItem) => TKey begin: () => void - write: (message: Omit, `key`>) => void + write: (message: Omit, `key`>) => void commit: () => void } | null = null // Enhanced internalSync that captures write functions for manual use - const enhancedInternalSync: SyncConfig[`sync`] = (params) => { + const enhancedInternalSync: SyncConfig[`sync`] = (params) => { const { begin, write, commit, collection } = params // Store references for manual write operations @@ -542,7 +577,7 @@ export function queryCollectionOptions< // Create wrapper handlers for direct persistence operations that handle refetching const wrappedOnInsert = onInsert - ? async (params: InsertMutationFnParams) => { + ? async (params: InsertMutationFnParams) => { const handlerResult = (await onInsert(params)) ?? {} const shouldRefetch = (handlerResult as { refetch?: boolean }).refetch !== false @@ -556,7 +591,7 @@ export function queryCollectionOptions< : undefined const wrappedOnUpdate = onUpdate - ? async (params: UpdateMutationFnParams) => { + ? async (params: UpdateMutationFnParams) => { const handlerResult = (await onUpdate(params)) ?? {} const shouldRefetch = (handlerResult as { refetch?: boolean }).refetch !== false @@ -570,7 +605,7 @@ export function queryCollectionOptions< : undefined const wrappedOnDelete = onDelete - ? async (params: DeleteMutationFnParams) => { + ? async (params: DeleteMutationFnParams) => { const handlerResult = (await onDelete(params)) ?? {} const shouldRefetch = (handlerResult as { refetch?: boolean }).refetch !== false diff --git a/packages/query-db-collection/tests/query.test-d.ts b/packages/query-db-collection/tests/query.test-d.ts index eb5d09db..18819bb9 100644 --- a/packages/query-db-collection/tests/query.test-d.ts +++ b/packages/query-db-collection/tests/query.test-d.ts @@ -186,4 +186,33 @@ describe(`Query collection type resolution tests`, () => { // Test that the getKey function has the correct parameter type expectTypeOf(queryOptions.getKey).parameters.toEqualTypeOf<[UserType]>() }) + it(`types getKey param as schema OUTPUT and onInsert as schema INPUT`, () => { + const schema = z.object({ + id: z.string(), + created_at: z.string().transform((s) => new Date(s)), + updated_at: z.string().transform((s) => new Date(s)), + }) + + type Output = z.output + type Input = z.input + + const queryClient = new QueryClient() + + const options = queryCollectionOptions({ + queryClient, + queryKey: [`users`], + queryFn: () => Promise.resolve([] as Array), + schema, + getKey: (item) => item.id, + onInsert: async () => {}, + onUpdate: async () => {}, + }) + + expectTypeOf(options.getKey).parameters.toEqualTypeOf<[Output]>() + // utils.writeInsert should accept schema INPUT + type WriteInsertParam0 = Parameters< + (typeof options.utils)[`writeInsert`] + >[0] + expectTypeOf().toEqualTypeOf>() + }) }) diff --git a/packages/trailbase-db-collection/src/trailbase.ts b/packages/trailbase-db-collection/src/trailbase.ts index 88519ec1..a9c9f66e 100644 --- a/packages/trailbase-db-collection/src/trailbase.ts +++ b/packages/trailbase-db-collection/src/trailbase.ts @@ -84,11 +84,11 @@ function convertPartial< * Configuration interface for Trailbase Collection */ export interface TrailBaseCollectionConfig< - TItem extends ShapeOf, - TRecord extends ShapeOf = TItem, + TExplicit extends ShapeOf, + TRecord extends ShapeOf = TExplicit, TKey extends string | number = string | number, > extends Omit< - CollectionConfig, + CollectionConfig, `sync` | `onInsert` | `onUpdate` | `onDelete` > { /** @@ -96,8 +96,8 @@ export interface TrailBaseCollectionConfig< */ recordApi: RecordApi - parse: Conversions - serialize: Conversions + parse: Conversions + serialize: Conversions } export type AwaitTxIdFn = (txId: string, timeout?: number) => Promise @@ -107,20 +107,20 @@ export interface TrailBaseCollectionUtils extends UtilsRecord { } export function trailBaseCollectionOptions< - TItem extends ShapeOf, - TRecord extends ShapeOf = TItem, + TExplicit extends ShapeOf, + TRecord extends ShapeOf = TExplicit, TKey extends string | number = string | number, >( - config: TrailBaseCollectionConfig -): CollectionConfig & { utils: TrailBaseCollectionUtils } { + config: TrailBaseCollectionConfig +): CollectionConfig & { utils: TrailBaseCollectionUtils } { const getKey = config.getKey const parse = (record: TRecord) => - convert(config.parse, record) - const serialUpd = (item: Partial) => - convertPartial(config.serialize, item) - const serialIns = (item: TItem) => - convert(config.serialize, item) + convert(config.parse, record) + const serialUpd = (item: Partial) => + convertPartial(config.serialize, item) + const serialIns = (item: TExplicit) => + convert(config.serialize, item) const seenIds = new Store(new Map()) @@ -159,7 +159,7 @@ export function trailBaseCollectionOptions< } } - type SyncParams = Parameters[`sync`]>[0] + type SyncParams = Parameters[`sync`]>[0] const sync = { sync: (params: SyncParams) => { const { begin, write, commit, markReady } = params @@ -216,7 +216,7 @@ export function trailBaseCollectionOptions< } begin() - let value: TItem | undefined + let value: TExplicit | undefined if (`Insert` in event) { value = parse(event.Insert as TRecord) write({ type: `insert`, value }) @@ -294,7 +294,7 @@ export function trailBaseCollectionOptions< sync, getKey, onInsert: async ( - params: InsertMutationFnParams + params: InsertMutationFnParams ): Promise> => { const ids = await config.recordApi.createBulk( params.transaction.mutations.map((tx) => { @@ -313,7 +313,7 @@ export function trailBaseCollectionOptions< return ids }, - onUpdate: async (params: UpdateMutationFnParams) => { + onUpdate: async (params: UpdateMutationFnParams) => { const ids: Array = await Promise.all( params.transaction.mutations.map(async (tx) => { const { type, changes, key } = tx @@ -332,7 +332,7 @@ export function trailBaseCollectionOptions< // DB by the subscription. await awaitIds(ids) }, - onDelete: async (params: DeleteMutationFnParams) => { + onDelete: async (params: DeleteMutationFnParams) => { const ids: Array = await Promise.all( params.transaction.mutations.map(async (tx) => { const { type, key } = tx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0ea21e2..d6af0bd6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -539,6 +539,9 @@ importers: packages/query-db-collection: dependencies: + '@standard-schema/spec': + specifier: ^1.0.0 + version: 1.0.0 '@tanstack/db': specifier: workspace:* version: link:../db