CloudFlare 运营着一个庞大的全球网络,在全球拥有超过 330 个数据中心。这种广泛的覆盖范围使得其服务距离大多数互联网用户仅有毫秒之遥。除了其基础的内容分发网络(CDN)和 DDoS 保护、Web 应用程序防火墙(WAF)等安全服务外,CloudFlare 还显著扩展了其“开发者服务”。这些服务现在包括无服务器计算(Workers)、对象存储(R2)、键值存储(KV)和无服务器 SQL 数据库(D1),以及针对 AI(AI Gateway、Workers AI)、图像和实时应用程序的专业服务。选择 CloudFlare 作为一站式开发平台,其优势在于性能、安全性、可拓展性、成本效益和开发体验的全面提升。
所涉及技术栈
- Workers:构建无服务器应用程序并立即在全球范围内部署,以实现卓越的性能、可靠性和规模。
- Pages:创建可立即部署到 CloudFlare 全球网络的全栈应用程序。
- Wrangler:CloudFlare 开发平台的 CLI 工具。
- D1:CloudFlare 原生的无服务数据库(SQLite)。
- R2:CloudFlare 的对象存储。
- KV:创建一个全局的、低延迟的键值数据存储。
- Workers AI:在 CloudFlare 的全球网络上运行由无服务器 GPU 提供支持的机器学习模型。
- Bun:新的 JavaScript 运行时,内置原生捆绑器、转译器、任务运行器和 npm 客户端。
- Hono:构建于 Web 标准的快速、轻量的 Web 应用框架。支持任意 JavaScript 运行时。
- Drizzle ORM:注重开发体验的轻量、高性能 TypeScript ORM。
Bun
基础
- 安装 Bun:
curl -fsSL https://bun.sh/install | bash
。 - 更新 Bun:
bun upgrade
。 - 初始化空白项目:
bun init [-y]
。- 添加 TypeScript 类型支持:bun add -d @types/bun。
- 基于模板创建项目:
bun create <template> [<destination>]
。- 基于 create-template 格式的 npm 包、GitHub 仓库或者本地模版创建新项目;
- 也可以使用 bunx create-template 创建,bunx 等同于 npx,如:
1
2bun create remix
bunx create-remix - 执行文件/指令:
bun [--watch] run [--bun] <filename.[js|jst|ts|tsx] | script-defined-in-package-dot-json>
。- --watch:以观察模式运行文件或指令,必须 紧跟在 bun 后面;
- --bun:指定以 bun 而不是 node 作为可执行文件(`#!/usr/bin/env node`)的运行时环境;
- 每个在
package.json
文件中的scripts
部分定义的指令(如:"scripts":{"dev": "bun server.ts"}
),都会有两个对应的生命周期钩子(格式:pre<script>
、post<script>
):predev
和postdev
,如果predev
执行失败,Bun 不会继续执行dev
指令。
- 编译为可执行的单文件程序:
bun build ./cli.ts —compile —outfile mycli && ./mycli
。 - 自动安装:如果Bun 没在工作目录或者更高层级目录中找到 node_modules 目录,Bun 将使用其模块解决方案算法。
- Bun 的行为配置文件
bunfig.toml
,通常与项目的package.json
文件同目录或者应用于全局的配置文件($HOME/.bunfig.toml
)。bunfig.toml
示例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54# Reduce memory usage at the cost of performance
smol = true
# Set the log level. "debug" | "warn" | "error"
logLevel = "debug"
# permit to enable/disable the analytics records
telemetry = false
# Test runner
[test]
root = "./__tests__" # The root directory to run tests from. Default `.`
preload = ["./setup.ts"] # Same as the top-level `preload` field, but only applies to `bun test`
smol = true
coverage = false # Enables coverage reporting. Default `false`. Use `--coverage` to override.
coverageThreshold = 0.9 # to require 90% line-level and function-level coverage
# coverageThreshold = { line = 0.7, function = 0.8, statement = 0.9 } # Different thresholds can be specified for line-wise, function-wise, and statement-wise coverage.
coverageSkipTestFiles = false # Whether to skip test files when computing coverage statistics. Default `false`
# Package manager: config the `bun install` behavior
[install]
optional = true # Whether to install optional dependencies, default `true`
dev = true # Whether to install development dependencies. Default `true`
peer = true # Whether to install peer dependencies. Default `true`
production = false # Whether `bun install` will run in "production mode". Default `false`. In production mode, `"devDependencies"` are not installed. Override with `--production`
exact = false # Whether to set an exact version in package.json. Default `false`.
# The default registry is https://registry.npmjs.org/. This can be globally configured in bunfig.toml
registry = "https://registry.npmjs.org" # set default registry as a string
registry = { url = "https://registry.npmjs.org", token = "123456" } # set a token
registry = "https://username:password@registry.npmjs.org" # set a username/password
# To configure a registry for a particular scope (e.g. `@myorg/<package>`) use `install.scopes`. You can reference environment variables with `$variable` notation.
[install.scopes]
# registry as string
myorg = "https://username:password@registry.myorg.com/"
# registry with username/password
# you can reference environment variables
myorg = { username = "myusername", password = "$npm_password", url = "https://registry.myorg.com/" }
# registry with token
myorg = { token = "$npm_token", url = "https://registry.myorg.com/" }
[install.cache]
# the directory to use for the cache
dir = "~/.bun/install/cache"
# when true, don't load from the global cache.
# Bun may still write to node_modules/.cache
disable = false
# when true, always resolve the latest versions from the registry
disableManifest = false
包管理器
- 安装项目依赖:
bun i
- 安装全局依赖:
bun i -g <package>
- 添加项目依赖:
bun add -d|--dev|--optional|-E|--exact|-g|--global <package-name | git@github.com:moment/moment.git>
- -d | --dev:添加开发依赖
- --optional:添加可选依赖
- -E | --exact:固定依赖的具体版本
- -g | --global:天津全局依赖,不会修改当前项目的 **package.json** 文件,与 `bun i -g` 相同
- 移除依赖:
bun remove <package>
- 更新依赖:
- 更新某个或者所有依赖:bun update [package]
- 更新到最新版:bun update --latest
- 清空 Bun 的全局模块缓存:
bun pm cache rm
- 列出所有全局安装的依赖:
bun pm ls -g [--all]
- 打包:
bun build ./index.tsx --outdir ./build
工作空间
Bun 支持在 package.json 中通过 workspaces
键配置工作空间。允许通过同一份代码管理多个独立的包。
示例目录结构
1 | tree |
根目录的package.json
package.json
1 | { |
在执行 bun i
时,会安装根项目以及全部的工作空间下的单体仓库的依赖,遇到重复的依赖,则会将其移至根目录的 node_modules
下。
如果只想安装工作空间下的某个单体仓库的依赖,可以使用 --filter
进行过滤:
1 | # 安装除了 `pkg-c` 外的其他以 `pkg-` 开头的单体仓库的依赖 |
测试
Bun 附带一个快速、内置、兼容 Jest 的测试运行器。测试使用 Bun 运行时执行,并支持以下功能。
- TypeScript 和 JSX
- 生命周期钩子
- 快照测试
- UI 和 DOM 测试
- 使用
--watch
实现监视模式 - 使用
--preload
实现脚本预加载
运行测试代码:bun test
。递归地搜索工作目录下匹配模式的文件:
*.test.{js|jsx|ts|tsx}
*_test.{js|jsx|ts|tsx}
*.spec.{js|jsx|ts|tsx}
*_spec.{js|jsx|ts|tsx}
示例代码main.test.ts
main.test.ts
1 | import { expect, test } from "bun:test"; |
CI/CD
Hono
基础
- 初始化项目:
bunx create-hono my-app
- 安装依赖:
bun i
- 启动开发模式:
bun dev
- 部署应用:
bun deploy
Hello World
1 | import { Hono } from 'hono' |
Hono 获取 Cloudflare Workers 的 KV、R2、D1 等绑定对象,只能通过上下文环境变量(c.env
)获取(相关的绑定需要提前在 wrangler.toml
中配置):
示例index.ts
index.ts
1 | import { Hono } from 'hono' |
API
应用初始化选项
1 | import { RegExpRouter } from 'hono/router/reg-exp-router' |
App
app.HTTP_METHOD([path,] handler | middleware…)
:注册路由app.basePath(path)
:统一设置路由前缀,如:/api
app.notFound(handler)
:自定义 404 响应app.onError(err, handler)
:全局异常处理app.mount(path, anotherApp)
:挂载其他应用(含其它 Web 标准编写的框架)到 Hono 应用上示例代码
1
2
3
4
5
6
7
8
9
10import { Router as IttyRouter } from 'itty-router'
import { Hono } from 'hono'
// 创建 itty-router 应用
const ittyRouter = IttyRouter()
ittyRouter.get('/hello', () => new Response('Hello from itty-router'))
// Hono 应用
const app = new Hono()
app.mount('/itty-router', ittyRouter.handle)
Routing
基础路由配置:
app.get(path, handler)
、app.post(path, handler)
、app.put(path, handler)
、app.delete(path, handler)
- 包含
*
的通配符:app.get('/wild/*/card', handler)
- 任意 HTTP 方法:
app.all(path, handler)
- 自定义 HTTP 方法:
app.on('PURGE', path, handler)
- 同一路径配置多个请求方法(也可以使用链式路由):
app.on(['PUT', 'DELETE'], path, handler)
- 多路径同方法:
app.on(METHOD, ['/hello', '/ja/hello'], handler)
路由参数:
- 定义:
/:参数名?
,冒号为参数的定义符号,参数后的问号标识该参数为可选参数,可选参数只能放在最末尾,否则问号会被当成参数名的一部分: - 正则表达式:
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => { const { date, title } = c.req.param() })
- 包含斜线:
app.get('/posts/:fileame{.+\\.png$}', handler)
- 链式路由:
app.get(path, handler).post(handler).delete(handler)
- 分组路由:实例化多个 Hono,每个实例设置好路由后再通过 route 方法挂载到主实例上。
示例代码
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import { Hono } from 'hono'
const book = new Hono()
book.get('/book', (c) => c.text('List Books')) // GET /book
book.post('/book', (c) => c.text('Create Book')) // POST /book
const user = new Hono().basePath('/user')
user.get('/', (c) => c.text('List Users')) // GET /user
user.post('/', (c) => c.text('Create User')) // POST /user
const app = new Hono()
app.route('/', book) // Handle /book
app.route('/', user) // Handle /user
export default app
- 定义:
路由优先级
- 路由处理器、中间件会按照注册顺序执行;
- 如果中间件需要应用于某些路由,则应将该中间件注册顺序排到所有这些路由前;
- 使用通配路由(
app.get('*', handler)
)作为兜底路由,并将其放在所有路由最末尾。
Context
上下文(Context)用于处理请求(req: HonoRequest
)、响应(res: Response
)对象。
Context.body(data, [status, headers])
:返回 HTTP 响应。Context.header(name, value [, options: {append?: boolean}])
:设置响应头。Context.status(code)
:设置响应状态码。Context.text(data, [status, headers])
:以Content-Type: text/plain
返回文本。Context.json(data, [status, headers])
:以Content-Type: application/json
返回数据。Context.html(data, [status, headers])
:以Content-Type: text/html
返回数据。Context.notFound()
:返回状态为404
到响应。Context.redirect(path, [statusCode])
:重定向,默认状态码为302
(资源已找到,但暂时移动到重定向路径上),可设置301
(永久)、303
(See Other)、307
(临时)。HonoRequest.param([key])
:获取所有路由参数对象,指定键名则返回对应值。HonoRequest.query([key])
:解析获取所有查询参数,指定键名则返回对应值。HonoRequest.queries([key])
:返回到查询参数为数组,相同键名多个值。HonoRequest.header([key])
:解析返回请求头对象,指定键名则返回对应值。HonoRequest.parseBody()
:解析请求体类型为multipart/form-data
或者application/x-www-form-urlencoded
的数据。解析获取上传的多文件对象时,键名需带上
[]
后缀:(await c.req.parseBody())['files[]']
。HonoRequest.json()
:解析请求体类型为application/json
的数据。HonoRequest.text()
:解析请求体类型为text/plain
的数据。HonoRequest.blob()
:将请求体数据转换成Blob
对象。HonoRequest.formData()
:将请求体数据转换成FormData
对象。HonoRequest.url
:获取请求的 URL,包含协议、域名、端口、路径。HonoRequest.raw
:返回 Web 标准的Request
对象。如果是 Cloudflare 运行环境,可以通过c.req.raw.cf
获取到相关数据。
异常处理
1 | import { HTTPException } from 'hono/http-exception' |
中间件
中间件作用于路由处理器之前或者之后。我们可以在请求(Request
)派发之前获取到它,也可以在响应(Response
)派发之后操作它。
中间件与路由处理器的区别:
- 路由处理器:必须返回
Response
对象,且只有一个处理器被调用。 - 中间件:无返回,需执行
await next()
处理下一个中间件。
用法
1 | // 匹配所有 HTTP 请求方法、所有路由 |
自定义中间件
1 | app.use('/message/*', async (c, next) => { |
一些有用的中间件
Basic Authentication
1 | import { Hono } from 'hono' |
CORS(跨域)
1 | import { Hono } from 'hono' |
JWT
如果未配置 cookie
字段,该 JWT 中间件将会使用请求头的 Authorization
项。
1 | import { Hono } from 'hono' |
Logger
1 | import { Hono } from 'hono' |
Secure Headers Middleware
简化了安全 headers 的设置过程,允许您控制特定安全 headers 的启用和禁用。参考
1 | import { Hono } from 'hono' |
Hono Rate Limiter
1 | bun add hono-rate-limiter |
1 | // index.ts |
1 | # wrangler.toml |
API 密钥、URL 路径或路由、应用程序使用的特定查询参数和/或用户 ID。
不建议使用IP地址(因为在许多有效情况下,许多用户可以共享这些地址)或位置(同上),因为您可能会发现自己意外地限制了比您预想的更广泛的用户群体。
Zod OpenAPI
Zod OpenAPI Hono 是扩展了 Hono 的一个类,它支持 OpenAPI 协议。使用它可以结合 Zod 来验证值和类型,并生成 OpenAPI 的 Swagger 文档。
安装:
1
bun add zod @hono/zod-openapi @scalar/hono-api-reference
参考:
Drizzle ORM
基础
安装:
bun add drizzle-orm && bun add -d drizzle-kit
- 对应的数据库驱动器:
- Neon Postgres:
bun add @neondatabase/serverless
- Xata:
bun add @xata.io/client
- Vercel Postgres:
bun add @vercel/postgres
- Turso:
bun add @libsql/client
- Cloudflare D1
- Neon Postgres:
- 对应的数据库驱动器:
配置文件:项目根目录下新建的
drizzle.config.[ts|js|json]
文件。示例代码
drizzle.config.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import { defineConfig } from 'drizzle-kit'
export default defineConfig({
dialect: "sqlite", // "mysql" | "sqlite" | "postgresql"
schema: "./src/schema.ts", // "./src/**/schema.ts" | ["./src/user/schema.ts", "./src/posts/schema.ts"] | "./src/schema/*"
out: "./drizzle",
// https://orm.drizzle.team/kit-docs/config-reference#driver
// driver: 'd1-http', // 'aws-data-api' | 'd1-http' | 'expo' | 'turso'
dbCredentials: {
url: process.env.DB_URL,
// or
//user: "postgres",
//password: process.env.DATABASE_PASSWORD,
//host: "127.0.0.1",
//port: 5432,
//database: "db",
// for Cloudlfare D1
//accountId: "",
//databaseId: "",
//token: "",
},
// multi-project schema, multi-project in one database
// `const pgTable = pgTableCreator((name) => `project1_${name}`);`
tablesFilter: ["project1_*"],
})Drizzle Kit 操作命令:
- 生成数据库 migrations:
drizzle-kit generate
- 应用 migrations:
drizzle-kit migrate
- 同步 schema 到数据库:
drizzle-kit push
- 删除上一次生成的 migrations:
drizzle-kit drop
- 多人协作时,检查 schema 的冲突:
drizzle-kit check
- 打开浏览器进行可视化操作:
drizzle-kit studio
示例代码
1
2
3
4
5
6
7
8import { sql } from "drizzle-orm";
import { text, integer, sqliteTable } from "drizzle-orm/sqlite-core";
const users = sqliteTable('users', {
id: text('id'),
textModifiers: text('text_modifiers').notNull().default(sql`CURRENT_TIMESTAMP`),
intModifiers: integer('int_modifiers', { mode: 'boolean' }).notNull().default(false),
});- 生成数据库 migrations:
数据类型(以 SQLite 为例)
基于官方的 SQLite 文档,每个存储在 SQLite 数据库中(或由数据库引擎操作)的值都具有以下一种存储类型:NULL
、INTEGER
、REAL
、TEXT
和 BLOB
。
Drizzle ORM 原生支持所有这些类型,并可以自由创建自定义类型。
- Integer:有符号整型,根据数值大小,使用 0、1、2、3、4、6 或 8 个字节存储。
integer('id')
:默认模式整型。integer('count', {mode: 'number' | 'boolean' | 'timestamp' | 'timestamp_ms'})
:自定义整型模式,调整其字节存储。
- Real:8字节 IEEE 浮点数,
real('interest')
。 - Text:文本字符串,使用数据库编码存储(UTF-8、UTF-16BE 或 UTF-16LE)。
text('title')
text('json', {mode: 'json'}).$type<{foo: string}>()
:text 数据类型设置为 JSON 类型,并通过$type<T>()
提供类型推导。text('status', { enum: ["value1", "value2"] })
:设置为枚举类型,类型为:"value1" | "value2" | null
。只提供类型推导,不会再运行时进行校验。
- Blob:二进制数据类型,输入什么存储什么。
blob('image')
blob('blob', { mode: 'buffer' | 'bigint' | 'json' })
- Boolean:SQLite 不支持原生
boolean
类型,可以通过设置integer
的mode
为boolean
来实现,值为0
和1
。integer('complete', { mode: 'boolean' })
- Bigint:SQLite 的数据类型中没有
bigint
,但可以通过设置blob
的mode
为bigint
来实现。 - 自定义数据类型:可以通过
.$type()
来实现自定义列数据类型。示例代码
1
2
3
4
5
6
7
8
9
10type UserId = number & {__brand: 'user_id'}
type Data = {
foo: string;
bar: number
}
const users = sqliteTable('users', {
id: integer('id').$type<UserId>().primaryKey(),
jsonField: blob('json_field').$type<Data>(),
})
索引/约束
SQL 索引是作用于数据库列的规则,防止无效数据插入数据库中。
Not Null - 非空约束:
.notNull()
,即数据结果不能为空NULL
。Default - 默认值:所有数据类型的默认值都是
NULL
,可以通过.default()
或者.$defaultFn()
,也可以通过.$onUpdate(() => null)
/.$onUpdateFn()
来更新默认值。示例代码
1
2
3
4
5
6
7
8
9
10
11import { sql } from 'drizzle-orm'
import { text, sqliteTable } from 'drizzle-orm/sqlite-core'
import { createId } from '@paralleldrive/cuid2';
const table = sqliteTable("table", {
id: text('id').$defaultFn(() => createId()),
time: text("time").default(sql`(CURRENT_TIME)`), // 默认值中的一些特殊关键词:当前时间
date: text("date").default(sql`(CURRENT_DATE)`), // 当前日前
timestamp: text("timestamp").default(sql`(CURRENT_TIMESTAMP)`), // 当前时间戳
updatedAt: text("updated_at").default(sql`(CURRENT_TIMESTAMP)`).$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
});Unique - 唯一约束:确保列中的所有值都不同,主键拥有同样的效果,但是一张表只能有一个主键,唯一约束却可以有多个。
int('id').unique()
int('id').unique('custom_name')
:自定义唯一约束名。sqliteTable('composite', { id: int('id'), name: text('name') }, (table) => { unq: unique().on(table.id, table.name) })
:组合键唯一约束。
Check:限制值在某个范围内,Drizzle ORM 暂未实现。
Primary Key - 主键:主键具有唯一非空性,一张表中只能有一个主键,主键可以包含一个或多个字段(column)
integer('id').primaryKey()
integer('id').primaryKey({ autoIncrement: true })
:自增主键- 组合主键:
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import { integer, text, primaryKey, sqliteTable} from "drizzle-orm/sqlite-core";
export const user = sqliteTable("user", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name"),
});
export const book = sqliteTable("book", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name"),
});
export const bookToAuthor = sqliteTable("book_to_author", {
authorId: integer("author_id"),
bookId: integer("book_id"),
}, (table) => {
return {
pk: primaryKey({ columns: [table.bookId, table.authorId] }),
pkWithCustomName: primaryKey({ name: 'custom_name', columns: [table.bookId, table.authorId] }),
};
});
Foreign Key - 外键:外键约束通常用于防止破坏表间的连接操作。外键可以是一张表中的一个或多个字段指向其它表的主键。包含外键的表叫子表,包含指向对应主键的表叫父表 / 指向表。
.references(() => table.pk)
:指向主键字段。示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56import { integer, text, sqliteTable } from "drizzle-orm/sqlite-core";
export const user = sqliteTable("user", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name"),
});
export const book = sqliteTable("book", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name"),
authorId: integer("author_id").references(() => user.id)
});
// 自引用
import { integer, text, foreignKey, sqliteTable, AnySQLiteColumn } from "drizzle-orm/sqlite-core";
export const user = sqliteTable("user", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name"),
parentId: integer("parent_id").references((): AnySQLiteColumn => user.id)
});
//or
export const user = sqliteTable("user", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name"),
parentId: integer("parent_id"),
}, (table) => {
return {
parentReference: foreignKey({
columns: [table.parentId],
foreignColumns: [table.id],
name: "custom_fk"
}),
};
});
// 组合外键
import { integer, text, primaryKey, foreignKey, sqliteTable, AnySQLiteColumn } from "drizzle-orm/sqlite-core";
export const user = sqliteTable("user", {
firstName: text("firstName"),
lastName: text("lastName"),
}, (table) => {
return {
pk: primaryKey({ columns: [table.firstName, table.lastName]}),
};
});
export const profile = sqliteTable("profile", {
id: integer("id").primaryKey({ autoIncrement: true }),
userFirstName: text("user_first_name"),
userLastName: text("user_last_name"),
}, (table) => {
return {
userReference: foreignKey(() => ({
columns: [table.userFirstName, table.userLastName],
foreignColumns: [user.firstName, user.lastName],
name: "custom_name"
}))
}
});
Indexes - 索引:Drizzle ORM 提供
index
和unique index
声明。示例代码
1
2
3
4
5
6
7
8
9
10
11import { integer, text, index, uniqueIndex, sqliteTable } from "drizzle-orm/sqlite-core";
export const user = sqliteTable("user", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name"),
email: text("email"),
}, (table) => {
return {
nameIdx: index("name_idx").on(table.name),
emailIdx: uniqueIndex("email_idx").on(table.email),
};
});
视图
-- 摘自 Microsoft Learn
视图是一个虚拟表,其内容由查询定义。同表一样,视图包含一系列带有名称的列和行数据。视图在数据库中并不是以数据值存储集形式存在,除非是索引视图。 行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。
对其中所引用的基础表来说,视图的作用类似于筛选。定义视图的筛选可以来自当前或其他数据库的一个或多个表,或者其他视图。
视图通常用来集中、简化和自定义每个用户对数据库的不同认识。 视图可用作安全机制,让用户通过视图访问数据,而不授予用户直接访问视图基础表的权限。 视图可用于提供向后兼容接口来模拟曾经存在但其架构已更改的表。
声明视图:
1
2
3
4
5import { sqliteView } from "drizzle-orm/sqlite-core";
export const userView = sqliteView("user_view").as((qb) => qb.select().from(user));
export const userView = sqliteView("user_view").as((qb) => qb.select({id: user.id, name: user.name}).from(user)); // 指定显示的字段
export const customersView = sqliteView("customers_view").as((qb) => qb.select().from(user).where(eq(user.role, "customer"))); // 筛选声明已存在的视图:
1
2
3
4
5export const trimmedUser = sqliteView("trimmed_user", {
id: serial("id"),
name: text("name"),
email: text("email"),
}).existing();
声明关系
一对一
1 | import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core' |
一对多
1 | import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core' |
多对多
多对多关系使用一张显式定义的中间表(junction/join table
)连接相关联的表:
1 | import { sqliteTable, integer, text, primaryKey } from 'drizzle-orm/sqlite-core' |
数据操作
数据查询
1 | import { drizzle } from 'drizzle-orm/bun-sqlite' |
数据插入
1 | import { drizzle } from 'drizzle-orm/bun-sqlite' |
更新数据
1 | await db.update(users) |
删除数据
1 | await db.delete(users) |
Wrangler
Wrangler 是开发 CloudFlare 相关应用的命令行工具。
新版 Wrangler 不再由 Rust 编写,而是使用 TypeScript 实现。
基础
- 初始化项目:
bunx create-cloudflare@latest
- 根据绑定和配置模块规则生成类型(TypeScript):
bun wrangler types
- 本地开发:
bun wrangler dev [--env dev] [--ip 0.0.0.0] [--port 12345]
- 部署项目到 Cloudflare:
bun wrangler deploy [--env prodction]
- 查看登陆身份:
bun wrangler whoami
- 登陆 Cloudflare:
bun wrangler login
- 查看部署版本:
bun wrangler deployments list
- 版本回滚:
bun wrangler rollback [<DEPLOYMENT_ID>]
- Pages 相关命令:
- 本地开发:
bun wrangler pages dev [--local | --ip | --port | --binding | --kv | --r2 | --do | --live-reload | --compatibility-date]
- 项目管理:
bun wrangler pages project <list | create | delete>
- 部署管理:
bun wrangler pages deployment <list | tail>
- 部署:
bun wrangler pages deploy
- 本地开发:
配置文件
Wrangler 使用一个可选的 wrangler.toml
文件作为配置文件。
- 只能作为顶层的键
keep_vars
:可选,boolean
类型。部署时,是否保持控制面板中配置的变量。send_metrics
:可选,boolean
类型。是否将该项目的使用指标发送给 Cloudflare。site
: 建议使用 Cloudflare Pages,可选,对象。用于配置静态文件。
- 可继承的键:配置于顶层的键,可被指定环境继承或覆盖。
name
:必填,string
类型。Worker 名称。main
:必填,string
类型。执行 Worker 的入口文件。compatibility_date
:必填,string
类型,格式yyyy-mm-dd
。指定 Workers 运行时使用的版本。account_id
:可选,string 类型。workers_dev
:可选,boolean
类型,默认值true
。部署到 Cloudflare 时使用*.worders.dev
子域名。route
/routes
:可选,Route
/Route[]
类型。部署 Worker 到路由。minify
:可选,boolean
类型。上传时是否精简代码。node_compat
:可选,boolean
类型。为 Node.js 的内置模块、全局添加 polyfills。logpush
:可选,boolean
类型,默认值false
。是否为 Workers 启用 Workers Trace Events Logpush。limits
:可选,Limits
类型。
- 非继承键:定义于顶层的键,不可被继承,只能在各个环境中配置。
define
:可选,Record<string, string> 类型。可替换的值。vars
:可选,object 类型。一组环境变量。durable_objects
:绑定的 Durable Objects。kv_namespaces
:绑定的 KV 命名空间。r2_buckets
:绑定的 R2 桶。vectorize
:绑定的 Vectorize 索引。services
:绑定的服务。tail_consumers
:Worker 发送数据的目的地。
示例代码wrangler.toml
wrangler.toml
1 | # Top-level configuration |