@lobomfz/db 0.2.0 → 0.2.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/README.md +46 -67
- package/package.json +1 -1
- package/src/database.ts +17 -11
- package/src/dialect/connection.ts +12 -12
- package/src/dialect/driver.ts +1 -3
- package/src/index.ts +1 -0
- package/src/plugin.ts +121 -17
- package/src/types.ts +6 -0
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# @lobomfz/db
|
|
2
2
|
|
|
3
|
-
SQLite database with Arktype schemas and typed Kysely client.
|
|
3
|
+
SQLite database with Arktype schemas and typed Kysely client for Bun.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
bun add @lobomfz/db
|
|
8
|
+
bun add @lobomfz/db arktype kysely
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
@@ -14,88 +14,67 @@ bun add @lobomfz/db
|
|
|
14
14
|
import { Database, generated, type } from "@lobomfz/db";
|
|
15
15
|
|
|
16
16
|
const db = new Database({
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
17
|
+
path: "data.db",
|
|
18
|
+
schema: {
|
|
19
|
+
tables: {
|
|
20
|
+
users: type({
|
|
21
|
+
id: generated("autoincrement"),
|
|
22
|
+
name: "string",
|
|
23
|
+
email: type("string").configure({ unique: true }),
|
|
24
|
+
"bio?": "string", // optional → nullable in SQLite
|
|
25
|
+
active: type("boolean").default(true),
|
|
26
|
+
created_at: generated("now"), // defaults to current time
|
|
27
|
+
}),
|
|
28
|
+
posts: type({
|
|
29
|
+
id: generated("autoincrement"),
|
|
30
|
+
user_id: type("number.integer").configure({ references: "users.id", onDelete: "cascade" }),
|
|
31
|
+
title: "string",
|
|
32
|
+
published_at: "Date", // native Date support
|
|
33
|
+
tags: "string[]", // JSON columns just work
|
|
34
|
+
metadata: type({ source: "string", "priority?": "number" }), // validated on write by default
|
|
35
|
+
status: type.enumerated("draft", "published").default("draft"),
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
indexes: {
|
|
39
|
+
posts: [{ columns: ["user_id", "status"] }, { columns: ["title"], unique: true }],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
pragmas: {
|
|
43
|
+
journal_mode: "wal",
|
|
44
|
+
synchronous: "normal",
|
|
45
|
+
},
|
|
37
46
|
});
|
|
38
47
|
|
|
39
|
-
// Fully typed Kysely client
|
|
48
|
+
// Fully typed Kysely client — generated/default fields are optional on insert
|
|
40
49
|
await db.kysely.insertInto("users").values({ name: "John", email: "john@example.com" }).execute();
|
|
41
50
|
|
|
42
51
|
const users = await db.kysely.selectFrom("users").selectAll().execute();
|
|
52
|
+
// users[0].active → true
|
|
53
|
+
// users[0].created_at → Date
|
|
43
54
|
```
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- Tables auto-created from Arktype schemas
|
|
48
|
-
- Full TypeScript inference (insert vs select types)
|
|
49
|
-
- JSON columns with validation
|
|
50
|
-
- Foreign keys, unique constraints, defaults
|
|
51
|
-
- Composite indexes
|
|
52
|
-
|
|
53
|
-
## Generated Fields
|
|
54
|
-
|
|
55
|
-
Use `generated()` for SQL-generated values:
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
generated("autoincrement"); // INTEGER PRIMARY KEY AUTOINCREMENT
|
|
59
|
-
generated("now"); // DEFAULT (unixepoch()) - Unix timestamp
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Default Values
|
|
56
|
+
Booleans, dates, objects, arrays — everything round-trips as the type you declared. The schema is the source of truth for table creation, TypeScript types, and runtime coercion.
|
|
63
57
|
|
|
64
|
-
|
|
58
|
+
## API
|
|
65
59
|
|
|
66
60
|
```typescript
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
generated("autoincrement"); // auto-incrementing primary key
|
|
62
|
+
generated("now"); // defaults to current timestamp, returned as Date
|
|
63
|
+
type("string").default("pending"); // SQL DEFAULT
|
|
64
|
+
type("string").configure({ unique: true }); // UNIQUE
|
|
65
|
+
type("number.integer").configure({ references: "users.id", onDelete: "cascade" }); // FK
|
|
69
66
|
```
|
|
70
67
|
|
|
71
|
-
|
|
68
|
+
JSON columns are validated against the schema on write by default. To also validate on read, or to disable write validation:
|
|
72
69
|
|
|
73
70
|
```typescript
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`onDelete` options: `"cascade"`, `"set null"`, `"restrict"`
|
|
79
|
-
|
|
80
|
-
## Composite Indexes
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
const db = new Database({
|
|
84
|
-
tables: { ... },
|
|
85
|
-
indexes: {
|
|
86
|
-
posts: [
|
|
87
|
-
{ columns: ["user_id", "category_id"], unique: true },
|
|
88
|
-
{ columns: ["created_at"] },
|
|
89
|
-
],
|
|
90
|
-
},
|
|
71
|
+
new Database({
|
|
72
|
+
// ...
|
|
73
|
+
validation: { onRead: true }, // default: { onRead: false, onWrite: true }
|
|
91
74
|
});
|
|
92
75
|
```
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
import { JsonParseError, JsonValidationError } from "@lobomfz/db";
|
|
98
|
-
```
|
|
77
|
+
> **Note:** Migrations are not supported yet. Tables are created with `CREATE TABLE IF NOT EXISTS`.
|
|
99
78
|
|
|
100
79
|
## License
|
|
101
80
|
|
package/package.json
CHANGED
package/src/database.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Kysely } from "kysely";
|
|
|
3
3
|
import { BunSqliteDialect } from "./dialect/dialect";
|
|
4
4
|
import type { Type } from "arktype";
|
|
5
5
|
import type { GeneratedPreset } from "./generated";
|
|
6
|
+
import type { DbFieldMeta } from "./env";
|
|
6
7
|
import { DeserializePlugin, type ColumnCoercion, type ColumnsMap } from "./plugin";
|
|
7
8
|
import type {
|
|
8
9
|
DatabaseOptions,
|
|
@@ -26,7 +27,7 @@ type StructureProp = {
|
|
|
26
27
|
value: Type & {
|
|
27
28
|
branches: ArkBranch[];
|
|
28
29
|
proto?: unknown;
|
|
29
|
-
meta:
|
|
30
|
+
meta: DbFieldMeta & { _generated?: GeneratedPreset };
|
|
30
31
|
};
|
|
31
32
|
inner: { default?: unknown };
|
|
32
33
|
};
|
|
@@ -41,7 +42,7 @@ type Prop = {
|
|
|
41
42
|
isDate?: boolean;
|
|
42
43
|
isJson?: boolean;
|
|
43
44
|
jsonSchema?: Type;
|
|
44
|
-
meta?:
|
|
45
|
+
meta?: DbFieldMeta;
|
|
45
46
|
generated?: GeneratedPreset;
|
|
46
47
|
defaultValue?: unknown;
|
|
47
48
|
};
|
|
@@ -71,9 +72,14 @@ export class Database<T extends SchemaRecord> {
|
|
|
71
72
|
|
|
72
73
|
this.createTables();
|
|
73
74
|
|
|
75
|
+
const validation = {
|
|
76
|
+
onRead: options.validation?.onRead ?? false,
|
|
77
|
+
onWrite: options.validation?.onWrite ?? true,
|
|
78
|
+
};
|
|
79
|
+
|
|
74
80
|
this.kysely = new Kysely<TablesFromSchemas<T>>({
|
|
75
81
|
dialect: new BunSqliteDialect({ database: this.sqlite }),
|
|
76
|
-
plugins: [new DeserializePlugin(this.columns)],
|
|
82
|
+
plugins: [new DeserializePlugin(this.columns, validation)],
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -95,10 +101,10 @@ export class Database<T extends SchemaRecord> {
|
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
|
|
98
|
-
private normalizeProp(structureProp: StructureProp, parentSchema: Type)
|
|
104
|
+
private normalizeProp(structureProp: StructureProp, parentSchema: Type) {
|
|
99
105
|
const { key, value: v, inner } = structureProp;
|
|
100
|
-
const kind = structureProp.required ? "required" : "optional";
|
|
101
|
-
const generated = v.meta._generated
|
|
106
|
+
const kind: Prop["kind"] = structureProp.required ? "required" : "optional";
|
|
107
|
+
const generated = v.meta._generated;
|
|
102
108
|
const defaultValue = inner.default;
|
|
103
109
|
|
|
104
110
|
const nonNull = v.branches.filter((b) => b.unit !== null);
|
|
@@ -155,7 +161,7 @@ export class Database<T extends SchemaRecord> {
|
|
|
155
161
|
return "TEXT";
|
|
156
162
|
}
|
|
157
163
|
|
|
158
|
-
private columnConstraint(prop: Prop)
|
|
164
|
+
private columnConstraint(prop: Prop) {
|
|
159
165
|
if (prop.generated === "autoincrement") {
|
|
160
166
|
return "PRIMARY KEY AUTOINCREMENT";
|
|
161
167
|
}
|
|
@@ -171,7 +177,7 @@ export class Database<T extends SchemaRecord> {
|
|
|
171
177
|
return null;
|
|
172
178
|
}
|
|
173
179
|
|
|
174
|
-
private defaultClause(prop: Prop)
|
|
180
|
+
private defaultClause(prop: Prop) {
|
|
175
181
|
if (prop.generated === "now") {
|
|
176
182
|
return "DEFAULT (unixepoch())";
|
|
177
183
|
}
|
|
@@ -192,7 +198,7 @@ export class Database<T extends SchemaRecord> {
|
|
|
192
198
|
return `DEFAULT ${prop.defaultValue}`;
|
|
193
199
|
}
|
|
194
200
|
|
|
195
|
-
|
|
201
|
+
throw new Error(`Unsupported default value type: ${typeof prop.defaultValue}`);
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
private columnDef(prop: Prop) {
|
|
@@ -208,7 +214,7 @@ export class Database<T extends SchemaRecord> {
|
|
|
208
214
|
}
|
|
209
215
|
|
|
210
216
|
private foreignKey(prop: Prop) {
|
|
211
|
-
const ref = prop.meta?.references
|
|
217
|
+
const ref = prop.meta?.references;
|
|
212
218
|
|
|
213
219
|
if (!ref) {
|
|
214
220
|
return null;
|
|
@@ -218,7 +224,7 @@ export class Database<T extends SchemaRecord> {
|
|
|
218
224
|
|
|
219
225
|
let fk = `FOREIGN KEY ("${prop.key}") REFERENCES "${table}"("${column}")`;
|
|
220
226
|
|
|
221
|
-
const onDelete = prop.meta?.onDelete
|
|
227
|
+
const onDelete = prop.meta?.onDelete;
|
|
222
228
|
|
|
223
229
|
if (onDelete) {
|
|
224
230
|
fk += ` ON DELETE ${onDelete.toUpperCase()}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Database } from "bun:sqlite";
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
2
|
import type { CompiledQuery, DatabaseConnection, QueryResult } from "kysely";
|
|
3
3
|
import { serializeParam } from "./serialize";
|
|
4
4
|
|
|
@@ -9,30 +9,30 @@ export class BunSqliteConnection implements DatabaseConnection {
|
|
|
9
9
|
this.#db = db;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
executeQuery<O>(
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const stmt = this.#db.query(sql);
|
|
12
|
+
async executeQuery<O>(compiled: CompiledQuery): Promise<QueryResult<O>> {
|
|
13
|
+
const serializedParams = compiled.parameters.map(serializeParam);
|
|
14
|
+
|
|
15
|
+
const stmt = this.#db.query(compiled.sql);
|
|
16
16
|
|
|
17
17
|
if (stmt.columnNames.length > 0) {
|
|
18
|
-
return
|
|
18
|
+
return {
|
|
19
19
|
rows: stmt.all(serializedParams as any) as O[],
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const results = stmt.run(serializedParams as any);
|
|
24
24
|
|
|
25
|
-
return
|
|
25
|
+
return {
|
|
26
26
|
insertId: BigInt(results.lastInsertRowid),
|
|
27
27
|
numAffectedRows: BigInt(results.changes),
|
|
28
28
|
rows: [],
|
|
29
|
-
}
|
|
29
|
+
};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
async *streamQuery<R>(compiledQuery: CompiledQuery): AsyncIterableIterator<QueryResult<R>> {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const stmt = this.#db.prepare(sql);
|
|
33
|
+
const serializedParams = compiledQuery.parameters.map(serializeParam);
|
|
34
|
+
|
|
35
|
+
const stmt = this.#db.prepare(compiledQuery.sql);
|
|
36
36
|
|
|
37
37
|
for await (const row of stmt.iterate(serializedParams as any)) {
|
|
38
38
|
yield { rows: [row as R] };
|
package/src/dialect/driver.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Database } from "bun:sqlite";
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
2
|
import { CompiledQuery, type DatabaseConnection, type Driver } from "kysely";
|
|
3
3
|
import type { BunSqliteDialectConfig } from "./config";
|
|
4
4
|
import { BunSqliteConnection } from "./connection";
|
|
@@ -38,12 +38,10 @@ export class BunSqliteDriver implements Driver {
|
|
|
38
38
|
await connection.executeQuery(CompiledQuery.raw("rollback"));
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// oxlint-disable-next-line require-await
|
|
42
41
|
async releaseConnection(): Promise<void> {
|
|
43
42
|
this.#connectionMutex.unlock();
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
// oxlint-disable-next-line require-await
|
|
47
45
|
async destroy(): Promise<void> {
|
|
48
46
|
this.#db?.close();
|
|
49
47
|
}
|
package/src/index.ts
CHANGED
package/src/plugin.ts
CHANGED
|
@@ -1,40 +1,71 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type KyselyPlugin,
|
|
3
|
+
type RootOperationNode,
|
|
4
|
+
type UnknownRow,
|
|
5
|
+
type QueryId,
|
|
6
|
+
TableNode,
|
|
7
|
+
AliasNode,
|
|
8
|
+
ValuesNode,
|
|
9
|
+
ValueNode,
|
|
10
|
+
ColumnNode,
|
|
11
|
+
} from "kysely";
|
|
2
12
|
import { type } from "arktype";
|
|
3
13
|
import type { Type } from "arktype";
|
|
4
14
|
import { JsonParseError } from "./errors";
|
|
5
15
|
import { JsonValidationError } from "./validation-error";
|
|
16
|
+
import type { JsonValidation } from "./types";
|
|
6
17
|
|
|
7
18
|
export type ColumnCoercion = "boolean" | "date" | { type: "json"; schema: Type };
|
|
8
19
|
export type ColumnsMap = Map<string, Map<string, ColumnCoercion>>;
|
|
9
20
|
|
|
10
21
|
export class DeserializePlugin implements KyselyPlugin {
|
|
11
|
-
private queryNodes = new
|
|
22
|
+
private queryNodes = new WeakMap<QueryId, RootOperationNode>();
|
|
12
23
|
|
|
13
|
-
constructor(
|
|
24
|
+
constructor(
|
|
25
|
+
private columns: ColumnsMap,
|
|
26
|
+
private validation: Required<JsonValidation>,
|
|
27
|
+
) {}
|
|
14
28
|
|
|
15
29
|
transformQuery: KyselyPlugin["transformQuery"] = (args) => {
|
|
16
30
|
this.queryNodes.set(args.queryId, args.node);
|
|
17
31
|
|
|
32
|
+
if (this.validation.onWrite) {
|
|
33
|
+
this.validateWriteNode(args.node);
|
|
34
|
+
}
|
|
35
|
+
|
|
18
36
|
return args.node;
|
|
19
37
|
};
|
|
20
38
|
|
|
21
|
-
private getTableFromNode(node: RootOperationNode)
|
|
39
|
+
private getTableFromNode(node: RootOperationNode) {
|
|
22
40
|
switch (node.kind) {
|
|
23
41
|
case "InsertQueryNode":
|
|
24
|
-
return
|
|
42
|
+
return node.into?.table.identifier.name ?? null;
|
|
25
43
|
|
|
26
|
-
case "UpdateQueryNode":
|
|
27
|
-
|
|
44
|
+
case "UpdateQueryNode": {
|
|
45
|
+
if (node.table && TableNode.is(node.table)) {
|
|
46
|
+
return node.table.table.identifier.name;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
28
51
|
|
|
29
52
|
case "SelectQueryNode":
|
|
30
53
|
case "DeleteQueryNode": {
|
|
31
|
-
const fromNode =
|
|
54
|
+
const fromNode = node.from?.froms[0];
|
|
55
|
+
|
|
56
|
+
if (!fromNode) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (AliasNode.is(fromNode) && TableNode.is(fromNode.node)) {
|
|
61
|
+
return fromNode.node.table.identifier.name;
|
|
62
|
+
}
|
|
32
63
|
|
|
33
|
-
if (fromNode
|
|
34
|
-
return fromNode.
|
|
64
|
+
if (TableNode.is(fromNode)) {
|
|
65
|
+
return fromNode.table.identifier.name;
|
|
35
66
|
}
|
|
36
67
|
|
|
37
|
-
return
|
|
68
|
+
return null;
|
|
38
69
|
}
|
|
39
70
|
|
|
40
71
|
default:
|
|
@@ -42,6 +73,82 @@ export class DeserializePlugin implements KyselyPlugin {
|
|
|
42
73
|
}
|
|
43
74
|
}
|
|
44
75
|
|
|
76
|
+
private validateJsonValue(table: string, col: string, value: unknown, schema: Type) {
|
|
77
|
+
if (value === null || value === undefined) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = schema(value);
|
|
82
|
+
|
|
83
|
+
if (result instanceof type.errors) {
|
|
84
|
+
throw new JsonValidationError(table, col, result.summary);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private validateWriteNode(node: RootOperationNode) {
|
|
89
|
+
if (node.kind !== "InsertQueryNode" && node.kind !== "UpdateQueryNode") {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const table = this.getTableFromNode(node);
|
|
94
|
+
|
|
95
|
+
if (!table) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const cols = this.columns.get(table);
|
|
100
|
+
|
|
101
|
+
if (!cols) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const [col, value] of this.writeValues(node)) {
|
|
106
|
+
const coercion = cols.get(col);
|
|
107
|
+
|
|
108
|
+
if (!coercion || typeof coercion === "string") {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.validateJsonValue(table, col, value, coercion.schema);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private *writeValues(node: RootOperationNode) {
|
|
117
|
+
if (node.kind === "InsertQueryNode") {
|
|
118
|
+
const columns = node.columns?.map((c) => c.column.name);
|
|
119
|
+
|
|
120
|
+
if (!columns || !node.values || !ValuesNode.is(node.values)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const valueList of node.values.values) {
|
|
125
|
+
for (let i = 0; i < columns.length; i++) {
|
|
126
|
+
const col = columns[i]!;
|
|
127
|
+
|
|
128
|
+
if (valueList.kind === "PrimitiveValueListNode") {
|
|
129
|
+
yield [col, valueList.values[i]] as [string, unknown];
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const raw = valueList.values[i];
|
|
134
|
+
yield [col, raw && ValueNode.is(raw) ? raw.value : raw] as [string, unknown];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (node.kind !== "UpdateQueryNode" || !node.updates) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (const update of node.updates) {
|
|
146
|
+
if (ColumnNode.is(update.column) && ValueNode.is(update.value)) {
|
|
147
|
+
yield [update.column.column.name, update.value.value] as [string, unknown];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
45
152
|
private coerceRow(table: string, row: UnknownRow, cols: Map<string, ColumnCoercion>) {
|
|
46
153
|
for (const [col, coercion] of cols) {
|
|
47
154
|
if (!(col in row)) {
|
|
@@ -78,17 +185,14 @@ export class DeserializePlugin implements KyselyPlugin {
|
|
|
78
185
|
throw new JsonParseError(table, col, value, e);
|
|
79
186
|
}
|
|
80
187
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (validated instanceof type.errors) {
|
|
84
|
-
throw new JsonValidationError(table, col, validated.summary);
|
|
188
|
+
if (this.validation.onRead) {
|
|
189
|
+
this.validateJsonValue(table, col, parsed, coercion.schema);
|
|
85
190
|
}
|
|
86
191
|
|
|
87
|
-
row[col] =
|
|
192
|
+
row[col] = parsed;
|
|
88
193
|
}
|
|
89
194
|
}
|
|
90
195
|
|
|
91
|
-
// oxlint-disable-next-line require-await
|
|
92
196
|
transformResult: KyselyPlugin["transformResult"] = async (args) => {
|
|
93
197
|
const node = this.queryNodes.get(args.queryId);
|
|
94
198
|
|
package/src/types.ts
CHANGED
|
@@ -58,8 +58,14 @@ export type DatabaseSchema<T extends SchemaRecord> = {
|
|
|
58
58
|
indexes?: IndexesConfig<T>;
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
export type JsonValidation = {
|
|
62
|
+
onRead?: boolean;
|
|
63
|
+
onWrite?: boolean;
|
|
64
|
+
};
|
|
65
|
+
|
|
61
66
|
export type DatabaseOptions<T extends SchemaRecord> = {
|
|
62
67
|
path: string;
|
|
63
68
|
schema: DatabaseSchema<T>;
|
|
64
69
|
pragmas?: DatabasePragmas;
|
|
70
|
+
validation?: JsonValidation;
|
|
65
71
|
};
|