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.
@@ -227,7 +227,32 @@ class QueryBuilder {
227
227
  }
228
228
 
229
229
  addSelect(...columns) {
230
- this.query.column(...columns);
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.18",
4
- "description": "A fully-featured, Eloquent-style ORM for Node.js with TypeScript support",
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
- "eloquent",
91
+ "query-builder",
92
+ "migrations",
93
+ "relationships",
84
94
  "mysql",
85
95
  "postgresql",
86
96
  "sqlite",
87
- "query-builder",
88
- "migrations",
89
- "relationships"
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",