SpacetimeDB Angular 快速入门:用spacetime dev --template angular-ts在 5 分钟内跑通前后端
【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB
本篇指南以 SpacetimeDB 官方 Angular 快速入门文档为骨架,结合当前仓库中的angular-ts模板源码,带你从零搭建一个「SpacetimeDB 服务端模块 + Angular 前端」的全栈应用。读完你将掌握:如何用一条命令完成本地服务器启动、模块发布、TypeScript 绑定生成与 Angular 开发服务器启动,如何编写表(table)与 reducer(reducer),如何用 SpacetimeDB CLI 直接调用 reducer、执行 SQL 查询和查看模块日志,以及模板中 Angular 连接层(injectSpacetimeDB、injectTable、injectReducer)的底层实现原理。
环境准备
开始前需要确保本机满足以下两个前提条件:
- Node.js 18+:Angular 应用构建与运行所需运行时环境;
- SpacetimeDB CLI:提供
spacetime dev、spacetime publish、spacetime call、spacetime sql、spacetime logs等核心命令,负责初始化模板、发布模块并与本地/云端数据库交互。
两个条件满足后,即可进入下一步创建项目。
创建项目:一条命令完成全链路启动
在任意空目录中执行:
spacetime dev --template angular-ts这条命令会替你依次完成四件事:
- 启动本地 SpacetimeDB 服务器;
- 发布你的模块(把
spacetimedb/目录下的服务端代码编译并发布到本地服务器); - 生成 TypeScript 绑定(生成到
src/module_bindings/目录); - 启动 Angular 开发服务器(
ng serve,默认端口 4200)。
从 CLI 源码看,--template参数由 crates/cli/src/subcommands/dev.rs 解析:它支持「模板 ID 或 GitHub 仓库(owner/repo或 URL)」两种形式,模板 ID 即本项目中的angular-ts。同时该文件注释明确要求所有服务端模板的代码必须放在spacetimedb/目录下,这正是模板目录结构如此设计的原因。
注意:如果当前目录已经存在 SpacetimeDB 项目,
--template会被忽略(CLI 会打印Warning: --template option is ignored because a SpacetimeDB project already exists.),因此该命令应在全新目录中执行。
也可以手动初始化
如果你想更精细地控制初始化过程,也可以使用spacetime init命令手动选择模板:
spacetime init --template angular-ts初始化完成后,再分别执行模块发布与前端启动:
spacetime publish --module-path spacetimedb --server local npm install npm run dev模板的 package.json 中预设了这些常用脚本:
dev:运行node scripts/dev.mjs,先为前端生成本地环境配置,再启动ng serve;build:ng build生产构建;spacetime:publish:local:发布到本地服务器(--server local);spacetime:publish:发布到主云服务器(--server maincloud);spacetime:generate:用 CLI 重新生成 TypeScript 绑定(spacetime generate --lang typescript)。
打开应用
开发服务器启动后,浏览器访问http://localhost:4200即可看到运行中的应用。
页面会显示:
- 连接状态(Connected / Disconnected,绿色/红色);
- 一个输入框和「Add Person」按钮;
- 一个「People」列表,实时展示
person表中的所有行。
模板自带了一个已连接到 SpacetimeDB 的基础 Angular 应用,你可以在页面上直接添加名字并实时看到列表更新——这一切的背后是订阅机制在起作用,详见下文「Angular 前端如何与数据库联动」。
项目结构解析
模板初始化后,你的项目(以my-spacetime-app/为例)包含服务端与客户端两部分代码:
my-spacetime-app/ ├── spacetimedb/ # 你的 SpacetimeDB 模块 │ └── src/ │ └── index.ts # 服务端逻辑(表与 reducer 定义) ├── src/ # Angular 前端 │ └── app/ │ ├── app.component.ts │ ├── app.config.ts │ └── module_bindings/ # 自动生成的类型 ├── angular.json └── package.json对照当前仓库中的真实模板(templates/angular-ts),完整的目录还要更丰富一些:
spacetimedb/src/index.ts:服务端模块入口,定义表与 reducer(下文详述);src/app/app.component.ts:Angular 根组件,负责 UI 渲染与 reducer 调用;src/app/app.config.ts:应用级配置,通过provideSpacetimeDB注入数据库连接;src/environments/environment.ts:默认连接配置(主机与数据库名);src/module_bindings/:自动生成的绑定层,包含tables、reducers、DbConnection、SubscriptionBuilder等类型化 API;scripts/dev.mjs:开发脚本,负责生成本地环境变量文件;angular.json、package.json、tsconfig*.json:Angular 工程配置。
一个值得注意的细节:src/module_bindings/下的文件是自动生成的,每个文件头部都有一行显式声明——THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.(此文件由 SpacetimeDB 自动生成,对它的修改不会被保存,请直接修改模块源码)。这意味着你不要手工编辑绑定文件,改表结构/ reducer 签名时只需修改spacetimedb/src/index.ts,然后重新生成绑定即可。
前后端各自的编辑入口
- 服务端:编辑
spacetimedb/src/index.ts,添加表(tables)和 reducer; - 客户端:编辑
src/app/app.component.ts构建你的 UI。
理解表(table)与 reducer(reducer)
打开spacetimedb/src/index.ts,可以看到模块的核心代码(与仓库中 templates/angular-ts/spacetimedb/src/index.ts 一致):
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!'); });表:存储数据的地方
schema({...})定义模块的数据库模式。模板中定义了一张person表:
table({ public: true }, {...}):第一个参数是表配置,public: true表示该表对客户端公开(客户端可以订阅并读取);第二个参数是列定义,name: t.string()声明了一列字符串类型的name。
表负责存储数据,是数据库的基本容器。
reducer:唯一的数据写入方式
spacetimedb.reducer(...)定义 reducer——一种服务端函数。Reducer 是修改数据的唯一方式,客户端不能直接写表,只能通过调用 reducer 间接写入。
模板提供了两个 reducer:
add:接收一个name参数,向person表插入一行。参数通过{ name: t.string() }进行类型声明,函数体内通过ctx.db.person.insert({ name })执行插入;sayHello:遍历person表中的每一行,用console.info打印问候语。ctx.db.person.iter()返回迭代器,逐行访问所有数据。
此外,模板还展示了三个生命周期钩子(这在原文档中未展开,但仓库源码中确实存在):
spacetimedb.init(_ctx => {...}):模块首次发布时调用;spacetimedb.clientConnected(_ctx => {...}):每次有新客户端连接时调用;spacetimedb.clientDisconnected(_ctx => {...}):每次有客户端断开时调用。
你可以在这些钩子里编写初始化数据、在线状态广播等逻辑。
用 CLI 测试:调用 reducer、查询与看日志
打开一个新的终端,进入项目目录,用 SpacetimeDB CLI 直接与本地数据库交互:
cd my-spacetime-app # 调用 add reducer,插入一个名叫 Alice 的人 spacetime call add Alice # 查询 person 表 spacetime sql "SELECT * FROM person" name --------- "Alice" # 调用 sayHello,向所有人打招呼 spacetime call say_hello # 查看模块日志 spacetime logs 2025-01-13T12:00:00.000000Z INFO: Hello, Alice! 2025-01-13T12:00:00.000000Z INFO: Hello, World!四个命令逐一说明:
| 命令 | 作用 |
|---|---|
spacetime call add Alice | 调用 reducer。位置参数按 reducer 声明顺序传入,Alice即name的值;注意 reducer 名sayHello在 CLI 中写作蛇形命名say_hello |
spacetime sql "SELECT * FROM person" | 用标准 SQL 查询表数据 |
spacetime call say_hello | 调用无参 reducer,触发遍历与日志输出 |
spacetime logs | 实时查看模块的console.info日志输出 |
这一轮操作完整验证了「写入 → 查询 → 计算 → 观测」的全链路:数据通过add写入,通过 SQL 读到,通过sayHello遍历并打日志,最终在logs中确认结果。
Angular 前端如何与数据库联动
模板的核心价值在于:Angular 前端与 SpacetimeDB 的连接层已经帮你写好了。打开 templates/angular-ts/src/app/app.config.ts,可以看到应用通过 Angular 依赖注入配置数据库连接:
import { ApplicationConfig, provideBrowserGlobalErrorListeners, } from '@angular/core'; import { provideSpacetimeDB } from 'spacetimedb/angular'; import { DbConnection, ErrorContext } from '../module_bindings'; import { Identity } from 'spacetimedb'; import { environment } from '../environments/environment'; const HOST = environment.SPACETIMEDB_HOST; const DB_NAME = environment.SPACETIMEDB_DB_NAME; const onConnect = (_conn: DbConnection, identity: Identity, token: string) => { localStorage.setItem('auth_token', 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); }; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideSpacetimeDB( DbConnection.builder() .withUri(HOST) .withDatabaseName(DB_NAME) .withToken(localStorage.getItem('auth_token') || undefined) .onConnect(onConnect) .onDisconnect(onDisconnect) .onConnectError(onConnectError) ), ], };关键点解读:
provideSpacetimeDB(...):来自spacetimedb/angular包,把连接注册为全局 provider;DbConnection.builder():来自自动生成的绑定(templates/angular-ts/src/module_bindings/index.ts),是带模块类型信息的连接构建器;.withUri(HOST).withDatabaseName(DB_NAME):指定服务器地址与数据库名;.withToken(...):从localStorage读取auth_token实现会话恢复——首次连接成功后 token 会被存到localStorage(见onConnect),刷新页面后无需重新认证;onConnect/onDisconnect/onConnectError:连接生命周期回调,分别处理连接成功、断开与失败。
根组件:响应式订阅与 reducer 调用
再看 templates/angular-ts/src/app/app.component.ts,UI 逻辑非常简洁,完全建立在 Angular 的响应式注入之上:
import { Component } from '@angular/core'; import { injectSpacetimeDB, injectTable, injectReducer, } from 'spacetimedb/angular'; import { tables, reducers } from '../module_bindings'; @Component({ selector: 'app-root', template: `...`, }) export class App { protected conn = injectSpacetimeDB(); protected people = injectTable(tables.person); private addReducer = injectReducer(reducers.add); protected name = ''; addPerson(event: Event) { event.preventDefault(); if (!this.name.trim() || !this.conn().isActive) return; // Call the add reducer this.addReducer({ name: this.name }); this.name = ''; } }三个注入函数各司其职:
injectSpacetimeDB():返回连接状态信号(signal),conn().isActive表示当前是否已连接,UI 据此显示绿/红状态并禁用输入框与按钮;injectTable(tables.person):订阅person表的变更,返回响应式查询结果,people().rows.length和people().rows在模板中直接渲染;当表数据变化时,页面会自动更新——这正是 SpacetimeDB 订阅机制带来的实时能力;injectReducer(reducers.add):返回一个可调用的 reducer 函数,this.addReducer({ name: this.name })即向服务端发起一次 reducer 调用。
自动生成的绑定层做了什么
在 templates/angular-ts/src/module_bindings/index.ts 中可以看到绑定的完整面貌:它从spacetimedb包导入底层运行时,再基于模块 schema 生成tablesSchema、reducersSchema、proceduresSchema,最终导出:
tables:表引用集合,兼作查询构建器(query builder);reducers:reducer 访问器映射;DbConnection/DbConnectionBuilder:带模块类型信息的连接 API;SubscriptionBuilder:订阅构建器;- 一系列类型定义(
EventContext、ReducerEventContext、ErrorContext等)。
比如person表的行类型由 templates/angular-ts/src/module_bindings/person_table.ts 生成:
export default __t.row({ name: __t.string(), });而addreducer 的参数类型由 templates/angular-ts/src/module_bindings/add_reducer.ts 生成:
export default { name: __t.string(), };这些文件是类型安全的关键:前端调用 reducer、读取表数据时都有完整的 TypeScript 类型推导,编译期即可捕获字段拼写错误与类型不匹配。
本地环境配置与scripts/dev.mjs的运行机制
Angular 的 esbuild 构建器不会像 Vite 那样自动读取.env文件,因此模板用 templates/angular-ts/scripts/dev.mjs 做了一个巧妙的桥接:
- 解析
.env.local(如果存在)与process.env; - 按优先级解析两个关键变量:
SPACETIMEDB_DB_NAME(数据库名)和SPACETIMEDB_HOST(服务器地址)——优先取spacetime dev注入的process.env,其次取.env.local中的VITE_SPACETIMEDB_DB_NAME/VITE_SPACETIMEDB_HOST,最后回退到 templates/angular-ts/src/environments/environment.ts 中的默认值(https://maincloud.spacetimedb.com与angular-ts); - 生成
src/environments/environment.local.ts; - 通过 angular.json 中的
fileReplacements配置,在 development 构建时用environment.local.ts替换默认的environment.ts; - 最后以
npx ng serve启动 Angular 开发服务器。
默认配置中SPACETIMEDB_HOST指向https://maincloud.spacetimedb.com(主云服务器);而当你使用spacetime dev时,CLI 会注入本地服务器地址,连接目标自动切换为本地。手动执行npm run dev而不设置环境变量时,应用则会尝试连接主云服务器。
下一步进阶
- 阅读 TypeScript SDK 参考文档,掌握
spacetimedb包的完整 API(查询构建器、订阅、事务与错误处理等); - 尝试在
spacetimedb/src/index.ts中添加新表(如message)、新 reducer(如sendMessage),然后重新运行spacetime generate或spacetime dev自动刷新绑定; - 将
person表改为非公开(public: false),观察前端订阅行为的变化,理解表可见性与权限控制; - 参考仓库中的其他 Angular 系模板(如 templates/react-ts、templates/nextjs-ts),对比不同框架接入 SpacetimeDB 的异同。
【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考