kysely-hydrate 0.10.0 → 0.10.2
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 +28 -26
- package/dist/index.d.mts +18 -28
- package/dist/index.mjs +45 -73
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1182,29 +1182,32 @@ In Postgres, this is done with data-modifying CTEs: a `SELECT` whose `WITH`
|
|
|
1182
1182
|
clause contains `INSERT`, `UPDATE`, or `DELETE` statements. Kysely supports this
|
|
1183
1183
|
natively with `.with()`.
|
|
1184
1184
|
|
|
1185
|
-
`writeAs()`
|
|
1186
|
-
|
|
1187
|
-
|
|
1185
|
+
`writeAs()` takes two callbacks. The first builds CTEs and returns a query
|
|
1186
|
+
creator. The second builds the SELECT that references CTE names. The CTEs are
|
|
1187
|
+
placed at the top level of the generated SQL—which is where Postgres requires
|
|
1188
|
+
data-modifying CTEs to live—while the SELECT becomes a derived table with no
|
|
1189
|
+
CTEs to strip.
|
|
1188
1190
|
|
|
1189
1191
|
```ts
|
|
1190
1192
|
const result = await querySet(db)
|
|
1191
|
-
.writeAs(
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1193
|
+
.writeAs(
|
|
1194
|
+
"updated",
|
|
1195
|
+
(db) =>
|
|
1196
|
+
db
|
|
1197
|
+
// Data-modifying CTE: update the user
|
|
1198
|
+
.with("updated", (qb) =>
|
|
1199
|
+
qb
|
|
1200
|
+
.updateTable("users")
|
|
1201
|
+
.set({ email: "new@example.com" })
|
|
1202
|
+
.where("id", "=", userId)
|
|
1203
|
+
.returningAll(),
|
|
1204
|
+
)
|
|
1205
|
+
// Data-modifying CTE: insert an audit log entry
|
|
1206
|
+
.with("audit", (qb) =>
|
|
1207
|
+
qb.insertInto("audit_log").values({ userId, action: "email_changed" }).returning(["id"]),
|
|
1208
|
+
),
|
|
1209
|
+
// Select from the update result
|
|
1210
|
+
(qc) => qc.selectFrom("updated").select(["id", "username", "email"]),
|
|
1208
1211
|
)
|
|
1209
1212
|
.executeTakeFirstOrThrow();
|
|
1210
1213
|
// ⬇
|
|
@@ -1238,17 +1241,16 @@ const usersQuerySet = querySet(db)
|
|
|
1238
1241
|
|
|
1239
1242
|
// Reuse the canonical query set for a select query with data-modifying CTE.
|
|
1240
1243
|
const result = await usersQuerySet
|
|
1241
|
-
.write(
|
|
1242
|
-
db
|
|
1243
|
-
.with("updated", (qb) =>
|
|
1244
|
+
.write(
|
|
1245
|
+
(db) =>
|
|
1246
|
+
db.with("updated", (qb) =>
|
|
1244
1247
|
qb
|
|
1245
1248
|
.updateTable("users")
|
|
1246
1249
|
.set({ email: "new@example.com" })
|
|
1247
1250
|
.where("id", "=", userId)
|
|
1248
1251
|
.returningAll(),
|
|
1249
|
-
)
|
|
1250
|
-
|
|
1251
|
-
.select(["id", "username", "email"]),
|
|
1252
|
+
),
|
|
1253
|
+
(qc) => qc.selectFrom("updated").select(["id", "username", "email"]),
|
|
1252
1254
|
)
|
|
1253
1255
|
.executeTakeFirstOrThrow();
|
|
1254
1256
|
// ⬇ Result includes posts and gravatarUrl!
|
package/dist/index.d.mts
CHANGED
|
@@ -1369,22 +1369,15 @@ interface MappedQuerySet<in out T extends TQuerySet> extends k.Compilable, k.Ope
|
|
|
1369
1369
|
/**
|
|
1370
1370
|
* Switches the base query to a `SELECT` that may contain data-modifying CTEs.
|
|
1371
1371
|
*
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
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
|
-
* ```
|
|
1372
|
+
* Callback 1 receives `db`, builds CTEs, and returns a query creator.
|
|
1373
|
+
* Callback 2 receives a query creator typed with the CTE names and builds
|
|
1374
|
+
* the SELECT.
|
|
1383
1375
|
*
|
|
1384
|
-
* @param
|
|
1376
|
+
* @param cteFn - Builds CTEs; returns a query creator.
|
|
1377
|
+
* @param selectFn - Builds the SELECT referencing CTE names.
|
|
1385
1378
|
* @returns A new QuerySet with the write query as the base.
|
|
1386
1379
|
*/
|
|
1387
|
-
write<SQB extends k.SelectQueryBuilder<any, any, T["BaseQuery"]["O"]>>(
|
|
1380
|
+
write<NewDB, SQB extends k.SelectQueryBuilder<any, any, T["BaseQuery"]["O"]>>(cteFn: (db: k.Kysely<T["DB"]>) => k.QueryCreator<NewDB>, selectFn: (qc: k.QueryCreator<NewDB>) => SQB): MaybeMappedQuerySet<TWithBaseQuery<T, InferTSelectQuery<SQB>>>;
|
|
1388
1381
|
}
|
|
1389
1382
|
/**
|
|
1390
1383
|
* A query set that supports nested joins and automatic hydration.
|
|
@@ -2484,8 +2477,6 @@ type UpdateQueryBuilderFactory<InitialDB, UQB extends AnyUpdateQueryBuilder> = (
|
|
|
2484
2477
|
type UpdateQueryBuilderOrFactory<InitialDB, UQB extends AnyUpdateQueryBuilder> = UQB | UpdateQueryBuilderFactory<InitialDB, UQB>;
|
|
2485
2478
|
type DeleteQueryBuilderFactory<InitialDB, DQB extends AnyDeleteQueryBuilder> = (db: DeleteCreator<InitialDB>) => DQB;
|
|
2486
2479
|
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>;
|
|
2489
2480
|
interface NestedQuerySetFn<in out DB$1, in out Alias extends string> {
|
|
2490
2481
|
<SQB extends k.SelectQueryBuilder<any, any, InputWithDefaultKey>>(query: SQB): InitialQuerySet<DB$1, Alias, InferTSelectQuery<SQB>>;
|
|
2491
2482
|
<SQB extends AnySelectQueryBuilder>(query: SQB, keyBy: KeyBy<InferO<NoInfer<SQB>>>): InitialQuerySet<DB$1, Alias, InferTSelectQuery<SQB>>;
|
|
@@ -2604,27 +2595,26 @@ declare class QuerySetCreator<in out DB$1> {
|
|
|
2604
2595
|
* This enables multi-write CTE orchestration patterns like:
|
|
2605
2596
|
* ```ts
|
|
2606
2597
|
* const result = await querySet(db)
|
|
2607
|
-
* .writeAs("updated",
|
|
2608
|
-
* db
|
|
2609
|
-
* .
|
|
2610
|
-
*
|
|
2611
|
-
*
|
|
2612
|
-
*
|
|
2613
|
-
*
|
|
2614
|
-
*
|
|
2615
|
-
* .selectFrom("updated")
|
|
2616
|
-
* .selectAll()
|
|
2598
|
+
* .writeAs("updated",
|
|
2599
|
+
* (db) => db.with("updated", (qb) =>
|
|
2600
|
+
* qb.updateTable("users")
|
|
2601
|
+
* .set({ email: "new@example.com" })
|
|
2602
|
+
* .where("id", "=", 1)
|
|
2603
|
+
* .returningAll()
|
|
2604
|
+
* ),
|
|
2605
|
+
* (qc) => qc.selectFrom("updated").selectAll()
|
|
2617
2606
|
* )
|
|
2618
2607
|
* .executeTakeFirst();
|
|
2619
2608
|
* ```
|
|
2620
2609
|
*
|
|
2621
2610
|
* @param alias - The alias for the base query.
|
|
2622
|
-
* @param
|
|
2611
|
+
* @param cteFn - A callback that receives `db` and builds the CTEs, returning a query creator.
|
|
2612
|
+
* @param selectFn - A callback that receives the query creator and builds the SELECT referencing CTE names.
|
|
2623
2613
|
* @param keyBy - The key(s) to uniquely identify rows. Defaults to `"id"`.
|
|
2624
2614
|
* @returns A new QuerySet.
|
|
2625
2615
|
*/
|
|
2626
|
-
writeAs<Alias extends string, SQB extends k.SelectQueryBuilder<any, any, InputWithDefaultKey>>(alias: Alias,
|
|
2627
|
-
writeAs<Alias extends string, SQB extends AnySelectQueryBuilder>(alias: Alias,
|
|
2616
|
+
writeAs<Alias extends string, NewDB, SQB extends k.SelectQueryBuilder<any, any, InputWithDefaultKey>>(alias: Alias, cteFn: (db: k.Kysely<DB$1>) => k.QueryCreator<NewDB>, selectFn: (qc: k.QueryCreator<NewDB>) => SQB): InitialQuerySet<DB$1, Alias, InferTSelectQuery<SQB>>;
|
|
2617
|
+
writeAs<Alias extends string, NewDB, SQB extends AnySelectQueryBuilder>(alias: Alias, cteFn: (db: k.Kysely<DB$1>) => k.QueryCreator<NewDB>, selectFn: (qc: k.QueryCreator<NewDB>) => SQB, keyBy: KeyBy<InferO<NoInfer<SQB>>>): InitialQuerySet<DB$1, Alias, InferTSelectQuery<SQB>>;
|
|
2628
2618
|
}
|
|
2629
2619
|
/**
|
|
2630
2620
|
* Creates a new {@link QuerySetCreator} for building query sets with nested joins
|
package/dist/index.mjs
CHANGED
|
@@ -609,56 +609,6 @@ 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
|
-
|
|
662
612
|
//#endregion
|
|
663
613
|
//#region src/helpers/select-renamer.ts
|
|
664
614
|
const fakeQb = k.createSelectQueryBuilder({
|
|
@@ -741,6 +691,25 @@ function extractSelectionName(selectionNode) {
|
|
|
741
691
|
* @see {@link QuerySet} - Query builder interface
|
|
742
692
|
* @see {@link MappedQuerySet} - Mapped query builder interface
|
|
743
693
|
*/
|
|
694
|
+
/**
|
|
695
|
+
* A stateless Kysely plugin that strips the WITH clause from a
|
|
696
|
+
* SelectQueryNode. Used to remove CTEs that the query creator
|
|
697
|
+
* attaches to queries built via `selectFn` in `.write()` / `.writeAs()`.
|
|
698
|
+
* The CTEs are already captured separately in `writeQueryCreator`.
|
|
699
|
+
*/
|
|
700
|
+
const stripWithPlugin = {
|
|
701
|
+
transformQuery(args) {
|
|
702
|
+
const node = args.node;
|
|
703
|
+
if (node.kind === "SelectQueryNode" && node.with) return {
|
|
704
|
+
...node,
|
|
705
|
+
with: void 0
|
|
706
|
+
};
|
|
707
|
+
return node;
|
|
708
|
+
},
|
|
709
|
+
async transformResult(args) {
|
|
710
|
+
return args.result;
|
|
711
|
+
}
|
|
712
|
+
};
|
|
744
713
|
const filteringJoins = new Set([
|
|
745
714
|
"innerJoin",
|
|
746
715
|
"innerJoinLateral",
|
|
@@ -783,17 +752,13 @@ var QuerySetImpl = class QuerySetImpl {
|
|
|
783
752
|
return this.#props.baseQuery;
|
|
784
753
|
}
|
|
785
754
|
#getSelectFromBase(isNested, isLocalSubquery) {
|
|
786
|
-
const { db, baseQuery, baseAlias,
|
|
755
|
+
const { db, baseQuery, baseAlias, writeQueryCreator } = this.#props;
|
|
787
756
|
if (isSelectQueryBuilder(baseQuery)) {
|
|
788
|
-
if (
|
|
757
|
+
if (writeQueryCreator) {
|
|
789
758
|
if (isNested) throw new InvalidJoinedQuerySetError(baseAlias);
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
-
}
|
|
759
|
+
let qb$1 = writeQueryCreator.selectFrom(baseQuery.as(baseAlias));
|
|
760
|
+
qb$1 = applyHoistedSelections(qb$1, baseQuery, baseAlias);
|
|
761
|
+
return qb$1;
|
|
797
762
|
}
|
|
798
763
|
return applyHoistedSelections(db.selectFrom(baseQuery.as(baseAlias)), baseQuery, baseAlias);
|
|
799
764
|
}
|
|
@@ -842,12 +807,16 @@ var QuerySetImpl = class QuerySetImpl {
|
|
|
842
807
|
if (offset !== null) qb = qb.offset(offset);
|
|
843
808
|
return qb;
|
|
844
809
|
}
|
|
845
|
-
#applyOrderBy(qb) {
|
|
810
|
+
#applyOrderBy(qb, isOuter = false) {
|
|
846
811
|
const { baseAlias, keyBy, orderBy } = this.#props;
|
|
812
|
+
const eb = k.expressionBuilder(qb);
|
|
847
813
|
let keyByArray = typeof keyBy === "string" ? [keyBy] : keyBy;
|
|
848
814
|
for (const { expr, modifiers } of orderBy) {
|
|
849
|
-
|
|
850
|
-
|
|
815
|
+
let orderExpr;
|
|
816
|
+
if (expr.includes(SEP)) if (isOuter) orderExpr = eb.ref(`${baseAlias}.${expr}`);
|
|
817
|
+
else orderExpr = expr.replace(SEP, ".");
|
|
818
|
+
else orderExpr = `${baseAlias}.${expr}`;
|
|
819
|
+
qb = qb.orderBy(orderExpr, modifiers);
|
|
851
820
|
keyByArray = keyByArray.filter((k$1) => k$1 !== expr);
|
|
852
821
|
}
|
|
853
822
|
if (this.#props.orderByKeys) for (const key of keyByArray) qb = qb.orderBy(`${baseAlias}.${key}`, "asc");
|
|
@@ -873,7 +842,7 @@ var QuerySetImpl = class QuerySetImpl {
|
|
|
873
842
|
const { joinCollections } = this.#props;
|
|
874
843
|
let qb = this.#getSelectFromBase(isNested, isLocalSubquery);
|
|
875
844
|
for (const [key, collection] of joinCollections) qb = this.#addCollectionAsJoin(qb, key, collection);
|
|
876
|
-
if (!(isNested || isLocalSubquery)) qb = this.#applyOrderBy(qb);
|
|
845
|
+
if (!(isNested || isLocalSubquery)) qb = this.#applyOrderBy(qb, false);
|
|
877
846
|
return qb;
|
|
878
847
|
}
|
|
879
848
|
toJoinedQuery() {
|
|
@@ -893,12 +862,12 @@ var QuerySetImpl = class QuerySetImpl {
|
|
|
893
862
|
if (this.#isCardinalityOne()) return this.#applyLimitAndOffset(this.#toJoinedQuery(isNested, isLocalSubquery));
|
|
894
863
|
let cardinalityOneQuery = this.#toCardinalityOneQuery(isNested, isLocalSubquery);
|
|
895
864
|
cardinalityOneQuery = this.#applyLimitAndOffset(cardinalityOneQuery);
|
|
896
|
-
if (limit || offset) cardinalityOneQuery = this.#applyOrderBy(cardinalityOneQuery);
|
|
865
|
+
if (limit || offset) cardinalityOneQuery = this.#applyOrderBy(cardinalityOneQuery, false);
|
|
897
866
|
const aliasedCardinalityOneQuery = cardinalityOneQuery.as(baseAlias);
|
|
898
867
|
let qb = db.selectFrom(aliasedCardinalityOneQuery);
|
|
899
868
|
qb = applyHoistedSelections(qb, cardinalityOneQuery, baseAlias);
|
|
900
869
|
for (const [key, collection] of joinCollections) if (!this.#isCollectionCardinalityOne(collection)) qb = this.#addCollectionAsJoin(qb, key, collection);
|
|
901
|
-
if (!(isNested || isLocalSubquery)) qb = this.#applyOrderBy(qb);
|
|
870
|
+
if (!(isNested || isLocalSubquery)) qb = this.#applyOrderBy(qb, true);
|
|
902
871
|
for (const modifier of this.#props.frontModifiers) qb = qb.modifyFront(modifier);
|
|
903
872
|
for (const modifier of this.#props.endModifiers) qb = qb.modifyEnd(modifier);
|
|
904
873
|
return qb;
|
|
@@ -1114,10 +1083,12 @@ var QuerySetImpl = class QuerySetImpl {
|
|
|
1114
1083
|
delete(iqb) {
|
|
1115
1084
|
return this.#asWrite(iqb);
|
|
1116
1085
|
}
|
|
1117
|
-
write(
|
|
1086
|
+
write(cteFn, selectFn) {
|
|
1087
|
+
const qc = cteFn(this.#props.db);
|
|
1088
|
+
const baseQuery = selectFn(qc).withPlugin(stripWithPlugin);
|
|
1118
1089
|
return this.#clone({
|
|
1119
|
-
baseQuery
|
|
1120
|
-
|
|
1090
|
+
baseQuery,
|
|
1091
|
+
writeQueryCreator: qc
|
|
1121
1092
|
});
|
|
1122
1093
|
}
|
|
1123
1094
|
};
|
|
@@ -1147,7 +1118,7 @@ var QuerySetCreator = class {
|
|
|
1147
1118
|
orderByKeys: true,
|
|
1148
1119
|
frontModifiers: [],
|
|
1149
1120
|
endModifiers: [],
|
|
1150
|
-
|
|
1121
|
+
writeQueryCreator: null
|
|
1151
1122
|
});
|
|
1152
1123
|
}
|
|
1153
1124
|
selectAs(alias, query, keyBy = DEFAULT_KEY_BY) {
|
|
@@ -1162,13 +1133,14 @@ var QuerySetCreator = class {
|
|
|
1162
1133
|
deleteAs(alias, query, keyBy = DEFAULT_KEY_BY) {
|
|
1163
1134
|
return this.#createQuerySet(alias, query, keyBy);
|
|
1164
1135
|
}
|
|
1165
|
-
writeAs(alias,
|
|
1166
|
-
const
|
|
1136
|
+
writeAs(alias, cteFn, selectFn, keyBy) {
|
|
1137
|
+
const qc = cteFn(this.#db);
|
|
1138
|
+
const baseQuery = selectFn(qc).withPlugin(stripWithPlugin);
|
|
1167
1139
|
return new QuerySetImpl({
|
|
1168
1140
|
db: this.#db,
|
|
1169
1141
|
baseAlias: alias,
|
|
1170
1142
|
baseQuery,
|
|
1171
|
-
keyBy,
|
|
1143
|
+
keyBy: keyBy ?? DEFAULT_KEY_BY,
|
|
1172
1144
|
hydrator: createHydrator().orderByKeys(),
|
|
1173
1145
|
joinCollections: /* @__PURE__ */ new Map(),
|
|
1174
1146
|
attachCollections: /* @__PURE__ */ new Map(),
|
|
@@ -1178,7 +1150,7 @@ var QuerySetCreator = class {
|
|
|
1178
1150
|
orderByKeys: true,
|
|
1179
1151
|
frontModifiers: [],
|
|
1180
1152
|
endModifiers: [],
|
|
1181
|
-
|
|
1153
|
+
writeQueryCreator: qc
|
|
1182
1154
|
});
|
|
1183
1155
|
}
|
|
1184
1156
|
};
|