ilana-orm 1.0.18 → 1.0.20
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 +133 -32
- package/cli/ilana.js +31 -145
- package/database/schema-builder.js +4 -0
- package/index.d.ts +8 -0
- package/index.edge.js +10 -0
- package/index.edge.mjs +38 -0
- package/index.js +2 -1
- package/index.mjs +1 -0
- package/orm/Errors.js +20 -1
- package/orm/Factory.js +2 -2
- package/orm/MigrationRunner.js +16 -48
- package/orm/Model.d.ts +128 -2
- package/orm/Model.js +187 -23
- package/orm/QueryBuilder.d.ts +10 -0
- package/orm/QueryBuilder.js +74 -1
- package/package.json +24 -7
package/orm/QueryBuilder.js
CHANGED
|
@@ -227,7 +227,32 @@ class QueryBuilder {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
addSelect(...columns) {
|
|
230
|
-
|
|
230
|
+
for (const col of columns) {
|
|
231
|
+
if (col && typeof col === 'object' && !Array.isArray(col)) {
|
|
232
|
+
// Subquery form: addSelect({ alias: (qb) => qb.from('table').select('col').where(...) })
|
|
233
|
+
for (const [alias, subFn] of Object.entries(col)) {
|
|
234
|
+
const knex = Database.connection(this.connectionName);
|
|
235
|
+
const subQuery = knex.queryBuilder();
|
|
236
|
+
const subQb = new QueryBuilder('', null, this.connectionName);
|
|
237
|
+
subQb.query = subQuery;
|
|
238
|
+
subFn(subQb);
|
|
239
|
+
this.query.column(knex.raw(`(${subQuery.toSQL().sql}) as "${alias}"`, subQuery.toSQL().bindings));
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
this.query.column(col);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return this;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
orderBySubquery(callback, direction = 'asc') {
|
|
249
|
+
const knex = Database.connection(this.connectionName);
|
|
250
|
+
const subQuery = knex.queryBuilder();
|
|
251
|
+
const subQb = new QueryBuilder('', null, this.connectionName);
|
|
252
|
+
subQb.query = subQuery;
|
|
253
|
+
callback(subQb);
|
|
254
|
+
const { sql, bindings } = subQuery.toSQL();
|
|
255
|
+
this.query.orderByRaw(`(${sql}) ${direction === 'desc' ? 'desc' : 'asc'}`, bindings);
|
|
231
256
|
return this;
|
|
232
257
|
}
|
|
233
258
|
|
|
@@ -250,6 +275,23 @@ class QueryBuilder {
|
|
|
250
275
|
return this;
|
|
251
276
|
}
|
|
252
277
|
|
|
278
|
+
withPendingAttributes(attrs) {
|
|
279
|
+
this._pendingAttributes = { ...(this._pendingAttributes || {}), ...attrs };
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async new(attrs = {}) {
|
|
284
|
+
const merged = { ...(this._pendingAttributes || {}), ...attrs };
|
|
285
|
+
const inst = new this.modelClass(merged);
|
|
286
|
+
inst._initialize();
|
|
287
|
+
return inst;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async create(attrs = {}) {
|
|
291
|
+
const merged = { ...(this._pendingAttributes || {}), ...attrs };
|
|
292
|
+
return this.modelClass.create(merged);
|
|
293
|
+
}
|
|
294
|
+
|
|
253
295
|
when(condition, callback, otherwise) {
|
|
254
296
|
if (condition) {
|
|
255
297
|
callback(this, condition);
|
|
@@ -549,6 +591,37 @@ class QueryBuilder {
|
|
|
549
591
|
return this.whereDoesntHave(relation);
|
|
550
592
|
}
|
|
551
593
|
|
|
594
|
+
has(relation, operator = '>=', count = 1) {
|
|
595
|
+
if (!this.modelClass) return this;
|
|
596
|
+
try {
|
|
597
|
+
const dummy = this._makeDummy();
|
|
598
|
+
const relFn = this.modelClass.prototype[relation];
|
|
599
|
+
if (typeof relFn !== 'function') return this;
|
|
600
|
+
const rel = relFn.call(dummy);
|
|
601
|
+
const relatedClass = rel.getRelatedClass();
|
|
602
|
+
const relatedTable = relatedClass.getTableName();
|
|
603
|
+
const parentTable = this.modelClass.getTableName();
|
|
604
|
+
const isBelongsTo = rel.constructor.name === 'BelongsTo';
|
|
605
|
+
const joinCol = isBelongsTo
|
|
606
|
+
? `${relatedTable}.${rel.localKey || relatedClass.getPrimaryKey()} = ${parentTable}.${rel.foreignKey}`
|
|
607
|
+
: `${relatedTable}.${rel.foreignKey} = ${parentTable}.${rel.localKey || this.modelClass.getPrimaryKey()}`;
|
|
608
|
+
const ops = ['=', '!=', '<', '<=', '>', '>='];
|
|
609
|
+
const safeOp = ops.includes(operator) ? operator : '>=';
|
|
610
|
+
const safeCount = parseInt(count, 10) || 1;
|
|
611
|
+
if (safeOp === '>=' && safeCount === 1) {
|
|
612
|
+
this.query.whereExists((builder) => {
|
|
613
|
+
builder.from(relatedTable).whereRaw(joinCol);
|
|
614
|
+
});
|
|
615
|
+
} else {
|
|
616
|
+
this.query.whereRaw(
|
|
617
|
+
`(select count(*) from ?? where ${joinCol}) ${safeOp} ?`,
|
|
618
|
+
[relatedTable, safeCount]
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
} catch (_) {}
|
|
622
|
+
return this;
|
|
623
|
+
}
|
|
624
|
+
|
|
552
625
|
_makeDummy() {
|
|
553
626
|
const dummy = Object.create(this.modelClass.prototype);
|
|
554
627
|
dummy.attributes = {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilana-orm",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "A fully-featured, Eloquent-style ORM for Node.js
|
|
3
|
+
"version": "1.0.20",
|
|
4
|
+
"description": "A fully-featured, Laravel Eloquent-style ORM for Node.js & TypeScript. MySQL, PostgreSQL, SQLite, Supabase, edge runtimes, pgvector AI search.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
"require": "./index.js",
|
|
11
11
|
"types": "./index.d.ts"
|
|
12
12
|
},
|
|
13
|
+
"./edge": {
|
|
14
|
+
"import": "./index.edge.mjs",
|
|
15
|
+
"require": "./index.edge.js",
|
|
16
|
+
"types": "./index.d.ts"
|
|
17
|
+
},
|
|
13
18
|
"./orm/Model": {
|
|
14
19
|
"import": "./orm/Model.mjs",
|
|
15
20
|
"require": "./orm/Model.js",
|
|
@@ -75,18 +80,30 @@
|
|
|
75
80
|
},
|
|
76
81
|
"keywords": [
|
|
77
82
|
"orm",
|
|
83
|
+
"eloquent",
|
|
84
|
+
"laravel",
|
|
85
|
+
"active-record",
|
|
78
86
|
"nodejs",
|
|
79
87
|
"javascript",
|
|
80
|
-
"typescript-orm",
|
|
81
88
|
"typescript",
|
|
89
|
+
"typescript-orm",
|
|
82
90
|
"database",
|
|
83
|
-
"
|
|
91
|
+
"query-builder",
|
|
92
|
+
"migrations",
|
|
93
|
+
"relationships",
|
|
84
94
|
"mysql",
|
|
85
95
|
"postgresql",
|
|
86
96
|
"sqlite",
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
97
|
+
"supabase",
|
|
98
|
+
"pgvector",
|
|
99
|
+
"vector-search",
|
|
100
|
+
"edge-runtime",
|
|
101
|
+
"cloudflare-workers",
|
|
102
|
+
"nextjs",
|
|
103
|
+
"ulid",
|
|
104
|
+
"knex",
|
|
105
|
+
"factory",
|
|
106
|
+
"seeder"
|
|
90
107
|
],
|
|
91
108
|
"author": "Raphael Abayomi <raphyabak@gmail.com>",
|
|
92
109
|
"license": "MIT",
|