@svadmin/drizzle 0.4.1

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/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@svadmin/drizzle",
3
+ "version": "0.4.1",
4
+ "description": "Drizzle ORM DataProvider — SQLite, PostgreSQL, MySQL, Cloudflare D1 via refine-sqlx",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "src/**/*"
9
+ ],
10
+ "main": "src/index.ts",
11
+ "types": "src/index.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./src/index.ts",
15
+ "default": "./src/index.ts"
16
+ }
17
+ },
18
+ "peerDependencies": {
19
+ "@svadmin/core": "^0.4.1",
20
+ "refine-sqlx": "^0.9.0"
21
+ },
22
+ "license": "MIT",
23
+ "author": "zuohuadong",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/zuohuadong/svadmin.git",
27
+ "directory": "packages/drizzle"
28
+ },
29
+ "homepage": "https://github.com/zuohuadong/svadmin#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/zuohuadong/svadmin/issues"
32
+ },
33
+ "devDependencies": {
34
+ "drizzle-orm": "^0.45.1"
35
+ }
36
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @svadmin/drizzle — DataProvider integration test
3
+ *
4
+ * Uses bun:sqlite in-memory database to verify the full CRUD lifecycle
5
+ * through the wrapper layer.
6
+ */
7
+
8
+ import { describe, it, expect, beforeAll } from 'bun:test';
9
+ import { Database } from 'bun:sqlite';
10
+ import { drizzle } from 'drizzle-orm/bun-sqlite';
11
+ import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
12
+ import { createDrizzleDataProvider } from './data-provider';
13
+ import type { DataProvider } from '@svadmin/core';
14
+
15
+ // ─── Test Schema ──────────────────────────────────────────────────
16
+
17
+ const users = sqliteTable('users', {
18
+ id: integer('id').primaryKey({ autoIncrement: true }),
19
+ name: text('name').notNull(),
20
+ email: text('email').notNull(),
21
+ role: text('role').default('user'),
22
+ });
23
+
24
+ const schema = { users };
25
+
26
+ // ─── Tests ────────────────────────────────────────────────────────
27
+
28
+ describe('@svadmin/drizzle DataProvider', () => {
29
+ let dataProvider: DataProvider;
30
+ let db: ReturnType<typeof drizzle>;
31
+
32
+ beforeAll(async () => {
33
+ const sqlite = new Database(':memory:');
34
+ sqlite.run(`
35
+ CREATE TABLE users (
36
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37
+ name TEXT NOT NULL,
38
+ email TEXT NOT NULL,
39
+ role TEXT DEFAULT 'user'
40
+ )
41
+ `);
42
+ db = drizzle(sqlite, { schema });
43
+ dataProvider = await createDrizzleDataProvider({ connection: db, schema });
44
+ });
45
+
46
+ it('returns a valid DataProvider with all required methods', () => {
47
+ expect(dataProvider.getList).toBeFunction();
48
+ expect(dataProvider.getOne).toBeFunction();
49
+ expect(dataProvider.create).toBeFunction();
50
+ expect(dataProvider.update).toBeFunction();
51
+ expect(dataProvider.deleteOne).toBeFunction();
52
+ expect(dataProvider.getApiUrl).toBeFunction();
53
+ });
54
+
55
+ it('create → getOne roundtrip', async () => {
56
+ const created = await dataProvider.create({
57
+ resource: 'users',
58
+ variables: { name: 'Alice', email: 'alice@example.com', role: 'admin' },
59
+ });
60
+
61
+ expect(created.data).toMatchObject({
62
+ name: 'Alice',
63
+ email: 'alice@example.com',
64
+ role: 'admin',
65
+ });
66
+
67
+ const fetched = await dataProvider.getOne({
68
+ resource: 'users',
69
+ id: (created.data as Record<string, unknown>).id as number,
70
+ });
71
+
72
+ expect(fetched.data).toMatchObject({
73
+ name: 'Alice',
74
+ email: 'alice@example.com',
75
+ });
76
+ });
77
+
78
+ it('getList with pagination and sorting', async () => {
79
+ // Seed more data
80
+ await dataProvider.create({ resource: 'users', variables: { name: 'Bob', email: 'bob@example.com' } });
81
+ await dataProvider.create({ resource: 'users', variables: { name: 'Charlie', email: 'charlie@example.com' } });
82
+
83
+ const result = await dataProvider.getList({
84
+ resource: 'users',
85
+ pagination: { current: 1, pageSize: 2 },
86
+ sorters: [{ field: 'name', order: 'asc' }],
87
+ });
88
+
89
+ expect(result.data.length).toBe(2);
90
+ expect(result.total).toBeGreaterThanOrEqual(3);
91
+ // Sorted ascending: Alice before Bob
92
+ expect((result.data[0] as Record<string, unknown>).name).toBe('Alice');
93
+ });
94
+
95
+ it('getList with filters', async () => {
96
+ const result = await dataProvider.getList({
97
+ resource: 'users',
98
+ filters: [{ field: 'role', operator: 'eq', value: 'admin' }],
99
+ });
100
+
101
+ expect(result.data.length).toBe(1);
102
+ expect((result.data[0] as Record<string, unknown>).name).toBe('Alice');
103
+ });
104
+
105
+ it('update modifies a record', async () => {
106
+ const list = await dataProvider.getList({ resource: 'users', filters: [{ field: 'name', operator: 'eq', value: 'Bob' }] });
107
+ const bobId = (list.data[0] as Record<string, unknown>).id as number;
108
+
109
+ const updated = await dataProvider.update({
110
+ resource: 'users',
111
+ id: bobId,
112
+ variables: { role: 'editor' },
113
+ });
114
+
115
+ expect((updated.data as Record<string, unknown>).role).toBe('editor');
116
+ });
117
+
118
+ it('deleteOne removes a record', async () => {
119
+ const list = await dataProvider.getList({ resource: 'users', filters: [{ field: 'name', operator: 'eq', value: 'Charlie' }] });
120
+ const charlieId = (list.data[0] as Record<string, unknown>).id as number;
121
+
122
+ await dataProvider.deleteOne({ resource: 'users', id: charlieId });
123
+
124
+ const afterDelete = await dataProvider.getList({
125
+ resource: 'users',
126
+ filters: [{ field: 'name', operator: 'eq', value: 'Charlie' }],
127
+ });
128
+ expect(afterDelete.data.length).toBe(0);
129
+ });
130
+
131
+ it('getMany fetches multiple records by IDs', async () => {
132
+ if (!dataProvider.getMany) return;
133
+
134
+ const list = await dataProvider.getList({ resource: 'users' });
135
+ const ids = list.data.slice(0, 2).map(d => (d as Record<string, unknown>).id as number);
136
+
137
+ const result = await dataProvider.getMany({ resource: 'users', ids });
138
+ expect(result.data.length).toBe(2);
139
+ });
140
+
141
+ it('getApiUrl returns empty string', () => {
142
+ expect(dataProvider.getApiUrl()).toBe('');
143
+ });
144
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @svadmin/drizzle — Drizzle ORM DataProvider adapter
3
+ *
4
+ * Thin wrapper around refine-sqlx that returns a @svadmin/core DataProvider.
5
+ * Supports SQLite, PostgreSQL, MySQL, and Cloudflare D1.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { createDrizzleDataProvider } from '@svadmin/drizzle';
10
+ * import { drizzle } from 'drizzle-orm/bun-sqlite';
11
+ * import * as schema from './schema';
12
+ *
13
+ * const db = drizzle(new Database(':memory:'), { schema });
14
+ * const dataProvider = await createDrizzleDataProvider({ connection: db, schema });
15
+ * ```
16
+ */
17
+
18
+ import type { DataProvider } from '@svadmin/core';
19
+ import type { RefineSQLConfig } from 'refine-sqlx';
20
+ import { createRefineSQL } from 'refine-sqlx';
21
+
22
+ export type { RefineSQLConfig } from 'refine-sqlx';
23
+
24
+ /**
25
+ * Create a svadmin DataProvider backed by Drizzle ORM.
26
+ *
27
+ * Wraps refine-sqlx's `createRefineSQL()` and returns a `@svadmin/core` DataProvider.
28
+ * Both type systems are structurally compatible (duck typing), so the wrapper is
29
+ * a simple type-level bridge with no runtime overhead.
30
+ *
31
+ * @param config - refine-sqlx configuration (connection, schema, features, etc.)
32
+ * @returns A @svadmin/core DataProvider
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * import { createDrizzleDataProvider } from '@svadmin/drizzle';
37
+ * import { drizzle } from 'drizzle-orm/bun-sqlite';
38
+ * import { Database } from 'bun:sqlite';
39
+ * import * as schema from './schema';
40
+ *
41
+ * const db = drizzle(new Database('./app.db'), { schema });
42
+ *
43
+ * const dataProvider = await createDrizzleDataProvider({
44
+ * connection: db,
45
+ * schema,
46
+ * softDelete: { enabled: true },
47
+ * features: {
48
+ * relations: { enabled: true },
49
+ * transactions: { enabled: true },
50
+ * },
51
+ * });
52
+ * ```
53
+ */
54
+ export async function createDrizzleDataProvider<
55
+ TSchema extends Record<string, unknown>,
56
+ >(config: RefineSQLConfig<TSchema>): Promise<DataProvider> {
57
+ const provider = await createRefineSQL(config);
58
+
59
+ // refine-sqlx's DataProvider is structurally compatible with @svadmin/core's DataProvider:
60
+ // - Same method names: getList, getOne, getMany, create, update, deleteOne, etc.
61
+ // - Same parameter shapes: { resource, pagination, sorters, filters, meta, ... }
62
+ // - Same return shapes: { data, total } / { data }
63
+ //
64
+ // The only nominal difference is type naming (GetListResponse vs GetListResult, etc.)
65
+ // which TypeScript's structural type system bridges automatically.
66
+ //
67
+ // The `custom()` method has a minor signature superset (refine-sqlx supports
68
+ // 'head' and 'options' HTTP methods that svadmin doesn't define), which is
69
+ // a non-breaking superset — safe to cast.
70
+ return provider as unknown as DataProvider;
71
+ }
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ // @svadmin/drizzle — Drizzle ORM DataProvider for svadmin
2
+ //
3
+ // Usage:
4
+ // import { createDrizzleDataProvider } from '@svadmin/drizzle';
5
+
6
+ export { createDrizzleDataProvider } from './data-provider';
7
+ export type { RefineSQLConfig } from './data-provider';
8
+
9
+ // Re-export commonly used refine-sqlx utilities for convenience
10
+ export type {
11
+ SecurityConfig,
12
+ FeaturesConfig,
13
+ CacheConfig,
14
+ CacheAdapter,
15
+ } from 'refine-sqlx';
16
+
17
+ export {
18
+ SecurityGuard,
19
+ validateConfig,
20
+ validateFeaturesConfig,
21
+ } from 'refine-sqlx';