kysely-hydrate 0.7.3 → 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,8 +130,10 @@ 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)
136
+ - [Computed properties with `.extend()`](#computed-properties-with-extend)
135
137
  - [Excluded properties with `.omit()`](#excluded-properties-with-omit)
136
138
  - [Output transformations with `.map()`](#output-transformations-with-map)
137
139
  - [Composable mappings with `.with()`](#composable-mappings-with-with)
@@ -146,7 +148,7 @@ type Result = Array<{
146
148
  - [Output transformations with `.map()`](#output-transformations-with-map-1)
147
149
  - [Attached collections with `.attach*()`](#attached-collections-with-attach)
148
150
  - [Prefixed collections with `.has*()`](#prefixed-collections-with-has)
149
- - [Composing hydrators with `.extend()`](#composing-hydrators-with-extend)
151
+ - [Composing hydrators with `.with()`](#composing-hydrators-with-with)
150
152
  - [FAQ](#faq)
151
153
 
152
154
  ## Installation
@@ -798,6 +800,53 @@ You can inspect the generated SQL using `.toQuery()`, `.toJoinedQuery()`, or `.t
798
800
  - `toJoinedQuery()`: Returns the query with all joins applied (subject to row explosion).
799
801
  - `toBaseQuery()`: Returns the base query without any joins (but with modifications).
800
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
+
801
850
  ### Mapped properties with `.mapFields()`
802
851
 
803
852
  Transform individual fields in the result set. This changes the output type for
@@ -843,6 +892,34 @@ type Result = Array<{
843
892
  }>;
844
893
  ```
845
894
 
895
+ ### Computed properties with `.extend()`
896
+
897
+ Like `.extras()`, but takes a single function that returns an object. All
898
+ returned keys are merged into the output. This is useful when multiple computed
899
+ fields share intermediate work.
900
+
901
+ ```ts
902
+ const users = await querySet(db)
903
+ .selectAs("user", db.selectFrom("users").select(["id", "firstName", "lastName", "birthDate"]))
904
+ .extend((row) => {
905
+ const names = [row.firstName, row.lastName];
906
+ return {
907
+ fullName: names.join(" "),
908
+ initials: names.map((n) => n[0]).join(""),
909
+ };
910
+ })
911
+ .execute();
912
+ // ⬇
913
+ type Result = Array<{
914
+ id: number;
915
+ firstName: string;
916
+ lastName: string;
917
+ birthDate: Date;
918
+ fullName: string;
919
+ initials: string;
920
+ }>;
921
+ ```
922
+
846
923
  ### Excluded properties with `.omit()`
847
924
 
848
925
  Remove fields from the final output. This is useful for removing intermediate
@@ -1355,7 +1432,7 @@ type Result = UserModel[];
1355
1432
 
1356
1433
  As in query sets, `.map()` is a terminal operation—after calling it, you can
1357
1434
  only call `.map()` again or `.hydrate()`. You cannot call configuration methods
1358
- like `.fields()`, `.extras()`, `.has*()`, or `.extend()`.
1435
+ like `.fields()`, `.extras()`, `.has*()`, or `.with()`.
1359
1436
 
1360
1437
  ```ts
1361
1438
  const mapped = createHydrator<User>()
@@ -1368,7 +1445,7 @@ mapped.hydrate(rows);
1368
1445
 
1369
1446
  // ❌ These don't compile:
1370
1447
  mapped.fields({ ... }); // Error: Property 'fields' does not exist
1371
- mapped.extend(...); // Error: Property 'extend' does not exist
1448
+ mapped.with(...); // Error: Property 'with' does not exist
1372
1449
  ```
1373
1450
 
1374
1451
  ### Attached collections with `.attach*()`
@@ -1423,7 +1500,7 @@ non-nullable column that was made nullable by a left join; or, (b) it's actually
1423
1500
  nullable in the "posts" table. The query set API, on the other hand, _does_
1424
1501
  know the difference, and so does not suffer from this problem.
1425
1502
 
1426
- ### Composing hydrators with `.extend()`
1503
+ ### Composing hydrators with `.with()`
1427
1504
 
1428
1505
  Merges two hydrators. The second hydrator's configuration takes precedence.
1429
1506
 
@@ -1431,7 +1508,7 @@ This is a good way to build small, reusable hydrators (for a "user preview", a
1431
1508
  "user display name", etc.) and compose them.
1432
1509
 
1433
1510
  > [!NOTE]
1434
- > Hydrators must have the same `keyBy`. If they don't, `.extend()` throws.
1511
+ > Hydrators must have the same `keyBy`. If they don't, `.with()` throws.
1435
1512
 
1436
1513
  ```ts
1437
1514
  type UserRow = { id: number; username: string; email: string };
@@ -1442,7 +1519,7 @@ const withDisplayName = createHydrator<UserRow>().extras({
1442
1519
  displayName: (u) => `${u.username} <${u.email}>`,
1443
1520
  });
1444
1521
 
1445
- const combined = base.extend(withDisplayName);
1522
+ const combined = base.with(withDisplayName);
1446
1523
  // ⬇
1447
1524
  type Result = Hydrator<UserRow, { id: number; username: string; displayName: string }>;
1448
1525
  ```
@@ -76,12 +76,12 @@ var UnsupportedNodeTypeError = class extends KyselyHydrateError {
76
76
  }
77
77
  };
78
78
  /**
79
- * Error thrown when attempting to extend a Hydrator with another Hydrator
79
+ * Error thrown when composing a Hydrator with another Hydrator
80
80
  * that has a different keyBy configuration.
81
81
  */
82
82
  var KeyByMismatchError = class extends KyselyHydrateError {
83
83
  constructor(thisKeyBy, otherKeyBy) {
84
- super(`Cannot extend hydrators with different keyBy: ${thisKeyBy} vs ${otherKeyBy}`);
84
+ super(`Cannot compose hydrators with different keyBy: ${thisKeyBy} vs ${otherKeyBy}`);
85
85
  }
86
86
  };
87
87
  /**
@@ -1,4 +1,4 @@
1
- import { d as UnsupportedAliasNodeTypeError, f as UnsupportedNodeTypeError, m as WildcardSelectionError, p as UnsupportedTableAliasNodeTypeError, t as AmbiguousColumnReferenceError, u as UnexpectedSelectionTypeError } from "./errors-EVqhxGJc.mjs";
1
+ import { d as UnsupportedAliasNodeTypeError, f as UnsupportedNodeTypeError, m as WildcardSelectionError, p as UnsupportedTableAliasNodeTypeError, t as AmbiguousColumnReferenceError, u as UnexpectedSelectionTypeError } from "./errors-CbAVHTlW.mjs";
2
2
  import * as k from "kysely";
3
3
 
4
4
  //#region src/experimental/mapped-expression.ts
package/dist/index.d.mts CHANGED
@@ -101,6 +101,15 @@ type Extras<Input> = Record<string, (input: Input) => unknown>;
101
101
  * Uses the return type of each extra function.
102
102
  */
103
103
  type InferExtras<Input, E extends Extras<Input>> = { [K in keyof E]: ReturnType<E[K]> };
104
+ /**
105
+ * An extender function that receives the full input and returns an object
106
+ * of computed properties to merge into the output.
107
+ */
108
+ type Extender<Input> = (input: Input) => Record<string, unknown>;
109
+ /**
110
+ * Infers the output type for an extender function.
111
+ */
112
+ type InferExtender<Input, F extends Extender<Input>> = ReturnType<F>;
104
113
  /**
105
114
  * The mode of a collection.
106
115
  *
@@ -298,18 +307,29 @@ interface FullHydrator<Input, Output> extends MappedHydrator<Input, Output> {
298
307
  */
299
308
  extras<E extends Extras<Input>>(extras: E): FullHydrator<Input, Extend<Output, InferExtras<Input, E>>>;
300
309
  /**
301
- * Extends this Hydrator with the configuration from another Hydrator. The
310
+ * Adds computed fields to the hydrated output by spreading the return value
311
+ * of a function. Unlike `.extras()` which defines one field at a time,
312
+ * `.extend()` calls a single function whose returned object is merged into
313
+ * the output.
314
+ *
315
+ * @param fn - A function that receives the input and returns an object of
316
+ * computed properties
317
+ * @returns A new Hydrator with the extender applied
318
+ */
319
+ extend<F extends Extender<Input>>(fn: F): FullHydrator<Input, Extend<Output, InferExtender<Input, F>>>;
320
+ /**
321
+ * Composes this Hydrator with the configuration from another Hydrator. The
302
322
  * other Hydrator's configuration takes precedence in case of conflicts.
303
323
  *
304
324
  * Both hydrators must have the same `keyBy`, and any overlapping fields
305
325
  * between the two input types must have compatible types.
306
326
  *
307
- * @param other - The Hydrator to extend with
327
+ * @param other - The Hydrator to compose with
308
328
  * @returns A new Hydrator with merged configuration
309
329
  * @throws {KeyByMismatchError} If the keyBy configurations don't match
310
330
  */
311
- extend<OtherInput extends Partial<Input>, OtherOutput>(other: FullHydrator<OtherInput, OtherOutput>): FullHydrator<Input & OtherInput, Extend<Output, OtherOutput>>;
312
- extend<OtherInput extends Partial<Input>, OtherOutput>(other: MappedHydrator<OtherInput, OtherOutput>): MappedHydrator<Input & OtherInput, Extend<Output, OtherOutput>>;
331
+ with<OtherInput extends Partial<Input>, OtherOutput>(other: FullHydrator<OtherInput, OtherOutput>): FullHydrator<Input & OtherInput, Extend<Output, OtherOutput>>;
332
+ with<OtherInput extends Partial<Input>, OtherOutput>(other: MappedHydrator<OtherInput, OtherOutput>): MappedHydrator<Input & OtherInput, Extend<Output, OtherOutput>>;
313
333
  /**
314
334
  * Configures a nested collection that exists in the same query result. The
315
335
  * child data is expected to be prefixed in the input (e.g., `posts$$id`,
@@ -593,7 +613,7 @@ interface TSelectQuerySet extends TQuerySet {
593
613
  BaseQuery: TSelectQuery;
594
614
  }
595
615
  type QuerySetFor<T extends TQuerySet> = T["IsMapped"] extends true ? MappedQuerySet<T> : QuerySet<T>;
596
- type TInput<T extends TQuerySet> = T["JoinedQuery"]["O"];
616
+ type THydrationInput<T extends TQuerySet> = T["JoinedQuery"]["O"];
597
617
  type TOutput<T extends TQuerySet> = T["HydratedOutput"];
598
618
  interface TMapped<in out T extends TQuerySet, in out Output> {
599
619
  DB: T["DB"];
@@ -848,6 +868,37 @@ interface MappedQuerySet<in out T extends TQuerySet> extends k.Compilable, k.Ope
848
868
  * ```
849
869
  */
850
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>[]>;
851
902
  /**
852
903
  * Executes the query and returns an array of hydrated rows.
853
904
  *
@@ -1358,7 +1409,38 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
1358
1409
  * the field value from the entire row.
1359
1410
  * @returns A new HydratedQueryBuilder with the extras applied.
1360
1411
  */
1361
- 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>>>;
1413
+ /**
1414
+ * Adds computed fields to the hydrated output by spreading the return value
1415
+ * of a function. Unlike `.extras()` which defines one field at a time,
1416
+ * `.extend()` calls a single function whose returned object is merged into
1417
+ * the output.
1418
+ *
1419
+ * ### Examples
1420
+ *
1421
+ * ```ts
1422
+ * const users = await querySet(db)
1423
+ * .selectAs("users", (eb) => eb.selectFrom("users").select(["users.id", "users.firstName", "users.lastName"]))
1424
+ * .extend((row) => ({
1425
+ * fullName: `${row.firstName} ${row.lastName}`,
1426
+ * initials: `${row.firstName[0]}${row.lastName[0]}`,
1427
+ * }))
1428
+ * .execute();
1429
+ * // ⬇
1430
+ * type Result = Array<{
1431
+ * id: number;
1432
+ * firstName: string;
1433
+ * lastName: string;
1434
+ * fullName: string;
1435
+ * initials: string;
1436
+ * }>;
1437
+ * ```
1438
+ *
1439
+ * @param fn - A function that receives the row and returns an object of
1440
+ * computed properties.
1441
+ * @returns A new HydratedQueryBuilder with the extender applied.
1442
+ */
1443
+ extend<F extends Extender<THydrationInput<T>>>(fn: F): QuerySet<TWithExtendedOutput<T, InferExtender<THydrationInput<T>, F>>>;
1362
1444
  /**
1363
1445
  * Transforms already-selected field values in the hydrated output. Fields
1364
1446
  * not mentioned in the mappings will still be included as-is.
@@ -1380,7 +1462,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
1380
1462
  * functions.
1381
1463
  * @returns A new HydratedQueryBuilder with the field transformations applied.
1382
1464
  */
1383
- 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>>>;
1384
1466
  /**
1385
1467
  * Omits specified fields from the hydrated output. Useful for excluding
1386
1468
  * fields that were selected for internal use (e.g., for extras).
@@ -1402,7 +1484,7 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
1402
1484
  * @param keys - Field names to omit from the output.
1403
1485
  * @returns A new HydratedQueryBuilder with the fields omitted.
1404
1486
  */
1405
- 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>>;
1406
1488
  /**
1407
1489
  * Extends this query builder's hydration configuration with another Hydrator.
1408
1490
  * The other Hydrator's configuration takes precedence in case of conflicts.
@@ -1434,8 +1516,8 @@ interface QuerySet<in out T extends TQuerySet> extends MappedQuerySet<T> {
1434
1516
  * @param hydrator - The Hydrator to extend with.
1435
1517
  * @returns A new HydratedQueryBuilder with merged hydration configuration.
1436
1518
  */
1437
- with<OtherInput extends StrictSubset<TInput<T>, OtherInput>, OtherOutput>(hydrator: FullHydrator<OtherInput, OtherOutput>): QuerySet<TWithExtendedOutput<T, OtherOutput>>;
1438
- 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>>;
1439
1521
  /**
1440
1522
  * Attaches data from an external source (not via SQL joins) as a nested
1441
1523
  * array. The `fetchFn` is called exactly once per query execution with all
@@ -2283,8 +2365,8 @@ interface ModifyCollectionReturnMap<T extends TQuerySet, Key extends string, TNe
2283
2365
  AttachOneOrThrow: QuerySetWithAttach<T, Key, "AttachOneOrThrow", NewValue>;
2284
2366
  AttachMany: QuerySetWithAttach<T, Key, "AttachMany", NewValue>;
2285
2367
  }
2286
- type ToFetchFn<T extends TQuerySet, FetchFnReturn extends SomeFetchFnReturn> = SomeFetchFn<TInput<T>, FetchFnReturn>;
2287
- 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>>>;
2288
2370
  interface AttachedOutputMap<in out FetchFnReturn extends SomeFetchFnReturn> {
2289
2371
  AttachOne: AttachedOutputFromFetchFnReturn<FetchFnReturn> | null;
2290
2372
  AttachOneOrThrow: AttachedOutputFromFetchFnReturn<FetchFnReturn>;
@@ -2581,7 +2663,7 @@ declare class UnsupportedNodeTypeError extends KyselyHydrateError {
2581
2663
  constructor(kind: string);
2582
2664
  }
2583
2665
  /**
2584
- * Error thrown when attempting to extend a Hydrator with another Hydrator
2666
+ * Error thrown when composing a Hydrator with another Hydrator
2585
2667
  * that has a different keyBy configuration.
2586
2668
  */
2587
2669
  declare class KeyByMismatchError extends KyselyHydrateError {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as KeyByMismatchError, c as UnexpectedComplexAliasError, d as UnsupportedAliasNodeTypeError, f as UnsupportedNodeTypeError, i as InvalidJoinedQuerySetError, l as UnexpectedSelectAllError, m as WildcardSelectionError, n as CardinalityViolationError, o as KyselyHydrateError, p as UnsupportedTableAliasNodeTypeError, r as ExpectedOneItemError, s as UnexpectedCaseError, t as AmbiguousColumnReferenceError, u as UnexpectedSelectionTypeError } from "./errors-EVqhxGJc.mjs";
1
+ import { a as KeyByMismatchError, c as UnexpectedComplexAliasError, d as UnsupportedAliasNodeTypeError, f as UnsupportedNodeTypeError, i as InvalidJoinedQuerySetError, l as UnexpectedSelectAllError, m as WildcardSelectionError, n as CardinalityViolationError, o as KyselyHydrateError, p as UnsupportedTableAliasNodeTypeError, r as ExpectedOneItemError, s as UnexpectedCaseError, t as AmbiguousColumnReferenceError, u as UnexpectedSelectionTypeError } from "./errors-CbAVHTlW.mjs";
2
2
  import * as k from "kysely";
3
3
 
4
4
  //#region src/helpers/order-by.ts
@@ -264,7 +264,13 @@ var HydratorImpl = class HydratorImpl {
264
264
  extras: addObjectToMap(this.#props.extras, extras)
265
265
  });
266
266
  }
267
- extend(other) {
267
+ extend(fn) {
268
+ return new HydratorImpl({
269
+ ...this.#props,
270
+ extenders: [...this.#props.extenders ?? [], fn]
271
+ });
272
+ }
273
+ with(other) {
268
274
  const otherImpl = other;
269
275
  const thisKeyBy = JSON.stringify(this.#props.keyBy);
270
276
  const otherKeyBy = JSON.stringify(otherImpl.#props.keyBy);
@@ -276,6 +282,7 @@ var HydratorImpl = class HydratorImpl {
276
282
  keyBy: otherProps.keyBy,
277
283
  fields: new Map([...ownProps.fields ?? [], ...otherProps.fields ?? []]),
278
284
  extras: new Map([...ownProps.extras ?? [], ...otherProps.extras ?? []]),
285
+ extenders: [...ownProps.extenders ?? [], ...otherProps.extenders ?? []],
279
286
  collections: mergedCollections,
280
287
  attachedCollections: new Map([...ownProps.attachedCollections ?? [], ...otherProps.attachedCollections ?? []]),
281
288
  mapFns: [...this.#props.mapFns ?? [], ...otherProps.mapFns ?? []],
@@ -412,7 +419,7 @@ var HydratorImpl = class HydratorImpl {
412
419
  * Hydrates a single entity. All attach collections are already fetched and provided in attachedDataMap.
413
420
  */
414
421
  #hydrateOne(ctx, prefix, input, inputRows) {
415
- const { fields, extras, collections, attachedCollections } = this.#props;
422
+ const { fields, extras, extenders, collections, attachedCollections } = this.#props;
416
423
  const entity = {};
417
424
  if (ctx.autoIncludeFields) for (const key of this.#getAutoFields(ctx, prefix, input)) entity[key] = getPrefixedValue(prefix, input, key);
418
425
  if (fields) for (const [key, field] of fields) {
@@ -420,9 +427,10 @@ var HydratorImpl = class HydratorImpl {
420
427
  const value = getPrefixedValue(prefix, input, key);
421
428
  entity[key] = field === true ? value : field(value);
422
429
  }
423
- if (extras) {
430
+ if (extras || extenders) {
424
431
  const accessor = createdPrefixedAccessor(prefix, input);
425
- for (const [key, extra] of extras) entity[key] = extra(accessor);
432
+ if (extras) for (const [key, extra] of extras) entity[key] = extra(accessor);
433
+ if (extenders) for (const extender of extenders) Object.assign(entity, extender(accessor));
426
434
  }
427
435
  if (collections) {
428
436
  const childCtx = this.#props.hasMultipleManyCollections && !ctx.hasSiblingManyCollections ? {
@@ -848,13 +856,16 @@ var QuerySetImpl = class QuerySetImpl {
848
856
  toOperationNode() {
849
857
  return this.toQuery().toOperationNode();
850
858
  }
851
- async execute() {
852
- const rows = await this.toQuery().execute();
853
- return this.#props.hydrator.hydrate(rows, {
859
+ async hydrate(input) {
860
+ return this.#props.hydrator.hydrate(await input, {
854
861
  [EnableAutoInclusion]: true,
855
862
  sort: "nested"
856
863
  });
857
864
  }
865
+ async execute() {
866
+ const rows = await this.toQuery().execute();
867
+ return this.hydrate(rows);
868
+ }
858
869
  async executeTakeFirst() {
859
870
  const [result] = await this.execute();
860
871
  return result;
@@ -878,6 +889,9 @@ var QuerySetImpl = class QuerySetImpl {
878
889
  extras(extras) {
879
890
  return this.#clone({ hydrator: asFullHydrator(this.#props.hydrator).extras(extras) });
880
891
  }
892
+ extend(fn) {
893
+ return this.#clone({ hydrator: asFullHydrator(this.#props.hydrator).extend(fn) });
894
+ }
881
895
  mapFields(mappings) {
882
896
  return this.#clone({ hydrator: asFullHydrator(this.#props.hydrator).fields(mappings) });
883
897
  }
@@ -885,7 +899,7 @@ var QuerySetImpl = class QuerySetImpl {
885
899
  return this.#clone({ hydrator: asFullHydrator(this.#props.hydrator).omit(keys) });
886
900
  }
887
901
  with(hydrator) {
888
- return this.#clone({ hydrator: asFullHydrator(this.#props.hydrator).extend(hydrator) });
902
+ return this.#clone({ hydrator: asFullHydrator(this.#props.hydrator).with(hydrator) });
889
903
  }
890
904
  #addAttach(mode, key, fetchFn, keys) {
891
905
  return this.#clone({ hydrator: asFullHydrator(this.#props.hydrator).attach(mode, key, fetchFn, keys) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely-hydrate",
3
- "version": "0.7.3",
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": {