diff --git a/README.md b/README.md index 272b1b9..3038bf0 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,10 @@ Features: `createHistoryScrollState`, `createStickToBottom`. Same options and snapshot shape as React, with accessors in the reactive slots (query functions are bound via `@rocicorp/zero/solid`). -- **`@rocicorp/zero-virtual/core`** — the framework-agnostic - `ZeroVirtualizer` the wrappers share. **Experimental: this entry point is +- **`@rocicorp/zero-virtual/core`** — the framework- and library-agnostic + `ZeroVirtualizer` the wrappers share. It has no dependency on + `@rocicorp/zero` — the query types are opaque generics — so bindings can + pair it with any fetching library. **Experimental: this entry point is public so you can build bindings for other frameworks, but its API may change in breaking ways in any release while it settles.** diff --git a/package.json b/package.json index dcad506..c9f0104 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,9 @@ "solid-js": "^1.9.4" }, "peerDependenciesMeta": { + "@rocicorp/zero": { + "optional": true + }, "react": { "optional": true }, diff --git a/src/core/index.ts b/src/core/index.ts index 89af1a8..839f2db 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,5 +1,7 @@ /** - * Framework-agnostic core of `@rocicorp/zero-virtual`. + * Framework- and library-agnostic core of `@rocicorp/zero-virtual`. Nothing + * here depends on `@rocicorp/zero`: the query types are opaque generics, so + * wrappers can bind the core to any fetching library. * * @experimental This entry point is public but UNSTABLE: it exists so * framework wrappers (react, solid, yours) can share one implementation, and @@ -67,13 +69,10 @@ export type { AnchoringMode, GetPageQuery, GetPageQueryOptions, - GetQueryReturnType, GetSingleQuery, GetSingleQueryOptions, - QueryOptions, QueryResult, RowKey, ScrollHistoryState, - TTL, VirtualRow, } from './types.ts'; diff --git a/src/core/rows.ts b/src/core/rows.ts index 424baf7..55d7eab 100644 --- a/src/core/rows.ts +++ b/src/core/rows.ts @@ -63,10 +63,10 @@ function isPermalink( * Stage 1: the single-row lookup (permalink anchors only; null otherwise so * wrappers can keep a stable query slot). */ -export function buildSingleQuery( +export function buildSingleQuery( inputs: RowsQueryInputs, - getSingleQuery: GetSingleQuery, -): QueryResult | null { + getSingleQuery: GetSingleQuery, +): QueryResult | null { return isPermalink(inputs.anchor) ? getSingleQuery({id: inputs.anchor.id, settled: inputs.settled}) : null; @@ -76,12 +76,12 @@ export function buildSingleQuery( * Stage 2: the main page query — page-before rows for a permalink (depends on * stage 1's result), or the whole page for forward/backward anchors. */ -export function buildMainQuery( +export function buildMainQuery( inputs: RowsQueryInputs, - getPageQuery: GetPageQuery, + getPageQuery: GetPageQuery, singleStart: TStartRow | null, permalinkNotFound: boolean, -): QueryResult | null { +): QueryResult | null { const {anchor, pageSize, settled} = inputs; if (isPermalink(anchor)) { assert(pageSize % 2 === 0); @@ -106,12 +106,12 @@ export function buildMainQuery( * Stage 3: the page-after query (permalink anchors only; depends on stage 1's * result). */ -export function buildAfterQuery( +export function buildAfterQuery( inputs: RowsQueryInputs, - getPageQuery: GetPageQuery, + getPageQuery: GetPageQuery, singleStart: TStartRow | null, permalinkNotFound: boolean, -): QueryResult | null { +): QueryResult | null { const {anchor, pageSize, settled} = inputs; if (!isPermalink(anchor)) return null; assert(pageSize % 2 === 0); diff --git a/src/core/types.ts b/src/core/types.ts index 0884ead..5b283e4 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -1,21 +1,3 @@ -import type { - DefaultContext, - DefaultSchema, - QueryOrQueryRequest, -} from '@rocicorp/zero'; - -/** - * Zero's query time-to-live. Replicated structurally (`@rocicorp/zero` doesn't - * export it from its root entry, and this package's core must stay - * framework-free) — assignable to the `ttl` of both the react and solid - * `UseQueryOptions`. - */ -export type TTL = - | `${number}${'s' | 'm' | 'h' | 'd' | 'y'}` - | 'forever' - | 'none' - | number; - /** * Stable per-row key (row id when loaded, index-derived otherwise). Kept * framework-free — assignable to React's `Key` and usable directly in Solid. @@ -45,18 +27,18 @@ export type Anchor = }>; /** - * Per-query options, framework-free. Structurally assignable to the - * `UseQueryOptions` of both `@rocicorp/zero/react` and `@rocicorp/zero/solid`. + * Result returned by query functions: a query plus optional per-query options. + * + * The query is opaque to the core — `TQuery` is whatever query/request type + * the consumer's fetching library uses. The `./react` and `./solid` entry + * points instantiate it with a Zero query (see `zero-types.ts`); a custom + * wrapper can use any other library's type. + * + * @typeParam TQuery - The query type consumed by the wrapper's data layer */ -export type QueryOptions = { - enabled?: boolean | undefined; - ttl?: TTL | undefined; -}; - -/** Result returned by query functions: a query plus optional per-query options. */ -export type QueryResult = { - query: GetQueryReturnType; - options?: QueryOptions; +export type QueryResult = { + query: TQuery; + options?: TOptions; }; /** @@ -78,12 +60,13 @@ export type GetPageQueryOptions = { /** * Function that returns a query for fetching a page of rows. * - * @typeParam TRow - The type of row data returned from queries + * @typeParam TQuery - The query type consumed by the wrapper's data layer + * (opaque to the core; see {@link QueryResult}) * @typeParam TStartRow - The type of data needed to anchor pagination */ -export type GetPageQuery = ( +export type GetPageQuery = ( options: GetPageQueryOptions, -) => QueryResult; +) => QueryResult; /** * Options passed to {@link GetSingleQuery}. @@ -98,45 +81,41 @@ export type GetSingleQueryOptions = { /** * Function that returns a query for fetching a single row by ID. * - * @typeParam TRow - The type of row data returned from queries + * @typeParam TQuery - The query type consumed by the wrapper's data layer + * (opaque to the core; see {@link QueryResult}) */ -export type GetSingleQuery = ( +export type GetSingleQuery = ( options: GetSingleQueryOptions, -) => QueryResult; +) => QueryResult; /** - * The query wiring the framework bindings add on top of the core options - * (the query functions are bound via `@rocicorp/zero/react` or - * `@rocicorp/zero/solid`). + * The query wiring the framework bindings add on top of the core options. + * The query types are opaque here — the `./react` and `./solid` entry points + * instantiate them with Zero queries (bound via `@rocicorp/zero/react` or + * `@rocicorp/zero/solid`); a custom wrapper can use any other library's + * query type. * * @typeParam TRow - The type of row data returned from queries * @typeParam TStartRow - The type of data needed to anchor pagination + * @typeParam TPageQuery - The query type returned by `getPageQuery` + * @typeParam TSingleQuery - The query type returned by `getSingleQuery` */ -export type VirtualizerQueryOptions = { +export type VirtualizerQueryOptions< + TRow, + TStartRow, + TPageQuery, + TPageOptions, + TSingleQuery, + TSingleOptions, +> = { /** Function that returns a query for fetching a page of rows */ - getPageQuery: GetPageQuery; + getPageQuery: GetPageQuery; /** Function that returns a query for fetching a single row by ID */ - getSingleQuery: GetSingleQuery; + getSingleQuery: GetSingleQuery; /** Function to extract the start row data from a full row (for pagination anchoring) */ toStartRow: (row: TRow) => TStartRow; }; -/** - * Return type of a Zero query or query request. - * - * @typeParam TReturn - The type of the query return value - */ -export type GetQueryReturnType = QueryOrQueryRequest< - keyof DefaultSchema['tables'] & string, - // oxlint-disable-next-line no-explicit-any - any, // input - // oxlint-disable-next-line no-explicit-any - any, // output - DefaultSchema, - TReturn, - DefaultContext ->; - /** * A single item to render. Rows are rendered in normal document flow (no * absolute positioning), so there is no `start`/`size` — the browser lays them diff --git a/src/core/virtualizer.ts b/src/core/virtualizer.ts index bcde3d1..569d8d4 100644 --- a/src/core/virtualizer.ts +++ b/src/core/virtualizer.ts @@ -153,15 +153,31 @@ export type VirtualizerSnapshot = { * The full options the framework bindings accept: the core options plus the * scroll wiring ({@linkcode VirtualizerScrollOptions}, which also makes the * observers optional — the bindings default them per entry-point variant) - * and the query functions ({@linkcode VirtualizerQueryOptions}). + * and the query functions ({@linkcode VirtualizerQueryOptions}, whose query + * types are opaque to the core — the bindings instantiate them with their + * data layer's query type). */ -export type VirtualizerBindingOptions = - Omit< - VirtualizerOptions, - 'observeElementRect' | 'observeElementOffset' - > & - VirtualizerScrollOptions & - VirtualizerQueryOptions; +export type VirtualizerBindingOptions< + TListContextParams, + TRow, + TStartRow, + TPageQuery, + TPageOptions, + TSingleQuery, + TSingleOptions, +> = Omit< + VirtualizerOptions, + 'observeElementRect' | 'observeElementOffset' +> & + VirtualizerScrollOptions & + VirtualizerQueryOptions< + TRow, + TStartRow, + TPageQuery, + TPageOptions, + TSingleQuery, + TSingleOptions + >; /** * What the framework bindings return: the snapshot plus, TanStack-style, the diff --git a/src/react/index.ts b/src/react/index.ts index 3bbf589..0610734 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -13,15 +13,18 @@ export { } from '../core/scroll.ts'; export type { AnchoringMode, - GetPageQuery, GetPageQueryOptions, - GetQueryReturnType, - GetSingleQuery, GetSingleQueryOptions, - QueryResult, ScrollHistoryState, VirtualRow, } from '../core/types.ts'; +export type { + GetPageQuery, + GetQueryReturnType, + GetSingleQuery, + QueryResult, + QueryOptions, +} from '../zero-types.ts'; export {useHistoryScrollState} from './use-history-scroll-state.ts'; export {useStickToBottom, type StickOptions} from './use-stick-to-edge.ts'; export { diff --git a/src/react/use-rows.ts b/src/react/use-rows.ts index 91417b5..927dca0 100644 --- a/src/react/use-rows.ts +++ b/src/react/use-rows.ts @@ -8,7 +8,8 @@ import { permalinkMissing, type RowsSnapshot, } from '../core/rows.ts'; -import type {Anchor, GetPageQuery, GetSingleQuery} from '../core/types.ts'; +import type {Anchor} from '../core/types.ts'; +import type {GetPageQuery, GetSingleQuery} from '../zero-types.ts'; /** * Internal hook that binds the virtualizer's staged queries to Zero's React diff --git a/src/react/use-zero-virtualizer.ts b/src/react/use-zero-virtualizer.ts index c8fce8b..efe01c1 100644 --- a/src/react/use-zero-virtualizer.ts +++ b/src/react/use-zero-virtualizer.ts @@ -12,9 +12,9 @@ import { import { virtualizerResult, ZeroVirtualizer, - type VirtualizerBindingOptions, type VirtualizerResult, } from '../core/virtualizer.ts'; +import type {VirtualizerBindingOptions} from '../zero-types.ts'; import {useRows} from './use-rows.ts'; /** diff --git a/src/solid/create-rows.ts b/src/solid/create-rows.ts index 0886280..b7768be 100644 --- a/src/solid/create-rows.ts +++ b/src/solid/create-rows.ts @@ -9,7 +9,7 @@ import { type RowsQueryInputs, type RowsSnapshot, } from '../core/rows.ts'; -import type {GetPageQuery, GetSingleQuery} from '../core/types.ts'; +import type {GetPageQuery, GetSingleQuery} from '../zero-types.ts'; /** * Binds the virtualizer's staged queries to Zero's Solid bindings. All diff --git a/src/solid/create-zero-virtualizer.ts b/src/solid/create-zero-virtualizer.ts index 37b804d..a81a8f9 100644 --- a/src/solid/create-zero-virtualizer.ts +++ b/src/solid/create-zero-virtualizer.ts @@ -21,9 +21,9 @@ import type {VirtualRow} from '../core/types.ts'; import { virtualizerResult, ZeroVirtualizer, - type VirtualizerBindingOptions, type VirtualizerResult, } from '../core/virtualizer.ts'; +import type {VirtualizerBindingOptions} from '../zero-types.ts'; import {createRows} from './create-rows.ts'; /** diff --git a/src/solid/index.ts b/src/solid/index.ts index f45512f..95e9c20 100644 --- a/src/solid/index.ts +++ b/src/solid/index.ts @@ -1,12 +1,14 @@ export type { - GetPageQuery, GetPageQueryOptions, + GetSingleQueryOptions, +} from '../core/types.ts'; +export type { + GetPageQuery, GetQueryReturnType, GetSingleQuery, - GetSingleQueryOptions, - QueryOptions, QueryResult, -} from '../core/types.ts'; + QueryOptions, +} from '../zero-types.ts'; export { observeElementOffset, observeElementRect, diff --git a/src/zero-types.ts b/src/zero-types.ts new file mode 100644 index 0000000..be3c259 --- /dev/null +++ b/src/zero-types.ts @@ -0,0 +1,108 @@ +import type { + DefaultContext, + DefaultSchema, + QueryOrQueryRequest, +} from '@rocicorp/zero'; +import type { + GetPageQuery as CoreGetPageQuery, + GetSingleQuery as CoreGetSingleQuery, + QueryResult as CoreQueryResult, +} from './core/types.ts'; +import type {VirtualizerBindingOptions as CoreVirtualizerBindingOptions} from './core/virtualizer.ts'; + +/** + * Zero's query time-to-live. Replicated structurally (`@rocicorp/zero` doesn't + * export it from its root entry, and this package's core must stay + * framework- and library-free) — assignable to the `ttl` of both the react + * and solid `UseQueryOptions`. + */ +export type TTL = + | `${number}${'s' | 'm' | 'h' | 'd' | 'y'}` + | 'forever' + | 'none' + | number; + +/** + * Per-query options, framework-free. Structurally assignable to the + * `UseQueryOptions` of both `@rocicorp/zero/react` and `@rocicorp/zero/solid`. + */ +export type QueryOptions = { + enabled?: boolean | undefined; + ttl?: TTL | undefined; +}; + +/** + * The Zero instantiations of the core's query-agnostic types, shared by the + * `./react` and `./solid` entry points. The core itself is library-agnostic + * (its query types are opaque generics); this module pins them to Zero + * queries so the bindings' public API is expressed in terms of the row type, + * exactly as before. + */ + +/** + * Return type of a Zero query or query request. + * + * @typeParam TReturn - The type of the query return value + */ +export type GetQueryReturnType = QueryOrQueryRequest< + keyof DefaultSchema['tables'] & string, + // oxlint-disable-next-line no-explicit-any + any, // input + // oxlint-disable-next-line no-explicit-any + any, // output + DefaultSchema, + TReturn, + DefaultContext +>; + +/** + * Result returned by query functions: a Zero query plus optional per-query + * options. + * + * @typeParam TReturn - The type of the query return value + */ +export type QueryResult = CoreQueryResult< + GetQueryReturnType, + QueryOptions +>; + +/** + * Function that returns a Zero query for fetching a page of rows. + * + * @typeParam TRow - The type of row data returned from queries + * @typeParam TStartRow - The type of data needed to anchor pagination + */ +export type GetPageQuery = CoreGetPageQuery< + GetQueryReturnType, + QueryOptions, + TStartRow +>; + +/** + * Function that returns a Zero query for fetching a single row by ID. + * + * @typeParam TRow - The type of row data returned from queries + */ +export type GetSingleQuery = CoreGetSingleQuery< + GetQueryReturnType, + QueryOptions +>; + +/** + * The core's binding options with the query types pinned to Zero queries + * (bound via `@rocicorp/zero/react` or `@rocicorp/zero/solid`). + * + * @typeParam TListContextParams - The type of parameters that define the list's query context + * @typeParam TRow - The type of row data returned from queries + * @typeParam TStartRow - The type of data needed to anchor pagination + */ +export type VirtualizerBindingOptions = + CoreVirtualizerBindingOptions< + TListContextParams, + TRow, + TStartRow, + GetQueryReturnType, + QueryOptions, + GetQueryReturnType, + QueryOptions + >;