@aws- blocks/ bb- data (原始内容存档于2018-09-28) (英语)
完整的 PostgreSQL 数据库——默认情况下提供 Aurora Serverles v2,或连接到现有的 PostgreSQL 数据库(Supabase, Neon等) 通过 fromExisting()。与外国密钥、交易、行级安全以及一个类型安全的 Kysely 查询构建器建立完整的关系模型.
何时使用 : 复杂的多表JOIN,ACID交易,外接键约束,聚合,行级安全,或连接到现有的PostgreSQL数据库. 需要PostgreSQL全能时使用.
不使用时 : 用于简单的密钥值检查, 使用 KVStore。对于带有二级索引的 NoSQL,使用 DistributedTable对于没有FK/RLS/触发器的无服务器 SQL(多区域,即时提供),使用 DistributedDatabase。 。 。 .
设计与模拟等价细节 : 设计
快速启动
import { Database, sql } from '@aws-blocks/bb-data';
const db = new Database(scope, 'main', {
migrationsPath: './aws-blocks/migrations',
});
// Parameterized queries via sql tagged template (injection-safe)
const users = await db.query<{ id: string; name: string }>(
sql`SELECT * FROM users WHERE active = ${true}`
);
const user = await db.queryOne<{ id: string; name: string }>(
sql`SELECT * FROM users WHERE id = ${userId}`
);
const { rowCount } = await db.execute(
sql`INSERT INTO users (id, name, email) VALUES (${id}, ${name}, ${email})`
);
// Transactions
await db.transaction(async (tx) => {
await tx.execute(sql`UPDATE accounts SET balance = balance - ${100} WHERE id = ${fromId}`);
await tx.execute(sql`UPDATE accounts SET balance = balance + ${100} WHERE id = ${toId}`);
});
移徙
创建编号 .sql 迁移目录中的文件 :
aws-blocks/migrations/
001_create_users.sql
002_create_posts.sql
003_seed_admin.sql
-- 001_create_users.sql
CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
迁移自动运行 :
- 本地 dev : 在第一次查询时( PGlite, 坚持在
.bb-data/(中文(简体) ) - AWS部署: 期间使用自定义资源 Lambda
cdk deploy
应用迁移情况在以下表格中跟踪: _migrations 页:1 每个文件运行一次.
设定
migrationsPath与工程根相对的路径 (如"快起步"——'./aws-blocks/migrations'); 由您运行的目录合成解析cdk编号 :npm run deploy进来 这是最简单的可靠模式.你用不着
fileURLToPath(import.meta.url)为了这个 您的后端模块在当地作为无害环境管理运行,但捆绑到 常见JS 在兰布达,在那里import.meta为空。 AWS 屏蔽闪烁import.meta.url编号 :import.meta.dirname{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}所以它不会在装货时坠毁 {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}但是在运行时 捆绑输出 位置,而不是您的源树。 所以别用import.meta.url在请求的时间读取与您来源相关的文件; 输入数据或将其作为Lambda资产.
Kysely 查询构建器
对于没有原始 SQL 的类型安全查询:
import { createKyselyAdapter } from '@aws-blocks/bb-data';
interface Schema {
users: { id: string; email: string; name: string };
posts: { id: string; user_id: string; title: string };
}
const kysely = createKyselyAdapter<Schema>(db);
// Type-safe SELECT
const users = await kysely
.selectFrom('users')
.where('email', '=', '[email protected]')
.selectAll()
.execute();
// JOINs
const posts = await kysely
.selectFrom('posts')
.innerJoin('users', 'users.id', 'posts.user_id')
.select(['posts.title', 'users.name'])
.execute();
// Transactions
await kysely.transaction().execute(async (trx) => {
await trx.insertInto('users').values({ id: '1', email: '[email protected]', name: 'A' }).execute();
await trx.insertInto('posts').values({ id: '1', user_id: '1', title: 'Hello' }).execute();
});
见 Kysely 文档 用于完整查询构建器 API.
行级安全( RLS)
与 Supabase 兼容会话变量的用户背景范围查询 :
const scoped = db.withRLS({ userId: 'user-123', role: 'authenticated' });
// All queries on `scoped` run inside a transaction with SET LOCAL ROLE
// and request.jwt.claims set — PostgreSQL RLS policies are enforced.
const myPosts = await scoped.query<Post>(sql`SELECT * FROM posts`);
request.jwt.claims 总是带着 sub =: userId 财务报告和审定财务报表 role = 数字 role 你通过。 额外 claims 被合并,但他们不能 覆盖这两个:a sub 或者说 role 键入 claims 被忽略。 因此政策可以信任两者,例如: USING (owner_id = current_setting('request.jwt.claims', true)::jsonb ->> 'sub')。当您从身份提供者处转发请求时,将特定提供者的值置于他们自己的密钥下(例如 claims: { idp_sub: token.sub }) (中文(简体) ).
先决条件(当地和AWS):
withRLS问题SET LOCAL ROLE <role>(默认)authenticated) (中文(简体) ). 该角色必须存在,数据库用户必须是其中的一员. 在 PostgreSQL 16 上,以及Aurora 运行后,创建角色的用户只获得ADMIN上面没有SET,所以明确赋予角色。 在您的迁移中添加:CREATE ROLE authenticated; CREATE ROLE anon; GRANT authenticated TO CURRENT_USER; GRANT anon TO CURRENT_USER; -- grant table privileges to these roles as needed, then define RLS policies如果角色切换被拒绝, 每个
withRLS()调用 500 失败QueryFailed,而服务器日志为缺失的GRANT。 。 。 .
CRUD 处理器
从计划定义中生成输入的 CRUD 方法 :
const crud = db.crud({
tables: ['users', 'posts'],
// `auth` takes no arguments — close over your request context to resolve the user.
auth: async () => {
const user = await auth.requireAuth(context);
return { userId: user.userId };
},
});
// Auto-generated flat method names per table:
// crud.listUsers(), crud.getUser(id), crud.createUser(data),
// crud.updateUser(id, data), crud.deleteUser(id)
// crud.listPosts(), crud.getPost(id), ...
连接到现有数据库
import { Database, fromExisting } from '@aws-blocks/bb-data';
// Supabase, Neon, or any PostgreSQL-compatible database
const db = new Database(scope, 'external', {
connection: fromExisting({ connectionString: process.env.DATABASE_URL! }),
});
TLS 证书验证
服务器的 TLS 证书是 默认验证管理下供应商
(Supabase, neon, RDS) 提交由特定供应商 CA 签名的证书
这不在Node的内置信托店里 所以验证需要打针
那个CA. ssl.ca 获取证书 目录 (一个PEM字符串),不是路径——
用于Supabase, 下载 prod-ca-2021.crt 从您的项目 数据库设置
— SSL 配置数字 :
import { readFileSync } from 'node:fs';
const db = new Database(scope, 'external', {
connection: fromExisting({
connectionString: process.env.DATABASE_URL!,
ssl: { ca: readFileSync('./supabase-ca.crt', 'utf8') },
}),
});
bb-data pull 给您接线: 它会提示您的 CA 证书并输入
给 aws-blocks/database.ca.ts (捆绑成
,所以连接是 默认验证 - 包括:
在部署的Lambda, 没有运行时间配置. DATABASE_CA_CERT (内在
PEM 或文件路径 ) 覆盖已执行的证书 。 如果两者都没有,则
生成的接线返回到 ssl: { rejectUnauthorized: false } (单位:千美元)加密但
未认证仅以本地数据表示 已部署的函数未关闭
(拒绝连接)而不是运行未经验证. 为生产提供CA.
从Supabase迁移
已经有Supabase软件了? bb-data pull 连接到您现有的 Supabase 数据库并生成一个完整、 类型安全的后端 —— 将您的表格、 数据和 RLS 政策保持原样 .
npx bb-data pull
它会做什么:
- 透视您的公示表( 只读 —— 您的数据库没有被修改)
- 生成打入的定义、 CRUD 操作和个性化迁移指南
- 将您的连接字符串保存到本地
SUPABASE_DB_URL内.env.local(部署时在SSM中加密). 只有该条目被添加或更新——其他设置、注释和文件的权限被保存SUPABASE_DB_URL线条本身是重写整行的,所以其末尾的注释不保存。 若为.env.local无法安全更新(例如,更改将改变另一个条目的读取方式),db pull在生成任何文件之前停止,请您手动设置条目。 选择“ 生产” 对.env.production。 。 。 .
它不迁移的东西:Supabase Auth、存储、实时或边缘函数。 如果使用第三方的OIDC供应商(Auth0, Clerfer, Google, Cognitto),可以将其接入Blocks——查看生成的 MIGRATION_GUIDE.md#auth。 。 。 .
在拉后,运行 npm run dev 开始根据您真正的数据库本地开发.
调取后, 用版本控制的 SQL 移入管理 ./migrations/ 自动应用 npm run dev 财务报告和审定财务报表 npm run deploy。参见生成 MIGRATION_GUIDE.md#evolving-your-schema。 。 。 .
处理错误
import { DatabaseErrors } from '@aws-blocks/bb-data';
import { isBlocksError } from '@aws-blocks/core';
try {
await db.execute(sql`INSERT INTO users (id, email) VALUES (${id}, ${email})`);
} catch (e: unknown) {
if (isBlocksError(e, DatabaseErrors.UniqueConstraintViolation)) {
// Duplicate key — email already exists. Serialized as HTTP 409 (Conflict),
// not retriable (a blind retry of the same insert fails identically).
}
if (isBlocksError(e, DatabaseErrors.PermissionDenied)) {
// Your statement was refused: a row-level-security policy rejected the row
// (for example a withRLS() user writing someone else's row), or the role has
// no grant on the table. Serialized as HTTP 403.
}
if (isBlocksError(e, DatabaseErrors.CheckViolation) || isBlocksError(e, DatabaseErrors.NotNullViolation)) {
// A value fails a CHECK constraint, or a required column is missing.
// Serialized as HTTP 400.
}
if (isBlocksError(e, DatabaseErrors.ForeignKeyViolation)) {
// The referenced row doesn't exist, or other rows still reference the one
// being deleted. Serialized as HTTP 409 (Conflict).
}
if (isBlocksError(e, DatabaseErrors.QueryFailed)) {
// General query failure (syntax error, missing table, etc.)
}
if (isBlocksError(e, DatabaseErrors.TransactionFailed)) {
// Transaction could not commit
}
if (isBlocksError(e, DatabaseErrors.SerializationFailure)) {
// Serializable-isolation conflict with a concurrent transaction — serialized
// as HTTP 409 (Conflict), retriable — safe to retry
}
if (isBlocksError(e, DatabaseErrors.ConnectionFailed)) {
// Cannot reach the database — includes a `minCapacity: 0` cluster resuming
// from auto-pause, which succeeds on retry a few seconds later
}
}
每个错误的客户端可见消息是固定字符串; 驱动程序本身
文本,将表格、列和限制的名称保留在服务器上
错误 cause。仅限 SerializationFailure 财务报告和审定财务报表 ConnectionFailed 价值
重试: 请求出错( r)PermissionDenied, (中文(简体) ), CheckViolation, (中文(简体) ),
NotNullViolation, (中文(简体) ), ForeignKeyViolation, (中文(简体) ), UniqueConstraintViolation失败
每次都一样.
每个错误都会中止所附的 Postgres 交易 。 内部a
db.transaction() 调回,一个被捕获的错误使交易无法使用:
下个语句因 QueryFailed. 在外接他们 transaction(),或
在保存点中将语句包裹( )SAVEPOINT s . ROLLBACK TO SAVEPOINT s如果
交易应继续下去.
它的规定
- Aurora 无服务器 v2 ——相容后SQL相容,分级为0.5-128ACU. 存储
休息时加密为 通过上下文旗选择进入 因此,新项目的安全通过
默认不替换已存在的集群。 新设
create-blocks-app项目 设定"@aws-blocks/bb-data:encryptStorageByDefault": true内cdk.json,哪个 打开账户AWS管理的加密aws/rds键。 通过一个storageEncryptionKeyArn(客户管理的 KMS 密钥的 ARN) 也转动 无论旗帜如何加密并使用该密钥( 它也加密) 生成的证书秘密 。 当国旗或密钥都没有设定时 集群合成时没有StorageEncrypted财产(现有, 未加密集群未受影响)和合成警告解释如何选择 进去 关键是 进口 (锁不能改变它的政策),所以它的政策 必须已经授予部署本金kms:CreateGrant+ 键kms:DescribeKey(RDS使用赠款加密存储量)和赠款kms:Decrypt页:1 在数据 API 上读取证书的校长。 钥匙必须在里面 页:1 同一账户和区域 作为集群. - 自动备份 ——默认保留 15天时间,这也是
点入时间恢复窗口。 由单人控制
pointInTimeRecovery选项(模仿所有其他块块):true启用15天窗口,{ retentionDays: n }针入1至35天窗口false夹住1天 最小值( Aurora 无法关闭自动备份) 。 省略时跟随 堆栈宽度defaults.pointInTimeRecovery(关于production关闭在下面sandbox——最少1天着陆. - 云表日志导出 — PostgreSQL 引擎日志被导出到 Cloud Watch
记录 日志组保留跟随整个堆栈
defaults.logRetention何时 设置;否则它使用账户默认值. - 职业介绍所 私人子网(同化,无NAT)
- 机密管理器 ——自动生成的证书. 尚未自动旋转
接通(在VPC组中旋转Lambda是计划的后续行动)
密钥用您的加密
storageEncryptionKeyArn当一个被提供. - 移徙Lambda —运行
.sql通过自定义资源部署的文件 - 行政会议 —— 说
rds-data:*财务报告和审定财务报表secretsmanager:GetSecretValue给Lambda的应用软件
访问模式 : 数据库完全通过 RDS 数据 API 连接 (HTTPS + 机密管理者证书),从不使用原始 Postgres socket. 数据库级别 IAM认证(
iamAuthentication因此,它被故意置之不理—— 不适用于 Data API 访问路径.对现有未加密集群进行加密需要 替换——替换具有破坏性. RDS 无法加密 已经设置了未加密的集群, 因此正在打开存储加密 上( 或更改 KMS 密钥) 使 CloudFormation 创建 新建, 空 加密集群并重点堆栈。 有一个 无位置路径。 。 。 . 旧的集群及其数据将取决于删除政策:
- 生产(保留): 古老的群落被抛在后头,成为孤儿,没有 更长时间的引用 。 应用程序回来 指向新的 空 分组。 迁移自定义资源 没有 在集群交换中重新运行—— 其唯一的 Cloud Formation 属性是服务符和迁移 散列, 集群被替换时也不会改变, 因此 Cloud Formation 永远不会 引用来. 因此,替换组产生 无计划· ; 仅当移取文件更改时( 更改移取文件) 移取会运行 ) (中文(简体) ). 您的数据仍然存在于孤星群中, 但未被恢复 .
- 沙盒(DESTROY): (中文(简体) ) 旧数据组及其所有数据为 删除。 。 。 .
- 抓图( T)
removalPolicy: 'snapshot': 旧组图示为 它被替换。 生产deletionProtection: true后续行动 删除旧集群可能失败并留作原地; 此清理路径是 未经核实的真实部署.取而代之为相. 将加密调回 关 - 删除 旗帜(或
storageEncryptionKeyArn)来自已经部署的项目 加密,是否通过删除cdk.json线条,或者通过一个将它放下的合并—— 并更改StorageEncrypted财产,所以 同样取代 分组 (恢复到未加密) 使用相同的新空集群 / no-schema 上述结果。 在删除旗帜前审查 diff 和快照 不仅在添加之前.唯一安全的路线是将数据传遍你们自己: 简介 页:1 现有集群, 恢复 以加密方式快照到新集群 启用,然后切到它。 检查"云形" diff 并拍摄快照 在将此项更改部署到您需要的数据组之前。 正在添加或 更改 KMS 密钥还替换集群生成的证书密钥( a) 新的逻辑 id, 从而生成新的密码),所以记录现有的 证书——或者在旧集群上重置主密码——如果您仍然 需要进入孤儿群体.
地方发展
- 引擎 : PGlite (WASM PostgreSQL) —— 完整的 Postgres 兼容性
- 存储 :
.bb-data/{fullId}/——坚持跨重启,以擦去rm -rf .bb-data - 移徙: 在第一个查询中自动运行
本地对 AWS: 列类型
阵列和 json 编号 : jsonb 列返回相同的 JavaScript 值
两个引擎。 阵列( R)text[], (中文(简体) ), int[])作为JS阵列回来
json 编号 : jsonb 列作为解析对象返回, 不管你在本地运行
PGlite)或位于RDS数据API上的AWS上. 要素表述仍然遵循
缩写如下——例如:. numeric[] 元素作为字符串回来
数据API.
一些平面数字类型在两者的表示上仍然可能有所不同
引擎 : numeric 编号 : decimal 以字符串的形式在数据 API 上返回,并且
bigint 编号 : int8 回来当JS number 在数据 API 上 pg 编号 :
PGlite 驱动程序默认情况下作为字符串返回. 刻出或清晰解析
如果你需要两个引擎的相同值.
一个 Postgres 键入 RDS 数据 API 无法返回 tsvector: 选择一个
tsvector 直接列(例如 select * 在一张产生量的桌子上
search tsvector 在 AWS 上失败。 从选定的列表中排除它——a
tsvector 通常只用于 WHERE 编号 : ORDER BY 全文上游,
不读取为值 .
配置
interface DatabaseOptions {
/** Path to directory containing numbered .sql migration files. */
migrationsPath?: string;
/** Connect to an existing database instead of provisioning one. */
connection?: ExternalDatabaseRef;
/** Schema metadata for crud() support. */
schema?: TableSchema;
/** Aurora PostgreSQL engine version, e.g. '16.13'. Override the Aurora engine version. @default '16.13' */
postgresVersion?: string;
/**
* ARN of a customer-managed KMS key for the cluster's storage-at-rest encryption
* (also encrypts the auto-generated credentials secret). Supplying it turns
* storage encryption on regardless of the `@aws-blocks/bb-data:encryptStorageByDefault`
* context flag. When omitted, encryption follows that flag — on with the
* account's AWS-managed `aws/rds` key when set, otherwise left unset. The key is
* imported, so its policy must already grant the deploying principal
* `kms:CreateGrant` + `kms:DescribeKey` and grant `kms:Decrypt` to the principal
* reading the credentials secret; it must be in the same account + region.
*/
storageEncryptionKeyArn?: string;
/**
* Automated-backup retention, which is also the point-in-time-recovery (PITR)
* window. `true` enables the 15-day window; `{ retentionDays: n }` pins a
* 1–35-day window; `false` clamps to the 1-day minimum (Aurora cannot disable
* automated backups). When omitted, follows `defaults.pointInTimeRecovery`.
*/
pointInTimeRecovery?: boolean | { retentionDays: number };
}
软件包导出条件
{
"exports": {
".": {
"cdk": "./dist/index.cdk.js",
"aws-runtime": "./dist/index.aws.js",
"default": "./dist/index.mock.js"
}
}
}
业绩
- 查询间隔 : 10-50ms(温),~500ms从0 ACU起冷
- 通过量 : 请求通过 RDS 数据 API (HTTPS) 而不是集合数据库连接,所以没有连接池上限或 RDS 代理服务器; 货币以 Data API 请求限制和集群的 ACU 缩放相接.
- 存储 : 最多128个TiB,10个GiB增量的自动分级
- 费用: ~ 0.12/ACU-小时+~ 0.10/GB-月存储
- 重复性 : 3个AZ区6份,可用率99.99%