@prisma-next/sql-orm-client 0.9.0-dev.2 → 0.9.0-dev.4

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/src/collection.ts CHANGED
@@ -224,6 +224,25 @@ export class Collection<
224
224
  this.includeRefinementMode = options.includeRefinementMode ?? false;
225
225
  }
226
226
 
227
+ /**
228
+ * Narrow the collection with a `WHERE` predicate. Returns a new
229
+ * collection — chain further builders or run a terminal on it.
230
+ *
231
+ * Accepts a callback receiving a typed model accessor, a raw
232
+ * `WhereArg` expression, or a shorthand field/value object. Multiple
233
+ * calls are AND-combined.
234
+ *
235
+ * ```typescript
236
+ * // Callback form with column-level operators:
237
+ * const matches = await db.orm.User.where((u) => u.email.eq('alice@example.com')).all();
238
+ *
239
+ * // Shorthand object form:
240
+ * const user = await db.orm.User.where({ id: 1, active: true }).first();
241
+ *
242
+ * // Chained AND — still a builder, run a terminal to execute:
243
+ * const adults = await db.orm.User.where({ active: true }).where((u) => u.age.gt(18)).all();
244
+ * ```
245
+ */
227
246
  where(
228
247
  fn: (model: ModelAccessor<TContract, ModelName>) => WhereDirectInput,
229
248
  ): Collection<TContract, ModelName, Row, WithWhereState<State>>;
@@ -258,6 +277,25 @@ export class Collection<
258
277
  });
259
278
  }
260
279
 
280
+ /**
281
+ * Narrow a polymorphic model to a specific variant. The returned
282
+ * collection has the variant's row shape and a discriminator filter
283
+ * is automatically applied. Chaining `.variant(...)` again replaces
284
+ * the previous variant filter.
285
+ *
286
+ * ```typescript
287
+ * // Read only admin users (STI):
288
+ * const admins = await db.orm.User.variant('Admin').all();
289
+ *
290
+ * // Iterate the rows:
291
+ * for await (const admin of db.orm.User.variant('Admin').all()) {
292
+ * console.log(admin.role);
293
+ * }
294
+ *
295
+ * // Insert under a variant — discriminator is injected automatically:
296
+ * await db.orm.User.variant('Admin').create({ name: 'Ada', role: 'super' });
297
+ * ```
298
+ */
261
299
  variant<V extends VariantNames<TContract, ModelName>>(
262
300
  variantName: V,
263
301
  ): Collection<
@@ -314,6 +352,34 @@ export class Collection<
314
352
  });
315
353
  }
316
354
 
355
+ /**
356
+ * Eagerly load a related model. The relation appears on every
357
+ * returned row under its declared name; to-one relations are mapped
358
+ * to a single object (or `null`), to-many relations to an array.
359
+ *
360
+ * An optional refinement callback receives a child collection that
361
+ * can be further constrained, projected, ordered, paginated, or
362
+ * reduced to scalars via `count()`/`sum()`/etc. or to multiple
363
+ * sub-aggregates via `combine()`.
364
+ *
365
+ * ```typescript
366
+ * // Simple include — every user comes back with its posts array:
367
+ * const users = await db.orm.User.include('posts').all();
368
+ *
369
+ * // Refine the related collection:
370
+ * const withRecent = await db.orm.User.include('posts', (posts) =>
371
+ * posts.where({ published: true }).orderBy((p) => p.createdAt.desc()).take(5),
372
+ * ).all();
373
+ *
374
+ * // Reduce a to-many relation to a scalar value:
375
+ * const withCounts = await db.orm.User.include('posts', (posts) => posts.count()).all();
376
+ *
377
+ * // Multiple sub-views via combine():
378
+ * const overview = await db.orm.User.include('posts', (posts) =>
379
+ * posts.combine({ recent: posts.take(3), total: posts.count() }),
380
+ * ).all();
381
+ * ```
382
+ */
317
383
  include<
318
384
  RelName extends RelationNames<TContract, ModelName>,
319
385
  RelatedName extends RelatedModelName<TContract, ModelName, RelName> & string = RelatedModelName<
@@ -441,6 +507,20 @@ export class Collection<
441
507
  });
442
508
  }
443
509
 
