- 数据库
- 后端
- 前端
【免费下载链接】tinybase
A reactive data store & sync engine.
本篇指南基于 tinybase 官方 Guides 中的 "Schemas" 系列文档(入口位于 site/guides/05_schemas/index.md),系统讲解如何在 Store 上设置ValuesSchema与TablesSchema来声明性地约束数据结构:从 schema 字段(type、enum、default、allowNull、required)的完整用法,到基于 schema 的 TypeScript 类型推导(with-schemas入口)、基于 mutator listener 的程序化数据校验,再到用 Schematizers 把 Zod、TypeBox、Valibot 等主流校验库的 schema 自动转换为 tinybase schema。读完后你将掌握 tinybase 中"声明结构 → 运行时保障 → 编译期类型"的完整数据治理方案。
一、Schemas 是什么:声明你想存储的数据
tinybase 中的 Schemas 是一种简单的声明式方式,用来描述你希望 Store 中保存的数据形态:
- ValuesSchema:描述 Store 中顶层 Values(键值对)里每个 Value 的类型与默认值;
- TablesSchema:描述每个 Table 中每个 Row 里各 Cell 的类型与默认值。
两者都是普通 JavaScript 对象,分别通过setValuesSchema方法与setTablesSchema方法应用到 Store 上。官方文档同时建议结合 Countries demo、Todo App demos 和 Drawing demo 来观察 schema 在真实应用中的效果(对应 site/demos 下的示例项目)。
从源码结构看,schema 的应用是一个"先校验、后重放"的过程。在 src/store/index.ts 中,setTablesSchema与setValuesSchema都会:
- 先
cloneSchema深拷贝传入的 schema,再通过validateTablesSchema/validateValuesSchema(见 src/store/index.ts)逐个字段校验; - 校验通过后,把 Store 当前全部数据取出、删除、再按新 schema 重新写入——这正是"数据会被纠正"这一行为的实现来源;
- 整个过程包在
fluentTransaction中,属于事务操作。
validateCellOrValueSchema还给出了一个重要的事实依据:每个 CellSchema / ValueSchema 只允许出现type、enum、default、allowNull、required这五个键;default为null时必须搭配allowNull,default的类型必须与type(或必须在enum成员中)匹配,否则该default会被静默剔除。
二、添加 ValuesSchema
通常你希望在向 Store 加载和设置数据之前就设置好 ValuesSchema:
import {createStore} from 'tinybase'; const store = createStore().setValuesSchema({ employees: {type: 'number'}, open: {type: 'boolean', default: false}, }); store.setValues({employees: 3, website: 'pets.com'}); console.log(store.getValues()); // -> {employees: 3, open: false}在这个例子中,我们声明了 Store 包含一个employeesValue(必须是 number)和一个openValue(必须是 boolean)。可以看到,当写入的 Values 对象不完全符合约束时,数据会被"纠正":website这个未声明的 Value 被忽略,缺失的open则被填充为默认值false。
tinybase 在 schema 中支持string、number、boolean三种基本类型,同时支持object与array类型以承载更丰富的结构化数据——它们内部以 JSON 编码字符串存储(对应源码中的encodeIfJson/decodeIfJson,见 src/store/index.ts 中对编码 JSON 单元格的校验分支)。任意 Cell 或 Value 都可以通过allowNull属性允许null:
const store2 = createStore().setValuesSchema({ nickname: {type: 'string', allowNull: true, default: null}, verified: {type: 'boolean'}, }); store2.setValues({nickname: null, verified: true}); console.log(store2.getValues()); // -> {nickname: null, verified: true} store2.setValue('nickname', 'Buddy'); console.log(store2.getValue('nickname')); // -> 'Buddy'当allowNull为true时,Cell 或 Value 可以取定义的类型值,也可以取null;没有allowNull时,试图写入null会被 schema 拒绝。
允许类型联合(Type Union)
用非空类型名数组列出 Cell 或 Value 可以拥有的"宽类型":
const unionStore = createStore().setValuesSchema({ reference: {type: ['string', 'number'], default: 'unknown'}, response: {type: ['boolean', 'object'], allowNull: true}, }); unionStore.setValues({reference: 42, response: {accepted: true}}); console.log(unionStore.getValues()); // -> {reference: 42, response: {accepted: true}}类型数组中可以包含string、number、boolean、object、array,多个类型名构成联合,重复的类型名没有额外效果。不要把null写进数组——需要用allowNull: true表达。default只有在匹配数组中某个类型(或在允许 null 时为null)时才会生效。
用 Enum 限制取值
当只有特定的基本值才是合法值时,用enum代替type。enum必须非空,成员可以是字符串、有限数字、布尔值的任意组合:
const store3 = createStore().setValuesSchema({ status: {enum: ['available', 'adopted'], default: 'available'}, rating: {enum: ['good', 5, true], allowNull: true}, }); store3.setValues({status: 'adopted', rating: true}); store3.setValue('status', 'missing'); console.log(store3.getValues()); // -> {status: 'available', rating: true}规则要点(与 src/store/index.ts 的校验逻辑一一对应):
- 每个 CellSchema 或 ValueSchema 必须有且仅有
type或enum之一; type联合允许所列类型的所有值,而enum只允许列出的具体值;allowNull、default、required两种模式下都可用;null既不能放进type数组,也不能放进enum,统一用allowNull: true;enum的default只有是其成员之一(或允许 null 时为null)才会被采用——例子中setValue('status', 'missing')写入非法值后,Store 将其纠正回了默认值'available'。
三、添加 TablesSchema
表格型 schema 的用法与 ValuesSchema 类似,同样建议在向 Table 加载数据之前设置:
store.setTablesSchema({ pets: { species: {type: 'string'}, sold: {type: 'boolean', default: false}, }, }); store.setRow('pets', 'fido', {species: 'dog', color: 'brown', sold: 'maybe'}); console.log(store.getTables()); // -> {pets: {fido: {species: 'dog', sold: false}}}我们声明了 Store 中有一个petsTable,每行包含speciesCell(必须是 string)和soldCell(必须是 boolean)。同样地,当写入的 Row 不完全符合约束时数据会被纠正:colorCell 被忽略,sold的字符串值'maybe'被纠正为默认值false。
关于 Cell 是否存在,有一个重要的一般性结论:
- 如果提供了
default(且类型正确),可以确信该 Cell 一定会出现在 Row 中; - 如果没有提供
default(或default类型不正确),该 Cell 可能从 Row 中缺失,但只要它存在,就能保证类型正确。
此外,还可以在 Cell 或 Value schema 上设置required: true,表示即使不提供默认值,该字段也应该存在。这对"基于 schema 的类型推导"尤其有用:tinybase 会把该字段视为"已定义",而不会把undefined加入允许的数据类型。
四、修改 Schema 与潜在的数据丢失
你可以在数据写入之后设置或变更 ValuesSchema / TablesSchema。但要注意:这可能引发 Store 内数据变化——默认值会被补上,不合法的 Value、Table、Row 或 Cell 对象会被移除,并且这些变更会像普通写入一样触发相应的 listener。
例如,TablesSchema 新增了一个带默认值的字段,现有 Row 会被补上它以符合新 schema:
store.setTablesSchema({ pets: { species: {type: 'string'}, legs: {type: 'number', default: 4}, sold: {type: 'boolean', default: false}, }, }); console.log(store.getTables()); // -> {pets: {fido: {species: 'dog', sold: false, legs: 4}}}TablesSchema 不做类型转换(cast):字段声明为某种类型,数据就必须是那种类型:
store.setCell('pets', 'fido', 'legs', '3'); console.log(store.getTables()); // -> {pets: {fido: {species: 'dog', sold: false, legs: 4}}} // '3' 非法,回落到 default 4 store.setCell('pets', 'fido', 'legs', 3); console.log(store.getTables()); // -> {pets: {fido: {species: 'dog', sold: false, legs: 3}}}为了保证schema 被满足,Value 或 Cell 数据可能被删除;Cell 被删光时,整个 Row 都可能被删除:
store.setTablesSchema({ pets: { color: {type: 'string'}, weight: {type: 'number'}, }, }); console.log(store.getTables()); // -> {} // 现有数据没有任何 Cell 匹配新 schema,Row 被删除当不再需要 schema 时,可以用delValuesSchema方法或delTablesSchema方法彻底移除。源码层面,这两个方法(src/store/index.ts)会保存当前持久化状态并清空内部 schema 映射;setSchema则是在一个事务内先setTablesSchema再setValuesSchema(src/store/index.ts)。
这套"写数据即校验、schema 变更即重放"的行为,在单元测试 test/unit/core/store/store-schema.test.ts 中有系统性的覆盖。
五、Schema 驱动的类型推导(with-schemas)
当你知道数据形状时,可以利用"从已应用 schema 推断 API 类型"的类型定义,大幅提升开发体验。方法是在导入路径后加with-schemas后缀:
import {createStore} from 'tinybase/with-schemas'; // 注意 'with-schemas' 后缀 const store = createStore().setValuesSchema({ employees: {type: 'number'}, open: {type: 'boolean', default: false}, }); store.setValues({employees: 3}); // OK store.setValues({employees: true}); // TypeScript error store.setValues({employees: 3, website: 'pets.com'}); // TypeScript error此时 Store 被"知道"具有该 ValuesSchema,所有相关方法都带上相应类型约束,连 listener 也一样:
store.addValueListener(null, (store, valueId, newValue, oldValue) => { valueId == 'employees'; // OK valueId == 'open'; // OK valueId == 'website'; // TypeScript error if (valueId == 'employees') { newValue as number; // OK oldValue as number; // OK newValue as boolean; // TypeScript error oldValue as boolean; // TypeScript error } if (valueId == 'open') { newValue as boolean; // OK oldValue as boolean; // OK } });几个类型规则:
- 带
default的字段被推断为"存在"(tinybase 会在缺失时补上);required: true的字段同样被推断为存在,并且在setRow、addRow、setValues等整段写入中是必填的,且不引入undefined类型; - 类型联合会推断为
type数组中所有宽类型的联合;schema 放在变量里时要加as const,让 TypeScript 把数组保留为元组; - enum schema会推断为精确的字面量联合,同样建议用
as const保留字面量成员。
const unionValuesSchema = { reference: {type: ['string', 'number']}, response: {type: ['boolean', 'object'], allowNull: true}, } as const; const unionStore = createStore().setValuesSchema(unionValuesSchema); unionStore.setValue('reference', 42); // OK unionStore.setValue('reference', 'pet-1'); // OK unionStore.setValue('reference', true); // TypeScript error unionStore.setValue('response', null); // OK const enumValuesSchema = { status: {enum: ['available', 'adopted'], default: 'available'}, rating: {enum: ['good', 5, true], allowNull: true}, } as const; const enumStore = createStore().setValuesSchema(enumValuesSchema); enumStore.setValue('status', 'adopted'); // OK enumStore.setValue('status', 'missing'); // TypeScript error enumStore.setValue('rating', null); // OK如何拿到带类型的 Store
只有setSchema、setTablesSchema、setValuesSchema三个方法返回带 schema 类型的 Store 对象。所以要受益必须把 Store 变量赋值为这些方法的返回值,而不是createStore()本身:
const store = createStore(); // 这不是 schema 类型化的 Store store.setValuesSchema({ employees: {type: 'number'}, open: {type: 'boolean', default: false}, }); // 你应该使用这个方法的返回类型还有一个细节:为了让类型推导生效,schema 要么直接内联传入,要么以as const常量变量的形式传入:
const valuesSchema = { employees: {type: 'number'}, open: {type: 'boolean', default: false}, } as const; // 注意 `as const` 修饰符 store.setValuesSchema(valuesSchema);类型会随 schema 的增删改自适应:
const tablesSchema = {pets: {species: {type: 'string'}}} as const; const valuesSchema = { employees: {type: 'number'}, open: {type: 'boolean', default: false}, } as const; const store = createStore(); const storeWithBothSchemas = store.setSchema(tablesSchema, valuesSchema); const storeWithJustValuesSchema = storeWithBothSchemas.delTablesSchema(); const storeWithValuesAndNewTablesSchema = storeWithBothSchemas.setTablesSchema({ pets: { species: {type: 'string'}, sold: {type: 'boolean', default: false}, }, });类型入口定义可参考 src/@types/with-schemas/index.d.ts,而 ui-react / ui-solid / ui-svelte 的类型化行为在 test/unit/core/types/ 下的类型测试文件中有验证。
ui-react 模块的类型化
由于 ui-react 的所有 hooks 与组件都是模块顶层函数,逐个应用 schema 会很繁琐。因此官方提供的模式是:用WithSchemas类型(接收 schema 的typeof)整体转换整个模块,再按需解构:
import React from 'react'; import * as UiReact from 'tinybase/ui-react/with-schemas'; import {createStore} from 'tinybase/with-schemas'; const tablesSchema = {pets: {species: {type: 'string'}}} as const; const valuesSchema = {employees: {type: 'number'}} as const; // 用 WithSchemas 把整个模块转换为 schema 类型化版本 const UiReactWithSchemas = UiReact as UiReact.WithSchemas< [typeof tablesSchema, typeof valuesSchema] >; // 解构出需要的 hooks 和组件 const {TableView, useTable, ValueView} = UiReactWithSchemas; const store = createStore().setSchema(tablesSchema, valuesSchema); const App = () => ( <div> <TableView store={store} tableId="species" /> {/* OK */} <TableView store={store} tableId="customers" /> {/* TypeScript error */} {/* ... */} </div> );在 React Native 中,模块与类型的解析尚不完全兼容 Node 与 TypeScript,可能需要显式地从不同入口加载代码与类型:
// code import React from 'react'; import * as UiReact from 'tinybase/ui-react'; // types import type {WithSchemas} from 'tinybase/ui-react/with-schemas'; import type { TablesSchema, ValuesSchema, createStore, } from 'tinybase/with-schemas'; const tablesSchema = {pets: {species: {type: 'string'}}} as const; const valuesSchema = {employees: {type: 'number'}} as const; const UiReactWithSchemas = UiReact as unknown as WithSchemas< [typeof tablesSchema, typeof valuesSchema] >;omni 模块
omni模块同时导出 ui-react 与 ui-react-dom,需要使用unknown as强转来区分两者:
import * as Omni from 'tinybase/omni'; import type { UiReactDomWithSchemas, UiReactWithSchemas, } from 'tinybase/omni/with-schemas'; const tablesSchema = {pets: {species: {type: 'string'}}} as const; const valuesSchema = {employees: {type: 'number'}} as const; type Schemas = [typeof tablesSchema, typeof valuesSchema]; const {useCell, useValue} = Omni as unknown as UiReactWithSchemas<Schemas>; const {EditableCellView} = Omni as unknown as UiReactDomWithSchemas<Schemas>;多个 Store 与在 React 组件中定义带 schema 的 Store
多个 Store 有不同 schema 时,对WithSchemas使用多次并各自解构、重命名:
const UiReactWithPetShopSchemas = UiReact as UiReact.WithSchemas< [typeof petShopTablesSchema, typeof petShopValuesSchema] >; const { TableView: PetShopTableView, useTable: usePetShopTable, ValueView: usePetShopValueView, } = UiReactWithPetShopSchemas; const UiReactWithSettingsSchemas = UiReact as UiReact.WithSchemas< [typeof settingsTablesSchema, typeof settingsValuesSchema] >; const { TableView: SettingsTableView, useTable: useSettingsTable, ValueView: useSettingsValueView, } = UiReactWithSettingsSchemas; const petShopStore = createStore().setSchema( petShopTablesSchema, petShopValuesSchema, ); const settingsStore = createStore().setSchema( settingsTablesSchema, settingsValuesSchema, ); const App = () => ( <div> <PetShopTableView store={petShopStore} tableId="species" /> <SettingsTableView store={settingsStore} tableId="viewSettings" /> </div> );官方文档还介绍了一个进阶模式:用WithSchemas类型在不渲染的 React 组件中创建带 schema 的 Store——在同一个自包含文件里完成"创建 Store、设置持久化、通过 Provider context 发布、暴露领域级 hooks"。应用顶层大致是:
export const App = () => { return ( <Provider> <MyStore /> {/* 这是 schema 化的 Store 组件 */} <ActualApp /> {/* 这是实际渲染的应用 */} </Provider> ); };MyStore组件渲染null(不可见),但负责创建 Store 并通过 Provider 向应用提供它。简化示例(本例只类型化了 Values,表格 Store 同理):
// 该 Store 的唯一 Id。 const STORE_ID = 'myStore'; // 该 Store 的 schema。 const VALUES_SCHEMA = { myStringValue: {type: 'string', default: 'foo'}, myNumericValue: {type: 'number', default: 42}, } as const; type Schemas = [NoTablesSchema, typeof VALUES_SCHEMA]; // 以 schema 应用方式解构 ui-react 模块。 const {useCreateStore, useProvideStore, useCreatePersister, useValue} = UiReact as UiReact.WithSchemas<Schemas>; export const MyStore = () => { // 创建 Store 并设置 schema const myStore = useCreateStore(() => createStore().setValuesSchema(VALUES_SCHEMA), ); // 创建本地存储 persister 并启动 useCreatePersister( settingsStore, (settingsStore) => createLocalPersister(settingsStore, STORE_ID), [], (persister) => persister.startAutoPersisting(), ); // 向应用其余部分提供该 Store。 useProvideStore(STORE_ID, settingsStore); // 不渲染任何内容。 return null; };同一文件里还可以定义暴露 schema 类型的领域级 hooks,例如:传'myStringValue'时该 hook 返回 string,传'myNumericValue'时返回 number:
type ValueIds = keyof typeof VALUES_SCHEMA; export const useSettingsValue = <ValueId extends ValueIds>(valueId: ValueId) => useValue<ValueId>(valueId, STORE_ID);这样,应用其余部分既能以正确类型使用 Values,Store 的实现细节又被封装在单个文件中,对复杂应用而言非常利于维护。
六、用 Mutator Listener 做程序化 Schema
声明式 schema 只能表达类型层面的约束;有时你需要在数据更新时进行程序化检查与修正。默认情况下 listener不能更新数据——下面的"把walnut改成brown"的代码会静默失败:
import {createStore} from 'tinybase'; const store = createStore(); store.setRow('pets', 'fido', {species: 'dog', color: 'black'}); const colorListenerId = store.addCellListener( 'pets', null, 'color', (store, tableId, rowId, cellId, newCell) => { if (newCell == 'walnut') { store.setCell(tableId, rowId, cellId, 'brown'); // 静默失败 } }, ); store.setCell('pets', 'fido', 'color', 'walnut'); console.log(store.getTables()); // -> {pets: {fido: {species: 'dog', color: 'walnut'}}} store.delListener(colorListenerId);要在添加 listener 的方法上把mutator参数设为true(第 5 个参数),表明这是一个"mutator"(愿意允许它修改数据)。例如要求species必须是已知物种之一,否则置为'unknown':
const SPECIES = ['unknown', 'dog', 'cat', 'worm']; store.addCellListener( 'pets', null, 'species', (store, tableId, rowId, cellId, newCell) => { if (!SPECIES.includes(newCell)) { store.setCell(tableId, rowId, cellId, SPECIES[0]); } }, true, // 允许该 listener 修改 Store。 ); store.setCell('pets', 'fido', 'species', 'worm'); console.log(store.getTables()); // -> {pets: {fido: {species: 'worm', color: 'walnut'}}} store.setCell('pets', 'fido', 'species', 'wolf'); console.log(store.getTables()); // -> {pets: {fido: {species: 'unknown', color: 'walnut'}}}一个关键执行顺序保证:所有 mutator listener 先于所有非 mutator listener 运行。这意味着你的只读 listener 触发时,Store 内数据已经按你的规则调整完毕。
这样就等于实现了一个"程序化 schema":能校验值是否合法,并把非法值落到某个默认值。同样的手法还可以把数值 Cell 限制在合法区间,甚至让某些 Cell 受其他 Cell 约束(但需谨慎,避免昂贵或无解的约束求解)。一个常见场景是:从外部数据源加载数据时,保证数据被塑形成你的应用要求的形状。
七、Schematizers:把主流校验库的 Schema 转成 tinybase Schema
Schematizers 提供了把流行校验库的 schema 转换成 tinybaseTablesSchema/ValuesSchema的便捷方式:不必手写 tinybase schema,可以直接复用 Zod、TypeBox、Valibot 等库中已有的 schema,在运行时用 schematizer 转换。支持的 schema 体系及创建函数如下:
| Library | 创建函数 |
|---|---|
| Zod | createZodSchematizer |
| TypeBox | createTypeBoxSchematizer |
| Valibot | createValibotSchematizer |
| ArkType | createArkTypeSchematizer |
| Yup | createYupSchematizer |
| Effect Schema | createEffectSchematizer |
此外,createCustomSchematizer函数允许你为 tinybase 未直接支持的任意 schema 体系构建自己的 schematizer。各 schematizer 的 API 文档位于 docs/api/schematizer-zod、docs/api/schematizer-typebox 等目录。
所有 schematizer 都由 src/schematizers/index.ts 中的createCustomSchematizer统一构建:你只需提供unwrapSchema(解包单个 schema,返回类型 / 默认值 / 可空性 / 必填性)、getProperties(提取对象字段)、可选的getPropertyRequired三个函数,它就组合出toTablesSchema与toValuesSchema两个转换方法。从源码可以看到转换语义:能识别为"一组有限枚举成员"的 schema 会被转成 tinybaseenum;必填且无默认值的字段会被加上required: true;无法转换的字段(toCellOrValueSchema返回undefined)会被直接排除。
使用 Zod Schematizer
用createZodSchematizer创建 schematizer,然后调用toTablesSchema/toValuesSchema完成转换:
import {createStore} from 'tinybase'; import {createZodSchematizer} from 'tinybase/schematizers/schematizer-zod'; import {z} from 'zod'; const schematizer = createZodSchematizer(); const store = createStore().setTablesSchema( schematizer.toTablesSchema({ pets: z.object({ petId: z.union([z.string(), z.number()]), species: z.enum(['dog', 'cat']), age: z.number(), sold: z.boolean().default(false), }), }), ); store.setRow('pets', 'fido', {petId: 1, species: 'dog', age: 3}); store.setCell('pets', 'fido', 'species', 'bird'); console.log(store.getRow('pets', 'fido')); // -> {petId: 1, species: 'dog', age: 3, sold: false}schematizer 做的是"尽力而为"的转换:从 Zod schema 中提取基本类型(string / number / boolean)、基本类型联合、字面量与原始枚举、默认值,以及 nullable / required 设置。例如petId被转成type: ['string', 'number'];z.enum(['dog', 'cat'])则成为 tinybase 原生 enum schema,因此写入'bird'这样的其他物种会被 Store 拒绝并回落到默认值。
从 Zod schematizer 的源码 src/schematizers/schematizer-zod/index.ts 可以印证这些行为:unwrapSchema递归处理optional/nullable/default/record/union/enum/literal各类 Zod def;unwrapUnion会把纯字面量/枚举成员合并成 tinybase enum,否则收集各分支的宽类型,并通过 src/schematizers/common.ts 的getTypeOrTypeUnion归一为string或string[](去重后单类型取标量、多类型取数组)。
类型联合支持差异
所有 schematizer 都能把一组字面量 / 枚举成员转成 tinybase enum;但"宽类型联合"只能从本身有 union 类型的 schema 体系转换:
| Library | Enums | 类型联合 |
|---|---|---|
| Zod | 支持 | 支持,来自z.union |
| TypeBox | 支持 | 支持,来自Type.Union |
| Valibot | 支持 | 支持,来自v.union |
| ArkType | 支持 | 支持,来自\|联合表达式 |
| Effect Schema | 支持 | 支持,来自Schema.Union |
| Yup | 支持 | 不支持:Yup 没有 union 类型 |
Yup 用oneOf表达枚举,Yup schematizer 会照常转成 tinybase enum;但因为 Yup 无法表达"值可以是若干宽类型之一",Yup 字段总是被转成单一type。如果需要在 Yup 之外同时获得类型联合,请在 tinybase schema 中直接声明该条目的type数组。
转换 Values Schema
也可以转换单个 Value 的 Zod schema:
const store2 = createStore().setValuesSchema( schematizer.toValuesSchema({ theme: z.string().default('light'), count: z.number(), isOpen: z.boolean().optional(), }), ); store2.setValues({count: 42}); console.log(store2.getValues()); // -> {theme: 'light', count: 42}这里只有theme有默认值;count与isOpen没有默认值,因此除非显式设置,它们不会出现在 Store 中。由于count是必填的,示例用setValues让 Store 收到一个完整的、合法的 Values 对象。
处理 nullable 与 optional 字段
Zod schematizer 理解 nullable 与 optional:
const store3 = createStore().setTablesSchema( schematizer.toTablesSchema({ users: z.object({ name: z.string(), nickname: z.string().nullable(), bio: z.string().optional(), }), }), ); store3.setRow('users', 'alice', {name: 'Alice', nickname: null}); console.log(store3.getRow('users', 'alice')); // -> {name: 'Alice', nickname: null}nickname被标记为 nullable,所以可以取null;bio是 optional,无需提供(Zod schema 中没有指定默认值,tinybase 也不会替它添加默认值)。
Object 与 Array Cell
Zod schematizer 还会把数组与对象(record)字段转成 tinybase 的array与objectCell 类型:
const store4 = createStore().setTablesSchema( schematizer.toTablesSchema({ pets: z.object({ species: z.string(), tags: z.array(z.string()), profile: z.record(z.string(), z.string()), }), }), ); store4.setRow('pets', 'fido', { species: 'dog', tags: ['friendly', 'playful'], profile: {color: 'brown'}, }); console.log(store4.getRow('pets', 'fido')); // -> {species: 'dog', tags: ['friendly', 'playful'], profile: {color: 'brown'}}object / array Cell 也可以指定默认值:
const store5 = createStore().setTablesSchema( schematizer.toTablesSchema({ pets: z.object({ species: z.string(), tags: z.array(z.string()).default(['unknown']), }), }), ); store5.setRow('pets', 'fido', {species: 'dog'}); console.log(store5.getRow('pets', 'fido')); // -> {species: 'dog', tags: ['unknown']}不支持的类型
Zod schematizer 只转换基本类型(string、number、boolean、array、object/record),并忽略 date 这类复杂类型——它们会直接从结果 tinybase schema 中被排除:
const store6 = createStore().setTablesSchema( schematizer.toTablesSchema({ pets: z.object({ species: z.string(), birthday: z.date(), // 被忽略 }), }), ); store6.setRow('pets', 'fido', {species: 'dog'}); console.log(store6.getRow('pets', 'fido')); // -> {species: 'dog'}复用 Schematizer
schematizer 可以创建一次、多次复用:
const schematizer2 = createZodSchematizer(); const tablesSchema = schematizer2.toTablesSchema({ pets: z.object({species: z.string(), age: z.number()}), stores: z.object({name: z.string(), city: z.string()}), }); const valuesSchema = schematizer2.toValuesSchema({ openStores: z.number().default(0), corporateName: z.string().default('Pet Store Inc'), }); const store7 = createStore().setSchema(tablesSchema, valuesSchema); store7.setRow('stores', 'downtown', {name: 'Downtown Pets', city: 'Boston'}); console.log(store7.getRow('stores', 'downtown')); // -> {name: 'Downtown Pets', city: 'Boston'} console.log(store7.getValues()); // -> {openStores: 0, corporateName: 'Pet Store Inc'}总结
- 声明式结构保障:
setValuesSchema/setTablesSchema让你用声明式对象描述数据形态;写入不符合约束的数据会被自动纠正——未知字段被忽略、非法值回落到default、缺失字段被补默认值。schema 变更会重放现有数据,可能造成 Cell / Row 甚至 Table 的删除,务必了解这一数据丢失风险;不再需要时用delValuesSchema/delTablesSchema移除。 - 编译期体验:通过
with-schemas导入后缀与WithSchemas类型(含 ui-react、omni 的整体类型化模式与多 Store 解构模式),把运行时 schema 变成 TypeScript 的精确类型约束;记住as const与"赋值方法返回值"两个要点。 - 程序化补充:声明式 schema 覆盖不到的约束(取值集合、数值区间、跨字段依赖),用
mutator: true的 listener 实现"程序化 schema",并依赖"mutator 先于只读 listener 执行"的顺序保证。 - 生态桥接:Schematizers(
createZodSchematizer等)把 Zod、TypeBox、Valibot、ArkType、Yup、Effect Schema 的现有 schema 尽力转换为 tinybase schema,支持类型联合、enum、nullable / optional 与 object / array Cell;无法转换的字段会被静默排除,需要时可直接在 tinybase schema 中补写。
这套"schema 声明 → 运行时校验 → 类型推导 → 生态转换"的完整链路,在单元测试 test/unit/core/store/store-schema.test.ts、类型测试 test/unit/core/types/store-schema-defaults.ts 以及 test/unit/schematizers/ 目录中都有对应验证,可作为行为基准进一步深入阅读。
- 数据库
- 后端
- 前端
【免费下载链接】tinybase
A reactive data store & sync engine.
相关推荐
tinybase 数据模式指南:用 ValuesSchema 与 TablesSchema 声明式约束、校验和自动纠正 Store 数据
tinybase 数据模式指南:用 ValuesSchema 与 TablesSchema 声明式约束、校验和自动纠正 Store 数据 Schema 是 ti
数据库后端前端Vibora Schemas 数据验证指南:异步、高性能的 Schema 声明与校验实战
Vibora Schemas 数据验证指南:异步、高性能的 Schema 声明与校验实战 导读 本文围绕 Vibora 内置的 schemas 模块展开,系统讲
后端TypeScript 接口(Interface)完全指南:声明合并、implements 与结构化类型实战
TypeScript 接口(Interface)完全指南:声明合并、implements 与结构化类型实战 接口( interface )是 TypeScrip
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考