본문으로 건너뛰기

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 타임스탬프)을 사용합니다. 이러한 변환을 처리하려면 parse 및 serialize을 사용합니다:

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>
)
}

자세히 알아보기​