510
+ /**
511
+ * Project the row down to a subset of scalar fields. Previously
512
+ * included relations are preserved on the resulting row shape; only
513
+ * scalar columns are narrowed.
514
+ *
515
+ * ```typescript
516
+ * const summaries = await db.orm.User.select('id', 'email').all();
517
+ * // typeof summaries[number] === { id: ...; email: ... }
518
+ *
519
+ * for await (const row of db.orm.User.select('id', 'email').all()) {
520
+ * console.log(row.id, row.email);
521
+ * }
522
+ * ```
523
+ */
444
524
  select<
445
525
  Fields extends readonly [
446
526
  keyof DefaultModelRow<TContract, ModelName> & string,
@@ -470,6 +550,23 @@ export class Collection<
470
550
  });
471
551
  }
472
552
 
553
+ /**
554
+ * Append an `ORDER BY` clause. Pass a single selector callback or an
555
+ * array of callbacks; each receives a typed model accessor whose
556
+ * columns expose `.asc()` and `.desc()`. Multiple calls append to the
557
+ * existing list (left-to-right ordering preserved).
558
+ *
559
+ * Calling `orderBy(...)` unlocks `cursor(...)` and `distinctOn(...)`,
560
+ * which both require a defined sort order.
561
+ *
562
+ * ```typescript
563
+ * const newest = await db.orm.User.orderBy((u) => u.createdAt.desc()).all();
564
+ *
565
+ * const byName = await db.orm.User
566
+ * .orderBy([(u) => u.lastName.asc(), (u) => u.firstName.asc()])
567
+ * .all();
568
+ * ```
569
+ */
473
570
  orderBy(
474
571
  selection:
475
572
  | ((model: ModelAccessor<TContract, ModelName>) => OrderByItem)
@@ -486,6 +583,19 @@ export class Collection<
486
583
  });
487
584
  }
488
585
 
586
+ /**
587
+ * Switch to grouped-aggregate mode. Returns a `GroupedCollection`
588
+ * whose `.aggregate(...)` terminal produces one row per group with
589
+ * the chosen key columns plus the requested aggregates.
590
+ *
591
+ * ```typescript
592
+ * const stats = await db.orm.Post
593
+ * .where({ published: true })
594
+ * .groupBy('userId')
595
+ * .aggregate((agg) => ({ count: agg.count(), totalViews: agg.sum('views') }));
596
+ * // [{ userId: 1, count: 3, totalViews: 120 }, ...]
597
+ * ```
598
+ */
489
599
  groupBy<
490
600
  Fields extends readonly [
491
601
  keyof DefaultModelRow<TContract, ModelName> & string,
@@ -503,11 +613,34 @@ export class Collection<
503
613
  });
504
614
  }
505
615
 
616
+ /**
617
+ * Scalar reducer — reduces a to-many relation to the number of
618
+ * related rows. Use inside an `include(...)` refinement callback as
619
+ * `include(..., (rel) => rel.count())`; throws if called elsewhere.
620
+ * The parent row's relation field becomes that count instead of an
621
+ * array.
622
+ *
623
+ * ```typescript
624
+ * const users = await db.orm.User.include('posts', (posts) => posts.count()).all();
625
+ * // each user row: { ...user, posts: number }
626
+ * ```
627
+ */
506
628
  count(): IncludeScalar<number> {
507
629
  this.#assertIncludeRefinementMode('count()');
508
630
  return createIncludeScalar<number>('count', this.state);
509
631
  }
510
632
 
633
+ /**
634
+ * Scalar reducer — reduces a to-many relation to the sum of `field`
635
+ * across related rows. Returns `null` when there are no related
636
+ * rows. Use inside an `include(...)` refinement callback; throws if
637
+ * called elsewhere.
638
+ *
639
+ * ```typescript
640
+ * const users = await db.orm.User.include('posts', (posts) => posts.sum('views')).all();
641
+ * // each user row: { ...user, posts: number | null }
642
+ * ```
643
+ */
511
644
  sum<FieldName extends NumericFieldNames<TContract, ModelName>>(
512
645
  field: FieldName,
513
646
  ): IncludeScalar<number | null> {
@@ -516,6 +649,17 @@ export class Collection<
516
649
  return createIncludeScalar<number | null>('sum', this.state, columnName);
517
650
  }
518
651
 
652
+ /**
653
+ * Scalar reducer — reduces a to-many relation to the average of
654
+ * `field` across related rows. Returns `null` when there are no
655
+ * related rows. Use inside an `include(...)` refinement callback;
656
+ * throws if called elsewhere.
657
+ *
658
+ * ```typescript
659
+ * const users = await db.orm.User.include('posts', (posts) => posts.avg('views')).all();
660
+ * // each user row: { ...user, posts: number | null }
661
+ * ```
662
+ */
519
663
  avg<FieldName extends NumericFieldNames<TContract, ModelName>>(
520
664
  field: FieldName,
521
665
  ): IncludeScalar<number | null> {
@@ -524,6 +668,16 @@ export class Collection<
524
668
  return createIncludeScalar<number | null>('avg', this.state, columnName);
525
669
  }
