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/src/entity.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * entity.ts — Entity augmentation logic extracted from Database class.
3
+ *
4
+ * Handles attaching .update(), .delete(), relationship navigation methods,
5
+ * and the auto-persist proxy to raw row objects.
6
+ */
7
+ import type { AugmentedEntity, Relationship } from './types';
8
+ import { getStorableFields, transformForStorage } from './schema';
9
+ import type { DatabaseContext } from './context';
10
+ import { getById, findMany, update, deleteEntity } from './crud';
11
+
12
+ /**
13
+ * Augment a raw entity with:
14
+ * - .update(data) → persist partial update
15
+ * - .delete() → delete from DB
16
+ * - Lazy relationship accessors (author(), books(), etc.)
17
+ * - Auto-persist proxy: `entity.name = 'New'` auto-updates DB
18
+ */
19
+ export function attachMethods<T extends Record<string, any>>(
20
+ ctx: DatabaseContext,
21
+ entityName: string,
22
+ entity: T,
23
+ ): AugmentedEntity<any> {
24
+ const augmented = entity as any;
25
+ augmented.update = (data: any) => update(ctx, entityName, entity.id, data);
26
+ augmented.delete = () => deleteEntity(ctx, entityName, entity.id);
27
+
28
+ // Attach lazy relationship navigation
29
+ for (const rel of ctx.relationships) {
30
+ if (rel.from === entityName && rel.type === 'belongs-to') {
31
+ // book.author() → lazy load parent via author_id FK
32
+ augmented[rel.relationshipField] = () => {
33
+ const fkValue = entity[rel.foreignKey];
34
+ return fkValue ? getById(ctx, rel.to, fkValue) : null;
35
+ };
36
+ } else if (rel.from === entityName && rel.type === 'one-to-many') {
37
+ // author.books() → lazy load children
38
+ const belongsToRel = ctx.relationships.find(
39
+ r => r.type === 'belongs-to' && r.from === rel.to && r.to === rel.from
40
+ );
41
+ if (belongsToRel) {
42
+ const fk = belongsToRel.foreignKey;
43
+ augmented[rel.relationshipField] = () => {
44
+ return findMany(ctx, rel.to, { [fk]: entity.id });
45
+ };
46
+ }
47
+ }
48
+ }
49
+
50
+ // Auto-persist proxy: setting a field auto-updates the DB row
51
+ const storableFieldNames = new Set(getStorableFields(ctx.schemas[entityName]!).map(f => f.name));
52
+ return new Proxy(augmented, {
53
+ set: (target, prop: string, value) => {
54
+ if (storableFieldNames.has(prop) && target[prop] !== value) {
55
+ update(ctx, entityName, target.id, { [prop]: value });
56
+ }
57
+ target[prop] = value;
58
+ return true;
59
+ },
60
+ get: (target, prop, receiver) => Reflect.get(target, prop, receiver),
61
+ });
62
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * sql-helpers.ts — SQL utility functions extracted from Database class.
3
+ *
4
+ * Contains WHERE clause building and other SQL-level helpers.
5
+ */
6
+ import { transformForStorage } from './schema';
7
+
8
+ /**
9
+ * Build a parameterized WHERE clause from a conditions object.
10
+ *
11
+ * Supports:
12
+ * - Simple equality: `{ name: 'Alice' }`
13
+ * - Operators: `{ age: { $gt: 18 } }`
14
+ * - $in: `{ status: { $in: ['active', 'pending'] } }`
15
+ * - $or: `{ $or: [{ name: 'Alice' }, { name: 'Bob' }] }`
16
+ */
17
+ export function buildWhereClause(conditions: Record<string, any>, tablePrefix?: string): { clause: string; values: any[] } {
18
+ const parts: string[] = [];
19
+ const values: any[] = [];
20
+
21
+ for (const key in conditions) {
22
+ if (key.startsWith('$')) {
23
+ if (key === '$or' && Array.isArray(conditions[key])) {
24
+ const orBranches = conditions[key] as Record<string, any>[];
25
+ const orParts: string[] = [];
26
+ for (const branch of orBranches) {
27
+ const sub = buildWhereClause(branch, tablePrefix);
28
+ if (sub.clause) {
29
+ orParts.push(`(${sub.clause.replace(/^WHERE /, '')})`);
30
+ values.push(...sub.values);
31
+ }
32
+ }
33
+ if (orParts.length > 0) parts.push(`(${orParts.join(' OR ')})`);
34
+ }
35
+ continue;
36
+ }
37
+ const value = conditions[key];
38
+ const fieldName = tablePrefix ? `"${tablePrefix}"."${key}"` : `"${key}"`;
39
+
40
+ if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
41
+ const operator = Object.keys(value)[0];
42
+ if (!operator?.startsWith('$')) {
43
+ throw new Error(`Querying on nested object '${key}' not supported. Use operators like $gt.`);
44
+ }
45
+ const operand = value[operator];
46
+
47
+ if (operator === '$in') {
48
+ if (!Array.isArray(operand)) throw new Error(`$in for '${key}' requires an array`);
49
+ if (operand.length === 0) { parts.push('1 = 0'); continue; }
50
+ parts.push(`${fieldName} IN (${operand.map(() => '?').join(', ')})`);
51
+ values.push(...operand.map((v: any) => transformForStorage({ v }).v));
52
+ continue;
53
+ }
54
+
55
+ if (operator === '$notIn') {
56
+ if (!Array.isArray(operand)) throw new Error(`$notIn for '${key}' requires an array`);
57
+ if (operand.length === 0) continue; // no-op: everything is "not in" an empty set
58
+ parts.push(`${fieldName} NOT IN (${operand.map(() => '?').join(', ')})`);
59
+ values.push(...operand.map((v: any) => transformForStorage({ v }).v));
60
+ continue;
61
+ }
62
+
63
+ if (operator === '$like') {
64
+ parts.push(`${fieldName} LIKE ?`);
65
+ values.push(operand);
66
+ continue;
67
+ }
68
+
69
+ if (operator === '$between') {
70
+ if (!Array.isArray(operand) || operand.length !== 2) throw new Error(`$between for '${key}' requires [min, max]`);
71
+ parts.push(`${fieldName} BETWEEN ? AND ?`);
72
+ values.push(transformForStorage({ v: operand[0] }).v, transformForStorage({ v: operand[1] }).v);
73
+ continue;
74
+ }
75
+
76
+ const sqlOp = ({ $gt: '>', $gte: '>=', $lt: '<', $lte: '<=', $ne: '!=' } as Record<string, string>)[operator];
77
+ if (!sqlOp) throw new Error(`Unsupported operator '${operator}' on '${key}'`);
78
+ parts.push(`${fieldName} ${sqlOp} ?`);
79
+ values.push(transformForStorage({ operand }).operand);
80
+ } else {
81
+ parts.push(`${fieldName} = ?`);
82
+ values.push(transformForStorage({ value }).value);
83
+ }
84
+ }
85
+
86
+ return { clause: parts.length > 0 ? `WHERE ${parts.join(' AND ')}` : '', values };
87
+ }
package/src/index.ts CHANGED
@@ -9,14 +9,13 @@ export type { DatabaseType } from './database';
9
9
  export type {
10
10
  SchemaMap, DatabaseOptions, Relationship,
11
11
  EntityAccessor, TypedAccessors, AugmentedEntity, UpdateBuilder,
12
- InferSchema, EntityData, IndexDef,
12
+ InferSchema, EntityData, IndexDef, ChangeEvent,
13
13
  ProxyColumns, ColumnRef,
14
14
  } from './types';
15
15
 
16
16
  export { z } from 'zod';
17
17
 
18
- export { QueryBuilder } from './query-builder';
19
- export { ColumnNode, type ProxyQueryResult } from './proxy-query';
18
+ export { QueryBuilder, ColumnNode, compileIQO, type ProxyQueryResult } from './query';
20
19
  export {
21
20
  type ASTNode, type WhereCallback, type SetCallback,
22
21
  type TypedColumnProxy, type FunctionProxy, type Operators,