@getstrata/core 2.2.2 → 2.2.3
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/CHANGELOG.md +4 -0
- package/dist/core/database/baseRepository.d.ts +2 -0
- package/dist/core/database/keyset.d.ts +7 -0
- package/dist/core/database/model.d.ts +14 -1
- package/dist/core/database/repositoryQuery.d.ts +9 -1
- package/dist/core/database/types.d.ts +5 -0
- package/dist/core/pagination/index.d.ts +21 -0
- package/dist/entries/database/query.js +6 -0
- package/dist/entries/database/repositoryQuery.js +20 -0
- package/dist/framework/public-api.d.ts +1 -1
- package/dist/index.js +137 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { KeysetCursor, KeysetOptions } from "../pagination/index.ts";
|
|
1
2
|
import { type CursorPaginatedResult, type PaginatedResult } from "../pagination/index.ts";
|
|
2
3
|
import { type BelongsToManyRelation, type BelongsToRelation, type HasManyRelation, type HasManyThroughRelation, type MorphManyRelation, type MorphOneRelation, type MorphToRelation } from "./relationships.ts";
|
|
3
4
|
import { RepositoryQuery } from "./repositoryQuery.ts";
|
|
@@ -37,6 +38,7 @@ declare class BaseRepository<TEntity extends object, PrimaryKey extends keyof TE
|
|
|
37
38
|
cursorColumn?: PrimaryKey;
|
|
38
39
|
direction?: "asc" | "desc";
|
|
39
40
|
} & Omit<ExtendedQueryOptions<TEntity>, "limit" | "offset">): Promise<CursorPaginatedResult<TEntity, TEntity[PrimaryKey]>>;
|
|
41
|
+
keysetPaginate(options: KeysetOptions<TEntity> & Omit<ExtendedQueryOptions<TEntity>, "orderBy" | "limit" | "offset">): Promise<CursorPaginatedResult<TEntity, KeysetCursor>>;
|
|
40
42
|
findById(id: TEntity[PrimaryKey]): Promise<TEntity | null>;
|
|
41
43
|
findByIdOrThrow(id: TEntity[PrimaryKey], errorFactory?: ErrorFactory<TEntity[PrimaryKey]>): Promise<TEntity>;
|
|
42
44
|
findByIds(ids: readonly TEntity[PrimaryKey][]): Promise<TEntity[]>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { KeysetCursor, KeysetOptions } from "../pagination/index.ts";
|
|
2
|
+
import type { TableDefinition } from "./table.ts";
|
|
3
|
+
import type { WhereNode } from "./whereBuilder.ts";
|
|
4
|
+
declare const KEYSET_ALIAS_PREFIX = "__strata_keyset_";
|
|
5
|
+
declare function validateKeyset<TEntity extends object, PK extends keyof TEntity & string>(table: TableDefinition<TEntity, PK>, options: KeysetOptions<TEntity>): KeysetCursor["order"];
|
|
6
|
+
declare function keysetBoundary<TEntity extends object>(order: KeysetCursor["order"], cursor?: KeysetCursor): WhereNode<TEntity>[];
|
|
7
|
+
export { KEYSET_ALIAS_PREFIX, keysetBoundary, validateKeyset };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CursorPaginatedResult, KeysetCursor, KeysetOptions } from "../pagination/index.ts";
|
|
1
2
|
import BaseRepository from "./baseRepository.ts";
|
|
2
3
|
import type { AnyRelationQuery, RelatedModelClass } from "./relationQuery.ts";
|
|
3
4
|
import { BelongsToManyRelationQuery, BelongsToRelationQuery, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery } from "./relationQuery.ts";
|
|
@@ -55,7 +56,9 @@ declare function applyCasts(values: LoadedAttributes, casts: ModelCasts, directi
|
|
|
55
56
|
/** Read-only plain records; partial selection never acquires model mutation methods. */
|
|
56
57
|
declare class ModelProjection<TEntity extends object, K extends keyof TEntity & string> {
|
|
57
58
|
private readonly load;
|
|
58
|
-
|
|
59
|
+
private readonly page;
|
|
60
|
+
constructor(load: (limit?: number) => Promise<Array<Pick<TEntity, K>>>, page: (options: KeysetOptions<TEntity>) => Promise<CursorPaginatedResult<Pick<TEntity, K>, KeysetCursor>>);
|
|
61
|
+
keysetPaginate(options: KeysetOptions<TEntity>): Promise<CursorPaginatedResult<Pick<TEntity, K>, KeysetCursor>>;
|
|
59
62
|
get(): Promise<Array<Pick<TEntity, K>>>;
|
|
60
63
|
first(): Promise<Pick<TEntity, K> | null>;
|
|
61
64
|
}
|
|
@@ -106,6 +109,10 @@ declare class ModelQuery<TModel extends object = AnyModel, TCounts extends strin
|
|
|
106
109
|
withTrashed(): this;
|
|
107
110
|
onlyTrashed(): this;
|
|
108
111
|
get(): Promise<ModelQueryResult<TModel, TCounts>[]>;
|
|
112
|
+
keysetPaginate(options: KeysetOptions<ModelAttributes<TModel>>): Promise<{
|
|
113
|
+
meta: import("../pagination/index.ts").CursorPaginationMeta<KeysetCursor>;
|
|
114
|
+
data: ModelQueryResult<TModel, TCounts>[];
|
|
115
|
+
}>;
|
|
109
116
|
private hydrateRows;
|
|
110
117
|
first(): Promise<ModelQueryResult<TModel, TCounts> | null>;
|
|
111
118
|
count(): Promise<number>;
|
|
@@ -186,6 +193,12 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
|
|
|
186
193
|
static chunk<TModel extends object>(this: {
|
|
187
194
|
prototype: TModel;
|
|
188
195
|
}, count: number, callback: (models: TModel[]) => Promise<boolean | void>): Promise<void>;
|
|
196
|
+
static keysetPaginate<TModel extends object>(this: {
|
|
197
|
+
prototype: TModel;
|
|
198
|
+
}, options: KeysetOptions<ModelAttributes<TModel>>): Promise<{
|
|
199
|
+
meta: import("../pagination/index.ts").CursorPaginationMeta<KeysetCursor>;
|
|
200
|
+
data: TModel[];
|
|
201
|
+
}>;
|
|
189
202
|
static cursorPaginate<TModel extends object>(this: {
|
|
190
203
|
prototype: TModel;
|
|
191
204
|
}, options: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PaginatedResult } from "../pagination/index.ts";
|
|
1
|
+
import type { KeysetOptions, PaginatedResult } from "../pagination/index.ts";
|
|
2
2
|
import type BaseRepository from "./baseRepository.ts";
|
|
3
3
|
import type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasManyThroughRelation, MorphManyRelation, MorphOneRelation, MorphToRelation } from "./relationships.ts";
|
|
4
4
|
import type { QueryLockOptions, QueryOptions, QueryWhere, UpdateValues } from "./types.ts";
|
|
@@ -66,6 +66,14 @@ declare class RepositoryQuery<TEntity extends object, PrimaryKey extends keyof T
|
|
|
66
66
|
meta: import("../pagination/index.ts").CursorPaginationMeta<TEntity[PrimaryKey]>;
|
|
67
67
|
data: (TEntity & LoadedRow)[];
|
|
68
68
|
}>;
|
|
69
|
+
keysetPaginate(options: KeysetOptions<TEntity>): Promise<{
|
|
70
|
+
meta: import("../pagination/index.ts").CursorPaginationMeta<import("../pagination/index.ts").KeysetCursor>;
|
|
71
|
+
data: (TEntity & LoadedRow)[];
|
|
72
|
+
}>;
|
|
73
|
+
projectKeyset<K extends keyof TEntity & string>(columns: readonly K[], options: KeysetOptions<TEntity>): Promise<{
|
|
74
|
+
meta: import("../pagination/index.ts").CursorPaginationMeta<import("../pagination/index.ts").KeysetCursor>;
|
|
75
|
+
data: Array<Pick<TEntity, K>>;
|
|
76
|
+
}>;
|
|
69
77
|
/** Compiled query filters for batched relationship loaders. */
|
|
70
78
|
getOptions(): QueryOptions<TEntity>;
|
|
71
79
|
private buildOptions;
|
|
@@ -25,3 +25,24 @@ declare function buildPaginationMeta(input: {
|
|
|
25
25
|
}): PaginationMeta;
|
|
26
26
|
export type { CursorPaginatedResult, CursorPaginationMeta, PaginatedResult, PaginationMeta };
|
|
27
27
|
export { buildPaginationMeta };
|
|
28
|
+
/** Ordering columns must be non-null scalar fields in the underlying schema. */
|
|
29
|
+
type KeysetColumn<TEntity> = {
|
|
30
|
+
[K in keyof TEntity & string]: TEntity[K] extends string | number | bigint | Date ? K : never;
|
|
31
|
+
}[keyof TEntity & string];
|
|
32
|
+
interface KeysetCursor {
|
|
33
|
+
version: 1;
|
|
34
|
+
order: Array<{
|
|
35
|
+
column: string;
|
|
36
|
+
direction: "asc" | "desc";
|
|
37
|
+
}>;
|
|
38
|
+
values: string[];
|
|
39
|
+
}
|
|
40
|
+
interface KeysetOptions<TEntity> {
|
|
41
|
+
perPage: number;
|
|
42
|
+
orderBy: readonly {
|
|
43
|
+
column: KeysetColumn<TEntity>;
|
|
44
|
+
direction: "asc" | "desc";
|
|
45
|
+
}[];
|
|
46
|
+
cursor?: KeysetCursor;
|
|
47
|
+
}
|
|
48
|
+
export type { KeysetColumn, KeysetCursor, KeysetOptions };
|
|
@@ -305,6 +305,12 @@ function buildSelectList(table, select, params = []) {
|
|
|
305
305
|
const column = qualifyColumn(item.table, item.column);
|
|
306
306
|
return item.as ? `${column} AS ${quoteIdentifier(item.as)}` : column;
|
|
307
307
|
}
|
|
308
|
+
if (item.kind === "textColumn") {
|
|
309
|
+
const dialect = currentSqlDialect();
|
|
310
|
+
const column = qualifyColumn(item.table, item.column);
|
|
311
|
+
const text = dialect.driver === "sqlite" ? `CASE WHEN typeof(${column}) = 'real' THEN printf('%!.26g', ${column}) ELSE ${dialect.castToText(column)} END` : dialect.castToText(column);
|
|
312
|
+
return `${text} AS ${quoteIdentifier(item.as)}`;
|
|
313
|
+
}
|
|
308
314
|
if (item.kind === "literalText") {
|
|
309
315
|
return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
|
|
310
316
|
}
|
|
@@ -329,6 +329,12 @@ function buildSelectList(table, select, params = []) {
|
|
|
329
329
|
const column = qualifyColumn(item.table, item.column);
|
|
330
330
|
return item.as ? `${column} AS ${quoteIdentifier(item.as)}` : column;
|
|
331
331
|
}
|
|
332
|
+
if (item.kind === "textColumn") {
|
|
333
|
+
const dialect = currentSqlDialect();
|
|
334
|
+
const column = qualifyColumn(item.table, item.column);
|
|
335
|
+
const text = dialect.driver === "sqlite" ? `CASE WHEN typeof(${column}) = 'real' THEN printf('%!.26g', ${column}) ELSE ${dialect.castToText(column)} END` : dialect.castToText(column);
|
|
336
|
+
return `${text} AS ${quoteIdentifier(item.as)}`;
|
|
337
|
+
}
|
|
332
338
|
if (item.kind === "literalText") {
|
|
333
339
|
return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
|
|
334
340
|
}
|
|
@@ -1157,6 +1163,20 @@ class RepositoryQuery {
|
|
|
1157
1163
|
const page = await this.repository.cursorPaginate({ ...this.buildOptions(), ...options });
|
|
1158
1164
|
return { ...page, data: await this.attach(page.data) };
|
|
1159
1165
|
}
|
|
1166
|
+
async keysetPaginate(options) {
|
|
1167
|
+
const page = await this.repository.keysetPaginate({ ...this.buildOptions(), ...options });
|
|
1168
|
+
return { ...page, data: await this.attach(page.data) };
|
|
1169
|
+
}
|
|
1170
|
+
async projectKeyset(columns, options) {
|
|
1171
|
+
if (!columns.length || columns.some((column) => !this.repository.getTable().columns.includes(column)))
|
|
1172
|
+
throw new TypeError("Keyset projections require declared columns.");
|
|
1173
|
+
const page = await this.repository.keysetPaginate({
|
|
1174
|
+
...this.buildOptions(),
|
|
1175
|
+
...options,
|
|
1176
|
+
select: uniqueColumnSelect(this.repository.getTable().name, columns)
|
|
1177
|
+
});
|
|
1178
|
+
return { ...page, data: page.data };
|
|
1179
|
+
}
|
|
1160
1180
|
getOptions() {
|
|
1161
1181
|
return this.buildOptions();
|
|
1162
1182
|
}
|
|
@@ -126,7 +126,7 @@ export type { MetricLabels } from "../core/metrics/prometheus.ts";
|
|
|
126
126
|
export { PrometheusRegistry, prometheusRegistry } from "../core/metrics/prometheus.ts";
|
|
127
127
|
export type { DatabaseNotificationPayload, DatabaseNotificationStore, MailNotificationMessage, Notifiable, NotificationChannelName, } from "../core/notifications/index.ts";
|
|
128
128
|
export { createNotificationDispatcher, Notification, NotificationDispatcher, } from "../core/notifications/index.ts";
|
|
129
|
-
export type { CursorPaginatedResult, PaginatedResult, PaginationMeta, } from "../core/pagination/index.ts";
|
|
129
|
+
export type { CursorPaginatedResult, KeysetColumn, KeysetCursor, KeysetOptions, PaginatedResult, PaginationMeta, } from "../core/pagination/index.ts";
|
|
130
130
|
export type { Queue, QueuePriority } from "../core/queue/index.ts";
|
|
131
131
|
export { AsyncQueue, createQueue, Job, SyncQueue } from "../core/queue/index.ts";
|
|
132
132
|
export { JobRegistry } from "../core/queue/jobRegistry.ts";
|
package/dist/index.js
CHANGED
|
@@ -4131,6 +4131,45 @@ function buildPaginationMeta(input) {
|
|
|
4131
4131
|
init_connectionContext();
|
|
4132
4132
|
init_dialect();
|
|
4133
4133
|
|
|
4134
|
+
// ../../src/core/database/keyset.ts
|
|
4135
|
+
var KEYSET_ALIAS_PREFIX = "__strata_keyset_";
|
|
4136
|
+
function validateKeyset(table, options) {
|
|
4137
|
+
if (!Number.isInteger(options.perPage) || options.perPage < 1 || options.perPage > 1000)
|
|
4138
|
+
throw new RangeError("Keyset page size must be between 1 and 1000.");
|
|
4139
|
+
if (!Array.isArray(options.orderBy) || options.orderBy.length < 1 || options.orderBy.length > 8)
|
|
4140
|
+
throw new TypeError("Keyset ordering requires between 1 and 8 columns.");
|
|
4141
|
+
if (table.columns.some((column) => column.toLowerCase().startsWith(KEYSET_ALIAS_PREFIX)))
|
|
4142
|
+
throw new TypeError("Table columns conflict with reserved keyset aliases.");
|
|
4143
|
+
const seen = new Set;
|
|
4144
|
+
const order = Array.from(options.orderBy).map(({ column, direction }) => {
|
|
4145
|
+
if (!table.columns.includes(column) || seen.has(column) || !["asc", "desc"].includes(direction))
|
|
4146
|
+
throw new TypeError("Invalid or duplicate keyset ordering column/direction.");
|
|
4147
|
+
seen.add(column);
|
|
4148
|
+
return { column, direction };
|
|
4149
|
+
});
|
|
4150
|
+
if (order.at(-1)?.column !== table.primaryKey)
|
|
4151
|
+
throw new TypeError("Keyset ordering must end with the table primary key.");
|
|
4152
|
+
if (options.cursor !== undefined) {
|
|
4153
|
+
const cursor = options.cursor;
|
|
4154
|
+
if (cursor?.version !== 1 || !Array.isArray(cursor.order) || cursor.order.length !== order.length || !Array.isArray(cursor.values) || cursor.values.length !== order.length || Array.from(cursor.order).some((item, i) => !item || item.column !== order[i]?.column || item.direction !== order[i]?.direction) || Array.from(cursor.values).some((value) => typeof value !== "string" || value.length > 4096))
|
|
4155
|
+
throw new TypeError("Invalid keyset cursor or ordering mismatch.");
|
|
4156
|
+
}
|
|
4157
|
+
return order;
|
|
4158
|
+
}
|
|
4159
|
+
function keysetBoundary(order, cursor) {
|
|
4160
|
+
if (!cursor)
|
|
4161
|
+
return [];
|
|
4162
|
+
const branches = order.map((item, i) => {
|
|
4163
|
+
const where = {};
|
|
4164
|
+
order.slice(0, i).forEach((prefix, j) => {
|
|
4165
|
+
where[prefix.column] = cursor.values[j];
|
|
4166
|
+
});
|
|
4167
|
+
where[item.column] = { [item.direction === "asc" ? "gt" : "lt"]: cursor.values[i] };
|
|
4168
|
+
return { kind: i === 0 ? "and" : "or", where };
|
|
4169
|
+
});
|
|
4170
|
+
return [{ kind: "and", group: branches }];
|
|
4171
|
+
}
|
|
4172
|
+
|
|
4134
4173
|
// ../../src/core/database/query.ts
|
|
4135
4174
|
init_dialect();
|
|
4136
4175
|
function quoteIdentifier(identifier) {
|
|
@@ -4437,6 +4476,12 @@ function buildSelectList(table, select, params = []) {
|
|
|
4437
4476
|
const column = qualifyColumn(item.table, item.column);
|
|
4438
4477
|
return item.as ? `${column} AS ${quoteIdentifier(item.as)}` : column;
|
|
4439
4478
|
}
|
|
4479
|
+
if (item.kind === "textColumn") {
|
|
4480
|
+
const dialect = currentSqlDialect();
|
|
4481
|
+
const column = qualifyColumn(item.table, item.column);
|
|
4482
|
+
const text = dialect.driver === "sqlite" ? `CASE WHEN typeof(${column}) = 'real' THEN printf('%!.26g', ${column}) ELSE ${dialect.castToText(column)} END` : dialect.castToText(column);
|
|
4483
|
+
return `${text} AS ${quoteIdentifier(item.as)}`;
|
|
4484
|
+
}
|
|
4440
4485
|
if (item.kind === "literalText") {
|
|
4441
4486
|
return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
|
|
4442
4487
|
}
|
|
@@ -5292,6 +5337,20 @@ class RepositoryQuery {
|
|
|
5292
5337
|
const page = await this.repository.cursorPaginate({ ...this.buildOptions(), ...options });
|
|
5293
5338
|
return { ...page, data: await this.attach(page.data) };
|
|
5294
5339
|
}
|
|
5340
|
+
async keysetPaginate(options) {
|
|
5341
|
+
const page = await this.repository.keysetPaginate({ ...this.buildOptions(), ...options });
|
|
5342
|
+
return { ...page, data: await this.attach(page.data) };
|
|
5343
|
+
}
|
|
5344
|
+
async projectKeyset(columns, options) {
|
|
5345
|
+
if (!columns.length || columns.some((column) => !this.repository.getTable().columns.includes(column)))
|
|
5346
|
+
throw new TypeError("Keyset projections require declared columns.");
|
|
5347
|
+
const page = await this.repository.keysetPaginate({
|
|
5348
|
+
...this.buildOptions(),
|
|
5349
|
+
...options,
|
|
5350
|
+
select: uniqueColumnSelect(this.repository.getTable().name, columns)
|
|
5351
|
+
});
|
|
5352
|
+
return { ...page, data: page.data };
|
|
5353
|
+
}
|
|
5295
5354
|
getOptions() {
|
|
5296
5355
|
return this.buildOptions();
|
|
5297
5356
|
}
|
|
@@ -5557,6 +5616,65 @@ class BaseRepository {
|
|
|
5557
5616
|
}
|
|
5558
5617
|
};
|
|
5559
5618
|
}
|
|
5619
|
+
async keysetPaginate(options) {
|
|
5620
|
+
const order = validateKeyset(this.table, options);
|
|
5621
|
+
if (options.joins?.length || options.groupBy || options.having || "offset" in options || "limit" in options)
|
|
5622
|
+
throw new TypeError("Keyset pagination does not accept joined/grouped/offset/limited queries.");
|
|
5623
|
+
if (options.select?.some((item) => item.as?.toLowerCase().startsWith(KEYSET_ALIAS_PREFIX)))
|
|
5624
|
+
throw new TypeError("Projection conflicts with reserved keyset aliases.");
|
|
5625
|
+
const filters = [...options.whereNodes ?? []];
|
|
5626
|
+
if (options.where)
|
|
5627
|
+
filters.push({ kind: "and", where: options.where });
|
|
5628
|
+
const rows = await this.findAll({
|
|
5629
|
+
...options,
|
|
5630
|
+
where: {},
|
|
5631
|
+
whereNodes: [
|
|
5632
|
+
{ kind: "and", group: filters },
|
|
5633
|
+
...keysetBoundary(order, options.cursor)
|
|
5634
|
+
],
|
|
5635
|
+
orderBy: order,
|
|
5636
|
+
limit: options.perPage + 1,
|
|
5637
|
+
select: [
|
|
5638
|
+
...options.select?.length ? options.select : this.table.columns.map((column) => ({
|
|
5639
|
+
kind: "column",
|
|
5640
|
+
table: this.table.name,
|
|
5641
|
+
column
|
|
5642
|
+
})),
|
|
5643
|
+
...order.map((item, i) => ({
|
|
5644
|
+
kind: "textColumn",
|
|
5645
|
+
table: this.table.name,
|
|
5646
|
+
column: item.column,
|
|
5647
|
+
as: `${KEYSET_ALIAS_PREFIX}${i}`
|
|
5648
|
+
}))
|
|
5649
|
+
]
|
|
5650
|
+
});
|
|
5651
|
+
const entries = rows.map((row) => {
|
|
5652
|
+
const record = { ...row };
|
|
5653
|
+
const values = order.map((_, i) => {
|
|
5654
|
+
const key = `${KEYSET_ALIAS_PREFIX}${i}`;
|
|
5655
|
+
const value = record[key];
|
|
5656
|
+
delete record[key];
|
|
5657
|
+
if (typeof value !== "string" || value.length > 4096)
|
|
5658
|
+
throw new TypeError("Keyset columns must contain non-null bounded scalar values.");
|
|
5659
|
+
return value;
|
|
5660
|
+
});
|
|
5661
|
+
return { record, values };
|
|
5662
|
+
});
|
|
5663
|
+
const hasMore = entries.length > options.perPage;
|
|
5664
|
+
const visible = entries.slice(0, options.perPage);
|
|
5665
|
+
const last = visible.at(-1);
|
|
5666
|
+
const nextCursor = hasMore && last ? { version: 1, order, values: last.values } : null;
|
|
5667
|
+
const data = visible.map(({ record }) => record);
|
|
5668
|
+
return {
|
|
5669
|
+
data,
|
|
5670
|
+
meta: {
|
|
5671
|
+
per_page: options.perPage,
|
|
5672
|
+
has_more: hasMore,
|
|
5673
|
+
next_cursor: nextCursor,
|
|
5674
|
+
prev_cursor: options.cursor ?? null
|
|
5675
|
+
}
|
|
5676
|
+
};
|
|
5677
|
+
}
|
|
5560
5678
|
async findById(id) {
|
|
5561
5679
|
return await this.firstOrNull({
|
|
5562
5680
|
[this.table.primaryKey]: id
|
|
@@ -7244,8 +7362,13 @@ function applyTimestampsOnUpdate(columns, values, enabled) {
|
|
|
7244
7362
|
|
|
7245
7363
|
class ModelProjection {
|
|
7246
7364
|
load;
|
|
7247
|
-
|
|
7365
|
+
page;
|
|
7366
|
+
constructor(load, page) {
|
|
7248
7367
|
this.load = load;
|
|
7368
|
+
this.page = page;
|
|
7369
|
+
}
|
|
7370
|
+
keysetPaginate(options) {
|
|
7371
|
+
return this.page(options);
|
|
7249
7372
|
}
|
|
7250
7373
|
get() {
|
|
7251
7374
|
return this.load();
|
|
@@ -7295,6 +7418,12 @@ class ModelQuery {
|
|
|
7295
7418
|
return new ModelProjection(async (limit) => {
|
|
7296
7419
|
const rows = await this.query.project(columns, limit);
|
|
7297
7420
|
return rows.map((row) => applyCasts(row, modelStatics(this.modelClass).$casts, "hydrate"));
|
|
7421
|
+
}, async (options) => {
|
|
7422
|
+
const page = await this.query.projectKeyset(columns, options);
|
|
7423
|
+
return {
|
|
7424
|
+
...page,
|
|
7425
|
+
data: page.data.map((row) => applyCasts(row, modelStatics(this.modelClass).$casts, "hydrate"))
|
|
7426
|
+
};
|
|
7298
7427
|
});
|
|
7299
7428
|
}
|
|
7300
7429
|
lockForUpdate(options = {}) {
|
|
@@ -7412,6 +7541,10 @@ class ModelQuery {
|
|
|
7412
7541
|
async get() {
|
|
7413
7542
|
return await this.hydrateRows(await this.query.get());
|
|
7414
7543
|
}
|
|
7544
|
+
async keysetPaginate(options) {
|
|
7545
|
+
const page = await this.query.keysetPaginate(options);
|
|
7546
|
+
return { ...page, data: await this.hydrateRows(page.data) };
|
|
7547
|
+
}
|
|
7415
7548
|
async hydrateRows(rows) {
|
|
7416
7549
|
const statics = modelStatics(this.modelClass);
|
|
7417
7550
|
const models = [];
|
|
@@ -7695,6 +7828,9 @@ class Model {
|
|
|
7695
7828
|
return await callback(rows.map((row) => statics.newFromRecord(row, true)));
|
|
7696
7829
|
});
|
|
7697
7830
|
}
|
|
7831
|
+
static keysetPaginate(options) {
|
|
7832
|
+
return Model.query.call(this).keysetPaginate(options);
|
|
7833
|
+
}
|
|
7698
7834
|
static async cursorPaginate(options) {
|
|
7699
7835
|
const statics = modelStatics(this);
|
|
7700
7836
|
ensureBooted(this);
|