news 2026/10/7 16:23:06

Lovefield + TypeScript 快速上手:从 schema 构建到增查全流程的浏览器端关系型数据库 Todo 示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Lovefield + TypeScript 快速上手:从 schema 构建到增查全流程的浏览器端关系型数据库 Todo 示例
  • 关系型数据库
  • 数据库
  • 前端

【免费下载链接】lovefield

Lovefield is a relational database for web apps. Written in JavaScript, works cross-browser. Provides SQL-like APIs that are fast, safe, and easy to use.

项目地址:https://gitcode.com/gh_mirrors/lov/lovefield
点击查看免费下载

Lovefield 是一个用 JavaScript 编写、跨浏览器运行的 Web 端关系型数据库,提供 SQL 风格的链式 API。本篇技术指南以仓库中的demos/todo_typescript示例为主线,完整讲解如何从环境准备、依赖安装、TypeScript 类型声明,到 schema 定义、数据库连接、插入与查询,亲手跑通一个"Lovefield + TypeScript"的最小 Todo 应用;读完你将掌握 Lovefield 在 TypeScript 项目中的标准接入姿势,并理解其底层 Builder、TableBuilder 与查询构建器的真实实现机制。

示例概览:一个最小的 Lovefield + TypeScript 应用

demos/todo_typescript是 Lovefield 官方仓库提供的 TypeScript 快速入门示例,其核心目标只有一个:用最少的代码演示在 TypeScript 中完成 Lovefield 的"建表—连接—插入—查询"完整闭环。整个示例由 6 个文件构成,职责清晰:

文件作用
todo.tsTypeScript 源码,定义 schema、连接数据库、执行插入与查询
todo.html页面入口,加载 Lovefield 运行时与编译产物 todo.js
package.jsonnpm 依赖清单(gulp 构建链、typings、lovefield 等)
typings.jsonTypeScript 全局类型声明依赖(es6-promise、lovefield)
gulpfile.js定义typings、build、debug三个构建/调试任务
README.md官方给出的 5 步运行指引

页面端 todo.html 的加载方式值得注意:它直接通过<script src="node_modules/lovefield/dist/lovefield.min.js">引入 npm 安装的 Lovefield 压缩运行时,随后加载 TypeScript 编译产物todo.js,页面标题也点明了用途——"Minimal example of using Lovefield"。这意味着 TypeScript 源码只负责类型安全与编译期检查,真正的数据库引擎仍由 Lovefield 运行时提供。

环境准备:官方 5 步运行流程

官方 README(demos/todo_typescript/README.md)给出了 5 个步骤,逐一拆解如下:

# 1. 安装 gulp(若尚未安装),用于执行构建与本地调试任务 npm install -g gulp # 2. 安装 typings 与 tsc(若尚未安装) # tsc:TypeScript 编译器,负责把 todo.ts 编译成 todo.js # typings:旧版 TypeScript 类型声明管理器,用于拉取全局 .d.ts 声明 npm install -g tsc typings # 3. 拉取 package.json 中声明的依赖 npm install # 4. 启动本地 Web 服务器(内部先执行 typings + build,再起 webserver) gulp debug # 5. 浏览器访问 # http://localhost:8000/todo.html

从步骤可以看出,该示例采用的是全局命令行工具 + 本地依赖的组合模式:gulp、tsc、typings属于全局工具,而gulp-typescript、gulp-typings、gulp-webserver、lovefield等属于devDependencies本地依赖。默认端口为 8000,gulp debug任务内部支持通过--port参数覆盖(详见下文 gulpfile 解析)。

依赖与类型声明:package.json 与 typings.json

示例的 package.json 将lovefield本身声明为devDependencies(版本^2.1.8),并配套了完整的 gulp 工具链:

{ "name": "todo", "version": "0.0.1", "description": "Quick-start todo example in TypeScript.", "main": "todo.html", "license": "http://www.apache.org/licenses/LICENSE-2.0", "devDependencies": { "gulp": "^3.9.0", "gulp-typescript": "^2.13.0", "gulp-typings": "^2.0.0", "gulp-webserver": "^0.9.1", "lovefield": "^2.1.8", "nopt": "^3.0.6", "typings": "^1.0.4" } }

各依赖分工如下:

  • gulp:任务流编排,串联类型声明拉取、TS 编译与静态服务器;
  • gulp-typescript:在 gulp 流中调用 tsc 编译todo.ts;
  • gulp-typings / typings:按typings.json拉取全局类型声明;
  • gulp-webserver:启动带目录列表的本地静态服务器;
  • nopt:解析gulp debug的命令行参数(如--port);
  • lovefield:浏览器端数据库运行时本体。