526
670
 
671
+ /**
672
+ * Scalar reducer — reduces a to-many relation to the minimum value
673
+ * of `field` across related rows. Returns `null` when there are no
674
+ * related rows. Use inside an `include(...)` refinement callback;
675
+ * throws if called elsewhere.
676
+ *
677
+ * ```typescript
678
+ * const users = await db.orm.User.include('posts', (posts) => posts.min('views')).all();
679
+ * ```
680
+ */
527
681
  min<FieldName extends NumericFieldNames<TContract, ModelName>>(
528
682
  field: FieldName,
529
683
  ): IncludeScalar<number | null> {
@@ -532,6 +686,16 @@ export class Collection<
532
686
  return createIncludeScalar<number | null>('min', this.state, columnName);
533
687
  }
534
688
 
689
+ /**
690
+ * Scalar reducer — reduces a to-many relation to the maximum value
691
+ * of `field` across related rows. Returns `null` when there are no
692
+ * related rows. Use inside an `include(...)` refinement callback;
693
+ * throws if called elsewhere.
694
+ *
695
+ * ```typescript
696
+ * const users = await db.orm.User.include('posts', (posts) => posts.max('views')).all();
697
+ * ```
698
+ */
535
699
  max<FieldName extends NumericFieldNames<TContract, ModelName>>(
536
700
  field: FieldName,
537
701
  ): IncludeScalar<number | null> {
@@ -540,6 +704,27 @@ export class Collection<
540
704
  return createIncludeScalar<number | null>('max', this.state, columnName);
541
705
  }
542
706
 
707
+ /**
708
+ * Produce multiple named sub-views of a to-many relation in a
709
+ * single `include(...)`. Each branch is either another refined
710
+ * collection (mapped to a row array on the parent) or a scalar
711
+ * reducer such as `count()`/`sum(...)`. Only valid inside an
712
+ * `include(...)` refinement callback for to-many relations.
713
+ *
714
+ * ```typescript
715
+ * const users = await db.orm.User.include('posts', (posts) =>
716
+ * posts.combine({
717
+ * recent: posts.where({ published: true }).take(3),
718
+ * total: posts.count(),
719
+ * averageViews: posts.avg('views'),
720
+ * }),
721
+ * ).all();
722
+ * // each user row: {
723
+ * // ...user,
724
+ * // posts: { recent: Post[]; total: number; averageViews: number | null };
725
+ * // }
726
+ * ```
727
+ */
543
728
  combine<
544
729
  Spec extends Record<
545
730
  string,
@@ -586,6 +771,26 @@ export class Collection<
586
771
  }>;
587
772
  }
588
773
 
774
+ /**
775
+ * Resume pagination from a known cursor position. Requires a prior
776
+ * `orderBy(...)` so the cursor has a stable basis; provide a value
777
+ * for every column referenced by the active `orderBy(...)` so each
778
+ * ordered axis has a defined boundary.
779
+ *
780
+ * ```typescript
781
+ * const page1 = await db.orm.Post
782
+ * .orderBy((p) => p.createdAt.desc())
783
+ * .take(20)
784
+ * .all();
785
+ *
786
+ * const last = page1[page1.length - 1]!;
787
+ * const page2 = await db.orm.Post
788
+ * .orderBy((p) => p.createdAt.desc())
789
+ * .cursor({ createdAt: last.createdAt })
790
+ * .take(20)
791
+ * .all();
792
+ * ```
793
+ */
589
794
  cursor(
590
795
  cursorValues: State['hasOrderBy'] extends true
591
796
  ? Partial<Record<keyof DefaultModelRow<TContract, ModelName> & string, unknown>>
@@ -606,6 +811,14 @@ export class Collection<
606
811
  });
607
812
  }
608
813
 
814
+ /**
815
+ * Emit `SELECT DISTINCT` keyed on the given fields. Replaces any
816
+ * previous `distinct(...)` / `distinctOn(...)` selection.
817
+ *
818
+ * ```typescript
819
+ * const groups = await db.orm.User.distinct('country', 'role').all();
820
+ * ```
821
+ */
609
822
  distinct<
