@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
@@ -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
  *
package/dist/index.cjs CHANGED
@@ -27,8 +27,10 @@ __export(src_exports, {
27
27
  Client: () => Client,
28
28
  Conflict: () => Conflict,
29
29
  Controller: () => Controller,
30
+ DECLARATION_REFUSAL: () => DECLARATION_REFUSAL,
30
31
  Database: () => Database,
31
32
  DeadlockDetected: () => DeadlockDetected,
33
+ DeclarationRefused: () => DeclarationRefused,
32
34
  Delete: () => Delete,
33
35
  Deny: () => Deny,
34
36
  DiError: () => DiError,
@@ -130,6 +132,7 @@ __export(src_exports, {
130
132
  index: () => index,
131
133
  installationRef: () => installationRef,
132
134
  integer: () => integer,
135
+ isDeclarationRefused: () => isDeclarationRefused,
133
136
  isPalbaseExtension: () => isPalbaseExtension,
134
137
  isRetryable: () => isRetryable,
135
138
  jobsOf: () => jobsOf,
@@ -452,7 +455,9 @@ function encodeMap(map, allowColumnExpr) {
452
455
  const out = {};
453
456
  for (const key of Object.keys(map).sort()) {
454
457
  const value = map[key];
455
- if (value === void 0) continue;
458
+ if (value === void 0) {
459
+ throw new TxPlanError(`${key} de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Anahtar duruyor ama de\u011Feri yok, yani kolon sessizce YAZILMAZDI (istek g\xF6vdesinden gelen bir alan\u0131n eksik olmas\u0131 bu \u015Fekilde g\xF6r\xFCn\xFCr). Kolon varsay\u0131lan\u0131n\u0131 istiyorsan\u0131z anahtar\u0131 hi\xE7 koymay\u0131n; NULL yazmak istiyorsan\u0131z a\xE7\u0131k\xE7a null yaz\u0131n.`);
460
+ }
456
461
  out[key] = encodeValue(value, key, allowColumnExpr);
457
462
  }
458
463
  return out;
@@ -913,6 +918,8 @@ var TooManyRequests = class extends NamedHttpError {
913
918
  function makeTypedTable(name, raw2) {
914
919
  return {
915
920
  insert: /* @__PURE__ */ __name((data) => raw2.insert(name, data), "insert"),
921
+ aggregate: /* @__PURE__ */ __name((q) => raw2.aggregate(name, q), "aggregate"),
922
+ insertMany: /* @__PURE__ */ __name((rows, opts) => raw2.insertMany(name, rows, opts), "insertMany"),
916
923
  put: /* @__PURE__ */ __name((q) => raw2.put(name, q.data, {
917
924
  onConflict: q.onConflict
918
925
  }), "put"),
@@ -923,7 +930,9 @@ function makeTypedTable(name, raw2) {
923
930
  const { where, ...opts } = q ?? {};
924
931
  return raw2.findMany(name, where, opts);
925
932
  }, "findMany"),
926
- updateMany: /* @__PURE__ */ __name((q) => raw2.updateMany(name, q.where, q.set), "updateMany"),
933
+ updateMany: /* @__PURE__ */ __name((q) => raw2.updateMany(name, q.where, q.set, q.returning === void 0 ? void 0 : {
934
+ returning: q.returning
935
+ }), "updateMany"),
927
936
  deleteMany: /* @__PURE__ */ __name((q) => raw2.deleteMany(name, q.where), "deleteMany"),
928
937
  count: /* @__PURE__ */ __name((q) => raw2.count(name, q?.where), "count")
929
938
  };
@@ -1364,6 +1373,11 @@ function makeTableProxy(ops, prefix) {
1364
1373
  const name = `${prefix}${prop}`;
1365
1374
  return {
1366
1375
  insert: /* @__PURE__ */ __name((data) => ops().insert(name, data), "insert"),
1376
+ // DÖRDÜNCÜ FİİL, aynı gerekçeyle: tip söz veriyor, ops katmanı
1377
+ // uyguluyor, ve bir handler'ın gerçekten dokunduğu yer BURASI.
1378
+ // `runtime-table-verbs` kapısı bunu adıyla saydı.
1379
+ aggregate: /* @__PURE__ */ __name((q) => ops().aggregate(name, q), "aggregate"),
1380
+ insertMany: /* @__PURE__ */ __name((rows, opts) => ops().insertMany(name, rows, opts), "insertMany"),
1367
1381
  update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
1368
1382
  delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
1369
1383
  findById: /* @__PURE__ */ __name((id) => ops().findById(name, id), "findById"),
@@ -1420,6 +1434,8 @@ function makeTypedSurface(raw2) {
1420
1434
  $claim: /* @__PURE__ */ __name((table, unique, extra) => reco.claim(table, unique, extra), "$claim"),
1421
1435
  $lockRows: /* @__PURE__ */ __name((table, ids) => reco.lockRows(table, ids), "$lockRows"),
1422
1436
  $advisoryXactLock: /* @__PURE__ */ __name((key) => reco.advisoryXactLock(key), "$advisoryXactLock"),
1437
+ $aggregate: /* @__PURE__ */ __name((table, q) => raw2.aggregate(table, q), "$aggregate"),
1438
+ $insertMany: /* @__PURE__ */ __name((table, rows, opts) => raw2.insertMany(table, rows, opts), "$insertMany"),
1423
1439
  $supersede: /* @__PURE__ */ __name((table, id, row) => raw2.supersede(table, id, row), "$supersede")
1424
1440
  };
1425
1441
  const base = Object.assign(ops, {
@@ -3558,6 +3574,24 @@ var openai = {
3558
3574
  }
3559
3575
  };
3560
3576
 
3577
+ // src/refusals.ts
3578
+ var DECLARATION_REFUSAL = /* @__PURE__ */ Symbol.for("palbase.backend.declarationRefusal");
3579
+ var DeclarationRefused = class extends Error {
3580
+ static {
3581
+ __name(this, "DeclarationRefused");
3582
+ }
3583
+ /** @see DECLARATION_REFUSAL — realm-safe, unlike `instanceof`. */
3584
+ [DECLARATION_REFUSAL] = true;
3585
+ constructor(message) {
3586
+ super(message);
3587
+ this.name = "DeclarationRefused";
3588
+ }
3589
+ };
3590
+ function isDeclarationRefused(e) {
3591
+ return typeof e === "object" && e !== null && e[DECLARATION_REFUSAL] === true;
3592
+ }
3593
+ __name(isDeclarationRefused, "isDeclarationRefused");
3594
+
3561
3595
  // src/db/env-gen.ts
3562
3596
  function baseTsType(def) {
3563
3597
  switch (def.type) {
@@ -3578,7 +3612,7 @@ function baseTsType(def) {
3578
3612
  case "boolean":
3579
3613
  return "boolean";
3580
3614
  case "jsonb":
3581
- return "unknown";
3615
+ return "Json";
3582
3616
  // FR-008: the authoring type of a pgvector column. The default `unknown`
3583
3617
  // branch below STAYS — an unknown wire type is refused on the Go side
3584
3618
  // (FR-005), never papered over here.
@@ -3594,9 +3628,20 @@ function baseTsType(def) {
3594
3628
  }
3595
3629
  }
3596
3630
  __name(baseTsType, "baseTsType");
3631
+ function pgBrand(def) {
3632
+ if (def.type === "enum") {
3633
+ const values = [
3634
+ ...def.enumValues ?? []
3635
+ ].sort();
3636
+ return values.length === 0 ? "enum" : `enum:${values.join("|")}`;
3637
+ }
3638
+ return def.type;
3639
+ }
3640
+ __name(pgBrand, "pgBrand");
3597
3641
  function rowType(def) {
3598
3642
  const base = baseTsType(def);
3599
- return def.nullable ? `${base} | null` : base;
3643
+ const branded = `Pg<${base}, ${JSON.stringify(pgBrand(def))}>`;
3644
+ return def.nullable ? `${branded} | null` : branded;
3600
3645
  }
3601
3646
  __name(rowType, "rowType");
3602
3647
  function optionalOnInsert(def) {
@@ -3639,12 +3684,12 @@ function buildRelations(schemas) {
3639
3684
  const held = names.get(name);
3640
3685
  if (held !== void 0) {
3641
3686
  if (held.def.owns === true && origin.def.owns === true) {
3642
- throw new Error(`table "${tableKey}" declares TWO owner columns (${held.column}, ${origin.column}) \u2014 a table has at most ONE ownedByUser(). If the other column only POINTS at a user, declare it userRef({ onDelete: \u2026 }) instead.`);
3687
+ throw new DeclarationRefused(`table "${tableKey}" declares TWO owner columns (${held.column}, ${origin.column}) \u2014 a table has at most ONE ownedByUser(). If the other column only POINTS at a user, declare it userRef({ onDelete: \u2026 }) instead.`);
3643
3688
  }
3644
3689
  const heldFix = renameCall(held);
3645
3690
  const originFix = renameCall(origin);
3646
3691
  const remedy = heldFix === originFix ? `rename one of them: ${heldFix}` : `rename one of them \u2014 "${held.table}.${held.column}": ${heldFix}; "${origin.table}.${origin.column}": ${originFix}`;
3647
- throw new Error(`table "${tableKey}": ${describeOrigin(held)} and ${describeOrigin(origin)} both resolve to the relation name "${name}" \u2014 ${remedy}.`);
3692
+ throw new DeclarationRefused(`table "${tableKey}": ${describeOrigin(held)} and ${describeOrigin(origin)} both resolve to the relation name "${name}" \u2014 ${remedy}.`);
3648
3693
  }
3649
3694
  names.set(name, origin);
3650
3695
  out.get(tableKey)?.push(edge);
@@ -3747,6 +3792,25 @@ ${schemaBlocks.join("\n")}
3747
3792
  // is typed with no import and no generic. Schemas other than \`public\` land
3748
3793
  // under \`Schemas\`, reached with \`Database.schema("<name>").tables.*\`.
3749
3794
 
3795
+ /**
3796
+ * Kolonun POSTGRES tipini tipte tasiyan marka.
3797
+ *
3798
+ * __pg ISTEGE BAGLI oldugu icin satir hala duz string/number gibi okunur ve duz
3799
+ * bir degerle yazilir \u2014 marka yalniz col() ve now() karsilastirma kurallarinin
3800
+ * gordugu bir bilgidir. TypeScript numeric, bigint, text, uuid ve timestamp'i
3801
+ * tek string'e dusurdugu icin bu bilgi olmadan integer<->numeric (gecerli) ile
3802
+ * integer<->text (gecersiz) ayirt edilemiyordu.
3803
+ */
3804
+ type Pg<T, N extends string> = T & { readonly __pg?: N };
3805
+
3806
+ /**
3807
+ * Bir jsonb kolonunun tasiyabilecegi deger.
3808
+ *
3809
+ * undefined DAHIL, cunku JSON.stringify undefined alanlari duserur ve
3810
+ * { a: undefined } yazan kod her yerde var \u2014 reddetmek dogru olmazdi.
3811
+ */
3812
+ type Json = string | number | boolean | null | undefined | Json[] | { [k: string]: Json };
3813
+
3750
3814
  declare module "@palbase/backend/env" {
3751
3815
  interface Tables {${body}}
3752
3816
  interface Schemas {${schemasBody}}
@@ -4711,6 +4775,14 @@ var DiError = class extends Error {
4711
4775
  path;
4712
4776
  at;
4713
4777
  fixes;
4778
+ /**
4779
+ * EVERY `DiKind` IS A FACT ABOUT THE AUTHOR'S DECLARATIONS — an unresolvable
4780
+ * dependency, a cycle, a class no module owns. Not one of them is something
4781
+ * the environment could change, so restarting re-reads the same bytes and
4782
+ * fails identically. The cure is a new artifact, and the tag is how the
4783
+ * runtime learns that without matching on wording.
4784
+ */
4785
+ [DECLARATION_REFUSAL] = true;
4714
4786
  constructor(kind, path, at, detail, fixes) {
4715
4787
  super(`
4716
4788
  Kind: ${kind}
@@ -5214,8 +5286,10 @@ var import_zod2 = require("zod");
5214
5286
  Client,
5215
5287
  Conflict,
5216
5288
  Controller,
5289
+ DECLARATION_REFUSAL,
5217
5290
  Database,
5218
5291
  DeadlockDetected,
5292
+ DeclarationRefused,
5219
5293
  Delete,
5220
5294
  Deny,
5221
5295
  DiError,
@@ -5317,6 +5391,7 @@ var import_zod2 = require("zod");
5317
5391
  index,
5318
5392
  installationRef,
5319
5393
  integer,
5394
+ isDeclarationRefused,
5320
5395
  isPalbaseExtension,
5321
5396
  isRetryable,
5322
5397
  jobsOf,