@bunyad/orm 0.2.0-beta.0 → 0.2.0-beta.2

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/factory.js CHANGED
@@ -83,6 +83,11 @@ export class Factory {
83
83
  #has = [];
84
84
  #for = [];
85
85
  #hasAttached = [];
86
+ #configured = false;
87
+ /** Models reused instead of creating new parents (`recycle`). */
88
+ #recycled = [];
89
+ /** Parents created from `for(Factory)` — one per batch, shared by every model in it. */
90
+ #forParents = new Map();
86
91
  /** Public model class accessor for relationship factories. */
87
92
  modelClass() {
88
93
  return this.model();
@@ -90,6 +95,10 @@ export class Factory {
90
95
  definition() {
91
96
  return {};
92
97
  }
98
+ /** Runs once, before the first `make` / `create` (`afterMaking`, default states, …). */
99
+ configure() {
100
+ return this;
101
+ }
93
102
  /** Number of models to make/create. */
94
103
  count(amount) {
95
104
  this.#count = amount;
@@ -140,6 +149,26 @@ export class Factory {
140
149
  this.#hasAttached.push({ related, pivot, relationship });
141
150
  return this;
142
151
  }
152
+ /**
153
+ * `recycle` — reuse existing models when this factory (or its nested `for` /
154
+ * `has` factories) would otherwise create a parent of the same class.
155
+ */
156
+ recycle(models) {
157
+ const list = Array.isArray(models)
158
+ ? models
159
+ : "all" in models && typeof models.all === "function"
160
+ ? models.all()
161
+ : [models];
162
+ this.#recycled.push(...list);
163
+ return this;
164
+ }
165
+ /** A recycled model of `Ctor`, chosen at random. */
166
+ #recycledFor(Ctor) {
167
+ const matches = this.#recycled.filter((m) => m.constructor === Ctor);
168
+ if (matches.length === 0)
169
+ return undefined;
170
+ return matches[Math.floor(Math.random() * matches.length)];
171
+ }
143
172
  /** `Factory::new()`. */
144
173
  static new() {
145
174
  return new this();
@@ -157,52 +186,89 @@ export class Factory {
157
186
  attrs = { ...attrs, ...patch };
158
187
  }
159
188
  for (const spec of this.#for) {
160
- const parentModel = spec.parent instanceof Factory
161
- ? (await spec.parent.create())
162
- : spec.parent;
189
+ let parentModel;
190
+ if (spec.parent instanceof Factory) {
191
+ let created = this.#forParents.get(spec);
192
+ if (!created) {
193
+ created = this.#recycledFor(spec.parent.modelClass());
194
+ }
195
+ if (!created) {
196
+ if (this.#recycled.length > 0)
197
+ spec.parent.recycle(this.#recycled);
198
+ created = (await spec.parent.create());
199
+ }
200
+ this.#forParents.set(spec, created);
201
+ parentModel = created;
202
+ }
203
+ else {
204
+ parentModel = spec.parent;
205
+ }
163
206
  const ParentCtor = parentModel.constructor;
164
207
  const fkName = spec.relationship?.endsWith("_id")
165
208
  ? spec.relationship
166
- : spec.relationship
167
- ? `${spec.relationship}_id`
168
- : `${singularTable(ParentCtor.table)}_id`;
209
+ : this.#belongsToKey(ParentCtor, spec.relationship);
169
210
  attrs[fkName] = parentModel[ParentCtor.primaryKey];
170
211
  }
171
212
  return { ...attrs, ...attributes };
172
213
  }
214
+ /**
215
+ * Foreign key for `for(parent)`: the child's `belongsTo` relation named after
216
+ * the parent class (a `Team` parent looks for `team()`), else `team_id`.
217
+ */
218
+ #belongsToKey(Parent, relationship) {
219
+ const camel = relationship ?? Parent.name.charAt(0).toLowerCase() + Parent.name.slice(1);
220
+ try {
221
+ const rel = new (this.model())().related(camel);
222
+ if (typeof rel.getForeignKeyName === "function")
223
+ return rel.getForeignKeyName();
224
+ }
225
+ catch {
226
+ // no such relation on the child model
227
+ }
228
+ const snake = camel.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
229
+ return `${snake}_id`;
230
+ }
173
231
  async #createRelated(parent) {
174
232
  const ParentCtor = parent.constructor;
175
233
  const parentId = parent[ParentCtor.primaryKey];
234
+ const idsOf = (list) => list.map((m) => m[m.constructor.primaryKey]);
176
235
  for (const spec of this.#has) {
236
+ if (this.#recycled.length > 0)
237
+ spec.factory.recycle(this.#recycled);
177
238
  const RelatedCtor = spec.factory.modelClass();
178
239
  const relName = spec.relationship ?? guessRelationName(ParentCtor, RelatedCtor);
179
240
  let fk = `${singularTable(ParentCtor.table)}_id`;
180
- let useAttach = false;
241
+ let fixed = null;
242
+ let rel = null;
181
243
  try {
182
- const rel = parent.related(relName);
183
- if (typeof rel.attach === "function") {
184
- useAttach = true;
185
- }
186
- else if (typeof rel.getForeignKeyName === "function") {
187
- fk = rel.getForeignKeyName();
188
- }
244
+ rel = parent.related(relName);
189
245
  }
190
246
  catch {
191
- // use default fk
247
+ // fall back to the conventional foreign key
192
248
  }
193
- if (useAttach) {
194
- const related = await spec.factory.create();
195
- const list = Array.isArray(related) ? related : [related];
196
- const rel = parent.related(relName);
197
- const ids = list.map((m) => m[m.constructor.primaryKey]);
198
- await rel.attach(ids);
249
+ if (rel && typeof rel.attach === "function") {
250
+ const created = await spec.factory.create();
251
+ const list = (Array.isArray(created) ? created : [created]);
252
+ await rel.attach(idsOf(list));
199
253
  continue;
200
254
  }
201
- await spec.factory.state({ [fk]: parentId }).create();
255
+ if (rel && typeof rel.getTypeColumn === "function") {
256
+ // morphOne / morphMany: set both the id and the type column.
257
+ fixed = {
258
+ [rel.getIdColumn()]: parentId,
259
+ [rel.getTypeColumn()]: rel.getMorphType(),
260
+ };
261
+ }
262
+ else if (rel && typeof rel.getForeignKeyName === "function") {
263
+ fk = rel.getForeignKeyName();
264
+ }
265
+ await spec.factory.state(fixed ?? { [fk]: parentId }).create();
202
266
  }
203
267
  for (const spec of this.#hasAttached) {
204
268
  let models;
205
269
  if (spec.related instanceof Factory) {
270
+ if (this.#recycled.length > 0)
271
+ spec.related.recycle(this.#recycled);
206
272
  const created = await spec.related.create();
207
273
  models = Array.isArray(created) ? created : [created];
208
274
  }
@@ -217,17 +283,33 @@ export class Factory {
217
283
  const RelatedCtor = models[0].constructor;
218
284
  const relName = spec.relationship ?? guessRelationName(ParentCtor, RelatedCtor);
219
285
  const rel = parent.related(relName);
220
- const ids = models.map((m) => m[m.constructor.primaryKey]);
221
- await rel.attach(ids);
222
- void spec.pivot;
286
+ const pivot = spec.pivot;
287
+ if (typeof pivot === "function") {
288
+ // Per-model pivot attributes.
289
+ const map = {};
290
+ for (const m of models)
291
+ map[String(idsOf([m])[0])] = pivot(m);
292
+ await rel.attach(map);
293
+ }
294
+ else {
295
+ await rel.attach(idsOf(models), pivot ?? {});
296
+ }
223
297
  }
224
298
  }
225
- /** Build an in-memory model instance (not persisted). */
226
- async make(attributes = {}) {
227
- const n = this.#count;
228
- const many = this.#many;
299
+ #begin() {
300
+ if (!this.#configured) {
301
+ this.#configured = true;
302
+ this.configure();
303
+ }
304
+ const out = { n: this.#count, many: this.#many };
229
305
  this.#count = 1;
230
306
  this.#many = false;
307
+ this.#forParents.clear();
308
+ return out;
309
+ }
310
+ /** Build an in-memory model instance (not persisted). */
311
+ async make(attributes = {}) {
312
+ const { n, many } = this.#begin();
231
313
  const ModelClass = this.model();
232
314
  const items = [];
233
315
  for (let i = 0; i < n; i++) {
@@ -242,18 +324,16 @@ export class Factory {
242
324
  }
243
325
  /** Persist model(s) via `Model.forceCreate` (factories set guarded columns too). */
244
326
  async create(attributes = {}) {
245
- const n = this.#count;
246
- const many = this.#many;
247
- this.#count = 1;
248
- this.#many = false;
327
+ const { n, many } = this.#begin();
249
328
  const ModelClass = this.model();
250
329
  const items = [];
251
330
  for (let i = 0; i < n; i++) {
252
331
  const base = await this.#resolveAttributes(attributes);
253
- const model = (await ModelClass.forceCreate(base));
254
- for (const cb of this.#afterMaking) {
255
- await cb(model);
256
- }
332
+ // `afterMaking` runs on the unsaved model, then it is persisted.
333
+ const model = (await ModelClass.forceCreate(base, async (unsaved) => {
334
+ for (const cb of this.#afterMaking)
335
+ await cb(unsaved);
336
+ }));
257
337
  for (const cb of this.#afterCreating) {
258
338
  await cb(model);
259
339
  }
@@ -262,4 +342,30 @@ export class Factory {
262
342
  }
263
343
  return (many ? items : items[0]);
264
344
  }
345
+ /** `makeOne`. */
346
+ async makeOne(attributes = {}) {
347
+ return (await this.make(attributes));
348
+ }
349
+ /** `createOne`. */
350
+ async createOne(attributes = {}) {
351
+ return (await this.create(attributes));
352
+ }
353
+ /** `createMany(3)` or `createMany([{ name: "a" }, { name: "b" }])`. */
354
+ async createMany(records) {
355
+ if (typeof records === "number") {
356
+ return (await this.count(records).create());
357
+ }
358
+ const out = [];
359
+ for (const attrs of records)
360
+ out.push((await this.createOne(attrs)));
361
+ return out;
362
+ }
363
+ /** `createQuietly` — no model events. */
364
+ async createQuietly(attributes = {}) {
365
+ return this.model().withoutEvents(() => this.create(attributes));
366
+ }
367
+ /** `createManyQuietly`. */
368
+ async createManyQuietly(records) {
369
+ return this.model().withoutEvents(() => this.createMany(records));
370
+ }
265
371
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { Model, ModelQuery, HasMany, HasManyThrough, HasOne, HasOneThrough, BelongsTo, BelongsToMany, MorphToMany, MorphMany, MorphOne, MorphTo, morphMap, clearMorphMap, eagerLoadModels, eagerLoadAggregates, ModelNotFoundException, LazyLoadingViolationException, MassAssignmentException, MissingAttributeException, resetModelStrictnessForTests, isModelCtor, Attribute, AsCollection, AsFluent, AsEnumCollection, resetModelEventsForTests, type ModelClass, type RelationFactory, type RelationsMap, type CastType, type CastDefinition, type EnumCast, type ModelEventName, type ModelEventListener, type ModelObserver, type ModelQueryOptions, type EagerAggregateSpec, type MorphToManyOptions, resolveConnection, } from "./model.js";
1
+ export { Model, ModelQuery, HasMany, HasManyThrough, HasOne, HasOneThrough, BelongsTo, BelongsToMany, MorphToMany, MorphedByMany, MorphMany, MorphOne, MorphTo, morphMap, enforceMorphMap, requireMorphMap, getMorphedModel, clearMorphMap, eagerLoadModels, eagerLoadAggregates, ModelNotFoundException, LazyLoadingViolationException, MassAssignmentException, MissingAttributeException, resetModelStrictnessForTests, isModelCtor, Attribute, AsCollection, AsFluent, AsEnumCollection, resetModelEventsForTests, type ModelClass, type RelationFactory, type RelationsMap, type CastType, type CastDefinition, type EnumCast, type ModelEventName, type ModelEventListener, type ModelObserver, type ModelQueryOptions, type EagerAggregateSpec, type MorphToManyOptions, resolveConnection, } from "./model.js";
2
2
  export type { GlobalScopeCallback } from "./scopes.js";
3
3
  export { clearGlobalScopes } from "./scopes.js";
4
4
  export { UseBuilder, resolveModelBuilder, type ModelBuilderConstructor, } from "./decorators.js";
@@ -8,3 +8,23 @@ export { Factory, fake, type FactoryAttributes, type FactoryState, type FactoryS
8
8
  export { Pivot } from "./pivot.js";
9
9
  export { LengthAwarePaginator } from "@bunyad/database";
10
10
  export { Collection, collect } from "@bunyad/common";
11
+ export type { AggregateRelations } from "./model-query.js";
12
+ /**
13
+ * Type-level switch for the whole ORM, declared here (the package entry) so an app can augment it.
14
+ *
15
+ * Relation and column names are checked at compile time by default: `User.with("psots")` and
16
+ * `User.where("emial", x)` are errors. To accept any string again, add this to a `.d.ts` file in your app:
17
+ *
18
+ * ```ts
19
+ * declare module "@bunyad/orm" {
20
+ * interface OrmTypeOptions {
21
+ * strictNames: false;
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export interface OrmTypeOptions {
27
+ }
28
+ export type { ColumnHint, ColumnNames, RelationHint, RelationNames, } from "./typed-names.js";
29
+ export { unsafeName } from "./typed-names.js";
30
+ export { checkModelSchema, generateModelTypes, modelColumnsInterface, tsTypeForColumn, type SchemaIssue } from "./schema-types.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Model, ModelQuery, HasMany, HasManyThrough, HasOne, HasOneThrough, BelongsTo, BelongsToMany, MorphToMany, MorphMany, MorphOne, MorphTo, morphMap, clearMorphMap, eagerLoadModels, eagerLoadAggregates, ModelNotFoundException, LazyLoadingViolationException, MassAssignmentException, MissingAttributeException, resetModelStrictnessForTests, isModelCtor, Attribute, AsCollection, AsFluent, AsEnumCollection, resetModelEventsForTests, resolveConnection, } from "./model.js";
1
+ export { Model, ModelQuery, HasMany, HasManyThrough, HasOne, HasOneThrough, BelongsTo, BelongsToMany, MorphToMany, MorphedByMany, MorphMany, MorphOne, MorphTo, morphMap, enforceMorphMap, requireMorphMap, getMorphedModel, clearMorphMap, eagerLoadModels, eagerLoadAggregates, ModelNotFoundException, LazyLoadingViolationException, MassAssignmentException, MissingAttributeException, resetModelStrictnessForTests, isModelCtor, Attribute, AsCollection, AsFluent, AsEnumCollection, resetModelEventsForTests, resolveConnection, } from "./model.js";
2
2
  export { clearGlobalScopes } from "./scopes.js";
3
3
  export { UseBuilder, resolveModelBuilder, } from "./decorators.js";
4
4
  export { HasUuids, HasUlids, HasFactory, SoftDeletes, Prunable, MassPrunable, prune, pruneAll, prunableModelClasses, massPrunableModelClasses, clearPrunableRegistries, uuid7, ulid, } from "./concerns.js";
@@ -7,3 +7,5 @@ export { Factory, fake, } from "./factory.js";
7
7
  export { Pivot } from "./pivot.js";
8
8
  export { LengthAwarePaginator } from "@bunyad/database";
9
9
  export { Collection, collect } from "@bunyad/common";
10
+ export { unsafeName } from "./typed-names.js";
11
+ export { checkModelSchema, generateModelTypes, modelColumnsInterface, tsTypeForColumn } from "./schema-types.js";
@@ -10,6 +10,9 @@ export type ModelEventListener<T extends Model = Model> = (model: T) => void | b
10
10
  /** Observer object with optional methods named after events. */
11
11
  export type ModelObserver<T extends Model = Model> = {
12
12
  [K in ModelEventName]?: (model: T) => void | boolean | Promise<void | boolean>;
13
+ } & {
14
+ /** Dispatch the after-events (`created`, `saved`, …) once the transaction commits. */
15
+ afterCommit?: boolean;
13
16
  };
14
17
  type ModelCtor = abstract new (...args: never[]) => Model;
15
18
  /** Whether events are currently suppressed for this model class. */
@@ -34,8 +37,11 @@ export declare function fireModelEvent(model: Model, event: ModelEventName): boo
34
37
  /** True when at least one listener is registered for this event. */
35
38
  export declare function hasModelEventListeners(ctor: ModelCtor, event: ModelEventName): boolean;
36
39
  export declare function hasAnyModelEventListeners(ctor: ModelCtor): boolean;
37
- /** Run `callback` with model events disabled for `ctor`. */
38
- export declare function withoutModelEvents<T>(ctor: ModelCtor, callback: () => T | Promise<T>): T | Promise<T>;
40
+ /**
41
+ * Run `callback` with model events disabled for `ctor`, or for every model
42
+ * when `all` is true (`Model.withoutEvents(...)` on the base class).
43
+ */
44
+ export declare function withoutModelEvents<T>(ctor: ModelCtor, callback: () => T | Promise<T>, all?: boolean): T | Promise<T>;
39
45
  /** Register an observer instance or class (`Model::observe`). */
40
46
  export declare function observeModel<T extends Model>(ctor: abstract new (...args: never[]) => T, observer: ModelObserver<T> | (new () => ModelObserver<T>) | Array<ModelObserver<T> | (new () => ModelObserver<T>)>): void;
41
47
  /** Test helper — clear boot state and listeners for a model class. */
@@ -18,6 +18,8 @@ export const MODEL_EVENT_NAMES = [
18
18
  const booted = new WeakSet();
19
19
  const listeners = new WeakMap();
20
20
  const eventsDisabled = new WeakMap();
21
+ /** `Model.withoutEvents()` called on the base class: every model is muted (seeders, imports). */
22
+ let globalEventsDisabled = 0;
21
23
  function listenerMap(ctor) {
22
24
  let map = listeners.get(ctor);
23
25
  if (!map) {
@@ -28,7 +30,7 @@ function listenerMap(ctor) {
28
30
  }
29
31
  /** Whether events are currently suppressed for this model class. */
30
32
  export function modelEventsDisabled(ctor) {
31
- return (eventsDisabled.get(ctor) ?? 0) > 0;
33
+ return globalEventsDisabled > 0 || (eventsDisabled.get(ctor) ?? 0) > 0;
32
34
  }
33
35
  /**
34
36
  * Ensure `boot` / `booted` have run once for this model class.
@@ -92,10 +94,20 @@ export function hasAnyModelEventListeners(ctor) {
92
94
  }
93
95
  return false;
94
96
  }
95
- /** Run `callback` with model events disabled for `ctor`. */
96
- export function withoutModelEvents(ctor, callback) {
97
- eventsDisabled.set(ctor, (eventsDisabled.get(ctor) ?? 0) + 1);
97
+ /**
98
+ * Run `callback` with model events disabled for `ctor`, or for every model
99
+ * when `all` is true (`Model.withoutEvents(...)` on the base class).
100
+ */
101
+ export function withoutModelEvents(ctor, callback, all = false) {
102
+ if (all)
103
+ globalEventsDisabled++;
104
+ else
105
+ eventsDisabled.set(ctor, (eventsDisabled.get(ctor) ?? 0) + 1);
98
106
  const finish = () => {
107
+ if (all) {
108
+ globalEventsDisabled = Math.max(0, globalEventsDisabled - 1);
109
+ return;
110
+ }
99
111
  const depth = (eventsDisabled.get(ctor) ?? 1) - 1;
100
112
  if (depth <= 0)
101
113
  eventsDisabled.delete(ctor);
@@ -125,6 +137,17 @@ export function observeModel(ctor, observer) {
125
137
  for (const event of MODEL_EVENT_NAMES) {
126
138
  const method = instance[event];
127
139
  if (typeof method === "function") {
140
+ const isBefore = event.endsWith("ing") && event !== "replicating";
141
+ if (instance.afterCommit === true && !isBefore) {
142
+ // Run once the outermost
143
+ // transaction commits (immediately when there is none); dropped on rollback.
144
+ registerModelEvent(ctor, event, (model) => {
145
+ model.getConnection().afterCommit(async () => {
146
+ await method.call(instance, model);
147
+ });
148
+ });
149
+ continue;
150
+ }
128
151
  registerModelEvent(ctor, event, (model) => method.call(instance, model));
129
152
  }
130
153
  }
@@ -3,8 +3,9 @@
3
3
  */
4
4
  import type { DateInput, QueryBuilder } from "@bunyad/database";
5
5
  import { CursorPaginator, LengthAwarePaginator, Paginator } from "@bunyad/database";
6
- import { Collection } from "@bunyad/common";
6
+ import { Collection, LazyCollection } from "@bunyad/common";
7
7
  import { OrmCollection } from "./orm-collection.js";
8
+ import type { ColumnHint, RelationHint, WithMap } from "./typed-names.js";
8
9
  import { type CastDefinition } from "./casts.js";
9
10
  import { Model, type ModelClass, type ModelQueryOptions } from "./model.js";
10
11
  /**
@@ -14,6 +15,13 @@ import { Model, type ModelClass, type ModelQueryOptions } from "./model.js";
14
15
  export declare const MODEL_QUERY_TARGET: unique symbol;
15
16
  /** Unwrap a local-scope Proxy to the underlying `ModelQuery` instance. */
16
17
  export declare function unwrapModelQuery<Q extends ModelQuery>(query: Q): Q;
18
+ /** Shared empty array — copy-on-write before mutate (ModelQuery ctor hot path). */
19
+ /** Relations accepted by `withCount` / `withSum` / …: names, lists, or constraint maps. */
20
+ export type AggregateRelations = string | AggregateRelations[] | Record<string, true | string | {
21
+ as?: string;
22
+ } | ((query: ModelQuery) => void)>;
23
+ /** A subquery: a query builder, a model query, or a closure that configures a builder. */
24
+ export type SubQuery = QueryBuilder | ModelQuery<any, any> | ((q: QueryBuilder) => void);
17
25
  export declare class ModelQuery<T extends Model = Model, TResult extends "model" | "row" = "model"> {
18
26
  #private;
19
27
  private model;
@@ -38,15 +46,15 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
38
46
  /** `withCasts` — merge query-time casts for hydration. */
39
47
  withCasts(casts: Record<string, CastDefinition>): this;
40
48
  /** `has`. */
41
- has(relation: string): this;
49
+ has(relation: RelationHint<T>): this;
42
50
  /** `doesntHave`. */
43
- doesntHave(relation: string): this;
51
+ doesntHave(relation: RelationHint<T>): this;
44
52
  /** `whereHas`. */
45
- whereHas(relation: string, callback?: (query: ModelQuery) => void): this;
53
+ whereHas(relation: RelationHint<T>, callback?: (query: ModelQuery) => void): this;
46
54
  /** `orWhereHas`. */
47
- orWhereHas(relation: string, callback?: (query: ModelQuery) => void): this;
55
+ orWhereHas(relation: RelationHint<T>, callback?: (query: ModelQuery) => void): this;
48
56
  /** `whereDoesntHave`. */
49
- whereDoesntHave(relation: string, callback?: (query: ModelQuery) => void): this;
57
+ whereDoesntHave(relation: RelationHint<T>, callback?: (query: ModelQuery) => void): this;
50
58
  /** `orWhereDoesntHave`. */
51
59
  orWhereDoesntHave(relation: string, callback?: (query: ModelQuery) => void): this;
52
60
  /**
@@ -93,7 +101,7 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
93
101
  /**
94
102
  * `withWhereHas($relation, $callback?)` — whereHas + eager with same constraint.
95
103
  */
96
- withWhereHas(relation: string, callback?: (query: ModelQuery) => void): this;
104
+ withWhereHas(relation: RelationHint<T>, callback?: (query: ModelQuery) => void): this;
97
105
  /** `whereAny` (forwarded to query builder). */
98
106
  whereAny(columns: string[], opOrValue: unknown, value?: unknown): this;
99
107
  orWhereAny(columns: string[], opOrValue: unknown, value?: unknown): this;
@@ -111,7 +119,11 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
111
119
  whereUlid(column: string, value: string): this;
112
120
  /** `whereNot(closure)` nested group. */
113
121
  whereNot(callback: (query: ModelQuery<T>) => void): this;
122
+ whereNot(column: ColumnHint<T>, value: unknown): this;
123
+ whereNot(column: ColumnHint<T>, op: string, value: unknown): this;
114
124
  orWhereNot(callback: (query: ModelQuery<T>) => void): this;
125
+ orWhereNot(column: ColumnHint<T>, value: unknown): this;
126
+ orWhereNot(column: ColumnHint<T>, op: string, value: unknown): this;
115
127
  /** `firstWhere`. */
116
128
  firstWhere(column: string, value: unknown): T | null | Promise<T | null>;
117
129
  firstWhere(column: string, op: string, value: unknown): T | null | Promise<T | null>;
@@ -127,19 +139,38 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
127
139
  valueOrFail(column: string): Promise<unknown>;
128
140
  /** `soleValue`. */
129
141
  soleValue(column: string): Promise<unknown>;
130
- /** `withCount` — `'posts'`, `'posts as post_total'`, a string list, or `{ products: { as: 'productsCount' } }`. */
131
- withCount(...relations: Array<string | string[] | Record<string, true | string | ((query: ModelQuery) => void) | {
132
- as?: string;
133
- }>>): this;
134
- /** `withSum($relation, $column)`. */
135
- withSum(relation: string, column: string): this;
136
- withAvg(relation: string, column: string): this;
137
- withMin(relation: string, column: string): this;
138
- withMax(relation: string, column: string): this;
142
+ /**
143
+ * `withCount` — `'posts'`, `'posts as post_total'`, a list, or a map such as
144
+ * `{ "payments as paid": (q) => q.where("status", "paid") }`.
145
+ */
146
+ withCount(...relations: AggregateRelations[]): this;
147
+ /** `withSum($relation, $column)` — relation may be a constrained map. */
148
+ withSum(relation: AggregateRelations, column: string): this;
149
+ withAvg(relation: AggregateRelations, column: string): this;
150
+ withMin(relation: AggregateRelations, column: string): this;
151
+ withMax(relation: AggregateRelations, column: string): this;
139
152
  /** `withExists($relation)`. */
140
- withExists(...relations: string[]): this;
153
+ withExists(...relations: AggregateRelations[]): this;
141
154
  /** `withAggregate($relation, $column, $function)`. */
142
- withAggregate(relation: string, column: string, fn: "count" | "sum" | "avg" | "min" | "max"): this;
155
+ withAggregate(relation: AggregateRelations, column: string, fn: "count" | "sum" | "avg" | "min" | "max"): this;
156
+ /** `selectSub($query, $as)` — add a subquery as a selected column. */
157
+ selectSub(query: SubQuery, as: string): this;
158
+ /** `whereExists($query)` and its `or` / `not` variants. */
159
+ whereExists(query: SubQuery): this;
160
+ orWhereExists(query: SubQuery): this;
161
+ whereNotExists(query: SubQuery): this;
162
+ orWhereNotExists(query: SubQuery): this;
163
+ /** `union` / `unionAll` — the other query must select the same columns. */
164
+ union(query: SubQuery): this;
165
+ unionAll(query: SubQuery): this;
166
+ /** `joinSub($query, $as, $first, $op, $second)`. */
167
+ joinSub(query: SubQuery, as: string, first: string, op: string, second: string): this;
168
+ leftJoinSub(query: SubQuery, as: string, first: string, op: string, second: string): this;
169
+ rightJoinSub(query: SubQuery, as: string, first: string, op: string, second: string): this;
170
+ /** `crossJoin($table)`. */
171
+ crossJoin(table: string): this;
172
+ /** `groupByRaw($sql, $bindings)`. */
173
+ groupByRaw(sql: string, bindings?: unknown[]): this;
143
174
  /** `join` (hydrated models still from the base table). */
144
175
  join(table: string, first: string, op: string, second: string): this;
145
176
  /** `leftJoin`. */
@@ -147,18 +178,18 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
147
178
  /** `rightJoin`. */
148
179
  rightJoin(table: string, first: string, op: string, second: string): this;
149
180
  where(callback: (query: ModelQuery<T>) => void): this;
150
- where(column: string, value: unknown): this;
151
- where(column: string, op: string, value: unknown): this;
181
+ where(column: ColumnHint<T>, value: unknown): this;
182
+ where(column: ColumnHint<T>, op: string, value: unknown): this;
152
183
  orWhere(callback: (query: ModelQuery<T>) => void): this;
153
- orWhere(column: string, value: unknown): this;
154
- orWhere(column: string, op: string, value: unknown): this;
155
- whereIn(column: string, values: unknown[]): this;
184
+ orWhere(column: ColumnHint<T>, value: unknown): this;
185
+ orWhere(column: ColumnHint<T>, op: string, value: unknown): this;
186
+ whereIn(column: ColumnHint<T>, values: unknown[]): this;
156
187
  orWhereIn(column: string, values: unknown[]): this;
157
188
  whereNotIn(column: string, values: unknown[]): this;
158
189
  orWhereNotIn(column: string, values: unknown[]): this;
159
- whereNull(column: string): this;
190
+ whereNull(column: ColumnHint<T>): this;
160
191
  orWhereNull(column: string): this;
161
- whereNotNull(column: string): this;
192
+ whereNotNull(column: ColumnHint<T>): this;
162
193
  orWhereNotNull(column: string): this;
163
194
  whereBetween(column: string, values: [unknown, unknown]): this;
164
195
  orWhereBetween(column: string, values: [unknown, unknown]): this;
@@ -184,10 +215,10 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
184
215
  addSelect(...columns: string[]): this;
185
216
  selectRaw(expression: string, bindings?: unknown[]): this;
186
217
  distinct(value?: boolean): this;
187
- orderBy(column: string, direction?: "asc" | "desc"): this;
188
- orderByDesc(column: string): this;
218
+ orderBy(column: ColumnHint<T>, direction?: "asc" | "desc"): this;
219
+ orderByDesc(column: ColumnHint<T>): this;
189
220
  orderByRaw(sql: string): this;
190
- /** Karobar / dialect alias for `orderByRaw`. */
221
+ /** Alias for `orderByRaw`. */
191
222
  orderBySql(sql: string): this;
192
223
  reorder(column?: string, direction?: "asc" | "desc"): this;
193
224
  inRandomOrder(): this;
@@ -198,6 +229,15 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
198
229
  havingRaw(sql: string, bindings?: unknown[]): this;
199
230
  limit(value: number): this;
200
231
  take(value: number): this;
232
+ /**
233
+ * Remove and return this query's limit / offset. Eager loading calls it so a
234
+ * `with({ posts: (q) => q.limit(3) })` limit applies per parent, not to the
235
+ * whole batched query.
236
+ */
237
+ takePaging(): {
238
+ limit?: number;
239
+ offset?: number;
240
+ };
201
241
  offset(value: number): this;
202
242
  skip(value: number): this;
203
243
  forPage(page: number, perPage?: number): this;
@@ -205,14 +245,14 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
205
245
  sharedLock(): this;
206
246
  when(condition: unknown, callback: (query: this) => void, defaultCallback?: (query: this) => void): this;
207
247
  /**
208
- * OR `LIKE %term%` across columns (Karobar list `search`).
248
+ * OR `LIKE %term%` across columns (a list `search`).
209
249
  * Empty/whitespace term is a no-op.
210
250
  */
211
251
  search(columns: string[], term: string): this;
212
252
  unless(condition: unknown, callback: (query: this) => void, defaultCallback?: (query: this) => void): this;
213
253
  tap(callback: (query: this) => void): this;
214
254
  clone(): ModelQuery<T, TResult>;
215
- with(...relations: Array<string | string[] | Record<string, unknown>>): this;
255
+ with(...relations: Array<RelationHint<T> | RelationHint<T>[] | WithMap<T>>): this;
216
256
  /** Underlying query builder (fresh compile each call). */
217
257
  toBase(): QueryBuilder;
218
258
  /** Compiled SQL (bindings via `getBindings()`). */
@@ -222,6 +262,12 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
222
262
  getBindings(): unknown[];
223
263
  dump(): this;
224
264
  dd(): never;
265
+ /**
266
+ * Apply this model's global scopes onto a correlated subquery (`whereHas`, `withCount`, …),
267
+ * qualifying columns with `table` so joined pivot / parent columns never clash.
268
+ * Honors `withoutGlobalScopes()` called inside the user's constraint closure.
269
+ */
270
+ applyGlobalScopesToSubquery(q: QueryBuilder, table: string): QueryBuilder;
225
271
  /**
226
272
  * Prefix unqualified columns with `table.` (BelongsTo whereHas → JOIN path).
227
273
  */
@@ -259,14 +305,20 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
259
305
  }): Promise<Paginator<T>>;
260
306
  /** `chunk($count, $callback)`. */
261
307
  chunk(count: number, callback: (models: OrmCollection<T>) => void | Promise<void>): Promise<void>;
308
+ /**
309
+ * `cursor($chunkSize)` — hydrate models one at a time from a single streamed
310
+ * query, so memory stays flat on large tables. `with()` relations are loaded
311
+ * per batch of `chunkSize` models rather than per row.
312
+ */
313
+ cursor(chunkSize?: number): LazyCollection<T>;
262
314
  /** `lazy($chunkSize)`. */
263
- lazy(chunkSize?: number): AsyncGenerator<T, void, unknown>;
315
+ lazy(chunkSize?: number): LazyCollection<T>;
264
316
  /** `chunkById`. */
265
317
  chunkById(count: number, callback: (models: OrmCollection<T>) => void | boolean | Promise<void | boolean>, column?: string): Promise<boolean>;
266
318
  chunkByIdDesc(count: number, callback: (models: OrmCollection<T>) => void | boolean | Promise<void | boolean>, column?: string): Promise<boolean>;
267
319
  /** `lazyById`. */
268
- lazyById(chunkSize?: number, column?: string): AsyncGenerator<T, void, unknown>;
269
- lazyByIdDesc(chunkSize?: number, column?: string): AsyncGenerator<T, void, unknown>;
320
+ lazyById(chunkSize?: number, column?: string): LazyCollection<T>;
321
+ lazyByIdDesc(chunkSize?: number, column?: string): LazyCollection<T>;
270
322
  increment(column: string, amount?: number): Promise<void>;
271
323
  decrement(column: string, amount?: number): Promise<void>;
272
324
  /** `cursorPaginate`. */