Show HN:构建SaaS应用,用户可自主控制数据存储位置
摘要
LinkedRecords是一个NoSQL数据库,让开发者能够构建用户自主控制数据存储位置的SaaS应用,无需后端代码即可直接从单页应用连接。
查看缓存全文
缓存时间: 2026/06/25 05:15
wolfoo2931/linkedrecords
来源:https://github.com/wolfoo2931/linkedrecords
LinkedRecords
LinkedRecords 是一种 NoSQL 数据库,你可以直接从单页应用连接它——无需后端代码。
📚 快速入门
想用 LinkedRecords 构建应用? 请查阅文档网站:
- 简介 (https://linkedrecords.com) —— 了解 LinkedRecords 是什么以及它的工作原理
- 快速入门指南 (https://linkedrecords.com/getting-started) —— 构建首个应用的分步指南
性能
下图展示了数据库增长时核心操作的性能。每次合并到主分支后,图表会自动更新。
负载测试模拟了一个真实的文档管理场景。每次 createDocument 操作都会创建一个包含 8 个属性(7 个 KeyValueAttribute + 1 个 LongTextAttribute)的蓝图,包括文档内容、协作者/读者组、评论、引用和配置。fetchDocuments 操作用于列出用户的所有文档,而 fetchDocument 则检索单个文档及其所有关联属性。
负载测试性能图表
负载测试的工作原理
测试模拟了一个多租户环境,包含三个用户:
- 用户 1 不断创建文档(当前测试配置为 5000 次迭代)。
- 用户 2 是“被测试用户”,每当用户 1 创建 10 个文档,该用户就创建 1 个文档,最多创建 300 个文档。达到 300 个文档后,该用户的文档创建停止。
- 用户 3 偶尔创建文档(每 1000 次迭代创建一次)
x 轴表示 数据库中的文档总数(所有用户拥有的文档)。y 轴表示响应时间(毫秒)。
测量内容:
| 操作 | 描述 |
|---|---|
createDocument | 创建一个包含所有相关属性的新文档所需的时间(内容、配置、评论、引用、协作者/读者组)。可以看到 createDocument 与数据库中的文档总数以及用户可见的文档数量无关。 |
fetchDocuments | 获取用户 2 的文档列表(最多 300 个可见文档)所需的时间。可以看到该时间取决于用户可见的文档数量,但与文档总数无关(图表在大约 3000 个文档时趋于平稳)。 |
fetchDocument | 获取单个文档及其所有相关数据所需的时间(内容、评论、组、活动状态、引用)。可以看到该时间取决于用户可见的文档数量,但与文档总数无关(图表在大约 3000 个文档时趋于平稳)。 |
概念
你可以将 LinkedRecords 看作一个桶,任何人都可以注册并向其中插入数据。只要你不将这些数据共享给其他用户或组,只有你能访问你写入的数据。
理论上,任何用户都可以直接使用 LinkedRecords API 写入和检索数据。但这并不方便——就像你不会期望用户编写 SQL 查询一样,你也不会期望他们与 LinkedRecords API 交互。LinkedRecords 应用是一个专门的前端,它隐藏了 API,并提供了方便的用户界面来完成任务。
在传统的 SQL 世界中,不方便并不是不让你直接访问数据库的唯一原因——授权问题更是一个重要原因。使用 LinkedRecords,这不再是问题:授权直接内置于 API 中。这需要一点思维转变:不再在后端为所有记录定义通用授权规则,而是由插入数据记录的用户指定谁可以读取它。
对于 LinkedRecords API,简单性、灵活性和解耦架构是我们追求的主要品质。
- 简单性:API 不应有很多端点或方法;而是由少数几个基本构建块组成。
- 灵活性:通过组合少数可用的端点,可以支持多种用例。内置的授权模型应允许实现不同的授权用例(如 RBAC 等)。
- 解耦:LinkedRecords 应与使用它作为数据存储的单页应用解耦。
可以将其视为可以直接从 React 应用调用的 SQL,而无需担心权限;它比 SQL 更易读,并提供实时更新。
配置
LinkedRecords 通过环境变量配置。请参见下表。
| 环境变量名 | 示例 | 描述 |
|---|---|---|
| PGHOST | localhost | PostgreSQL 服务器的主机名。 |
| PGUSER | linkedrecords | PostgreSQL 用户名。 |
| PGPASSWORD | xxxx | PostgreSQL 密码。 |
| PGDATABASE | xxxx | PostgreSQL 数据库名。 |
| CORS_ORIGIN | [“https://app.example.com”, “https://app.example.app”] | CORS 起源头部内容。如果未提供,将使用 FRONTEND_BASE_URL 的值。 |
| SERVER_BASE_URL | http://localhost:6543 | LinkedRecords 服务器的公共 URL。 |
| DEFAULT_STORAGE_SIZE_QUOTA | 500 | 默认存储大小配额(MB)。 |
| QUOTA_COUNT_KV_ATTRIBUTES | false | KeyValue 属性的存储空间是否从账户所有者配额中扣除。 |
| QUOTA_COUNT_LT_ATTRIBUTES | false | LongText 属性的存储空间是否从账户所有者配额中扣除。 |
| ENABLE_AUTH_RULE_CACHE | false | 启用授权查找缓存。可能需要大量内存。 |
| SHORT_LIVED_ACCESS_TOKEN_SIGNING | xxxx | 此配置可选,但可以减少数据库负载,因为当客户端订阅属性更改时,将使用短期访问令牌进行访问检查。 |
机密客户端模式
如果提供了“公共客户端模式”部分描述的配置,则本节中的环境变量均为可选。
如果 LinkedRecords 以机密客户端模式运行,则会将会话令牌存储在 HttpOnly cookie 中。从安全角度来看,这被认为是推荐方法。但是,如果 LinkedRecords 服务器和前端不在同一域下,则无法实现此模式。跨域时,cookie 会成为第三方 cookie,因此无法使用此模式。
| 环境变量名 | 示例 | 描述 |
|---|---|---|
| FRONTEND_BASE_URL | http://localhost:3001 | 前端的基本 URL。它将用于 Access-Control-Allow-Origin HTTP 头部,并且也是 OpenID Connect 重定向所必需的。 |
| AUTH_ISSUER_BASE_URL | https://xxx.us.auth0.com/ | OIDC 发行者的 URL。可以是任何符合 OpenID Connect 规范的身份提供者(例如 Auth0、Okta)。 |
| AUTH_CLIENT_ID | 客户端 ID。可以从身份提供者获取。 | |
| AUTH_CLIENT_SECRET | 客户端密钥。可以从身份提供者获取。 | |
| AUTH_IDP_LOGOUT | true | 设置为 true 时,用户会话将在应用和身份提供者中同时销毁。 |
| AUTH_COOKIE_SIGNING_SECRET | xxxx | 用于签名 cookie 的密钥。 |
公共客户端模式
如果单页应用托管在与 LinkedRecords 服务器不同的域上,则单页应用必须将访问令牌存储在浏览器中。在此场景下,需要配置以下环境变量。
| 环境变量名 | 示例 | 描述 |
|---|---|---|
| ALLOW_HTTP_AUTHENTICATION_HEADER | true | 允许公共客户端通过 HTTP 身份验证头部提供访问令牌来发起请求。 |
| AUTH_ISSUER_BASE_URL | https://xxx.us.auth0.com/ | OIDC 发行者的 URL。可以是任何符合 OpenID Connect 规范的身份提供者(例如 Auth0、Okta)。 |
| AUTH_TOKEN_AUDIENCE | your-audience-id | LinkedRecords 将检查 JWT 承载令牌中指定的 audience,并与该字段指定的值进行匹配。 |
| AUTH_CLIENT_ID | 客户端 ID。可以从身份提供者获取。 |
单页应用需要按如下方式初始化 LinkedRecords SDK:
import LinkedRecords from './src/browser_sdk';
const oidcConfig = {
client_id: 'your-client-id',
redirect_uri: window.location.origin + '/callback',
};
// 实例化 LinkedRecords 将自动处理 OIDC 重定向回调
const lr = new LinkedRecords(new URL('https://your-backend.com'), oidcConfig);
// 检查用户是否已认证:
// const isAuth = await lr.isAuthenticated();
// 开始登录流程(例如,在按钮点击时):
// lr.login();
可选配置
S3
如果配置了 S3,则将使用它来存储 blob 属性值。如果未配置,则它们将存储在 PostgreSQL 数据库中。建议配置 S3。
| 环境变量名 | 示例 | 描述 |
|---|---|---|
| S3_COPY_FROM_BL_ATTRIBUTE_TABLE | false | 用于从 PostgreSQL 迁移 blob 存储到 S3。 |
| S3_ENDPOINT | s3.system.svc.cluster.local | S3 端点的主机名。 |
| S3_BUCKET | linkedrecords-blobs | 存储桶名称。该存储桶必须已存在。 |
| S3_ACCESS_KEY | xxx | 用于上传 blob 到 S3 的访问密钥 ID。 |
| S3_SECRET_KEY | xxx | 用于上传 blob 到 S3 的秘密密钥 ID。 |
| S3_USE_SSL | false | 上传/下载到 S3 时不要使用 TLS。 |
Paddle
| 环境变量名 | 示例 | 描述 |
|---|---|---|
| PADDLE_NOTIFICATION_SECRET | xxxx | 如果使用 Paddle 升级配额,则需要将此值设为通知密钥,以验证 webhook 内容的签名。 |
| PADDLE_API_URL | https://sandbox-api.paddle.com | Paddle API 的 URL。 |
| PADDLE_API_KEY | xxx | Paddle API 密钥。 |
相似文章
Launch HN: Prized (YC S26) – 让非工程师员工构建安全的内部工具
Prized 让非技术人员通过自然语言描述需求,即可构建安全的内部工具,生成连接到公司数据的全栈应用,并内置权限和审计日志。
Show HN: GETadb.com – 每个GET请求创建一个数据库
GETadb.com 提供一个即时后端,包含关系型数据库、同步引擎和认证,通过简单的GET请求即可访问,无需注册,允许像Claude或Codex这样的AI智能体无缝构建全栈应用。
展示 HN:LatticeDB – 类似于 SQLite 但用于图数据库
LatticeDB 是一款嵌入式属性图数据库,它将向量和全文索引集成到单一文件格式中,支持在一个查询层内进行图遍历、相似性搜索和BM25搜索,适用于本地应用。
Show HN: Kedge – 具有可分支虚拟机快照和全局SQLite的全栈云平台
Kedge 是一个全球分布式云平台,提供硬件隔离的虚拟机、全局SQLite数据库、无服务器函数以及按秒计费的自动扩缩容。它旨在简化靠近用户的全栈应用部署。
Helicone 允许用户直接向共享 ClickHouse 编写 SQL
Helicone 实现了一项名为 HQL 的功能,允许客户针对共享的 ClickHouse 数据库编写原始 SQL 查询。他们利用 ClickHouse 的行策略和自定义设置来实施多租户隔离,确保每个组织只能看到自己的数据。