kysely-hydrate 0.8.0 → 0.10.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 +141 -1
- package/dist/index.d.mts +92 -9
- package/dist/index.mjs +97 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ compromising the power or control of SQL. It offers these features:
|
|
|
36
36
|
- [Application-level joins](#application-level-joins-with-attach)
|
|
37
37
|
- [Mapped fields](#mapped-properties-with-mapfields) in hydrated queries
|
|
38
38
|
- [Computed properties](#computed-properties-with-extras) in hydrated queries
|
|
39
|
-
- [Hydrated writes](#hydrated-writes) (INSERT/UPDATE/DELETE with RETURNING)
|
|
39
|
+
- [Hydrated writes](#hydrated-writes) (INSERT/UPDATE/DELETE with RETURNING, including multi-write CTE orchestration)
|
|
40
40
|
- [Counts, ordering, and limits](#pagination-and-aggregation) accounting for row explosion from nested joins
|
|
41
41
|
|
|
42
42
|
For example:
|
|
@@ -130,6 +130,7 @@ type Result = Array<{
|
|
|
130
130
|
- [Sorting with `.orderBy()`](#sorting-with-orderby)
|
|
131
131
|
- [Pagination and aggregation](#pagination-and-aggregation)
|
|
132
132
|
- [Inspect the SQL](#inspecting-the-sql)
|
|
133
|
+
- [Hydrating pre-fetched rows with `.hydrate()`](#hydrating-pre-fetched-rows-with-hydrate)
|
|
133
134
|
- [Mapped properties with `.mapFields()`](#mapped-properties-with-mapfields)
|
|
134
135
|
- [Computed properties with `.extras()`](#computed-properties-with-extras)
|
|
135
136
|
- [Computed properties with `.extend()`](#computed-properties-with-extend)
|
|
@@ -137,6 +138,9 @@ type Result = Array<{
|
|
|
137
138
|
- [Output transformations with `.map()`](#output-transformations-with-map)
|
|
138
139
|
- [Composable mappings with `.with()`](#composable-mappings-with-with)
|
|
139
140
|
- [Hydrated writes](#hydrated-writes)
|
|
141
|
+
- [Initializing with writes](#initializing-with-writes-querysetas)
|
|
142
|
+
- [Reusing query sets for writes](#reusing-query-sets-for-writes)
|
|
143
|
+
- [Multi-write orchestration with `.writeAs()` and `.write()`](#multi-write-orchestration-with-writeas-and-write)
|
|
140
144
|
- [Type helpers](#type-helpers)
|
|
141
145
|
- [Hydrators](#hydrators)
|
|
142
146
|
- [Creating hydrators with `createHydrator()`](#creating-hydrators-with-createhydrator)
|
|
@@ -799,6 +803,53 @@ You can inspect the generated SQL using `.toQuery()`, `.toJoinedQuery()`, or `.t
|
|
|
799
803
|
- `toJoinedQuery()`: Returns the query with all joins applied (subject to row explosion).
|
|
800
804
|
- `toBaseQuery()`: Returns the base query without any joins (but with modifications).
|
|
801
805
|
|
|
806
|
+
### Hydrating pre-fetched rows with `.hydrate()`
|
|
807
|
+
|
|
808
|
+
Sometimes you already have the flat rows—from a separate query, a cache, a
|
|
809
|
+
transaction, or from calling `.toQuery().execute()` directly—and want to hydrate
|
|
810
|
+
them without re-executing the query.
|
|
811
|
+
|
|
812
|
+
The `.hydrate()` method applies the same hydration logic that `.execute()` uses,
|
|
813
|
+
including nested joins, `mapFields`, `extras`, `omit`, and `map`.
|
|
814
|
+
|
|
815
|
+
```ts
|
|
816
|
+
const qs = querySet(db)
|
|
817
|
+
.selectAs("user", db.selectFrom("users").select(["id", "username"]))
|
|
818
|
+
.leftJoinMany(
|
|
819
|
+
"posts",
|
|
820
|
+
({ eb, qs }) => qs(eb.selectFrom("posts").select(["id", "title", "userId"])),
|
|
821
|
+
"posts.userId",
|
|
822
|
+
"user.id",
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
// Fetch the flat rows yourself
|
|
826
|
+
const rows = await qs.toQuery().execute();
|
|
827
|
+
|
|
828
|
+
// Hydrate them
|
|
829
|
+
const users = await qs.hydrate(rows);
|
|
830
|
+
// ⬇
|
|
831
|
+
type Result = Array<{
|
|
832
|
+
id: number;
|
|
833
|
+
username: string;
|
|
834
|
+
posts: Array<{ id: number; title: string; userId: number }>;
|
|
835
|
+
}>;
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
It also accepts a single row and returns a single hydrated result:
|
|
839
|
+
|
|
840
|
+
```ts
|
|
841
|
+
const [row] = await qs.toQuery().execute();
|
|
842
|
+
const user = await qs.hydrate(row);
|
|
843
|
+
// ⬇ { id: number; username: string; posts: Array<...> }
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
For convenience, `.hydrate()` accepts a `Promise` as input, so you can skip
|
|
847
|
+
the intermediate `await`:
|
|
848
|
+
|
|
849
|
+
```ts
|
|
850
|
+
const users = await qs.hydrate(qs.toQuery().execute());
|
|
851
|
+
```
|
|
852
|
+
|
|
802
853
|
### Mapped properties with `.mapFields()`
|
|
803
854
|
|
|
804
855
|
Transform individual fields in the result set. This changes the output type for
|
|
@@ -1121,6 +1172,95 @@ type Result = {
|
|
|
1121
1172
|
};
|
|
1122
1173
|
```
|
|
1123
1174
|
|
|
1175
|
+
#### Multi-write orchestration with `.writeAs()` and `.write()`
|
|
1176
|
+
|
|
1177
|
+
Sometimes a single `INSERT`, `UPDATE`, or `DELETE` isn't enough and you need to
|
|
1178
|
+
orchestrate multiple writes in one statement. For example, updating a user _and_
|
|
1179
|
+
inserting an audit log entry atomically.
|
|
1180
|
+
|
|
1181
|
+
In Postgres, this is done with data-modifying CTEs: a `SELECT` whose `WITH`
|
|
1182
|
+
clause contains `INSERT`, `UPDATE`, or `DELETE` statements. Kysely supports this
|
|
1183
|
+
natively with `.with()`.
|
|
1184
|
+
|
|
1185
|
+
`writeAs()` initializes a query set from such a query. Any CTEs on the provided
|
|
1186
|
+
`SELECT` are hoisted to the top level of the generated SQL—which is where
|
|
1187
|
+
Postgres requires data-modifying CTEs to live.
|
|
1188
|
+
|
|
1189
|
+
```ts
|
|
1190
|
+
const result = await querySet(db)
|
|
1191
|
+
.writeAs("updated", (db) =>
|
|
1192
|
+
db
|
|
1193
|
+
// Data-modifying CTE: update the user
|
|
1194
|
+
.with("updated", (qb) =>
|
|
1195
|
+
qb
|
|
1196
|
+
.updateTable("users")
|
|
1197
|
+
.set({ email: "new@example.com" })
|
|
1198
|
+
.where("id", "=", userId)
|
|
1199
|
+
.returningAll(),
|
|
1200
|
+
)
|
|
1201
|
+
// Data-modifying CTE: insert an audit log entry
|
|
1202
|
+
.with("audit", (qb) =>
|
|
1203
|
+
qb.insertInto("audit_log").values({ userId, action: "email_changed" }).returning(["id"]),
|
|
1204
|
+
)
|
|
1205
|
+
// Select from the update result
|
|
1206
|
+
.selectFrom("updated")
|
|
1207
|
+
.select(["id", "username", "email"]),
|
|
1208
|
+
)
|
|
1209
|
+
.executeTakeFirstOrThrow();
|
|
1210
|
+
// ⬇
|
|
1211
|
+
type Result = { id: number; username: string; email: string };
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
**Generated SQL:**
|
|
1215
|
+
|
|
1216
|
+
```sql
|
|
1217
|
+
WITH "updated" AS (
|
|
1218
|
+
UPDATE "users" SET "email" = $1 WHERE "id" = $2 RETURNING *
|
|
1219
|
+
),
|
|
1220
|
+
"audit" AS (
|
|
1221
|
+
INSERT INTO "audit_log" ("userId", "action") VALUES ($3, $4) RETURNING "id"
|
|
1222
|
+
)
|
|
1223
|
+
SELECT "updated"."id", "updated"."username", "updated"."email"
|
|
1224
|
+
FROM (SELECT "id", "username", "email" FROM "updated") AS "updated"
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
Like `insertAs` and friends, `writeAs()` supports joins, extras, and all the
|
|
1228
|
+
usual query set features.
|
|
1229
|
+
|
|
1230
|
+
`.write()` is the instance-method equivalent—it switches the base query of an
|
|
1231
|
+
existing query set, just like `.insert()` does for inserts:
|
|
1232
|
+
|
|
1233
|
+
```ts
|
|
1234
|
+
const usersQuerySet = querySet(db)
|
|
1235
|
+
.selectAs("user", db.selectFrom("users").select(["id", "username", "email"]))
|
|
1236
|
+
.leftJoinMany("posts" /* ... */)
|
|
1237
|
+
.extras({ gravatarUrl: (u) => getGravatar(u.email) });
|
|
1238
|
+
|
|
1239
|
+
// Reuse the canonical query set for a select query with data-modifying CTE.
|
|
1240
|
+
const result = await usersQuerySet
|
|
1241
|
+
.write((db) =>
|
|
1242
|
+
db
|
|
1243
|
+
.with("updated", (qb) =>
|
|
1244
|
+
qb
|
|
1245
|
+
.updateTable("users")
|
|
1246
|
+
.set({ email: "new@example.com" })
|
|
1247
|
+
.where("id", "=", userId)
|
|
1248
|
+
.returningAll(),
|
|
1249
|
+
)
|
|
1250
|
+
.selectFrom("updated")
|
|
1251
|
+
.select(["id", "username", "email"]),
|
|
1252
|
+
)
|
|
1253
|
+
.executeTakeFirstOrThrow();
|
|
1254
|
+
// ⬇ Result includes posts and gravatarUrl!
|
|
1255
|
+
type Result = {
|
|
1256
|
+
id: number;
|
|
1257
|
+
username: string;
|
|
1258
|
+
email: string;
|
|
1259
|
+
gravatarUrl: string;
|
|
1260
|
+
posts: Array<{ id: number; title: string; user_id: number }>;
|
|
1261
|
+
};
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1124
1264
|
### Type Helpers
|
|
1125
1265
|
|
|
1126
1266
|
Kysely Hydrate provides type helpers that mirror
|
package/dist/index.d.mts
CHANGED
|
@@ -613,7 +613,7 @@ interface TSelectQuerySet extends TQuerySet {
|
|
|
613
613
|
BaseQuery: TSelectQuery;
|
|
614
614
|
}
|
|
615
615
|
type QuerySetFor<T extends TQuerySet> = T["IsMapped"] extends true ? MappedQuerySet<T> : QuerySet<T>;
|
|
616
|
-
type
|
|
616
|
+
type THydrationInput<T extends TQuerySet> = T["JoinedQuery"]["O"];
|
|
617
617
|
type TOutput<T extends TQuerySet> = T["HydratedOutput"];
|
|
618
618
|
interface TMapped<in out T extends TQuerySet, in out Output> {
|
|
619
619
|
DB: T["DB"];
|
|
@@ -868,6 +868,37 @@ interface MappedQuerySet<in out T extends TQuerySet> extends k.Compilable, k.Ope
|
|
|
868
868
|
* ```
|
|
869
869
|
*/
|
|
870
870
|
toExistsQuery(): OpaqueExistsQueryBuilder;
|
|
871
|
+
/**
|
|
872
|
+
* Hydrates pre-fetched raw joined rows into nested output objects without
|
|
873
|
+
* executing a query.
|
|
874
|
+
*
|
|
875
|
+
* This is useful when you already have the flat SQL result (e.g. from a
|
|
876
|
+
* separate query, a cache, or a transaction) and want to apply the same
|
|
877
|
+
* hydration logic that `.execute()` uses.
|
|
878
|
+
*
|
|
879
|
+
* **Example - single row:**
|
|
880
|
+
* ```ts
|
|
881
|
+
* const qs = querySet(db)
|
|
882
|
+
* .selectAs("user", db.selectFrom("users").select(["id", "username"]))
|
|
883
|
+
* .leftJoinMany("posts", ...);
|
|
884
|
+
*
|
|
885
|
+
* const row = await qs.toQuery().executeTakeFirstOrThrow();
|
|
886
|
+
* const user = await qs.hydrate(row);
|
|
887
|
+
* ```
|
|
888
|
+
*
|
|
889
|
+
* **Example - multiple rows:**
|
|
890
|
+
* ```ts
|
|
891
|
+
* const rows = await qs.toQuery().execute();
|
|
892
|
+
* const users = await qs.hydrate(rows);
|
|
893
|
+
* ```
|
|
894
|
+
*
|
|
895
|
+
* @param input - A single flat row or an iterable of flat rows matching the
|
|
896
|
+
* joined query output shape.
|
|
897
|
+
* @returns The hydrated output(s).
|
|
898
|
+
*/
|
|
899
|
+
hydrate(input: THydrationInput<T> | Promise<THydrationInput<T>>): Promise<TOutput<T>>;
|
|
900
|
+
hydrate(input: Iterable<THydrationInput<T>> | Promise<Iterable<THydrationInput<T>>>): Promise<TOutput<T>[]>;
|
|
901
|
+
hydrate(input: THydrationInput<T> | Iterable<THydrationInput<T>> | Promise<THydrationInput<T>> | Promise<Iterable<THydrationInput<T>>>): Promise<TOutput<T> | TOutput<T>[]>;
|
|
871
902
|
/**
|
|
872
903
|
* Executes the query and returns an array of hydrated rows.
|
|
873
904
|
*
|
|
@@ -1335,6 +1366,25 @@ interface MappedQuerySet<in out T extends TQuerySet> extends k.Compilable, k.Ope
|
|
|
1335
1366
|
* Like {@link insert}, but switches to a `DELETE` statement.
|
|
1336
1367
|
*/
|
|
1337
1368
|
delete<IQB extends k.DeleteQueryBuilder<any, any, T["BaseQuery"]["O"]>>(dqb: DeleteQueryBuilderOrFactory<T["DB"], IQB>): MaybeMappedQuerySet<TWithBaseQuery<T, InferTDeleteQuery<IQB>>>;
|
|
1369
|
+
/**
|
|
1370
|
+
* Switches the base query to a `SELECT` that may contain data-modifying CTEs.
|
|
1371
|
+
*
|
|
1372
|
+
* Any CTEs on the provided query will be hoisted to the top level of the
|
|
1373
|
+
* generated SQL, which is required by Postgres for data-modifying CTEs.
|
|
1374
|
+
* The stripped `SELECT` is inlined as a derived table, just like
|
|
1375
|
+
* {@link selectAs}.
|
|
1376
|
+
*
|
|
1377
|
+
* This enables multi-write CTE orchestration patterns like:
|
|
1378
|
+
* ```sql
|
|
1379
|
+
* WITH "updated" AS (UPDATE ... RETURNING *),
|
|
1380
|
+
* "audit" AS (INSERT INTO audit_log ... RETURNING *)
|
|
1381
|
+
* SELECT * FROM "updated"
|
|
1382
|
+
* ```
|
|
1383
|
+
*
|
|
1384
|
+
* @param sqb - A select query builder (possibly with CTEs) or factory function.
|
|
1385
|
+
* @returns A new QuerySet with the write query as the base.
|
|
1386
|
+
*/
|
|
1387
|
+
write<SQB extends k.SelectQueryBuilder<any, any, T["BaseQuery"]["O"]>>(sqb: WriteQueryBuilderOrFactory<T["DB"], SQB>): MaybeMappedQuerySet<TWithBaseQuery<T, InferTSelectQuery<SQB>>>;
|
|
1338
1388
|
}
|
|
1339
1389
|
/**
|
|
1340
1390
|
* A query set that supports nested joins and automatic hydration.
|
|
@@ -1378,7 +1428,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
|
|
|
1378
1428
|
* the field value from the entire row.
|
|
1379
1429
|
* @returns A new HydratedQueryBuilder with the extras applied.
|
|
1380
1430
|
*/
|
|
1381
|
-
extras<E extends Extras<
|
|
1431
|
+
extras<E extends Extras<THydrationInput<T>>>(extras: E): QuerySet<TWithExtendedOutput<T, InferExtras<THydrationInput<T>, E>>>;
|
|
1382
1432
|
/**
|
|
1383
1433
|
* Adds computed fields to the hydrated output by spreading the return value
|
|
1384
1434
|
* of a function. Unlike `.extras()` which defines one field at a time,
|
|
@@ -1409,7 +1459,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
|
|
|
1409
1459
|
* computed properties.
|
|
1410
1460
|
* @returns A new HydratedQueryBuilder with the extender applied.
|
|
1411
1461
|
*/
|
|
1412
|
-
extend<F extends Extender<
|
|
1462
|
+
extend<F extends Extender<THydrationInput<T>>>(fn: F): QuerySet<TWithExtendedOutput<T, InferExtender<THydrationInput<T>, F>>>;
|
|
1413
1463
|
/**
|
|
1414
1464
|
* Transforms already-selected field values in the hydrated output. Fields
|
|
1415
1465
|
* not mentioned in the mappings will still be included as-is.
|
|
@@ -1431,7 +1481,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
|
|
|
1431
1481
|
* functions.
|
|
1432
1482
|
* @returns A new HydratedQueryBuilder with the field transformations applied.
|
|
1433
1483
|
*/
|
|
1434
|
-
mapFields<M extends FieldMappings<
|
|
1484
|
+
mapFields<M extends FieldMappings<THydrationInput<T>>>(mappings: M): QuerySet<TWithExtendedOutput<T, InferFields<THydrationInput<T>, M>>>;
|
|
1435
1485
|
/**
|
|
1436
1486
|
* Omits specified fields from the hydrated output. Useful for excluding
|
|
1437
1487
|
* fields that were selected for internal use (e.g., for extras).
|
|
@@ -1453,7 +1503,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
|
|
|
1453
1503
|
* @param keys - Field names to omit from the output.
|
|
1454
1504
|
* @returns A new HydratedQueryBuilder with the fields omitted.
|
|
1455
1505
|
*/
|
|
1456
|
-
omit<K$1 extends keyof
|
|
1506
|
+
omit<K$1 extends keyof THydrationInput<T>>(keys: readonly K$1[]): QuerySet<TWithOmit<T, K$1>>;
|
|
1457
1507
|
/**
|
|
1458
1508
|
* Extends this query builder's hydration configuration with another Hydrator.
|
|
1459
1509
|
* The other Hydrator's configuration takes precedence in case of conflicts.
|
|
@@ -1485,8 +1535,8 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
|
|
|
1485
1535
|
* @param hydrator - The Hydrator to extend with.
|
|
1486
1536
|
* @returns A new HydratedQueryBuilder with merged hydration configuration.
|
|
1487
1537
|
*/
|
|
1488
|
-
with<OtherInput extends StrictSubset<
|
|
1489
|
-
with<OtherInput extends StrictSubset<
|
|
1538
|
+
with<OtherInput extends StrictSubset<THydrationInput<T>, OtherInput>, OtherOutput>(hydrator: FullHydrator<OtherInput, OtherOutput>): QuerySet<TWithExtendedOutput<T, OtherOutput>>;
|
|
1539
|
+
with<OtherInput extends StrictSubset<THydrationInput<T>, OtherInput>, OtherOutput>(hydrator: MappedHydrator<OtherInput, OtherOutput>): QuerySet<TWithExtendedOutput<T, OtherOutput>>;
|
|
1490
1540
|
/**
|
|
1491
1541
|
* Attaches data from an external source (not via SQL joins) as a nested
|
|
1492
1542
|
* array. The `fetchFn` is called exactly once per query execution with all
|
|
@@ -2334,8 +2384,8 @@ interface ModifyCollectionReturnMap<T extends TQuerySet, Key extends string, TNe
|
|
|
2334
2384
|
AttachOneOrThrow: QuerySetWithAttach<T, Key, "AttachOneOrThrow", NewValue>;
|
|
2335
2385
|
AttachMany: QuerySetWithAttach<T, Key, "AttachMany", NewValue>;
|
|
2336
2386
|
}
|
|
2337
|
-
type ToFetchFn<T extends TQuerySet, FetchFnReturn extends SomeFetchFnReturn> = SomeFetchFn<
|
|
2338
|
-
type ToAttachedKeysArg<T extends TQuerySet, FetchFnReturn extends SomeFetchFnReturn> = AttachedKeysArg<
|
|
2387
|
+
type ToFetchFn<T extends TQuerySet, FetchFnReturn extends SomeFetchFnReturn> = SomeFetchFn<THydrationInput<T>, FetchFnReturn>;
|
|
2388
|
+
type ToAttachedKeysArg<T extends TQuerySet, FetchFnReturn extends SomeFetchFnReturn> = AttachedKeysArg<THydrationInput<T>, AttachedOutputFromFetchFnReturn<NoInfer<FetchFnReturn>>>;
|
|
2339
2389
|
interface AttachedOutputMap<in out FetchFnReturn extends SomeFetchFnReturn> {
|
|
2340
2390
|
AttachOne: AttachedOutputFromFetchFnReturn<FetchFnReturn> | null;
|
|
2341
2391
|
AttachOneOrThrow: AttachedOutputFromFetchFnReturn<FetchFnReturn>;
|
|
@@ -2434,6 +2484,8 @@ type UpdateQueryBuilderFactory<InitialDB, UQB extends AnyUpdateQueryBuilder> = (
|
|
|
2434
2484
|
type UpdateQueryBuilderOrFactory<InitialDB, UQB extends AnyUpdateQueryBuilder> = UQB | UpdateQueryBuilderFactory<InitialDB, UQB>;
|
|
2435
2485
|
type DeleteQueryBuilderFactory<InitialDB, DQB extends AnyDeleteQueryBuilder> = (db: DeleteCreator<InitialDB>) => DQB;
|
|
2436
2486
|
type DeleteQueryBuilderOrFactory<InitialDB, DQB extends AnyDeleteQueryBuilder> = DQB | DeleteQueryBuilderFactory<InitialDB, DQB>;
|
|
2487
|
+
type WriteQueryBuilderFactory<InitialDB, SQB extends AnySelectQueryBuilder> = (db: k.Kysely<InitialDB>) => SQB;
|
|
2488
|
+
type WriteQueryBuilderOrFactory<InitialDB, SQB extends AnySelectQueryBuilder> = SQB | WriteQueryBuilderFactory<InitialDB, SQB>;
|
|
2437
2489
|
interface NestedQuerySetFn<in out DB$1, in out Alias extends string> {
|
|
2438
2490
|
<SQB extends k.SelectQueryBuilder<any, any, InputWithDefaultKey>>(query: SQB): InitialQuerySet<DB$1, Alias, InferTSelectQuery<SQB>>;
|
|
2439
2491
|
<SQB extends AnySelectQueryBuilder>(query: SQB, keyBy: KeyBy<InferO<NoInfer<SQB>>>): InitialQuerySet<DB$1, Alias, InferTSelectQuery<SQB>>;
|
|
@@ -2542,6 +2594,37 @@ declare class QuerySetCreator<in out DB$1> {
|
|
|
2542
2594
|
*/
|
|
2543
2595
|
deleteAs<Alias extends string, DQB extends k.DeleteQueryBuilder<any, any, InputWithDefaultKey>>(alias: Alias, query: DeleteQueryBuilderOrFactory<DB$1, DQB>): InitialQuerySet<DB$1, Alias, InferTDeleteQuery<DQB>>;
|
|
2544
2596
|
deleteAs<Alias extends string, UQB extends AnyDeleteQueryBuilder>(alias: Alias, query: DeleteQueryBuilderOrFactory<DB$1, UQB>, keyBy: KeyBy<InferO<NoInfer<UQB>>>): InitialQuerySet<DB$1, Alias, InferTDeleteQuery<UQB>>;
|
|
2597
|
+
/**
|
|
2598
|
+
* Initializes a new query set with a base `SELECT` query that may contain
|
|
2599
|
+
* data-modifying CTEs.
|
|
2600
|
+
*
|
|
2601
|
+
* Any CTEs on the provided query will be hoisted to the top level of the
|
|
2602
|
+
* generated SQL, which is required by Postgres for data-modifying CTEs.
|
|
2603
|
+
*
|
|
2604
|
+
* This enables multi-write CTE orchestration patterns like:
|
|
2605
|
+
* ```ts
|
|
2606
|
+
* const result = await querySet(db)
|
|
2607
|
+
* .writeAs("updated", (db) =>
|
|
2608
|
+
* db
|
|
2609
|
+
* .with("updated", (qb) =>
|
|
2610
|
+
* qb.updateTable("users")
|
|
2611
|
+
* .set({ email: "new@example.com" })
|
|
2612
|
+
* .where("id", "=", 1)
|
|
2613
|
+
* .returningAll()
|
|
2614
|
+
* )
|
|
2615
|
+
* .selectFrom("updated")
|
|
2616
|
+
* .selectAll()
|
|
2617
|
+
* )
|
|
2618
|
+
* .executeTakeFirst();
|
|
2619
|
+
* ```
|
|
2620
|
+
*
|
|
2621
|
+
* @param alias - The alias for the base query.
|
|
2622
|
+
* @param query - A Kysely select query builder (possibly with CTEs) or factory function.
|
|
2623
|
+
* @param keyBy - The key(s) to uniquely identify rows. Defaults to `"id"`.
|
|
2624
|
+
* @returns A new QuerySet.
|
|
2625
|
+
*/
|
|
2626
|
+
writeAs<Alias extends string, SQB extends k.SelectQueryBuilder<any, any, InputWithDefaultKey>>(alias: Alias, query: WriteQueryBuilderOrFactory<DB$1, SQB>): InitialQuerySet<DB$1, Alias, InferTSelectQuery<SQB>>;
|
|
2627
|
+
writeAs<Alias extends string, SQB extends AnySelectQueryBuilder>(alias: Alias, query: WriteQueryBuilderOrFactory<DB$1, SQB>, keyBy: KeyBy<InferO<NoInfer<SQB>>>): InitialQuerySet<DB$1, Alias, InferTSelectQuery<SQB>>;
|
|
2545
2628
|
}
|
|
2546
2629
|
/**
|
|
2547
2630
|
* Creates a new {@link QuerySetCreator} for building query sets with nested joins
|
package/dist/index.mjs
CHANGED
|
@@ -609,6 +609,56 @@ function groupByKey(prefix, inputs, keyBy) {
|
|
|
609
609
|
return map;
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
+
//#endregion
|
|
613
|
+
//#region src/helpers/cte-hoisting.ts
|
|
614
|
+
/**
|
|
615
|
+
* Extracts the CTE expressions from a query builder's operation node.
|
|
616
|
+
*/
|
|
617
|
+
function extractCTEs(qb) {
|
|
618
|
+
return qb.toOperationNode().with?.expressions;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* A Kysely plugin that strips the WITH clause from a SelectQueryNode,
|
|
622
|
+
* effectively removing all CTEs from the query.
|
|
623
|
+
*/
|
|
624
|
+
var StripWithPlugin = class {
|
|
625
|
+
transformQuery(args) {
|
|
626
|
+
const node = args.node;
|
|
627
|
+
if (node.kind === "SelectQueryNode" && node.with) return {
|
|
628
|
+
...node,
|
|
629
|
+
with: void 0
|
|
630
|
+
};
|
|
631
|
+
return node;
|
|
632
|
+
}
|
|
633
|
+
async transformResult(args) {
|
|
634
|
+
return args.result;
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
/**
|
|
638
|
+
* A Kysely plugin that prepends CTE expressions to the outer query's WithNode.
|
|
639
|
+
*/
|
|
640
|
+
var AddCTEsPlugin = class {
|
|
641
|
+
#expressions;
|
|
642
|
+
constructor(expressions) {
|
|
643
|
+
this.#expressions = expressions;
|
|
644
|
+
}
|
|
645
|
+
transformQuery(args) {
|
|
646
|
+
const node = args.node;
|
|
647
|
+
if (node.kind !== "SelectQueryNode") return node;
|
|
648
|
+
const existing = node.with?.expressions ?? [];
|
|
649
|
+
const merged = [...this.#expressions, ...existing];
|
|
650
|
+
let withNode = k.WithNode.create(merged[0]);
|
|
651
|
+
for (let i = 1; i < merged.length; i++) withNode = k.WithNode.cloneWithExpression(withNode, merged[i]);
|
|
652
|
+
return {
|
|
653
|
+
...node,
|
|
654
|
+
with: withNode
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
async transformResult(args) {
|
|
658
|
+
return args.result;
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
|
|
612
662
|
//#endregion
|
|
613
663
|
//#region src/helpers/select-renamer.ts
|
|
614
664
|
const fakeQb = k.createSelectQueryBuilder({
|
|
@@ -733,8 +783,20 @@ var QuerySetImpl = class QuerySetImpl {
|
|
|
733
783
|
return this.#props.baseQuery;
|
|
734
784
|
}
|
|
735
785
|
#getSelectFromBase(isNested, isLocalSubquery) {
|
|
736
|
-
const { db, baseQuery, baseAlias } = this.#props;
|
|
737
|
-
if (isSelectQueryBuilder(baseQuery))
|
|
786
|
+
const { db, baseQuery, baseAlias, hoistCTEs } = this.#props;
|
|
787
|
+
if (isSelectQueryBuilder(baseQuery)) {
|
|
788
|
+
if (hoistCTEs) {
|
|
789
|
+
if (isNested) throw new InvalidJoinedQuerySetError(baseAlias);
|
|
790
|
+
const ctes = extractCTEs(baseQuery);
|
|
791
|
+
if (ctes?.length) {
|
|
792
|
+
const stripped = baseQuery.withPlugin(new StripWithPlugin());
|
|
793
|
+
let qb$1 = db.selectFrom(stripped.as(baseAlias));
|
|
794
|
+
qb$1 = applyHoistedSelections(qb$1, baseQuery, baseAlias);
|
|
795
|
+
return qb$1.withPlugin(new AddCTEsPlugin(ctes));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return applyHoistedSelections(db.selectFrom(baseQuery.as(baseAlias)), baseQuery, baseAlias);
|
|
799
|
+
}
|
|
738
800
|
if (isNested) throw new InvalidJoinedQuerySetError(baseAlias);
|
|
739
801
|
let qb = (isLocalSubquery ? db : db.with("__base", () => baseQuery)).selectFrom(`__base as ${baseAlias}`);
|
|
740
802
|
if (!isLocalSubquery) return qb.selectAll(baseAlias);
|
|
@@ -856,13 +918,16 @@ var QuerySetImpl = class QuerySetImpl {
|
|
|
856
918
|
toOperationNode() {
|
|
857
919
|
return this.toQuery().toOperationNode();
|
|
858
920
|
}
|
|
859
|
-
async
|
|
860
|
-
|
|
861
|
-
return this.#props.hydrator.hydrate(rows, {
|
|
921
|
+
async hydrate(input) {
|
|
922
|
+
return this.#props.hydrator.hydrate(await input, {
|
|
862
923
|
[EnableAutoInclusion]: true,
|
|
863
924
|
sort: "nested"
|
|
864
925
|
});
|
|
865
926
|
}
|
|
927
|
+
async execute() {
|
|
928
|
+
const rows = await this.toQuery().execute();
|
|
929
|
+
return this.hydrate(rows);
|
|
930
|
+
}
|
|
866
931
|
async executeTakeFirst() {
|
|
867
932
|
const [result] = await this.execute();
|
|
868
933
|
return result;
|
|
@@ -1049,6 +1114,12 @@ var QuerySetImpl = class QuerySetImpl {
|
|
|
1049
1114
|
delete(iqb) {
|
|
1050
1115
|
return this.#asWrite(iqb);
|
|
1051
1116
|
}
|
|
1117
|
+
write(sqb) {
|
|
1118
|
+
return this.#clone({
|
|
1119
|
+
baseQuery: typeof sqb === "function" ? sqb(this.#props.db) : sqb,
|
|
1120
|
+
hoistCTEs: true
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1052
1123
|
};
|
|
1053
1124
|
/**
|
|
1054
1125
|
* Factory for creating query sets. Obtained by calling {@link querySet}.
|
|
@@ -1075,7 +1146,8 @@ var QuerySetCreator = class {
|
|
|
1075
1146
|
orderBy: [],
|
|
1076
1147
|
orderByKeys: true,
|
|
1077
1148
|
frontModifiers: [],
|
|
1078
|
-
endModifiers: []
|
|
1149
|
+
endModifiers: [],
|
|
1150
|
+
hoistCTEs: false
|
|
1079
1151
|
});
|
|
1080
1152
|
}
|
|
1081
1153
|
selectAs(alias, query, keyBy = DEFAULT_KEY_BY) {
|
|
@@ -1090,6 +1162,25 @@ var QuerySetCreator = class {
|
|
|
1090
1162
|
deleteAs(alias, query, keyBy = DEFAULT_KEY_BY) {
|
|
1091
1163
|
return this.#createQuerySet(alias, query, keyBy);
|
|
1092
1164
|
}
|
|
1165
|
+
writeAs(alias, query, keyBy = DEFAULT_KEY_BY) {
|
|
1166
|
+
const baseQuery = typeof query === "function" ? query(this.#db) : query;
|
|
1167
|
+
return new QuerySetImpl({
|
|
1168
|
+
db: this.#db,
|
|
1169
|
+
baseAlias: alias,
|
|
1170
|
+
baseQuery,
|
|
1171
|
+
keyBy,
|
|
1172
|
+
hydrator: createHydrator().orderByKeys(),
|
|
1173
|
+
joinCollections: /* @__PURE__ */ new Map(),
|
|
1174
|
+
attachCollections: /* @__PURE__ */ new Map(),
|
|
1175
|
+
limit: null,
|
|
1176
|
+
offset: null,
|
|
1177
|
+
orderBy: [],
|
|
1178
|
+
orderByKeys: true,
|
|
1179
|
+
frontModifiers: [],
|
|
1180
|
+
endModifiers: [],
|
|
1181
|
+
hoistCTEs: true
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1093
1184
|
};
|
|
1094
1185
|
/**
|
|
1095
1186
|
* Creates a new {@link QuerySetCreator} for building query sets with nested joins
|