而 typings.json 声明了 2 个全局类型包:

{ "globalDependencies": { "es6-promise": "registry:dt/es6-promise#0.0.0+20160423074304", "lovefield": "registry:dt/lovefield#2.0.62+20160317120654" } }

其中es6-promise提供 Promise 的类型定义(Lovefield 的connect()、exec()均返回 thenable),lovefield则提供lf.*命名空间下全部 API 的类型声明。二者都会落盘到 todo.ts 顶部/// <reference path>指令所指向的typings/目录中,从而让 tsc 能对lf.schema.create、lf.Type.INTEGER等符号做静态类型检查。

gulpfile.js 解析:typings、build、debug 三个任务的协作

gulpfile.js 定义了三个 gulp 任务,构成完整的"拉声明 → 编译 → 起服务"流水线:

任务 1:typings——按typings.json安装全局类型声明:

gulp.task('typings', function() { return gulp.src('typings.json').pipe(typings()); });

任务 2:build——以typings为前置依赖,将todo.ts编译为todo.js:

gulp.task('build', ['typings'], function() { var tsResult = gulp.src('todo.ts') .pipe(ts({ noEmitOnError: true, // 编译报错时不产出 JS,避免运行期出错 noImplicitAny: true, // 开启隐式 any 检查,强制显式标注类型 out: 'todo.js' // 合并输出为单文件 todo.js })); return tsResult.js.pipe(gulp.dest('.')); });

注意这里开启了noEmitOnError: true与noImplicitAny: true:前者保证类型错误时不会生成损坏的todo.js,后者要求所有变量/参数显式声明类型。这也是示例源码中频繁出现lf.schema.Builder、lf.Database、lf.schema.Table、lf.schema.Column等显式类型标注的原因。

任务 3:debug——以build为前置依赖,启动静态服务器:

gulp.task('debug', ['build'], function() { var knownOps = { 'port': [Number, null] }; var portNumber = nopt(knownOps).port || 8000; // 默认端口 8000 gulp.src('.').pipe(webserver({ directoryListing: true, // 开启目录列表,便于直接浏览 todo.html open: false, port: portNumber })); });

debug默认监听 8000 端口;如需换端口,可执行gulp debug --port 9000(由 nopt 解析)。directoryListing: true意味着访问根路径时可看到目录下的所有文件,方便直接点击进入todo.html。

todo.ts 源码逐段精读:完整的数据流

todo.ts 是整个示例的灵魂,共分四个阶段:定义 schema → 连接数据库 → 插入记录 → 条件查询。下面分段讲解。

阶段一:定义 schema——建库、建表、加索引

function main(): void { var schemaBuilder: lf.schema.Builder = lf.schema.create('todo', 1); schemaBuilder.createTable('Item'). addColumn('id', lf.Type.INTEGER). addColumn('description', lf.Type.STRING). addColumn('deadline', lf.Type.DATE_TIME). addColumn('done', lf.Type.BOOLEAN). addPrimaryKey(['id'], false). addIndex('idxDeadline', ['deadline'], false, lf.Order.DESC);
  • lf.schema.create('todo', 1)创建名为todo、版本号为1的 schema 构建器。从源码 lib/schema/builder.js 可见,lf.schema.create只是new lf.schema.Builder(dbName, dbVersion)的全局便捷入口,Builder 内部会构造一个lf.schema.DatabaseSchema并维护一张tableBuilders_映射表。
  • createTable('Item')注册名为Item的表构建器;从 lib/schema/builder.js 的实现看,重复建同名表会抛出异常码 503("Name {0} is already defined"),且 schema 一旦finalized(即已调用过getSchema()/connect())就不允许再建表(异常码 535)。
  • addColumn依次声明 4 列,类型分别为lf.Type.INTEGER、lf.Type.STRING、lf.Type.DATE_TIME、lf.Type.BOOLEAN。查看 lib/schema/table_builder.js 的实现会发现:addColumn会先做命名规则与重名检查,并且对默认允许为 NULL 的类型(如字符串、日期时间)自动调用addNullable——这解释了为什么示例中只有主键id被隐式视为不可空。
  • addPrimaryKey(['id'], false)将id设为主键,第二个参数false表示不使用自增(auto increment)。从 lib/schema/table_builder.js 看,主键本质上是自动创建了一个名为pkItem(pk+ 表名帕斯卡化)的唯一索引。
  • addIndex('idxDeadline', ['deadline'], false, lf.Order.DESC)为deadline列创建降序索引。其签名对应 lib/schema/table_builder.js 的addIndex(name, columns, opt_unique, opt_order):opt_unique默认为false,opt_order默认为lf.Order.ASC,本例显式传入了lf.Order.DESC。

阶段二:连接数据库——选择内存存储引擎

var todoDb: lf.Database = null; var dummyItem: lf.schema.Table = null; var connectOptions: lf.schema.ConnectOptions = { storeType: lf.schema.DataStoreType.MEMORY }; schemaBuilder.connect(connectOptions).then( function(db) { todoDb = db; dummyItem = db.getSchema().table('Item'); ...

ConnectOptions中的storeType: lf.schema.DataStoreType.MEMORY明确指定内存型后端(数据随页面关闭而消失),这是演示场景最合适的选择。Lovefield 的存储层在 lib/backstore 目录下还提供了IndexedDB、WebSQL、LocalStorage等后端实现,如需持久化可改为lf.schema.DataStoreType.INDEXED_DB。

底层机制上,connect()由 lib/schema/builder.js 实现:它会先做连接状态检查(已连接/连接中时抛出异常码 113),随后调用lf.proc.Database的init(opt_options)返回 Promise。connect()成功后,通过db.getSchema().table('Item')获取Item表的运行时对象(即dummyItem),供后续createRow、insertOrReplace、select使用。

阶段三:插入记录——createRow 与 insertOrReplace

var row = dummyItem.createRow({ 'id': 1, 'description': 'Get a cup of coffee', 'deadline': new Date(), 'done': false }); return db.insertOrReplace().into(dummyItem).values([row]).exec();

createRow将普通 JS 对象包装成 Lovefield 行对象,字段名与 schema 列一一对应;其中deadline传入的是new Date(),对应lf.Type.DATE_TIME列。

db.insertOrReplace()生成一个InsertBuilder,into(dummyItem)指定目标表,values([row])传入行数组,exec()执行写入。注意示例选用的是insertOrReplace而非insert——两者的差异在 lib/query/insert_builder.js 中有明确实现:InsertBuilder构造时以opt_allowReplace区分,insertOrReplace()即allowReplace = true的版本;执行前会断言into与values均已设置(异常码 518),并且当allowReplace为 true 时目标表必须存在主键,否则抛出异常码 519——示例中Item表恰好声明了id主键,因此合法。

阶段四:条件查询——select 与 where 的链式调用

}).then( function() { var column: lf.schema.Column = (<any>dummyItem).done; return todoDb.select().from(dummyItem).where(column.eq(false)).exec(); }).then( function(results) { results.forEach(function(row) { console.log((<any>row).description, 'before', (<any>row).deadline); document.body.textContent = (<any>row).description + ' before ' + (<any>row).deadline; }); });
  • todoDb.select()创建 SelectBuilder,from(dummyItem)指定查询表,where(column.eq(false))过滤done == false的记录,exec()返回 Promise,resolve 出结果数组。
  • 由于dummyItem是lf.schema.Table类型而非强类型化的列对象,源码用(<any>dummyItem).done绕过类型限制取到done列,再用lf.schema.Column.eq(false)构造等值谓词。
  • 查询结果遍历时同样以(<any>row).description的方式读取行字段,并把结果写入document.body.textContent,最终页面会显示 "Get a cup of coffee before <时间>" 字样,验证整条数据链路。

从构建器实现看,lib/query/select_builder.js 的assertExecPreconditions会在执行前校验from已设置(异常码 522)以及 limit/skip 绑定参数是否合法(异常码 523);where谓词与查询计划则交由 lib/proc 目录下的逻辑/物理计划生成器与PushDownSelectionsPass等重写器处理。整个 API 是异步 Promise 链,示例中connect → insertOrReplace → select三个阶段依次串联在.then()回调里,保证前序写操作完成后再发起查询。

运行效果与验证方式

按前文 5 步操作后,浏览器打开http://localhost:8000/todo.html即可看到查询结果文本(默认只有一条done=false的待办项被选中输出)。可以用以下几种方式验证示例确实按预期工作:

  1. 观察控制台:todo.ts中console.log((<any>row).description, 'before', (<any>row).deadline)会在 DevTools Console 打印记录内容;
  2. 修改存储后端:把connectOptions.storeType改为lf.schema.DataStoreType.INDEXED_DB,刷新页面后数据持久存在,可验证 Lovefield 多后端切换能力(对应 lib/backstore 的 IndexedDB 实现);
  3. 扩展数据:在values([row])中追加更多createRow产物,或把where条件改为eq(true),观察筛选结果变化,体会lf.Order.DESC索引对deadline查询排序的影响。

从示例到源码:Lovefield 类型系统与构建器的关键事实

为便于读者继续深入,这里把示例背后可验证的源码事实集中列出:

  • lf.schema.create是 Builder 的全局入口:lib/schema/builder.js 中lf.schema.create = function(dbName, dbVersion) { return new lf.schema.Builder(dbName, dbVersion); },Builder 内部持有DatabaseSchema与各表的TableBuilder集合。
  • schema 有"已定稿"状态:Builder 的getSchema()会触发一次finalize_(),此后createTable将抛出异常码 535;connect()期间重复连接会抛异常码 113(见 lib/schema/builder.js)。
  • 主键/唯一约束在底层都表现为索引:addPrimaryKey自动生成pk + 表名的唯一索引并登记进uniqueIndices_(lib/schema/table_builder.js),addIndex的opt_unique参数决定该索引是否唯一(lib/schema/table_builder.js)。
  • 插入/替换语义由 InsertBuilder 的allowReplace标志控制,且替换操作强制要求目标表存在主键,否则抛异常码 519(lib/query/insert_builder.js)。
  • 查询 API 是 Promise 驱动:lf.Database接口(lib/database.js)统一声明了select/insert/insertOrReplace/update/delete/observe/unobserve/createTransaction/close/export/import等操作,示例所用的getSchema、select、insertOrReplace均在此接口之上。

小结

demos/todo_typescript以最小成本展示了 Lovefield 在 TypeScript 生态中的完整接入路径:全局工具(gulp、tsc、typings)负责构建链,typings.json补齐lf.*的类型声明,todo.ts用链式 API 走通"建 schema → 连接内存库 → 插入 → 条件查询"四个环节,底层则有 Builder/TableBuilder/InsertBuilder/SelectBuilder 等模块提供严格的执行前置校验。理解了这条主线,后续无论是接入 IndexedDB 持久化、构造多表 join 查询,还是使用observe实现响应式 UI,都可以在此基础上平滑展开。

  • 关系型数据库
  • 数据库
  • 前端

【免费下载链接】lovefield

Lovefield is a relational database for web apps. Written in JavaScript, works cross-browser. Provides SQL-like APIs that are fast, safe, and easy to use.

项目地址:https://gitcode.com/gh_mirrors/lov/lovefield
点击查看免费下载

相关推荐

上一篇:Apache SeaTunnel Shade 机制指南:包重定位 JAR 的版本约定、依赖消费与发布流程
下一篇:Semi Design List 列表组件实战指南:从基础用法到虚拟滚动、拖拽排序与组合增强

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 16:22:33

体育研究生论文写到emo,这个AI让我提前两周交稿✅

体育研究生写论文有多emo&#xff1f;白天带训练队、上训练课、做体质测试&#xff0c;晚上拖着酸痛的身体坐到电脑前&#xff0c;面对一个字没动的论文&#xff0c;真的会想转行。 体育学研究生的论文是"文理双修"——既要做实验&#xff08;训练干预、体质测试、生…

作者头像 李华
网站建设 2026/10/7 16:21:53

eFuse与MCU协同实现工业电源路径保护:从原理到实战

做嵌入式项目&#xff0c;尤其是工业控制这一类&#xff0c;电源永远是最容易出幺蛾子的环节。前阵子帮客户调一套24V供电的控制器&#xff0c;现场反馈了一个很刁钻的问题&#xff1a;某一路负载在热插拔的瞬间&#xff0c;整个控制器会出现偶发性重启&#xff0c;十次里能撞上…

作者头像 李华
网站建设 2026/10/7 16:20:01

六行业实战拆解:如何把WorkBuddy从玩具变成生产力工具

提到 WorkBuddy&#xff0c;很多人第一反应是"又一个 AI 助手"&#xff0c;然后打开界面问几个问题、让它写几段内容&#xff0c;就放回角落吃灰了。但把最近的搜索热词翻一遍&#xff0c;你会发现情况远不止这么简单&#xff1a;有人在到处找"从入门到精通&quo…

作者头像 李华
网站建设 2026/10/7 16:18:54

The Prompt Makes the Person(a): A Systematic Evaluation of Sociodemographic Persona Prompting for...

文章主要内容总结 本文系统评估了大型语言模型(LLMs)中社会人口统计学角色提示(sociodemographic persona prompting)的效果,重点探究不同提示策略对模型模拟15个交叉人口群体(如种族、性别交叉)的影响。研究使用5个开源LLM,分析了两种核心提示维度: 角色采用格式:直…

作者头像 李华