npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

drizzle-solid

v0.1.8

Published

Drizzle ORM adapter for Solid Pods

Readme

Drizzle Solid

一个为Solid Pod设计的类型安全ORM,基于Drizzle ORM构建,让您能够像操作传统数据库一样操作Solid Pod中的RDF数据。

✨ 特性

  • 🔒 类型安全: 完整的 TypeScript 支持与严格模式提示
  • 🧭 Drizzle 对齐: 沿用 Drizzle ORM 的查询构建器与错误形态,降低迁移成本
  • 🌐 Solid 实测: CSS 集成测试覆盖 CRUD、条件组合、聚合与联结场景
  • 🔁 智能回退: SQL 查询自动转换为 SPARQL;当 CSS/Comunica 无法处理过滤器或聚合时由方言拉取数据并在内存中回放
  • 🔧 灵活映射: 自定义命名空间、谓词和列类型(字符串、数字、布尔、时间、JSON/Object)

🚀 快速开始

安装

yarn add drizzle-solid

基本用法

import { drizzle } from 'drizzle-solid';
import { podTable, string, int } from 'drizzle-solid';
import { Session } from '@inrupt/solid-client-authn-node';

// 定义表结构
const profileTable = podTable('profile', {
  name: string('name'),
  email: string('email'),
  age: int('age')
}, {
  // 相对 Pod 根的容器/资源路径,不需要 idp:// 前缀
  base: '/profiles/',
  type: 'https://schema.org/Person'
});

// 自定义 subject 模板示例:按年月目录、文件名是 id,仍指向单独文件
const logsTable = podTable('logs', {
  id: string('id').primaryKey(),
  message: string('message')
}, {
  type: 'https://schema.org/DigitalDocument',
  base: '/logs/',
  subjectTemplate: '{yyyy}/{MM}/{dd}/{id}.ttl'
});

// subjectTemplate(选填):
// - 作用:控制每条记录主体 URI 的形态,支持占位符 {yyyy}/{MM}/{dd}/{HH}/{mm}/{ss}/{id}。
// - 推断:未指定时按 base 判断:base 是容器(以 / 结尾且包含表名)→ document 模式;base 是具体文件 → fragment 模式。模板含 #(如 '#{id}')也强制 fragment;其他模板按 document 处理,可用 {yyyy}/{MM}/{dd}/{id}.ttl 等。
// - document 模式:每条记录独立文件,URI 形如 .../users/{id}.ttl,SELECT/UPDATE/DELETE 由 Comunica dereference 目标文件后本地求值;适合记录级隔离/按目录分片。
// - fragment 模式:多条记录共享同一文件,主体是片段(.../file.ttl#{id}),SELECT 同样由 Comunica 拉取该文件求值;适合小表或聚合存放的数据。
// - 示例:subjectTemplate: '{yyyy}/{MM}/{dd}/{id}.ttl'(document 分日期分片);subjectTemplate: '#{id}'(fragment,共享文件)。

// 创建数据库连接
const session = new Session(); // 已认证的session
const db = drizzle(session);

// 初始化需要使用的表(创建容器、资源并注册 TypeIndex)
await db.init([profileTable]);

// 查询数据
const profiles = await db.select().from(profileTable);

// 插入数据
await db.insert(profileTable).values({
  name: 'Alice',
  email: '[email protected]',
  age: 30
});

// 直连 SPARQL 端点(跳过 LDP 容器)
const sparqlTable = podTable('posts', {
  id: string('id').primaryKey(),
  title: string('title'),
}, {
  type: 'https://schema.org/CreativeWork',
  // 只要提供 sparqlEndpoint 即可,CRUD 会直接走端点,不再创建容器/资源
  sparqlEndpoint: 'https://your-endpoint.example/sparql'
});
await db.init([sparqlTable]); // 不会创建容器,直接使用端点执行 SPARQL UPDATE/SELECT
await db.insert(sparqlTable).values({ id: 'post-1', title: 'Hello SPARQL' });

