@palbase/backend 27.1.0 → 29.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/bin/palbase-backend.cjs +284 -50
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -4
  4. package/dist/{chunk-JVZQCC77.js → chunk-3TUJWHC2.js} +8 -3
  5. package/dist/chunk-3TUJWHC2.js.map +1 -0
  6. package/dist/{chunk-EB3TUX5J.js → chunk-DHJ3SYAE.js} +251 -50
  7. package/dist/chunk-DHJ3SYAE.js.map +1 -0
  8. package/dist/{chunk-XABHGMUT.js → chunk-ENZ2RFFJ.js} +2 -2
  9. package/dist/{chunk-BQN723PL.js → chunk-ESSQ3YML.js} +73 -7
  10. package/dist/chunk-ESSQ3YML.js.map +1 -0
  11. package/dist/{chunk-OZKSM3JW.js → chunk-SG4UTNOP.js} +7 -3
  12. package/dist/chunk-SG4UTNOP.js.map +1 -0
  13. package/dist/db/index.cjs +8 -2
  14. package/dist/db/index.cjs.map +1 -1
  15. package/dist/db/index.d.cts +1 -1
  16. package/dist/db/index.d.ts +1 -1
  17. package/dist/db/index.js +3 -3
  18. package/dist/engine/index.cjs +294 -50
  19. package/dist/engine/index.cjs.map +1 -1
  20. package/dist/engine/index.d.cts +3 -3
  21. package/dist/engine/index.d.ts +3 -3
  22. package/dist/engine/index.js +11 -4
  23. package/dist/{index-V7QRh1wg.d.ts → index-BOe82fsK.d.ts} +256 -19
  24. package/dist/{index-CAKOgAlP.d.ts → index-BYDUKexK.d.ts} +139 -10
  25. package/dist/{index-H-0qv5d4.d.cts → index-BZfmdRmh.d.cts} +139 -10
  26. package/dist/{index-CgE4sVhg.d.cts → index-ChvoVczS.d.cts} +256 -19
  27. package/dist/index.cjs +81 -6
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +6 -22
  30. package/dist/index.d.ts +6 -22
  31. package/dist/index.js +10 -4
  32. package/dist/index.js.map +1 -1
  33. package/dist/openapi/index.d.cts +2 -2
  34. package/dist/openapi/index.d.ts +2 -2
  35. package/dist/{registry-DHsPDY0_.d.cts → registry-B2KfKWv1.d.cts} +1 -1
  36. package/dist/{registry-4EI8aaFs.d.ts → registry-DaqHsOq8.d.ts} +1 -1
  37. package/dist/test/index.cjs +129 -6
  38. package/dist/test/index.cjs.map +1 -1
  39. package/dist/test/index.d.cts +1 -1
  40. package/dist/test/index.d.ts +1 -1
  41. package/dist/test/index.js +126 -6
  42. package/dist/test/index.js.map +1 -1
  43. package/docs/README.md +1 -1
  44. package/docs/database.md +118 -1
  45. package/docs/llms-full.txt +119 -2
  46. package/package.json +1 -1
  47. package/template/package.json +1 -1
  48. package/dist/chunk-BQN723PL.js.map +0 -1
  49. package/dist/chunk-EB3TUX5J.js.map +0 -1
  50. package/dist/chunk-JVZQCC77.js.map +0 -1
  51. package/dist/chunk-OZKSM3JW.js.map +0 -1
  52. /package/dist/{chunk-XABHGMUT.js.map → chunk-ENZ2RFFJ.js.map} +0 -0
@@ -1,8 +1,8 @@
1
- import '../index-CgE4sVhg.cjs';
2
- export { A as App, e as AuthVerifier, B as BootRefused, f as CreateAppOptions, E as EgressPolicy, g as EngineConfig, M as ModuleClients, R as RateLimiter, h as RequestDatabase, i as RouteEntry, j as RuntimeHooks, S as ScrubResult, k as SqlDriver, l as SqlTx, m as buildRouteTable, n as createApp, o as createLazyTransaction, p as createOps, q as createRequestDatabase, r as effectiveAuth, s as hostAllowed, t as installEgressFence, u as loadConfig, v as makeMemoryCache, w as matchRoute, x as quoteIdent, y as scrubSecrets, z as withTables } from '../index-H-0qv5d4.cjs';
1
+ import '../index-ChvoVczS.cjs';
2
+ export { A as App, g as AuthVerifier, B as BootRefused, h as CreateAppOptions, D as DECLARATION_REFUSAL, a as DeclarationRefused, E as EgressPolicy, j as EngineConfig, M as ModuleClients, R as RateLimiter, k as RequestDatabase, l as RouteEntry, m as RuntimeHooks, S as ScrubResult, n as SqlDriver, o as SqlTx, p as buildRouteTable, q as createApp, r as createLazyTransaction, s as createOps, t as createRequestDatabase, u as effectiveAuth, v as hostAllowed, w as installEgressFence, i as isDeclarationRefused, x as loadConfig, y as makeMemoryCache, z as matchRoute, F as quoteIdent, G as scrubSecrets, H as withTables } from '../index-BZfmdRmh.cjs';
3
3
  import '../db/env.cjs';
