Skip to content

Commit 59bcb8d

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

File tree

4 files changed

+83
-23
lines changed

4 files changed

+83
-23
lines changed

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

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,34 @@ 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+
...record,
128+
created_at: new Date(record.created_at * 1000),
129+
updated_at: new Date(record.updated_at * 1000),
130+
}),
131+
serialize: (item) => ({
132+
...item,
133+
created_at:
134+
item.created_at && Math.floor(item.created_at.valueOf() / 1000),
135+
updated_at:
136+
item.updated_at && Math.floor(item.updated_at.valueOf() / 1000),
137+
}),
117138
})
118139
)
119140

@@ -185,12 +206,33 @@ export const queryConfigCollection = createCollection(
185206
})
186207
)
187208

209+
type Config = {
210+
id: number
211+
key: string
212+
value: string
213+
created_at: number
214+
updated_at: number
215+
}
216+
188217
// TrailBase Config Collection
189-
export const trailBaseConfigCollection = createCollection<SelectConfig>(
190-
trailBaseCollectionOptions({
218+
export const trailBaseConfigCollection = createCollection(
219+
trailBaseCollectionOptions<SelectConfig, Config>({
191220
id: `config`,
192221
getKey: (item) => item.id,
193222
schema: selectConfigSchema,
194223
recordApi: trailBaseClient.records(`config`),
224+
// Re-using the example's drizzle-schema requires remapping the items.
225+
parse: (record: Config): SelectConfig => ({
226+
...record,
227+
created_at: new Date(record.created_at * 1000),
228+
updated_at: new Date(record.updated_at * 1000),
229+
}),
230+
serialize: (item) => ({
231+
...item,
232+
created_at:
233+
item.created_at && Math.floor(item.created_at.valueOf() / 1000),
234+
updated_at:
235+
item.updated_at && Math.floor(item.updated_at.valueOf() / 1000),
236+
}),
195237
})
196238
)

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: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import type { Event, RecordApi } from "trailbase"
55
import type { CollectionConfig, SyncConfig, UtilsRecord } from "@tanstack/db"
66

77
/**
8-
* Configuration interface for TrailbaseCollection
8+
* Configuration interface for Trailbase Collection
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> | TItem) => Partial<TRecord> | TRecord
2125
}
2226

2327
export type AwaitTxIdFn = (txId: string, timeout?: number) => Promise<boolean>
@@ -26,11 +30,21 @@ 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

42+
const parse = (record: TRecord) => (config.parse?.(record) ?? record) as TItem
43+
const serialUpd = (item: Partial<TItem>) =>
44+
(config.serialize?.(item) ?? item) as Partial<TRecord>
45+
const serialIns = (item: TItem) =>
46+
(config.serialize?.(item) ?? item) as TRecord
47+
3448
const seenIds = new Store(new Map<string, number>())
3549

3650
const awaitIds = (
@@ -83,7 +97,7 @@ export function trailBaseCollectionOptions<TItem extends object>(
8397
}
8498
}, 120 * 1000)
8599

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

88102
let eventReader: ReadableStreamDefaultReader<Event> | undefined
89103
const cancel = () => {
@@ -117,7 +131,10 @@ export function trailBaseCollectionOptions<TItem extends object>(
117131

118132
got = got + length
119133
for (const item of response.records) {
120-
write({ type: `insert`, value: item })
134+
write({
135+
type: `insert`,
136+
value: parse(item),
137+
})
121138
}
122139

123140
if (length < limit) break
@@ -149,13 +166,13 @@ export function trailBaseCollectionOptions<TItem extends object>(
149166
begin()
150167
let value: TItem | undefined
151168
if (`Insert` in event) {
152-
value = event.Insert as TItem
169+
value = parse(event.Insert as TRecord)
153170
write({ type: `insert`, value })
154171
} else if (`Delete` in event) {
155-
value = event.Delete as TItem
172+
value = parse(event.Delete as TRecord)
156173
write({ type: `delete`, value })
157174
} else if (`Update` in event) {
158-
value = event.Update as TItem
175+
value = parse(event.Update as TRecord)
159176
write({ type: `update`, value })
160177
} else {
161178
console.error(`Error: ${event.Error}`)
@@ -205,7 +222,7 @@ export function trailBaseCollectionOptions<TItem extends object>(
205222
if (type !== `insert`) {
206223
throw new Error(`Expected 'insert', got: ${type}`)
207224
}
208-
return changes
225+
return serialIns(changes)
209226
})
210227
)
211228

@@ -224,7 +241,8 @@ export function trailBaseCollectionOptions<TItem extends object>(
224241
throw new Error(`Expected 'update', got: ${type}`)
225242
}
226243

227-
await config.recordApi.update(key, changes)
244+
await config.recordApi.update(key, serialUpd(changes))
245+
228246
return String(key)
229247
})
230248
)

0 commit comments

Comments
 (0)