@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/dist/index.mjs CHANGED
@@ -441,7 +441,7 @@ function capabilityFlag(contract, flag) {
441
441
  //#endregion
442
442
  //#region src/query-plan-meta.ts
443
443
  function deriveParamsFromAst(ast) {
444
- return { params: collectOrderedParamRefs(ast).map((p) => p.value) };
444
+ return { params: collectOrderedParamRefs(ast).map((p) => p.kind === "param-ref" ? p.value : void 0) };
445
445
  }
446
446
  function resolveTableColumns(contract, tableName) {
447
447
  const table = contract.storage.tables[tableName];
@@ -557,6 +557,9 @@ function validateGroupedHavingExpr(expr) {
557
557
  param() {
558
558
  throw new Error("ParamRef is not supported in grouped having expressions");
559
559
  },
560
+ preparedParam() {
561
+ throw new Error("PreparedParamRef is not supported in grouped having expressions");
562
+ },
560
563
  list: rejectHavingExpr,
561
564
  and(expr) {
562
565
  return AndExpr.of(expr.exprs.map((child) => validateGroupedHavingExpr(child)));
@@ -767,6 +770,9 @@ function bindWhereExprNode(contract, expr) {
767
770
  param(expr) {
768
771
  return expr;
769
772
  },
773
+ preparedParam(expr) {
774
+ return expr;
775
+ },
770
776
  list(expr) {
771
777
  return bindExpression(contract, expr);
772
778
  },
@@ -2218,6 +2224,25 @@ var Collection = class Collection {
2218
2224
  if (!filter) return this;
2219
2225
  return this.#clone({ filters: [...this.state.filters, filter] });
2220
2226
  }
2227
+ /**
2228
+ * Narrow a polymorphic model to a specific variant. The returned
2229
+ * collection has the variant's row shape and a discriminator filter
2230
+ * is automatically applied. Chaining `.variant(...)` again replaces
2231
+ * the previous variant filter.
2232
+ *
2233
+ * ```typescript
2234
+ * // Read only admin users (STI):
2235
+ * const admins = await db.orm.User.variant('Admin').all();
2236
+ *
2237
+ * // Iterate the rows:
2238
+ * for await (const admin of db.orm.User.variant('Admin').all()) {
2239
+ * console.log(admin.role);
2240
+ * }
2241
+ *
2242
+ * // Insert under a variant — discriminator is injected automatically:
2243
+ * await db.orm.User.variant('Admin').create({ name: 'Ada', role: 'super' });
2244
+ * ```
2245
+ */
2221
2246
  variant(variantName) {
2222
2247
  const model = this.contract.models[this.modelName];
2223
2248
  const discriminator = model?.["discriminator"];
@@ -2233,6 +2258,34 @@ var Collection = class Collection {
2233
2258
  variantName
2234
2259
  });
2235
2260
  }
2261
+ /**
2262
+ * Eagerly load a related model. The relation appears on every
2263
+ * returned row under its declared name; to-one relations are mapped
2264
+ * to a single object (or `null`), to-many relations to an array.
2265
+ *
2266
+ * An optional refinement callback receives a child collection that
2267
+ * can be further constrained, projected, ordered, paginated, or
2268
+ * reduced to scalars via `count()`/`sum()`/etc. or to multiple
2269
+ * sub-aggregates via `combine()`.
2270
+ *
2271
+ * ```typescript
2272
+ * // Simple include — every user comes back with its posts array:
2273
+ * const users = await db.orm.User.include('posts').all();
2274
+ *
2275
+ * // Refine the related collection:
2276
+ * const withRecent = await db.orm.User.include('posts', (posts) =>
2277
+ * posts.where({ published: true }).orderBy((p) => p.createdAt.desc()).take(5),
2278
+ * ).all();
2279
+ *
2280
+ * // Reduce a to-many relation to a scalar value:
2281
+ * const withCounts = await db.orm.User.include('posts', (posts) => posts.count()).all();
2282
+ *
2283
+ * // Multiple sub-views via combine():
2284
+ * const overview = await db.orm.User.include('posts', (posts) =>
2285
+ * posts.combine({ recent: posts.take(3), total: posts.count() }),
2286
+ * ).all();
2287
+ * ```
2288
+ */
2236
2289
  include(relationName, refineFn) {
2237
2290
  const relation = resolveIncludeRelation(this.contract, this.modelName, relationName);
2238
2291
  let nestedState = emptyState();
@@ -2267,16 +2320,60 @@ var Collection = class Collection {
2267
2320
  };
2268
2321
  return this.#cloneWithRow({ includes: [...this.state.includes, includeExpr] });
2269
2322
  }
2323
+ /**
2324
+ * Project the row down to a subset of scalar fields. Previously
2325
+ * included relations are preserved on the resulting row shape; only
2326
+ * scalar columns are narrowed.
2327
+ *
2328
+ * ```typescript
2329
+ * const summaries = await db.orm.User.select('id', 'email').all();
2330
+ * // typeof summaries[number] === { id: ...; email: ... }
2331
+ *
2332
+ * for await (const row of db.orm.User.select('id', 'email').all()) {
2333
+ * console.log(row.id, row.email);
2334
+ * }
2335
+ * ```
2336
+ */
2270
2337
  select(...fields) {
2271
2338
  const selectedFields = mapFieldsToColumns(this.contract, this.modelName, fields);
2272
2339
  return this.#cloneWithRow({ selectedFields });
2273
2340
  }
2341
+ /**
2342
+ * Append an `ORDER BY` clause. Pass a single selector callback or an
2343
+ * array of callbacks; each receives a typed model accessor whose
2344
+ * columns expose `.asc()` and `.desc()`. Multiple calls append to the
2345
+ * existing list (left-to-right ordering preserved).
2346
+ *
2347
+ * Calling `orderBy(...)` unlocks `cursor(...)` and `distinctOn(...)`,
2348
+ * which both require a defined sort order.
2349
+ *
2350
+ * ```typescript
2351
+ * const newest = await db.orm.User.orderBy((u) => u.createdAt.desc()).all();
2352
+ *
2353
+ * const byName = await db.orm.User
2354
+ * .orderBy([(u) => u.lastName.asc(), (u) => u.firstName.asc()])
2355
+ * .all();
2356
+ * ```
2357
+ */
2274
2358
  orderBy(selection) {
2275
2359
  const accessor = createModelAccessor(this.ctx.context, this.modelName);
2276
2360
  const nextOrders = (Array.isArray(selection) ? selection : [selection]).map((selector) => selector(accessor));
2277
2361
  const existing = this.state.orderBy ?? [];
2278
2362
  return this.#clone({ orderBy: [...existing, ...nextOrders] });
2279
2363
  }
2364
+ /**
2365
+ * Switch to grouped-aggregate mode. Returns a `GroupedCollection`
2366
+ * whose `.aggregate(...)` terminal produces one row per group with
2367
+ * the chosen key columns plus the requested aggregates.
2368
+ *
2369
+ * ```typescript
2370
+ * const stats = await db.orm.Post
2371
+ * .where({ published: true })
2372
+ * .groupBy('userId')
2373
+ * .aggregate((agg) => ({ count: agg.count(), totalViews: agg.sum('views') }));
2374
+ * // [{ userId: 1, count: 3, totalViews: 120 }, ...]
2375
+ * ```
2376
+ */
2280
2377
  groupBy(...fields) {
2281
2378
  const groupByColumns = mapFieldsToColumns(this.contract, this.modelName, fields);
2282
2379
  return new GroupedCollection(this.ctx, this.modelName, {
@@ -2287,30 +2384,105 @@ var Collection = class Collection {
2287
2384
  havingFilters: []
2288
2385
  });
2289
2386
  }
2387
+ /**
2388
+ * Scalar reducer — reduces a to-many relation to the number of
2389
+ * related rows. Use inside an `include(...)` refinement callback as
2390
+ * `include(..., (rel) => rel.count())`; throws if called elsewhere.
2391
+ * The parent row's relation field becomes that count instead of an
2392
+ * array.
2393
+ *
2394
+ * ```typescript
2395
+ * const users = await db.orm.User.include('posts', (posts) => posts.count()).all();
2396
+ * // each user row: { ...user, posts: number }
2397
+ * ```
2398
+ */
2290
2399
  count() {
2291
2400
  this.#assertIncludeRefinementMode("count()");
2292
2401
  return createIncludeScalar("count", this.state);
2293
2402
  }
2403
+ /**
2404
+ * Scalar reducer — reduces a to-many relation to the sum of `field`
2405
+ * across related rows. Returns `null` when there are no related
2406
+ * rows. Use inside an `include(...)` refinement callback; throws if
2407
+ * called elsewhere.
2408
+ *
2409
+ * ```typescript
2410
+ * const users = await db.orm.User.include('posts', (posts) => posts.sum('views')).all();
2411
+ * // each user row: { ...user, posts: number | null }
2412
+ * ```
2413
+ */
2294
2414
  sum(field) {
2295
2415
  this.#assertIncludeRefinementMode("sum()");
2296
2416
  const columnName = resolveFieldToColumn(this.contract, this.modelName, field);
2297
2417
  return createIncludeScalar("sum", this.state, columnName);
2298
2418
  }
2419
+ /**
2420
+ * Scalar reducer — reduces a to-many relation to the average of
2421
+ * `field` across related rows. Returns `null` when there are no
2422
+ * related rows. Use inside an `include(...)` refinement callback;
2423
+ * throws if called elsewhere.
2424
+ *
2425
+ * ```typescript
2426
+ * const users = await db.orm.User.include('posts', (posts) => posts.avg('views')).all();
2427
+ * // each user row: { ...user, posts: number | null }
2428
+ * ```
2429
+ */
2299
2430
  avg(field) {
2300
2431
  this.#assertIncludeRefinementMode("avg()");
2301
2432
  const columnName = resolveFieldToColumn(this.contract, this.modelName, field);
2302
2433
  return createIncludeScalar("avg", this.state, columnName);
2303
2434
  }
2435
+ /**
2436
+ * Scalar reducer — reduces a to-many relation to the minimum value
2437
+ * of `field` across related rows. Returns `null` when there are no
2438
+ * related rows. Use inside an `include(...)` refinement callback;
2439
+ * throws if called elsewhere.
2440
+ *
2441
+ * ```typescript
2442
+ * const users = await db.orm.User.include('posts', (posts) => posts.min('views')).all();
2443
+ * ```
2444
+ */
2304
2445
  min(field) {
2305
2446
  this.#assertIncludeRefinementMode("min()");
2306
2447
  const columnName = resolveFieldToColumn(this.contract, this.modelName, field);
2307
2448
  return createIncludeScalar("min", this.state, columnName);
2308
2449
  }
2450
+ /**
2451
+ * Scalar reducer — reduces a to-many relation to the maximum value
2452
+ * of `field` across related rows. Returns `null` when there are no
2453
+ * related rows. Use inside an `include(...)` refinement callback;
2454
+ * throws if called elsewhere.
2455
+ *
2456
+ * ```typescript
2457
+ * const users = await db.orm.User.include('posts', (posts) => posts.max('views')).all();
2458
+ * ```
2459
+ */
2309
2460
  max(field) {
2310
2461
  this.#assertIncludeRefinementMode("max()");
2311
2462
  const columnName = resolveFieldToColumn(this.contract, this.modelName, field);
2312
2463
  return createIncludeScalar("max", this.state, columnName);
2313
2464
  }
2465
+ /**
2466
+ * Produce multiple named sub-views of a to-many relation in a
2467
+ * single `include(...)`. Each branch is either another refined
2468
+ * collection (mapped to a row array on the parent) or a scalar
2469
+ * reducer such as `count()`/`sum(...)`. Only valid inside an
2470
+ * `include(...)` refinement callback for to-many relations.
2471
+ *
2472
+ * ```typescript
2473
+ * const users = await db.orm.User.include('posts', (posts) =>
2474
+ * posts.combine({
2475
+ * recent: posts.where({ published: true }).take(3),
2476
+ * total: posts.count(),
2477
+ * averageViews: posts.avg('views'),
2478
+ * }),
2479
+ * ).all();
2480
+ * // each user row: {
2481
+ * // ...user,
2482
+ * // posts: { recent: Post[]; total: number; averageViews: number | null };
2483
+ * // }
2484
+ * ```
2485
+ */
2314
2486
  combine(spec) {
2315
2487
  this.#assertIncludeRefinementMode("combine()");
2316
2488
  const branches = {};
@@ -2333,11 +2505,39 @@ var Collection = class Collection {
2333
2505
  }
2334
2506
  return createIncludeCombine(branches);
2335
2507
  }
2508
+ /**
2509
+ * Resume pagination from a known cursor position. Requires a prior
2510
+ * `orderBy(...)` so the cursor has a stable basis; provide a value
2511
+ * for every column referenced by the active `orderBy(...)` so each
2512
+ * ordered axis has a defined boundary.
2513
+ *
2514
+ * ```typescript
2515
+ * const page1 = await db.orm.Post
2516
+ * .orderBy((p) => p.createdAt.desc())
2517
+ * .take(20)
2518
+ * .all();
2519
+ *
2520
+ * const last = page1[page1.length - 1]!;
2521
+ * const page2 = await db.orm.Post
2522
+ * .orderBy((p) => p.createdAt.desc())
2523
+ * .cursor({ createdAt: last.createdAt })
2524
+ * .take(20)
2525
+ * .all();
2526
+ * ```
2527
+ */
2336
2528
  cursor(cursorValues) {
2337
2529
  const mappedCursor = mapCursorValuesToColumns(this.contract, this.modelName, cursorValues);
2338
2530
  if (Object.keys(mappedCursor).length === 0) return this;
2339
2531
  return this.#clone({ cursor: mappedCursor });
2340
2532
  }
2533
+ /**
2534
+ * Emit `SELECT DISTINCT` keyed on the given fields. Replaces any
2535
+ * previous `distinct(...)` / `distinctOn(...)` selection.
2536
+ *
2537
+ * ```typescript
2538
+ * const groups = await db.orm.User.distinct('country', 'role').all();
2539
+ * ```
2540
+ */
2341
2541
  distinct(...fields) {
2342
2542
  const distinctFields = mapFieldsToColumns(this.contract, this.modelName, fields);
2343
2543
  return this.#clone({
@@ -2345,6 +2545,20 @@ var Collection = class Collection {
2345
2545
  distinctOn: void 0
2346
2546
  });
2347
2547
  }
2548
+ /**
2549
+ * Emit `SELECT DISTINCT ON (fields)` — keep the first row per
2550
+ * distinct key according to the current `orderBy(...)`. Requires a
2551
+ * prior `orderBy(...)`; replaces any previous `distinct(...)` /
2552
+ * `distinctOn(...)` selection.
2553
+ *
2554
+ * ```typescript
2555
+ * // Latest post per user:
2556
+ * const latestPerUser = await db.orm.Post
2557
+ * .orderBy([(p) => p.userId.asc(), (p) => p.createdAt.desc()])
2558
+ * .distinctOn('userId')
2559
+ * .all();
2560
+ * ```
2561
+ */
2348
2562
  distinctOn(...fields) {
2349
2563
  const distinctOnFields = mapFieldsToColumns(this.contract, this.modelName, fields);
2350
2564
  return this.#clone({
@@ -2352,44 +2566,93 @@ var Collection = class Collection {
2352
2566
  distinctOn: distinctOnFields
2353
2567
  });
2354
2568
  }
2569
+ /**
2570
+ * Apply `LIMIT n`. Replaces any previous limit set on this collection.
2571
+ *
2572
+ * ```typescript
2573
+ * const firstTen = await db.orm.User.orderBy((u) => u.id.asc()).take(10).all();
2574
+ * ```
2575
+ */
2355
2576
  take(n) {
2356
2577
  return this.#clone({ limit: n });
2357
2578
  }
2579
+ /**
2580
+ * Apply `OFFSET n`. Replaces any previous offset set on this collection.
2581
+ *
2582
+ * ```typescript
2583
+ * const page2 = await db.orm.User
2584
+ * .orderBy((u) => u.id.asc())
2585
+ * .skip(10)
2586
+ * .take(10)
2587
+ * .all();
2588
+ * ```
2589
+ */
2358
2590
  skip(n) {
2359
2591
  return this.#clone({ offset: n });
2360
2592
  }
2361
2593
  /**
2362
- * Read terminal: stream all rows matching the current state.
2594
+ * Read terminal: execute the query and stream every matching row.
2595
+ *
2596
+ * The returned `AsyncIterableResult<Row>` is BOTH a thenable that
2597
+ * resolves to `Row[]` (so `await` collects all rows into an array)
2598
+ * AND an async iterable (so `for await` streams rows as they
2599
+ * arrive, without buffering the whole result set in memory). Pick
2600
+ * whichever fits the caller. A single result can only be consumed
2601
+ * once.
2602
+ *
2603
+ * Streaming is the default and the expected execution model. The
2604
+ * only scenarios that fall back to buffering internally before
2605
+ * yielding are drivers that cannot expose a cursor to the
2606
+ * underlying database, and — for queries with `include(...)` —
2607
+ * targets whose SQL dialect supports neither lateral joins nor
2608
+ * correlated subqueries (so child rows cannot be stitched in a
2609
+ * single streaming query). These are implementation details below
2610
+ * the public API; the iteration shape itself is genuinely
2611
+ * streaming whenever the driver and plan allow it.
2612
+ *
2613
+ * ```typescript
2614
+ * // Thenable — collect to an array:
2615
+ * const users = await db.orm.User.all();
2616
+ * for (const user of users) console.log(user.id);
2617
+ *
2618
+ * // Async iterable — stream rows as they arrive:
2619
+ * for await (const user of db.orm.User.all()) {
2620
+ * console.log(user.id);
2621
+ * }
2622
+ * ```
2363
2623
  *
2364
2624
  * Accepts an optional `configure` callback that receives a
2365
2625
  * `MetaBuilder<'read'>` so the caller can attach typed user
2366
2626
  * annotations to the executed plan. `meta.annotate(...)` enforces
2367
2627
  * applicability at the type level and at runtime; annotations are
2368
2628
  * merged into `plan.meta.annotations` at compile time.
2369
- */
2370
- all(configure) {
2371
- return this.#withAnnotationsFromMeta(configure, "all").#dispatch();
2372
- }
2373
- /**
2374
- * Read terminal: return the first matching row, or `null`.
2375
- *
2376
- * Accepts an optional `filter` (function or shorthand) followed by an
2377
- * optional `configure` callback that receives a `MetaBuilder<'read'>`
2378
- * for attaching typed annotations. To attach annotations without
2379
- * narrowing further, pass `undefined` as the filter (or chain
2380
- * `.where(...)` first):
2381
2629
  *
2382
2630
  * ```typescript
2383
- * await db.User.first({ id }, (meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
2384
- * await db.User.first(undefined, (meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
2631
+ * await db.orm.User.all((meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
2385
2632
  * ```
2386
2633
  */
2634
+ all(configure) {
2635
+ return this.#withAnnotationsFromMeta(configure, "all").#dispatch();
2636
+ }
2387
2637
  async first(filter, configure) {
2388
2638
  return (await (filter === void 0 ? this : typeof filter === "function" ? this.where(filter) : this.where(filter)).take(1).#withAnnotationsFromMeta(configure, "first").#dispatch().toArray())[0] ?? null;
2389
2639
  }
2390
2640
  /**
2391
2641
  * Read terminal: run an aggregate query (count, sum, avg, min, max)
2392
- * built via the `AggregateBuilder` callback.
2642
+ * built via the `AggregateBuilder` callback. Returns one object
2643
+ * with the requested aggregate values keyed by the aliases supplied
2644
+ * in the spec.
2645
+ *
2646
+ * ```typescript
2647
+ * const stats = await db.orm.Post
2648
+ * .where({ published: true })
2649
+ * .aggregate((agg) => ({
2650
+ * total: agg.count(),
2651
+ * averageViews: agg.avg('views'),
2652
+ * maxViews: agg.max('views'),
2653
+ * }));
2654
+ * // { total: 42, averageViews: 17.3, maxViews: 9001 }
2655
+ * ```
2393
2656
  *
2394
2657
  * Accepts an optional `configure` callback that receives a
2395
2658
  * `MetaBuilder<'read'>` for attaching typed annotations.
@@ -2404,20 +2667,6 @@ var Collection = class Collection {
2404
2667
  const compiled = mergeAnnotations(compileAggregate(this.contract, this.tableName, this.state.filters, aggregateSpec), annotationsMap);
2405
2668
  return normalizeAggregateResult(aggregateSpec, (await executeQueryPlan(this.ctx.runtime, compiled).toArray())[0] ?? {});
2406
2669
  }
2407
- /**
2408
- * Write terminal: insert one row and return it.
2409
- *
2410
- * Accepts an optional `configure` callback that receives a
2411
- * `MetaBuilder<'write'>` for attaching typed annotations.
2412
- * Annotations are merged into the compiled mutation plan's
2413
- * `meta.annotations`.
2414
- *
2415
- * Note: when the input contains nested-mutation callbacks, the
2416
- * operation is executed as a graph of internal queries via
2417
- * `withMutationScope`. In that path, annotations apply to the
2418
- * logical `create()` call but do not currently flow into each
2419
- * constituent SQL statement issued for the related rows.
2420
- */
2421
2670
  async create(data, configure) {
2422
2671
  assertReturningCapability(this.contract, "create()");
2423
2672
  const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "create");
@@ -2437,6 +2686,33 @@ var Collection = class Collection {
2437
2686
  if (created) return created;
2438
2687
  throw new Error(`create() for model "${this.modelName}" did not return a row`);
2439
2688
  }
2689
+ /**
2690
+ * Write terminal: insert many rows and stream the inserted rows.
2691
+ *
2692
+ * The returned `AsyncIterableResult<Row>` is BOTH a thenable that
2693
+ * resolves to `Row[]` AND an async iterable that streams inserted
2694
+ * rows as they arrive. Use whichever shape fits the caller — but
2695
+ * only consume it once. Streaming is the default; some
2696
+ * driver/plan combinations may still buffer internally before
2697
+ * yielding.
2698
+ *
2699
+ * ```typescript
2700
+ * // Thenable — collect all inserted rows into an array:
2701
+ * const created = await db.orm.User.createAll([
2702
+ * { email: 'a@example.com' },
2703
+ * { email: 'b@example.com' },
2704
+ * ]);
2705
+ *
2706
+ * // Async iterable — stream inserted rows as they arrive:
2707
+ * for await (const row of db.orm.User.createAll(seedUsers)) {
2708
+ * console.log('inserted', row.id);
2709
+ * }
2710
+ * ```
2711
+ *
2712
+ * Accepts an optional `configure` callback that receives a
2713
+ * `MetaBuilder<'write'>` for attaching typed annotations to the
2714
+ * compiled insert plan.
2715
+ */
2440
2716
  createAll(data, configure) {
2441
2717
  return this.#createAllWithAnnotations(data, this.#collectAnnotationsFromMeta(configure, "write", "createAll"));
2442
2718
  }
@@ -2568,6 +2844,24 @@ var Collection = class Collection {
2568
2844
  return mapped;
2569
2845
  });
2570
2846
  }
2847
+ /**
2848
+ * Write terminal: insert many rows without materializing the
2849
+ * inserted rows, returning the number of inserted records.
2850
+ *
2851
+ * Prefer `createAll(...)` when you need the returned rows; prefer
2852
+ * this when you only need to know how many rows were inserted (the
2853
+ * compiled plan skips `RETURNING`).
2854
+ *
2855
+ * ```typescript
2856
+ * const inserted = await db.orm.User.createCount([
2857
+ * { email: 'a@example.com' },
2858
+ * { email: 'b@example.com' },
2859
+ * ]);
2860
+ * // inserted === 2
2861
+ * ```
2862
+ *
2863
+ * Not supported on MTI variants — use `createAll(...)` instead.
2864
+ */
2571
2865
  async createCount(data, configure) {
2572
2866
  if (data.length === 0) return 0;
2573
2867
  this.#assertNotMtiVariant("createCount()");
@@ -2585,9 +2879,36 @@ var Collection = class Collection {
2585
2879
  return data.length;
2586
2880
  }
2587
2881
  /**
2588
- * Passing `update: {}` makes this behave like a conditional create.
2589
- * On conflict, `ON CONFLICT DO NOTHING RETURNING ...` may return zero rows,
2590
- * so this method may issue a follow-up reload query to return the existing row.
2882
+ * Write terminal: insert a row, or update the existing row on
2883
+ * conflict. Returns the resulting row (the inserted one or the
2884
+ * updated/existing one).
2885
+ *
2886
+ * `conflictOn` selects which unique constraint drives the conflict
2887
+ * resolution — omit to use the model's primary key.
2888
+ *
2889
+ * ```typescript
2890
+ * // Insert-or-update on email uniqueness:
2891
+ * await db.orm.User.upsert({
2892
+ * create: { email: 'alice@example.com', name: 'Alice' },
2893
+ * update: { name: 'Alice (updated)' },
2894
+ * conflictOn: { email: 'alice@example.com' },
2895
+ * });
2896
+ *
2897
+ * // Conditional create — `update: {}` keeps the existing row
2898
+ * // unchanged. `conflictOn` must reference the constraint that
2899
+ * // makes the row "already exist"; omit only when the conflict is
2900
+ * // on the primary key. On conflict,
2901
+ * // `ON CONFLICT DO NOTHING RETURNING ...` may return zero rows,
2902
+ * // so a follow-up reload is issued to fetch and return the
2903
+ * // existing row.
2904
+ * await db.orm.User.upsert({
2905
+ * create: { email: 'alice@example.com', name: 'Alice' },
2906
+ * update: {},
2907
+ * conflictOn: { email: 'alice@example.com' },
2908
+ * });
2909
+ * ```
2910
+ *
2911
+ * Not supported on MTI variants.
2591
2912
  */
2592
2913
  async upsert(input, configure) {
2593
2914
  assertReturningCapability(this.contract, "upsert()");
@@ -2623,7 +2944,30 @@ var Collection = class Collection {
2623
2944
  }
2624
2945
  /**
2625
2946
  * Write terminal: update matching rows and return the first one (or
2626
- * null when no row matched).
2947
+ * `null` when no row matched). Requires a prior `.where(...)` —
2948
+ * calling `update(...)` on an unfiltered collection is a type error.
2949
+ *
2950
+ * Related rows can be created or relinked through relation
2951
+ * callbacks on parent/child-owned relations (one-to-one or
2952
+ * one-to-many). The callback receives a mutator exposing
2953
+ * `create(...)`, `connect(...)`, and `disconnect(...)`. Nested
2954
+ * updates against existing related rows, and many-to-many relations
2955
+ * as nested-mutation targets, are not supported through this API.
2956
+ *
2957
+ * ```typescript
2958
+ * // Update one row by id:
2959
+ * const updated = await db.orm.User
2960
+ * .where({ id: 1 })
2961
+ * .update({ name: 'Alice Renamed' });
2962
+ *
2963
+ * // Update + relink — runs as a graph of internal mutations:
2964
+ * await db.orm.User
2965
+ * .where({ id: 1 })
2966
+ * .update({
2967
+ * name: 'Alice',
2968
+ * posts: (posts) => posts.connect([{ id: 5 }]),
2969
+ * });
2970
+ * ```
2627
2971
  *
2628
2972
  * Accepts an optional `configure` callback that receives a
2629
2973
  * `MetaBuilder<'write'>` for attaching typed annotations.
@@ -2656,6 +3000,31 @@ var Collection = class Collection {
2656
3000
  return (await scoped.#clone({ filters: [identityWhere] }).#updateAllWithAnnotations(data, annotationsMap))[0] ?? null;
2657
3001
  });
2658
3002
  }
3003
+ /**
3004
+ * Write terminal: update every matching row and stream the updated
3005
+ * rows. Requires a prior `.where(...)` filter.
3006
+ *
3007
+ * The returned `AsyncIterableResult<Row>` is BOTH a thenable that
3008
+ * resolves to `Row[]` AND an async iterable that streams updated
3009
+ * rows as they arrive. Use whichever fits; a result can only be
3010
+ * consumed once. Streaming is the default; some driver/plan
3011
+ * combinations may still buffer internally before yielding.
3012
+ *
3013
+ * ```typescript
3014
+ * // Thenable — collect updated rows into an array:
3015
+ * const updated = await db.orm.Post
3016
+ * .where({ published: false })
3017
+ * .updateAll({ published: true });
3018
+ *
3019
+ * // Async iterable — stream updated rows as they arrive:
3020
+ * for await (const row of db.orm.Post.where({ draft: true }).updateAll({ draft: false })) {
3021
+ * console.log('published', row.id);
3022
+ * }
3023
+ * ```
3024
+ *
3025
+ * Accepts an optional `configure` callback that receives a
3026
+ * `MetaBuilder<'write'>` for attaching typed annotations.
3027
+ */
2659
3028
  updateAll(data, configure) {
2660
3029
  return this.#updateAllWithAnnotations(data, this.#collectAnnotationsFromMeta(configure, "write", "updateAll"));
2661
3030
  }
@@ -2680,6 +3049,20 @@ var Collection = class Collection {
2680
3049
  mapRow: (mapped) => mapped
2681
3050
  });
2682
3051
  }
3052
+ /**
3053
+ * Write terminal: update every matching row without returning them,
3054
+ * resolving to the count of rows that were updated. Requires a prior
3055
+ * `.where(...)` filter.
3056
+ *
3057
+ * Prefer `updateAll(...)` when you need the updated rows; prefer
3058
+ * this when you only need the affected-row count.
3059
+ *
3060
+ * ```typescript
3061
+ * const count = await db.orm.Post
3062
+ * .where({ published: false })
3063
+ * .updateCount({ published: true });
3064
+ * ```
3065
+ */
2683
3066
  async updateCount(data, configure) {
2684
3067
  const mappedData = mapModelDataToStorageRow(this.contract, this.modelName, data);
2685
3068
  if (Object.keys(mappedData).length === 0) return 0;
@@ -2698,8 +3081,14 @@ var Collection = class Collection {
2698
3081
  return matchingRows.length;
2699
3082
  }
2700
3083
  /**
2701
- * Write terminal: delete matching rows and return the first one (or
2702
- * null when no row matched).
3084
+ * Write terminal: delete matching rows and return the first deleted
3085
+ * row (or `null` when no row matched). Requires a prior `.where(...)`
3086
+ * — calling `delete()` on an unfiltered collection is a type error.
3087
+ *
3088
+ * ```typescript
3089
+ * const deleted = await db.orm.User.where({ id: 1 }).delete();
3090
+ * if (deleted) console.log('deleted', deleted.email);
3091
+ * ```
2703
3092
  *
2704
3093
  * Accepts an optional `configure` callback that receives a
2705
3094
  * `MetaBuilder<'write'>` for attaching typed annotations.
@@ -2714,6 +3103,29 @@ var Collection = class Collection {
2714
3103
  return (await scoped.#clone({ filters: [identityWhere] }).#executeDeleteReturning(annotationsMap).toArray())[0] ?? null;
2715
3104
  });
2716
3105
  }
3106
+ /**
3107
+ * Write terminal: delete every matching row and stream the deleted
3108
+ * rows. Requires a prior `.where(...)` filter.
3109
+ *
3110
+ * The returned `AsyncIterableResult<Row>` is BOTH a thenable that
3111
+ * resolves to `Row[]` AND an async iterable that streams deleted
3112
+ * rows as they arrive. Use whichever fits; a result can only be
3113
+ * consumed once. Streaming is the default; some driver/plan
3114
+ * combinations may still buffer internally before yielding.
3115
+ *
3116
+ * ```typescript
3117
+ * // Thenable — collect the deleted rows into an array:
3118
+ * const deleted = await db.orm.Post.where({ archived: true }).deleteAll();
3119
+ *
3120
+ * // Async iterable — stream deleted rows as they arrive:
3121
+ * for await (const row of db.orm.Post.where({ archived: true }).deleteAll()) {
3122
+ * console.log('removed', row.id);
3123
+ * }
3124
+ * ```
3125
+ *
3126
+ * Accepts an optional `configure` callback that receives a
3127
+ * `MetaBuilder<'write'>` for attaching typed annotations.
3128
+ */
2717
3129
  deleteAll(configure) {
2718
3130
  return this.#deleteAllWithAnnotations(this.#collectAnnotationsFromMeta(configure, "write", "deleteAll"));
2719
3131
  }
@@ -2735,6 +3147,18 @@ var Collection = class Collection {
2735
3147
  mapRow: (mapped) => mapped
2736
3148
  });
2737
3149
  }
3150
+ /**
3151
+ * Write terminal: delete every matching row without returning them,
3152
+ * resolving to the count of rows that were deleted. Requires a prior
3153
+ * `.where(...)` filter.
3154
+ *
3155
+ * Prefer `deleteAll(...)` when you need the deleted rows; prefer
3156
+ * this when you only need the affected-row count.
3157
+ *
3158
+ * ```typescript
3159
+ * const removed = await db.orm.Post.where({ archived: true }).deleteCount();
3160
+ * ```
3161
+ */
2738
3162
  async deleteCount(configure) {
2739
3163
  const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "deleteCount");
2740
3164
  const primaryKeyColumn = resolvePrimaryKeyColumn(this.contract, this.tableName);