imean-cassandra-orm 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. package/README.md +190 -280
  2. package/dist/mod.cjs +643 -422
  3. package/dist/mod.d.cts +169 -250
  4. package/dist/mod.d.ts +169 -250
  5. package/dist/mod.js +641 -418
  6. package/package.json +63 -63
package/README.md CHANGED
@@ -1,6 +1,17 @@
1
- # Cassandra ORM
1
+ # IMean Cassandra ORM
2
2
 
3
- 一个简单易用的 Cassandra ORM 库,提供类型安全的 API。
3
+ 一个基于 Zod 的 Cassandra ORM,提供类型安全的 schema 定义和数据库操作。
4
+
5
+ ## 特性
6
+
7
+ - 🎯 **类型安全**: 基于 Zod 的 schema 验证和 TypeScript 类型推断,确保入库和出库的数据类型安全。
8
+ - 🔧 **自动 CQL 生成**: 自动生成 Cassandra CQL 语句,无需手写。
9
+ - 📊 **完整 CRUD**: 支持插入、查询、更新、删除操作。
10
+ - 🚀 **批量操作**: 支持批量插入,提升性能。
11
+ - 🎨 **简洁 API**: 简单易用的 API 设计,通过 Client 统一管理模型。
12
+ - 🔄 **Schema 同步**: 自动创建和同步 keyspace 和表结构。
13
+ - ⚙️ **Keyspace 配置**: 支持自定义复制策略和配置。
14
+ - 🤖 **自动序列化**: 自动处理复杂类型(如 JSON 对象、数组)的序列化和反序列化。
4
15
 
5
16
  ## 安装
6
17
 
@@ -8,341 +19,240 @@
8
19
  npm install imean-cassandra-orm
9
20
  ```
10
21
 
11
- ## 使用示例
12
-
13
- ```typescript
14
- import { Client as CassandraClient, auth } from "cassandra-driver";
15
- import { Client, Field, uuid, type Infer } from "imean-cassandra-orm";
22
+ ## 快速开始
16
23
 
17
- // 创建原始 Cassandra 客户端
18
- const cassandraClient = new CassandraClient({
19
- contactPoints: ["localhost"],
20
- localDataCenter: "datacenter1",
21
- authProvider: new auth.PlainTextAuthProvider("cassandra", "cassandra"),
22
- });
24
+ ### 1. 定义 Schema
23
25
 
24
- // 创建我们的客户端
25
- const client = new Client(cassandraClient);
26
+ 使用 `TableSchema` 和 `zod` 来定义你的表结构。ORM 会根据你的定义自动推断类型和生成数据库表。
26
27
 
