@svadmin/drizzle 0.11.16 → 0.12.6
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 +12 -3
- package/src/column-metadata.ts +45 -0
- package/src/data-provider.ts +5 -16
- package/src/infer-fields.ts +16 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.6",
|
|
4
4
|
"description": "Drizzle ORM DataProvider — SQLite, PostgreSQL, MySQL, Cloudflare D1 via refine-sqlx",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -20,14 +20,19 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@svadmin/core": "^0.
|
|
23
|
+
"@svadmin/core": "^0.52.0",
|
|
24
|
+
"drizzle-orm": "^1.0.0-rc.1",
|
|
24
25
|
"refine-sqlx": "^0.10.3"
|
|
25
26
|
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@sinclair/typebox": "^0.34.52",
|
|
29
|
+
"@svadmin/refine-adapter": "^0.11.6"
|
|
30
|
+
},
|
|
26
31
|
"license": "MIT",
|
|
27
32
|
"author": "zuohuadong",
|
|
28
33
|
"repository": {
|
|
29
34
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/vibeunion/svadmin.git",
|
|
35
|
+
"url": "git+https://github.com/vibeunion/svadmin.git",
|
|
31
36
|
"directory": "packages/drizzle"
|
|
32
37
|
},
|
|
33
38
|
"homepage": "https://github.com/vibeunion/svadmin#readme",
|
|
@@ -35,7 +40,11 @@
|
|
|
35
40
|
"url": "https://github.com/vibeunion/svadmin/issues"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
43
|
+
"@cloudflare/workers-types": "^5.20260908.1",
|
|
44
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
38
45
|
"drizzle-orm": "^1.0.0-rc.1",
|
|
46
|
+
"mysql2": "^3.24.4",
|
|
47
|
+
"postgres": "^3.4.9",
|
|
39
48
|
"refine-sqlx": "^0.10.3"
|
|
40
49
|
},
|
|
41
50
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Type, type Static } from '@sinclair/typebox';
|
|
2
|
+
import { createExactSchemaValidator } from '@svadmin/core/schema';
|
|
3
|
+
|
|
4
|
+
const columnSchema = Type.Object({
|
|
5
|
+
name: Type.String({ minLength: 1 }),
|
|
6
|
+
dataType: Type.String({ minLength: 1 }),
|
|
7
|
+
columnType: Type.String({ minLength: 1 }),
|
|
8
|
+
notNull: Type.Boolean(),
|
|
9
|
+
hasDefault: Type.Boolean(),
|
|
10
|
+
primary: Type.Boolean(),
|
|
11
|
+
enumValues: Type.Optional(Type.Array(Type.String())),
|
|
12
|
+
});
|
|
13
|
+
const columnValidator = createExactSchemaValidator(columnSchema);
|
|
14
|
+
|
|
15
|
+
export type ColumnMetadata = Static<typeof columnSchema>;
|
|
16
|
+
|
|
17
|
+
function isObject(value: unknown): value is Record<PropertyKey, unknown> {
|
|
18
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Project only consumed metadata; Drizzle columns contain circular table links. */
|
|
22
|
+
export function decodeColumnMetadata(value: unknown): ColumnMetadata {
|
|
23
|
+
if (!isObject(value)) throw new TypeError('Invalid Drizzle column metadata');
|
|
24
|
+
const candidate = {
|
|
25
|
+
name: value['name'],
|
|
26
|
+
dataType: value['dataType'],
|
|
27
|
+
columnType: value['columnType'],
|
|
28
|
+
notNull: value['notNull'],
|
|
29
|
+
hasDefault: value['hasDefault'],
|
|
30
|
+
primary: value['primary'],
|
|
31
|
+
...(value['enumValues'] === undefined ? {} : { enumValues: value['enumValues'] }),
|
|
32
|
+
};
|
|
33
|
+
if (!columnValidator.Check(candidate)) {
|
|
34
|
+
throw new TypeError('Invalid Drizzle column metadata');
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
...candidate,
|
|
38
|
+
...(candidate.enumValues === undefined ? {} : { enumValues: [...candidate.enumValues] }),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function decodeColumns(value: unknown): [string, ColumnMetadata][] {
|
|
43
|
+
if (!isObject(value)) throw new TypeError('Invalid Drizzle table columns');
|
|
44
|
+
return Object.entries(value).map(([key, column]) => [key, decodeColumnMetadata(column)]);
|
|
45
|
+
}
|
package/src/data-provider.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { DataProvider } from '@svadmin/core';
|
|
19
|
+
import { createRefineAdapter } from '@svadmin/refine-adapter';
|
|
19
20
|
import type { RefineSQLConfig } from 'refine-sqlx';
|
|
20
21
|
import { createRefineSQL } from 'refine-sqlx';
|
|
21
22
|
|
|
@@ -25,8 +26,8 @@ export type { RefineSQLConfig } from 'refine-sqlx';
|
|
|
25
26
|
* Create a svadmin DataProvider backed by Drizzle ORM.
|
|
26
27
|
*
|
|
27
28
|
* Wraps refine-sqlx's `createRefineSQL()` and returns a `@svadmin/core` DataProvider.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* The shared adapter validates method availability and every response envelope
|
|
30
|
+
* before database results enter the application's typed data layer.
|
|
30
31
|
*
|
|
31
32
|
* @param config - refine-sqlx configuration (connection, schema, features, etc.)
|
|
32
33
|
* @returns A @svadmin/core DataProvider
|
|
@@ -54,18 +55,6 @@ export type { RefineSQLConfig } from 'refine-sqlx';
|
|
|
54
55
|
export async function createDrizzleDataProvider<
|
|
55
56
|
TSchema extends Record<string, unknown>,
|
|
56
57
|
>(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;
|
|
58
|
+
const provider: unknown = await createRefineSQL(config);
|
|
59
|
+
return createRefineAdapter(provider);
|
|
71
60
|
}
|
package/src/infer-fields.ts
CHANGED
|
@@ -15,24 +15,9 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { FieldDefinition } from '@svadmin/core';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
dataType?: string;
|
|
22
|
-
columnType?: string;
|
|
23
|
-
name?: string;
|
|
24
|
-
notNull?: boolean;
|
|
25
|
-
hasDefault?: boolean;
|
|
26
|
-
default?: unknown;
|
|
27
|
-
primary?: boolean;
|
|
28
|
-
enumValues?: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Drizzle table shape (duck-typed — works with any Drizzle table object) */
|
|
32
|
-
interface DrizzleTableLike {
|
|
33
|
-
[Symbol.iterator]?: never;
|
|
34
|
-
_?: { columns?: Record<string, DrizzleColumnLike> };
|
|
35
|
-
}
|
|
18
|
+
import { getTableColumns } from 'drizzle-orm/utils';
|
|
19
|
+
import { isTable, type Table } from 'drizzle-orm/table';
|
|
20
|
+
import { decodeColumns, type ColumnMetadata } from './column-metadata';
|
|
36
21
|
|
|
37
22
|
/** Options for field inference */
|
|
38
23
|
export interface InferFieldsOptions {
|
|
@@ -49,11 +34,13 @@ export interface InferFieldsOptions {
|
|
|
49
34
|
/**
|
|
50
35
|
* Map a Drizzle column type string to a svadmin FieldDefinition type.
|
|
51
36
|
*/
|
|
52
|
-
function mapColumnType(col:
|
|
53
|
-
const dt =
|
|
37
|
+
function mapColumnType(col: ColumnMetadata): FieldDefinition['type'] {
|
|
38
|
+
const dt = `${col.dataType} ${col.columnType}`.toLowerCase();
|
|
54
39
|
|
|
55
40
|
// Integer / serial / numeric types
|
|
56
|
-
if (
|
|
41
|
+
if (col.dataType.includes('bool')) return 'boolean';
|
|
42
|
+
if (col.dataType.includes('date')) return 'date';
|
|
43
|
+
if (dt.includes('int') || dt.includes('serial') || dt.includes('numeric') || dt.includes('real') || dt.includes('float') || dt.includes('double') || dt.includes('decimal') || dt.includes('number')) {
|
|
57
44
|
return 'number';
|
|
58
45
|
}
|
|
59
46
|
// Boolean
|
|
@@ -90,37 +77,18 @@ function humanizeKey(key: string): string {
|
|
|
90
77
|
/**
|
|
91
78
|
* Infer FieldDefinition[] from a Drizzle ORM table schema.
|
|
92
79
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
80
|
+
* Uses Drizzle's runtime column API; `_` is type-only metadata, not a table
|
|
81
|
+
* property. Malformed column metadata is rejected before deriving UI fields.
|
|
95
82
|
*/
|
|
96
83
|
export function inferFieldsFromDrizzle(
|
|
97
|
-
table:
|
|
84
|
+
table: Table,
|
|
98
85
|
options: InferFieldsOptions = {},
|
|
99
86
|
): FieldDefinition[] {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
: undefined;
|
|
103
|
-
|
|
104
|
-
if (!columns) {
|
|
105
|
-
// Fallback: try to iterate over table keys that look like columns
|
|
106
|
-
const result: FieldDefinition[] = [];
|
|
107
|
-
for (const [key, val] of Object.entries(table as Record<string, unknown>)) {
|
|
108
|
-
if (key.startsWith('_') || typeof val === 'function') continue;
|
|
109
|
-
if (options.exclude?.includes(key)) continue;
|
|
110
|
-
|
|
111
|
-
const col = val as DrizzleColumnLike;
|
|
112
|
-
if (!col || typeof col !== 'object') continue;
|
|
113
|
-
|
|
114
|
-
// Check if it looks like a Drizzle column (has dataType or columnType)
|
|
115
|
-
if (!col.dataType && !col.columnType) continue;
|
|
116
|
-
|
|
117
|
-
result.push(buildField(key, col, options));
|
|
118
|
-
}
|
|
119
|
-
return result;
|
|
120
|
-
}
|
|
87
|
+
if (!isTable(table)) throw new TypeError('Expected a Drizzle table');
|
|
88
|
+
const columns: unknown = getTableColumns(table);
|
|
121
89
|
|
|
122
90
|
const fields: FieldDefinition[] = [];
|
|
123
|
-
for (const [key, col] of
|
|
91
|
+
for (const [key, col] of decodeColumns(columns)) {
|
|
124
92
|
if (options.exclude?.includes(key)) continue;
|
|
125
93
|
fields.push(buildField(key, col, options));
|
|
126
94
|
}
|
|
@@ -129,12 +97,12 @@ export function inferFieldsFromDrizzle(
|
|
|
129
97
|
|
|
130
98
|
function buildField(
|
|
131
99
|
key: string,
|
|
132
|
-
col:
|
|
100
|
+
col: ColumnMetadata,
|
|
133
101
|
options: InferFieldsOptions,
|
|
134
102
|
): FieldDefinition {
|
|
135
103
|
const type = mapColumnType(col);
|
|
136
104
|
const field: FieldDefinition = {
|
|
137
|
-
key
|
|
105
|
+
key,
|
|
138
106
|
label: options.labels?.[key] ?? humanizeKey(key),
|
|
139
107
|
type,
|
|
140
108
|
required: col.notNull === true && !col.hasDefault,
|