610
823
  Fields extends readonly [
611
824
  keyof DefaultModelRow<TContract, ModelName> & string,
@@ -620,6 +833,20 @@ export class Collection<
620
833
  });
621
834
  }
622
835
 
836
+ /**
837
+ * Emit `SELECT DISTINCT ON (fields)` — keep the first row per
838
+ * distinct key according to the current `orderBy(...)`. Requires a
839
+ * prior `orderBy(...)`; replaces any previous `distinct(...)` /
840
+ * `distinctOn(...)` selection.
841
+ *
842
+ * ```typescript
843
+ * // Latest post per user:
844
+ * const latestPerUser = await db.orm.Post
845
+ * .orderBy([(p) => p.userId.asc(), (p) => p.createdAt.desc()])
846
+ * .distinctOn('userId')
847
+ * .all();
848
+ * ```
849
+ */
623
850
  distinctOn<
624
851
  Fields extends readonly [
625
852
  keyof DefaultModelRow<TContract, ModelName> & string,
@@ -640,27 +867,101 @@ export class Collection<
640
867
  });
641
868
  }
642
869
 
870
+ /**
871
+ * Apply `LIMIT n`. Replaces any previous limit set on this collection.
872
+ *
873
+ * ```typescript
874
+ * const firstTen = await db.orm.User.orderBy((u) => u.id.asc()).take(10).all();
875
+ * ```
876
+ */
643
877
  take(n: number): Collection<TContract, ModelName, Row, State> {
644
878
  return this.#clone({ limit: n });
645
879
  }
646
880
 
881
+ /**
882
+ * Apply `OFFSET n`. Replaces any previous offset set on this collection.
883
+ *
884
+ * ```typescript
885
+ * const page2 = await db.orm.User
886
+ * .orderBy((u) => u.id.asc())
887
+ * .skip(10)
888
+ * .take(10)
889
+ * .all();
890
+ * ```
891
+ */
647
892
  skip(n: number): Collection<TContract, ModelName, Row, State> {
648
893
  return this.#clone({ offset: n });
649
894
  }
650
895
 
651
896
  /**
652
- * Read terminal: stream all rows matching the current state.
897
+ * Read terminal: execute the query and stream every matching row.
898
+ *
899
+ * The returned `AsyncIterableResult<Row>` is BOTH a thenable that
900
+ * resolves to `Row[]` (so `await` collects all rows into an array)
901
+ * AND an async iterable (so `for await` streams rows as they
902
+ * arrive, without buffering the whole result set in memory). Pick
903
+ * whichever fits the caller. A single result can only be consumed
904
+ * once.
905
+ *
906
+ * Streaming is the default and the expected execution model. The
907
+ * only scenarios that fall back to buffering internally before
908
+ * yielding are drivers that cannot expose a cursor to the
909
+ * underlying database, and — for queries with `include(...)` —
910
+ * targets whose SQL dialect supports neither lateral joins nor
911
+ * correlated subqueries (so child rows cannot be stitched in a
912
+ * single streaming query). These are implementation details below
913
+ * the public API; the iteration shape itself is genuinely
914
+ * streaming whenever the driver and plan allow it.
915
+ *
916
+ * ```typescript
917
+ * // Thenable — collect to an array:
918
+ * const users = await db.orm.User.all();
919
+ * for (const user of users) console.log(user.id);
920
+ *
921
+ * // Async iterable — stream rows as they arrive:
922
+ * for await (const user of db.orm.User.all()) {
923
+ * console.log(user.id);
924
+ * }
925
+ * ```
653
926
  *
654
927
  * Accepts an optional `configure` callback that receives a
655
928
  * `MetaBuilder<'read'>` so the caller can attach typed user
656
929
  * annotations to the executed plan. `meta.annotate(...)` enforces
657
930
  * applicability at the type level and at runtime; annotations are
658
931
  * merged into `plan.meta.annotations` at compile time.
932
+ *
933
+ * ```typescript
934
+ * await db.orm.User.all((meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
935
+ * ```
659
936
  */
660
937
  all(configure?: (meta: MetaBuilder<'read'>) => void): AsyncIterableResult<Row> {
661
938
  return this.#withAnnotationsFromMeta(configure, 'all').#dispatch();
662
939
  }
663
940
 
