← Public packages
@kentcdodds/world-builder
Build a shared 3D voxel world with friends and your AI agents, live.
app/client/powersync.ts
128 lines · 3.2 KB · TypeScriptimport {
PowerSyncDatabase,
Schema,
Table,
column,
type AbstractPowerSyncDatabase,
type PowerSyncBackendConnector,
} from '@powersync/web'
const worlds = new Table({ name: column.text, slug: column.text, created_by: column.text, created_at: column.text })
const players = new Table(
{
world_id: column.text,
kind: column.text,
owner_id: column.text,
display_name: column.text,
color: column.text,
cursor_x: column.integer,
cursor_y: column.integer,
cursor_z: column.integer,
last_seen: column.text,
},
{ indexes: { world: ['world_id'] } },
)
const builds = new Table(
{
world_id: column.text,
author_id: column.text,
prompt: column.text,
status: column.text,
block_count: column.integer,
created_at: column.text,
},
{ indexes: { world: ['world_id'] } },
)
const blocks = new Table(
{
world_id: column.text,
x: column.integer,
y: column.integer,
z: column.integer,
material: column.text,
build_id: column.text,
placed_by: column.text,
placed_at: column.text,
seq: column.integer,
},
{ indexes: { world: ['world_id'] } },
)
export const schema = new Schema({ worlds, players, builds, blocks })
export type BlockRow = {
id: string
world_id: string
x: number
y: number
z: number
material: string
build_id: string | null
placed_by: string | null
seq: number | null
}
export type PlayerRow = {
id: string
world_id: string
kind: 'human' | 'agent'
owner_id: string
display_name: string
color: string
cursor_x: number | null
cursor_y: number | null
cursor_z: number | null
last_seen: string | null
}
export type BuildRow = {
id: string
world_id: string
author_id: string
prompt: string | null
status: string
block_count: number
created_at: string
}
class UploadRetry extends Error {}
export function createConnector(appBase: string): PowerSyncBackendConnector {
return {
async fetchCredentials() {
const response = await fetch(`${appBase}/api/token`, { credentials: 'same-origin' })
if (!response.ok) throw new Error(`token ${response.status}`)
const body = (await response.json()) as { endpoint: string; token: string; expiresAt: string }
return { endpoint: body.endpoint, token: body.token, expiresAt: new Date(body.expiresAt) }
},
async uploadData(database: AbstractPowerSyncDatabase) {
const batch = await database.getCrudBatch(150)
if (!batch) return
const ops = batch.crud.map((entry) => ({
op: entry.op,
type: entry.table,
id: entry.id,
data: entry.opData ?? {},
}))
const response = await fetch(`${appBase}/api/upload`, {
method: 'POST',
credentials: 'same-origin',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ ops }),
})
if (response.status === 429 || response.status >= 500 || response.status === 401) {
throw new UploadRetry(`upload ${response.status}`)
}
// 2xx and other 4xx: the server applied what it accepted; rejected
// writes are reverted locally by the next sync checkpoint.
await batch.complete()
},
}
}
export function openDatabase(assetBase: string) {
const worker = `${assetBase}/powersync/worker.js`
return new PowerSyncDatabase({
schema,
database: { dbFilename: 'kody-world-builder.sqlite', worker },
sync: { worker },
})
}