ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从 `spacetime dev` 到全栈实时应用:SpacetimeDB Nuxt-TS 模板实战指南

从 `spacetime dev` 到全栈实时应用:SpacetimeDB Nuxt-TS 模板实战指南 从spacetime dev到全栈实时应用SpacetimeDB Nuxt-TS 模板实战指南【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB本篇指南围绕 SpacetimeDB 官方 Nuxt 模板templates/nuxt-ts展开讲清如何在 5 分钟内用spacetime dev --template nuxt-ts搭建一个「服务端模块 Nuxt 客户端」的全栈应用。读完后你将掌握模板的项目结构、模块中表table与 reducer 的定义方式、用 CLI 直接调用 reducer 和查询数据、以及模板最有价值的混合渲染架构——服务端 API 路由负责 SSR 首屏取数客户端 composable 负责 WebSocket 实时更新。前置条件已安装 Node.js 18已安装 SpacetimeDB CLI仓库根目录的spacetime-install.sh/spacetime-install.ps1位于 crates/update可用于安装 CLI 工具链在继续之前请先确保 SpacetimeDB CLI 可用后续所有流程都依赖spacetime命令。创建项目执行一条命令即可创建包含 SpacetimeDB 模块和 Nuxt 客户端的新项目spacetime dev --template nuxt-ts这条命令会自动完成四件事启动本地 SpacetimeDB 服务器默认监听ws://localhost:3000发布你的模块将spacetimedb/下的 TypeScript 模块编译为 WASM 并加载生成 TypeScript 类型绑定输出到module_bindings/目录启动 Nuxt 开发服务器。浏览器访问http://localhost:5173即可看到运行中的应用——端口 5173 来自 nuxt.config.ts 中的devServer.port配置。模板自带一个可连接 SpacetimeDB 的基础 Nuxt 应用含连接状态指示、添加人的表单和实时名单列表。项目结构模板同时包含服务端模块和客户端代码my-spacetime-app/ ├── spacetimedb/ # Your SpacetimeDB module │ └── src/ │ └── index.ts # SpacetimeDB module logic ├── app.vue # Root component with provider ├── components/ │ └── AppContent.vue # Main UI component ├── server/ │ └── api/ │ └── people.get.ts # Server-side data fetching ├── module_bindings/ # Auto-generated types ├── nuxt.config.ts # Nuxt configuration └── package.json各部分职责spacetimedb/src/index.ts模块逻辑定义表、reducer 和生命周期钩子。修改这里后需重新生成绑定app.vue根组件挂载SpacetimeDBProvider管理 WebSocket 连接components/AppContent.vue主 UI 组件演示 SSR 数据与实时数据的切换server/api/people.get.tsNuxt 服务端 API 路由SSR 阶段取数module_bindings/由 CLI 从模块 schema 自动生成的类型与运行时对象文件头注释明确标注THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE WILL NOT BE SAVED请勿手改。理解表Tables与 Reducers打开 spacetimedb/src/index.ts 可以看到模块代码。模板包含一个person表和两个 reduceradd用于插入一个人sayHello用于问候所有人。表存储数据reducer 是修改数据的功能函数——它是写入数据库的唯一途径。这是 SpacetimeDB 的核心模型客户端不直接写库所有写操作都通过 reducer 经过确定性执行保证所有订阅者看到一致的状态。完整模块代码结合仓库中该文件的真实实现import { schema, table, t } from spacetimedb/server; const spacetimedb schema({ person: table( { public: true }, { name: t.string(), } ), }); export default spacetimedb; // 模块首次发布时调用 export const init spacetimedb.init(_ctx { // Called when the module is initially published }); // 每次新客户端连接时调用 export const onConnect spacetimedb.clientConnected(_ctx { // Called every time a new client connects }); // 每次客户端断开时调用 export const onDisconnect spacetimedb.clientDisconnected(_ctx { // Called every time a client disconnects }); export const add spacetimedb.reducer( { name: t.string() }, (ctx, { name }) { ctx.db.person.insert({ name }); } ); export const sayHello spacetimedb.reducer(ctx { for (const person of ctx.db.person.iter()) { console.info(Hello, ${person.name}!); } console.info(Hello, World!); });几个值得注意的点table({ public: true }, { name: t.string() })中第一个参数是表配置public: true表示对所有客户端可见、可订阅第二个参数是列定义t.string()是列类型reducer 通过spacetimedb.reducer(参数schema, 函数体)声明参数 schema 与表列定义使用同一套t.*类型系统客户端绑定的调用签名由此推导除 reducer 外模板还声明了init模块首次发布、clientConnected/clientDisconnected连接生命周期三个钩子从源码结构看这是模块中响应生命周期事件的入口可用于在线人数统计、欢迎消息等场景reducer 函数体内通过ctx.db.person.insert(...)写数据、ctx.db.person.iter()迭代查询。用 CLI 测试开一个新终端进入项目目录后用 SpacetimeDB CLI 直接调用 reducer 和查询数据cd my-spacetime-app # 调用 add reducer 插入一个人 spacetime call add Alice # 查询 person 表 spacetime sql SELECT * FROM person name --------- Alice # 调用 say_hello 问候所有人 spacetime call say_hello # 查看模块日志 spacetime logs 2025-01-13T12:00:00.000000Z INFO: Hello, Alice! 2025-01-13T12:00:00.000000Z INFO: Hello, World!两个容易踩坑的细节reducer 的调用名是蛇形命名模块中定义的 JS 标识符sayHello在调用时写作say_helloadd单字不变。这一点可以从 module_bindings/index.ts 中的__reducerSchema(say_hello, SayHelloReducer)得到印证——绑定生成时就把 reducer 名登记为say_hellospacetime sql支持直接对数据库执行 SQL适合在开发阶段绕过前端快速验证数据状态。另外package.json 中预置了几个常用脚本可脱离dev环境单独执行{ spacetime:generate: spacetime generate --lang typescript --out-dir module_bindings --module-path spacetimedb, spacetime:publish:local: spacetime publish --module-path spacetimedb --server local, spacetime:publish: spacetime publish --module-path spacetimedb --server maincloud, generate: pnpm --dir spacetimedb install cargo run -p gen-bindings -- --out-dir module_bindings --module-path spacetimedb prettier --write module_bindings }即修改模块后重跑spacetime:generate刷新绑定spacetime:publish:local发布到本地服务器spacetime:publish发布到 MainCloud。理解服务端渲染SSR混合架构SpacetimeDB SDK 同时支持服务端和客户端运行。模板采用混合策略服务端 API 路由server/api/people.get.tsSSR 阶段取初始数据保证首屏快速呈现客户端 composables在浏览器中维护实时 WebSocket 订阅接收后续更新。服务端 API 路由的工作方式是连接 SpacetimeDB → 订阅 → 取数 → 断开。真实的 people.get.ts 实现比基础版更稳健加入了 10 秒超时与错误处理// server/api/people.get.ts import { DbConnection, tables, type PersonRow } from ../../module_bindings; import type { Infer } from spacetimedb; const HOST process.env.SPACETIMEDB_HOST ?? ws://localhost:3000; const DB_NAME process.env.SPACETIMEDB_DB_NAME ?? nuxt-ts; type PersonData Infertypeof PersonRow; export default defineEventHandler(async (): PromisePersonData[] { return new Promise((resolve, reject) { const timeoutId setTimeout(() { reject(new Error(SpacetimeDB connection timeout)); }, 10000); DbConnection.builder() .withUri(HOST) .withDatabaseName(DB_NAME) .onConnect(conn { conn .subscriptionBuilder() .onApplied(() { clearTimeout(timeoutId); const people Array.from(conn.db.person.iter()); conn.disconnect(); resolve(people); }) .onError((_ctx, error) { clearTimeout(timeoutId); conn.disconnect(); reject(error); }) .subscribe(tables.person); }) .onConnectError((_ctx, error) { clearTimeout(timeoutId); reject(error); }) .build(); }); });关键机制解读DbConnection.builder().withUri(...).withDatabaseName(...).build()是连接构建器模式连接参数来自环境变量SPACETIMEDB_HOST与SPACETIMEDB_DB_NAME默认分别为ws://localhost:3000和nuxt-ts——部署到不同环境时只需改环境变量无需改代码subscriptionBuilder().subscribe(tables.person)订阅person表onApplied回调在初始快照或增量应用完毕后触发此时一次性iter()取出全部行、disconnect()断开并 resolve。这种「订一次、读完即断」的模式正是为 SSR 的短生命周期连接设计的与客户端长期保持的实时订阅形成对比Infertypeof PersonRow从生成的行类型推导出行数据的 TypeScript 类型API 路由因此具备完整的类型安全。配置 SpacetimeDB Provider根组件 app.vue 用SpacetimeDBProvider包裹应用由它统一管理 WebSocket 连接。Provider 外层包了ClientOnly使连接逻辑只在浏览器中运行SSR 阶段则依赖上面的服务端 API 路由取初始数据!-- app.vue -- template ClientOnly SpacetimeDBProvider :connection-builderconnectionBuilder AppContent / /SpacetimeDBProvider template #fallback AppContent / /template /ClientOnly /template script setup langts import { Identity } from spacetimedb; import { SpacetimeDBProvider } from spacetimedb/vue; import { DbConnection, type ErrorContext } from ./module_bindings; const HOST import.meta.env.VITE_SPACETIMEDB_HOST ?? ws://localhost:3000; const DB_NAME import.meta.env.VITE_SPACETIMEDB_DB_NAME ?? nuxt-ts; const TOKEN_KEY ${HOST}/${DB_NAME}/auth_token; const onConnect (_conn: DbConnection, identity: Identity, token: string) { localStorage.setItem(TOKEN_KEY, token); console.log( Connected to SpacetimeDB with identity:, identity.toHexString() ); }; const onDisconnect () { console.log(Disconnected from SpacetimeDB); }; const onConnectError (_ctx: ErrorContext, err: Error) { console.log(Error connecting to SpacetimeDB:, err); }; const connectionBuilder import.meta.client ? DbConnection.builder() .withUri(HOST) .withDatabaseName(DB_NAME) .withToken(localStorage.getItem(TOKEN_KEY) || undefined) .onConnect(onConnect) .onDisconnect(onDisconnect) .onConnectError(onConnectError) : undefined; /script几个值得关注的实现细节import.meta.client守卫connectionBuilder在服务端渲染时直接为undefined浏览器端才构建连接。配合模板中的template #fallbackhydration 前先用AppContent占位避免服务端执行localStorage等浏览器 API身份令牌缓存连接成功后回调把token以${HOST}/${DB_NAME}/auth_token为键写入localStorage下次连接时通过.withToken(...)带上缓存 token。由此推断这保证同一浏览器反复访问时复用同一个客户端身份identity而不是每次生成新身份。客户端连接参数改用VITE_前缀的环境变量VITE_SPACETIMEDB_HOST/VITE_SPACETIMEDB_DB_NAME因为 Nuxt/Vite 只有VITE_前缀的变量才会暴露给客户端代码事件回调onConnect拿到连接、identity 与 token、onDisconnect、onConnectError三个钩子覆盖了连接的主要生命周期模板把它们用于日志输出实际项目中可在此实现重连提示等逻辑。组合使用 composables 与 SSR 数据components/AppContent.vue 演示了数据层的切换策略先用useFetch在服务端加载初始数据再在客户端切换为实时 composable组件会立即显示服务端取回的数据直到 WebSocket 连接建立!-- components/AppContent.vue -- script setup langts import { ref, computed } from vue; import { tables, reducers } from ../module_bindings; // Fetch initial data server-side for SSR const { data: initialPeople } await useFetch(/api/people); // On the client, use real-time composables let conn, people, addReducer; if (import.meta.client) { const { useSpacetimeDB, useTable, useReducer } await import(spacetimedb/vue); conn useSpacetimeDB(); [people] useTable(tables.person); addReducer useReducer(reducers.add); } // Use real-time data once connected, fall back to SSR data const displayPeople computed(() { if (conn?.isActive people?.value) return people.value; return initialPeople.value ?? []; }); /script真实模板代码在此基础上有两处工程化增强spacetimedb/vue用动态import()加载并包在try/catch中确保该客户端专属模块绝不会在服务端执行路径上被静态引入useTable的第二个返回值tableReady也被解构出来用于追踪订阅就绪状态交互层的守卫表单在!conn?.isActive时禁用输入框与提交按钮addPerson提交前再次校验conn?.isActive addReducer避免连接未建立时调用 reducer。数据流总结为一条清晰的降级链WebSocket 已激活 → 用useTable的实时数据否则 → 用useFetch(/api/people)的 SSR 数据都没有 → 空数组。computed让这一切换对模板完全透明。生成的绑定module_bindingsmodule_bindings/目录由 CLI 依据模块 schema 生成模板中由 spacetimedb CLI 2.0.0 版本生成见 module_bindings/index.ts 文件头注释。它对客户端导出三样东西tables表引用每个引用同时是查询构造器__makeQueryBuilder的产物server/api/people.get.ts中subscribe(tables.person)用的就是它reducersreducer 的访问器映射add、say_hellouseReducer(reducers.add)从中取用DbConnection带本模块类型信息的连接类static builder()返回DbConnectionBuildersubscriptionBuilder()返回SubscriptionBuilder——服务端与客户端代码都从这里获取连接能力类型由REMOTE_MODULE的satisfies约束保证与模块 schema 一致。修改spacetimedb/src/index.ts后运行pnpm spacetime:generate即spacetime generate --lang typescript --out-dir module_bindings --module-path spacetimedb即可重新生成绑定。小结这套 Nuxt 模板的价值在于给出了一套可直接照搬的 SpacetimeDB Vue 全栈数据流范式模块侧用schema/table/reducer定义数据与写入口spacetime dev一条命令跑通「本地服务器 模块发布 绑定生成 开发服务器」客户端侧则用SpacetimeDBProvider ClientOnly管连接、用服务端 API 路由做 SSR 取数、用useTable/useReducer做实时更新并以computed无缝衔接两者。在此基础上扩展自己的表与 reducer再配合spacetime:publish发布到 MainCloud就是一个可上线的实时全栈应用起点。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表