# Cassandra ORM

一个简单易用的 Cassandra ORM 库，提供类型安全的 API。

## 安装

```bash
npm install imean-cassandra-orm
```

## 使用示例

```typescript
import { Client as CassandraClient, auth } from "cassandra-driver";
import { Client, Field, uuid, type Infer } from "imean-cassandra-orm";

// 创建原始 Cassandra 客户端
const cassandraClient = new CassandraClient({
  contactPoints: ["localhost"],
  localDataCenter: "datacenter1",
  authProvider: new auth.PlainTextAuthProvider("cassandra", "cassandra"),
});

// 创建我们的客户端
const client = new Client(cassandraClient);

// 设置 keyspace
await client.useKeyspace("my_keyspace", {
  class: "SimpleStrategy",
  replication_factor: 1,
});

// 创建用户模型
const userModel = client.createModel(
  {
    id: Field.uuid().partitionKey(),      // 分区键，必填
    name: Field.text().clusteringKey(),   // 聚类键，必填
    age: Field.int().optional(),          // 可选字段
    email: Field.text(),                  // 普通字段，必填
  },
  "users"
);
type User = Infer<typeof userModel>;

// 创建文章模型
const postModel = client.createModel(
  {
    id: Field.uuid().partitionKey(),      // 分区键，必填
    title: Field.text(),                  // 普通字段，必填
    content: Field.text(),                // 普通字段，必填
    created_at: Field.timestamp(),        // 普通字段，必填
  },
  "posts"
);

// 同步所有表结构
await client.syncAllSchemas();

// 创建用户
const user = await userModel.create({
  id: uuid(),           // 必须提供分区键
  name: "张三",         // 必须提供聚类键
  age: 25,             // 可选
  email: "zhangsan@example.com"  // 必填
});

// 创建文章
await postModel.create({
  id: uuid(),
  title: "测试文章",
  content: "内容",
  created_at: new Date(),
});

// 查询用户
const foundUser = await userModel.findOne({ id: user.id });
console.log(foundUser);

// 关闭连接
await client.close();
```

## API 参考

### 类型推导工具

ORM 库提供了一系列类型推导工具，可以帮助开发者在上层 API 中实现类型安全。

#### Infer<M>

从 Model 实例中推断出原始字段类型。

```typescript
const userModel = client.createModel({
  id: Field.uuid().partitionKey(),
  name: Field.text().clusteringKey(),
  age: Field.int().optional(),
  email: Field.text(),
}, "users");

type User = Infer<typeof userModel>;
// 推导结果: { id: string; name: string; age?: number; email: string }
```

#### InferPartitionKey<M>

推导分区键字段类型。

```typescript
type UserPartitionKey = InferPartitionKey<typeof userModel>;
// 推导结果: { id: string }
```

#### InferClusteringKey<M>

推导聚类键字段类型。

```typescript
type UserClusteringKey = InferClusteringKey<typeof userModel>;
// 推导结果: { name: string }
```

#### InferNonPartitionFields<S>

推导非分区键字段类型。

```typescript
type UserFields = {
  id: { type: "uuid"; flags: { partitionKey: true } };
  name: { type: "text" };
  age: { type: "int" };
};

type NonPartitionFields = InferNonPartitionFields<UserFields>;
// 推导结果: { name: string; age: number }
```

#### InferStaticFields<M>

推导静态字段类型。

```typescript
const userModel = client.createModel({
  id: Field.uuid().partitionKey(),
  name: Field.text().static(),    // 静态字段
  age: Field.int().optional(),
  email: Field.text(),
}, "users");

type UserStaticFields = InferStaticFields<typeof userModel>;
// 推导结果: { name: string }
```

静态列的特点：
1. 在分区级别存储数据，而不是在每一行重复存储
2. 适用于在分区内所有行共享相同值的字段
3. 可以节省存储空间
4. 提高查询效率

这些类型工具可以用于：

1. 在 API 层定义类型安全的接口
2. 在服务层进行类型检查
3. 在数据访问层确保数据一致性
4. 在业务逻辑层进行类型推导

### Client

`Client` 类是对原始 Cassandra 客户端的封装，提供了更高级的 API。

#### 构造函数

```typescript
constructor(cassandraClient: CassandraClient)
```

#### 方法

- `useKeyspace(keyspace: string, options: KeyspaceOptions): Promise<void>`
  - 设置当前使用的 keyspace，如果不存在则创建
  - `options`:
    - `class`: 复制策略，可以是 `"SimpleStrategy"` 或 `"NetworkTopologyStrategy"`
    - `replication_factor`: 复制因子（仅用于 `SimpleStrategy`）
    - `datacenters`: 数据中心配置（仅用于 `NetworkTopologyStrategy`）

