@bunyad/orm 0.2.0-beta.0 → 0.2.0-beta.1
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 +22 -0
- package/dist/model-query.d.ts +10 -4
- package/dist/model-query.js +43 -6
- package/dist/model.d.ts +9 -1
- package/dist/model.js +17 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -51,6 +51,28 @@ await User.query().count();
|
|
|
51
51
|
|
|
52
52
|
Relations load lazily (`await user.posts().get()`) or eagerly (`User.with("posts")`).
|
|
53
53
|
|
|
54
|
+
## Large tables
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
for await (const user of User.where("active", true).cursor()) {
|
|
58
|
+
// hydrated one model at a time from a single streamed query
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for await (const user of User.cursor()) {} // whole table
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`User.cursor()`, `User.lazy()`, `User.lazyById()` and `User.lazyByIdDesc()` are also available as statics. They return a `LazyCollection`, so you can chain without loading the table:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const emails = await User.cursor()
|
|
68
|
+
.filter((user) => user.active)
|
|
69
|
+
.map((user) => user.email)
|
|
70
|
+
.take(100)
|
|
71
|
+
.toArray();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`cursor()` keeps memory flat and fires `retrieved` events. `with()` relations are loaded per batch of `chunkSize` models (default 1000), not per row. Use `lazyById()` when the loop updates the rows it reads. See `@bunyad/database` for per-driver behavior.
|
|
75
|
+
|
|
54
76
|
## Drivers
|
|
55
77
|
|
|
56
78
|
Pick one and install it next to `@bunyad/database`: `better-sqlite3`, `pg` or `mysql2`. Bun uses its built-in drivers.
|
package/dist/model-query.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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
8
|
import { type CastDefinition } from "./casts.js";
|
|
9
9
|
import { Model, type ModelClass, type ModelQueryOptions } from "./model.js";
|
|
@@ -259,14 +259,20 @@ export declare class ModelQuery<T extends Model = Model, TResult extends "model"
|
|
|
259
259
|
}): Promise<Paginator<T>>;
|
|
260
260
|
/** `chunk($count, $callback)`. */
|
|
261
261
|
chunk(count: number, callback: (models: OrmCollection<T>) => void | Promise<void>): Promise<void>;
|
|
262
|
+
/**
|
|
263
|
+
* `cursor($chunkSize)` — hydrate models one at a time from a single streamed
|
|
264
|
+
* query, so memory stays flat on large tables. `with()` relations are loaded
|
|
265
|
+
* per batch of `chunkSize` models rather than per row.
|
|
266
|
+
*/
|
|
267
|
+
cursor(chunkSize?: number): LazyCollection<T>;
|
|
262
268
|
/** `lazy($chunkSize)`. */
|
|
263
|
-
lazy(chunkSize?: number):
|
|
269
|
+
lazy(chunkSize?: number): LazyCollection<T>;
|
|
264
270
|
/** `chunkById`. */
|
|
265
271
|
chunkById(count: number, callback: (models: OrmCollection<T>) => void | boolean | Promise<void | boolean>, column?: string): Promise<boolean>;
|
|
266
272
|
chunkByIdDesc(count: number, callback: (models: OrmCollection<T>) => void | boolean | Promise<void | boolean>, column?: string): Promise<boolean>;
|
|
267
273
|
/** `lazyById`. */
|
|
268
|
-
lazyById(chunkSize?: number, column?: string):
|
|
269
|
-
lazyByIdDesc(chunkSize?: number, column?: string):
|
|
274
|
+
lazyById(chunkSize?: number, column?: string): LazyCollection<T>;
|
|
275
|
+
lazyByIdDesc(chunkSize?: number, column?: string): LazyCollection<T>;
|
|
270
276
|
increment(column: string, amount?: number): Promise<void>;
|
|
271
277
|
decrement(column: string, amount?: number): Promise<void>;
|
|
272
278
|
/** `cursorPaginate`. */
|
package/dist/model-query.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatabaseManager as Db, CursorPaginator, LengthAwarePaginator, Paginator, dateTimeForStorage, hasQueryListeners, resolvePaginatorPage, wrapSqlName, } from "@bunyad/database";
|
|
2
|
-
import { Collection } from "@bunyad/common";
|
|
2
|
+
import { Collection, LazyCollection } from "@bunyad/common";
|
|
3
3
|
import { OrmCollection } from "./orm-collection.js";
|
|
4
4
|
import { bootIfNotBooted, fireModelEvent, hasModelEventListeners, } from "./model-events.js";
|
|
5
5
|
import { getGlobalScopes, hasGlobalScopes, } from "./scopes.js";
|
|
@@ -2922,8 +2922,45 @@ export class ModelQuery {
|
|
|
2922
2922
|
page += 1;
|
|
2923
2923
|
}
|
|
2924
2924
|
}
|
|
2925
|
+
/**
|
|
2926
|
+
* `cursor($chunkSize)` — hydrate models one at a time from a single streamed
|
|
2927
|
+
* query, so memory stays flat on large tables. `with()` relations are loaded
|
|
2928
|
+
* per batch of `chunkSize` models rather than per row.
|
|
2929
|
+
*/
|
|
2930
|
+
cursor(chunkSize = 1000) {
|
|
2931
|
+
return new LazyCollection(() => this.#cursorModels(chunkSize));
|
|
2932
|
+
}
|
|
2933
|
+
async *#cursorModels(chunkSize) {
|
|
2934
|
+
const size = Math.max(1, Math.floor(chunkSize));
|
|
2935
|
+
const fireRetrieved = hasModelEventListeners(this.model, "retrieved");
|
|
2936
|
+
const eager = this.#eagerLoad.length > 0;
|
|
2937
|
+
let batch = [];
|
|
2938
|
+
for await (const row of this.#buildQuery().cursor(size)) {
|
|
2939
|
+
const model = this.#makeModel(row);
|
|
2940
|
+
if (fireRetrieved) {
|
|
2941
|
+
await fireModelEvent(model, "retrieved");
|
|
2942
|
+
}
|
|
2943
|
+
if (!eager) {
|
|
2944
|
+
yield model;
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
batch.push(model);
|
|
2948
|
+
if (batch.length >= size) {
|
|
2949
|
+
await this.#loadEager(batch);
|
|
2950
|
+
yield* batch;
|
|
2951
|
+
batch = [];
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
if (batch.length > 0) {
|
|
2955
|
+
await this.#loadEager(batch);
|
|
2956
|
+
yield* batch;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2925
2959
|
/** `lazy($chunkSize)`. */
|
|
2926
|
-
|
|
2960
|
+
lazy(chunkSize = 1000) {
|
|
2961
|
+
return new LazyCollection(() => this.#lazyPages(chunkSize));
|
|
2962
|
+
}
|
|
2963
|
+
async *#lazyPages(chunkSize) {
|
|
2927
2964
|
let page = 1;
|
|
2928
2965
|
const fireRetrieved = hasModelEventListeners(this.model, "retrieved");
|
|
2929
2966
|
for (;;) {
|
|
@@ -2976,11 +3013,11 @@ export class ModelQuery {
|
|
|
2976
3013
|
}
|
|
2977
3014
|
}
|
|
2978
3015
|
/** `lazyById`. */
|
|
2979
|
-
|
|
2980
|
-
|
|
3016
|
+
lazyById(chunkSize = 1000, column) {
|
|
3017
|
+
return new LazyCollection(() => this.#lazyById(chunkSize, column ?? this.model.primaryKey, "asc"));
|
|
2981
3018
|
}
|
|
2982
|
-
|
|
2983
|
-
|
|
3019
|
+
lazyByIdDesc(chunkSize = 1000, column) {
|
|
3020
|
+
return new LazyCollection(() => this.#lazyById(chunkSize, column ?? this.model.primaryKey, "desc"));
|
|
2984
3021
|
}
|
|
2985
3022
|
async *#lazyById(chunkSize, column, direction) {
|
|
2986
3023
|
const size = Math.max(1, Math.floor(chunkSize));
|
package/dist/model.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Connection, DatabaseManager, DateInput } from "@bunyad/database";
|
|
2
2
|
import { LengthAwarePaginator, CursorPaginator } from "@bunyad/database";
|
|
3
|
-
import { Collection } from "@bunyad/common";
|
|
3
|
+
import { Collection, LazyCollection } from "@bunyad/common";
|
|
4
4
|
import { OrmCollection } from "./orm-collection.js";
|
|
5
5
|
import { type CastDefinition } from "./casts.js";
|
|
6
6
|
import { type ModelEventListener, type ModelEventName, type ModelObserver } from "./model-events.js";
|
|
@@ -353,6 +353,14 @@ export declare class Model {
|
|
|
353
353
|
path?: string;
|
|
354
354
|
cursorName?: string;
|
|
355
355
|
}): Promise<CursorPaginator<InstanceType<T>>>;
|
|
356
|
+
/** `Model::cursor()`: stream models from a single query. */
|
|
357
|
+
static cursor<T extends typeof Model>(this: T, chunkSize?: number): LazyCollection<InstanceType<T>>;
|
|
358
|
+
/** `Model::lazy()`: chunked iteration, one query per chunk. */
|
|
359
|
+
static lazy<T extends typeof Model>(this: T, chunkSize?: number): LazyCollection<InstanceType<T>>;
|
|
360
|
+
/** `Model::lazyById()`: chunked iteration by primary key (safe while updating). */
|
|
361
|
+
static lazyById<T extends typeof Model>(this: T, chunkSize?: number, column?: string): LazyCollection<InstanceType<T>>;
|
|
362
|
+
/** `Model::lazyByIdDesc()`. */
|
|
363
|
+
static lazyByIdDesc<T extends typeof Model>(this: T, chunkSize?: number, column?: string): LazyCollection<InstanceType<T>>;
|
|
356
364
|
/** `Model::create` (respects `$fillable` / `$guarded`). */
|
|
357
365
|
static create<T extends typeof Model>(this: T, attributes: Record<string, unknown>): InstanceType<T> | Promise<InstanceType<T>>;
|
|
358
366
|
/** `Model::forceCreate` — insert every given attribute, ignoring `$fillable`. */
|
package/dist/model.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatabaseManager as Db, setDefaultConnection, connections, LengthAwarePaginator, CursorPaginator, Paginator, dateTimeForStorage, withoutQueryWriteHook, wrapSqlName, } from "@bunyad/database";
|
|
2
|
-
import { Collection } from "@bunyad/common";
|
|
2
|
+
import { Collection, LazyCollection } from "@bunyad/common";
|
|
3
3
|
import { OrmCollection } from "./orm-collection.js";
|
|
4
4
|
import { Attribute, castFromStorage, castToStorage, hashCastValue, isAlreadyHashed, enumFromStorage, enumToStorage, isAttribute, isCastType, isEnumCast, } from "./casts.js";
|
|
5
5
|
import { bootIfNotBooted, fireModelEvent, hasAnyModelEventListeners, hasModelEventListeners, observeModel, registerModelEvent, withoutModelEvents, } from "./model-events.js";
|
|
@@ -1138,6 +1138,22 @@ export class Model {
|
|
|
1138
1138
|
static async cursorPaginate(perPage = 15, cursor = null, options = {}) {
|
|
1139
1139
|
return this.newQuery().cursorPaginate(perPage, cursor, options);
|
|
1140
1140
|
}
|
|
1141
|
+
/** `Model::cursor()`: stream models from a single query. */
|
|
1142
|
+
static cursor(chunkSize) {
|
|
1143
|
+
return this.newQuery().cursor(chunkSize);
|
|
1144
|
+
}
|
|
1145
|
+
/** `Model::lazy()`: chunked iteration, one query per chunk. */
|
|
1146
|
+
static lazy(chunkSize) {
|
|
1147
|
+
return this.newQuery().lazy(chunkSize);
|
|
1148
|
+
}
|
|
1149
|
+
/** `Model::lazyById()`: chunked iteration by primary key (safe while updating). */
|
|
1150
|
+
static lazyById(chunkSize, column) {
|
|
1151
|
+
return this.newQuery().lazyById(chunkSize, column);
|
|
1152
|
+
}
|
|
1153
|
+
/** `Model::lazyByIdDesc()`. */
|
|
1154
|
+
static lazyByIdDesc(chunkSize, column) {
|
|
1155
|
+
return this.newQuery().lazyByIdDesc(chunkSize, column);
|
|
1156
|
+
}
|
|
1141
1157
|
/** `Model::create` (respects `$fillable` / `$guarded`). */
|
|
1142
1158
|
static create(attributes) {
|
|
1143
1159
|
this.bootIfNotBooted();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bunyad/orm",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.1",
|
|
4
4
|
"description": "Active-record ORM for Bunyad: models, relations, casts, scopes, events, factories.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Shah Khalid",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@bunyad/common": "^0.2.0-beta.
|
|
40
|
-
"@bunyad/database": "^0.2.0-beta.
|
|
39
|
+
"@bunyad/common": "^0.2.0-beta.1",
|
|
40
|
+
"@bunyad/database": "^0.2.0-beta.1"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|