Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# DASHSCOPE_API_KEY=your_qwen_api_key_here
# OPENAI_API_KEY=your_qwen_api_key_here

# Server-mode tests / examples (optional; defaults: host=127.0.0.1, port=2881, user=root, database=test)
# SEEKDB_HOST=127.0.0.1
# SEEKDB_PORT=2881
# SEEKDB_USER=root
# SEEKDB_PASSWORD=
# SEEKDB_DATABASE=test
22 changes: 10 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,11 @@ jobs:
- name: Run server tests
working-directory: packages/seekdb
env:
SERVER_HOST: 127.0.0.1
SERVER_PORT: 2881
SERVER_USER: root
SERVER_PASSWORD: ""
SERVER_DATABASE: test
SERVER_TENANT: sys
SEEKDB_HOST: 127.0.0.1
SEEKDB_PORT: 2881
SEEKDB_USER: root
SEEKDB_PASSWORD: ""
SEEKDB_DATABASE: test
run: pnpm exec vitest run --exclude 'tests/embedded/**'

# Embedded-mode tests on multiple platforms (requires native bindings build per OS; Docker per job)
Expand Down Expand Up @@ -162,12 +161,11 @@ jobs:
- name: Run embedded tests
working-directory: packages/seekdb
env:
SERVER_HOST: 127.0.0.1
SERVER_PORT: 2881
SERVER_USER: root
SERVER_PASSWORD: ""
SERVER_DATABASE: test
SERVER_TENANT: sys
SEEKDB_HOST: 127.0.0.1
SEEKDB_PORT: 2881
SEEKDB_USER: root
SEEKDB_PASSWORD: ""
SEEKDB_DATABASE: test
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
pnpm exec vitest run tests/embedded/ 2>&1 | tee /tmp/vitest.log
Expand Down
2 changes: 1 addition & 1 deletion DEVELOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pnpm --filter seekdb exec vitest run tests/embedded/
**Tests and running mode**:

- **Embedded-mode tests** live under `packages/seekdb/tests/embedded/` and use a temporary database path per test file. They do not require a seekdb/OceanBase server.
- **Server-mode tests** (under `packages/seekdb/tests/` but outside `embedded/`) connect to `127.0.0.1:2881` and require a local seekdb or OceanBase instance.
- **Server-mode tests** (under `packages/seekdb/tests/` but outside `embedded/`) connect to `127.0.0.1:2881` and require a local seekdb or OceanBase instance. Override with `SEEKDB_HOST`, `SEEKDB_PORT`, `SEEKDB_USER`, `SEEKDB_PASSWORD`, `SEEKDB_DATABASE`.
- **Mode consistency** tests (`tests/embedded/mode-consistency.test.ts`) run both embedded and server modes; they require the native addon and a server for the server part.
- Embedded test coverage vs server is documented in `packages/seekdb/tests/embedded/COVERAGE_REPORT.md`.

Expand Down
202 changes: 183 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,21 @@ The SDK supports two modes; the constructor arguments to `SeekdbClient` determin
| **Embedded** | `path` (database directory path) | Runs locally with no separate seekdb server; data is stored under the given path (e.g. `./seekdb.db`). Requires native addon `@seekdb/js-bindings`. |
| **Server** | `host` (and `port`, `user`, `password`, etc.) | Connects to a remote seekdb or OceanBase instance. |

