@confect/react 10.0.0-next.20 → 10.0.0-next.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,127 @@
1
+ /**
2
+ * EXPERIMENTAL—the pure state machine behind {@link useStreamPaginatedQuery}
3
+ * (see `notes/stream-based-querying.md` in the repo root).
4
+ *
5
+ * Reactive pagination without the query journal: every loaded page—including the first—is pinned to a fixed index range by echoing its
6
+ * `continueCursor` back as the next subscription's `endCursor` (the last
7
+ * page pins to the end-of-stream sentinel once exhausted), so adjacent
8
+ * pages stay exactly contiguous and pages never shed items as data changes—the "range-defined pages" the Fully Reactive Pagination article calls
9
+ * for, and the mechanism of `convex-helpers/react`'s `usePaginatedQuery`.
10
+ *
11
+ * Pinning a freshly loaded page, loading more, and splitting an overgrown
12
+ * page are the same transition shape: the affected page keeps rendering
13
+ * while its replacement subscriptions ("ongoing split") load, and is
14
+ * swapped out once all of them have results.
15
+ *
16
+ * This module is framework-free (it imports only `effect/*` and a type
17
+ * from `convex/server`); it lives here for now because the React hook is
18
+ * its only consumer—a second client (e.g. `@confect/foldkit`) should
19
+ * prompt a move down to `@confect/js`.
20
+ */
21
+ import type { PaginationResult } from "convex/server";
22
+ import * as Option from "effect/Option";
23
+ import * as Record from "effect/Record";
24
+ /** The `paginationOpts` one subscribed page queries with. */
25
+ export interface PageRequest {
26
+ readonly numItems: number;
27
+ readonly cursor: string | null;
28
+ /** Present once the page is pinned to a fixed range. */
29
+ readonly endCursor?: string;
30
+ /**
31
+ * Per-page read budgets, forwarded to the server so a scan-heavy page
32
+ * fails over to `SplitRequired` instead of exceeding query limits.
33
+ */
34
+ readonly maximumRowsRead?: number;
35
+ readonly maximumBytesRead?: number;
36
+ }
37
+ /** The per-page read budgets every growing page is requested with. */
38
+ export interface ReadBudget {
39
+ readonly maximumRowsRead?: number | undefined;
40
+ readonly maximumBytesRead?: number | undefined;
41
+ }
42
+ export interface State {
43
+ readonly nextPageKey: number;
44
+ /** Page keys in display order. */
45
+ readonly pageKeys: ReadonlyArray<string>;
46
+ /** The request each subscribed page queries with, by page key. */
47
+ readonly pages: Record.ReadonlyRecord<string, PageRequest>;
48
+ /**
49
+ * Pages being replaced by one (a pin) or two (a split) narrower
50
+ * subscriptions: the original stays subscribed (and rendered) until every
51
+ * replacement has a result.
52
+ */
53
+ readonly ongoingSplits: Record.ReadonlyRecord<string, ReadonlyArray<string>>;
54
+ }
55
+ /** The skipped state: nothing subscribed. */
56
+ export declare const empty: State;
57
+ /** One growing (unpinned) page from the start of the stream. */
58
+ export declare const initial: (initialNumItems: number, budget?: ReadBudget) => State;
59
+ /**
60
+ * Pin a freshly loaded growing page at its `continueCursor` (or, once
61
+ * exhausted, at the end-of-stream sentinel), so it stops being a sliding
62
+ * window: a pinned page grows and shrinks with the data in its range but
63
+ * never sheds items past its edges. Modeled as a one-replacement split so
64
+ * the original keeps rendering until the pinned twin has a result.
65
+ */
66
+ export declare const pin: (key: string, endCursor: string) => ((state: State) => State);
67
+ /**
68
+ * Pin the growing last page at `continueCursor` and start a new growing
69
+ * page from there. Modeled as a split of the last page into its pinned
70
+ * replacement and the new page, so the swap waits for both.
71
+ */
72
+ export declare const loadMore: (continueCursor: string, numItems: number, budget?: ReadBudget) => ((state: State) => State);
73
+ /**
74
+ * Split the page at `key` in two at `splitCursor`. The first replacement
75
+ * pins at the split point; the second keeps the page's own `endCursor`—for a pinned page its original end (so a truncated `SplitRequired` page
76
+ * loses none of its range), and for a growing page no end at all (it keeps
77
+ * growing).
78
+ */
79
+ export declare const split: (key: string, splitCursor: string) => ((state: State) => State);
80
+ /** Swap a completed split's replacements in for the original page. */
81
+ export declare const completeSplit: (key: string) => ((state: State) => State);
82
+ /** Whether the last (rendered) page is currently being split or pinned. */
83
+ export declare const isLastPageSplitting: (state: State) => boolean;
84
+ /** Build one subscription request per subscribed page. */
85
+ export declare const pageRequests: <A>(state: State, f: (page: PageRequest) => A) => Record.ReadonlyRecord<string, A>;
86
+ /** The per-page results a render observes, keyed like {@link pageRequests}. */
87
+ export type Results = Record.ReadonlyRecord<string, PageResult | Error | undefined>;
88
+ /**
89
+ * The wire shape of one loaded page—`PaginationResult` from
90
+ * `convex/server`, so the protocol has a single source of truth.
91
+ */
92
+ export type PageResult = PaginationResult<unknown>;
93
+ /**
94
+ * What one render pass derives from the subscribed pages' results: the
95
+ * items to show, the trailing complete result (absent while the tail is
96
+ * loading or force-splitting), state transitions to apply, and whether a
97
+ * failure or an invalid cursor was hit.
98
+ */
99
+ export type Interpretation = {
100
+ readonly _tag: "ResetRequired";
101
+ } | {
102
+ readonly _tag: "Failed";
103
+ readonly error: unknown;
104
+ /** Encoded items loaded before the failing page. */
105
+ readonly items: ReadonlyArray<unknown>;
106
+ } | {
107
+ readonly _tag: "Interpreted";
108
+ /** Encoded items across the loaded pages, in order. */
109
+ readonly items: ReadonlyArray<unknown>;
110
+ /** The last page's result, when every page has loaded completely. */
111
+ readonly lastResult: Option.Option<PageResult>;
112
+ /** Split/swap transitions this render discovered. */
113
+ readonly transitions: ReadonlyArray<(state: State) => State>;
114
+ };
115
+ /**
116
+ * Walk the pages in display order, concatenating their items and
117
+ * collecting the transitions to apply (completed split swaps, recommended
118
+ * splits, eager splits of pages that outgrew `initialNumItems`).
119
+ *
120
+ * `isInvalidCursorError` marks errors that call for a full pagination
121
+ * reset rather than a failure (the cursor no longer matches the query).
122
+ */
123
+ export declare const interpret: (state: State, results: Record.ReadonlyRecord<string, PageResult | Error | undefined>, options: {
124
+ readonly initialNumItems: number;
125
+ readonly isInvalidCursorError: (error: Error) => boolean;
126
+ }) => Interpretation;
127
+ //# sourceMappingURL=StreamPagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamPagination.d.ts","sourceRoot":"","sources":["../src/StreamPagination.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,170 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
+ import * as Option from "effect/Option";
3
+ import { pipe } from "effect/Function";
4
+ import * as Array from "effect/Array";
5
+ import * as Record from "effect/Record";
6
+ //#region src/StreamPagination.ts
7
+ var StreamPagination_exports = /* @__PURE__ */ __exportAll({
8
+ completeSplit: () => completeSplit,
9
+ empty: () => empty,
10
+ initial: () => initial,
11
+ interpret: () => interpret,
12
+ isLastPageSplitting: () => isLastPageSplitting,
13
+ loadMore: () => loadMore,
14
+ pageRequests: () => pageRequests,
15
+ pin: () => pin,
16
+ split: () => split
17
+ });
18
+ /** A fresh growing-page request (no `endCursor`). */
19
+ const growingRequest = (numItems, cursor, budget) => ({
20
+ numItems,
21
+ cursor,
22
+ ...budget.maximumRowsRead === void 0 ? {} : { maximumRowsRead: budget.maximumRowsRead },
23
+ ...budget.maximumBytesRead === void 0 ? {} : { maximumBytesRead: budget.maximumBytesRead }
24
+ });
25
+ /** The skipped state: nothing subscribed. */
26
+ const empty = {
27
+ nextPageKey: 0,
28
+ pageKeys: [],
29
+ pages: Record.empty(),
30
+ ongoingSplits: Record.empty()
31
+ };
32
+ /** One growing (unpinned) page from the start of the stream. */
33
+ const initial = (initialNumItems, budget = {}) => ({
34
+ nextPageKey: 1,
35
+ pageKeys: ["0"],
36
+ pages: Record.singleton("0", growingRequest(initialNumItems, null, budget)),
37
+ ongoingSplits: Record.empty()
38
+ });
39
+ /**
40
+ * Pin a freshly loaded growing page at its `continueCursor` (or, once
41
+ * exhausted, at the end-of-stream sentinel), so it stops being a sliding
42
+ * window: a pinned page grows and shrinks with the data in its range but
43
+ * never sheds items past its edges. Modeled as a one-replacement split so
44
+ * the original keeps rendering until the pinned twin has a result.
45
+ */
46
+ const pin = (key, endCursor) => (state) => Option.match(Record.get(state.pages, key), {
47
+ onNone: () => state,
48
+ onSome: (page) => {
49
+ if (page.endCursor !== void 0 || Record.has(state.ongoingSplits, key)) return state;
50
+ const pinnedKey = String(state.nextPageKey);
51
+ return {
52
+ nextPageKey: state.nextPageKey + 1,
53
+ pageKeys: state.pageKeys,
54
+ pages: Record.set(state.pages, pinnedKey, {
55
+ ...page,
56
+ endCursor
57
+ }),
58
+ ongoingSplits: Record.set(state.ongoingSplits, key, [pinnedKey])
59
+ };
60
+ }
61
+ });
62
+ /**
63
+ * Pin the growing last page at `continueCursor` and start a new growing
64
+ * page from there. Modeled as a split of the last page into its pinned
65
+ * replacement and the new page, so the swap waits for both.
66
+ */
67
+ const loadMore = (continueCursor, numItems, budget = {}) => (state) => pipe(Option.Do, Option.bind("lastKey", () => Array.last(state.pageKeys)), Option.bind("lastPage", ({ lastKey }) => Record.get(state.pages, lastKey)), Option.match({
68
+ onNone: () => state,
69
+ onSome: ({ lastKey, lastPage }) => {
70
+ if (lastPage.endCursor !== void 0) {
71
+ const nextKey = String(state.nextPageKey);
72
+ return {
73
+ nextPageKey: state.nextPageKey + 1,
74
+ pageKeys: Array.append(state.pageKeys, nextKey),
75
+ pages: Record.set(state.pages, nextKey, growingRequest(numItems, continueCursor, budget)),
76
+ ongoingSplits: state.ongoingSplits
77
+ };
78
+ }
79
+ const pinnedKey = String(state.nextPageKey);
80
+ const nextKey = String(state.nextPageKey + 1);
81
+ return {
82
+ nextPageKey: state.nextPageKey + 2,
83
+ pageKeys: state.pageKeys,
84
+ pages: pipe(state.pages, Record.set(pinnedKey, {
85
+ ...lastPage,
86
+ endCursor: continueCursor
87
+ }), Record.set(nextKey, growingRequest(numItems, continueCursor, budget))),
88
+ ongoingSplits: Record.set(state.ongoingSplits, lastKey, [pinnedKey, nextKey])
89
+ };
90
+ }
91
+ }));
92
+ /**
93
+ * Split the page at `key` in two at `splitCursor`. The first replacement
94
+ * pins at the split point; the second keeps the page's own `endCursor`—for a pinned page its original end (so a truncated `SplitRequired` page
95
+ * loses none of its range), and for a growing page no end at all (it keeps
96
+ * growing).
97
+ */
98
+ const split = (key, splitCursor) => (state) => Option.match(Record.get(state.pages, key), {
99
+ onNone: () => state,
100
+ onSome: (page) => {
101
+ const firstKey = String(state.nextPageKey);
102
+ const secondKey = String(state.nextPageKey + 1);
103
+ return {
104
+ nextPageKey: state.nextPageKey + 2,
105
+ pageKeys: state.pageKeys,
106
+ pages: pipe(state.pages, Record.set(firstKey, {
107
+ ...page,
108
+ endCursor: splitCursor
109
+ }), Record.set(secondKey, {
110
+ ...page,
111
+ cursor: splitCursor
112
+ })),
113
+ ongoingSplits: Record.set(state.ongoingSplits, key, [firstKey, secondKey])
114
+ };
115
+ }
116
+ });
117
+ /** Swap a completed split's replacements in for the original page. */
118
+ const completeSplit = (key) => (state) => Option.match(Record.get(state.ongoingSplits, key), {
119
+ onNone: () => state,
120
+ onSome: (replacements) => ({
121
+ nextPageKey: state.nextPageKey,
122
+ pageKeys: Array.flatMap(state.pageKeys, (pageKey) => pageKey === key ? replacements : [pageKey]),
123
+ pages: Record.remove(state.pages, key),
124
+ ongoingSplits: Record.remove(state.ongoingSplits, key)
125
+ })
126
+ });
127
+ /** Whether the last (rendered) page is currently being split or pinned. */
128
+ const isLastPageSplitting = (state) => Option.exists(Array.last(state.pageKeys), (lastKey) => Record.has(state.ongoingSplits, lastKey));
129
+ /** Build one subscription request per subscribed page. */
130
+ const pageRequests = (state, f) => Record.map(state.pages, f);
131
+ const interpreted = (items, lastResult, transitions) => ({
132
+ _tag: "Interpreted",
133
+ items,
134
+ lastResult,
135
+ transitions
136
+ });
137
+ /**
138
+ * Walk the pages in display order, concatenating their items and
139
+ * collecting the transitions to apply (completed split swaps, recommended
140
+ * splits, eager splits of pages that outgrew `initialNumItems`).
141
+ *
142
+ * `isInvalidCursorError` marks errors that call for a full pagination
143
+ * reset rather than a failure (the cursor no longer matches the query).
144
+ */
145
+ const interpret = (state, results, options) => {
146
+ const go = (remaining, items, transitions, lastResult) => Option.match(Array.head(remaining), {
147
+ onNone: () => interpreted(items, lastResult, transitions),
148
+ onSome: (pageKey) => {
149
+ const result = Record.get(results, pageKey).pipe(Option.flatMap(Option.fromNullishOr), Option.getOrUndefined);
150
+ if (result === void 0) return interpreted(items, Option.none(), transitions);
151
+ if (result instanceof Error) return options.isInvalidCursorError(result) ? { _tag: "ResetRequired" } : {
152
+ _tag: "Failed",
153
+ error: result,
154
+ items
155
+ };
156
+ const hasResult = (key) => Record.get(results, key).pipe(Option.flatMap(Option.fromNullishOr), Option.isSome);
157
+ const ongoingSplit = Record.get(state.ongoingSplits, pageKey);
158
+ const nextTransitions = Option.match(ongoingSplit, {
159
+ onSome: (replacements) => Array.every(replacements, hasResult) ? Array.append(transitions, completeSplit(pageKey)) : transitions,
160
+ onNone: () => typeof result.splitCursor === "string" && (result.pageStatus === "SplitRecommended" || result.pageStatus === "SplitRequired" || result.page.length > options.initialNumItems) ? Array.append(transitions, split(pageKey, result.splitCursor)) : Option.exists(Record.get(state.pages, pageKey), (request) => request.endCursor === void 0) && result.pageStatus !== "SplitRequired" ? Array.append(transitions, pin(pageKey, result.continueCursor)) : transitions
161
+ });
162
+ return result.pageStatus === "SplitRequired" ? interpreted(items, Option.none(), nextTransitions) : go(Array.drop(remaining, 1), Array.appendAll(items, result.page), nextTransitions, Option.some(result));
163
+ }
164
+ });
165
+ return go(state.pageKeys, [], [], Option.none());
166
+ };
167
+ //#endregion
168
+ export { StreamPagination_exports, completeSplit, empty, initial, interpret, isLastPageSplitting, loadMore, pageRequests, pin, split };
169
+
170
+ //# sourceMappingURL=StreamPagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamPagination.js","names":[],"sources":["../src/StreamPagination.ts"],"sourcesContent":["/**\n * EXPERIMENTAL—the pure state machine behind {@link useStreamPaginatedQuery}\n * (see `notes/stream-based-querying.md` in the repo root).\n *\n * Reactive pagination without the query journal: every loaded page—including the first—is pinned to a fixed index range by echoing its\n * `continueCursor` back as the next subscription's `endCursor` (the last\n * page pins to the end-of-stream sentinel once exhausted), so adjacent\n * pages stay exactly contiguous and pages never shed items as data changes—the \"range-defined pages\" the Fully Reactive Pagination article calls\n * for, and the mechanism of `convex-helpers/react`'s `usePaginatedQuery`.\n *\n * Pinning a freshly loaded page, loading more, and splitting an overgrown\n * page are the same transition shape: the affected page keeps rendering\n * while its replacement subscriptions (\"ongoing split\") load, and is\n * swapped out once all of them have results.\n *\n * This module is framework-free (it imports only `effect/*` and a type\n * from `convex/server`); it lives here for now because the React hook is\n * its only consumer—a second client (e.g. `@confect/foldkit`) should\n * prompt a move down to `@confect/js`.\n */\nimport type { PaginationResult } from \"convex/server\";\nimport * as Array from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport { pipe } from \"effect/Function\";\nimport * as Record from \"effect/Record\";\n\n/** The `paginationOpts` one subscribed page queries with. */\nexport interface PageRequest {\n readonly numItems: number;\n readonly cursor: string | null;\n /** Present once the page is pinned to a fixed range. */\n readonly endCursor?: string;\n /**\n * Per-page read budgets, forwarded to the server so a scan-heavy page\n * fails over to `SplitRequired` instead of exceeding query limits.\n */\n readonly maximumRowsRead?: number;\n readonly maximumBytesRead?: number;\n}\n\n/** The per-page read budgets every growing page is requested with. */\nexport interface ReadBudget {\n readonly maximumRowsRead?: number | undefined;\n readonly maximumBytesRead?: number | undefined;\n}\n\n/** A fresh growing-page request (no `endCursor`). */\nconst growingRequest = (\n numItems: number,\n cursor: string | null,\n budget: ReadBudget,\n): PageRequest => ({\n numItems,\n cursor,\n ...(budget.maximumRowsRead === undefined\n ? {}\n : { maximumRowsRead: budget.maximumRowsRead }),\n ...(budget.maximumBytesRead === undefined\n ? {}\n : { maximumBytesRead: budget.maximumBytesRead }),\n});\n\nexport interface State {\n readonly nextPageKey: number;\n /** Page keys in display order. */\n readonly pageKeys: ReadonlyArray<string>;\n /** The request each subscribed page queries with, by page key. */\n readonly pages: Record.ReadonlyRecord<string, PageRequest>;\n /**\n * Pages being replaced by one (a pin) or two (a split) narrower\n * subscriptions: the original stays subscribed (and rendered) until every\n * replacement has a result.\n */\n readonly ongoingSplits: Record.ReadonlyRecord<string, ReadonlyArray<string>>;\n}\n\n/** The skipped state: nothing subscribed. */\nexport const empty: State = {\n nextPageKey: 0,\n pageKeys: [],\n pages: Record.empty(),\n ongoingSplits: Record.empty(),\n};\n\n/** One growing (unpinned) page from the start of the stream. */\nexport const initial = (\n initialNumItems: number,\n budget: ReadBudget = {},\n): State => ({\n nextPageKey: 1,\n pageKeys: [\"0\"],\n pages: Record.singleton(\"0\", growingRequest(initialNumItems, null, budget)),\n ongoingSplits: Record.empty(),\n});\n\n/**\n * Pin a freshly loaded growing page at its `continueCursor` (or, once\n * exhausted, at the end-of-stream sentinel), so it stops being a sliding\n * window: a pinned page grows and shrinks with the data in its range but\n * never sheds items past its edges. Modeled as a one-replacement split so\n * the original keeps rendering until the pinned twin has a result.\n */\nexport const pin =\n (key: string, endCursor: string): ((state: State) => State) =>\n (state) =>\n Option.match(Record.get(state.pages, key), {\n onNone: () => state,\n onSome: (page) => {\n if (\n page.endCursor !== undefined ||\n Record.has(state.ongoingSplits, key)\n ) {\n return state;\n }\n const pinnedKey = String(state.nextPageKey);\n return {\n nextPageKey: state.nextPageKey + 1,\n pageKeys: state.pageKeys,\n pages: Record.set(state.pages, pinnedKey, { ...page, endCursor }),\n ongoingSplits: Record.set(state.ongoingSplits, key, [pinnedKey]),\n };\n },\n });\n\n/**\n * Pin the growing last page at `continueCursor` and start a new growing\n * page from there. Modeled as a split of the last page into its pinned\n * replacement and the new page, so the swap waits for both.\n */\nexport const loadMore =\n (\n continueCursor: string,\n numItems: number,\n budget: ReadBudget = {},\n ): ((state: State) => State) =>\n (state) =>\n pipe(\n Option.Do,\n Option.bind(\"lastKey\", () => Array.last(state.pageKeys)),\n Option.bind(\"lastPage\", ({ lastKey }) =>\n Record.get(state.pages, lastKey),\n ),\n Option.match({\n onNone: () => state,\n onSome: ({ lastKey, lastPage }) => {\n if (lastPage.endCursor !== undefined) {\n // The last page is already pinned (the usual case, since pages\n // pin themselves on first load): just append a new growing page.\n const nextKey = String(state.nextPageKey);\n return {\n nextPageKey: state.nextPageKey + 1,\n pageKeys: Array.append(state.pageKeys, nextKey),\n pages: Record.set(\n state.pages,\n nextKey,\n growingRequest(numItems, continueCursor, budget),\n ),\n ongoingSplits: state.ongoingSplits,\n };\n }\n const pinnedKey = String(state.nextPageKey);\n const nextKey = String(state.nextPageKey + 1);\n return {\n nextPageKey: state.nextPageKey + 2,\n pageKeys: state.pageKeys,\n pages: pipe(\n state.pages,\n Record.set(pinnedKey, { ...lastPage, endCursor: continueCursor }),\n Record.set(\n nextKey,\n growingRequest(numItems, continueCursor, budget),\n ),\n ),\n ongoingSplits: Record.set(state.ongoingSplits, lastKey, [\n pinnedKey,\n nextKey,\n ]),\n };\n },\n }),\n );\n\n/**\n * Split the page at `key` in two at `splitCursor`. The first replacement\n * pins at the split point; the second keeps the page's own `endCursor`—for a pinned page its original end (so a truncated `SplitRequired` page\n * loses none of its range), and for a growing page no end at all (it keeps\n * growing).\n */\nexport const split =\n (key: string, splitCursor: string): ((state: State) => State) =>\n (state) =>\n Option.match(Record.get(state.pages, key), {\n onNone: () => state,\n onSome: (page) => {\n const firstKey = String(state.nextPageKey);\n const secondKey = String(state.nextPageKey + 1);\n return {\n nextPageKey: state.nextPageKey + 2,\n pageKeys: state.pageKeys,\n pages: pipe(\n state.pages,\n Record.set(firstKey, { ...page, endCursor: splitCursor }),\n Record.set(secondKey, { ...page, cursor: splitCursor }),\n ),\n ongoingSplits: Record.set(state.ongoingSplits, key, [\n firstKey,\n secondKey,\n ]),\n };\n },\n });\n\n/** Swap a completed split's replacements in for the original page. */\nexport const completeSplit =\n (key: string): ((state: State) => State) =>\n (state) =>\n Option.match(Record.get(state.ongoingSplits, key), {\n onNone: () => state,\n onSome: (replacements) => ({\n nextPageKey: state.nextPageKey,\n pageKeys: Array.flatMap(state.pageKeys, (pageKey) =>\n pageKey === key ? replacements : [pageKey],\n ),\n pages: Record.remove(state.pages, key),\n ongoingSplits: Record.remove(state.ongoingSplits, key),\n }),\n });\n\n/** Whether the last (rendered) page is currently being split or pinned. */\nexport const isLastPageSplitting = (state: State): boolean =>\n Option.exists(Array.last(state.pageKeys), (lastKey) =>\n Record.has(state.ongoingSplits, lastKey),\n );\n\n/** Build one subscription request per subscribed page. */\nexport const pageRequests = <A>(\n state: State,\n f: (page: PageRequest) => A,\n): Record.ReadonlyRecord<string, A> => Record.map(state.pages, f);\n\n/** The per-page results a render observes, keyed like {@link pageRequests}. */\nexport type Results = Record.ReadonlyRecord<\n string,\n PageResult | Error | undefined\n>;\n\n// -----------------------------------------------------------------------------\n// Interpretation\n// -----------------------------------------------------------------------------\n\n/**\n * The wire shape of one loaded page—`PaginationResult` from\n * `convex/server`, so the protocol has a single source of truth.\n */\nexport type PageResult = PaginationResult<unknown>;\n\n/**\n * What one render pass derives from the subscribed pages' results: the\n * items to show, the trailing complete result (absent while the tail is\n * loading or force-splitting), state transitions to apply, and whether a\n * failure or an invalid cursor was hit.\n */\nexport type Interpretation =\n | { readonly _tag: \"ResetRequired\" }\n | {\n readonly _tag: \"Failed\";\n readonly error: unknown;\n /** Encoded items loaded before the failing page. */\n readonly items: ReadonlyArray<unknown>;\n }\n | {\n readonly _tag: \"Interpreted\";\n /** Encoded items across the loaded pages, in order. */\n readonly items: ReadonlyArray<unknown>;\n /** The last page's result, when every page has loaded completely. */\n readonly lastResult: Option.Option<PageResult>;\n /** Split/swap transitions this render discovered. */\n readonly transitions: ReadonlyArray<(state: State) => State>;\n };\n\nconst interpreted = (\n items: ReadonlyArray<unknown>,\n lastResult: Option.Option<PageResult>,\n transitions: ReadonlyArray<(state: State) => State>,\n): Interpretation => ({ _tag: \"Interpreted\", items, lastResult, transitions });\n\n/**\n * Walk the pages in display order, concatenating their items and\n * collecting the transitions to apply (completed split swaps, recommended\n * splits, eager splits of pages that outgrew `initialNumItems`).\n *\n * `isInvalidCursorError` marks errors that call for a full pagination\n * reset rather than a failure (the cursor no longer matches the query).\n */\nexport const interpret = (\n state: State,\n results: Record.ReadonlyRecord<string, PageResult | Error | undefined>,\n options: {\n readonly initialNumItems: number;\n readonly isInvalidCursorError: (error: Error) => boolean;\n },\n): Interpretation => {\n const go = (\n remaining: ReadonlyArray<string>,\n items: ReadonlyArray<unknown>,\n transitions: ReadonlyArray<(s: State) => State>,\n lastResult: Option.Option<PageResult>,\n ): Interpretation =>\n Option.match(Array.head(remaining), {\n onNone: () => interpreted(items, lastResult, transitions),\n onSome: (pageKey) => {\n const result = Record.get(results, pageKey).pipe(\n Option.flatMap(Option.fromNullishOr),\n Option.getOrUndefined,\n );\n if (result === undefined) {\n // The trailing pages are still loading.\n return interpreted(items, Option.none(), transitions);\n }\n if (result instanceof Error) {\n return options.isInvalidCursorError(result)\n ? { _tag: \"ResetRequired\" }\n : { _tag: \"Failed\", error: result, items };\n }\n\n const hasResult = (key: string) =>\n Record.get(results, key).pipe(\n Option.flatMap(Option.fromNullishOr),\n Option.isSome,\n );\n const ongoingSplit = Record.get(state.ongoingSplits, pageKey);\n const nextTransitions = Option.match(ongoingSplit, {\n onSome: (replacements) =>\n // Swap the replacements in once all of them have results.\n Array.every(replacements, hasResult)\n ? Array.append(transitions, completeSplit(pageKey))\n : transitions,\n onNone: () =>\n typeof result.splitCursor === \"string\" &&\n (result.pageStatus === \"SplitRecommended\" ||\n result.pageStatus === \"SplitRequired\" ||\n result.page.length > options.initialNumItems)\n ? Array.append(transitions, split(pageKey, result.splitCursor))\n : // A loaded growing page pins itself to the range it just\n // served (`END_CURSOR` once exhausted), so it stops being a\n // sliding window that sheds items as new documents arrive.\n Option.exists(\n Record.get(state.pages, pageKey),\n (request) => request.endCursor === undefined,\n ) && result.pageStatus !== \"SplitRequired\"\n ? Array.append(transitions, pin(pageKey, result.continueCursor))\n : transitions,\n });\n\n // A force-split page couldn't be fetched in full: show the items\n // before it and report the tail as still loading.\n return result.pageStatus === \"SplitRequired\"\n ? interpreted(items, Option.none(), nextTransitions)\n : go(\n Array.drop(remaining, 1),\n Array.appendAll(items, result.page),\n nextTransitions,\n Option.some(result),\n );\n },\n });\n\n return go(state.pageKeys, [], [], Option.none());\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+CA,MAAM,kBACJ,UACA,QACA,YACiB;CACjB;CACA;CACA,GAAI,OAAO,oBAAoB,KAAA,IAC3B,CAAC,IACD,EAAE,iBAAiB,OAAO,gBAAgB;CAC9C,GAAI,OAAO,qBAAqB,KAAA,IAC5B,CAAC,IACD,EAAE,kBAAkB,OAAO,iBAAiB;AAClD;;AAiBA,MAAa,QAAe;CAC1B,aAAa;CACb,UAAU,CAAC;CACX,OAAO,OAAO,MAAM;CACpB,eAAe,OAAO,MAAM;AAC9B;;AAGA,MAAa,WACX,iBACA,SAAqB,CAAC,OACX;CACX,aAAa;CACb,UAAU,CAAC,GAAG;CACd,OAAO,OAAO,UAAU,KAAK,eAAe,iBAAiB,MAAM,MAAM,CAAC;CAC1E,eAAe,OAAO,MAAM;AAC9B;;;;;;;;AASA,MAAa,OACV,KAAa,eACb,UACC,OAAO,MAAM,OAAO,IAAI,MAAM,OAAO,GAAG,GAAG;CACzC,cAAc;CACd,SAAS,SAAS;EAChB,IACE,KAAK,cAAc,KAAA,KACnB,OAAO,IAAI,MAAM,eAAe,GAAG,GAEnC,OAAO;EAET,MAAM,YAAY,OAAO,MAAM,WAAW;EAC1C,OAAO;GACL,aAAa,MAAM,cAAc;GACjC,UAAU,MAAM;GAChB,OAAO,OAAO,IAAI,MAAM,OAAO,WAAW;IAAE,GAAG;IAAM;GAAU,CAAC;GAChE,eAAe,OAAO,IAAI,MAAM,eAAe,KAAK,CAAC,SAAS,CAAC;EACjE;CACF;AACF,CAAC;;;;;;AAOL,MAAa,YAET,gBACA,UACA,SAAqB,CAAC,OAEvB,UACC,KACE,OAAO,IACP,OAAO,KAAK,iBAAiB,MAAM,KAAK,MAAM,QAAQ,CAAC,GACvD,OAAO,KAAK,aAAa,EAAE,cACzB,OAAO,IAAI,MAAM,OAAO,OAAO,CACjC,GACA,OAAO,MAAM;CACX,cAAc;CACd,SAAS,EAAE,SAAS,eAAe;EACjC,IAAI,SAAS,cAAc,KAAA,GAAW;GAGpC,MAAM,UAAU,OAAO,MAAM,WAAW;GACxC,OAAO;IACL,aAAa,MAAM,cAAc;IACjC,UAAU,MAAM,OAAO,MAAM,UAAU,OAAO;IAC9C,OAAO,OAAO,IACZ,MAAM,OACN,SACA,eAAe,UAAU,gBAAgB,MAAM,CACjD;IACA,eAAe,MAAM;GACvB;EACF;EACA,MAAM,YAAY,OAAO,MAAM,WAAW;EAC1C,MAAM,UAAU,OAAO,MAAM,cAAc,CAAC;EAC5C,OAAO;GACL,aAAa,MAAM,cAAc;GACjC,UAAU,MAAM;GAChB,OAAO,KACL,MAAM,OACN,OAAO,IAAI,WAAW;IAAE,GAAG;IAAU,WAAW;GAAe,CAAC,GAChE,OAAO,IACL,SACA,eAAe,UAAU,gBAAgB,MAAM,CACjD,CACF;GACA,eAAe,OAAO,IAAI,MAAM,eAAe,SAAS,CACtD,WACA,OACF,CAAC;EACH;CACF;AACF,CAAC,CACH;;;;;;;AAQJ,MAAa,SACV,KAAa,iBACb,UACC,OAAO,MAAM,OAAO,IAAI,MAAM,OAAO,GAAG,GAAG;CACzC,cAAc;CACd,SAAS,SAAS;EAChB,MAAM,WAAW,OAAO,MAAM,WAAW;EACzC,MAAM,YAAY,OAAO,MAAM,cAAc,CAAC;EAC9C,OAAO;GACL,aAAa,MAAM,cAAc;GACjC,UAAU,MAAM;GAChB,OAAO,KACL,MAAM,OACN,OAAO,IAAI,UAAU;IAAE,GAAG;IAAM,WAAW;GAAY,CAAC,GACxD,OAAO,IAAI,WAAW;IAAE,GAAG;IAAM,QAAQ;GAAY,CAAC,CACxD;GACA,eAAe,OAAO,IAAI,MAAM,eAAe,KAAK,CAClD,UACA,SACF,CAAC;EACH;CACF;AACF,CAAC;;AAGL,MAAa,iBACV,SACA,UACC,OAAO,MAAM,OAAO,IAAI,MAAM,eAAe,GAAG,GAAG;CACjD,cAAc;CACd,SAAS,kBAAkB;EACzB,aAAa,MAAM;EACnB,UAAU,MAAM,QAAQ,MAAM,WAAW,YACvC,YAAY,MAAM,eAAe,CAAC,OAAO,CAC3C;EACA,OAAO,OAAO,OAAO,MAAM,OAAO,GAAG;EACrC,eAAe,OAAO,OAAO,MAAM,eAAe,GAAG;CACvD;AACF,CAAC;;AAGL,MAAa,uBAAuB,UAClC,OAAO,OAAO,MAAM,KAAK,MAAM,QAAQ,IAAI,YACzC,OAAO,IAAI,MAAM,eAAe,OAAO,CACzC;;AAGF,MAAa,gBACX,OACA,MACqC,OAAO,IAAI,MAAM,OAAO,CAAC;AA0ChE,MAAM,eACJ,OACA,YACA,iBACoB;CAAE,MAAM;CAAe;CAAO;CAAY;AAAY;;;;;;;;;AAU5E,MAAa,aACX,OACA,SACA,YAImB;CACnB,MAAM,MACJ,WACA,OACA,aACA,eAEA,OAAO,MAAM,MAAM,KAAK,SAAS,GAAG;EAClC,cAAc,YAAY,OAAO,YAAY,WAAW;EACxD,SAAS,YAAY;GACnB,MAAM,SAAS,OAAO,IAAI,SAAS,OAAO,CAAC,CAAC,KAC1C,OAAO,QAAQ,OAAO,aAAa,GACnC,OAAO,cACT;GACA,IAAI,WAAW,KAAA,GAEb,OAAO,YAAY,OAAO,OAAO,KAAK,GAAG,WAAW;GAEtD,IAAI,kBAAkB,OACpB,OAAO,QAAQ,qBAAqB,MAAM,IACtC,EAAE,MAAM,gBAAgB,IACxB;IAAE,MAAM;IAAU,OAAO;IAAQ;GAAM;GAG7C,MAAM,aAAa,QACjB,OAAO,IAAI,SAAS,GAAG,CAAC,CAAC,KACvB,OAAO,QAAQ,OAAO,aAAa,GACnC,OAAO,MACT;GACF,MAAM,eAAe,OAAO,IAAI,MAAM,eAAe,OAAO;GAC5D,MAAM,kBAAkB,OAAO,MAAM,cAAc;IACjD,SAAS,iBAEP,MAAM,MAAM,cAAc,SAAS,IAC/B,MAAM,OAAO,aAAa,cAAc,OAAO,CAAC,IAChD;IACN,cACE,OAAO,OAAO,gBAAgB,aAC7B,OAAO,eAAe,sBACrB,OAAO,eAAe,mBACtB,OAAO,KAAK,SAAS,QAAQ,mBAC3B,MAAM,OAAO,aAAa,MAAM,SAAS,OAAO,WAAW,CAAC,IAI5D,OAAO,OACH,OAAO,IAAI,MAAM,OAAO,OAAO,IAC9B,YAAY,QAAQ,cAAc,KAAA,CACrC,KAAK,OAAO,eAAe,kBAC3B,MAAM,OAAO,aAAa,IAAI,SAAS,OAAO,cAAc,CAAC,IAC7D;GACV,CAAC;GAID,OAAO,OAAO,eAAe,kBACzB,YAAY,OAAO,OAAO,KAAK,GAAG,eAAe,IACjD,GACE,MAAM,KAAK,WAAW,CAAC,GACvB,MAAM,UAAU,OAAO,OAAO,IAAI,GAClC,iBACA,OAAO,KAAK,MAAM,CACpB;EACN;CACF,CAAC;CAEH,OAAO,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,OAAO,KAAK,CAAC;AACjD"}
package/dist/index.d.ts CHANGED
@@ -4,7 +4,8 @@ import * as Result from "effect/Result";
4
4
  import * as OptimisticLocalStore from "./OptimisticLocalStore";
