ilana-orm 1.0.19 → 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 +19 -1
- package/index.d.ts +8 -0
- package/index.edge.mjs +1 -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/Model.d.ts +112 -1
- package/orm/Model.js +57 -3
- package/package.json +19 -7
package/README.md
CHANGED
|
@@ -5,7 +5,19 @@
|
|
|
5
5
|
|
|
6
6
|
**Ìlànà** (pronounced "ee-LAH-nah") - A Yoruba word meaning "pattern," "system," or "protocol."
|
|
7
7
|
|
|
8
|
-
A fully-featured, Eloquent-style ORM for Node.js
|
|
8
|
+
A fully-featured, Laravel Eloquent-style ORM for Node.js & TypeScript. If you know Eloquent, you already know IlanaORM — same API, same patterns, same conventions. MySQL, PostgreSQL, SQLite, Supabase, edge runtimes, and pgvector AI search out of the box.
|
|
9
|
+
|
|
10
|
+
| Feature | IlanaORM | Prisma | Drizzle | TypeORM |
|
|
11
|
+
|---|:---:|:---:|:---:|:---:|
|
|
12
|
+
| Eloquent-identical API | ✅ | ❌ | ❌ | ❌ |
|
|
13
|
+
| pgvector / AI search built-in | ✅ | ❌ | ❌ | ❌ |
|
|
14
|
+
| Edge runtime (Cloudflare, Next.js) | ✅ | ⚠️ | ✅ | ❌ |
|
|
15
|
+
| Supabase compatible | ✅ | ✅ | ✅ | ⚠️ |
|
|
16
|
+
| ULID primary keys | ✅ | ⚠️ | ⚠️ | ⚠️ |
|
|
17
|
+
| Factories & seeders built-in | ✅ | ❌ | ❌ | ❌ |
|
|
18
|
+
| Model events | ✅ | ⚠️ | ❌ | ✅ |
|
|
19
|
+
| Soft deletes | ✅ | ❌ | ❌ | ✅ |
|
|
20
|
+
| No code generation step | ✅ | ❌ | ✅ | ✅ |
|
|
9
21
|
|
|
10
22
|
## Table of Contents
|
|
11
23
|
|
|
@@ -718,6 +730,11 @@ class User extends Model {
|
|
|
718
730
|
fillable = ["name", "email", "password"];
|
|
719
731
|
guarded = ["id", "created_at", "updated_at"];
|
|
720
732
|
|
|
733
|
+
// By default, fill()/update() silently drops any key not covered by
|
|
734
|
+
// fillable/guarded — e.g. update({ typo_column: x }) resolves successfully
|
|
735
|
+
// and writes nothing. Opt in to catch that instead of silently swallowing it:
|
|
736
|
+
static preventsSilentlyDiscardingAttributes = true; // throws MassAssignmentException on any discarded key
|
|
737
|
+
|
|
721
738
|
// Hidden attributes (won't appear in JSON)
|
|
722
739
|
hidden = ["password", "remember_token"];
|
|
723
740
|
|
|
@@ -4543,6 +4560,7 @@ user.replicate(except?); // Clone as unsaved record (excludes PK + timestamps)
|
|
|
4543
4560
|
|
|
4544
4561
|
// Attributes
|
|
4545
4562
|
user.fill(attributes); // Mass assign (respects fillable/guarded)
|
|
4563
|
+
user.forceFill(attributes); // Mass assign, bypassing fillable/guarded entirely
|
|
4546
4564
|
user.getAttribute(key); // Get attribute (calls accessor if defined)
|
|
4547
4565
|
user.setAttribute(key, value); // Set attribute (calls mutator if defined)
|
|
4548
4566
|
user.getKey(); // Get primary key value
|
package/index.d.ts
CHANGED
|
@@ -22,5 +22,13 @@ export declare class ModelNotFoundException extends Error {
|
|
|
22
22
|
toResponse(): { status: 404; message: string };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export declare class MassAssignmentException extends Error {
|
|
26
|
+
name: 'MassAssignmentException';
|
|
27
|
+
model: string;
|
|
28
|
+
discardedKeys: string[];
|
|
29
|
+
constructor(model: string, discardedKeys: string[]);
|
|
30
|
+
toResponse(): { status: 422; message: string };
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
// Default export
|
|
26
34
|
export { default } from './orm/Model';
|
package/index.edge.mjs
CHANGED
package/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const Seeder = require('./orm/Seeder');
|
|
|
10
10
|
const Factory = require('./orm/Factory');
|
|
11
11
|
const Relation = require('./orm/Relation');
|
|
12
12
|
const CustomCasts = require('./orm/CustomCasts');
|
|
13
|
-
const { ModelNotFoundException } = require('./orm/Errors');
|
|
13
|
+
const { ModelNotFoundException, MassAssignmentException } = require('./orm/Errors');
|
|
14
14
|
const { F } = require('./orm/F');
|
|
15
15
|
|
|
16
16
|
module.exports = {
|
|
@@ -25,6 +25,7 @@ module.exports = {
|
|
|
25
25
|
Factory: Factory.Factory,
|
|
26
26
|
defineFactory: Factory.defineFactory,
|
|
27
27
|
ModelNotFoundException,
|
|
28
|
+
MassAssignmentException,
|
|
28
29
|
F,
|
|
29
30
|
|
|
30
31
|
// Relationships
|
package/index.mjs
CHANGED
package/orm/Errors.js
CHANGED
|
@@ -15,4 +15,23 @@ class ModelNotFoundException extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
class MassAssignmentException extends Error {
|
|
19
|
+
constructor(model, discardedKeys) {
|
|
20
|
+
const keys = discardedKeys.join(', ');
|
|
21
|
+
super(
|
|
22
|
+
`Mass assignment blocked on ${model}: [${keys}] are not fillable, so ${model}.preventsSilentlyDiscardingAttributes ` +
|
|
23
|
+
`stopped the call instead of dropping them silently. Add the intended column(s) to ${model}.fillable, adjust ` +
|
|
24
|
+
`${model}.guarded, or set the attribute(s) directly (e.g. instance.column = value) instead of going through fill()/update().`
|
|
25
|
+
);
|
|
26
|
+
this.name = 'MassAssignmentException';
|
|
27
|
+
this.model = model;
|
|
28
|
+
this.discardedKeys = discardedKeys;
|
|
29
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, MassAssignmentException);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toResponse() {
|
|
33
|
+
return { status: 422, message: this.message };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { ModelNotFoundException, MassAssignmentException };
|
package/orm/Factory.js
CHANGED
|
@@ -172,7 +172,7 @@ class Factory {
|
|
|
172
172
|
const modelAttributes = this.makeRaw(attributes);
|
|
173
173
|
|
|
174
174
|
const model = new this.model();
|
|
175
|
-
model.
|
|
175
|
+
model.forceFill(modelAttributes);
|
|
176
176
|
|
|
177
177
|
// Run afterMaking callbacks
|
|
178
178
|
for (const callback of this._afterMakingCallbacks) {
|
|
@@ -364,7 +364,7 @@ class BulkFactory {
|
|
|
364
364
|
|
|
365
365
|
for (let k = 0; k < batchData.length; k++) {
|
|
366
366
|
const model = new this.model();
|
|
367
|
-
model.
|
|
367
|
+
model.forceFill({ ...batchData[k], id: insertedIds[k] });
|
|
368
368
|
model.exists = true;
|
|
369
369
|
results.push(model);
|
|
370
370
|
}
|
package/orm/Model.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import QueryBuilder from './QueryBuilder';
|
|
1
|
+
import QueryBuilder, { PaginationResult, SimplePaginationResult, CursorPaginationResult } from './QueryBuilder';
|
|
2
|
+
import Collection from './Collection';
|
|
2
3
|
import { HasOne, HasMany, BelongsTo, BelongsToMany, HasManyThrough, MorphTo, MorphOne, MorphMany } from './Relation';
|
|
3
4
|
|
|
4
5
|
export interface ModelAttributes {
|
|
@@ -51,6 +52,7 @@ export default class Model<TAttributes extends ModelAttributes = ModelAttributes
|
|
|
51
52
|
protected static appends: string[];
|
|
52
53
|
protected static timezone: string;
|
|
53
54
|
static strictLoading: boolean;
|
|
55
|
+
static preventsSilentlyDiscardingAttributes: boolean;
|
|
54
56
|
static touches: string[];
|
|
55
57
|
static enums: { [column: string]: string[] };
|
|
56
58
|
static embeddingColumn: string;
|
|
@@ -75,6 +77,7 @@ export default class Model<TAttributes extends ModelAttributes = ModelAttributes
|
|
|
75
77
|
|
|
76
78
|
// Static methods
|
|
77
79
|
static register(): void;
|
|
80
|
+
static _autoRegister(): void;
|
|
78
81
|
static resolveRelatedModel(related: string | typeof Model): typeof Model;
|
|
79
82
|
static query(): QueryBuilder;
|
|
80
83
|
static with(...relations: string[]): QueryBuilder;
|
|
@@ -112,6 +115,113 @@ export default class Model<TAttributes extends ModelAttributes = ModelAttributes
|
|
|
112
115
|
static firstOrNew(attributes: ModelAttributes, values?: ModelAttributes): Promise<Model>;
|
|
113
116
|
static updateOrCreate(attributes: ModelAttributes, values?: ModelAttributes): Promise<Model>;
|
|
114
117
|
|
|
118
|
+
// QueryBuilder methods forwarded from Model.query() — at runtime, any
|
|
119
|
+
// QueryBuilder method not already declared above is proxied automatically
|
|
120
|
+
// from Model.<method>() to Model.query().<method>() (see Model.js), so this
|
|
121
|
+
// list exists purely to give TypeScript users type information for it.
|
|
122
|
+
static where(column: string, value: any): QueryBuilder;
|
|
123
|
+
static where(column: string, operator: string, value: any): QueryBuilder;
|
|
124
|
+
static orWhere(column: string, value: any): QueryBuilder;
|
|
125
|
+
static orWhere(column: string, operator: string, value: any): QueryBuilder;
|
|
126
|
+
static whereIn(column: string, values: any[]): QueryBuilder;
|
|
127
|
+
static whereNotIn(column: string, values: any[]): QueryBuilder;
|
|
128
|
+
static whereNull(column: string): QueryBuilder;
|
|
129
|
+
static whereNotNull(column: string): QueryBuilder;
|
|
130
|
+
static whereBetween(column: string, range: [any, any]): QueryBuilder;
|
|
131
|
+
static whereNotBetween(column: string, range: [any, any]): QueryBuilder;
|
|
132
|
+
static whereJsonContains(column: string, value: any): QueryBuilder;
|
|
133
|
+
static whereJsonLength(column: string, operator: string, value: number): QueryBuilder;
|
|
134
|
+
static whereDate(column: string, value: string): QueryBuilder;
|
|
135
|
+
static whereDate(column: string, operator: string, value: string): QueryBuilder;
|
|
136
|
+
static whereMonth(column: string, month: number): QueryBuilder;
|
|
137
|
+
static whereYear(column: string, year: number): QueryBuilder;
|
|
138
|
+
static whereDay(column: string, operatorOrValue: any, value?: any): QueryBuilder;
|
|
139
|
+
static whereTime(column: string, operatorOrValue: any, value?: any): QueryBuilder;
|
|
140
|
+
static whereRaw(sql: string, bindings?: any[]): QueryBuilder;
|
|
141
|
+
static orWhereNull(column: string): QueryBuilder;
|
|
142
|
+
static orWhereNotNull(column: string): QueryBuilder;
|
|
143
|
+
static orWhereIn(column: string, values: any[]): QueryBuilder;
|
|
144
|
+
static orWhereNotIn(column: string, values: any[]): QueryBuilder;
|
|
145
|
+
static orWhereRaw(sql: string, bindings?: any[]): QueryBuilder;
|
|
146
|
+
static whereExists(callback: (query: QueryBuilder) => void): QueryBuilder;
|
|
147
|
+
static whereNotExists(callback: (query: QueryBuilder) => void): QueryBuilder;
|
|
148
|
+
static when<T>(condition: T, callback: (query: QueryBuilder, condition: T) => void, otherwise?: (query: QueryBuilder) => void): QueryBuilder;
|
|
149
|
+
static unless<T>(condition: T, callback: (query: QueryBuilder) => void, otherwise?: (query: QueryBuilder, condition: T) => void): QueryBuilder;
|
|
150
|
+
|
|
151
|
+
static join(table: string, first: string, operator: string, second: string): QueryBuilder;
|
|
152
|
+
static leftJoin(table: string, first: string, operator: string, second: string): QueryBuilder;
|
|
153
|
+
static rightJoin(table: string, first: string, operator: string, second: string): QueryBuilder;
|
|
154
|
+
static innerJoin(table: string, first: string, operator: string, second: string): QueryBuilder;
|
|
155
|
+
static crossJoin(table: string): QueryBuilder;
|
|
156
|
+
|
|
157
|
+
static orderBy(column: string, direction?: 'asc' | 'desc'): QueryBuilder;
|
|
158
|
+
static orderByRaw(sql: string): QueryBuilder;
|
|
159
|
+
static orderBySubquery(callback: (query: QueryBuilder) => void, direction?: 'asc' | 'desc'): QueryBuilder;
|
|
160
|
+
static inRandomOrder(): QueryBuilder;
|
|
161
|
+
static limit(count: number): QueryBuilder;
|
|
162
|
+
static offset(count: number): QueryBuilder;
|
|
163
|
+
static take(count: number): QueryBuilder;
|
|
164
|
+
static skip(count: number): QueryBuilder;
|
|
165
|
+
static from(table: string): QueryBuilder;
|
|
166
|
+
static forPage(page: number, perPage?: number): QueryBuilder;
|
|
167
|
+
|
|
168
|
+
static groupBy(...columns: string[]): QueryBuilder;
|
|
169
|
+
static having(column: string, operator: string, value: any): QueryBuilder;
|
|
170
|
+
static having(rawSql: string): QueryBuilder;
|
|
171
|
+
static havingRaw(sql: string, bindings?: any[]): QueryBuilder;
|
|
172
|
+
|
|
173
|
+
static lockForUpdate(): QueryBuilder;
|
|
174
|
+
static sharedLock(): QueryBuilder;
|
|
175
|
+
static skipLocked(): QueryBuilder;
|
|
176
|
+
static noWait(): QueryBuilder;
|
|
177
|
+
|
|
178
|
+
static select(...columns: any[]): QueryBuilder;
|
|
179
|
+
static addSelect(...columns: any[]): QueryBuilder;
|
|
180
|
+
static addSelect(subqueries: { [alias: string]: (query: QueryBuilder) => void }): QueryBuilder;
|
|
181
|
+
static distinct(): QueryBuilder;
|
|
182
|
+
static selectRaw(sql: string, bindings?: any[]): QueryBuilder;
|
|
183
|
+
|
|
184
|
+
static withPendingAttributes(attributes: { [key: string]: any }): QueryBuilder;
|
|
185
|
+
static withConstraints(relation: string, callback: (query: QueryBuilder) => void): QueryBuilder;
|
|
186
|
+
static withConstraints(relations: { [key: string]: (query: QueryBuilder) => void }): QueryBuilder;
|
|
187
|
+
static whereHas(relation: string, callback?: (query: QueryBuilder) => void): QueryBuilder;
|
|
188
|
+
static doesntHave(relation: string): QueryBuilder;
|
|
189
|
+
static whereDoesntHave(relation: string, callback?: (query: QueryBuilder) => void): QueryBuilder;
|
|
190
|
+
static has(relation: string, operator?: '=' | '!=' | '<' | '<=' | '>' | '>=', count?: number): QueryBuilder;
|
|
191
|
+
|
|
192
|
+
static count(column?: string): Promise<number>;
|
|
193
|
+
static sum(column: string): Promise<number>;
|
|
194
|
+
static avg(column: string): Promise<number>;
|
|
195
|
+
static min(column: string): Promise<any>;
|
|
196
|
+
static max(column: string): Promise<any>;
|
|
197
|
+
|
|
198
|
+
static pluck(column: string): Promise<any[]>;
|
|
199
|
+
static exists(): Promise<boolean>;
|
|
200
|
+
static doesntExist(): Promise<boolean>;
|
|
201
|
+
static sole(): Promise<Model>;
|
|
202
|
+
static tap(callback: (query: QueryBuilder) => void): QueryBuilder;
|
|
203
|
+
static get(): Promise<Collection<Model>>;
|
|
204
|
+
|
|
205
|
+
static paginate(page?: number, perPage?: number): Promise<PaginationResult<Model>>;
|
|
206
|
+
static simplePaginate(page?: number, perPage?: number): Promise<SimplePaginationResult<Model>>;
|
|
207
|
+
static cursorPaginate(perPage?: number, cursor?: string, column?: string, direction?: 'asc' | 'desc'): Promise<CursorPaginationResult<Model>>;
|
|
208
|
+
|
|
209
|
+
static chunk(size: number, callback: (models: Collection<Model>) => Promise<void>): Promise<void>;
|
|
210
|
+
static cursor(chunkSize?: number): AsyncGenerator<Model, void, unknown>;
|
|
211
|
+
static lazy(chunkSize?: number): AsyncGenerator<Model, void, unknown>;
|
|
212
|
+
|
|
213
|
+
static update(data: any): Promise<number>;
|
|
214
|
+
static increment(column: string, amount?: number): Promise<number>;
|
|
215
|
+
static decrement(column: string, amount?: number): Promise<number>;
|
|
216
|
+
static delete(): Promise<number>;
|
|
217
|
+
static restore(): Promise<number>;
|
|
218
|
+
|
|
219
|
+
static clone(): QueryBuilder;
|
|
220
|
+
static toKnex(): any;
|
|
221
|
+
static toSql(): string;
|
|
222
|
+
static values(): Promise<any[]>;
|
|
223
|
+
static new(attributes?: { [key: string]: any }): Promise<Model>;
|
|
224
|
+
|
|
115
225
|
// Scopes
|
|
116
226
|
static addGlobalScope(name: string, scope: (query: QueryBuilder) => void): void;
|
|
117
227
|
static removeGlobalScope(name: string): void;
|
|
@@ -142,6 +252,7 @@ export default class Model<TAttributes extends ModelAttributes = ModelAttributes
|
|
|
142
252
|
// Instance methods
|
|
143
253
|
getKey(): any;
|
|
144
254
|
fill(attributes: ModelAttributes): this;
|
|
255
|
+
forceFill(attributes: ModelAttributes): this;
|
|
145
256
|
load(...relations: string[]): Promise<this>;
|
|
146
257
|
loadMissing(...relations: string[]): Promise<this>;
|
|
147
258
|
getRelation(key: string): any;
|
package/orm/Model.js
CHANGED
|
@@ -3,6 +3,7 @@ const QueryBuilder = require('./QueryBuilder');
|
|
|
3
3
|
const { HasOne, HasMany, BelongsTo, BelongsToMany, HasManyThrough, MorphTo, MorphMany, MorphOne } = require('./Relation');
|
|
4
4
|
const ModelRegistry = require('./ModelRegistry');
|
|
5
5
|
const Database = require('../database/connection');
|
|
6
|
+
const { MassAssignmentException } = require('./Errors');
|
|
6
7
|
|
|
7
8
|
// Auto-load configuration on first import (skipped in edge runtime)
|
|
8
9
|
if (typeof process !== 'undefined' && process.versions && process.versions.node && !global.__ILANA_EDGE__) {
|
|
@@ -38,6 +39,7 @@ class Model {
|
|
|
38
39
|
static appends = [];
|
|
39
40
|
static timezone = 'UTC';
|
|
40
41
|
static strictLoading = false;
|
|
42
|
+
static preventsSilentlyDiscardingAttributes = false;
|
|
41
43
|
static touches = [];
|
|
42
44
|
static enums = {};
|
|
43
45
|
static embeddingColumn = 'embedding';
|
|
@@ -57,6 +59,8 @@ class Model {
|
|
|
57
59
|
_deferred;
|
|
58
60
|
|
|
59
61
|
constructor(attrs = {}) {
|
|
62
|
+
this.constructor._autoRegister();
|
|
63
|
+
|
|
60
64
|
// instance-level fillable/guarded/casts
|
|
61
65
|
this.fillable = Array.isArray(this.fillable) && this.fillable.length
|
|
62
66
|
? this.fillable
|
|
@@ -168,10 +172,21 @@ class Model {
|
|
|
168
172
|
ModelRegistry.register(this.name, this);
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
// Registers this class the first time it's actually used (constructed or
|
|
176
|
+
// queried), so string-based relations (this.hasMany('Post')) resolve
|
|
177
|
+
// without requiring an explicit Post.register() call, as long as Post gets
|
|
178
|
+
// used somewhere before the relation is loaded. Cheap to call repeatedly:
|
|
179
|
+
// skips the registry write once this class is already registered as itself.
|
|
180
|
+
static _autoRegister() {
|
|
181
|
+
if (ModelRegistry.get(this.name) !== this) {
|
|
182
|
+
ModelRegistry.register(this.name, this);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
171
186
|
static resolveRelatedModel(related) {
|
|
172
187
|
if (typeof related === 'string') {
|
|
173
188
|
const cls = ModelRegistry.get(related);
|
|
174
|
-
if (!cls) throw new Error(`Model '${related}' not found.
|
|
189
|
+
if (!cls) throw new Error(`Model '${related}' not found. It gets registered automatically the first time it's queried or constructed, so require/use ${related} somewhere before this point, or call ${related}.register() explicitly.`);
|
|
175
190
|
return cls;
|
|
176
191
|
}
|
|
177
192
|
|
|
@@ -193,6 +208,7 @@ class Model {
|
|
|
193
208
|
|
|
194
209
|
// --- Query builder ---
|
|
195
210
|
static query() {
|
|
211
|
+
this._autoRegister();
|
|
196
212
|
|
|
197
213
|
const qb = new QueryBuilder(this.getTableName(), this, this.getConnectionName());
|
|
198
214
|
|
|
@@ -447,8 +463,29 @@ class Model {
|
|
|
447
463
|
getKey() { return this.attributes[this.constructor.primaryKey]; }
|
|
448
464
|
|
|
449
465
|
fill(attrs) {
|
|
466
|
+
const discarded = [];
|
|
467
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
468
|
+
if (!this.isFillable(k)) { discarded.push(k); continue; }
|
|
469
|
+
this.setAttribute(k, v);
|
|
470
|
+
}
|
|
471
|
+
// Off by default, matching every prior release: a key rejected by
|
|
472
|
+
// fillable/guarded is silently dropped, same as always. Opt in per-model
|
|
473
|
+
// with `static preventsSilentlyDiscardingAttributes = true` to instead
|
|
474
|
+
// throw on any discarded key — useful for catching a forgotten
|
|
475
|
+
// `fillable` declaration or a typo'd column name in update() calls,
|
|
476
|
+
// without changing behavior for anyone who hasn't asked for it.
|
|
477
|
+
if (discarded.length > 0 && this.constructor.preventsSilentlyDiscardingAttributes) {
|
|
478
|
+
throw new MassAssignmentException(this.constructor.name, discarded);
|
|
479
|
+
}
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Sets attributes bypassing fillable/guarded entirely, for trusted,
|
|
484
|
+
// programmatic data (factories, seeders, internal code) rather than
|
|
485
|
+
// user-supplied mass assignment — mirrors fill()'s counterpart in
|
|
486
|
+
// Eloquent-style ORMs.
|
|
487
|
+
forceFill(attrs) {
|
|
450
488
|
for (const [k, v] of Object.entries(attrs)) {
|
|
451
|
-
if (!this.isFillable(k)) continue;
|
|
452
489
|
this.setAttribute(k, v);
|
|
453
490
|
}
|
|
454
491
|
return this;
|
|
@@ -909,4 +946,21 @@ class Model {
|
|
|
909
946
|
}
|
|
910
947
|
}
|
|
911
948
|
|
|
912
|
-
|
|
949
|
+
// Any QueryBuilder instance method not already forwarded above (e.g. where(),
|
|
950
|
+
// orderBy(), whereIn(), paginate()...) is auto-forwarded from Model.<method>()
|
|
951
|
+
// to Model.query().<method>(), so new QueryBuilder methods don't need a
|
|
952
|
+
// matching static added here to be callable directly on a Model subclass.
|
|
953
|
+
const ModelStaticHandler = {
|
|
954
|
+
get(target, prop, receiver) {
|
|
955
|
+
if (typeof prop === 'symbol' || prop in target) {
|
|
956
|
+
return Reflect.get(target, prop, receiver);
|
|
957
|
+
}
|
|
958
|
+
const qbMethod = QueryBuilder.prototype[prop];
|
|
959
|
+
if (typeof prop === 'string' && prop[0] !== '_' && typeof qbMethod === 'function') {
|
|
960
|
+
return (...args) => receiver.query()[prop](...args);
|
|
961
|
+
}
|
|
962
|
+
return Reflect.get(target, prop, receiver);
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
module.exports = new Proxy(Model, ModelStaticHandler);
|
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": {
|
|
@@ -80,18 +80,30 @@
|
|
|
80
80
|
},
|
|
81
81
|
"keywords": [
|
|
82
82
|
"orm",
|
|
83
|
+
"eloquent",
|
|
84
|
+
"laravel",
|
|
85
|
+
"active-record",
|
|
83
86
|
"nodejs",
|
|
84
87
|
"javascript",
|
|
85
|
-
"typescript-orm",
|
|
86
88
|
"typescript",
|
|
89
|
+
"typescript-orm",
|
|
87
90
|
"database",
|
|
88
|
-
"
|
|
91
|
+
"query-builder",
|
|
92
|
+
"migrations",
|
|
93
|
+
"relationships",
|
|
89
94
|
"mysql",
|
|
90
95
|
"postgresql",
|
|
91
96
|
"sqlite",
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
97
|
+
"supabase",
|
|
98
|
+
"pgvector",
|
|
99
|
+
"vector-search",
|
|
100
|
+
"edge-runtime",
|
|
101
|
+
"cloudflare-workers",
|
|
102
|
+
"nextjs",
|
|
103
|
+
"ulid",
|
|
104
|
+
"knex",
|
|
105
|
+
"factory",
|
|
106
|
+
"seeder"
|
|
95
107
|
],
|
|
96
108
|
"author": "Raphael Abayomi <raphyabak@gmail.com>",
|
|
97
109
|
"license": "MIT",
|