imean-cassandra-orm 1.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.
package/README.md ADDED
@@ -0,0 +1,317 @@
1
+ # Cassandra ORM
2
+
3
+ 一个简单易用的 Cassandra ORM 库,提供类型安全的 API。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install imean-cassandra-orm
9
+ ```
10
+
11
+ ## 使用示例
12
+
13
+ ```typescript
14
+ import { Client as CassandraClient, auth } from "cassandra-driver";
15
+ import { Client, Field, uuid, type Infer } from "imean-cassandra-orm";
16
+
17
+ // 创建原始 Cassandra 客户端
18
+ const cassandraClient = new CassandraClient({
19
+ contactPoints: ["localhost"],
20
+ localDataCenter: "datacenter1",
21
+ authProvider: new auth.PlainTextAuthProvider("cassandra", "cassandra"),
22
+ });
23
+
24
+ // 创建我们的客户端
25
+ const client = new Client(cassandraClient);
26
+
27
+ // 设置 keyspace
28
+ await client.useKeyspace("my_keyspace", {
29
+ class: "SimpleStrategy",
30
+ replication_factor: 1,
31
+ });
32
+
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(), // 普通字段,必填
40
+ },
41
+ "users"
42
+ );
43
+ type User = Infer<typeof userModel>;
44
+
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
+ );
55
+
56
+ // 同步所有表结构
57
+ await client.syncAllSchemas();
58
+
59
+ // 创建用户
60
+ const user = await userModel.create({
61
+ id: uuid(), // 必须提供分区键
62
+ name: "张三", // 必须提供聚类键
63
+ age: 25, // 可选
64
+ email: "zhangsan@example.com" // 必填
65
+ });
66
+
67
+ // 创建文章
68
+ await postModel.create({
69
+ id: uuid(),
70
+ title: "测试文章",
71
+ content: "内容",
72
+ created_at: new Date(),
73
+ });
74
+
75
+ // 查询用户
76
+ const foundUser = await userModel.findOne({ id: user.id });
77
+ console.log(foundUser);
78
+
79
+ // 关闭连接
80
+ await client.close();
81
+ ```
82
+
83
+ ## API 参考
84
+
85
+ ### 类型推导工具
86
+
87
+ ORM 库提供了一系列类型推导工具,可以帮助开发者在上层 API 中实现类型安全。
88
+
89
+ #### Infer<M>
90
+
91
+ 从 Model 实例中推断出原始字段类型。
92
+
93
+ ```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
+ ```
104
+
105
+ #### InferPartitionKey<M>
106
+
107
+ 推导分区键字段类型。
108
+
109
+ ```typescript
110
+ type UserPartitionKey = InferPartitionKey<typeof userModel>;
111
+ // 推导结果: { id: string }
112
+ ```
113
+
114
+ #### InferClusteringKey<M>
115
+
116
+ 推导聚类键字段类型。
117
+
118
+ ```typescript
119
+ type UserClusteringKey = InferClusteringKey<typeof userModel>;
120
+ // 推导结果: { name: string }
121
+ ```
122
+
123
+ #### InferNonPartitionFields<S>
124
+
125
+ 推导非分区键字段类型。
126
+
127
+ ```typescript
128
+ type UserFields = {
129
+ id: { type: "uuid"; flags: { partitionKey: true } };
130
+ name: { type: "text" };
131
+ age: { type: "int" };
132
+ };
133
+
134
+ type NonPartitionFields = InferNonPartitionFields<UserFields>;
135
+ // 推导结果: { name: string; age: number }
136
+ ```
137
+
138
+ 这些类型工具可以用于:
139
+
140
+ 1. 在 API 层定义类型安全的接口
141
+ 2. 在服务层进行类型检查
142
+ 3. 在数据访问层确保数据一致性
143
+ 4. 在业务逻辑层进行类型推导
144
+
145
+ ### Client
146
+
147
+ `Client` 类是对原始 Cassandra 客户端的封装,提供了更高级的 API。
148
+
149
+ #### 构造函数
150
+
151
+ ```typescript
152
+ constructor(cassandraClient: CassandraClient)
153
+ ```
154
+
155
+ #### 方法
156
+
157
+ - `useKeyspace(keyspace: string, options: KeyspaceOptions): Promise<void>`
158
+ - 设置当前使用的 keyspace,如果不存在则创建
159
+ - `options`:
160
+ - `class`: 复制策略,可以是 `"SimpleStrategy"` 或 `"NetworkTopologyStrategy"`
161
+ - `replication_factor`: 复制因子(仅用于 `SimpleStrategy`)
162
+ - `datacenters`: 数据中心配置(仅用于 `NetworkTopologyStrategy`)
163
+
164
+ - `createModel<S extends Record<string, FieldConfig<keyof TypeMap>>>(schema: S, tableName: string): Model<S>`
165
+ - 创建并注册一个新的模型
166
+ - 返回的模型实例可以直接使用,不需要额外获取
167
+
168
+ - `syncAllSchemas(): Promise<void>`
169
+ - 同步所有已注册模型的结构到数据库
170
+
171
+ - `close(): Promise<void>`
172
+ - 关闭数据库连接
173
+
174
+ ### Model
175
+
176
+ `Model` 类提供了对数据库表的操作接口。
177
+
178
+ #### 类型推导示例
179
+
180
+ ```typescript
181
+ // 定义模型 schema
182
+ const userModel = client.createModel({
183
+ id: Field.uuid().partitionKey(), // 分区键,必填
184
+ name: Field.text().clusteringKey(), // 聚类键,必填
185
+ age: Field.int().optional(), // 可选字段
186
+ email: Field.text(), // 普通字段,必填
187
+ }, "users");
188
+
189
+ // 推导出的类型:
190
+ type User = {
191
+ id: string; // 分区键,必填
192
+ name: string; // 聚类键,必填
193
+ age?: number; // 可选字段
194
+ email: string; // 普通字段,必填
195
+ };
196
+
197
+ // 分区键类型(用于查询和删除)
198
+ type PartitionKey = {
199
+ id: string;
200
+ };
201
+
202
+ // 聚类键类型(用于查询和删除)
203
+ type ClusteringKey = {
204
+ name: string;
205
+ };
206
+
207
+ // 可更新字段类型(用于更新操作)
208
+ type UpdatableFields = {
209
+ age?: number; // 可选字段
210
+ email: string; // 普通字段
211
+ };
212
+ ```
213
+
214
+ #### 方法
215
+
216
+ - `create(data: User): Promise<void>`
217
+ - 创建新记录
218
+ - 示例:
219
+ ```typescript
220
+ await userModel.create({
221
+ id: uuid(), // 必须提供分区键
222
+ name: "张三", // 必须提供聚类键
223
+ age: 25, // 可选
224
+ email: "zhangsan@example.com" // 必填
225
+ });
226
+ ```
227
+
228
+ - `findOne(partitionKey: PartitionKey, clusteringKey?: Partial<ClusteringKey>): Promise<User | null>`
229
+ - 查询单条记录
230
+ - 示例:
231
+ ```typescript
232
+ // 只使用分区键查询
233
+ const user = await userModel.findOne({ id: "some-uuid" });
234
+
235
+ // 使用分区键和聚类键查询
236
+ const user = await userModel.findOne(
237
+ { id: "some-uuid" },
238
+ { name: "张三" }
239
+ );
240
+ ```
241
+
242
+ - `findAll(partitionKey: PartitionKey, clusteringKey?: Partial<ClusteringKey>): Promise<User[]>`
243
+ - 查询多条记录
244
+ - 参数同 `findOne`
245
+
246
+ - `update(partitionKey: PartitionKey, data: Partial<UpdatableFields>): Promise<void>`
247
+ - 更新记录
248
+ - 示例:
249
+ ```typescript
250
+ await userModel.update(
251
+ { id: "some-uuid" }, // 必须提供分区键
252
+ {
253
+ age: 26, // 可以更新可选字段
254
+ email: "new@example.com" // 可以更新普通字段
255
+ }
256
+ );
257
+ ```
258
+
259
+ - `delete(partitionKey: PartitionKey, clusteringKey?: Partial<ClusteringKey>): Promise<void>`
260
+ - 删除记录
261
+ - 参数同 `findOne`
262
+
263
+ ### Field
264
+
265
+ `Field` 类提供了创建字段配置的方法。
266
+
267
+ #### 静态方法
268
+
269
+ - `uuid(): FieldBuilder<"uuid">`
270
+ - `text(): FieldBuilder<"text">`
271
+ - `int(): FieldBuilder<"int">`
272
+ - `bigint(): FieldBuilder<"bigint">`
273
+ - `float(): FieldBuilder<"float">`
274
+ - `double(): FieldBuilder<"double">`
275
+ - `boolean(): FieldBuilder<"boolean">`
276
+ - `timestamp(): FieldBuilder<"timestamp">`
277
+
278
+ #### 实例方法
279
+
280
+ - `partitionKey(): this`
281
+ - 将字段设置为分区键
282
+ - 分区键字段是必填的,不能为 null
283
+ - 示例:
284
+ ```typescript
285
+ Field.uuid().partitionKey() // 将 UUID 字段设置为分区键
286
+ ```
287
+
288
+ - `clusteringKey(order?: "ASC" | "DESC"): this`
289
+ - 将字段设置为聚类键
290
+ - 聚类键字段是否必填取决于是否设置了 `optional()`
291
+ - `order` 可选,指定排序方向
292
+ - 示例:
293
+ ```typescript
294
+ Field.text().clusteringKey() // 默认升序,必填
295
+ Field.text().clusteringKey("DESC") // 降序,必填
296
+ Field.text().clusteringKey().optional() // 可选聚类键
297
+ ```
298
+
299
+ - `optional(): this`
300
+ - 将字段设置为可选
301
+ - 可以用于任何类型的字段,包括聚类键
302
+ - 示例:
303
+ ```typescript
304
+ Field.int().optional() // 将整数字段设置为可选
305
+ ```
306
+
307
+ ## 注意事项
308
+
309
+ 1. 所有模型必须在调用 `syncAllSchemas` 之前创建
310
+ 2. 创建记录时必须提供所有分区键字段
311
+ 3. 聚类键字段是否必填取决于是否设置了 `optional()`
312
+ 4. 更新记录时可以更新非分区键字段(包括聚类键和普通字段)
313
+ 5. 删除记录时必须提供所有分区键字段
314
+
315
+ ## 许可证
316
+
317
+ MIT
package/dist/mod.cjs ADDED
@@ -0,0 +1,308 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var cassandraDriver = require('cassandra-driver');
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/index.ts
14
+ var src_exports = {};
15
+ __export(src_exports, {
16
+ Client: () => Client2,
17
+ Field: () => Field,
18
+ FieldBuilder: () => FieldBuilder,
19
+ Model: () => Model,
20
+ uuid: () => uuid
21
+ });
22
+ var Model = class {
23
+ constructor(client, schema, tableName) {
24
+ this.client = client;
25
+ this.schema = schema;
26
+ this.tableName = tableName;
27
+ this.partitionKeys = Object.entries(schema).filter(([_, config]) => config.flags.partitionKey).map(([key]) => key);
28
+ this.clusteringKeys = Object.entries(schema).filter(([_, config]) => config.flags.clusteringKey).map(([key]) => key);
29
+ }
30
+ tableName;
31
+ partitionKeys;
32
+ clusteringKeys;
33
+ // 获取表结构 CQL
34
+ getTableSchema() {
35
+ const columns = Object.entries(this.schema).map(([name, config]) => {
36
+ const type = config.type;
37
+ return `${name} ${type}`;
38
+ }).join(",\n ");
39
+ const primaryKey = this.clusteringKeys.length > 0 ? `((${this.partitionKeys.join(", ")}), ${this.clusteringKeys.join(
40
+ ", "
41
+ )})` : `((${this.partitionKeys.join(", ")}))`;
42
+ const clusteringOrder = this.clusteringKeys.map((key) => {
43
+ const config = this.schema[key];
44
+ if (typeof config.flags.clusteringKey === "object") {
45
+ return `${key} ${config.flags.clusteringKey.order}`;
46
+ }
47
+ return `${key} ASC`;
48
+ }).join(", ");
49
+ return `CREATE TABLE IF NOT EXISTS ${this.tableName} (
50
+ ${columns},
51
+ PRIMARY KEY ${primaryKey}
52
+ )${clusteringOrder ? ` WITH CLUSTERING ORDER BY (${clusteringOrder})` : ""};`;
53
+ }
54
+ // 同步表结构
55
+ async syncSchema() {
56
+ const checkTableQuery = `
57
+ SELECT table_name
58
+ FROM system_schema.tables
59
+ WHERE keyspace_name = ? AND table_name = ?`;
60
+ const result = await this.client.execute(
61
+ checkTableQuery,
62
+ [this.client.keyspace, this.tableName],
63
+ { prepare: true }
64
+ );
65
+ if (result.rows.length === 0) {
66
+ await this.client.execute(this.getTableSchema(), [], { prepare: true });
67
+ } else {
68
+ const getColumnsQuery = `
69
+ SELECT column_name, type
70
+ FROM system_schema.columns
71
+ WHERE keyspace_name = ? AND table_name = ?`;
72
+ const columnsResult = await this.client.execute(
73
+ getColumnsQuery,
74
+ [this.client.keyspace, this.tableName],
75
+ { prepare: true }
76
+ );
77
+ const existingColumns = new Map(
78
+ columnsResult.rows.map((row) => [row.column_name, row.type])
79
+ );
80
+ const newColumns = Object.entries(this.schema).filter(([name]) => !existingColumns.has(name)).map(([name, config]) => {
81
+ const type = config.type;
82
+ return `ALTER TABLE ${this.tableName} ADD ${name} ${type}`;
83
+ });
84
+ for (const query of newColumns) {
85
+ await this.client.execute(query, [], { prepare: true });
86
+ }
87
+ }
88
+ }
89
+ // 构建查询条件
90
+ buildWhereClause(partitionFields, clusteringFields) {
91
+ const conditions = [];
92
+ this.partitionKeys.forEach((key) => {
93
+ const value = partitionFields[key];
94
+ if (value !== void 0) {
95
+ conditions.push(`${key} = ?`);
96
+ }
97
+ });
98
+ if (clusteringFields) {
99
+ this.clusteringKeys.forEach((key) => {
100
+ const value = clusteringFields[key];
101
+ if (value !== void 0) {
102
+ conditions.push(`${key} = ?`);
103
+ }
104
+ });
105
+ }
106
+ return conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
107
+ }
108
+ // 构建查询参数
109
+ buildQueryParams(partitionFields, clusteringFields) {
110
+ const params = [];
111
+ this.partitionKeys.forEach((key) => {
112
+ const value = partitionFields[key];
113
+ if (value !== void 0) {
114
+ params.push(value);
115
+ }
116
+ });
117
+ if (clusteringFields) {
118
+ this.clusteringKeys.forEach((key) => {
119
+ const value = clusteringFields[key];
120
+ if (value !== void 0) {
121
+ params.push(value);
122
+ }
123
+ });
124
+ }
125
+ return params;
126
+ }
127
+ // 将查询结果转换为我们的类型
128
+ convertResultToType(result) {
129
+ const converted = {};
130
+ Object.keys(this.schema).forEach((key) => {
131
+ converted[key] = result[key];
132
+ });
133
+ return converted;
134
+ }
135
+ // 查询方法
136
+ async findAll(partitionFields, clusteringFields) {
137
+ const whereClause = this.buildWhereClause(
138
+ partitionFields,
139
+ clusteringFields
140
+ );
141
+ const params = this.buildQueryParams(partitionFields, clusteringFields);
142
+ const query = `SELECT * FROM ${this.tableName} ${whereClause}`;
143
+ const result = await this.client.execute(query, params, { prepare: true });
144
+ return result.rows.map((row) => this.convertResultToType(row));
145
+ }
146
+ async findOne(partitionFields, clusteringFields) {
147
+ const whereClause = this.buildWhereClause(
148
+ partitionFields,
149
+ clusteringFields
150
+ );
151
+ const params = this.buildQueryParams(partitionFields, clusteringFields);
152
+ const query = `SELECT * FROM ${this.tableName} ${whereClause} LIMIT 1`;
153
+ const result = await this.client.execute(query, params, { prepare: true });
154
+ return result.rows[0] ? this.convertResultToType(result.rows[0]) : null;
155
+ }
156
+ // 添加记录方法
157
+ async create(data) {
158
+ const columns = Object.keys(data).join(", ");
159
+ const placeholders = Object.keys(data).map(() => "?").join(", ");
160
+ const params = Object.values(data);
161
+ const query = `INSERT INTO ${this.tableName} (${columns}) VALUES (${placeholders})`;
162
+ await this.client.execute(query, params, { prepare: true });
163
+ }
164
+ // 更新记录方法
165
+ async update(partitionFields, data) {
166
+ const setClause = Object.keys(data).map((key) => `${key} = ?`).join(", ");
167
+ const whereClause = this.buildWhereClause(partitionFields);
168
+ const params = [
169
+ ...Object.values(data),
170
+ ...this.buildQueryParams(partitionFields)
171
+ ];
172
+ const query = `UPDATE ${this.tableName} SET ${setClause} ${whereClause}`;
173
+ await this.client.execute(query, params, { prepare: true });
174
+ }
175
+ // 删除记录方法
176
+ async delete(partitionFields, clusteringFields) {
177
+ const whereClause = this.buildWhereClause(
178
+ partitionFields,
179
+ clusteringFields
180
+ );
181
+ const params = this.buildQueryParams(partitionFields, clusteringFields);
182
+ const query = `DELETE FROM ${this.tableName} ${whereClause}`;
183
+ await this.client.execute(query, params, { prepare: true });
184
+ }
185
+ };
186
+ var uuid = () => cassandraDriver.types.Uuid.random().toString();
187
+
188
+ // src/client.ts
189
+ var Client2 = class {
190
+ constructor(cassandraClient) {
191
+ this.cassandraClient = cassandraClient;
192
+ }
193
+ models = /* @__PURE__ */ new Map();
194
+ // 使用 keyspace
195
+ async useKeyspace(keyspace, options) {
196
+ const checkKeyspaceQuery = `
197
+ SELECT keyspace_name
198
+ FROM system_schema.keyspaces
199
+ WHERE keyspace_name = ?`;
200
+ const result = await this.cassandraClient.execute(
201
+ checkKeyspaceQuery,
202
+ [keyspace],
203
+ { prepare: true }
204
+ );
205
+ if (result.rows.length === 0) {
206
+ let createKeyspaceQuery = `CREATE KEYSPACE IF NOT EXISTS ${keyspace} WITH REPLICATION = { 'class' : '${options.class}'`;
207
+ if (options.class === "SimpleStrategy") {
208
+ createKeyspaceQuery += `, 'replication_factor' : ${options.replication_factor || 1}`;
209
+ } else if (options.class === "NetworkTopologyStrategy" && options.datacenters) {
210
+ const datacenterConfigs = Object.entries(options.datacenters).map(([dc, rf]) => `'${dc}' : ${rf}`).join(", ");
211
+ createKeyspaceQuery += `, ${datacenterConfigs}`;
212
+ }
213
+ createKeyspaceQuery += " }";
214
+ await this.cassandraClient.execute(createKeyspaceQuery, [], {
215
+ prepare: true
216
+ });
217
+ }
218
+ await this.cassandraClient.execute(`USE ${keyspace}`, [], { prepare: true });
219
+ }
220
+ // 创建模型
221
+ createModel(schema, tableName) {
222
+ const model = new Model(this.cassandraClient, schema, tableName);
223
+ this.models.set(tableName, model);
224
+ return model;
225
+ }
226
+ // 同步所有表结构
227
+ async syncAllSchemas() {
228
+ for (const model of this.models.values()) {
229
+ await model.syncSchema();
230
+ }
231
+ }
232
+ // 获取原始 Cassandra 客户端
233
+ getRawClient() {
234
+ return this.cassandraClient;
235
+ }
236
+ // 关闭连接
237
+ async close() {
238
+ await this.cassandraClient.shutdown();
239
+ }
240
+ };
241
+
242
+ // src/field.ts
243
+ var FieldBuilder = class _FieldBuilder {
244
+ constructor(type, flags = {
245
+ partitionKey: false,
246
+ clusteringKey: false,
247
+ optional: false
248
+ }) {
249
+ this.type = type;
250
+ this.flags = flags;
251
+ }
252
+ partitionKey() {
253
+ const newFlags = {
254
+ ...this.flags,
255
+ partitionKey: true
256
+ };
257
+ return new _FieldBuilder(this.type, newFlags);
258
+ }
259
+ clusteringKey(order) {
260
+ const newFlags = {
261
+ ...this.flags,
262
+ clusteringKey: order ? { order } : true
263
+ };
264
+ return new _FieldBuilder(this.type, newFlags);
265
+ }
266
+ optional() {
267
+ const newFlags = {
268
+ ...this.flags,
269
+ optional: true
270
+ };
271
+ return new _FieldBuilder(this.type, newFlags);
272
+ }
273
+ };
274
+ var createField = (type) => {
275
+ return new FieldBuilder(type);
276
+ };
277
+ var Field = {
278
+ // 文本类型
279
+ text: () => createField("text"),
280
+ uuid: () => createField("uuid"),
281
+ // 数值类型
282
+ int: () => createField("int"),
283
+ bigint: () => createField("bigint"),
284
+ varint: () => createField("varint"),
285
+ decimal: () => createField("decimal"),
286
+ float: () => createField("float"),
287
+ double: () => createField("double"),
288
+ // 布尔类型
289
+ boolean: () => createField("boolean"),
290
+ // 时间类型
291
+ timestamp: () => createField("timestamp"),
292
+ // 二进制类型
293
+ blob: () => createField("blob"),
294
+ // 集合类型
295
+ list: () => createField("list"),
296
+ set: () => createField("set"),
297
+ map: () => createField("map")
298
+ };
299
+
300
+ // mod.ts
301
+ var mod_default = src_exports;
302
+
303
+ exports.Client = Client2;
304
+ exports.Field = Field;
305
+ exports.FieldBuilder = FieldBuilder;
306
+ exports.Model = Model;
307
+ exports.default = mod_default;
308
+ exports.uuid = uuid;