📚 示例教程

我们提供了完整的示例来帮助您快速上手:

🏗️ 示例1: 服务器设置和Pod创建

yarn example:setup

这个示例会:

  • 启动本地Community Solid Server(如果需要)
  • 引导您创建Solid Pod
  • 验证Pod创建成功
  • 获取WebID用于后续示例

📖 示例2: 认证与 Session 复用

yarn example:auth

这个示例展示:

  • 如何使用 @inrupt/solid-client-authn-node 进行客户端凭证登录
  • 如何复用已存在的 session 凭证
  • 如何在命令行中检查访问令牌与 Pod 元数据

🛠️ 示例3: 基础 CRUD 演练

yarn example:usage

这个示例展示:

  • 如何连接到 Solid Pod 并定义表结构
  • 使用 Drizzle 风格 API 执行插入、查询、更新、删除
  • 如何查看生成的 SPARQL 语句与本地回放逻辑

📖 详细文档

表定义

import { podTable, string, int, boolean, date, uri, eq, gte, and } from 'drizzle-solid';

const userTable = podTable('users', {
  name: string('name'),           // foaf:name
  email: string('email'),         // foaf:mbox
  age: int('age'),               // foaf:age
  verified: boolean('verified'),     // 自定义谓词
  createdAt: date('createdAt'),   // dcterms:created
  organization: uri('organization')
    .predicate('https://schema.org/member') // <org> schema:member <person>
    .inverse()
}, {
  // 目标 Turtle 资源,必填,可以是相对 Pod 路径或绝对 URL
  base: 'data/users.ttl',
  // 主体类型
  type: 'https://schema.org/Person',
  // 可选:注册 TypeIndex(仅在提供 typeIndex 时才会尝试)
  typeIndex: 'private' // 'public' | 'private' | undefined
});

// 初始化:在 CRUD 前确保容器/资源存在(会按 base 自动创建)
await db.init([userTable]);
  • 使用 .inverse() 可以把列映射为 <object> predicate <subject> 方向,适合例如 <org> schema:member <person> 这样的反向边;查询/写入都会自动交换 RDF 三元组的主体和宾语。

Drizzle 风格查询:db.query + findByIRI

将 schema 传入 drizzle(session, { schema }) 后,可通过 Drizzle 对齐的查询助手调用:

import * as schema from './schema';
const db = drizzle(session, { schema });

const users = await db.query.users.findMany({
  where: { verified: true },
  orderBy: [{ column: schema.users.name, direction: 'asc' }],
  with: {
    posts: true // 根据子表 referenceTarget + @id 预加载关联行
  }
});

const alice = await db.query.users.findByIRI('https://pod.example/data/users.ttl#alice');
  • findMany/findFirst/findById/count 与 Drizzle ORM 行为一致,复用现有 select 管道。
  • with 支持基于 reference(target) 的引用外键(通过 @id 关联),结果会嵌套数组挂在相应键上。
  • findByIRI 可直接接受绝对 IRI 或 fragment(无协议时按 id 匹配)。
  • TypeIndex 注册策略:仅当表配置了 typeIndex: 'private' | 'public' 时才会尝试写入 TypeIndex;未配置则跳过。

支持的列类型

Drizzle Solid 完全兼容所有 Drizzle ORM 数据库方言的列类型:

基础类型

// 字符串类型
string('name')     // 通用字符串
text('content')    // 文本内容
varchar('title')   // 可变长度字符串
char('code')       // 固定长度字符串

// 数字类型
int('count')       // MySQL 风格整数
integer('id')      // PostgreSQL 风格整数
bigint('large')    // 大整数
smallint('small')  // 小整数
tinyint('tiny')    // 微整数 (MySQL)
mediumint('medium') // 中等整数 (MySQL)
serial('auto')     // 自增序列

