millas 0.2.7 → 0.2.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "millas",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "A modern batteries-included backend framework for Node.js — built on Express, inspired by Laravel, Django, and FastAPI",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -165,37 +165,80 @@ class AdminResource {
165
165
  const limit = perPage || this.perPage;
166
166
  const offset = (page - 1) * limit;
167
167
 
168
- let qb = this.model.query().orderBy(sort, order);
168
+ // _db() is available on all ORM versions — it returns a raw knex table query.
169
+ // We build everything via knex directly so this works regardless of whether
170
+ // the ORM changes (changes3) have been applied.
171
+ let q = this.model._db().orderBy(sort, order);
169
172
 
170
- // Search
173
+ // ── Search ───────────────────────────────────────────────────────────────
171
174
  if (search && this.searchable.length) {
172
- const searchable = this.searchable;
173
- qb._query = qb._query.where(function () {
174
- for (const col of searchable) {
175
+ const cols = this.searchable;
176
+ q = q.where(function () {
177
+ for (const col of cols) {
175
178
  this.orWhere(col, 'like', `%${search}%`);
176
179
  }
177
180
  });
178
181
  }
179
182
 
180
- // Filters (supports __ lookups)
183
+ // ── Filters ──────────────────────────────────────────────────────────────
184
+ // Translate __ lookup syntax into knex calls so filter controls work
185
+ // even without the ORM changes applied.
181
186
  for (const [key, value] of Object.entries(filters)) {
182
- if (value !== '' && value !== null && value !== undefined) {
183
- qb.where(key, value);
187
+ if (value === '' || value === null || value === undefined) continue;
188
+
189
+ const dunder = key.lastIndexOf('__');
190
+ if (dunder === -1) {
191
+ q = q.where(key, value);
192
+ continue;
193
+ }
194
+
195
+ const col = key.slice(0, dunder);
196
+ const lookup = key.slice(dunder + 2);
197
+
198
+ switch (lookup) {
199
+ case 'exact': q = q.where(col, value); break;
200
+ case 'not': q = q.where(col, '!=', value); break;
201
+ case 'gt': q = q.where(col, '>', value); break;
202
+ case 'gte': q = q.where(col, '>=', value); break;
203
+ case 'lt': q = q.where(col, '<', value); break;
204
+ case 'lte': q = q.where(col, '<=', value); break;
205
+ case 'isnull': q = value ? q.whereNull(col) : q.whereNotNull(col); break;
206
+ case 'in': q = q.whereIn(col, Array.isArray(value) ? value : [value]); break;
207
+ case 'notin': q = q.whereNotIn(col, Array.isArray(value) ? value : [value]); break;
208
+ case 'between': q = q.whereBetween(col, value); break;
209
+ case 'contains':
210
+ case 'icontains': q = q.where(col, 'like', `%${value}%`); break;
211
+ case 'startswith':
212
+ case 'istartswith': q = q.where(col, 'like', `${value}%`); break;
213
+ case 'endswith':
214
+ case 'iendswith': q = q.where(col, 'like', `%${value}`); break;
215
+ default: q = q.where(key, value); break;
184
216
  }
185
217
  }
186
218
 
187
- // Date hierarchy drill-down
219
+ // ── Date hierarchy ────────────────────────────────────────────────────────
188
220
  if (this.dateHierarchy) {
189
- if (year) qb.where(`${this.dateHierarchy}__year`, Number(year));
190
- if (month) qb.where(`${this.dateHierarchy}__month`, Number(month));
221
+ const col = this.dateHierarchy;
222
+ if (year) {
223
+ // SQLite / MySQL / PG compatible
224
+ q = q.whereRaw(`strftime('%Y', "${col}") = ?`, [String(year)])
225
+ .catch
226
+ // If strftime not available (PG), fall through — best effort
227
+ || q;
228
+ }
229
+ if (month) {
230
+ q = q.whereRaw(`strftime('%m', "${col}") = ?`, [String(month).padStart(2, '0')]);
231
+ }
191
232
  }
192
233
 
193
- const [rows, total] = await Promise.all([
194
- qb._query.clone().limit(limit).offset(offset),
195
- qb._query.clone().clearSelect().count('* as count').first()
196
- .then(r => Number(r?.count ?? 0)),
234
+ // ── Execute ───────────────────────────────────────────────────────────────
235
+ const [rows, countResult] = await Promise.all([
236
+ q.clone().limit(limit).offset(offset),
237
+ q.clone().count('* as count').first(),
197
238
  ]);
198
239
 
240
+ const total = Number(countResult?.count ?? 0);
241
+
199
242
  return {
200
243
  data: rows.map(r => this.model._hydrate(r)),
201
244
  total,
@@ -494,11 +537,11 @@ class AdminInline {
494
537
  async fetchRows(parentId) {
495
538
  if (!this.model || !this.foreignKey) return [];
496
539
  try {
497
- const rows = await this.model.query()
540
+ const rows = await this.model._db()
498
541
  .where(this.foreignKey, parentId)
499
542
  .limit(this.perPage)
500
- .get();
501
- return rows.map(r => r.toJSON ? r.toJSON() : r);
543
+ .orderBy('id', 'desc');
544
+ return rows.map(r => this.model._hydrate ? this.model._hydrate(r) : r);
502
545
  } catch { return []; }
503
546
  }
504
547