941
+ /**
942
+ * Read terminal: return the first matching row, or `null` if none
943
+ * match. Optionally accepts a filter (callback or shorthand object)
944
+ * followed by a `configure` callback for typed read annotations.
945
+ *
946
+ * To attach annotations without further narrowing, pass `undefined`
947
+ * as the filter (or chain `.where(...)` first):
948
+ *
949
+ * ```typescript
950
+ * // No filter — first row in the collection:
951
+ * const someone = await db.orm.User.first();
952
+ *
953
+ * // Shorthand filter:
954
+ * const alice = await db.orm.User.first({ email: 'alice@example.com' });
955
+ *
956
+ * // Callback filter:
957
+ * const old = await db.orm.User.first((u) => u.age.gt(60));
958
+ *
959
+ * // Annotate without filtering further:
960
+ * await db.orm.User.first(undefined, (meta) =>
961
+ * meta.annotate(cacheAnnotation({ ttl: 60 })),
962
+ * );
963
+ * ```
964
+ */
664
965
  async first(): Promise<Row | null>;
665
966
  async first(
666
967
  filter: undefined,
@@ -674,20 +975,6 @@ export class Collection<
674
975
  filter: ShorthandWhereFilter<TContract, ModelName>,
675
976
  configure?: (meta: MetaBuilder<'read'>) => void,
676
977
  ): Promise<Row | null>;
677
- /**
678
- * Read terminal: return the first matching row, or `null`.
679
- *
680
- * Accepts an optional `filter` (function or shorthand) followed by an
681
- * optional `configure` callback that receives a `MetaBuilder<'read'>`
682
- * for attaching typed annotations. To attach annotations without
683
- * narrowing further, pass `undefined` as the filter (or chain
684
- * `.where(...)` first):
685
- *
686
- * ```typescript
687
- * await db.User.first({ id }, (meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
688
- * await db.User.first(undefined, (meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
689
- * ```
690
- */
691
978
  async first(
692
979
  filter?:
693
980
  | ((model: ModelAccessor<TContract, ModelName>) => WhereArg)
@@ -707,7 +994,20 @@ export class Collection<
707
994
 
708
995
  /**
709
996
  * Read terminal: run an aggregate query (count, sum, avg, min, max)
710
- * built via the `AggregateBuilder` callback.
997
+ * built via the `AggregateBuilder` callback. Returns one object
998
+ * with the requested aggregate values keyed by the aliases supplied
999
+ * in the spec.
1000
+ *
1001
+ * ```typescript
1002
+ * const stats = await db.orm.Post
1003
+ * .where({ published: true })
1004
+ * .aggregate((agg) => ({
1005
+ * total: agg.count(),
1006
+ * averageViews: agg.avg('views'),
1007
+ * maxViews: agg.max('views'),
1008
+ * }));
1009
+ * // { total: 42, averageViews: 17.3, maxViews: 9001 }
1010
+ * ```
711
1011
  *
712
1012
  * Accepts an optional `configure` callback that receives a
713
1013
  * `MetaBuilder<'read'>` for attaching typed annotations.
@@ -742,16 +1042,40 @@ export class Collection<
742
1042
  return normalizeAggregateResult(aggregateSpec, rows[0] ?? {});
743
1043
  }
744
1044
 
745
- async create(
746
- data: ResolvedCreateInput<TContract, ModelName, State['variantName']>,
747
- configure?: (meta: MetaBuilder<'write'>) => void,
748
- ): Promise<Row>;
749
- async create(
750
- data: MutationCreateInputWithRelations<TContract, ModelName>,
751
- configure?: (meta: MetaBuilder<'write'>) => void,
752
- ): Promise<Row>;
753
1045
  /**
754
- * Write terminal: insert one row and return it.
1046
+ * Write terminal: insert one row and return it (with any configured
1047
+ * `select(...)` / `include(...)` projections applied to the returned
1048
+ * shape).
1049
+ *
1050
+ * Related rows can be created or linked through relation callbacks
1051
+ * on parent/child-owned relations (one-to-one or one-to-many).
1052
+ * The callback receives a mutator exposing `create(...)` and
1053
+ * `connect(...)`; `disconnect(...)` is only supported in nested
1054
+ * `update(...)` mutations. Many-to-many relations are not yet
1055
+ * supported as nested-mutation targets.
1056
+ *
1057
+ * ```typescript
1058
+ * // Simple insert:
1059
+ * const user = await db.orm.User.create({
1060
+ * email: 'alice@example.com',
1061
+ * name: 'Alice',
1062
+ * });
1063
+ *
1064
+ * // Nested create on a child-owned to-many relation:
1065
+ * const author = await db.orm.User.create({
1066
+ * email: 'bob@example.com',
1067
+ * posts: (posts) => posts.create([
1068
+ * { title: 'Hello' },
1069
+ * { title: 'World' },
1070
+ * ]),
1071
+ * });
1072
+ *
1073
+ * // Connect a child-owned post to an existing parent author:
1074
+ * const reply = await db.orm.Post.create({
1075
+ * title: 'Re: Hello',
1076
+ * author: (author) => author.connect({ id: 1 }),
1077
+ * });
1078
+ * ```
755
1079
  *
756
1080
  * Accepts an optional `configure` callback that receives a
757
1081
  * `MetaBuilder<'write'>` for attaching typed annotations.
@@ -764,6 +1088,14 @@ export class Collection<
764
1088
  * logical `create()` call but do not currently flow into each
765
1089
  * constituent SQL statement issued for the related rows.
766
1090
  */
1091
+ async create(
1092
+ data: ResolvedCreateInput<TContract, ModelName, State['variantName']>,
1093
+ configure?: (meta: MetaBuilder<'write'>) => void,
1094
+ ): Promise<Row>;
1095
+ async create(
1096
+ data: MutationCreateInputWithRelations<TContract, ModelName>,
1097
+ configure?: (meta: MetaBuilder<'write'>) => void,
1098
+ ): Promise<Row>;
767
1099
  async create(
768
1100
  data:
769
1101
  | ResolvedCreateInput<TContract, ModelName, State['variantName']>
@@ -803,6 +1135,33 @@ export class Collection<
803
1135
  throw new Error(`create() for model "${this.modelName}" did not return a row`);
804
1136
  }
805
1137
 
1138
+ /**
1139
+ * Write terminal: insert many rows and stream the inserted rows.
1140
+ *
1141
+ * The returned `AsyncIterableResult<Row>` is BOTH a thenable that
1142
+ * resolves to `Row[]` AND an async iterable that streams inserted
1143
+ * rows as they arrive. Use whichever shape fits the caller — but
1144
+ * only consume it once. Streaming is the default; some
1145
+ * driver/plan combinations may still buffer internally before
1146
+ * yielding.
1147
+ *
1148
+ * ```typescript
1149
+ * // Thenable — collect all inserted rows into an array:
1150
+ * const created = await db.orm.User.createAll([
1151
+ * { email: 'a@example.com' },
1152
+ * { email: 'b@example.com' },
1153
+ * ]);
1154
+ *
1155
+ * // Async iterable — stream inserted rows as they arrive:
1156
+ * for await (const row of db.orm.User.createAll(seedUsers)) {
1157
+ * console.log('inserted', row.id);
1158
+ * }
1159
+ * ```
1160
+ *
1161
+ * Accepts an optional `configure` callback that receives a
1162
+ * `MetaBuilder<'write'>` for attaching typed annotations to the
1163
+ * compiled insert plan.
1164
+ */
806
1165
  createAll(
807
1166
  data: readonly ResolvedCreateInput<TContract, ModelName, State['variantName']>[],
808
1167
  configure?: (meta: MetaBuilder<'write'>) => void,
@@ -1024,6 +1383,24 @@ export class Collection<
1024
1383
  });
