kysely-hydrate 0.8.0 → 0.9.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 CHANGED
@@ -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)
@@ -799,6 +800,53 @@ You can inspect the generated SQL using `.toQuery()`, `.toJoinedQuery()`, or `.t
799
800
  - `toJoinedQuery()`: Returns the query with all joins applied (subject to row explosion).
800
801
  - `toBaseQuery()`: Returns the base query without any joins (but with modifications).
801
802
 
803
+ ### Hydrating pre-fetched rows with `.hydrate()`
804
+
805
+ Sometimes you already have the flat rows—from a separate query, a cache, a
806
+ transaction, or from calling `.toQuery().execute()` directly—and want to hydrate
807
+ them without re-executing the query.
808
+
809
+ The `.hydrate()` method applies the same hydration logic that `.execute()` uses,
810
+ including nested joins, `mapFields`, `extras`, `omit`, and `map`.
811
+
812
+ ```ts
813
+ const qs = querySet(db)
814
+ .selectAs("user", db.selectFrom("users").select(["id", "username"]))
815
+ .leftJoinMany(
816
+ "posts",
817
+ ({ eb, qs }) => qs(eb.selectFrom("posts").select(["id", "title", "userId"])),
818
+ "posts.userId",
819
+ "user.id",
820
+ );
821
+
822
+ // Fetch the flat rows yourself
823
+ const rows = await qs.toQuery().execute();
824
+
825
+ // Hydrate them
826
+ const users = await qs.hydrate(rows);
827
+ // ⬇
828
+ type Result = Array<{
829
+ id: number;
830
+ username: string;
831
+ posts: Array<{ id: number; title: string; userId: number }>;
832
+ }>;
833
+ ```
834
+
835
+ It also accepts a single row and returns a single hydrated result:
836
+
837
+ ```ts
838
+ const [row] = await qs.toQuery().execute();
839
+ const user = await qs.hydrate(row);
840
+ // ⬇ { id: number; username: string; posts: Array<...> }
841
+ ```
842
+
843
+ For convenience, `.hydrate()` accepts a `Promise` as input, so you can skip
844
+ the intermediate `await`:
845
+
846
+ ```ts
847
+ const users = await qs.hydrate(qs.toQuery().execute());
848
+ ```
849
+
802
850
  ### Mapped properties with `.mapFields()`
803
851
 
804
852
  Transform individual fields in the result set. This changes the output type for
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 TInput<T extends TQuerySet> = T["JoinedQuery"]["O"];
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
  *
@@ -1378,7 +1409,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
1378
1409
  * the field value from the entire row.
1379
1410
  * @returns A new HydratedQueryBuilder with the extras applied.
1380
1411
  */
1381
- extras<E extends Extras<TInput<T>>>(extras: E): QuerySet<TWithExtendedOutput<T, InferExtras<TInput<T>, E>>>;
1412
+ extras<E extends Extras<THydrationInput<T>>>(extras: E): QuerySet<TWithExtendedOutput<T, InferExtras<THydrationInput<T>, E>>>;
1382
1413
  /**
1383
1414
  * Adds computed fields to the hydrated output by spreading the return value
1384
1415
  * of a function. Unlike `.extras()` which defines one field at a time,
@@ -1409,7 +1440,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
1409
1440
  * computed properties.
1410
1441
  * @returns A new HydratedQueryBuilder with the extender applied.
1411
1442
  */
1412
- extend<F extends Extender<TInput<T>>>(fn: F): QuerySet<TWithExtendedOutput<T, InferExtender<TInput<T>, F>>>;
1443
+ extend<F extends Extender<THydrationInput<T>>>(fn: F): QuerySet<TWithExtendedOutput<T, InferExtender<THydrationInput<T>, F>>>;
1413
1444
  /**
1414
1445
  * Transforms already-selected field values in the hydrated output. Fields
1415
1446
  * not mentioned in the mappings will still be included as-is.
@@ -1431,7 +1462,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
1431
1462
  * functions.
1432
1463
  * @returns A new HydratedQueryBuilder with the field transformations applied.
1433
1464
  */
1434
- mapFields<M extends FieldMappings<TInput<T>>>(mappings: M): QuerySet<TWithExtendedOutput<T, InferFields<TInput<T>, M>>>;
1465
+ mapFields<M extends FieldMappings<THydrationInput<T>>>(mappings: M): QuerySet<TWithExtendedOutput<T, InferFields<THydrationInput<T>, M>>>;
1435
1466
  /**
1436
1467
  * Omits specified fields from the hydrated output. Useful for excluding
1437
1468
  * fields that were selected for internal use (e.g., for extras).
@@ -1453,7 +1484,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
1453
1484
  * @param keys - Field names to omit from the output.
1454
1485
  * @returns A new HydratedQueryBuilder with the fields omitted.
1455
1486
  */
1456
- omit<K$1 extends keyof TInput<T>>(keys: readonly K$1[]): QuerySet<TWithOmit<T, K$1>>;
1487
+ omit<K$1 extends keyof THydrationInput<T>>(keys: readonly K$1[]): QuerySet<TWithOmit<T, K$1>>;
1457
1488
  /**
1458
1489
  * Extends this query builder's hydration configuration with another Hydrator.
1459
1490
  * The other Hydrator's configuration takes precedence in case of conflicts.
@@ -1485,8 +1516,8 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
1485
1516
  * @param hydrator - The Hydrator to extend with.
1486
1517
  * @returns A new HydratedQueryBuilder with merged hydration configuration.
1487
1518
  */
1488
- with<OtherInput extends StrictSubset<TInput<T>, OtherInput>, OtherOutput>(hydrator: FullHydrator<OtherInput, OtherOutput>): QuerySet<TWithExtendedOutput<T, OtherOutput>>;
1489
- with<OtherInput extends StrictSubset<TInput<T>, OtherInput>, OtherOutput>(hydrator: MappedHydrator<OtherInput, OtherOutput>): QuerySet<TWithExtendedOutput<T, OtherOutput>>;
1519
+ with<OtherInput extends StrictSubset<THydrationInput<T>, OtherInput>, OtherOutput>(hydrator: FullHydrator<OtherInput, OtherOutput>): QuerySet<TWithExtendedOutput<T, OtherOutput>>;
1520
+ with<OtherInput extends StrictSubset<THydrationInput<T>, OtherInput>, OtherOutput>(hydrator: MappedHydrator<OtherInput, OtherOutput>): QuerySet<TWithExtendedOutput<T, OtherOutput>>;
1490
1521
  /**
1491
1522
  * Attaches data from an external source (not via SQL joins) as a nested
1492
1523
  * array. The `fetchFn` is called exactly once per query execution with all
@@ -2334,8 +2365,8 @@ interface ModifyCollectionReturnMap<T extends TQuerySet, Key extends string, TNe
2334
2365
  AttachOneOrThrow: QuerySetWithAttach<T, Key, "AttachOneOrThrow", NewValue>;
2335
2366
  AttachMany: QuerySetWithAttach<T, Key, "AttachMany", NewValue>;
2336
2367
  }
2337
- type ToFetchFn<T extends TQuerySet, FetchFnReturn extends SomeFetchFnReturn> = SomeFetchFn<TInput<T>, FetchFnReturn>;
2338
- type ToAttachedKeysArg<T extends TQuerySet, FetchFnReturn extends SomeFetchFnReturn> = AttachedKeysArg<TInput<T>, AttachedOutputFromFetchFnReturn<NoInfer<FetchFnReturn>>>;
2368
+ type ToFetchFn<T extends TQuerySet, FetchFnReturn extends SomeFetchFnReturn> = SomeFetchFn<THydrationInput<T>, FetchFnReturn>;
2369
+ type ToAttachedKeysArg<T extends TQuerySet, FetchFnReturn extends SomeFetchFnReturn> = AttachedKeysArg<THydrationInput<T>, AttachedOutputFromFetchFnReturn<NoInfer<FetchFnReturn>>>;
2339
2370
  interface AttachedOutputMap<in out FetchFnReturn extends SomeFetchFnReturn> {
2340
2371
  AttachOne: AttachedOutputFromFetchFnReturn<FetchFnReturn> | null;
2341
2372
  AttachOneOrThrow: AttachedOutputFromFetchFnReturn<FetchFnReturn>;
package/dist/index.mjs CHANGED
@@ -856,13 +856,16 @@ var QuerySetImpl = class QuerySetImpl {
856
856
  toOperationNode() {
857
857
  return this.toQuery().toOperationNode();
858
858
  }
859
- async execute() {
860
- const rows = await this.toQuery().execute();
861
- return this.#props.hydrator.hydrate(rows, {
859
+ async hydrate(input) {
860
+ return this.#props.hydrator.hydrate(await input, {
862
861
  [EnableAutoInclusion]: true,
863
862
  sort: "nested"
864
863
  });
865
864
  }
865
+ async execute() {
866
+ const rows = await this.toQuery().execute();
867
+ return this.hydrate(rows);
868
+ }
866
869
  async executeTakeFirst() {
867
870
  const [result] = await this.execute();
868
871
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely-hydrate",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Explicit ORM-style queries with Kysely",
5
5
  "homepage": "https://github.com/GiacoCorsiglia/kysely-hydrate#readme",
6
6
  "bugs": {