본문으로 건너뛰기

TrailBase 컬렉션

TrailBase 컬렉션은 TanStack DB와 TrailBase 간의 원활한 통합을 제공하여 TrailBase의 자체 호스팅 애플리케이션 백엔드와 실시간 데이터 동기화를 지원합니다.

개요

TrailBase는 기본 제공 SQLite, V8 JS 런타임, 인증, 관리자 UI 및 동기화 기능을 갖춘 자체 호스팅이 쉬운 단일 실행 파일 애플리케이션 백엔드입니다.

@tanstack/trailbase-db-collection 패키지를 사용하면 다음 기능을 갖춘 컬렉션을 생성할 수 있습니다:

  • TrailBase Record API에서 데이터를 자동으로 동기화합니다
  • enable_subscriptions이 활성화되면 실시간 구독을 지원합니다
  • 오류 발생 시 자동 롤백으로 낙관적 업데이트를 처리합니다
  • 데이터 변환을 위한 파싱/직렬화 함수를 제공합니다

설치

npm install @tanstack/trailbase-db-collection @tanstack/react-db trailbase

기본 사용법

import { createCollection } from '@tanstack/react-db'
import { trailBaseCollectionOptions } from '@tanstack/trailbase-db-collection'
import { initClient } from 'trailbase'

const trailBaseClient = initClient(`https://your-trailbase-instance.com`)

const todosCollection = createCollection(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
})
)

구성 옵션

trailBaseCollectionOptions 함수는 다음 옵션을 허용합니다:

필수 옵션

  • id: 컬렉션의 고유 식별자
  • recordApi: trailBaseClient.records()를 통해 생성된 TrailBase Record API 인스턴스
  • getKey: 항목에서 고유 키를 추출하는 함수

선택적 옵션

  • schema: 클라이언트 측 검증을 위한 Standard Schema 호환 스키마(예: Zod, Effect)
  • parse: TrailBase에서 들어오는 데이터를 변환하는 파싱 함수에 필드 이름을 매핑하는 객체
  • serialize: TrailBase로 나가는 데이터를 변환하는 직렬화 함수에 필드 이름을 매핑하는 객체
  • onInsert: 항목이 삽입될 때 호출되는 핸들러 함수
  • onUpdate: 항목이 업데이트될 때 호출되는 핸들러 함수
  • onDelete: 항목이 삭제될 때 호출되는 핸들러 함수

데이터 변환

TrailBase는 저장소에 서로 다른 데이터 형식(예: Unix 타임스탬프)을 사용합니다. 이러한 변환을 처리하려면 parseserialize을 사용합니다:

type SelectTodo = {
id: string
text: string
created_at: number // Unix timestamp from TrailBase
completed: boolean
}

type Todo = {
id: string
text: string
created_at: Date // JavaScript Date for app usage
completed: boolean
}

const todosCollection = createCollection<SelectTodo, Todo>(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
schema: todoSchema,
// Transform TrailBase data to application format
parse: {
created_at: (ts) => new Date(ts * 1000),
},
// Transform application data to TrailBase format
serialize: {
created_at: (date) => Math.floor(date.valueOf() / 1000),
},
})
)

실시간 구독

TrailBase는 서버에서 활성화된 경우 실시간 구독을 지원합니다. 컬렉션은 변경 사항을 자동으로 구독하고 실시간으로 업데이트합니다:

const todosCollection = createCollection(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
// Real-time updates work automatically when
// enable_subscriptions is set in TrailBase config
})
)

// Changes from other clients will automatically update
// the collection in real-time

뮤테이션 핸들러

뮤테이션 핸들러를 제공하여 삽입, 업데이트 및 삭제를 처리합니다:

const todosCollection = createCollection(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
const newTodo = transaction.mutations[0].modified
// TrailBase handles the persistence automatically
// Add custom logic here if needed
},
onUpdate: async ({ transaction }) => {
const { original, modified } = transaction.mutations[0]
// TrailBase handles the persistence automatically
// Add custom logic here if needed
},
onDelete: async ({ transaction }) => {
const deletedTodo = transaction.mutations[0].original
// TrailBase handles the persistence automatically
// Add custom logic here if needed
},
})
)

완전한 예제

import { createCollection } from '@tanstack/react-db'
import { not } from '@tanstack/db'
import { trailBaseCollectionOptions } from '@tanstack/trailbase-db-collection'
import { initClient } from 'trailbase'
import { z } from 'zod'

const trailBaseClient = initClient(`https://your-trailbase-instance.com`)

// Define schema
const todoSchema = z.object({
id: z.string(),
text: z.string(),
completed: z.boolean(),
created_at: z.date(),
})

type SelectTodo = {
id: string
text: string
completed: boolean
created_at: number
}

type Todo = z.infer<typeof todoSchema>

// Create collection
export const todosCollection = createCollection<SelectTodo, Todo>(
trailBaseCollectionOptions({
id: 'todos',
recordApi: trailBaseClient.records('todos'),
getKey: (item) => item.id,
schema: todoSchema,
parse: {
created_at: (ts) => new Date(ts * 1000),
},
serialize: {
created_at: (date) => Math.floor(date.valueOf() / 1000),
},
onInsert: async ({ transaction }) => {
const newTodo = transaction.mutations[0].modified
console.log('Todo created:', newTodo)
},
})
)

// Use in component
function TodoList() {
const { data: todos } = useLiveQuery({
query: (q) =>
q
.from({ todo: todosCollection })
.where(({ todo }) => not(todo.completed))
.orderBy(({ todo }) => todo.created_at, 'desc'),
})

const addTodo = (text: string) => {
todosCollection.insert({
id: crypto.randomUUID(),
text,
completed: false,
created_at: new Date(),
})
}

return (
<div>
{todos.map((todo) => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
)
}

자세히 알아보기