1025
1384
  }
1026
1385
 
1386
+ /**
1387
+ * Write terminal: insert many rows without materializing the
1388
+ * inserted rows, returning the number of inserted records.
1389
+ *
1390
+ * Prefer `createAll(...)` when you need the returned rows; prefer
1391
+ * this when you only need to know how many rows were inserted (the
1392
+ * compiled plan skips `RETURNING`).
1393
+ *
1394
+ * ```typescript
1395
+ * const inserted = await db.orm.User.createCount([
1396
+ * { email: 'a@example.com' },
1397
+ * { email: 'b@example.com' },
1398
+ * ]);
1399
+ * // inserted === 2
1400
+ * ```
1401
+ *
1402
+ * Not supported on MTI variants — use `createAll(...)` instead.
1403
+ */
1027
1404
  async createCount(
1028
1405
  data: readonly ResolvedCreateInput<TContract, ModelName, State['variantName']>[],
1029
1406
  configure?: (meta: MetaBuilder<'write'>) => void,
@@ -1058,9 +1435,36 @@ export class Collection<
1058
1435
  }
1059
1436
 
1060
1437
  /**
1061
- * Passing `update: {}` makes this behave like a conditional create.
1062
- * On conflict, `ON CONFLICT DO NOTHING RETURNING ...` may return zero rows,
1063
- * so this method may issue a follow-up reload query to return the existing row.
1438
+ * Write terminal: insert a row, or update the existing row on
1439
+ * conflict. Returns the resulting row (the inserted one or the
1440
+ * updated/existing one).
1441
+ *
1442
+ * `conflictOn` selects which unique constraint drives the conflict
1443
+ * resolution — omit to use the model's primary key.
1444
+ *
1445
+ * ```typescript
1446
+ * // Insert-or-update on email uniqueness:
1447
+ * await db.orm.User.upsert({
1448
+ * create: { email: 'alice@example.com', name: 'Alice' },
1449
+ * update: { name: 'Alice (updated)' },
1450
+ * conflictOn: { email: 'alice@example.com' },
1451
+ * });
1452
+ *
1453
+ * // Conditional create — `update: {}` keeps the existing row
1454
+ * // unchanged. `conflictOn` must reference the constraint that
1455
+ * // makes the row "already exist"; omit only when the conflict is
1456
+ * // on the primary key. On conflict,
1457
+ * // `ON CONFLICT DO NOTHING RETURNING ...` may return zero rows,
1458
+ * // so a follow-up reload is issued to fetch and return the
1459
+ * // existing row.
1460
+ * await db.orm.User.upsert({
1461
+ * create: { email: 'alice@example.com', name: 'Alice' },
1462
+ * update: {},
1463
+ * conflictOn: { email: 'alice@example.com' },
1464
+ * });
1465
+ * ```
1466
+ *
1467
+ * Not supported on MTI variants.
1064
1468
  */