5
5
  import * as PaginatedQueryResult from "./PaginatedQueryResult";
6
6
  import * as QueryResult from "./QueryResult";
7
- export { OptimisticLocalStore, PaginatedQueryResult, QueryResult };
7
+ import * as StreamPagination from "./StreamPagination";
8
+ export { OptimisticLocalStore, PaginatedQueryResult, QueryResult, StreamPagination, };
8
9
  export type InvokeReturn<Ref_ extends Ref.Any> = [Ref.Error<Ref_>] extends [
9
10
  never
10
11
  ] ? Promise<Ref.Returns<Ref_>> : Promise<Result.Result<Ref.Returns<Ref_>, Ref.Error<Ref_>>>;
@@ -53,6 +54,35 @@ export type PaginatedQueryOptions = Parameters<typeof useConvexPaginatedQuery>[2
53
54
  * errors are thrown, to be caught by an error boundary.
54
55
  */
55
56
  export declare const usePaginatedQuery: <Query extends Ref.AnyPublicPaginatedQuery>(ref: Query, args: UsePaginatedQueryArgs<Query>, options: PaginatedQueryOptions) => PaginatedQueryResult.PaginatedQueryResult<PaginatedQueryItem<Query>, Ref.Error<Query>>;
57
+ /**
58
+ * EXPERIMENTAL—endCursor-pinned reactive pagination (see
59
+ * `notes/stream-based-querying.md`). Use it with paginated queries whose
60
+ * handlers paginate via `QueryStream.paginate`: those don't write the query
61
+ * journal that {@link usePaginatedQuery}'s built-in reactivity relies on,
62
+ * so gap-free pages must be maintained by the client instead. (It works
63
+ * with any paginated query honoring the `endCursor` protocol field,
64
+ * including the built-in `paginate`.)
65
+ *
66
+ * Each loaded page is pinned to a fixed index range by re-subscribing it
67
+ * with its `continueCursor` echoed back as `endCursor`—pages then grow
68
+ * and shrink reactively but always meet exactly, and a page that outgrows
69
+ * `initialNumItems` is split in two. This is the mechanism of
70
+ * `convex-helpers/react`'s `usePaginatedQuery`, re-expressed over the pure
71
+ * {@link StreamPagination} state machine, with args/items/errors codec'd
72
+ * through the ref's schemas exactly like {@link usePaginatedQuery}.
73
+ */
74
+ export declare const useStreamPaginatedQuery: <Query extends Ref.AnyPublicPaginatedQuery>(ref: Query, args: UsePaginatedQueryArgs<Query>, options: {
75
+ readonly initialNumItems: number;
76
+ /**
77
+ * Per-page read budgets forwarded as `paginationOpts.maximumRowsRead`
78
+ * and `paginationOpts.maximumBytesRead`: a page that would scan more
79
+ * rows, or read more bytes, than these returns truncated with
80
+ * `SplitRequired` (and the hook splits it) instead of exceeding
81
+ * Convex's query limits on a filter-heavy stream.
82
+ */
83
+ readonly maximumRowsRead?: number;
84
+ readonly maximumBytesRead?: number;
85
+ }) => PaginatedQueryResult.PaginatedQueryResult<PaginatedQueryItem<Query>, Ref.Error<Query>>;
56
86
  /**
57
87
  * An optimistic update for a Confect mutation. Mirrors Convex's
58
88
  * `OptimisticUpdate`, but receives a Confect {@link OptimisticLocalStore} and
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,17 +1,20 @@
1
1
  import { OptimisticLocalStore_exports, make } from "./OptimisticLocalStore.js";
2
2
  import { PaginatedQueryResult_exports, canLoadMore, exhausted, failure, loadingFirstPage, loadingMore } from "./PaginatedQueryResult.js";
3
3
  import { QueryResult_exports, fail, load, succeed } from "./QueryResult.js";
4
+ import { StreamPagination_exports, empty, initial, interpret, isLastPageSplitting, loadMore, pageRequests } from "./StreamPagination.js";
4
5
  import { Ref } from "@confect/core";
5
6
  import { version } from "convex";
6
7
  import * as ConvexReact from "convex/react";
7
- import { useAction as useAction$1, useMutation as useMutation$1, useQuery as useQuery$1 } from "convex/react";
8
+ import { useAction as useAction$1, useMutation as useMutation$1, useQueries, useQuery as useQuery$1 } from "convex/react";
9
+ import { getFunctionName } from "convex/server";
10
+ import { ConvexError, convexToJson } from "convex/values";
8
11
  import * as Cause from "effect/Cause";
9
12
  import * as Effect from "effect/Effect";
10
13
  import * as Result from "effect/Result";
11
14
  import * as Exit from "effect/Exit";
12
15
  import * as Match from "effect/Match";
13
16
  import * as Option from "effect/Option";
14
- import { useCallback, useMemo } from "react";
17
+ import { useCallback, useMemo, useState } from "react";
15
18
  //#region src/index.ts
16
19
  const useQuery = (ref, ...rest) => {
17
20
  const functionReference = Ref.getFunctionReference(ref);
@@ -37,8 +40,8 @@ const useQuery = (ref, ...rest) => {
37
40
  const useConvexPaginatedQueryInternal = ConvexReact.usePaginatedQueryInternal;
38
41
  const MINIMUM_CONVEX_VERSION = "1.36.0";
39
42
  /**
40
- * The non-throwing mode of `usePaginatedQueryInternal` — its fourth,
41
- * `throwOnError` parameter — arrived in convex 1.36.0. Convex 1.32 through
43
+ * The non-throwing mode of `usePaginatedQueryInternal`—its fourth,
44
+ * `throwOnError` parameter—arrived in convex 1.36.0. Convex 1.32 through
42
45
  * 1.35 export the same symbol taking only three parameters, where a fourth
43
46
  * argument is silently ignored and errors are always thrown.
44
47
  *
@@ -48,7 +51,7 @@ const MINIMUM_CONVEX_VERSION = "1.36.0";
48
51
  * version is the only reliable signal.
49
52
  */
50
53
  const supportsErrorsAsValues = (() => {
51
- const [major, minor] = String(version).split(".").map(Number);
54
+ const [major, minor] = version.split(".").map(Number);
52
55
  if (major === void 0 || minor === void 0 || Number.isNaN(major) || Number.isNaN(minor)) return false;
53
56
  const [minimumMajor, minimumMinor] = MINIMUM_CONVEX_VERSION.split(".").map(Number);
54
57
  return major > minimumMajor || major === minimumMajor && minor >= minimumMinor;
@@ -70,7 +73,7 @@ const supportsErrorsAsValues = (() => {
70
73
  * errors are thrown, to be caught by an error boundary.
71
74
  */
72
75
  const usePaginatedQuery = (ref, args, options) => {
73
- if (useConvexPaginatedQueryInternal === void 0 || !supportsErrorsAsValues) throw new Error(`usePaginatedQuery requires convex >= ${MINIMUM_CONVEX_VERSION}, but found ${version}. Earlier versions always throw paginated query errors instead of returning them, so a declared \`error\` schema cannot be surfaced as a \`Failure\` — upgrade the \`convex\` package.`);
76
+ if (useConvexPaginatedQueryInternal === void 0 || !supportsErrorsAsValues) throw new Error(`usePaginatedQuery requires convex >= ${MINIMUM_CONVEX_VERSION}, but found ${version}. Earlier versions always throw paginated query errors instead of returning them, so a declared \`error\` schema cannot be surfaced as a \`Failure\`—upgrade the \`convex\` package.`);
74
77
  const functionReference = Ref.getFunctionReference(ref);
75
78
  const skipped = args === "skip";
76
79
  const encodedArgs = useMemo(() => args === "skip" ? "skip" : Ref.encodePaginatedQueryArgsSync(ref, args), [ref, args]);
@@ -100,6 +103,142 @@ const usePaginatedQuery = (ref, args, options) => {
100
103
  error
101
104
  ]);
102
105
  };
106
+ /**
107
+ * Whether a paginated query error means the stored cursors no longer match
108
+ * the query (a data-dependent query changed underneath us), calling for a
109
+ * full pagination reset rather than a failure.
110
+ */
111
+ const isInvalidCursorError = (error) => error.message.includes("InvalidCursor") || error instanceof ConvexError && typeof error.data === "object" && error.data !== null && error.data.paginationError === "InvalidCursor";
112
+ const NO_ITEMS = [];
113
+ /**
114
+ * EXPERIMENTAL—endCursor-pinned reactive pagination (see
115
+ * `notes/stream-based-querying.md`). Use it with paginated queries whose
116
+ * handlers paginate via `QueryStream.paginate`: those don't write the query
117
+ * journal that {@link usePaginatedQuery}'s built-in reactivity relies on,
118
+ * so gap-free pages must be maintained by the client instead. (It works
119
+ * with any paginated query honoring the `endCursor` protocol field,
120
+ * including the built-in `paginate`.)
121
+ *
122
+ * Each loaded page is pinned to a fixed index range by re-subscribing it
123
+ * with its `continueCursor` echoed back as `endCursor`—pages then grow
124
+ * and shrink reactively but always meet exactly, and a page that outgrows
125
+ * `initialNumItems` is split in two. This is the mechanism of
126
+ * `convex-helpers/react`'s `usePaginatedQuery`, re-expressed over the pure
127
+ * {@link StreamPagination} state machine, with args/items/errors codec'd
128
+ * through the ref's schemas exactly like {@link usePaginatedQuery}.
129
+ */
130
+ const useStreamPaginatedQuery = (ref, args, options) => {
131
+ const functionReference = useMemo(() => Ref.getFunctionReference(ref), [ref]);
132
+ const skipped = args === "skip";
133
+ const rawEncodedArgs = args === "skip" ? void 0 : Ref.encodePaginatedQueryArgsSync(ref, args);
134
+ const encodedArgsKey = rawEncodedArgs === void 0 ? "skip" : JSON.stringify(convexToJson(rawEncodedArgs));
135
+ const encodedArgs = useMemo(() => rawEncodedArgs, [ref, encodedArgsKey]);
136
+ const resetKey = useMemo(() => JSON.stringify([
137
+ getFunctionName(functionReference),
138
+ encodedArgsKey,
139
+ options.initialNumItems,
140
+ options.maximumRowsRead ?? null,
141
+ options.maximumBytesRead ?? null
142
+ ]), [
143
+ functionReference,
144
+ encodedArgsKey,
145
+ options.initialNumItems,
146
+ options.maximumRowsRead,
147
+ options.maximumBytesRead
148
+ ]);
149
+ const freshState = () => skipped ? empty : initial(options.initialNumItems, options);
150
+ const [tracked, setTracked] = useState(() => ({
151
+ resetKey,
152
+ state: freshState()
153
+ }));
154
+ let current = tracked;
155
+ if (current.resetKey !== resetKey) {
156
+ current = {
157
+ resetKey,
158
+ state: freshState()
159
+ };
160
+ setTracked(current);
161
+ }
162
+ const applyTransitions = useCallback((transitions) => setTracked((previous) => ({
163
+ resetKey: previous.resetKey,
164
+ state: transitions.reduce((state, transition) => transition(state), previous.state)
165
+ })), []);
166
+ const queries = useMemo(() => pageRequests(current.state, (page) => ({
167
+ query: functionReference,
168
+ args: {
169
+ ...encodedArgs,
170
+ paginationOpts: page
171
+ }
172
+ })), [
173
+ current.state,
174
+ functionReference,
175
+ encodedArgs
176
+ ]);
177
+ const resultsObject = useQueries(queries);
178
+ const interpretation = useMemo(() => interpret(current.state, resultsObject, {
179
+ initialNumItems: options.initialNumItems,
180
+ isInvalidCursorError
181
+ }), [
182
+ current.state,
183
+ resultsObject,
184
+ options.initialNumItems
185
+ ]);
186
+ if (interpretation._tag === "ResetRequired") setTracked((previous) => ({
187
+ resetKey: previous.resetKey,
188
+ state: freshState()
189
+ }));
190
+ else if (interpretation._tag === "Interpreted" && interpretation.transitions.length > 0) applyTransitions(interpretation.transitions);
191
+ const encodedItems = interpretation._tag === "ResetRequired" ? NO_ITEMS : interpretation.items;
192
+ const decodeCache = useMemo(() => /* @__PURE__ */ new WeakMap(), [ref]);
193
+ const decodedResults = useMemo(() => encodedItems.map((item) => {
194
+ if (typeof item !== "object" || item === null) return Ref.decodePaginationPageSync(ref, [item])[0];
195
+ const cached = decodeCache.get(item);
196
+ if (cached !== void 0) return cached;
197
+ const decoded = Ref.decodePaginationPageSync(ref, [item])[0];
198
+ decodeCache.set(item, decoded);
199
+ return decoded;
200
+ }), [
201
+ ref,
202
+ encodedItems,
203
+ decodeCache
204
+ ]);
205
+ return useMemo(() => {
206
+ if (interpretation._tag === "ResetRequired") return loadingFirstPage({ skipped });
207
+ if (interpretation._tag === "Failed") {
208
+ const error = interpretation.error;
209
+ if (Ref.isConvexError(error)) {
210
+ const decoded = Ref.decodeErrorOption(ref, error.data);
211
+ if (Option.isSome(decoded)) return failure({
212
+ error: decoded.value,
213
+ results: decodedResults
214
+ });
215
+ }
216
+ throw error;
217
+ }
218
+ const { lastResult } = interpretation;
219
+ if (Option.isNone(lastResult) && current.state.pageKeys.length <= 1) return loadingFirstPage({ skipped });
220
+ if (Option.isNone(lastResult) || isLastPageSplitting(current.state)) return loadingMore({ results: decodedResults });
221
+ if (lastResult.value.isDone) return exhausted({ results: decodedResults });
222
+ const continueCursor = lastResult.value.continueCursor;
223
+ let alreadyLoadingMore = false;
224
+ return canLoadMore({
225
+ results: decodedResults,
226
+ loadMore: (numItems) => {
227
+ if (!alreadyLoadingMore) {
228
+ alreadyLoadingMore = true;
229
+ applyTransitions([loadMore(continueCursor, numItems, options)]);
230
+ }
231
+ }
232
+ });
233
+ }, [
234
+ interpretation,
235
+ decodedResults,
236
+ current.state,
237
+ skipped,
238
+ ref,
239
+ applyTransitions
240
+ ]);
241
+ };
103
242
  const makeReactMutation = (ref, convexReactMutation) => {
104
243
  const callable = ((...args) => invokeAsResult(ref, (_, encodedArgs) => convexReactMutation(encodedArgs), args).then((result) => Ref.hasErrorSchema(ref) ? result : Result.getOrThrow(result)));
105
244
  const withOptimisticUpdate = (optimisticUpdate) => {
@@ -129,7 +268,8 @@ const makeReactMutation = (ref, convexReactMutation) => {
129
268
  * Any other failure rejects the promise.
130
269
  */
131
270
  const useMutation = (ref) => {
132
- const convexReactMutation = useMutation$1(Ref.getFunctionReference(ref));
271
+ const functionReference = Ref.getFunctionReference(ref);
272
+ const convexReactMutation = useMutation$1(functionReference);
133
273
  return useMemo(() => makeReactMutation(ref, convexReactMutation), [ref, convexReactMutation]);
134
274
  };
135
275
  /**
@@ -146,15 +286,17 @@ const useMutation = (ref) => {
146
286
  * Any other failure rejects the promise.
147
287
  */
148
288
  const useAction = (ref) => {
149
- const actualAction = useAction$1(Ref.getFunctionReference(ref));
289
+ const functionReference = Ref.getFunctionReference(ref);
290
+ const actualAction = useAction$1(functionReference);
150
291
  return useCallback(((...args) => invokeAsResult(ref, (_, encodedArgs) => actualAction(encodedArgs), args).then((result) => Ref.hasErrorSchema(ref) ? result : Result.getOrThrow(result))), [ref, actualAction]);
151
292
  };
152
- const invokeAsResult = async (ref, invoke, args) => {
153
- const exit = await Effect.runPromiseExit(Ref.runWithCodec(ref, args[0] ?? {}, invoke).pipe(Effect.catchTag("SchemaError", Effect.die), Effect.result));
154
- if (Exit.isSuccess(exit)) return exit.value;
155
- throw Cause.squash(exit.cause);
293
+ const invokeAsResult = (ref, invoke, args) => {
294
+ return Effect.runPromiseExit(Ref.runWithCodec(ref, args[0] ?? {}, invoke).pipe(Effect.catchTag("SchemaError", Effect.die), Effect.result)).then((exit) => {
295
+ if (Exit.isSuccess(exit)) return exit.value;
296
+ throw Cause.squash(exit.cause);
297
+ });
156
298
  };
157
299
  //#endregion
158
- export { OptimisticLocalStore_exports as OptimisticLocalStore, PaginatedQueryResult_exports as PaginatedQueryResult, QueryResult_exports as QueryResult, useAction, useMutation, usePaginatedQuery, useQuery };
300
+ export { OptimisticLocalStore_exports as OptimisticLocalStore, PaginatedQueryResult_exports as PaginatedQueryResult, QueryResult_exports as QueryResult, StreamPagination_exports as StreamPagination, useAction, useMutation, usePaginatedQuery, useQuery, useStreamPaginatedQuery };
159
301
 
160
302
  //# sourceMappingURL=index.js.map