**OceanBase and seekdb**: OceanBase is compatible with seekdb and can be understood as its distributed, multi-tenant, etc. version. seekdb-js therefore supports **OceanBase server mode** with the same API: use the same `SeekdbClient` / `AdminClient` and connection parameters; when connecting to OceanBase, additionally pass `tenant` (e.g. `"sys"` or your tenant name). See [OceanBase mode](#oceanbase-mode-server-mode-with-tenant) below.

- **SeekdbClient**: Pass `path` for embedded mode, or `host` (and port, user, password, etc.) for server mode.
- **AdminClient()**: For admin operations only; pass `path` for embedded or `host` for server. In embedded mode you do not specify a database name.

## Quick Start

**Server mode** (connect to a deployed seekdb):
**Embedded mode** (local file, no server):

```typescript
import { SeekdbClient } from "seekdb";

// 1. Connect
const client = new SeekdbClient({
host: "127.0.0.1",
port: 2881,
user: "root",
password: "",
path: "./seekdb.db",
database: "test",
});

Expand All @@ -97,14 +96,17 @@ const results = await collection.query({ queryTexts: "Hello", nResults: 5 });
console.log("query results", results);
```

**Embedded mode** (local file, no server):
**Server mode** (connect to a deployed seekdb):

```typescript
import { SeekdbClient } from "seekdb";

// 1. Connect
const client = new SeekdbClient({
path: "./seekdb.db",
host: "127.0.0.1",
port: 2881,
user: "root",
password: "",
database: "test",
});

Expand All @@ -128,7 +130,18 @@ This section covers basic usage. See the [official SDK documentation](https://ww

### Client Connection

**Server mode** (seekdb / OceanBase):
**Embedded mode** (local database file):

```typescript
import { SeekdbClient } from "seekdb";

const client = new SeekdbClient({
path: "./seekdb.db", // database file path
database: "test",
});
```

**Server mode**:

```typescript
import { SeekdbClient } from "seekdb";
Expand All @@ -139,19 +152,19 @@ const client = new SeekdbClient({
user: "root",
password: "",
database: "test",
// Required for OceanBase mode
// tenant: "sys",
});
```

**Embedded mode** (local database file):
**OceanBase mode** (server mode with tenant): OceanBase is compatible with seekdb (distributed, multi-tenant, etc.). Use the same server-mode connection; when the backend is OceanBase, pass `tenant` (e.g. `"sys"` or your tenant name):

```typescript
import { SeekdbClient } from "seekdb";

const client = new SeekdbClient({
path: "./seekdb.db", // database file path
host: "127.0.0.1",
port: 2881,
user: "root",
password: "",
database: "test",
tenant: "sys", // or your OceanBase tenant
});
```

Expand Down Expand Up @@ -637,10 +650,157 @@ const collection = await client.createCollection({
});
```

### Vector search + relational tables

You can combine vector (or hybrid) search with relational tables: run `collection.query()` or `collection.hybridSearch()` to get `ids`, then query your relational table by those ids. For type-safe relational queries, prefer an ORM (see [Integration with ORM](#integration-with-orm)); here is a raw-SQL recipe.

**Recipe**

1. Get ids (and optional metadata) from vector/hybrid search.
2. Query the relational table with `client.execute()`. For `WHERE id IN (...)` with MySQL/mysql2, use parameterized placeholders to avoid SQL injection: one `?` per id and pass the ids array as params.

**Example (TypeScript)** — hybrid search, then fetch users by id and merge:

```typescript
const client = new SeekdbClient({
host: "127.0.0.1",
port: 2881,
user: "root",
password: "",
database: "test",
});
const collection = await client.getCollection({ name: "my_collection" });

// 1. Hybrid search → get ids
const hybridResult = await collection.hybridSearch({
query: { whereDocument: { $contains: "seekdb" } },
knn: { queryTexts: ["fast database"] },
nResults: 5,
});
const ids = hybridResult.ids?.flat().filter(Boolean) ?? []; // e.g. string[]

// 2. Query relational table by ids (parameterized)
type UserRow = { id: string; name: string };
let users: UserRow[] = [];
if (ids.length > 0) {
const placeholders = ids.map(() => "?").join(",");
const rows = await client.execute(
`SELECT id, name FROM users WHERE id IN (${placeholders})`,
ids
);
users = (rows ?? []) as UserRow[];
}

// 3. Merge: e.g. map ids to (vector result + user row)
const merged = ids.map((id) => ({
id,
user: users.find((u) => u.id === id) ?? null,
}));
```

**Transactions**: There is no explicit transaction API on the client. For transactions that span both vector and relational operations, use a separate mysql2 connection (same DB config) and run `beginTransaction()` / `commit()` / `rollback()` on that connection (see [Integration with ORM](#integration-with-orm)).

### Integration with ORM

Use seekdb-js for vector/full-text/hybrid search and an ORM (Drizzle or Prisma) for type-safe relational tables. seekdb is **MySQL-compatible** in both Server and Embedded mode. **Drizzle**: in Server mode create a mysql2 connection with the same DB config and pass it to `drizzle-orm/mysql2` (same database, two connections); in **Embedded mode** use `drizzle-orm/mysql-proxy` with a callback that calls `client.execute()` and maps to `{ rows }` (see `examples/seekdb-drizzle/index-embedded.ts`). **Prisma**: in Server mode use DATABASE_URL (same database, two connections); in **Embedded mode** use the [@seekdb/prisma-adapter](https://www.npmjs.com/package/@seekdb/prisma-adapter) so Prisma runs SQL via `client.execute()` (see `examples/seekdb-prisma/index-embedded.ts`).

#### With Drizzle (Server: same DB two connections; Embedded: mysql-proxy)

**Server mode**: Create a mysql2 connection with the same host/port/user/password/database as SeekdbClient and pass it to Drizzle (same database, two connections):

```typescript
import { createConnection } from "mysql2/promise";
import { SeekdbClient } from "seekdb";
import { drizzle } from "drizzle-orm/mysql2";
import { inArray } from "drizzle-orm";
import { users } from "./schema"; // your relational table (mysqlTable), no vector tables

const dbConfig = {
host: "127.0.0.1",
port: 2881,
user: "root",
password: "",
database: "test",
};

const client = new SeekdbClient(dbConfig);
const conn = await createConnection(dbConfig);
const db = drizzle(conn);

const collection = await client.getCollection({ name: "docs" });
const result = await collection.hybridSearch({
query: { whereDocument: { $contains: "seekdb" } },
knn: { queryTexts: ["database"] },
nResults: 5,
});
const ids = result.ids?.flat().filter(Boolean) ?? [];

const usersList = await db.select().from(users).where(inArray(users.id, ids));

// when done: await conn.end(); await client.close();
```

**Embedded mode**: Use Drizzle's mysql-proxy driver with a callback that runs SQL via `client.execute()` and maps results to `{ rows }`. See `examples/seekdb-drizzle/index-embedded.ts` for a full runnable sample.

- Schema: define only **relational tables** with `mysqlTable`; vector tables are managed by seekdb Collection.
- See `examples/seekdb-drizzle/` for a runnable sample.

#### With Prisma (same database, two connections)

Use **same database, two connections**: one SeekdbClient, one PrismaClient (via `DATABASE_URL`). Vector search with seekdb, then query relational by ids with Prisma.

```typescript
import { SeekdbClient } from "seekdb";
import { PrismaClient } from "@prisma/client";

const client = new SeekdbClient({
host: "127.0.0.1",
port: 2881,
user: "root",
password: "",
database: "test",
});

const prisma = new PrismaClient(); // uses DATABASE_URL="mysql://root:@127.0.0.1:2881/test"

const collection = await client.getCollection({ name: "docs" });
const result = await collection.hybridSearch({
query: { whereDocument: { $contains: "seekdb" } },
knn: { queryTexts: ["database"] },
nResults: 5,
});
const ids = result.ids?.flat().filter(Boolean) ?? [];

const users = await prisma.user.findMany({
where: { id: { in: ids } },
});

// merge vector results with users as needed
await client.close();
await prisma.$disconnect();
```

- Set `DATABASE_URL` to the same host/port/user/password/database as SeekdbClient (e.g. `mysql://user:password@host:port/database`). For OceanBase tenant, see your Prisma/MySQL docs.
- **Embedded mode**: use the [@seekdb/prisma-adapter](https://www.npmjs.com/package/@seekdb/prisma-adapter) and `PrismaClient({ adapter })` so Prisma runs SQL via `client.execute()`; run `pnpm run start:embedded` in `examples/seekdb-prisma/`.
- See `examples/seekdb-prisma/` for runnable samples (Server and Embedded).

### Database Management

Use `AdminClient()` for database management. It returns a `SeekdbClient` instance. In **embedded mode** you only pass `path`; no database name is required.

**Embedded mode** (local database file):

```typescript
import { AdminClient } from "seekdb";

const admin = AdminClient({ path: "./seekdb.db" });
await admin.createDatabase("new_database");
const databases = await admin.listDatabases();
const db = await admin.getDatabase("new_database");
await admin.deleteDatabase("new_database");
await admin.close();
```

**Server mode**:

```typescript
Expand All @@ -651,7 +811,6 @@ const admin = AdminClient({
port: 2881,
user: "root",
password: "",
// OceanBase mode requires tenant: "sys"
});

await admin.createDatabase("new_database");
Expand All @@ -661,12 +820,17 @@ await admin.deleteDatabase("new_database");
await admin.close();
```

**Embedded mode** (no server):
**OceanBase mode** (server mode with tenant): add `tenant` (e.g. `"sys"` or your tenant name) to the config:

```typescript
import { AdminClient } from "seekdb";
const admin = AdminClient({
host: "127.0.0.1",
port: 2881,
user: "root",
password: "",
tenant: "sys", // or your OceanBase tenant
});

const admin = AdminClient({ path: "./seekdb.db" });
await admin.createDatabase("new_database");
const databases = await admin.listDatabases();
const db = await admin.getDatabase("new_database");
Expand All @@ -689,7 +853,7 @@ Check out the [examples](./examples) directory for complete usage examples:
- [seekdb-drizzle](./examples/seekdb-drizzle) - Drizzle ORM (Server: same DB two connections; Embedded: mysql-proxy)
- [seekdb-prisma](./examples/seekdb-prisma) - Prisma ORM (Server: DATABASE_URL; Embedded: [@seekdb/prisma-adapter](https://www.npmjs.com/package/@seekdb/prisma-adapter))

# To run the examples, see [Run Examples](./DEVELOP.md#run-examples) in the development guide.
To run the examples, see [Run Examples](./DEVELOP.md#run-examples) in the development guide.

## Development

Expand Down
2 changes: 1 addition & 1 deletion examples/hybrid-search-example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async function main() {
// database: "test",
// user: "root",
// password: "",
// // for OceanBase, set tenant to "sys"
// // For OceanBase: add tenant: "sys" or your tenant.
// // tenant: "sys",
// });

Expand Down
2 changes: 1 addition & 1 deletion examples/seekdb-prisma/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Vector/hybrid search with seekdb-js and type-safe relational tables with Prisma.

- **Server mode** (`pnpm start`): same database, two connections (SeekdbClient + PrismaClient via `DATABASE_URL`).
- **Embedded mode** (`pnpm run start:embedded`): use [@seekdb/prisma-adapter](https://www.npmjs.com/package/@seekdb/prisma-adapter) so Prisma runs SQL via `client.execute()` — no MySQL server.
- **Embedded mode** (`pnpm run start:embedded`): use [@seekdb/prisma-adapter](https://www.npmjs.com/package/@seekdb/prisma-adapter) so Prisma runs SQL via `client.execute()`.

## Prerequisites

Expand Down
2 changes: 1 addition & 1 deletion examples/simple-example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async function main() {
// database: "test",
// user: "root",
// password: "",
// // for OceanBase, set tenant to "sys"
// // For OceanBase: add tenant: "sys" or your tenant.
// // tenant: "sys",
// });

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
}
},
"repository": "[email protected]:oceanbase/seekdb-js.git"
}
2 changes: 1 addition & 1 deletion packages/prisma-adapter/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @seekdb/prisma-adapter

Prisma driver adapter for **seekdb Embedded**. Use Prisma ORM with seekdb in-process — no MySQL server required.
Prisma driver adapter for **seekdb Embedded**. Use Prisma with seekdb Embedded.

- **Provider**: `mysql` (seekdb is MySQL-compatible)
- **Use case**: Vector/hybrid search with seekdb Collection API + type-safe relational tables with Prisma, all in one embedded database file.
Expand Down
2 changes: 1 addition & 1 deletion packages/prisma-adapter/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@seekdb/prisma-adapter",
"version": "0.1.0",
"description": "Prisma driver adapter for seekdb Embedded — use Prisma with seekdb in-process (no MySQL server)",
"description": "Prisma driver adapter for seekdb Embedded — use Prisma with seekdb Embedded",
"keywords": [
"seekdb",
"prisma",
Expand Down
Loading
Loading