1065
1469
  async upsert(
1066
1470
  input: {
@@ -1137,7 +1541,30 @@ export class Collection<
1137
1541
 
1138
1542
  /**
1139
1543
  * Write terminal: update matching rows and return the first one (or
1140
- * null when no row matched).
1544
+ * `null` when no row matched). Requires a prior `.where(...)` —
1545
+ * calling `update(...)` on an unfiltered collection is a type error.
1546
+ *
1547
+ * Related rows can be created or relinked through relation
1548
+ * callbacks on parent/child-owned relations (one-to-one or
1549
+ * one-to-many). The callback receives a mutator exposing
1550
+ * `create(...)`, `connect(...)`, and `disconnect(...)`. Nested
1551
+ * updates against existing related rows, and many-to-many relations
1552
+ * as nested-mutation targets, are not supported through this API.
1553
+ *
1554
+ * ```typescript
1555
+ * // Update one row by id:
1556
+ * const updated = await db.orm.User
1557
+ * .where({ id: 1 })
1558
+ * .update({ name: 'Alice Renamed' });
1559
+ *
1560
+ * // Update + relink — runs as a graph of internal mutations:
1561
+ * await db.orm.User
1562
+ * .where({ id: 1 })
1563
+ * .update({
1564
+ * name: 'Alice',
1565
+ * posts: (posts) => posts.connect([{ id: 5 }]),
1566
+ * });
1567
+ * ```
1141
1568
  *
1142
1569
  * Accepts an optional `configure` callback that receives a
1143
1570
  * `MetaBuilder<'write'>` for attaching typed annotations.
@@ -1190,6 +1617,31 @@ export class Collection<
1190
1617
  });
1191
1618
  }
1192
1619
 
1620
+ /**
1621
+ * Write terminal: update every matching row and stream the updated
1622
+ * rows. Requires a prior `.where(...)` filter.
1623
+ *
1624
+ * The returned `AsyncIterableResult<Row>` is BOTH a thenable that
1625
+ * resolves to `Row[]` AND an async iterable that streams updated
1626
+ * rows as they arrive. Use whichever fits; a result can only be
1627
+ * consumed once. Streaming is the default; some driver/plan
1628
+ * combinations may still buffer internally before yielding.
1629
+ *
1630
+ * ```typescript
1631
+ * // Thenable — collect updated rows into an array:
1632
+ * const updated = await db.orm.Post
1633
+ * .where({ published: false })
1634
+ * .updateAll({ published: true });
1635
+ *
1636
+ * // Async iterable — stream updated rows as they arrive:
1637
+ * for await (const row of db.orm.Post.where({ draft: true }).updateAll({ draft: false })) {
1638
+ * console.log('published', row.id);
1639
+ * }
1640
+ * ```
1641
+ *
1642
+ * Accepts an optional `configure` callback that receives a
1643
+ * `MetaBuilder<'write'>` for attaching typed annotations.
1644
+ */
1193
1645
  updateAll(
1194
1646
  data: State['hasWhere'] extends true ? Partial<DefaultModelRow<TContract, ModelName>> : never,
1195
1647
  configure?: (meta: MetaBuilder<'write'>) => void,
@@ -1240,6 +1692,20 @@ export class Collection<
1240
1692
  });
1241
1693
  }
