@prisma-next/sql-orm-client 0.9.0-dev.3 → 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/dist/index.d.mts +543 -8
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +454 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -20
- package/src/collection.ts +538 -31
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:
|
|
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
|
-
*
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
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
|
|
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,
|