sqlite-zod-orm 3.13.0 → 3.14.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/dist/index.js +35 -0
- package/package.json +1 -1
- package/src/builder.ts +14 -0
- package/src/crud.ts +13 -0
- package/src/database.ts +2 -1
- package/src/iqo.ts +9 -0
- package/src/schema.ts +21 -0
- package/src/types.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -4042,6 +4042,8 @@ function zodTypeToSqlType(zodType) {
|
|
|
4042
4042
|
return "TEXT";
|
|
4043
4043
|
if (zodType instanceof exports_external.ZodNumber || zodType instanceof exports_external.ZodBoolean)
|
|
4044
4044
|
return "INTEGER";
|
|
4045
|
+
if (zodType instanceof exports_external.ZodEnum)
|
|
4046
|
+
return "TEXT";
|
|
4045
4047
|
if (zodType._def.typeName === "ZodInstanceOf" && zodType._def.type === Buffer)
|
|
4046
4048
|
return "BLOB";
|
|
4047
4049
|
return "TEXT";
|
|
@@ -4053,6 +4055,8 @@ function transformForStorage(data) {
|
|
|
4053
4055
|
transformed[key] = value.toISOString();
|
|
4054
4056
|
} else if (typeof value === "boolean") {
|
|
4055
4057
|
transformed[key] = value ? 1 : 0;
|
|
4058
|
+
} else if (value !== null && value !== undefined && typeof value === "object" && !(value instanceof Buffer)) {
|
|
4059
|
+
transformed[key] = JSON.stringify(value);
|
|
4056
4060
|
} else {
|
|
4057
4061
|
transformed[key] = value;
|
|
4058
4062
|
}
|
|
@@ -4073,12 +4077,23 @@ function transformFromStorage(row, schema) {
|
|
|
4073
4077
|
transformed[key] = new Date(value);
|
|
4074
4078
|
} else if (fieldSchema instanceof exports_external.ZodBoolean && typeof value === "number") {
|
|
4075
4079
|
transformed[key] = value === 1;
|
|
4080
|
+
} else if (isJsonSchema(fieldSchema) && typeof value === "string") {
|
|
4081
|
+
try {
|
|
4082
|
+
transformed[key] = JSON.parse(value);
|
|
4083
|
+
} catch {
|
|
4084
|
+
transformed[key] = value;
|
|
4085
|
+
}
|
|
4076
4086
|
} else {
|
|
4077
4087
|
transformed[key] = value;
|
|
4078
4088
|
}
|
|
4079
4089
|
}
|
|
4080
4090
|
return transformed;
|
|
4081
4091
|
}
|
|
4092
|
+
function isJsonSchema(schema) {
|
|
4093
|
+
if (!schema)
|
|
4094
|
+
return false;
|
|
4095
|
+
return schema instanceof exports_external.ZodObject || schema instanceof exports_external.ZodArray || schema instanceof exports_external.ZodRecord || schema instanceof exports_external.ZodTuple || schema instanceof exports_external.ZodUnion || schema instanceof exports_external.ZodDiscriminatedUnion;
|
|
4096
|
+
}
|
|
4082
4097
|
|
|
4083
4098
|
// src/ast.ts
|
|
4084
4099
|
var wrapNode = (val) => val !== null && typeof val === "object" && ("type" in val) ? val : { type: "literal", value: val };
|
|
@@ -4244,6 +4259,12 @@ function compileIQO(tableName, iqo) {
|
|
|
4244
4259
|
}
|
|
4245
4260
|
}
|
|
4246
4261
|
}
|
|
4262
|
+
if (iqo.rawWheres && iqo.rawWheres.length > 0) {
|
|
4263
|
+
for (const rw of iqo.rawWheres) {
|
|
4264
|
+
sql += sql.includes(" WHERE ") ? ` AND (${rw.sql})` : ` WHERE (${rw.sql})`;
|
|
4265
|
+
params.push(...rw.params);
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4247
4268
|
if (iqo.groupBy.length > 0) {
|
|
4248
4269
|
sql += ` GROUP BY ${iqo.groupBy.join(", ")}`;
|
|
4249
4270
|
}
|
|
@@ -4293,6 +4314,7 @@ class QueryBuilder {
|
|
|
4293
4314
|
selects: [],
|
|
4294
4315
|
wheres: [],
|
|
4295
4316
|
whereOrs: [],
|
|
4317
|
+
rawWheres: [],
|
|
4296
4318
|
whereAST: null,
|
|
4297
4319
|
joins: [],
|
|
4298
4320
|
groupBy: [],
|
|
@@ -4403,6 +4425,10 @@ class QueryBuilder {
|
|
|
4403
4425
|
this.iqo.raw = true;
|
|
4404
4426
|
return this;
|
|
4405
4427
|
}
|
|
4428
|
+
whereRaw(sql, params = []) {
|
|
4429
|
+
this.iqo.rawWheres.push({ sql, params });
|
|
4430
|
+
return this;
|
|
4431
|
+
}
|
|
4406
4432
|
with(...relations) {
|
|
4407
4433
|
this.iqo.includes.push(...relations);
|
|
4408
4434
|
return this;
|
|
@@ -5001,6 +5027,14 @@ function upsert(ctx, entityName, data, conditions = {}) {
|
|
|
5001
5027
|
delete insertData.id;
|
|
5002
5028
|
return insert(ctx, entityName, insertData);
|
|
5003
5029
|
}
|
|
5030
|
+
function findOrCreate(ctx, entityName, conditions, defaults = {}) {
|
|
5031
|
+
const existing = getOne(ctx, entityName, conditions);
|
|
5032
|
+
if (existing)
|
|
5033
|
+
return { entity: existing, created: false };
|
|
5034
|
+
const data = { ...conditions, ...defaults };
|
|
5035
|
+
delete data.id;
|
|
5036
|
+
return { entity: insert(ctx, entityName, data), created: true };
|
|
5037
|
+
}
|
|
5004
5038
|
function deleteEntity(ctx, entityName, id) {
|
|
5005
5039
|
const hooks = ctx.hooks[entityName];
|
|
5006
5040
|
if (hooks?.beforeDelete) {
|
|
@@ -5184,6 +5218,7 @@ class _Database {
|
|
|
5184
5218
|
},
|
|
5185
5219
|
upsert: (conditions, data) => upsert(this._ctx, entityName, data, conditions),
|
|
5186
5220
|
upsertMany: (rows, conditions) => upsertMany(this._ctx, entityName, rows, conditions),
|
|
5221
|
+
findOrCreate: (conditions, defaults) => findOrCreate(this._ctx, entityName, conditions, defaults),
|
|
5187
5222
|
delete: (id) => {
|
|
5188
5223
|
if (typeof id === "number") {
|
|
5189
5224
|
const hooks = this._ctx.hooks[entityName];
|
package/package.json
CHANGED
package/src/builder.ts
CHANGED
|
@@ -54,6 +54,7 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
54
54
|
selects: [],
|
|
55
55
|
wheres: [],
|
|
56
56
|
whereOrs: [],
|
|
57
|
+
rawWheres: [],
|
|
57
58
|
whereAST: null,
|
|
58
59
|
joins: [],
|
|
59
60
|
groupBy: [],
|
|
@@ -215,6 +216,19 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
215
216
|
return this;
|
|
216
217
|
}
|
|
217
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Add a raw SQL WHERE fragment with parameterized values.
|
|
221
|
+
* Can be combined with `.where()` — fragments are AND'd together.
|
|
222
|
+
*
|
|
223
|
+
* ```ts
|
|
224
|
+
* db.users.select().whereRaw('score > ? AND role != ?', [50, 'guest']).all()
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
whereRaw(sql: string, params: any[] = []): this {
|
|
228
|
+
this.iqo.rawWheres.push({ sql, params });
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
|
|
218
232
|
/**
|
|
219
233
|
* Eagerly load a related entity and attach as an array property.
|
|
220
234
|
*
|
package/src/crud.ts
CHANGED
|
@@ -156,6 +156,19 @@ export function upsert<T extends Record<string, any>>(ctx: DatabaseContext, enti
|
|
|
156
156
|
return insert(ctx, entityName, insertData);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/** Find a row matching conditions, or create it with the merged data. Returns { entity, created }. */
|
|
160
|
+
export function findOrCreate<T extends Record<string, any>>(
|
|
161
|
+
ctx: DatabaseContext, entityName: string,
|
|
162
|
+
conditions: Record<string, any>,
|
|
163
|
+
defaults: Record<string, any> = {},
|
|
164
|
+
): { entity: AugmentedEntity<any>; created: boolean } {
|
|
165
|
+
const existing = getOne(ctx, entityName, conditions);
|
|
166
|
+
if (existing) return { entity: existing, created: false };
|
|
167
|
+
const data = { ...conditions, ...defaults };
|
|
168
|
+
delete (data as any).id;
|
|
169
|
+
return { entity: insert(ctx, entityName, data), created: true };
|
|
170
|
+
}
|
|
171
|
+
|
|
159
172
|
export function deleteEntity(ctx: DatabaseContext, entityName: string, id: number): void {
|
|
160
173
|
// beforeDelete hook — return false to cancel
|
|
161
174
|
const hooks = ctx.hooks[entityName];
|
package/src/database.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { DatabaseContext } from './context';
|
|
|
24
24
|
import { buildWhereClause } from './helpers';
|
|
25
25
|
import { attachMethods } from './entity';
|
|
26
26
|
import {
|
|
27
|
-
insert, insertMany, update, upsert, upsertMany, deleteEntity, createDeleteBuilder,
|
|
27
|
+
insert, insertMany, update, upsert, upsertMany, findOrCreate, deleteEntity, createDeleteBuilder,
|
|
28
28
|
getById, getOne, findMany, updateWhere, createUpdateBuilder,
|
|
29
29
|
} from './crud';
|
|
30
30
|
|
|
@@ -107,6 +107,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
107
107
|
},
|
|
108
108
|
upsert: (conditions, data) => upsert(this._ctx, entityName, data, conditions),
|
|
109
109
|
upsertMany: (rows: any[], conditions?: any) => upsertMany(this._ctx, entityName, rows, conditions),
|
|
110
|
+
findOrCreate: (conditions: any, defaults?: any) => findOrCreate(this._ctx, entityName, conditions, defaults),
|
|
110
111
|
delete: ((id?: any) => {
|
|
111
112
|
if (typeof id === 'number') {
|
|
112
113
|
// beforeDelete hook — return false to cancel
|
package/src/iqo.ts
CHANGED
|
@@ -31,6 +31,7 @@ export interface IQO {
|
|
|
31
31
|
selects: string[];
|
|
32
32
|
wheres: WhereCondition[];
|
|
33
33
|
whereOrs: WhereCondition[][]; // Each sub-array is an OR group
|
|
34
|
+
rawWheres: { sql: string; params: any[] }[]; // Raw WHERE fragments
|
|
34
35
|
whereAST: ASTNode | null;
|
|
35
36
|
joins: JoinClause[];
|
|
36
37
|
groupBy: string[];
|
|
@@ -162,6 +163,14 @@ export function compileIQO(tableName: string, iqo: IQO): { sql: string; params:
|
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
// Append raw WHERE fragments
|
|
167
|
+
if (iqo.rawWheres && iqo.rawWheres.length > 0) {
|
|
168
|
+
for (const rw of iqo.rawWheres) {
|
|
169
|
+
sql += sql.includes(' WHERE ') ? ` AND (${rw.sql})` : ` WHERE (${rw.sql})`;
|
|
170
|
+
params.push(...rw.params);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
165
174
|
// GROUP BY
|
|
166
175
|
if (iqo.groupBy.length > 0) {
|
|
167
176
|
sql += ` GROUP BY ${iqo.groupBy.join(', ')}`;
|
package/src/schema.ts
CHANGED
|
@@ -80,7 +80,9 @@ export function zodTypeToSqlType(zodType: ZodType): string {
|
|
|
80
80
|
}
|
|
81
81
|
if (zodType instanceof z.ZodString || zodType instanceof z.ZodDate) return 'TEXT';
|
|
82
82
|
if (zodType instanceof z.ZodNumber || zodType instanceof z.ZodBoolean) return 'INTEGER';
|
|
83
|
+
if (zodType instanceof z.ZodEnum) return 'TEXT';
|
|
83
84
|
if ((zodType as any)._def.typeName === 'ZodInstanceOf' && (zodType as any)._def.type === Buffer) return 'BLOB';
|
|
85
|
+
// z.object(), z.array(), z.record() → stored as JSON TEXT
|
|
84
86
|
return 'TEXT';
|
|
85
87
|
}
|
|
86
88
|
|
|
@@ -92,6 +94,9 @@ export function transformForStorage(data: Record<string, any>): Record<string, a
|
|
|
92
94
|
transformed[key] = value.toISOString();
|
|
93
95
|
} else if (typeof value === 'boolean') {
|
|
94
96
|
transformed[key] = value ? 1 : 0;
|
|
97
|
+
} else if (value !== null && value !== undefined && typeof value === 'object' && !(value instanceof Buffer)) {
|
|
98
|
+
// Auto-serialize objects and arrays to JSON
|
|
99
|
+
transformed[key] = JSON.stringify(value);
|
|
95
100
|
} else {
|
|
96
101
|
transformed[key] = value;
|
|
97
102
|
}
|
|
@@ -114,9 +119,25 @@ export function transformFromStorage(row: Record<string, any>, schema: z.ZodType
|
|
|
114
119
|
transformed[key] = new Date(value);
|
|
115
120
|
} else if (fieldSchema instanceof z.ZodBoolean && typeof value === 'number') {
|
|
116
121
|
transformed[key] = value === 1;
|
|
122
|
+
} else if (isJsonSchema(fieldSchema) && typeof value === 'string') {
|
|
123
|
+
// Auto-parse JSON columns
|
|
124
|
+
try { transformed[key] = JSON.parse(value); } catch { transformed[key] = value; }
|
|
117
125
|
} else {
|
|
118
126
|
transformed[key] = value;
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
129
|
return transformed;
|
|
122
130
|
}
|
|
131
|
+
|
|
132
|
+
/** Check if a Zod schema represents a JSON-serializable type */
|
|
133
|
+
function isJsonSchema(schema: any): boolean {
|
|
134
|
+
if (!schema) return false;
|
|
135
|
+
return (
|
|
136
|
+
schema instanceof z.ZodObject ||
|
|
137
|
+
schema instanceof z.ZodArray ||
|
|
138
|
+
schema instanceof z.ZodRecord ||
|
|
139
|
+
schema instanceof z.ZodTuple ||
|
|
140
|
+
schema instanceof z.ZodUnion ||
|
|
141
|
+
schema instanceof z.ZodDiscriminatedUnion
|
|
142
|
+
);
|
|
143
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -206,6 +206,7 @@ export type NavEntityAccessor<
|
|
|
206
206
|
& ((data: Partial<Omit<z.input<S[Table & keyof S]>, 'id'>>) => UpdateBuilder<NavEntity<S, R, Table>>);
|
|
207
207
|
upsert: (conditions?: Partial<z.infer<S[Table & keyof S]>>, data?: Partial<z.infer<S[Table & keyof S]>>) => NavEntity<S, R, Table>;
|
|
208
208
|
upsertMany: (rows: Partial<z.infer<S[Table & keyof S]>>[], conditions?: Partial<z.infer<S[Table & keyof S]>>) => NavEntity<S, R, Table>[];
|
|
209
|
+
findOrCreate: (conditions: Partial<z.infer<S[Table & keyof S]>>, defaults?: Partial<z.infer<S[Table & keyof S]>>) => { entity: NavEntity<S, R, Table>; created: boolean };
|
|
209
210
|
delete: ((id: number) => void) & (() => DeleteBuilder<NavEntity<S, R, Table>>);
|
|
210
211
|
restore: (id: number) => void;
|
|
211
212
|
select: (...cols: (keyof z.infer<S[Table & keyof S]> & string)[]) => QueryBuilder<NavEntity<S, R, Table>>;
|
|
@@ -238,6 +239,7 @@ export type EntityAccessor<S extends z.ZodType<any>> = {
|
|
|
238
239
|
update: ((id: number, data: Partial<EntityData<S>>) => AugmentedEntity<S> | null) & ((data: Partial<EntityData<S>>) => UpdateBuilder<AugmentedEntity<S>>);
|
|
239
240
|
upsert: (conditions?: Partial<InferSchema<S>>, data?: Partial<InferSchema<S>>) => AugmentedEntity<S>;
|
|
240
241
|
upsertMany: (rows: Partial<InferSchema<S>>[], conditions?: Partial<InferSchema<S>>) => AugmentedEntity<S>[];
|
|
242
|
+
findOrCreate: (conditions: Partial<InferSchema<S>>, defaults?: Partial<InferSchema<S>>) => { entity: AugmentedEntity<S>; created: boolean };
|
|
241
243
|
delete: ((id: number) => void) & (() => DeleteBuilder<AugmentedEntity<S>>);
|
|
242
244
|
/** Undo a soft delete by setting deletedAt = null. Requires softDeletes. */
|
|
243
245
|
restore: (id: number) => void;
|