1242
1694
 
1695
+ /**
1696
+ * Write terminal: update every matching row without returning them,
1697
+ * resolving to the count of rows that were updated. Requires a prior
1698
+ * `.where(...)` filter.
1699
+ *
1700
+ * Prefer `updateAll(...)` when you need the updated rows; prefer
1701
+ * this when you only need the affected-row count.
1702
+ *
1703
+ * ```typescript
1704
+ * const count = await db.orm.Post
1705
+ * .where({ published: false })
1706
+ * .updateCount({ published: true });
1707
+ * ```
1708
+ */
1243
1709
  async updateCount(
1244
1710
  data: State['hasWhere'] extends true ? Partial<DefaultModelRow<TContract, ModelName>> : never,
1245
1711
  configure?: (meta: MetaBuilder<'write'>) => void,
@@ -1276,8 +1742,14 @@ export class Collection<
1276
1742
  }
1277
1743
 
1278
1744
  /**
1279
- * Write terminal: delete matching rows and return the first one (or
1280
- * null when no row matched).
1745
+ * Write terminal: delete matching rows and return the first deleted
1746
+ * row (or `null` when no row matched). Requires a prior `.where(...)`
1747
+ * — calling `delete()` on an unfiltered collection is a type error.
1748
+ *
1749
+ * ```typescript
1750
+ * const deleted = await db.orm.User.where({ id: 1 }).delete();
1751
+ * if (deleted) console.log('deleted', deleted.email);
1752
+ * ```
1281
1753
  *
1282
1754
  * Accepts an optional `configure` callback that receives a
1283
1755
  * `MetaBuilder<'write'>` for attaching typed annotations.
@@ -1300,6 +1772,29 @@ export class Collection<
1300
1772
  });
1301
1773
  }
1302
1774
 
1775
+ /**
1776
+ * Write terminal: delete every matching row and stream the deleted
1777
+ * rows. Requires a prior `.where(...)` filter.
1778
+ *
1779
+ * The returned `AsyncIterableResult<Row>` is BOTH a thenable that
1780
+ * resolves to `Row[]` AND an async iterable that streams deleted
1781
+ * rows as they arrive. Use whichever fits; a result can only be
1782
+ * consumed once. Streaming is the default; some driver/plan
1783
+ * combinations may still buffer internally before yielding.
1784
+ *
1785
+ * ```typescript
1786
+ * // Thenable — collect the deleted rows into an array:
1787
+ * const deleted = await db.orm.Post.where({ archived: true }).deleteAll();
1788
+ *
1789
+ * // Async iterable — stream deleted rows as they arrive:
1790
+ * for await (const row of db.orm.Post.where({ archived: true }).deleteAll()) {
1791
+ * console.log('removed', row.id);
1792
+ * }
1793
+ * ```
1794
+ *
1795
+ * Accepts an optional `configure` callback that receives a
1796
+ * `MetaBuilder<'write'>` for attaching typed annotations.
1797
+ */
1303
1798
  deleteAll(
1304
1799
  this: State['hasWhere'] extends true ? Collection<TContract, ModelName, Row, State> : never,
1305
1800
  configure?: (meta: MetaBuilder<'write'>) => void,
@@ -1339,6 +1834,18 @@ export class Collection<
1339
1834
  });
1340
1835
  }
1341
1836
 
1837
+ /**
1838
+ * Write terminal: delete every matching row without returning them,
1839
+ * resolving to the count of rows that were deleted. Requires a prior
1840
+ * `.where(...)` filter.
1841
+ *
1842
+ * Prefer `deleteAll(...)` when you need the deleted rows; prefer
1843
+ * this when you only need the affected-row count.
1844
+ *
1845
+ * ```typescript
1846
+ * const removed = await db.orm.Post.where({ archived: true }).deleteCount();
1847
+ * ```
1848
+ */
1342
1849
  async deleteCount(
1343
1850
  this: State['hasWhere'] extends true ? Collection<TContract, ModelName, Row, State> : never,
1344
1851
  configure?: (meta: MetaBuilder<'write'>) => void,