// 浮点数类型
real('price')      // 实数
decimal('amount')  // 十进制数
numeric('value')   // 数值
float('ratio')     // 单精度浮点
double('precise')  // 双精度浮点

// 布尔类型
boolean('active')  // 布尔值

// 日期时间类型
date('birthday')   // 日期
datetime('event')  // 日期时间
timestamp('created') // 时间戳

// JSON 类型
json('data')       // JSON 数据
jsonb('config')    // 二进制 JSON (PostgreSQL)
object('metadata') // 对象类型 (扩展)

查询操作

// 查询所有记录
const users = await db.select().from(userTable);

// 条件查询
const adults = await db.select()
  .from(userTable)
  .where(gte(userTable.age, 18));

// 选择特定字段
const names = await db.select({ name: userTable.name })
  .from(userTable);

// 使用条件构建器
const verifiedAdults = await db.select()
  .from(userTable)
  .where(and(gte(userTable.age, 18), eq(userTable.verified, true)));

// 排序、分页查询
const recentUsers = await db.select()
  .from(userTable)
  .orderBy(userTable.createdAt, 'desc') // 默认升序,可显式指定 'desc'
  .limit(10)  // 取前 10 条
  .offset(10); // 跳过前 10 条,实现分页

// DISTINCT 查询,去重后返回唯一记录
const uniqueEmails = await db.select({ email: userTable.email })
  .from(userTable)
  .distinct();

聚合查询

import { count, max } from 'drizzle-solid';

const stats = await db
  .select({
    totalUsers: count(),
    oldestAge: max(userTable.age)
  })
  .from(userTable)
  .where(gte(userTable.age, 18));

console.log(stats[0]);
// { totalUsers: 42, oldestAge: 63 }

当前聚合支持 count/sum/avg/min/max,由客户端在内存中计算,选择列表需全部为聚合字段;JOINGROUP BY 亦已通过客户端回放实现(在 CSS 升级至最新 Comunica 前仍保留此策略)。

插入数据

// 插入单条记录
await db.insert(userTable).values({
  name: 'Bob',
  email: '[email protected]',
  age: 25
});

// 批量插入
await db.insert(userTable).values([
  { name: 'Alice', email: '[email protected]', age: 30 },
  { name: 'Charlie', email: '[email protected]', age: 35 }
]);

更新数据

await db.update(userTable)
  .set({ age: 26 })
  .where(eq(userTable.name, 'Bob'));

删除数据

await db.delete(userTable)
  .where(eq(userTable.name, 'Bob'));

✅ 当前 SQL 支持范围

  • 已实现:select/insert/update/delete、Drizzle 风格的 where 条件构建器(eq/ne/lt/gte/like/in/not 等)、orderBylimit/offsetdistinct、嵌套布尔组合,以及基于本地回放的 count/sum/avg/min/max 聚合、JOINGROUP BY
  • 运行策略:聚合、JOINGROUP BY 会先获取符合条件的行,再在内存中完成聚合/联结,避免依赖当前 CSS (Comunica v2) 缺失的 SPARQL 1.1 聚合与联结实现;后续待 CSS 升级后可切回原生支持。
  • 未覆盖:HAVING、窗口函数、UNION/UNION ALL、子查询与跨容器联结;如需这些能力,请暂时改用手写 SPARQL 或拆分查询。

🗺️ Roadmap

  • rightJoin/fullJoin 原生支持: 完成查询构建器、SPARQL 转换与 fallback 扩展,详见设计方案
  • SPARQL Endpoint 直连模式: 为纯端点跳过 LDP 探测并持续支持 CRUD,详见设计方案

🔧 配置

自定义命名空间

Drizzle Solid 不再内置 vocab 常量,请从 RDF vocab 库(例如 @inrupt/vocab-common-rdf)导入需要的术语;若需要扩展缺失字段,可使用 extendNamespace