27
- // 设置 keyspace
28
- await client.useKeyspace("my_keyspace", {
29
- class: "SimpleStrategy",
30
- replication_factor: 1,
28
+ ```typescript
29
+ import { z } from "zod";
30
+ import { TableSchema } from "imean-cassandra-orm";
31
+
32
+ // 定义用户表 schema
33
+ const userSchema = new TableSchema({
34
+ fields: {
35
+ user_id: z.string(),
36
+ email: z.string().email(),
37
+ name: z.string(),
38
+ age: z.number().int().positive(),
39
+ is_active: z.boolean(),
40
+ metadata: z.record(z.any()), // 可以是任意复杂的对象
41
+ created_at: z.date(),
42
+ },
43
+ partitionKey: ["user_id"],
44
+ tableName: "users",
45
+ keyspace: "my_app_keyspace",
31
46
  });
32
47
 
33
- // 创建用户模型
34
- const userModel = client.createModel(
35
- {
36
- id: Field.uuid().partitionKey(), // 分区键,必填
37
- name: Field.text().clusteringKey(), // 聚类键,必填
38
- age: Field.int().optional(), // 可选字段
39
- email: Field.text(), // 普通字段,必填
48
+ // 定义帖子表 schema(带聚类键)
49
+ const postSchema = new TableSchema({
50
+ fields: {
51
+ user_id: z.string(),
52
+ post_id: z.string(),
53
+ title: z.string(),
54
+ content: z.string(),
55
+ tags: z.array(z.string()), // 数组类型
56
+ published_at: z.date(),
40
57
  },
41
- "users"
42
- );
43
- type User = Infer<typeof userModel>;
58
+ partitionKey: ["user_id"],
59
+ clusteringKey: { post_id: "desc" }, // 按 post_id 降序
60
+ tableName: "posts",
61
+ keyspace: "my_app_keyspace",
62
+ });
63
+ ```
44
64
 
45
- // 创建文章模型
46
- const postModel = client.createModel(
47
- {
48
- id: Field.uuid().partitionKey(), // 分区键,必填
49
- title: Field.text(), // 普通字段,必填
50
- content: Field.text(), // 普通字段,必填
51
- created_at: Field.timestamp(), // 普通字段,必填
52
- },
53
- "posts"
54
- );
65
+ ### 2. 创建 Client 和模型
55
66
 
56
- // 同步所有表结构
57
- await client.syncAllSchemas();
67
+ `Client` 是所有操作的入口。你需要先创建一个 `Client` 实例,然后用它来创建 `Model` 实例。
58
68
 
59
- // 创建用户
60
- const user = await userModel.create({
61
- id: uuid(), // 必须提供分区键
62
- name: "张三", // 必须提供聚类键
63
- age: 25, // 可选
64
- email: "zhangsan@example.com" // 必填
65
- });
69
+ ```typescript
70
+ import { Client } from "imean-cassandra-orm";
66
71
 
67
- // 创建文章
68
- await postModel.create({
69
- id: uuid(),
70
- title: "测试文章",
71
- content: "内容",
72
- created_at: new Date(),
72
+ // 1. 创建 Client 实例
73
+ const client = new Client({
74
+ contactPoints: ["localhost"],
75
+ localDataCenter: "datacenter1",
73
76
  });
74
77
 
75
- // 查询用户
76
- const foundUser = await userModel.findOne({ id: user.id });
77
- console.log(foundUser);
78
+ // 2. 连接到 Cassandra
79
+ await client.connect();
78
80
 
79
- // 关闭连接
80
- await client.close();
81
- ```
81
+ // 3. (可选) 删除旧的 keyspace 以进行全新测试
82
+ await client.dropKeyspace("my_app_keyspace");
82
83
 
83
- ## API 参考
84
+ // 4. 通过 Client 创建模型实例
85
+ const userModel = client.createModel(userSchema);
86
+ const postModel = client.createModel(postSchema);
84
87
 
85
- ### 类型推导工具
88
+ // 5. 同步所有模型的 Schema
89
+ // 这会确保 keyspace 和所有相关的表都已创建
90
+ await client.syncSchema();
91
+ ```
86
92
 
87
- ORM 库提供了一系列类型推导工具,可以帮助开发者在上层 API 中实现类型安全。
93
+ `client.syncSchema()` 会自动处理所有已创建模型的 schema 同步,你不再需要为每个模型单独调用 `syncSchema()`。
88
94
 
89
- #### Infer<M>
95
+ ### 3. 基本操作
90
96
 
91
- Model 实例中推断出原始字段类型。
97
+ 模型实例提供了完整的 CRUD API。
92
98
 
93
99
  ```typescript
94
- const userModel = client.createModel({
95
- id: Field.uuid().partitionKey(),
96
- name: Field.text().clusteringKey(),
97
- age: Field.int().optional(),
98
- email: Field.text(),
99
- }, "users");
100
-
101
- type User = Infer<typeof userModel>;
102
- // 推导结果: { id: string; name: string; age?: number; email: string }
103
- ```
100
+ // 插入数据 (自动序列化 metadata 对象)
101
+ const userData = {
102
+ user_id: "user1",
103
+ email: "user1@example.com",
104
+ name: "张三",
105
+ age: 25,
106
+ is_active: true,
107
+ metadata: { score: 95.5, level: 3, tags: ["vip"] },
108
+ created_at: new Date(),
109
+ };
110
+ await userModel.insert(userData);
104
111
 
105
- #### InferPartitionKey<M>
112
+ // 查询数据 (自动反序列化 metadata)
113
+ const foundUser = await userModel.findOne({ user_id: "user1" });
114
+ console.log(foundUser?.metadata); // { score: 95.5, level: 3, tags: ["vip"] }
106
115
 
107
- 推导分区键字段类型。
116
+ // 更新数据
117
+ await userModel.update(
118
+ { user_id: "user1" },
119
+ { name: "李四", age: 26 }
120
+ );
108
121
 
109
- ```typescript
110
- type UserPartitionKey = InferPartitionKey<typeof userModel>;
111
- // 推导结果: { id: string }
122
+ // 删除数据
123
+ await userModel.delete({ user_id: "user1" });
112
124
  ```
113
125
 
114
- #### InferClusteringKey<M>
115
-
116
- 推导聚类键字段类型。
126
+ ### 4. 批量操作
117
127
 
118
128
  ```typescript
119
- type UserClusteringKey = InferClusteringKey<typeof userModel>;
120
- // 推导结果: { name: string }
129
+ // 批量插入
130
+ const usersData = [
131
+ {
132
+ user_id: "user2",
133
+ email: "user2@example.com",
134
+ name: "王五",
135
+ age: 30,
136
+ is_active: true,
137
+ metadata: { score: 88.0 },
138
+ created_at: new Date(),
139
+ },
140
+ {
141
+ user_id: "user3",
142
+ email: "user3@example.com",
143
+ name: "赵六",
144
+ age: 28,
145
+ is_active: false,
146
+ metadata: { score: 79.2 },
147
+ created_at: new Date(),
148
+ },
149
+ ];
150
+ await userModel.batchInsert(usersData);
121
151
  ```
122
152
 
123
- #### InferNonPartitionFields<S>
153
+ ### 5. 使用聚类键
124
154
 
125
- 推导非分区键字段类型。
155
+ 当你的 schema 定义了 `clusteringKey` 时,你可以在查询、更新和删除操作中指定它。
126
156
 
127
157
  ```typescript
128
- type UserFields = {
129
- id: { type: "uuid"; flags: { partitionKey: true } };
130
- name: { type: "text" };
131
- age: { type: "int" };
158
+ // 插入带聚类键的数据
159
+ const postData = {
160
+ user_id: "user1",
161
+ post_id: "post1",
162
+ title: "我的第一篇帖子",
163
+ content: "这是内容...",
164
+ tags: ["技术", "编程"],
165
+ published_at: new Date(),
132
166
  };
167
+ await postModel.insert(postData);
168
+
169
+ // 查询特定帖子
170
+ const post = await postModel.findOne(
171
+ { user_id: "user1" },
172
+ { post_id: "post1" } // 指定聚类键
173
+ );
174
+
175
+ // 更新特定帖子
176
+ await postModel.update(
177
+ { user_id: "user1" },
178
+ { title: "更新后的标题" },
179
+ { post_id: "post1" } // 指定聚类键
180
+ );
133
181
 
134
- type NonPartitionFields = InferNonPartitionFields<UserFields>;
135
- // 推导结果: { name: string; age: number }
182
+ // 删除特定帖子
183
+ await postModel.delete(
184
+ { user_id: "user1" },
185
+ { post_id: "post1" } // 指定聚类键
186
+ );
136
187
  ```
137
188
 
138
- #### InferStaticFields<M>
189
+ ### 6. 类型安全与校验
139
190
 
140
- 推导静态字段类型。
191
+ ORM 会在数据返回时使用 Zod schema 进行校验,确保你得到的数据是类型安全的。
141
192
 
142
193
  ```typescript
143
- const userModel = client.createModel({
144
- id: Field.uuid().partitionKey(),
145
- name: Field.text().static(), // 静态字段
146
- age: Field.int().optional(),
147
- email: Field.text(),
148
- }, "users");
149
-
150
- type UserStaticFields = InferStaticFields<typeof userModel>;
151
- // 推导结果: { name: string }
194
+ // 从数据库返回的数据
195
+ const userFromDb = await userModel.findOne({ user_id: "user1" });
196
+
197
+ // userFromDb 的类型是根据 userSchema 推断出来的,具有完整的类型提示
198
+ if (userFromDb) {
199
+ console.log(userFromDb.name.toUpperCase());
200
+ }
201
+
202
+ // 如果数据库中的数据不符合 schema(例如,手动修改了数据库),
203
+ // 在查询时会抛出 ZodError,防止脏数据进入你的应用。
152
204
  ```
153
205
 
154
- 静态列的特点:
155
- 1. 在分区级别存储数据,而不是在每一行重复存储
156
- 2. 适用于在分区内所有行共享相同值的字段
157
- 3. 可以节省存储空间
158
- 4. 提高查询效率
206
+ ## API 参考
207
+
208
+ ### `Client`
159
209
 
160
- 这些类型工具可以用于:
210
+ #### `new Client(options: cassandra.ClientOptions)`
161
211
 
162
- 1. API 层定义类型安全的接口
163
- 2. 在服务层进行类型检查
164
- 3. 在数据访问层确保数据一致性
165
- 4. 在业务逻辑层进行类型推导
212
+ 创建一个新的 Client 实例。`options` 与 `cassandra-driver` 的 `ClientOptions` 相同。
166
213
 
167
- ### Client
214
+ #### `client.connect(): Promise<void>`
168
215
 
169
- `Client` 类是对原始 Cassandra 客户端的封装,提供了更高级的 API。
216
+ 连接到 Cassandra 集群。
170
217
 
171
- #### 构造函数
218
+ #### `client.createModel(schema: TableSchema): Model`
172
219
 
173
- ```typescript
174
- constructor(cassandraClient: CassandraClient)
175
- ```
220
+ 根据 `TableSchema` 创建一个 `Model` 实例。
176
221
 
177
- #### 方法
222
+ #### `client.syncSchema(): Promise<void>`
178
223
 
179
- - `useKeyspace(keyspace: string, options: KeyspaceOptions): Promise<void>`
180
- - 设置当前使用的 keyspace,如果不存在则创建
181
- - `options`:
182
- - `class`: 复制策略,可以是 `"SimpleStrategy"` 或 `"NetworkTopologyStrategy"`
183
- - `replication_factor`: 复制因子(仅用于 `SimpleStrategy`)
184
- - `datacenters`: 数据中心配置(仅用于 `NetworkTopologyStrategy`)
224
+ 同步所有通过 `createModel` 创建的模型的 schema。
185
225
 
186
- - `createModel<S extends Record<string, FieldConfig<keyof TypeMap>>>(schema: S, tableName: string): Model<S>`
187
- - 创建并注册一个新的模型
188
- - 返回的模型实例可以直接使用,不需要额外获取
226
+ #### `client.dropKeyspace(keyspace: string): Promise<void>`
189
227
 
190
- - `syncAllSchemas(): Promise<void>`
191
- - 同步所有已注册模型的结构到数据库
228
+ 删除一个 keyspace。
192
229
 
193
- - `close(): Promise<void>`
194
- - 关闭数据库连接
230
+ ### `Model`
195
231
 
196
- ### Model
232
+ #### `model.insert(data: T): Promise<void>`
197
233
 
198
- `Model` 类提供了对数据库表的操作接口。
234
+ 插入一条数据。
199
235
 
200
- #### 类型推导示例
236
+ #### `model.batchInsert(data: T[]): Promise<void>`
201
237
 
202
- ```typescript
203
- // 定义模型 schema
204
- const userModel = client.createModel({
205
- id: Field.uuid().partitionKey(), // 分区键,必填
206
- name: Field.text().clusteringKey(), // 聚类键,必填
207
- age: Field.int().optional(), // 可选字段
208
- email: Field.text(), // 普通字段,必填
209
- }, "users");
210
-
211
- // 推导出的类型:
212
- type User = {
213
- id: string; // 分区键,必填
214
- name: string; // 聚类键,必填
215
- age?: number; // 可选字段
216
- email: string; // 普通字段,必填
217
- };
238
+ 批量插入多条数据。
218
239
 
219
- // 分区键类型(用于查询和删除)
220
- type PartitionKey = {
221
- id: string;
222
- };
240
+ #### `model.find(partitionKey: PK): Promise<T[]>`
223
241
 
224
- // 聚类键类型(用于查询和删除)
225
- type ClusteringKey = {
226
- name: string;
227
- };
242
+ 根据分区键查询多条数据。
228
243
 
229
- // 可更新字段类型(用于更新操作)
230
- type UpdatableFields = {
231
- age?: number; // 可选字段
232
- email: string; // 普通字段
233
- };
234
- ```
244
+ #### `model.findOne(partitionKey: PK, clusteringKey?: CK): Promise<T | null>`
245
+
246
+ 根据分区键和(可选的)聚类键查询单条数据。
247
+
248
+ #### `model.update(partitionKey: PK, data: Partial<T>, clusteringKey?: CK): Promise<void>`
249
+
250
+ 更新一条数据。
251
+
252
+ #### `model.delete(partitionKey: PK, clusteringKey?: CK): Promise<void>`
253
+
254
+ 删除一条数据。
235
255
 
236
- #### 方法
237
-
238
- - `create(data: User): Promise<void>`
239
- - 创建新记录
240
- - 示例:
241
- ```typescript
242
- await userModel.create({
243
- id: uuid(), // 必须提供分区键
244
- name: "张三", // 必须提供聚类键
245
- age: 25, // 可选
246
- email: "zhangsan@example.com" // 必填
247
- });
248
- ```
249
-
250
- - `findOne(partitionKey: PartitionKey, clusteringKey?: Partial<ClusteringKey>): Promise<User | null>`
251
- - 查询单条记录
252
- - 示例:
253
- ```typescript
254
- // 只使用分区键查询
255
- const user = await userModel.findOne({ id: "some-uuid" });
256
-
257
- // 使用分区键和聚类键查询
258
- const user = await userModel.findOne(
259
- { id: "some-uuid" },
260
- { name: "张三" }
261
- );
262
- ```
263
-
264
- - `findAll(partitionKey: PartitionKey, clusteringKey?: Partial<ClusteringKey>): Promise<User[]>`
265
- - 查询多条记录
266
- - 参数同 `findOne`
267
-
268
- - `update(partitionKey: PartitionKey, data: Partial<UpdatableFields>): Promise<void>`
269
- - 更新记录
270
- - 示例:
271
- ```typescript
272
- await userModel.update(
273
- { id: "some-uuid" }, // 必须提供分区键
274
- {
275
- age: 26, // 可以更新可选字段
276
- email: "new@example.com" // 可以更新普通字段
277
- }
278
- );
279
- ```
280
-
281
- - `delete(partitionKey: PartitionKey, clusteringKey?: Partial<ClusteringKey>): Promise<void>`
282
- - 删除记录
283
- - 参数同 `findOne`
284
-
285
- ### Field
286
-
287
- `Field` 类提供了创建字段配置的方法。
288
-
289
- #### 静态方法
290
-
291
- - `uuid(): FieldBuilder<"uuid">`
292
- - `text(): FieldBuilder<"text">`
293
- - `int(): FieldBuilder<"int">`
294
- - `bigint(): FieldBuilder<"bigint">`
295
- - `float(): FieldBuilder<"float">`
296
- - `double(): FieldBuilder<"double">`
297
- - `boolean(): FieldBuilder<"boolean">`
298
- - `timestamp(): FieldBuilder<"timestamp">`
299
-
300
- #### 实例方法
301
-
302
- - `partitionKey(): this`
303
- - 将字段设置为分区键
304
- - 分区键字段是必填的,不能为 null
305
- - 示例:
306
- ```typescript
307
- Field.uuid().partitionKey() // 将 UUID 字段设置为分区键
308
- ```
309
-
310
- - `clusteringKey(order?: "ASC" | "DESC"): this`
311
- - 将字段设置为聚类键
312
- - 聚类键字段是否必填取决于是否设置了 `optional()`
313
- - `order` 可选,指定排序方向
314
- - 示例:
315
- ```typescript
316
- Field.text().clusteringKey() // 默认升序,必填
317
- Field.text().clusteringKey("DESC") // 降序,必填
318
- Field.text().clusteringKey().optional() // 可选聚类键
319
- ```
320
-
321
- - `optional(): this`
322
- - 将字段设置为可选
323
- - 可以用于任何类型的字段,包括聚类键
324
- - 示例:
325
- ```typescript
326
- Field.int().optional() // 将整数字段设置为可选
327
- ```
328
-
329
- - `static(): this`
330
- - 将字段设置为静态列
331
- - 静态列在分区级别存储,而不是在每一行重复存储
332
- - 适用于在分区内所有行共享相同值的字段
333
- - 示例:
334
- ```typescript
335
- Field.text().static() // 将文本字段设置为静态列
336
- ```
337
-
338
- ## 注意事项
339
-
340
- 1. 所有模型必须在调用 `syncAllSchemas` 之前创建
341
- 2. 创建记录时必须提供所有分区键字段
342
- 3. 聚类键字段是否必填取决于是否设置了 `optional()`
343
- 4. 更新记录时可以更新非分区键字段(包括聚类键和普通字段)
344
- 5. 删除记录时必须提供所有分区键字段
345
-
346
- ## 许可证
347
-
348
- MIT
256
+ ---
257
+ 贡献者:imean
258
+ License: MIT