4
4
  import '../stack.cjs';
5
5
  import 'zod';
6
6
  import 'node:async_hooks';
7
7
  import '../module-Dl1KFVtc.cjs';
8
- import '../registry-DHsPDY0_.cjs';
8
+ import '../registry-B2KfKWv1.cjs';
@@ -1,8 +1,8 @@
1
- import '../index-V7QRh1wg.js';
2
- export { A as App, e as AuthVerifier, B as BootRefused, f as CreateAppOptions, E as EgressPolicy, g as EngineConfig, M as ModuleClients, R as RateLimiter, h as RequestDatabase, i as RouteEntry, j as RuntimeHooks, S as ScrubResult, k as SqlDriver, l as SqlTx, m as buildRouteTable, n as createApp, o as createLazyTransaction, p as createOps, q as createRequestDatabase, r as effectiveAuth, s as hostAllowed, t as installEgressFence, u as loadConfig, v as makeMemoryCache, w as matchRoute, x as quoteIdent, y as scrubSecrets, z as withTables } from '../index-CAKOgAlP.js';
1
+ import '../index-BOe82fsK.js';
2
+ export { A as App, g as AuthVerifier, B as BootRefused, h as CreateAppOptions, D as DECLARATION_REFUSAL, a as DeclarationRefused, E as EgressPolicy, j as EngineConfig, M as ModuleClients, R as RateLimiter, k as RequestDatabase, l as RouteEntry, m as RuntimeHooks, S as ScrubResult, n as SqlDriver, o as SqlTx, p as buildRouteTable, q as createApp, r as createLazyTransaction, s as createOps, t as createRequestDatabase, u as effectiveAuth, v as hostAllowed, w as installEgressFence, i as isDeclarationRefused, x as loadConfig, y as makeMemoryCache, z as matchRoute, F as quoteIdent, G as scrubSecrets, H as withTables } from '../index-BYDUKexK.js';
3
3
  import '../db/env.js';
4
4
  import '../stack.js';
5
5
  import 'zod';
6
6
  import 'node:async_hooks';
7
7
  import '../module-Dl1KFVtc.js';
8
- import '../registry-4EI8aaFs.js';
8
+ import '../registry-DaqHsOq8.js';
@@ -16,16 +16,22 @@ import {
16
16
  quoteIdent,
17
17
  scrubSecrets,
18
18
  withTables
19
- } from "../chunk-EB3TUX5J.js";
20
- import "../chunk-BQN723PL.js";
21
- import "../chunk-OZKSM3JW.js";
22
- import "../chunk-JVZQCC77.js";
19
+ } from "../chunk-DHJ3SYAE.js";
20
+ import {
21
+ DECLARATION_REFUSAL,
22
+ DeclarationRefused,
23
+ isDeclarationRefused
24
+ } from "../chunk-ESSQ3YML.js";
25
+ import "../chunk-SG4UTNOP.js";
26
+ import "../chunk-3TUJWHC2.js";
23
27
  import "../chunk-VVMJEVQP.js";
24
28
  import "../chunk-CGNN2PUH.js";
25
29
  import "../chunk-VXPNPVAG.js";
