Skip to content

Commit 6d57ff1

Browse files
committed
Update tanstack-db TB integation to match upstream PR.
1 parent f1e2388 commit 6d57ff1

File tree

2 files changed

+59
-48
lines changed

2 files changed

+59
-48
lines changed

examples/local-first/src/App.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { QueryClient } from "@tanstack/query-core";
2-
import {
3-
useLiveQuery,
4-
createCollection,
5-
type MutationFn,
6-
} from "@tanstack/react-db";
2+
import { useLiveQuery, createCollection } from "@tanstack/react-db";
73
import { queryCollectionOptions } from "@tanstack/db-collections";
84

95
import { initClient, type Client } from "trailbase";
@@ -24,13 +20,15 @@ type Data = {
2420
const queryClient = new QueryClient();
2521
const useTrailBase = true;
2622

27-
const dataCollection = createCollection(
28-
useTrailBase
29-
? trailBaseCollectionOptions<Data>({
23+
const dataCollection = useTrailBase
24+
? createCollection(
25+
trailBaseCollectionOptions<Data>({
3026
recordApi: client.records<Data>("data"),
3127
getKey: (item) => item.id ?? -1,
32-
})
33-
: queryCollectionOptions<Data>({
28+
}),
29+
)
30+
: createCollection(
31+
queryCollectionOptions<Data>({
3432
id: "data",
3533
queryKey: ["data"],
3634
queryFn: async () => {
@@ -40,7 +38,7 @@ const dataCollection = createCollection(
4038
getKey: (item) => item.id ?? -1,
4139
queryClient: queryClient,
4240
}),
43-
);
41+
);
4442

4543
function App() {
4644
const [input, setInput] = useState("");
@@ -67,6 +65,7 @@ function App() {
6765
updated: null,
6866
data: formJson.text as string,
6967
});
68+
setInput("");
7069
}
7170
}
7271

@@ -101,6 +100,7 @@ function App() {
101100
<input
102101
name="text"
103102
type="text"
103+
value={input}
104104
onInput={(e) => setInput(e.currentTarget.value)}
105105
/>
106106

examples/local-first/src/lib/trailbase.ts

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { type RecordApi } from "trailbase";
1+
import { Store } from "@tanstack/store";
2+
import type { Event, RecordApi } from "trailbase";
23

34
import type { CollectionConfig, SyncConfig, UtilsRecord } from "@tanstack/db";
4-
import { Store } from "@tanstack/store";
55

66
/**
77
* Configuration interface for TrailbaseCollection
@@ -11,7 +11,7 @@ export interface TrailBaseCollectionConfig<
1111
TKey extends string | number = string | number,
1212
> extends Omit<
1313
CollectionConfig<TItem, TKey>,
14-
"sync" | "onInsert" | "onUpdate" | "onDelete"
14+
`sync` | `onInsert` | `onUpdate` | `onDelete`
1515
> {
1616
/**
1717
* Record API name
@@ -21,10 +21,8 @@ export interface TrailBaseCollectionConfig<
2121

2222
export type AwaitTxIdFn = (txId: string, timeout?: number) => Promise<boolean>;
2323

24-
export type RefetchFn = () => Promise<void>;
25-
2624
export interface TrailBaseCollectionUtils extends UtilsRecord {
27-
refetch: RefetchFn;
25+
cancel: () => void;
2826
}
2927

3028
export function trailBaseCollectionOptions<TItem extends object>(
@@ -35,7 +33,7 @@ export function trailBaseCollectionOptions<TItem extends object>(
3533
const seenIds = new Store(new Map<string, number>());
3634

3735
const awaitIds = (
38-
ids: string[],
36+
ids: Array<string>,
3937
timeout: number = 120 * 1000,
4038
): Promise<void> => {
4139
const completed = (value: Map<string, number>) =>
@@ -67,6 +65,7 @@ export function trailBaseCollectionOptions<TItem extends object>(
6765
seen.setState((curr) => {
6866
const now = Date.now();
6967
let anyExpired = false;
68+
7069
const notExpired = curr.entries().filter(([_, v]) => {
7170
const expired = now - v > 300 * 1000;
7271
anyExpired = anyExpired || expired;
@@ -84,33 +83,39 @@ export function trailBaseCollectionOptions<TItem extends object>(
8483
}, 120 * 1000);
8584

8685
type SyncParams = Parameters<SyncConfig<TItem>[`sync`]>[0];
87-
let syncParams: SyncParams | undefined;
86+
87+
let eventReader: ReadableStreamDefaultReader<Event> | undefined;
8888
const sync = {
8989
sync: (params: SyncParams) => {
90-
syncParams = params;
9190
const { begin, write, commit } = params;
9291

9392
// Initial fetch.
9493
async function initialFetch() {
95-
let response = await config.recordApi.list({ count: true });
94+
const limit = 256;
95+
let response = await config.recordApi.list({
96+
pagination: {
97+
limit,
98+
},
99+
});
96100
let cursor = response.cursor;
97101
let got = 0;
98102

99103
begin();
100104

101105
while (true) {
102106
const length = response.records.length;
103-
if (length === 0) {
104-
break;
105-
}
107+
if (length === 0) break;
106108

107109
got = got + length;
108110
for (const item of response.records) {
109-
write({ type: "insert", value: item as TItem });
111+
write({ type: `insert`, value: item });
110112
}
111113

114+
if (length < limit) break;
115+
112116
response = await config.recordApi.list({
113117
pagination: {
118+
limit,
114119
cursor,
115120
offset: cursor === undefined ? got : undefined,
116121
},
@@ -123,22 +128,29 @@ export function trailBaseCollectionOptions<TItem extends object>(
123128

124129
// Afterwards subscribe.
125130
async function subscribe() {
126-
const eventStream = await config.recordApi.subscribe("*");
131+
const eventStream = await config.recordApi.subscribe(`*`);
132+
const reader = (eventReader = eventStream.getReader());
133+
134+
while (true) {
135+
const { done, value: event } = await reader.read();
127136

128-
for await (const event of eventStream) {
129-
console.debug(`Event: ${JSON.stringify(event)}`);
137+
if (done || !event) {
138+
reader.releaseLock();
139+
eventReader = undefined;
140+
return;
141+
}
130142

131143
begin();
132144
let value: TItem | undefined;
133-
if ("Insert" in event) {
145+
if (`Insert` in event) {
134146
value = event.Insert as TItem;
135-
write({ type: "insert", value });
136-
} else if ("Delete" in event) {
147+
write({ type: `insert`, value });
148+
} else if (`Delete` in event) {
137149
value = event.Delete as TItem;
138-
write({ type: "delete", value });
139-
} else if ("Update" in event) {
150+
write({ type: `delete`, value });
151+
} else if (`Update` in event) {
140152
value = event.Update as TItem;
141-
write({ type: "update", value });
153+
write({ type: `update`, value });
142154
} else {
143155
console.error(`Error: ${event.Error}`);
144156
}
@@ -154,9 +166,7 @@ export function trailBaseCollectionOptions<TItem extends object>(
154166
}
155167
}
156168

157-
initialFetch().then(() => {
158-
subscribe();
159-
});
169+
initialFetch().then(() => subscribe());
160170
},
161171
// Expose the getSyncMetadata function
162172
getSyncMetadata: undefined,
@@ -165,11 +175,11 @@ export function trailBaseCollectionOptions<TItem extends object>(
165175
return {
166176
sync,
167177
getKey,
168-
onInsert: async (params): Promise<(number | string)[]> => {
178+
onInsert: async (params): Promise<Array<number | string>> => {
169179
const ids = await config.recordApi.createBulk(
170180
params.transaction.mutations.map((tx) => {
171181
const { type, changes } = tx;
172-
if (type !== "insert") {
182+
if (type !== `insert`) {
173183
throw new Error(`Expected 'insert', got: ${type}`);
174184
}
175185
return changes as TItem;
@@ -184,10 +194,10 @@ export function trailBaseCollectionOptions<TItem extends object>(
184194
return ids;
185195
},
186196
onUpdate: async (params) => {
187-
const ids: string[] = await Promise.all(
197+
const ids: Array<string> = await Promise.all(
188198
params.transaction.mutations.map(async (tx) => {
189199
const { type, changes, key } = tx;
190-
if (type !== "update") {
200+
if (type !== `update`) {
191201
throw new Error(`Expected 'update', got: ${type}`);
192202
}
193203

@@ -202,10 +212,10 @@ export function trailBaseCollectionOptions<TItem extends object>(
202212
await awaitIds(ids);
203213
},
204214
onDelete: async (params) => {
205-
const ids: string[] = await Promise.all(
215+
const ids: Array<string> = await Promise.all(
206216
params.transaction.mutations.map(async (tx) => {
207217
const { type, key } = tx;
208-
if (type !== "delete") {
218+
if (type !== `delete`) {
209219
throw new Error(`Expected 'delete', got: ${type}`);
210220
}
211221

@@ -220,11 +230,12 @@ export function trailBaseCollectionOptions<TItem extends object>(
220230
await awaitIds(ids);
221231
},
222232
utils: {
223-
// NOTE: Refetch shouldn't be necessary, we'll see. It may still be
224-
// necessary if subscriptions gets temporarily disconnected and changes
225-
// get lost.
226-
refetch: async () => {
227-
console.warn(`Not implemented: refetch`, syncParams?.collection);
233+
cancel: () => {
234+
if (eventReader) {
235+
eventReader.cancel();
236+
eventReader.releaseLock();
237+
eventReader = undefined;
238+
}
228239
},
229240
},
230241
};

0 commit comments

Comments
 (0)