@bunyad/orm 0.1.0-alpha.0 → 0.2.0-beta.0

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 CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  Active-record models on top of [`@bunyad/database`](https://www.npmjs.com/package/@bunyad/database): relations, casts, scopes, events, soft deletes and factories. The same model code runs on Node and Bun.
4
4
 
5
- > **Alpha.** APIs may change between `0.x` releases.
5
+ > **Beta.** Public APIs change only in minor releases, with a changelog entry and migration note. See the [stability policy](https://github.com/bunyadjs/framework/blob/main/docs/STABILITY.md).
6
6
 
7
7
  ```bash
8
- npm install @bunyad/orm@alpha @bunyad/database@alpha better-sqlite3
8
+ npm install @bunyad/orm@beta @bunyad/database@beta better-sqlite3
9
9
  ```
10
10
 
11
11
  ## Quick start
@@ -63,7 +63,7 @@ Pick one and install it next to `@bunyad/database`: `better-sqlite3`, `pg` or `m
63
63
 
64
64
  ## Status
65
65
 
66
- Alpha. SQLite is the most tested driver; PostgreSQL and MySQL tests need a live database and are skipped by default. Aggregate helpers such as `sumCase`/`countCase` are not implemented yet.
66
+ Beta scope: SQLite, PostgreSQL and MySQL are tested on Bun and Node 20+ (the PostgreSQL and MySQL suites run when `BUNYAD_TEST_POSTGRES_URL` / `BUNYAD_TEST_MYSQL_URL` are set). SQL Server is experimental and needs `mssql` installed. Conditional aggregate helpers (`sumCase`, `countCase`) are not available yet.
67
67
 
68
68
  ## License
69
69
 
@@ -18,8 +18,8 @@ export declare function HasUuids(): ClassDecorator;
18
18
  * ULID primary keys (`incrementing = false`).
19
19
  */
20
20
  export declare function HasUlids(): ClassDecorator;
21
- type FactoryClass = new () => Factory;
22
- type FactoryResolver = FactoryClass | (() => Factory);
21
+ type FactoryClass = new () => Factory<any>;
22
+ type FactoryResolver = FactoryClass | (() => Factory<any>);
23
23
  /**
24
24
  * Bind a factory class and expose `Model.factory()`.
25
25
  *
package/dist/factory.d.ts CHANGED
@@ -49,15 +49,15 @@ export declare abstract class Factory<T extends Model = Model> {
49
49
  /**
50
50
  * Create related models after the parent (`has(PostFactory.new().count(3))`).
51
51
  */
52
- has(factory: Factory, relationship?: string): this;
52
+ has(factory: Factory<any>, relationship?: string): this;
53
53
  /**
54
54
  * Associate a parent model / factory (`for(TeamFactory.new())`).
55
55
  */
56
- for(parent: Model | Factory, relationship?: string): this;
56
+ for(parent: Model | Factory<any>, relationship?: string): this;
57
57
  /**
58
58
  * Attach related models on a belongs-to-many after create.
59
59
  */
60
- hasAttached(related: Factory | Model | Model[], pivot?: FactoryAttributes, relationship?: string): this;
60
+ hasAttached(related: Factory<any> | Model | Model[], pivot?: FactoryAttributes, relationship?: string): this;
61
61
  /** `Factory::new()`. */
62
62
  static new<T extends Factory>(this: new () => T): T;
63
63
  /** Build an in-memory model instance (not persisted). */
@@ -1,5 +1,5 @@
1
1
  import type { Model } from "./model.js";
2
- /** Eloquent model lifecycle event names. */
2
+ /** Model lifecycle event names. */
3
3
  export type ModelEventName = "retrieved" | "creating" | "created" | "updating" | "updated" | "saving" | "saved" | "deleting" | "deleted" | "trashed" | "forceDeleting" | "forceDeleted" | "restoring" | "restored" | "replicating";
4
4
  export declare const MODEL_EVENT_NAMES: readonly ModelEventName[];
5
5
  /**
@@ -36,7 +36,7 @@ export declare function hasModelEventListeners(ctor: ModelCtor, event: ModelEven
36
36
  export declare function hasAnyModelEventListeners(ctor: ModelCtor): boolean;
37
37
  /** Run `callback` with model events disabled for `ctor`. */
38
38
  export declare function withoutModelEvents<T>(ctor: ModelCtor, callback: () => T | Promise<T>): T | Promise<T>;
39
- /** Register an observer instance or class (Laravel `Model::observe`). */
39
+ /** Register an observer instance or class (`Model::observe`). */
40
40
  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
41
  /** Test helper — clear boot state and listeners for a model class. */
42
42
  export declare function resetModelEventsForTests(ctor: ModelCtor): void;
@@ -115,7 +115,7 @@ export function withoutModelEvents(ctor, callback) {
115
115
  throw error;
116
116
  }
117
117
  }
118
- /** Register an observer instance or class (Laravel `Model::observe`). */
118
+ /** Register an observer instance or class (`Model::observe`). */
119
119
  export function observeModel(ctor, observer) {
120
120
  const list = Array.isArray(observer) ? observer : [observer];
121
121
  for (const item of list) {
@@ -20,68 +20,68 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
20
20
  constructor(model: ModelClass, options?: ModelQueryOptions);
21
21
  /** `get()` returns plain row objects. Models are not built. */
22
22
  rows(): ModelQuery<T, "row">;
23
- /** Laravel `withoutGlobalScopes`. */
23
+ /** `withoutGlobalScopes`. */
24
24
  withoutGlobalScopes(names?: string[]): this;
25
- /** Laravel `withoutGlobalScope`. */
25
+ /** `withoutGlobalScope`. */
26
26
  withoutGlobalScope(name: string): this;
27
- /** Laravel `withTrashed()`. */
27
+ /** `withTrashed()`. */
28
28
  withTrashed(): this;
29
- /** Laravel `onlyTrashed()`. */
29
+ /** `onlyTrashed()`. */
30
30
  onlyTrashed(): this;
31
- /** Laravel `withoutTrashed()`. */
31
+ /** `withoutTrashed()`. */
32
32
  withoutTrashed(): this;
33
33
  /**
34
- * Laravel `Builder::withAttributes` — pending attrs for `create()`, and
34
+ * `Builder::withAttributes` — pending attrs for `create()`, and
35
35
  * matching `where` conditions when `asConditions` is true (default).
36
36
  */
37
37
  withAttributes(attributes: Record<string, unknown> | string, valueOrAsConditions?: unknown, asConditions?: boolean): this;
38
- /** Laravel `withCasts` — merge query-time casts for hydration. */
38
+ /** `withCasts` — merge query-time casts for hydration. */
39
39
  withCasts(casts: Record<string, CastDefinition>): this;
40
- /** Laravel `has`. */
40
+ /** `has`. */
41
41
  has(relation: string): this;
42
- /** Laravel `doesntHave`. */
42
+ /** `doesntHave`. */
43
43
  doesntHave(relation: string): this;
44
- /** Laravel `whereHas`. */
44
+ /** `whereHas`. */
45
45
  whereHas(relation: string, callback?: (query: ModelQuery) => void): this;
46
- /** Laravel `orWhereHas`. */
46
+ /** `orWhereHas`. */
47
47
  orWhereHas(relation: string, callback?: (query: ModelQuery) => void): this;
48
- /** Laravel `whereDoesntHave`. */
48
+ /** `whereDoesntHave`. */
49
49
  whereDoesntHave(relation: string, callback?: (query: ModelQuery) => void): this;
50
- /** Laravel `orWhereDoesntHave`. */
50
+ /** `orWhereDoesntHave`. */
51
51
  orWhereDoesntHave(relation: string, callback?: (query: ModelQuery) => void): this;
52
52
  /**
53
- * Laravel `whereRelation($relation, $column, $operator = null, $value = null)`.
53
+ * `whereRelation($relation, $column, $operator = null, $value = null)`.
54
54
  * Convenience wrapper around `whereHas` with a column constraint.
55
55
  */
56
56
  whereRelation(relation: string, column: string, opOrValue: unknown, value?: unknown): this;
57
- /** Laravel `orWhereRelation`. */
57
+ /** `orWhereRelation`. */
58
58
  orWhereRelation(relation: string, column: string, opOrValue: unknown, value?: unknown): this;
59
- /** Laravel `orHas`. */
59
+ /** `orHas`. */
60
60
  orHas(relation: string): this;
61
- /** Laravel `orDoesntHave`. */
61
+ /** `orDoesntHave`. */
62
62
  orDoesntHave(relation: string): this;
63
63
  /**
64
- * Laravel `whereBelongsTo($model, $relationshipName = null)`.
64
+ * `whereBelongsTo($model, $relationshipName = null)`.
65
65
  * Constrains by the foreign key of a belongsTo relation.
66
66
  */
67
67
  whereBelongsTo(related: Model, relationName?: string): this;
68
- /** Laravel `orWhereBelongsTo`. */
68
+ /** `orWhereBelongsTo`. */
69
69
  orWhereBelongsTo(related: Model, relationName?: string): this;
70
70
  /**
71
- * Laravel `whereMorphedTo($relation, $model)`.
71
+ * `whereMorphedTo($relation, $model)`.
72
72
  */
73
73
  whereMorphedTo(relation: string, model: Model): this;
74
74
  orWhereMorphedTo(relation: string, model: Model): this;
75
75
  whereNotMorphedTo(relation: string, model: Model): this;
76
76
  orWhereNotMorphedTo(relation: string, model: Model): this;
77
77
  /**
78
- * Laravel `whereMorphRelation($relation, $types, $column, …)` — simplified:
78
+ * `whereMorphRelation($relation, $types, $column, …)` — simplified:
79
79
  * `whereMorphRelation('commentable', TypeModel, column, op?, value?)`.
80
80
  */
81
81
  whereMorphRelation(relation: string, type: ModelClass, column: string, opOrValue: unknown, value?: unknown): this;
82
82
  orWhereMorphRelation(relation: string, type: ModelClass, column: string, opOrValue: unknown, value?: unknown): this;
83
83
  /**
84
- * Laravel `whereHasMorph($relation, $types, $callback?)`.
84
+ * `whereHasMorph($relation, $types, $callback?)`.
85
85
  * `$types` may be a single model class or an array.
86
86
  */
87
87
  whereHasMorph(relation: string, types: ModelClass | ModelClass[], callback?: (query: ModelQuery) => void): this;
@@ -91,60 +91,60 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
91
91
  hasMorph(relation: string, types: ModelClass | ModelClass[]): this;
92
92
  doesntHaveMorph(relation: string, types: ModelClass | ModelClass[]): this;
93
93
  /**
94
- * Laravel `withWhereHas($relation, $callback?)` — whereHas + eager with same constraint.
94
+ * `withWhereHas($relation, $callback?)` — whereHas + eager with same constraint.
95
95
  */
96
96
  withWhereHas(relation: string, callback?: (query: ModelQuery) => void): this;
97
- /** Laravel `whereAny` (forwarded to query builder). */
97
+ /** `whereAny` (forwarded to query builder). */
98
98
  whereAny(columns: string[], opOrValue: unknown, value?: unknown): this;
99
99
  orWhereAny(columns: string[], opOrValue: unknown, value?: unknown): this;
100
100
  whereAll(columns: string[], opOrValue: unknown, value?: unknown): this;
101
101
  orWhereAll(columns: string[], opOrValue: unknown, value?: unknown): this;
102
102
  whereNone(columns: string[], opOrValue: unknown, value?: unknown): this;
103
103
  orWhereNone(columns: string[], opOrValue: unknown, value?: unknown): this;
104
- /** Laravel `whereKey($id)` / `whereKey([$ids])`. */
104
+ /** `whereKey($id)` / `whereKey([$ids])`. */
105
105
  whereKey(id: string | number | Array<string | number>): this;
106
- /** Laravel `whereKeyNot`. */
106
+ /** `whereKeyNot`. */
107
107
  whereKeyNot(id: string | number | Array<string | number>): this;
108
- /** Laravel `whereUuid($column, $uuid)`. */
108
+ /** `whereUuid($column, $uuid)`. */
109
109
  whereUuid(column: string, value: string): this;
110
- /** Laravel `whereUlid($column, $ulid)`. */
110
+ /** `whereUlid($column, $ulid)`. */
111
111
  whereUlid(column: string, value: string): this;
112
- /** Laravel `whereNot(closure)` nested group. */
112
+ /** `whereNot(closure)` nested group. */
113
113
  whereNot(callback: (query: ModelQuery<T>) => void): this;
114
114
  orWhereNot(callback: (query: ModelQuery<T>) => void): this;
115
- /** Laravel `firstWhere`. */
115
+ /** `firstWhere`. */
116
116
  firstWhere(column: string, value: unknown): T | null | Promise<T | null>;
117
117
  firstWhere(column: string, op: string, value: unknown): T | null | Promise<T | null>;
118
- /** Laravel `findMany`. */
118
+ /** `findMany`. */
119
119
  findMany(ids: Array<string | number>): OrmCollection<T> | Promise<OrmCollection<T>>;
120
- /** Laravel `findOr($id, $callback)`. */
120
+ /** `findOr($id, $callback)`. */
121
121
  findOr(id: string | number, callback: () => T | Promise<T>): Promise<T>;
122
- /** Laravel `findOrNew`. */
122
+ /** `findOrNew`. */
123
123
  findOrNew(id: string | number): Promise<T>;
124
- /** Laravel `firstOr($callback)`. */
124
+ /** `firstOr($callback)`. */
125
125
  firstOr(callback: () => T | Promise<T>): Promise<T>;
126
- /** Laravel `valueOrFail`. */
126
+ /** `valueOrFail`. */
127
127
  valueOrFail(column: string): Promise<unknown>;
128
- /** Laravel `soleValue`. */
128
+ /** `soleValue`. */
129
129
  soleValue(column: string): Promise<unknown>;
130
130
  /** `withCount` — `'posts'`, `'posts as post_total'`, a string list, or `{ products: { as: 'productsCount' } }`. */
131
131
  withCount(...relations: Array<string | string[] | Record<string, true | string | ((query: ModelQuery) => void) | {
132
132
  as?: string;
133
133
  }>>): this;
134
- /** Laravel `withSum($relation, $column)`. */
134
+ /** `withSum($relation, $column)`. */
135
135
  withSum(relation: string, column: string): this;
136
136
  withAvg(relation: string, column: string): this;
137
137
  withMin(relation: string, column: string): this;
138
138
  withMax(relation: string, column: string): this;
139
- /** Laravel `withExists($relation)`. */
139
+ /** `withExists($relation)`. */
140
140
  withExists(...relations: string[]): this;
141
- /** Laravel `withAggregate($relation, $column, $function)`. */
141
+ /** `withAggregate($relation, $column, $function)`. */
142
142
  withAggregate(relation: string, column: string, fn: "count" | "sum" | "avg" | "min" | "max"): this;
143
- /** Laravel Eloquent `join` (hydrated models still from the base table). */
143
+ /** `join` (hydrated models still from the base table). */
144
144
  join(table: string, first: string, op: string, second: string): this;
145
- /** Laravel `leftJoin`. */
145
+ /** `leftJoin`. */
146
146
  leftJoin(table: string, first: string, op: string, second: string): this;
147
- /** Laravel `rightJoin`. */
147
+ /** `rightJoin`. */
148
148
  rightJoin(table: string, first: string, op: string, second: string): this;
149
149
  where(callback: (query: ModelQuery<T>) => void): this;
150
150
  where(column: string, value: unknown): this;
@@ -174,7 +174,7 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
174
174
  orWhereLike(column: string, value: string, caseSensitive?: boolean): this;
175
175
  whereNotLike(column: string, value: string, caseSensitive?: boolean): this;
176
176
  orWhereNotLike(column: string, value: string, caseSensitive?: boolean): this;
177
- /** Laravel `whereDate` — same on SQLite / Postgres / MySQL. */
177
+ /** `whereDate` — same on SQLite / Postgres / MySQL. */
178
178
  whereDate(column: string, value: DateInput): this;
179
179
  whereDate(column: string, op: string, value: DateInput): this;
180
180
  whereYear(column: string, value: number): this;
@@ -229,7 +229,7 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
229
229
  /** Apply this query's wheres onto a QueryBuilder (exists subqueries). */
230
230
  applyConstraintsTo(q: QueryBuilder): QueryBuilder;
231
231
  first(): T | null | Promise<T | null>;
232
- /** Laravel `firstOrFail`. */
232
+ /** `firstOrFail`. */
233
233
  firstOrFail(): Promise<T>;
234
234
  /**
235
235
  * Fetch matching models as an {@link OrmCollection}.
@@ -246,7 +246,7 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
246
246
  firstSync(): T | null;
247
247
  /** SQLite-only synchronous `find($id)`. */
248
248
  findSync(id: string | number): T | null;
249
- /** Laravel `Builder::create` — insert on this query's connection. */
249
+ /** `Builder::create` — insert on this query's connection. */
250
250
  create(attributes: Record<string, unknown>): Promise<T>;
251
251
  paginate(perPage?: number, page?: number, options?: {
252
252
  path?: string;
@@ -257,24 +257,24 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
257
257
  path?: string;
258
258
  pageName?: string;
259
259
  }): Promise<Paginator<T>>;
260
- /** Laravel `chunk($count, $callback)`. */
260
+ /** `chunk($count, $callback)`. */
261
261
  chunk(count: number, callback: (models: OrmCollection<T>) => void | Promise<void>): Promise<void>;
262
- /** Laravel `lazy($chunkSize)`. */
262
+ /** `lazy($chunkSize)`. */
263
263
  lazy(chunkSize?: number): AsyncGenerator<T, void, unknown>;
264
- /** Laravel `chunkById`. */
264
+ /** `chunkById`. */
265
265
  chunkById(count: number, callback: (models: OrmCollection<T>) => void | boolean | Promise<void | boolean>, column?: string): Promise<boolean>;
266
266
  chunkByIdDesc(count: number, callback: (models: OrmCollection<T>) => void | boolean | Promise<void | boolean>, column?: string): Promise<boolean>;
267
- /** Laravel `lazyById`. */
267
+ /** `lazyById`. */
268
268
  lazyById(chunkSize?: number, column?: string): AsyncGenerator<T, void, unknown>;
269
269
  lazyByIdDesc(chunkSize?: number, column?: string): AsyncGenerator<T, void, unknown>;
270
270
  increment(column: string, amount?: number): Promise<void>;
271
271
  decrement(column: string, amount?: number): Promise<void>;
272
- /** Laravel `cursorPaginate`. */
272
+ /** `cursorPaginate`. */
273
273
  cursorPaginate(perPage?: number, cursor?: string | null, options?: {
274
274
  path?: string;
275
275
  cursorName?: string;
276
276
  }): Promise<CursorPaginator<T>>;
277
- /** Laravel `Builder::count($columns = '*')` — honors `distinct()` for non-`*`. */
277
+ /** `Builder::count($columns = '*')` — honors `distinct()` for non-`*`. */
278
278
  count(column?: string): Promise<number>;
279
279
  exists(): Promise<boolean>;
280
280
  doesntExist(): Promise<boolean>;
@@ -285,9 +285,9 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
285
285
  sole(): Promise<T>;
286
286
  value(column: string): Promise<unknown>;
287
287
  /**
288
- * Laravel `Builder::pluck($column, $key = null)`.
288
+ * `Builder::pluck($column, $key = null)`.
289
289
  * One column → list {@link Collection}. With `key`, selects both columns and
290
- * returns a keyed plain object via {@link Collection.pluck} (Laravel assoc map).
290
+ * returns a keyed plain object via {@link Collection.pluck}.
291
291
  */
292
292
  pluck(column: string): Promise<Collection<unknown>>;
293
293
  pluck(column: string, key: string): Promise<Record<string, unknown>>;
@@ -301,8 +301,8 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
301
301
  */
302
302
  insert(values: Record<string, unknown> | Record<string, unknown>[]): Promise<boolean>;
303
303
  /**
304
- * Laravel `Builder::upsert` — insert or update on unique conflict.
305
- * When the model uses timestamps, stamps `created_at`/`updated_at` like Eloquent
304
+ * `Builder::upsert` — insert or update on unique conflict.
305
+ * When the model uses timestamps, stamps `created_at`/`updated_at`
306
306
  * (row values win; `updated_at` is always included in the update column list).
307
307
  */
308
308
  upsert(values: Record<string, unknown> | Record<string, unknown>[], uniqueBy: string | string[], update?: string[] | null): Promise<number>;