26
30
  export {
27
31
  AuthVerifier,
28
32
  BootRefused,
33
+ DECLARATION_REFUSAL,
34
+ DeclarationRefused,
29
35
  RateLimiter,
30
36
  buildRouteTable,
31
37
  createApp,
@@ -35,6 +41,7 @@ export {
35
41
  effectiveAuth,
36
42
  hostAllowed,
37
43
  installEgressFence,
44
+ isDeclarationRefused,
38
45
  loadConfig,
39
46
  makeMemoryCache,
40
47
  matchRoute,
@@ -51,12 +51,24 @@ interface User {
51
51
  interface AuthConfig {
52
52
  /** Whether authentication is required. Defaults to true. */
53
53
  required: boolean;
54
- /** Required role for access. If undefined, any authenticated user is allowed.
54
+ /** Required application role for access. If undefined, any authenticated user is allowed.
55
55
  *
56
- * Matched against the caller's `metadata.role` — NOT `user.role`, which is the
57
- * database role RLS reads and is always "authenticated" for a signed-in user.
58
- * Not signed in 401; signed in with a different or missing role → 403. */
56
+ * Matched against the caller's rows in `auth.user_roles` — NOT against the
57
+ * `role` claim, which is the DATABASE role RLS reads and is the literal
58
+ * string "authenticated" for every signed-in user. Reading it from the table
59
+ * is what makes a revoked role take effect on the very NEXT request instead
60
+ * of whenever the token happens to expire.
61
+ *
62
+ * Not signed in → 401; signed in without the role → 403. */
59
63
  role?: string;
64
+ /** Required permission for access, as `resource.action` (e.g. "posts.delete_any").
65
+ *
66
+ * Resolved through `auth.has_permission(...)`, which answers from the roles
67
+ * the caller holds and the permissions those roles carry. The query runs in
68
+ * the request's own transaction, so it costs no extra connection.
69
+ *
70
+ * Signed in without the permission → 403. */
71
+ permission?: string;
60
72
  /** Require a confirmed email address. An unverified caller gets 403
61
73
  * `email_not_verified`. Fences a whole controller; for a partial rule read
62
74
  * `user.emailVerified` in the handler instead. */
@@ -2825,7 +2837,12 @@ type RowShape<T extends TableDef> = {
2825
2837
  };
2826
2838
  /** A typed table accessor that mirrors the runtime DBClient surface. */
2827
2839
  interface TypedTable<T extends TableDef> {
2828
- insert(data: InsertShape<T>): Promise<RowShape<T>>;
2840
+ insert(data: InsertValues<InsertShape<T>>): Promise<RowShape<T>>;
2841
+ /** Çok satır, TEK statement. Satırların anahtar kümesi aynı olmalı. */
2842
+ insertMany(rows: readonly InsertValues<InsertShape<T>>[], opts?: {
2843
+ onConflict: readonly string[];
2844
+ action?: "ignore" | "update";
2845
+ }): Promise<RowShape<T>[]>;
2829
2846
  put(q: {
2830
2847
  data: InsertShape<T>;
2831
2848
  onConflict: readonly string[];
@@ -2837,7 +2854,7 @@ interface TypedTable<T extends TableDef> {
2837
2854
  where: {
2838
2855
  id: string;
2839
2856
  };
2840
- set: Partial<InsertShape<T>>;
2857
+ set: SetShape<InsertShape<T>>;
2841
2858
  }): Promise<RowShape<T> | null>;
2842
2859
  delete(id: string): Promise<void>;
2843
2860
  findById(id: string): Promise<RowShape<T> | null>;
@@ -2859,11 +2876,33 @@ interface TypedTable<T extends TableDef> {
2859
2876
  * if (row === undefined) throw new Conflict("yetersiz bakiye");
2860
2877
  * ```
2861
2878
  */
2862
- updateMany(q: MutateInput<RowShape<T>, InsertShape<T>>): Promise<RowShape<T>[]>;
2879
+ updateMany<const Q extends MutateInput<RowShape<T>, InsertShape<T>>>(q: Q): Promise<Q extends {
2880
+ returning: false;
2881
+ } ? number : RowShape<T>[]>;
2863
2882
  /** Delete every matching row; resolves to how many. Empty filter refused. */
2864
2883
  deleteMany(q: {
2865
2884
  where: WhereFilter<RowShape<T>> | SqlFragment;
2866
2885
  }): Promise<number>;
2886
+ /**
2887
+ * Toplama — `sum` / `avg` / `min` / `max` / `count`, isteğe bağlı `groupBy`.
2888
+ *
2889
+ * ```ts
2890
+ * const r = await Database.public.entries.aggregate({
2891
+ * where: { account_id },
2892
+ * sum: ["amount_kurus"],
2893
+ * count: true,
2894
+ * });
2895
+ * r.sum.amount_kurus; // string | null — KAYIPSIZ
2896
+ * r.count; // number
2897
+ * ```
2898
+ *
2899
+ * `sum`/`avg` yalnız sayısal kolonlarda ve HER ZAMAN `string` döner: para JS
2900
+ * `number`'ına uğradığı anda kesinliğini kaybeder. `| null` çünkü Postgres
2901
+ * boş kümede NULL döndürür — "hiç satır yoktu" ile "toplam sıfırdı" farklı.
2902
+ *
2903
+ * `groupBy` verilirse dönüş bir DİZİ ve her satır grup kolonlarını taşır.
2904
+ */
2905
+ aggregate<const Q extends AggregateInput<RowShape<T>>>(q: Q): Promise<AggregateResult<Q, RowShape<T>>>;
2867
2906
  /** How many rows match. An empty filter is legitimate: counting is a read. */
2868
2907
  count(q?: {
2869
2908
  where?: WhereFilter<RowShape<T>> | SqlFragment;
@@ -2974,9 +3013,39 @@ declare function col<const N extends string>(name: N): ColRef<N>;
2974
3013
  * kabul, `numeric ↔ text` ret olur ve ikisi de DOĞRU olur. Defterde teklif.
2975
3014
  */
2976
3015
  type PgFamily<V> = [NonNullable<V>] extends [boolean] ? "bool" : [NonNullable<V>] extends [readonly (number | string)[]] ? "array" : [NonNullable<V>] extends [Date] ? "date" : [NonNullable<V>] extends [string | number] ? "scalar" : "opaque";
2977
- /** `Row`'un `V` ile karşılaştırılabilir kolonları — aynı aileden olanlar. */
3016
+ /**
3017
+ * Kolonun BİLDİRİLMİŞ Postgres tipi — `env-gen`'in bastığı markadan.
3018
+ *
3019
+ * Marka yoksa `"?"`: elle yazılmış bir `Tables` (testler) ya da yeniden
3020
+ * üretilmemiş bir `palbase-env.d.ts`. O durumda eski TS-tipi sezgiseline
3021
+ * düşülüyor, çünkü marka olmadan söylenebilecek daha doğru bir şey yok.
3022
+ */
3023
+ type PgTag<V> = [NonNullable<V>] extends [{
3024
+ readonly __pg?: infer N;
3025
+ }] ? [N] extends [string] ? N : "?" : "?";
3026
+ /**
3027
+ * Postgres'te birbirleriyle karşılaştırılabilen tipler tek "cins".
3028
+ *
3029
+ * `integer`, `bigint` ve `numeric` örtük sayısal dönüşümle karşılaştırılır;
3030
+ * geri kalan her tip yalnız kendisiyle. `uuid ↔ text` ve `enum ↔ text`
3031
+ * Postgres'te `operator does not exist` verir — D-021'de CANLI ölçülmüştü ve
3032
+ * tip bunu göremediği için kabul ediyordu.
3033
+ */
3034
+ type PgKind<P> = P extends "integer" | "bigint" | "numeric" ? "num" : P;
3035
+ /** İki tarafın da markası var mı — yoksa sezgisele düşülür. */
3036
+ type BothBranded<A, B> = "?" extends PgTag<A> ? false : "?" extends PgTag<B> ? false : true;
3037
+ type PgSameKind<A, B> = [PgKind<PgTag<A>>] extends [PgKind<PgTag<B>>] ? [PgKind<PgTag<B>>] extends [PgKind<PgTag<A>>] ? true : false : false;
3038
+ /**
3039
+ * `A` kolonu `B` ile karşılaştırılabilir mi.
3040
+ *
3041
+ * Marka varsa CEVAP KESİN: ne yanlış ret ne yanlış kabul. D-021 bu kuralı
3042
+ * "doğru olamaz" diye kaydetmişti çünkü tipte ayırt edecek bilgi yoktu; bilgi
3043
+ * artık tipte.
3044
+ */
3045
+ type ColComparable<A, B> = BothBranded<A, B> extends true ? PgSameKind<A, B> : [PgFamily<A>] extends [PgFamily<B>] ? true : false;
3046
+ /** `Row`'un `V` ile karşılaştırılabilir kolonları. */
2978
3047
  type ColumnsComparableTo<Row, V> = {
2979
- [K in keyof Row]-?: PgFamily<Row[K]> extends PgFamily<V> ? K : never;
3048
+ [K in keyof Row]-?: ColComparable<Row[K], V> extends true ? K : never;
2980
3049
  }[keyof Row];
2981
3050
  /**
2982
3051
  * Bu satırın kolonlarından birine referans. `Row` bilinmiyorsa hiçbiri.
@@ -2985,7 +3054,26 @@ type ColumnsComparableTo<Row, V> = {
2985
3054
  * `findMany`'nin `Row`'u bilmediği yolların ve jsonb kolonlarının hâli, ve
2986
3055
  * ikisinde de tipin söyleyebileceği bir şey yok.
2987
3056
  */
2988
- type ColRefOf<Row, V = unknown> = [keyof Row] extends [never] ? never : [unknown] extends [V] ? ColRef<Extract<keyof Row, string>> : ColRef<Extract<ColumnsComparableTo<Row, V>, string>>;
3057
+ /**
3058
+ * "Kısıt verilmedi" için AYRI bir işaret — `unknown` DEĞİL.
3059
+ *
3060
+ * Eskiden varsayılan `unknown`'dı ve dal `[unknown] extends [V]` ile
3061
+ * seçiliyordu. Ama `jsonb` kolonlarının DEĞER TİPİ de `unknown`: yani gerçek
3062
+ * bir jsonb kolonu "kısıt verilmedi" sanılıp TÜM kolonlarla karşılaştırılabilir
3063
+ * hâle geliyordu. D-021 bunu "jsonb ↔ text derleniyor" diye kaydetmişti ve PG
3064
+ * markası tek başına kapatmadı — `unknown & X` kesişimi `X`'e çöktüğü için
3065
+ * jsonb markalanamıyor (markalamak kolonu daraltıp meşru yazmaları kırdı,
3066
+ * gerçek projede ölçüldü).
3067
+ *
3068
+ * Ayrı bir işaretle iki durum artık ayrışıyor: kısıtsız yol kısıtsız kalıyor,
3069
+ * jsonb ise KENDİ ailesine düşüyor — yani yalnız başka bir jsonb ile
3070
+ * karşılaştırılabiliyor.
3071
+ */
3072
+ declare const NO_CONSTRAINT: unique symbol;
3073
+ type NoColConstraint = {
3074
+ readonly [NO_CONSTRAINT]: true;
3075
+ };
3076
+ type ColRefOf<Row, V = NoColConstraint> = [keyof Row] extends [never] ? never : [V] extends [NoColConstraint] ? ColRef<Extract<keyof Row, string>> : ColRef<Extract<ColumnsComparableTo<Row, V>, string>>;
2989
3077
  /**
2990
3078
  * Doğrulanmış, GÖMÜLEBİLİR bir SQL parçası (FR-018, FR-019).
2991
3079
  *
@@ -3063,7 +3151,7 @@ type TextOps<V> = V extends string ? {
3063
3151
  * kolonunda YASAKLAMAK yazarı bu yüzden `sqlFragment`e düşürürdü — ve bu
3064
3152
  * özelliğin var olma nedeni tam olarak o düşüşü kaldırmak.
3065
3153
  */
3066
- type NowComparable<V> = [NonNullable<V>] extends [string | Date] ? TxNow : never;
3154
+ type NowComparable<V> = "?" extends PgTag<V> ? [NonNullable<V>] extends [string | Date] ? TxNow : never : PgTag<V> extends "timestamp" ? TxNow : never;
3067
3155
  type WhereOpBody<V, C> = {
3068
3156
  gt?: V | C | NowComparable<V>;
3069
3157
  gte?: V | C | NowComparable<V>;
@@ -3246,6 +3334,15 @@ type QueryInput<Row, K extends keyof Row = keyof Row, Rels = unknown> = {
3246
3334
  type MutateInput<Row, Insert, Rels = unknown> = {
3247
3335
  where: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels>;
3248
3336
  set: SetShape<Insert>;
3337
+ /**
3338
+ * `false` verilirse SATIRLAR değil SAYI döner — `deleteMany` ile aynı şekil.
3339
+ *
3340
+ * ÖLÇÜLDÜ (200 bin satır, canlı pg16): `RETURNING *` sunucuda %55 daha
3341
+ * pahalı (7,2 sn → 11,1 sn) ve JS'te 45 MB yığın tutuyor — satır başına 236
3342
+ * bayt, yani bir milyon satırda 225 MB. Çoğu çağrı için dönen diziyi okumak
3343
+ * DOĞRU ve varsayılan odur; toplu bir iş için çıkış yolu olması gerekiyordu.
3344
+ */
3345
+ returning?: boolean;
3249
3346
  };
3250
3347
  /**
3251
3348
  * `set`'e yazılabilen değer (FR-012): kolonun kendi tipi ya da — sayısal
@@ -3257,7 +3354,25 @@ type MutateInput<Row, Insert, Rels = unknown> = {
3257
3354
  * string de sayısal sayılır; ayrımı burada yapamayız, ama D-007 zaten miktarın
3258
3355
  * JS `number`'a hiç uğramamasını istiyor.
3259
3356
  */
3260
- type SetValue<V> = V | (NonNullable<V> extends number | string ? TxColumnExpr : never) | (NonNullable<V> extends Date ? TxNow : never);
3357
+ /** `increment()`/`decrement()` HANGİ kolonlarda anlamlı bildirilmiş PG tipine
3358
+ * göre. Marka yoksa eski sezgisel (`number | string`), ki o `text` kolonda da
3359
+ * izin veriyordu: Postgres `text + 1` demez. */
3360
+ type NumericCol<V> = "?" extends PgTag<V> ? NonNullable<V> extends number | string ? TxColumnExpr : never : PgKind<PgTag<V>> extends "num" ? TxColumnExpr : never;
3361
+ /**
3362
+ * Bir EKLEME yolunda yazılabilecek değer.
3363
+ *
3364
+ * Kolonun kendi tipi ya da — zaman kolonlarında — sunucu saati. `increment()`
3365
+ * burada YOK ve olamaz: satır henüz yokken "kolonun şu anki değeri" diye bir
3366
+ * şey yok, motor da bunu adıyla reddediyor.
3367
+ *
3368
+ * Var olma nedeni ölçülmüş: introspect edilmiş şemalarda zaman kolonlarının
3369
+ * çoğu varsayılansız (`timestamp()`), yani "eklerken sunucu saatini yaz"ın tek
3370
+ * karşılığı istemci saatiydi — 33 tablolu bir projede 12 yerde.
3371
+ */
3372
+ type InsertValues<Insert> = {
3373
+ [K in keyof Insert]: Insert[K] | NowComparable<Insert[K]>;
3374
+ };
3375
+ type SetValue<V> = V | NumericCol<V> | NowComparable<V>;
3261
3376
  /** Bir update'in `set`'i: insert şeklinin herhangi bir alt kümesi, ifadelerle. */
3262
3377
  type SetShape<Insert> = {
3263
3378
  [K in keyof Insert]?: SetValue<Insert[K]>;
@@ -3338,9 +3453,90 @@ type RecommendParamsTyped<T extends TableTypes> = SimilarParamsTyped<T> & {
3338
3453
  type RelsOf<T> = T extends {
3339
3454
  relations: infer R;
3340
3455
  } ? R : unknown;
3456
+ /**
3457
+ * TOPLAMA — dönüş tipi PG markasından TÜRETİLİR, tahmin edilmez.
3458
+ *
3459
+ * Para bu SDK'dan geçecek, ve toplamada kaybedilecek tek şey kesinliktir:
3460
+ * `sum(numeric)` Postgres'te `numeric`, `sum(integer)` `bigint` — İKİSİ DE JS
3461
+ * `number`'ında kayıplı. Bu yüzden `sum`/`avg` HER ZAMAN `string` döner ve
3462
+ * motor `::text` ile döker. D-007'nin kapattığı hata buydu:
3463
+ * 41.00821234567890123 canlıda 41.0082123456789 oluyordu.
3464
+ *
3465
+ * `| null` bir süs değil: Postgres BOŞ kümede NULL döndürür, 0 değil. "Hiç
3466
+ * satır yoktu" ile "toplam sıfırdı" farklı şeyler, ve tip bunu sormaya
3467
+ * ZORLUYOR.
3468
+ */
3469
+ type SummableCols<Row> = {
3470
+ [K in keyof Row]-?: "?" extends PgTag<Row[K]> ? NonNullable<Row[K]> extends number | string ? K : never : PgKind<PgTag<Row[K]>> extends "num" ? K : never;
3471
+ }[keyof Row];
3472
+ /** `min`/`max` sıralanabilir her kolonda — jsonb ve vektör hariç. */
3473
+ type OrderableCols<Row> = {
3474
+ [K in keyof Row]-?: "?" extends PgTag<Row[K]> ? NonNullable<Row[K]> extends number | string | Date ? K : never : PgTag<Row[K]> extends "jsonb" | "vector" ? never : K;
3475
+ }[keyof Row];
3476
+ type AggCols<Row> = Extract<SummableCols<Row>, string>;
3477
+ type AggOrderCols<Row> = Extract<OrderableCols<Row>, string>;
3478
+ type AggregateInput<Row, Rels = unknown> = {
3479
+ where?: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels>;
3480
+ /** Toplam — yalnız sayısal kolonlarda; sonuç `string | null` (kayıpsız). */
3481
+ sum?: readonly AggCols<Row>[];
3482
+ /** Ortalama — sonuç `numeric`, yani `string | null`. */
3483
+ avg?: readonly AggCols<Row>[];
3484
+ min?: readonly AggOrderCols<Row>[];
3485
+ max?: readonly AggOrderCols<Row>[];
3486
+ /** Kaç satır — `count()` ile aynı sayı, ama AYNI turda. */
3487
+ count?: boolean;
3488
+ /** Verilirse SATIRLAR döner ve her satır grup kolonlarını taşır. */
3489
+ groupBy?: readonly Extract<keyof Row, string>[];
3490
+ };
3491
+ type AggPart<Q, Row, F extends "sum" | "avg" | "min" | "max"> = Q extends {
3492
+ [K in F]: readonly (infer C extends keyof Row)[];
3493
+ } ? {
3494
+ [P in C]: F extends "sum" | "avg" ? string | null : Row[P] | null;
3495
+ } : never;
3496
+ type AggBody<Q, Row> = ([AggPart<Q, Row, "sum">] extends [never] ? unknown : {
3497
+ sum: AggPart<Q, Row, "sum">;
3498
+ }) & ([AggPart<Q, Row, "avg">] extends [never] ? unknown : {
3499
+ avg: AggPart<Q, Row, "avg">;
3500
+ }) & ([AggPart<Q, Row, "min">] extends [never] ? unknown : {
3501
+ min: AggPart<Q, Row, "min">;
3502
+ }) & ([AggPart<Q, Row, "max">] extends [never] ? unknown : {
3503
+ max: AggPart<Q, Row, "max">;
3504
+ }) & (Q extends {
3505
+ count: true;
3506
+ } ? {
3507
+ count: number;
3508
+ } : unknown);
3509
+ type AggregateResult<Q, Row> = Q extends {
3510
+ groupBy: readonly (infer G extends keyof Row)[];
3511
+ } ? ({
3512
+ [P in G]: Row[P];
3513
+ } & AggBody<Q, Row>)[] : AggBody<Q, Row>;
3341
3514
  /** Temel tablo erişimcisi — search'süz beş op. */
3342
3515
  interface EnvTypedTableBase<T extends TableTypes> {
3343
- insert(data: T["insert"]): Promise<T["row"]>;
3516
+ insert(data: InsertValues<T["insert"]>): Promise<T["row"]>;
3517
+ /**
3518
+ * Çok satırı TEK statement'la ekle (tek gidiş-dönüş).
3519
+ *
3520
+ * ```ts
3521
+ * await Database.public.journey_stops.insertMany(
3522
+ * stops.map((s, i) => ({ journey_id, name: s.name, sort_order: i })),
3523
+ * );
3524
+ * ```
3525
+ *
3526
+ * Satırların anahtar kümesi AYNI olmalı: tek statement kolon listesini
3527
+ * paylaşır, yani farklı şekilli bir satırın fazla kolonu sessizce yazılmaz
3528
+ * ve eksiği yanlış sütuna kayardı. Eksik alan için `null` yazın.
3529
+ *
3530
+ * Boş dizi boş sonuç döner — hata değil: "eklenecek bir şey yoktu".
3531
+ */
3532
+ insertMany(rows: readonly InsertValues<T["insert"]>[], opts?: {
3533
+ /** Çakışmanın ARANDIĞI kolonlar — benzersiz bir kısıt ya da index taşımalı. */
3534
+ onConflict: readonly Extract<keyof T["row"], string>[];
3535
+ /** `"ignore"` (VARSAYILAN, plan yoluyla aynı) çakışanı atlar ve o satır
3536
+ * SONUÇTA DÖNMEZ — Postgres yalnız yazdıklarını döndürür; `"update"`
3537
+ * üzerine yazar. */
3538
+ action?: "ignore" | "update";
3539
+ }): Promise<T["row"][]>;
3344
3540
  /**
3345
3541
  * Bir idempotency anahtarını sahiplen (FR-033).
3346
3542
  *
@@ -3356,7 +3552,7 @@ interface EnvTypedTableBase<T extends TableTypes> {
3356
3552
  * İlk argüman satırı BULAN alanlar, ikincisi yalnız yazılanlar — ikinci çağrı
3357
3553
  * farklı bir yük gönderse bile satır anahtarla bulunur.
3358
3554
  */
3359
- claim(unique: Partial<T["insert"]>, extra?: Partial<T["insert"]>): Promise<{
3555
+ claim(unique: Partial<T["insert"]>, extra?: Partial<InsertValues<T["insert"]>>): Promise<{
3360
3556
  inserted: boolean;
3361
3557
  row: T["row"];
3362
3558
  }>;
@@ -3381,7 +3577,7 @@ interface EnvTypedTableBase<T extends TableTypes> {
3381
3577
  * seçmeyi kolay yapıyordu.
3382
3578
  */
3383
3579
  put(q: {
3384
- data: T["insert"];
3580
+ data: InsertValues<T["insert"]>;
3385
3581
  onConflict: readonly Extract<keyof T["row"], string>[];
3386
3582
  }): Promise<T["row"]>;
3387
3583
  /** Update the row by id; resolves to the updated row, or `null` if no row
@@ -3391,7 +3587,7 @@ interface EnvTypedTableBase<T extends TableTypes> {
3391
3587
  where: {
3392
3588
  id: string;
3393
3589
  };
3394
- set: Partial<T["insert"]>;
3590
+ set: SetShape<T["insert"]>;
3395
3591
  }): Promise<T["row"] | null>;
3396
3592
  delete(id: string): Promise<void>;
3397
3593
  findById(id: string): Promise<T["row"] | null>;
@@ -3413,11 +3609,34 @@ interface EnvTypedTableBase<T extends TableTypes> {
3413
3609
  * if (row === undefined) throw new Conflict("yetersiz bakiye");
3414
3610
  * ```
3415
3611
  */
3416
- updateMany(q: MutateInput<T["row"], T["insert"], RelsOf<T>>): Promise<T["row"][]>;
3612
+ updateMany<const Q extends MutateInput<T["row"], T["insert"], RelsOf<T>>>(q: Q): Promise<Q extends {
3613
+ returning: false;
3614
+ } ? number : T["row"][]>;
3417
3615
  /** Delete every matching row; resolves to how many. Empty filter refused. */
3418
3616
  deleteMany(q: {
3419
3617
  where: WhereFilter<T["row"]> | SqlFragment | HasBranch<T["row"], RelsOf<T>>;
3420
3618
  }): Promise<number>;
3619
+ /**
3620
+ * Toplama — `sum` / `avg` / `min` / `max` / `count`, isteğe bağlı `groupBy`.
3621
+ *
3622
+ * ```ts
3623
+ * const r = await Database.public.entries.aggregate({
3624
+ * where: { account_id },
3625
+ * sum: ["amount_kurus"],
3626
+ * count: true,
3627
+ * });
3628
+ * r.sum.amount_kurus; // string | null — KAYIPSIZ
3629
+ * r.count; // number
3630
+ * ```
3631
+ *
3632
+ * `sum`/`avg` yalnız sayısal kolonlarda ve HER ZAMAN `string` döner: para JS
3633
+ * `number`'ına uğradığı anda kesinliğini kaybeder. `| null` çünkü Postgres
3634
+ * boş kümede NULL döndürür — "hiç satır yoktu" ile "toplam sıfırdı" farklı
3635
+ * şeyler ve tip bunu sormaya zorluyor.
3636
+ *
3637
+ * `groupBy` verilirse dönüş bir DİZİ ve her satır grup kolonlarını taşır.
3638
+ */
3639
+ aggregate<const Q extends AggregateInput<T["row"], RelsOf<T>>>(q: Q): Promise<AggregateResult<Q, T["row"]>>;
3421
3640
  /** How many rows match. An empty filter is legitimate: counting is a read. */
3422
3641
  count(q?: {
3423
3642
  where?: WhereFilter<T["row"]> | SqlFragment | HasBranch<T["row"], RelsOf<T>>;
@@ -3426,7 +3645,7 @@ interface EnvTypedTableBase<T extends TableTypes> {
3426
3645
  * kapanır (valid_to/superseded_by), yenisi TEK savepoint'te eklenir; dönüş
3427
3646
  * yeni satır. Validity beyanı olmayan tabloda adlandırılmış çalışma-zamanı
3428
3647
  * hatası — tip düzeyinde ayrım env `Tables` bayrağı taşımadığından yapılamaz. */
3429
- supersede(id: string, row: T["insert"]): Promise<T["row"]>;
3648
+ supersede(id: string, row: InsertValues<T["insert"]>): Promise<T["row"]>;
3430
3649
  }
3431
3650
  /** Tablo erişimcisi: env girdisi `searchable: true` taşıyorsa (vector kolonu ya da
3432
3651
  * search beyanı — env-gen üretir) `search()` üyesi VARDIR; yoksa üye hiç yoktur ve
@@ -4362,6 +4581,22 @@ interface DBOps {
4362
4581
  */
4363
4582
  query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
4364
4583
  insert(table: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
4584
+ /** Çok satır, TEK statement. Satırların anahtar kümesi aynı olmalı — kolon
4585
+ * listesi paylaşıldığı için farklı şekilli bir satırın kolonu sessizce
4586
+ * yazılmazdı. */
4587
+ aggregate(table: string, q: {
4588
+ where?: Record<string, unknown>;
4589
+ sum?: readonly string[];
4590
+ avg?: readonly string[];
4591
+ min?: readonly string[];
4592
+ max?: readonly string[];
4593
+ count?: boolean;
4594
+ groupBy?: readonly string[];
4595
+ }): Promise<Record<string, unknown> | Record<string, unknown>[]>;
4596
+ insertMany(table: string, rows: readonly Record<string, unknown>[], opts?: {
4597
+ onConflict: readonly string[];
4598
+ action?: "ignore" | "update";
4599
+ }): Promise<Record<string, unknown>[]>;
4365
4600
  /**
4366
4601
  * Insert the row, or update it when it collides on `onConflict` — one
4367
4602
  * statement, so two concurrent callers cannot both lose.
@@ -4435,7 +4670,9 @@ interface DBOps {
4435
4670
  * yetmedi) — ikisini ayıran tek şey dönen dizidir. Çağrı hata fırlatmadı diye
4436
4671
  * işlem oldu SAYILAMAZ; kontrol çağıranın işi.
4437
4672
  */
4438
- updateMany(table: string, where: Record<string, unknown>, set: Record<string, unknown>): Promise<Record<string, unknown>[]>;
4673
+ updateMany(table: string, where: Record<string, unknown>, set: Record<string, unknown>, opts?: {
4674
+ returning?: boolean;
4675
+ }): Promise<Record<string, unknown>[] | number>;
4439
4676
  /**
4440
4677
  * Bir idempotency anahtarını sahiplen (FR-033).
4441
4678
  *