- `createModel<S extends Record<string, FieldConfig<keyof TypeMap>>>(schema: S, tableName: string): Model<S>`
  - 创建并注册一个新的模型
  - 返回的模型实例可以直接使用，不需要额外获取

- `syncAllSchemas(): Promise<void>`
  - 同步所有已注册模型的结构到数据库

- `close(): Promise<void>`
  - 关闭数据库连接

### Model

`Model` 类提供了对数据库表的操作接口。

#### 类型推导示例

```typescript
// 定义模型 schema
const userModel = client.createModel({
  id: Field.uuid().partitionKey(),      // 分区键，必填
  name: Field.text().clusteringKey(),   // 聚类键，必填
  age: Field.int().optional(),          // 可选字段
  email: Field.text(),                  // 普通字段，必填
}, "users");

// 推导出的类型：
type User = {
  id: string;           // 分区键，必填
  name: string;         // 聚类键，必填
  age?: number;         // 可选字段
  email: string;        // 普通字段，必填
};

// 分区键类型（用于查询和删除）
type PartitionKey = {
  id: string;
};

// 聚类键类型（用于查询和删除）
type ClusteringKey = {
  name: string;
};

// 可更新字段类型（用于更新操作）
type UpdatableFields = {
  age?: number;         // 可选字段
  email: string;        // 普通字段
};
```

#### 方法

- `create(data: User): Promise<void>`
  - 创建新记录
  - 示例：
    ```typescript
    await userModel.create({
      id: uuid(),           // 必须提供分区键
      name: "张三",         // 必须提供聚类键
      age: 25,             // 可选
      email: "zhangsan@example.com"  // 必填
    });
    ```

- `findOne(partitionKey: PartitionKey, clusteringKey?: Partial<ClusteringKey>): Promise<User | null>`
  - 查询单条记录
  - 示例：
    ```typescript
    // 只使用分区键查询
    const user = await userModel.findOne({ id: "some-uuid" });
    
    // 使用分区键和聚类键查询
    const user = await userModel.findOne(
      { id: "some-uuid" },
      { name: "张三" }
    );
    ```

- `findAll(partitionKey: PartitionKey, clusteringKey?: Partial<ClusteringKey>): Promise<User[]>`
  - 查询多条记录
  - 参数同 `findOne`

- `update(partitionKey: PartitionKey, data: Partial<UpdatableFields>): Promise<void>`
  - 更新记录
  - 示例：
    ```typescript
    await userModel.update(
      { id: "some-uuid" },  // 必须提供分区键
      { 
        age: 26,            // 可以更新可选字段
        email: "new@example.com"  // 可以更新普通字段
      }
    );
    ```

- `delete(partitionKey: PartitionKey, clusteringKey?: Partial<ClusteringKey>): Promise<void>`
  - 删除记录
  - 参数同 `findOne`

### Field

`Field` 类提供了创建字段配置的方法。

#### 静态方法

- `uuid(): FieldBuilder<"uuid">`
- `text(): FieldBuilder<"text">`
- `int(): FieldBuilder<"int">`
- `bigint(): FieldBuilder<"bigint">`
- `float(): FieldBuilder<"float">`
- `double(): FieldBuilder<"double">`
- `boolean(): FieldBuilder<"boolean">`
- `timestamp(): FieldBuilder<"timestamp">`

#### 实例方法

- `partitionKey(): this`
  - 将字段设置为分区键
  - 分区键字段是必填的，不能为 null
  - 示例：
    ```typescript
    Field.uuid().partitionKey()  // 将 UUID 字段设置为分区键
    ```

- `clusteringKey(order?: "ASC" | "DESC"): this`
  - 将字段设置为聚类键
  - 聚类键字段是否必填取决于是否设置了 `optional()`
  - `order` 可选，指定排序方向
  - 示例：
    ```typescript
    Field.text().clusteringKey()          // 默认升序，必填
    Field.text().clusteringKey("DESC")    // 降序，必填
    Field.text().clusteringKey().optional()  // 可选聚类键
    ```

- `optional(): this`
  - 将字段设置为可选
  - 可以用于任何类型的字段，包括聚类键
  - 示例：
    ```typescript
    Field.int().optional()  // 将整数字段设置为可选
    ```

- `static(): this`
  - 将字段设置为静态列
  - 静态列在分区级别存储，而不是在每一行重复存储
  - 适用于在分区内所有行共享相同值的字段
  - 示例：
    ```typescript
    Field.text().static()  // 将文本字段设置为静态列
    ```

## 注意事项

1. 所有模型必须在调用 `syncAllSchemas` 之前创建
2. 创建记录时必须提供所有分区键字段
3. 聚类键字段是否必填取决于是否设置了 `optional()`
4. 更新记录时可以更新非分区键字段（包括聚类键和普通字段）
5. 删除记录时必须提供所有分区键字段

## 许可证

MIT 