Skip to content

Commit ebd710f

Browse files
committed
Add a parse/serialize translation layer to map JS Date objects w/o changing downstream example code.
1 parent db6a550 commit ebd710f

File tree

4 files changed

+83
-19
lines changed

4 files changed

+83
-19
lines changed

examples/react/todo/src/lib/collections.ts

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,38 @@ export const queryTodoCollection = createCollection(
107107
})
108108
)
109109

110+
type Todo = {
111+
id: number
112+
text: string
113+
completed: boolean
114+
created_at: number
115+
updated_at: number
116+
}
117+
110118
// TrailBase Todo Collection
111-
export const trailBaseTodoCollection = createCollection<SelectTodo>(
112-
trailBaseCollectionOptions({
119+
export const trailBaseTodoCollection = createCollection(
120+
trailBaseCollectionOptions<SelectTodo, Todo>({
113121
id: `todos`,
114122
getKey: (item) => item.id,
115123
schema: selectTodoSchema,
116124
recordApi: trailBaseClient.records(`todos`),
125+
// Re-using the example's drizzle-schema requires remapping the items.
126+
parse: (record: Todo): SelectTodo => ({
127+
id: record.id,
128+
text: record.text,
129+
completed: record.completed,
130+
created_at: new Date(record.created_at * 1000),
131+
updated_at: new Date(record.updated_at * 1000),
132+
}),
133+
serialize: (item) => ({
134+
id: item.id,
135+
text: item.text,
136+
completed: item.completed,
137+
created_at:
138+
item.created_at && Math.floor(item.created_at.valueOf() / 1000),
139+
updated_at:
140+
item.updated_at && Math.floor(item.updated_at.valueOf() / 1000),
141+
}),
117142
})
118143
)
119144

@@ -185,12 +210,37 @@ export const queryConfigCollection = createCollection(
185210
})
186211
)
187212

213+
type Config = {
214+
id: number
215+
key: string
216+
value: string
217+
created_at: number
218+
updated_at: number
219+
}
220+
188221
// TrailBase Config Collection
189-
export const trailBaseConfigCollection = createCollection<SelectConfig>(
190-
trailBaseCollectionOptions({
222+
export const trailBaseConfigCollection = createCollection(
223+
trailBaseCollectionOptions<SelectConfig, Config>({
191224
id: `config`,
192225
getKey: (item) => item.id,
193226
schema: selectConfigSchema,
194227
recordApi: trailBaseClient.records(`config`),
228+
// Re-using the example's drizzle-schema requires remapping the items.
229+
parse: (record: Config): SelectConfig => ({
230+
id: record.id,
231+
key: record.key,
232+
value: record.value,
233+
created_at: new Date(record.created_at * 1000),
234+
updated_at: new Date(record.updated_at * 1000),
235+
}),
236+
serialize: (item) => ({
237+
id: item.id,
238+
key: item.key,
239+
value: item.value,
240+
created_at:
241+
item.created_at && Math.floor(item.created_at.valueOf() / 1000),
242+
updated_at:
243+
item.updated_at && Math.floor(item.updated_at.valueOf() / 1000),
244+
}),
195245
})
196246
)

examples/react/todo/traildepot/migrations/U1752518653__create_table_todos.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ CREATE TABLE todos (
22
"id" INTEGER PRIMARY KEY NOT NULL,
33
"text" TEXT NOT NULL,
44
"completed" INTEGER NOT NULL DEFAULT 0,
5-
"created_at" TEXT NOT NULL DEFAULT(STRFTIME('%FT%TZ')),
6-
"updated_at" TEXT NOT NULL DEFAULT(STRFTIME('%FT%TZ'))
5+
"created_at" INTEGER NOT NULL DEFAULT(UNIXEPOCH()),
6+
"updated_at" INTEGER NOT NULL DEFAULT(UNIXEPOCH())
77
) STRICT;
88

99
CREATE TRIGGER _todos__update_trigger AFTER UPDATE ON todos FOR EACH ROW
1010
BEGIN
11-
UPDATE todos SET updated_at = STRFTIME('%FT%TZ') WHERE id = OLD.id;
11+
UPDATE todos SET updated_at = UNIXEPOCH() WHERE id = OLD.id;
1212
END;

examples/react/todo/traildepot/migrations/U1752518746__create_table_config.sql

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ CREATE TABLE config (
22
"id" INTEGER PRIMARY KEY NOT NULL,
33
"key" TEXT NOT NULL,
44
"value" TEXT NOT NULL,
5-
"created_at" TEXT NOT NULL DEFAULT(STRFTIME('%FT%TZ')),
6-
"updated_at" TEXT NOT NULL DEFAULT(STRFTIME('%FT%TZ'))
7-
);
5+
"created_at" INTEGER NOT NULL DEFAULT(UNIXEPOCH()),
6+
"updated_at" INTEGER NOT NULL DEFAULT(UNIXEPOCH())
7+
) STRICT;
88

99
CREATE UNIQUE INDEX _config_key_index ON config ("key");
1010

1111
CREATE TRIGGER _config__update_trigger AFTER UPDATE ON config FOR EACH ROW
1212
BEGIN
13-
UPDATE config SET updated_at = STRFTIME('%FT%TZ') WHERE id = OLD.id;
13+
UPDATE config SET updated_at = UNIXEPOCH() WHERE id = OLD.id;
1414
END;
1515

1616
-- Insert default config for background color

packages/trailbase-db-collection/src/trailbase.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { CollectionConfig, SyncConfig, UtilsRecord } from "@tanstack/db"
99
*/
1010
export interface TrailBaseCollectionConfig<
1111
TItem extends object,
12+
TRecord extends object = TItem,
1213
TKey extends string | number = string | number,
1314
> extends Omit<
1415
CollectionConfig<TItem, TKey>,
@@ -17,7 +18,10 @@ export interface TrailBaseCollectionConfig<
1718
/**
1819
* Record API name
1920
*/
20-
recordApi: RecordApi<TItem>
21+
recordApi: RecordApi<TRecord>
22+
23+
parse?: (record: TRecord) => TItem
24+
serialize?: (item: Partial<TItem>) => Partial<TRecord>
2125
}
2226

2327
export type AwaitTxIdFn = (txId: string, timeout?: number) => Promise<boolean>
@@ -26,9 +30,13 @@ export interface TrailBaseCollectionUtils extends UtilsRecord {
2630
cancel: () => void
2731
}
2832

29-
export function trailBaseCollectionOptions<TItem extends object>(
30-
config: TrailBaseCollectionConfig<TItem>
31-
): CollectionConfig<TItem> & { utils: TrailBaseCollectionUtils } {
33+
export function trailBaseCollectionOptions<
34+
TItem extends object,
35+
TRecord extends object = TItem,
36+
TKey extends string | number = string | number,
37+
>(
38+
config: TrailBaseCollectionConfig<TItem, TRecord, TKey>
39+
): CollectionConfig<TItem, TKey> & { utils: TrailBaseCollectionUtils } {
3240
const getKey = config.getKey
3341

3442
const seenIds = new Store(new Map<string, number>())
@@ -83,7 +91,7 @@ export function trailBaseCollectionOptions<TItem extends object>(
8391
}
8492
}, 120 * 1000)
8593

86-
type SyncParams = Parameters<SyncConfig<TItem>[`sync`]>[0]
94+
type SyncParams = Parameters<SyncConfig<TItem, TKey>[`sync`]>[0]
8795

8896
let eventReader: ReadableStreamDefaultReader<Event> | undefined
8997
const cancel = () => {
@@ -117,7 +125,10 @@ export function trailBaseCollectionOptions<TItem extends object>(
117125

118126
got = got + length
119127
for (const item of response.records) {
120-
write({ type: `insert`, value: item })
128+
write({
129+
type: `insert`,
130+
value: (config.parse?.(item) ?? item) as TItem,
131+
})
121132
}
122133

123134
if (length < limit) break
@@ -205,7 +216,7 @@ export function trailBaseCollectionOptions<TItem extends object>(
205216
if (type !== `insert`) {
206217
throw new Error(`Expected 'insert', got: ${type}`)
207218
}
208-
return changes
219+
return (config.serialize?.(changes) ?? changes) as TRecord
209220
})
210221
)
211222

@@ -224,7 +235,10 @@ export function trailBaseCollectionOptions<TItem extends object>(
224235
throw new Error(`Expected 'update', got: ${type}`)
225236
}
226237

227-
await config.recordApi.update(key, changes)
238+
await config.recordApi.update(
239+
key,
240+
(config.serialize?.(changes) ?? changes) as Partial<TRecord>
241+
)
228242
return String(key)
229243
})
230244
)

0 commit comments

Comments
 (0)