@shaferllc/keel 0.80.0 → 0.81.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/dist/accounts/accounts.config.stub +50 -0
- package/dist/accounts/config.d.ts +46 -0
- package/dist/accounts/config.js +39 -0
- package/dist/accounts/flows.d.ts +50 -0
- package/dist/accounts/flows.js +133 -0
- package/dist/accounts/index.d.ts +28 -0
- package/dist/accounts/index.js +23 -0
- package/dist/accounts/migration.d.ts +14 -0
- package/dist/accounts/migration.js +39 -0
- package/dist/accounts/provider.d.ts +18 -0
- package/dist/accounts/provider.js +37 -0
- package/dist/accounts/routes.d.ts +15 -0
- package/dist/accounts/routes.js +116 -0
- package/dist/accounts/store.d.ts +33 -0
- package/dist/accounts/store.js +37 -0
- package/dist/accounts/tokens.d.ts +60 -0
- package/dist/accounts/tokens.js +116 -0
- package/dist/accounts/totp.d.ts +58 -0
- package/dist/accounts/totp.js +134 -0
- package/dist/accounts/two-factor.d.ts +56 -0
- package/dist/accounts/two-factor.js +146 -0
- package/dist/core/database.d.ts +36 -0
- package/dist/core/database.js +141 -4
- package/dist/core/index.d.ts +5 -2
- package/dist/core/index.js +3 -2
- package/dist/core/migrations.d.ts +52 -2
- package/dist/core/migrations.js +134 -3
- package/dist/core/model-events.d.ts +34 -0
- package/dist/core/model-events.js +71 -0
- package/dist/core/model-query.d.ts +68 -0
- package/dist/core/model-query.js +234 -0
- package/dist/core/model.d.ts +91 -4
- package/dist/core/model.js +217 -32
- package/dist/core/relations.d.ts +53 -0
- package/dist/core/relations.js +242 -0
- package/docs/accounts.md +214 -0
- package/docs/ai-manifest.json +63 -1
- package/docs/database.md +33 -0
- package/docs/examples/accounts.ts +150 -0
- package/docs/migrations.md +32 -3
- package/docs/models.md +133 -3
- package/llms-full.txt +419 -6
- package/llms.txt +2 -0
- package/package.json +6 -2
package/dist/core/model.js
CHANGED
|
@@ -17,8 +17,12 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { db } from "./database.js";
|
|
19
19
|
import { NotFoundException } from "./exceptions.js";
|
|
20
|
-
import { BelongsTo, BelongsToMany, HasMany, HasOne } from "./relations.js";
|
|
20
|
+
import { BelongsTo, BelongsToMany, HasMany, HasOne, MorphMany, MorphOne, MorphTo } from "./relations.js";
|
|
21
21
|
import { applyCasts, castGet, castSet } from "./casts.js";
|
|
22
|
+
import { addModelHook, addModelObserver, fireModelEvent, } from "./model-events.js";
|
|
23
|
+
import { ModelQuery } from "./model-query.js";
|
|
24
|
+
/** Registered global scopes, keyed by the model class. */
|
|
25
|
+
const globalScopes = new WeakMap();
|
|
22
26
|
/**
|
|
23
27
|
* Loaded relations live off the model itself so they never leak into `save()`
|
|
24
28
|
* (which spreads own columns) — they're keyed here by the owning instance.
|
|
@@ -46,14 +50,120 @@ export class Model {
|
|
|
46
50
|
static timestamps = false;
|
|
47
51
|
static createdAtColumn = "created_at";
|
|
48
52
|
static updatedAtColumn = "updated_at";
|
|
53
|
+
/** Columns stripped from `toJSON()` output (e.g. `["password"]`). */
|
|
54
|
+
static hidden = [];
|
|
55
|
+
/** If set, ONLY these keys survive `toJSON()` — an allowlist that wins over `hidden`. */
|
|
56
|
+
static visible = [];
|
|
57
|
+
/** Computed accessor names (getters or zero-arg methods) added to `toJSON()`. */
|
|
58
|
+
static appends = [];
|
|
59
|
+
/** Soft deletes: `delete()` sets `deleted_at` instead of removing the row. */
|
|
60
|
+
static softDeletes = false;
|
|
61
|
+
static deletedAtColumn = "deleted_at";
|
|
49
62
|
constructor(attributes = {}) {
|
|
50
63
|
// Hydration is unguarded (rows come from the database) but always cast.
|
|
51
64
|
Object.assign(this, applyCasts(attributes, this.constructor.casts, castGet));
|
|
52
65
|
}
|
|
53
66
|
/* ------------------------------ static -------------------------------- */
|
|
54
|
-
|
|
67
|
+
/* ---------------------------- scopes & events ------------------------- */
|
|
68
|
+
/** Register a global scope — a constraint applied to every query this model builds. */
|
|
69
|
+
static addGlobalScope(name, scope) {
|
|
70
|
+
let map = globalScopes.get(this);
|
|
71
|
+
if (!map)
|
|
72
|
+
globalScopes.set(this, (map = new Map()));
|
|
73
|
+
map.set(name, scope);
|
|
74
|
+
}
|
|
75
|
+
/** Build this model's base query, applying global scopes and the soft-delete filter. */
|
|
76
|
+
static baseQuery(trashed = "exclude") {
|
|
77
|
+
const query = db(this.table, this.connection);
|
|
78
|
+
for (const scope of globalScopes.get(this)?.values() ?? [])
|
|
79
|
+
scope(query);
|
|
80
|
+
if (this.softDeletes) {
|
|
81
|
+
if (trashed === "exclude")
|
|
82
|
+
query.whereNull(this.deletedAtColumn);
|
|
83
|
+
else if (trashed === "only")
|
|
84
|
+
query.whereNotNull(this.deletedAtColumn);
|
|
85
|
+
}
|
|
86
|
+
return query;
|
|
87
|
+
}
|
|
88
|
+
/** A query builder scoped to this model's table (global scopes applied). */
|
|
55
89
|
static query() {
|
|
56
|
-
return
|
|
90
|
+
return this.baseQuery();
|
|
91
|
+
}
|
|
92
|
+
/** Include soft-deleted rows (bypasses the soft-delete scope). */
|
|
93
|
+
static withTrashed() {
|
|
94
|
+
return this.baseQuery("with");
|
|
95
|
+
}
|
|
96
|
+
/** Only soft-deleted rows. */
|
|
97
|
+
static onlyTrashed() {
|
|
98
|
+
return this.baseQuery("only");
|
|
99
|
+
}
|
|
100
|
+
/* --------------------- model-aware query (eager, has) ----------------- */
|
|
101
|
+
/** A model-aware query — hydrates to models and supports `with`/`withCount`/`whereHas`. */
|
|
102
|
+
static newQuery() {
|
|
103
|
+
return new ModelQuery(this, this.baseQuery());
|
|
104
|
+
}
|
|
105
|
+
/** Start a query eager-loading the given relations (dotted paths nest). */
|
|
106
|
+
static with(...names) {
|
|
107
|
+
return this.newQuery().with(...names);
|
|
108
|
+
}
|
|
109
|
+
/** Start a query counting the given relations into `<relation>_count`. */
|
|
110
|
+
static withCount(...names) {
|
|
111
|
+
return this.newQuery().withCount(...names);
|
|
112
|
+
}
|
|
113
|
+
/** Start a query constrained to models that have at least one related row. */
|
|
114
|
+
static has(name) {
|
|
115
|
+
return this.newQuery().has(name);
|
|
116
|
+
}
|
|
117
|
+
/** Start a query constrained to models whose related rows match `constrain`. */
|
|
118
|
+
static whereHas(name, constrain) {
|
|
119
|
+
return this.newQuery().whereHas(name, constrain);
|
|
120
|
+
}
|
|
121
|
+
/** Start a query constrained to models with no matching related row. */
|
|
122
|
+
static doesntHave(name, constrain) {
|
|
123
|
+
return this.newQuery().doesntHave(name, constrain);
|
|
124
|
+
}
|
|
125
|
+
/** Construct a model from a row and fire its `retrieved` event. */
|
|
126
|
+
static async hydrate(row) {
|
|
127
|
+
const model = new this(row);
|
|
128
|
+
await fireModelEvent(this, "retrieved", model);
|
|
129
|
+
return model;
|
|
130
|
+
}
|
|
131
|
+
static creating(hook) {
|
|
132
|
+
addModelHook(this, "creating", hook);
|
|
133
|
+
}
|
|
134
|
+
static created(hook) {
|
|
135
|
+
addModelHook(this, "created", hook);
|
|
136
|
+
}
|
|
137
|
+
static updating(hook) {
|
|
138
|
+
addModelHook(this, "updating", hook);
|
|
139
|
+
}
|
|
140
|
+
static updated(hook) {
|
|
141
|
+
addModelHook(this, "updated", hook);
|
|
142
|
+
}
|
|
143
|
+
static saving(hook) {
|
|
144
|
+
addModelHook(this, "saving", hook);
|
|
145
|
+
}
|
|
146
|
+
static saved(hook) {
|
|
147
|
+
addModelHook(this, "saved", hook);
|
|
148
|
+
}
|
|
149
|
+
static deleting(hook) {
|
|
150
|
+
addModelHook(this, "deleting", hook);
|
|
151
|
+
}
|
|
152
|
+
static deleted(hook) {
|
|
153
|
+
addModelHook(this, "deleted", hook);
|
|
154
|
+
}
|
|
155
|
+
static restoring(hook) {
|
|
156
|
+
addModelHook(this, "restoring", hook);
|
|
157
|
+
}
|
|
158
|
+
static restored(hook) {
|
|
159
|
+
addModelHook(this, "restored", hook);
|
|
160
|
+
}
|
|
161
|
+
static retrieved(hook) {
|
|
162
|
+
addModelHook(this, "retrieved", hook);
|
|
163
|
+
}
|
|
164
|
+
/** Register an observer whose methods are named after the events they handle. */
|
|
165
|
+
static observe(observer) {
|
|
166
|
+
addModelObserver(this, observer);
|
|
57
167
|
}
|
|
58
168
|
/** Keep only the attributes mass-assignment allows (fillable / guarded). */
|
|
59
169
|
static filterFillable(attributes) {
|
|
@@ -88,12 +198,12 @@ export class Model {
|
|
|
88
198
|
return out;
|
|
89
199
|
}
|
|
90
200
|
static async all() {
|
|
91
|
-
const rows = await
|
|
92
|
-
return rows.map((row) =>
|
|
201
|
+
const rows = await this.baseQuery().get();
|
|
202
|
+
return Promise.all(rows.map((row) => this.hydrate(row)));
|
|
93
203
|
}
|
|
94
204
|
static async find(id) {
|
|
95
|
-
const row = await
|
|
96
|
-
return row ?
|
|
205
|
+
const row = await this.baseQuery().where(this.primaryKey, id).first();
|
|
206
|
+
return row ? this.hydrate(row) : null;
|
|
97
207
|
}
|
|
98
208
|
static async findOrFail(id) {
|
|
99
209
|
const model = await this.find(id);
|
|
@@ -102,54 +212,49 @@ export class Model {
|
|
|
102
212
|
return model;
|
|
103
213
|
}
|
|
104
214
|
static async first() {
|
|
105
|
-
const row = await
|
|
106
|
-
return row ?
|
|
215
|
+
const row = await this.baseQuery().first();
|
|
216
|
+
return row ? this.hydrate(row) : null;
|
|
107
217
|
}
|
|
108
218
|
/** Fetch models matching a simple equality condition. */
|
|
109
219
|
static async where(column, value) {
|
|
110
|
-
const rows = await
|
|
111
|
-
return rows.map((row) =>
|
|
220
|
+
const rows = await this.baseQuery().where(column, value).get();
|
|
221
|
+
return Promise.all(rows.map((row) => this.hydrate(row)));
|
|
112
222
|
}
|
|
113
223
|
static async create(attributes) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
model[this.createdAtColumn] = write[this.createdAtColumn];
|
|
120
|
-
model[this.updatedAtColumn] = write[this.updatedAtColumn];
|
|
121
|
-
}
|
|
122
|
-
if (id != null)
|
|
123
|
-
model[this.primaryKey] = id;
|
|
224
|
+
// Route through save() so mass-assignment, timestamps, and the saving/creating
|
|
225
|
+
// lifecycle events all apply in one place.
|
|
226
|
+
const model = new this();
|
|
227
|
+
model.fill(attributes);
|
|
228
|
+
await model.save();
|
|
124
229
|
return model;
|
|
125
230
|
}
|
|
126
231
|
/** A page of models plus pagination metadata. */
|
|
127
232
|
static async paginate(page = 1, perPage = 15) {
|
|
128
|
-
const result = await
|
|
129
|
-
return { ...result, data: result.data.map((row) =>
|
|
233
|
+
const result = await this.baseQuery().paginate(page, perPage);
|
|
234
|
+
return { ...result, data: await Promise.all(result.data.map((row) => this.hydrate(row))) };
|
|
130
235
|
}
|
|
131
236
|
/** Find the first row matching `match`, or create one from `{ ...match, ...values }`. */
|
|
132
237
|
static async firstOrCreate(match, values = {}) {
|
|
133
238
|
const row = await this.matching(match).first();
|
|
134
239
|
if (row)
|
|
135
|
-
return
|
|
240
|
+
return this.hydrate(row);
|
|
136
241
|
return this.create({ ...match, ...values });
|
|
137
242
|
}
|
|
138
243
|
/** Update the first row matching `match` with `values`, or create it. */
|
|
139
244
|
static async updateOrCreate(match, values = {}) {
|
|
140
245
|
const row = await this.matching(match).first();
|
|
141
246
|
if (row) {
|
|
142
|
-
const model =
|
|
247
|
+
const model = await this.hydrate(row);
|
|
143
248
|
await model.forceFill(values).save();
|
|
144
249
|
return model;
|
|
145
250
|
}
|
|
146
251
|
return this.create({ ...match, ...values });
|
|
147
252
|
}
|
|
148
|
-
/** A query scoped to every column/value in `match
|
|
253
|
+
/** A query scoped to every column/value in `match` (global scopes applied). */
|
|
149
254
|
static matching(match) {
|
|
150
|
-
|
|
255
|
+
const q = this.baseQuery();
|
|
151
256
|
for (const [column, value] of Object.entries(match))
|
|
152
|
-
q
|
|
257
|
+
q.where(column, value);
|
|
153
258
|
return q;
|
|
154
259
|
}
|
|
155
260
|
/**
|
|
@@ -197,6 +302,18 @@ export class Model {
|
|
|
197
302
|
.join("_"), foreignPivotKey = `${this.constructor.name.toLowerCase()}_${this.ctor().primaryKey}`, relatedPivotKey = `${related.name.toLowerCase()}_${related.primaryKey}`, parentKey = this.ctor().primaryKey, relatedKey = related.primaryKey) {
|
|
198
303
|
return new BelongsToMany(this, related, pivotTable, foreignPivotKey, relatedPivotKey, parentKey, relatedKey);
|
|
199
304
|
}
|
|
305
|
+
/** Polymorphic one-to-many: children carry `<name>_id` + `<name>_type`. */
|
|
306
|
+
morphMany(related, name, localKey = this.ctor().primaryKey) {
|
|
307
|
+
return new MorphMany(this, related, this.constructor.name, `${name}_id`, `${name}_type`, localKey);
|
|
308
|
+
}
|
|
309
|
+
/** Polymorphic one-to-one. */
|
|
310
|
+
morphOne(related, name, localKey = this.ctor().primaryKey) {
|
|
311
|
+
return new MorphOne(this, related, this.constructor.name, `${name}_id`, `${name}_type`, localKey);
|
|
312
|
+
}
|
|
313
|
+
/** The owning side of a polymorphic relation — resolves via `<name>_type` (see `registerMorphType`). */
|
|
314
|
+
morphTo(name, idColumn = `${name}_id`, typeColumn = `${name}_type`) {
|
|
315
|
+
return new MorphTo(this, idColumn, typeColumn);
|
|
316
|
+
}
|
|
200
317
|
/** Store an eager-loaded relation result under `name`. */
|
|
201
318
|
setRelation(name, value) {
|
|
202
319
|
const bag = relationStore.get(this) ?? {};
|
|
@@ -208,12 +325,18 @@ export class Model {
|
|
|
208
325
|
getRelation(name) {
|
|
209
326
|
return relationStore.get(this)?.[name];
|
|
210
327
|
}
|
|
211
|
-
/** Insert (no primary key) or update (has one). */
|
|
328
|
+
/** Insert (no primary key) or update (has one). Fires save/create/update events. */
|
|
212
329
|
async save() {
|
|
213
330
|
const ctor = this.ctor();
|
|
331
|
+
const cls = this.constructor;
|
|
214
332
|
const { table, primaryKey, connection } = ctor;
|
|
215
333
|
const idValue = this[primaryKey];
|
|
216
334
|
const forInsert = idValue == null;
|
|
335
|
+
// `*ing` hooks run before the write and may mutate the model or veto it.
|
|
336
|
+
if (!(await fireModelEvent(cls, "saving", this)))
|
|
337
|
+
return this;
|
|
338
|
+
if (!(await fireModelEvent(cls, forInsert ? "creating" : "updating", this)))
|
|
339
|
+
return this;
|
|
217
340
|
const data = ctor.stampTimestamps(ctor.toDatabase({ ...this }), forInsert);
|
|
218
341
|
// Reflect the stamps back onto the instance.
|
|
219
342
|
if (ctor.timestamps) {
|
|
@@ -230,6 +353,8 @@ export class Model {
|
|
|
230
353
|
if (id != null)
|
|
231
354
|
this[primaryKey] = id;
|
|
232
355
|
}
|
|
356
|
+
await fireModelEvent(cls, forInsert ? "created" : "updated", this);
|
|
357
|
+
await fireModelEvent(cls, "saved", this);
|
|
233
358
|
return this;
|
|
234
359
|
}
|
|
235
360
|
/** Mass-assign then save — `fill` + `save` in one call. */
|
|
@@ -244,9 +369,51 @@ export class Model {
|
|
|
244
369
|
Object.assign(this, applyCasts(row, ctor.casts, castGet));
|
|
245
370
|
return this;
|
|
246
371
|
}
|
|
372
|
+
/** Delete the row — or, with soft deletes on, set `deleted_at`. Fires delete events. */
|
|
247
373
|
async delete() {
|
|
248
|
-
const
|
|
249
|
-
|
|
374
|
+
const ctor = this.ctor();
|
|
375
|
+
const cls = this.constructor;
|
|
376
|
+
const { table, primaryKey, connection } = ctor;
|
|
377
|
+
if (!(await fireModelEvent(cls, "deleting", this)))
|
|
378
|
+
return;
|
|
379
|
+
if (ctor.softDeletes) {
|
|
380
|
+
const now = new Date().toISOString();
|
|
381
|
+
await db(table, connection)
|
|
382
|
+
.where(primaryKey, this[primaryKey])
|
|
383
|
+
.update({ [ctor.deletedAtColumn]: now });
|
|
384
|
+
this[ctor.deletedAtColumn] = now;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
await db(table, connection).where(primaryKey, this[primaryKey]).delete();
|
|
388
|
+
}
|
|
389
|
+
await fireModelEvent(cls, "deleted", this);
|
|
390
|
+
}
|
|
391
|
+
/** Permanently delete a soft-deletable row (bypasses soft deletes). */
|
|
392
|
+
async forceDelete() {
|
|
393
|
+
const ctor = this.ctor();
|
|
394
|
+
const cls = this.constructor;
|
|
395
|
+
if (!(await fireModelEvent(cls, "deleting", this)))
|
|
396
|
+
return;
|
|
397
|
+
await db(ctor.table, ctor.connection).where(ctor.primaryKey, this[ctor.primaryKey]).delete();
|
|
398
|
+
await fireModelEvent(cls, "deleted", this);
|
|
399
|
+
}
|
|
400
|
+
/** Restore a soft-deleted row (clears `deleted_at`). Fires restore events. */
|
|
401
|
+
async restore() {
|
|
402
|
+
const ctor = this.ctor();
|
|
403
|
+
const cls = this.constructor;
|
|
404
|
+
if (!(await fireModelEvent(cls, "restoring", this)))
|
|
405
|
+
return this;
|
|
406
|
+
await db(ctor.table, ctor.connection)
|
|
407
|
+
.where(ctor.primaryKey, this[ctor.primaryKey])
|
|
408
|
+
.update({ [ctor.deletedAtColumn]: null });
|
|
409
|
+
this[ctor.deletedAtColumn] = null;
|
|
410
|
+
await fireModelEvent(cls, "restored", this);
|
|
411
|
+
return this;
|
|
412
|
+
}
|
|
413
|
+
/** Whether this soft-deletable model is currently trashed. */
|
|
414
|
+
trashed() {
|
|
415
|
+
const ctor = this.ctor();
|
|
416
|
+
return ctor.softDeletes && this[ctor.deletedAtColumn] != null;
|
|
250
417
|
}
|
|
251
418
|
/** Merge mass-assignable attributes into the model (cast, not saved). */
|
|
252
419
|
fill(attributes) {
|
|
@@ -261,7 +428,25 @@ export class Model {
|
|
|
261
428
|
return this;
|
|
262
429
|
}
|
|
263
430
|
toJSON() {
|
|
264
|
-
const
|
|
431
|
+
const ctor = this.ctor();
|
|
432
|
+
let data = applyCasts({ ...this }, ctor.casts, castGet);
|
|
433
|
+
// Appended computed attributes: a getter (value) or a zero-arg method.
|
|
434
|
+
for (const name of ctor.appends) {
|
|
435
|
+
const value = this[name];
|
|
436
|
+
data[name] = typeof value === "function" ? value.call(this) : value;
|
|
437
|
+
}
|
|
438
|
+
// `visible` is an allowlist and wins; otherwise `hidden` is a denylist.
|
|
439
|
+
if (ctor.visible.length) {
|
|
440
|
+
const kept = {};
|
|
441
|
+
for (const key of ctor.visible)
|
|
442
|
+
if (key in data)
|
|
443
|
+
kept[key] = data[key];
|
|
444
|
+
data = kept;
|
|
445
|
+
}
|
|
446
|
+
else if (ctor.hidden.length) {
|
|
447
|
+
for (const key of ctor.hidden)
|
|
448
|
+
delete data[key];
|
|
449
|
+
}
|
|
265
450
|
const relations = relationStore.get(this);
|
|
266
451
|
if (relations) {
|
|
267
452
|
for (const [name, value] of Object.entries(relations))
|
package/dist/core/relations.d.ts
CHANGED
|
@@ -49,6 +49,9 @@ export declare class HasMany<T extends Model> extends Relation<T, T[]> {
|
|
|
49
49
|
query(): QueryBuilder;
|
|
50
50
|
get(): Promise<T[]>;
|
|
51
51
|
eager(models: Model[], name: string): Promise<void>;
|
|
52
|
+
parentColumn(): string;
|
|
53
|
+
matchingParentKeys(constrain?: (q: QueryBuilder) => void): Promise<unknown[]>;
|
|
54
|
+
countsByParent(parentKeys: unknown[]): Promise<Map<unknown, number>>;
|
|
52
55
|
}
|
|
53
56
|
export declare class HasOne<T extends Model> extends Relation<T, T | null> {
|
|
54
57
|
private foreignKey;
|
|
@@ -58,6 +61,9 @@ export declare class HasOne<T extends Model> extends Relation<T, T | null> {
|
|
|
58
61
|
query(): QueryBuilder;
|
|
59
62
|
get(): Promise<T | null>;
|
|
60
63
|
eager(models: Model[], name: string): Promise<void>;
|
|
64
|
+
parentColumn(): string;
|
|
65
|
+
matchingParentKeys(constrain?: (q: QueryBuilder) => void): Promise<unknown[]>;
|
|
66
|
+
countsByParent(parentKeys: unknown[]): Promise<Map<unknown, number>>;
|
|
61
67
|
}
|
|
62
68
|
export declare class BelongsTo<T extends Model> extends Relation<T, T | null> {
|
|
63
69
|
private foreignKey;
|
|
@@ -67,6 +73,9 @@ export declare class BelongsTo<T extends Model> extends Relation<T, T | null> {
|
|
|
67
73
|
query(): QueryBuilder;
|
|
68
74
|
get(): Promise<T | null>;
|
|
69
75
|
eager(models: Model[], name: string): Promise<void>;
|
|
76
|
+
parentColumn(): string;
|
|
77
|
+
matchingParentKeys(constrain?: (q: QueryBuilder) => void): Promise<unknown[]>;
|
|
78
|
+
countsByParent(parentKeys: unknown[]): Promise<Map<unknown, number>>;
|
|
70
79
|
}
|
|
71
80
|
export declare class BelongsToMany<T extends Model> extends Relation<T, T[]> {
|
|
72
81
|
private pivotTable;
|
|
@@ -86,5 +95,49 @@ export declare class BelongsToMany<T extends Model> extends Relation<T, T[]> {
|
|
|
86
95
|
detach(id?: unknown): Promise<void>;
|
|
87
96
|
/** Make the pivot contain exactly `ids` (detach the rest, attach the new). */
|
|
88
97
|
sync(ids: unknown[]): Promise<void>;
|
|
98
|
+
parentColumn(): string;
|
|
99
|
+
matchingParentKeys(constrain?: (q: QueryBuilder) => void): Promise<unknown[]>;
|
|
100
|
+
countsByParent(parentKeys: unknown[]): Promise<Map<unknown, number>>;
|
|
101
|
+
}
|
|
102
|
+
/** Register a model under a morph type string (usually its class name). */
|
|
103
|
+
export declare function registerMorphType(type: string, related: ModelClass<Model>): void;
|
|
104
|
+
/** The parent side of a polymorphic one-to-many (`Post.comments()` over `commentable`). */
|
|
105
|
+
export declare class MorphMany<T extends Model> extends Relation<T, T[]> {
|
|
106
|
+
private morphType;
|
|
107
|
+
private idColumn;
|
|
108
|
+
private typeColumn;
|
|
109
|
+
private localKey;
|
|
110
|
+
constructor(parent: Model, related: ModelClass<T>, morphType: string, idColumn: string, typeColumn: string, localKey: string);
|
|
111
|
+
private localValue;
|
|
112
|
+
query(): QueryBuilder;
|
|
113
|
+
get(): Promise<T[]>;
|
|
114
|
+
eager(models: Model[], name: string): Promise<void>;
|
|
115
|
+
parentColumn(): string;
|
|
116
|
+
matchingParentKeys(constrain?: (q: QueryBuilder) => void): Promise<unknown[]>;
|
|
117
|
+
countsByParent(parentKeys: unknown[]): Promise<Map<unknown, number>>;
|
|
118
|
+
/** Create a related row with the morph keys (`*_id` / `*_type`) filled in. */
|
|
119
|
+
create(attributes: Row): Promise<T>;
|
|
120
|
+
}
|
|
121
|
+
/** The parent side of a polymorphic one-to-one. */
|
|
122
|
+
export declare class MorphOne<T extends Model> extends Relation<T, T | null> {
|
|
123
|
+
private morphType;
|
|
124
|
+
private idColumn;
|
|
125
|
+
private typeColumn;
|
|
126
|
+
private localKey;
|
|
127
|
+
constructor(parent: Model, related: ModelClass<T>, morphType: string, idColumn: string, typeColumn: string, localKey: string);
|
|
128
|
+
query(): QueryBuilder;
|
|
129
|
+
get(): Promise<T | null>;
|
|
130
|
+
eager(models: Model[], name: string): Promise<void>;
|
|
131
|
+
}
|
|
132
|
+
/** The owning side of a polymorphic relation — resolves its parent by stored type + id. */
|
|
133
|
+
export declare class MorphTo implements PromiseLike<Model | null> {
|
|
134
|
+
private parent;
|
|
135
|
+
private idColumn;
|
|
136
|
+
private typeColumn;
|
|
137
|
+
constructor(parent: Model, idColumn: string, typeColumn: string);
|
|
138
|
+
private relatedClass;
|
|
139
|
+
get(): Promise<Model | null>;
|
|
140
|
+
eager(models: Model[], name: string): Promise<void>;
|
|
141
|
+
then<R1 = Model | null, R2 = never>(onFulfilled?: ((value: Model | null) => R1 | PromiseLike<R1>) | null, onRejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
|
|
89
142
|
}
|
|
90
143
|
export {};
|