import { podTable, string, extendNamespace } from 'drizzle-solid';
import { SCHEMA_INRUPT as SCHEMA } from '@inrupt/vocab-common-rdf';

const LINQ = extendNamespace(
  { prefix: 'linq', uri: 'https://linq.dev/ns/' },
  { favorite: 'profile#favorite' },
  { namespace: 'https://linq.dev/ns/' }
);

const customTable = podTable('custom', {
  title: string('title').predicate(`${SCHEMA.NAMESPACE}title`),
  favorite: string('favorite').predicate(LINQ.favorite)
}, {
  base: 'idp:///custom/index.ttl', // 目标资源
  type: `${SCHEMA.NAMESPACE}CreativeWork`,
  namespace: LINQ
});

认证配置

import { Session } from '@inrupt/solid-client-authn-node';

const session = new Session();
await session.login({
  oidcIssuer: 'https://solidcommunity.net',
  redirectUrl: 'http://localhost:3000/callback',
  clientName: 'My Solid App'
});

const db = drizzle(session);

base / @id / id 与 Pod 根的关系

  • podUrl(Pod 根)由 WebID 推导,所有相对路径都基于它解析。
  • base 是表的目标 Turtle 资源(必填),可为相对路径或绝对 URL。示例:base: '/data/contacts.ttl'https://pod.example/data/contacts.ttl
  • 写入:
    • 提供 @id 则直接作为 subject;
    • 提供 id(或库自动生成)则 subject 形如 base#<id>(或按表的 subject 模板选择 #//)。
  • 查询:
    • where({ '@id': 'https://…#foo' }) 精确匹配该 subject;
    • where({ id: 'foo' }) 匹配 fragment 为 foo 且落在该表 base 下的 subject。
  • base 同时决定存储地址(PUT/PATCH 目标)和 subject 生成;podUrl 只负责解析相对的 base

🏗️ 架构

Drizzle Solid基于以下组件构建:

  • PodDialect: Solid Pod的Drizzle方言实现
  • SPARQL转换器: 将Drizzle查询转换为SPARQL
  • Comunica执行器: 执行SPARQL查询
  • 类型系统: 完整的TypeScript类型支持

Comunica CRUD 流程

  • 查询会经过 AST → SPARQL 转换;若 Comunica v2 无法执行带过滤器/聚合的 UPDATE/DELETE,方言会先通过 SELECT 拉取命中的 subject,再以 PATCH 方式回写,实现与 SQL 行级操作一致的语义。
  • PodDialect 会自动推导目标容器与 .ttl 资源文件路径,必要时发送 HEAD/PUT 请求确保容器和资源已经存在,再交由 Comunica 处理数据修改。
  • 插入会预先读取现有资源以检测重复 subject,避免重复写入;删除或更新只针对匹配的 subject 生成最小化补丁。
  • 对于 JOINGROUP BY 与聚合,选取的数据仍由 SPARQL 拉取,但结果会在内存中组合或聚合,直到 CSS 升级到支持完整 SPARQL 1.1 为止。

🤝 贡献

欢迎贡献代码!请阅读 CONTRIBUTING.md 了解测试要求、提交流程与验证内容。

在提交 PR 之前,请同步运行完整的 CSS 集成测试(覆盖 CRUD、TypeIndex 等场景):

SOLID_ENABLE_REAL_TESTS=true npx vitest run tests/integration/css --runInBand

SOLID_ENABLE_REAL_TESTS=true 会启用真实 Pod,--runInBand 保证所有 suite 共用一个会话并顺序执行,避免对 OIDC 服务造成并发压力。

📄 许可证

MIT License - 查看LICENSE文件了解详情。

🔗 相关链接

📞 支持

如果遇到问题,可先查阅:

  1. docs/quick-start-local.md 获取本地 CSS 启动与疑难解答
  2. examples/README.md 了解脚本入口与运行方式
  3. Issue 列表 提交复现步骤与日志

开始您的 Solid 数据之旅! 🚀