UNPKG

@durable-streams/state

Version:
284 lines (280 loc) 9.46 kB
import { Collection, Collection as Collection$1, SyncConfig, and, avg, count, createCollection, createOptimisticAction, createOptimisticAction as createOptimisticAction$1, eq, gt, gte, ilike, inArray, isNull, isUndefined, like, lt, lte, max, min, not, or, sum } from "@tanstack/db"; import { DurableStream, DurableStreamOptions } from "@durable-streams/client"; import { StandardSchemaV1 } from "@standard-schema/spec"; //#region src/types.d.ts /** * Operation types for change events */ /** * Operation types for change events */ type Operation = `insert` | `update` | `delete` | `upsert`; /** * A generic value type supporting primitives, arrays, and objects */ type Value<Extensions = never> = string | number | boolean | bigint | null | Array<Value<Extensions>> | { [key: string]: Value<Extensions>; } | Extensions; /** * A row is a record of values */ type Row<Extensions = never> = Record<string, Value<Extensions>>; /** * Headers for change messages */ type ChangeHeaders = { operation: Operation; txid?: string; timestamp?: string; }; /** * A change event represents a state change event (insert/update/delete) */ type ChangeEvent<T = unknown> = { type: string; key: string; value?: T; old_value?: T; headers: ChangeHeaders; }; /** * Control event types for stream management */ type ControlEvent = { headers: { control: `snapshot-start` | `snapshot-end` | `reset`; offset?: string; }; }; /** * A state event is either a change event or a control event */ type StateEvent<T = unknown> = ChangeEvent<T> | ControlEvent; /** * Type guard to check if an event is a change event */ declare function isChangeEvent<T = unknown>(event: StateEvent<T>): event is ChangeEvent<T>; /** * Type guard to check if an event is a control event */ declare function isControlEvent<T = unknown>(event: StateEvent<T>): event is ControlEvent; //#endregion //#region src/materialized-state.d.ts /** * MaterializedState maintains an in-memory view of state from change events. * * It organizes data by type, where each type contains a map of key -> value. * This supports multi-type streams where different entity types can coexist. */ declare class MaterializedState { private data; constructor(); /** * Apply a single change event to update the materialized state */ apply(event: ChangeEvent): void; /** * Apply a batch of change events */ applyBatch(events: Array<ChangeEvent>): void; /** * Get a specific value by type and key */ get<T = unknown>(type: string, key: string): T | undefined; /** * Get all entries for a specific type */ getType(type: string): Map<string, unknown>; /** * Clear all state */ clear(): void; /** * Get the number of types in the state */ get typeCount(): number; /** * Get all type names */ get types(): Array<string>; } //#endregion //#region src/stream-db.d.ts /** * Definition for a single collection in the stream state */ interface CollectionDefinition<T = unknown> { /** Standard Schema for validating values */ schema: StandardSchemaV1<T>; /** The type field value in change events that map to this collection */ type: string; /** The property name in T that serves as the primary key */ primaryKey: string; } /** * Helper methods for creating change events for a collection */ interface CollectionEventHelpers<T> { /** * Create an insert change event */ insert: (params: { key?: string; value: T; headers?: Omit<Record<string, string>, `operation`>; }) => ChangeEvent<T>; /** * Create an update change event */ update: (params: { key?: string; value: T; oldValue?: T; headers?: Omit<Record<string, string>, `operation`>; }) => ChangeEvent<T>; /** * Create a delete change event */ delete: (params: { key?: string; oldValue?: T; headers?: Omit<Record<string, string>, `operation`>; }) => ChangeEvent<T>; /** * Create an upsert change event (insert or update) */ upsert: (params: { key?: string; value: T; headers?: Omit<Record<string, string>, `operation`>; }) => ChangeEvent<T>; } /** * Collection definition enhanced with event creation helpers */ type CollectionWithHelpers<T = unknown> = CollectionDefinition<T> & CollectionEventHelpers<T>; /** * Stream state definition containing all collections */ type StreamStateDefinition = Record<string, CollectionDefinition>; /** * Stream state schema with helper methods for creating change events */ type StateSchema<T extends Record<string, CollectionDefinition>> = { [K in keyof T]: CollectionWithHelpers<T[K] extends CollectionDefinition<infer U> ? U : unknown> }; /** * Definition for a single action that can be passed to createOptimisticAction */ interface ActionDefinition<TParams = any, TContext = any> { onMutate: (params: TParams) => void; mutationFn: (params: TParams, context: TContext) => Promise<any>; } /** * Factory function for creating actions with access to db and stream context */ type ActionFactory<TDef extends StreamStateDefinition, TActions extends Record<string, ActionDefinition<any>>> = (context: { db: StreamDB<TDef>; stream: DurableStream; }) => TActions; /** * Map action definitions to callable action functions */ type ActionMap<TActions extends Record<string, ActionDefinition<any>>> = { [K in keyof TActions]: ReturnType<typeof createOptimisticAction$1<any>> }; /** * Options for creating a stream DB */ interface CreateStreamDBOptions<TDef extends StreamStateDefinition = StreamStateDefinition, TActions extends Record<string, ActionDefinition<any>> = Record<string, never>> { /** Options for creating the durable stream (stream is created lazily on preload) */ streamOptions: DurableStreamOptions; /** The stream state definition */ state: TDef; /** Optional factory function to create actions with db and stream context */ actions?: ActionFactory<TDef, TActions>; } /** * Extract the value type from a CollectionDefinition */ type ExtractCollectionType<T extends CollectionDefinition> = T extends CollectionDefinition<infer U> ? U : unknown; /** * Map collection definitions to TanStack DB Collection types */ type CollectionMap<TDef extends StreamStateDefinition> = { [K in keyof TDef]: Collection$1<ExtractCollectionType<TDef[K]> & object, string> }; /** * The StreamDB interface - provides typed access to collections */ type StreamDB<TDef extends StreamStateDefinition> = { collections: CollectionMap<TDef>; } & StreamDBMethods; /** * StreamDB with actions */ type StreamDBWithActions<TDef extends StreamStateDefinition, TActions extends Record<string, ActionDefinition<any>>> = StreamDB<TDef> & { actions: ActionMap<TActions>; }; /** * Utility methods available on StreamDB */ interface StreamDBUtils { /** * Wait for a specific transaction ID to be synced through the stream * @param txid The transaction ID to wait for (UUID string) * @param timeout Optional timeout in milliseconds (defaults to 5000ms) * @returns Promise that resolves when the txid is synced */ awaitTxId: (txid: string, timeout?: number) => Promise<void>; } /** * Methods available on a StreamDB instance */ interface StreamDBMethods { /** * The underlying DurableStream instance */ stream: DurableStream; /** * Preload all collections by consuming the stream until up-to-date */ preload: () => Promise<void>; /** * Close the stream connection and cleanup */ close: () => void; /** * Utility methods for advanced stream operations */ utils: StreamDBUtils; } /** * Create a state schema definition with typed collections and event helpers */ declare function createStateSchema<T extends Record<string, CollectionDefinition>>(collections: T): StateSchema<T>; /** * Create a stream-backed database with TanStack DB collections * * This function is synchronous - it creates the stream handle and collections * but does not start the stream connection. Call `db.preload()` to connect * and sync initial data. * * @example * ```typescript * const stateSchema = createStateSchema({ * users: { schema: userSchema, type: "user", primaryKey: "id" }, * messages: { schema: messageSchema, type: "message", primaryKey: "id" }, * }) * * // Create a stream DB (synchronous - stream is created lazily on preload) * const db = createStreamDB({ * streamOptions: { * url: "https://api.example.com/streams/my-stream", * contentType: "application/json", * }, * state: stateSchema, * }) * * // preload() creates the stream and loads initial data * await db.preload() * const user = await db.collections.users.get("123") * ``` */ declare function createStreamDB<TDef extends StreamStateDefinition, TActions extends Record<string, ActionDefinition<any>> = Record<string, never>>(options: CreateStreamDBOptions<TDef, TActions>): TActions extends Record<string, never> ? StreamDB<TDef> : StreamDBWithActions<TDef, TActions>; //#endregion export { ActionDefinition, ActionFactory, ActionMap, ChangeEvent, ChangeHeaders, Collection, CollectionDefinition, CollectionEventHelpers, CollectionWithHelpers, ControlEvent, CreateStreamDBOptions, MaterializedState, Operation, Row, StateEvent, StateSchema, StreamDB, StreamDBMethods, StreamDBUtils, StreamDBWithActions, StreamStateDefinition, SyncConfig, Value, and, avg, count, createCollection, createOptimisticAction, createStateSchema, createStreamDB, eq, gt, gte, ilike, inArray, isChangeEvent, isControlEvent, isNull, isUndefined, like, lt, lte, max, min, not, or, sum };