sqlite-zod-orm 3.8.0 → 3.9.0
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 +146 -93
- package/dist/index.js +1631 -1604
- package/package.json +5 -3
- package/src/ast.ts +1 -1
- package/src/context.ts +25 -0
- package/src/crud.ts +145 -0
- package/src/database.ts +170 -396
- package/src/entity.ts +62 -0
- package/src/helpers.ts +87 -0
- package/src/index.ts +2 -3
- package/src/query.ts +856 -0
- package/src/types.ts +19 -4
- package/dist/satidb.js +0 -26
- package/src/build.ts +0 -21
- package/src/proxy-query.ts +0 -312
- package/src/query-builder.ts +0 -669
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sqlite-zod-orm",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "Type-safe SQLite ORM for Bun — Zod schemas, fluent queries, auto relationships, zero SQL",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "bun
|
|
16
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
17
18
|
"test": "bun test",
|
|
19
|
+
"bench": "bun bench/triggers-vs-naive.ts && bun bench/poll-strategy.ts && bun bench/indexes.ts",
|
|
18
20
|
"prepublishOnly": "bun run build"
|
|
19
21
|
},
|
|
20
22
|
"files": [
|
|
@@ -52,4 +54,4 @@
|
|
|
52
54
|
"engines": {
|
|
53
55
|
"bun": ">=1.0.0"
|
|
54
56
|
}
|
|
55
|
-
}
|
|
57
|
+
}
|
package/src/ast.ts
CHANGED
package/src/context.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* context.ts — Shared context interface for Database internals.
|
|
3
|
+
*
|
|
4
|
+
* Provides a slim interface so extracted modules (crud.ts, entity.ts, etc.)
|
|
5
|
+
* can access the Database's internals without importing the full class.
|
|
6
|
+
*/
|
|
7
|
+
import type { Database as SqliteDatabase } from 'bun:sqlite';
|
|
8
|
+
import type { SchemaMap, Relationship, AugmentedEntity } from './types';
|
|
9
|
+
|
|
10
|
+
export interface DatabaseContext {
|
|
11
|
+
/** The raw bun:sqlite Database handle. */
|
|
12
|
+
db: SqliteDatabase;
|
|
13
|
+
|
|
14
|
+
/** All registered Zod schemas, keyed by entity name. */
|
|
15
|
+
schemas: SchemaMap;
|
|
16
|
+
|
|
17
|
+
/** Parsed relationship descriptors. */
|
|
18
|
+
relationships: Relationship[];
|
|
19
|
+
|
|
20
|
+
/** Augment a raw row with .update()/.delete()/nav methods + auto-persist proxy. */
|
|
21
|
+
attachMethods<T extends Record<string, any>>(entityName: string, entity: T): AugmentedEntity<any>;
|
|
22
|
+
|
|
23
|
+
/** Build a WHERE clause from a conditions object. */
|
|
24
|
+
buildWhereClause(conditions: Record<string, any>, tablePrefix?: string): { clause: string; values: any[] };
|
|
25
|
+
}
|
package/src/crud.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* crud.ts — CRUD operations extracted from Database class.
|
|
3
|
+
*
|
|
4
|
+
* Each function accepts a `DatabaseContext` so it can access
|
|
5
|
+
* the db handle, schemas, and entity methods without tight coupling.
|
|
6
|
+
*/
|
|
7
|
+
import type { AugmentedEntity, UpdateBuilder } from './types';
|
|
8
|
+
import { asZodObject } from './types';
|
|
9
|
+
import { transformForStorage, transformFromStorage } from './schema';
|
|
10
|
+
import type { DatabaseContext } from './context';
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Read helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export function getById(ctx: DatabaseContext, entityName: string, id: number): AugmentedEntity<any> | null {
|
|
17
|
+
const row = ctx.db.query(`SELECT * FROM "${entityName}" WHERE id = ?`).get(id) as any;
|
|
18
|
+
if (!row) return null;
|
|
19
|
+
return ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]!));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getOne(ctx: DatabaseContext, entityName: string, conditions: Record<string, any>): AugmentedEntity<any> | null {
|
|
23
|
+
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
24
|
+
const row = ctx.db.query(`SELECT * FROM "${entityName}" ${clause} LIMIT 1`).get(...values) as any;
|
|
25
|
+
if (!row) return null;
|
|
26
|
+
return ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]!));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function findMany(ctx: DatabaseContext, entityName: string, conditions: Record<string, any> = {}): AugmentedEntity<any>[] {
|
|
30
|
+
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
31
|
+
const rows = ctx.db.query(`SELECT * FROM "${entityName}" ${clause}`).all(...values);
|
|
32
|
+
return rows.map((row: any) =>
|
|
33
|
+
ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]!))
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Write operations
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
export function insert<T extends Record<string, any>>(ctx: DatabaseContext, entityName: string, data: Omit<T, 'id'>): AugmentedEntity<any> {
|
|
42
|
+
const schema = ctx.schemas[entityName]!;
|
|
43
|
+
const validatedData = asZodObject(schema).passthrough().parse(data);
|
|
44
|
+
const transformed = transformForStorage(validatedData);
|
|
45
|
+
const columns = Object.keys(transformed);
|
|
46
|
+
|
|
47
|
+
const quotedCols = columns.map(c => `"${c}"`);
|
|
48
|
+
const sql = columns.length === 0
|
|
49
|
+
? `INSERT INTO "${entityName}" DEFAULT VALUES`
|
|
50
|
+
: `INSERT INTO "${entityName}" (${quotedCols.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
|
|
51
|
+
|
|
52
|
+
const result = ctx.db.query(sql).run(...Object.values(transformed));
|
|
53
|
+
const newEntity = getById(ctx, entityName, result.lastInsertRowid as number);
|
|
54
|
+
if (!newEntity) throw new Error('Failed to retrieve entity after insertion');
|
|
55
|
+
|
|
56
|
+
return newEntity;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function update<T extends Record<string, any>>(ctx: DatabaseContext, entityName: string, id: number, data: Partial<Omit<T, 'id'>>): AugmentedEntity<any> | null {
|
|
60
|
+
const schema = ctx.schemas[entityName]!;
|
|
61
|
+
const validatedData = asZodObject(schema).partial().parse(data);
|
|
62
|
+
const transformed = transformForStorage(validatedData);
|
|
63
|
+
if (Object.keys(transformed).length === 0) return getById(ctx, entityName, id);
|
|
64
|
+
|
|
65
|
+
const setClause = Object.keys(transformed).map(key => `"${key}" = ?`).join(', ');
|
|
66
|
+
ctx.db.query(`UPDATE "${entityName}" SET ${setClause} WHERE id = ?`).run(...Object.values(transformed), id);
|
|
67
|
+
|
|
68
|
+
return getById(ctx, entityName, id);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function updateWhere(ctx: DatabaseContext, entityName: string, data: Record<string, any>, conditions: Record<string, any>): number {
|
|
72
|
+
const schema = ctx.schemas[entityName]!;
|
|
73
|
+
const validatedData = asZodObject(schema).partial().parse(data);
|
|
74
|
+
const transformed = transformForStorage(validatedData);
|
|
75
|
+
if (Object.keys(transformed).length === 0) return 0;
|
|
76
|
+
|
|
77
|
+
const { clause, values: whereValues } = ctx.buildWhereClause(conditions);
|
|
78
|
+
if (!clause) throw new Error('update().where() requires at least one condition');
|
|
79
|
+
|
|
80
|
+
const setCols = Object.keys(transformed);
|
|
81
|
+
const setClause = setCols.map(key => `"${key}" = ?`).join(', ');
|
|
82
|
+
const result = ctx.db.query(`UPDATE "${entityName}" SET ${setClause} ${clause}`).run(
|
|
83
|
+
...setCols.map(key => transformed[key]),
|
|
84
|
+
...whereValues
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return (result as any).changes ?? 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createUpdateBuilder(ctx: DatabaseContext, entityName: string, data: Record<string, any>): UpdateBuilder<any> {
|
|
91
|
+
let _conditions: Record<string, any> = {};
|
|
92
|
+
const builder: UpdateBuilder<any> = {
|
|
93
|
+
where: (conditions) => { _conditions = { ..._conditions, ...conditions }; return builder; },
|
|
94
|
+
exec: () => updateWhere(ctx, entityName, data, _conditions),
|
|
95
|
+
};
|
|
96
|
+
return builder;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function upsert<T extends Record<string, any>>(ctx: DatabaseContext, entityName: string, data: any, conditions: any = {}): AugmentedEntity<any> {
|
|
100
|
+
const hasId = data?.id && typeof data.id === 'number';
|
|
101
|
+
const existing = hasId
|
|
102
|
+
? getById(ctx, entityName, data.id)
|
|
103
|
+
: Object.keys(conditions ?? {}).length > 0
|
|
104
|
+
? getOne(ctx, entityName, conditions)
|
|
105
|
+
: null;
|
|
106
|
+
|
|
107
|
+
if (existing) {
|
|
108
|
+
const updateData = { ...data };
|
|
109
|
+
delete updateData.id;
|
|
110
|
+
return update(ctx, entityName, existing.id, updateData) as AugmentedEntity<any>;
|
|
111
|
+
}
|
|
112
|
+
const insertData = { ...(conditions ?? {}), ...(data ?? {}) };
|
|
113
|
+
delete insertData.id;
|
|
114
|
+
return insert(ctx, entityName, insertData);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function deleteEntity(ctx: DatabaseContext, entityName: string, id: number): void {
|
|
118
|
+
ctx.db.query(`DELETE FROM "${entityName}" WHERE id = ?`).run(id);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Insert multiple rows in a single transaction for better performance. */
|
|
122
|
+
export function insertMany<T extends Record<string, any>>(ctx: DatabaseContext, entityName: string, rows: Omit<T, 'id'>[]): AugmentedEntity<any>[] {
|
|
123
|
+
if (rows.length === 0) return [];
|
|
124
|
+
const schema = ctx.schemas[entityName]!;
|
|
125
|
+
const zodSchema = asZodObject(schema).passthrough();
|
|
126
|
+
|
|
127
|
+
const txn = ctx.db.transaction(() => {
|
|
128
|
+
const ids: number[] = [];
|
|
129
|
+
for (const data of rows) {
|
|
130
|
+
const validatedData = zodSchema.parse(data);
|
|
131
|
+
const transformed = transformForStorage(validatedData);
|
|
132
|
+
const columns = Object.keys(transformed);
|
|
133
|
+
const quotedCols = columns.map(c => `"${c}"`);
|
|
134
|
+
const sql = columns.length === 0
|
|
135
|
+
? `INSERT INTO "${entityName}" DEFAULT VALUES`
|
|
136
|
+
: `INSERT INTO "${entityName}" (${quotedCols.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
|
|
137
|
+
const result = ctx.db.query(sql).run(...Object.values(transformed));
|
|
138
|
+
ids.push(result.lastInsertRowid as number);
|
|
139
|
+
}
|
|
140
|
+
return ids;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const ids = txn();
|
|
144
|
+
return ids.map((id: number) => getById(ctx, entityName, id)!).filter(Boolean);
|
|
145
|
+
}
|