@palbase/backend 28.0.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 (48) hide show
  1. package/dist/bin/palbase-backend.cjs +151 -12
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +3 -3
  4. package/dist/{chunk-75YROPRZ.js → chunk-DHJ3SYAE.js} +152 -15
  5. package/dist/chunk-DHJ3SYAE.js.map +1 -0
  6. package/dist/{chunk-IVZERLTM.js → chunk-ENZ2RFFJ.js} +2 -2
  7. package/dist/{chunk-RVP6BTEZ.js → chunk-ESSQ3YML.js} +13 -4
  8. package/dist/chunk-ESSQ3YML.js.map +1 -0
  9. package/dist/{chunk-SNDXY565.js → chunk-SG4UTNOP.js} +5 -2
  10. package/dist/chunk-SG4UTNOP.js.map +1 -0
  11. package/dist/db/index.cjs +4 -1
  12. package/dist/db/index.cjs.map +1 -1
  13. package/dist/db/index.d.cts +1 -1
  14. package/dist/db/index.d.ts +1 -1
  15. package/dist/db/index.js +2 -2
  16. package/dist/engine/index.cjs +151 -12
  17. package/dist/engine/index.cjs.map +1 -1
  18. package/dist/engine/index.d.cts +3 -3
  19. package/dist/engine/index.d.ts +3 -3
  20. package/dist/engine/index.js +3 -3
  21. package/dist/{index-dTTLlHIn.d.ts → index-BOe82fsK.d.ts} +162 -8
  22. package/dist/{index-BbvOoZFr.d.ts → index-BYDUKexK.d.ts} +29 -7
  23. package/dist/{index-9C3JHxg-.d.cts → index-BZfmdRmh.d.cts} +29 -7
  24. package/dist/{index-DtISj9QX.d.cts → index-ChvoVczS.d.cts} +162 -8
  25. package/dist/index.cjs +15 -3
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +6 -6
  28. package/dist/index.d.ts +6 -6
  29. package/dist/index.js +3 -3
  30. package/dist/openapi/index.d.cts +2 -2
  31. package/dist/openapi/index.d.ts +2 -2
  32. package/dist/{registry-qIPM5BQe.d.cts → registry-B2KfKWv1.d.cts} +1 -1
  33. package/dist/{registry-JQNIX-eA.d.ts → registry-DaqHsOq8.d.ts} +1 -1
  34. package/dist/test/index.cjs +56 -0
  35. package/dist/test/index.cjs.map +1 -1
  36. package/dist/test/index.d.cts +1 -1
  37. package/dist/test/index.d.ts +1 -1
  38. package/dist/test/index.js +56 -0
  39. package/dist/test/index.js.map +1 -1
  40. package/docs/README.md +1 -1
  41. package/docs/database.md +49 -0
  42. package/docs/llms-full.txt +50 -1
  43. package/package.json +1 -1
  44. package/template/package.json +1 -1
  45. package/dist/chunk-75YROPRZ.js.map +0 -1
  46. package/dist/chunk-RVP6BTEZ.js.map +0 -1
  47. package/dist/chunk-SNDXY565.js.map +0 -1
  48. /package/dist/{chunk-IVZERLTM.js.map → chunk-ENZ2RFFJ.js.map} +0 -0
@@ -1,8 +1,8 @@
1
- import '../index-DtISj9QX.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-9C3JHxg-.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-qIPM5BQe.cjs';
8
+ import '../registry-B2KfKWv1.cjs';
@@ -1,8 +1,8 @@
1
- import '../index-dTTLlHIn.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-BbvOoZFr.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-JQNIX-eA.js';
8
+ import '../registry-DaqHsOq8.js';
@@ -16,13 +16,13 @@ import {
16
16
  quoteIdent,
17
17
  scrubSecrets,
18
18
  withTables
19
- } from "../chunk-75YROPRZ.js";
19
+ } from "../chunk-DHJ3SYAE.js";
20
20
  import {
21
21
  DECLARATION_REFUSAL,
22
22
  DeclarationRefused,
23
23
  isDeclarationRefused
24
- } from "../chunk-RVP6BTEZ.js";
25
- import "../chunk-SNDXY565.js";
24
+ } from "../chunk-ESSQ3YML.js";
25
+ import "../chunk-SG4UTNOP.js";
26
26
  import "../chunk-3TUJWHC2.js";
27
27
  import "../chunk-VVMJEVQP.js";
28
28
  import "../chunk-CGNN2PUH.js";
@@ -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. */
@@ -2864,11 +2876,33 @@ interface TypedTable<T extends TableDef> {
2864
2876
  * if (row === undefined) throw new Conflict("yetersiz bakiye");
2865
2877
  * ```
2866
2878
  */
2867
- 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>[]>;
2868
2882
  /** Delete every matching row; resolves to how many. Empty filter refused. */
2869
2883
  deleteMany(q: {
2870
2884
  where: WhereFilter<RowShape<T>> | SqlFragment;
2871
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>>>;
2872
2906
  /** How many rows match. An empty filter is legitimate: counting is a read. */
2873
2907
  count(q?: {
2874
2908
  where?: WhereFilter<RowShape<T>> | SqlFragment;
@@ -3020,7 +3054,26 @@ type ColumnsComparableTo<Row, V> = {
3020
3054
  * `findMany`'nin `Row`'u bilmediği yolların ve jsonb kolonlarının hâli, ve
3021
3055
  * ikisinde de tipin söyleyebileceği bir şey yok.
3022
3056
  */
3023
- 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>>;
3024
3077
  /**
3025
3078
  * Doğrulanmış, GÖMÜLEBİLİR bir SQL parçası (FR-018, FR-019).
3026
3079
  *
@@ -3281,6 +3334,15 @@ type QueryInput<Row, K extends keyof Row = keyof Row, Rels = unknown> = {
3281
3334
  type MutateInput<Row, Insert, Rels = unknown> = {
3282
3335
  where: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels>;
3283
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;
3284
3346
  };
3285
3347
  /**
3286
3348
  * `set`'e yazılabilen değer (FR-012): kolonun kendi tipi ya da — sayısal
@@ -3391,6 +3453,64 @@ type RecommendParamsTyped<T extends TableTypes> = SimilarParamsTyped<T> & {
3391
3453
  type RelsOf<T> = T extends {
3392
3454
  relations: infer R;
3393
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>;
3394
3514
  /** Temel tablo erişimcisi — search'süz beş op. */
3395
3515
  interface EnvTypedTableBase<T extends TableTypes> {
3396
3516
  insert(data: InsertValues<T["insert"]>): Promise<T["row"]>;
@@ -3489,11 +3609,34 @@ interface EnvTypedTableBase<T extends TableTypes> {
3489
3609
  * if (row === undefined) throw new Conflict("yetersiz bakiye");
3490
3610
  * ```
3491
3611
  */
3492
- 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"][]>;
3493
3615
  /** Delete every matching row; resolves to how many. Empty filter refused. */
3494
3616
  deleteMany(q: {
3495
3617
  where: WhereFilter<T["row"]> | SqlFragment | HasBranch<T["row"], RelsOf<T>>;
3496
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"]>>;
3497
3640
  /** How many rows match. An empty filter is legitimate: counting is a read. */
3498
3641
  count(q?: {
3499
3642
  where?: WhereFilter<T["row"]> | SqlFragment | HasBranch<T["row"], RelsOf<T>>;
@@ -4441,6 +4584,15 @@ interface DBOps {
4441
4584
  /** Çok satır, TEK statement. Satırların anahtar kümesi aynı olmalı — kolon
4442
4585
  * listesi paylaşıldığı için farklı şekilli bir satırın kolonu sessizce
4443
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>[]>;
4444
4596
  insertMany(table: string, rows: readonly Record<string, unknown>[], opts?: {
4445
4597
  onConflict: readonly string[];
4446
4598
  action?: "ignore" | "update";
@@ -4518,7 +4670,9 @@ interface DBOps {
4518
4670
  * yetmedi) — ikisini ayıran tek şey dönen dizidir. Çağrı hata fırlatmadı diye
4519
4671
  * işlem oldu SAYILAMAZ; kontrol çağıranın işi.
4520
4672
  */
4521
- 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>;
4522
4676
  /**
4523
4677
  * Bir idempotency anahtarını sahiplen (FR-033).
4524
4678
  *
@@ -1,6 +1,6 @@
1
- import { D as DBClient, a as DBOps, T as TxPlanBody, b as TxPlanResponse, A as AuthSpec, C as CacheClient, c as RuntimeServices, _ as __runWithRuntime, d as __requestALS } from './index-dTTLlHIn.js';
1
+ import { D as DBClient, a as DBOps, T as TxPlanBody, b as TxPlanResponse, A as AuthSpec, C as CacheClient, c as RuntimeServices, _ as __runWithRuntime, d as __requestALS } from './index-BOe82fsK.js';
2
2
  import { T as Token } from './module-Dl1KFVtc.js';
3
- import { R as RouteMeta } from './registry-JQNIX-eA.js';
3
+ import { R as RouteMeta } from './registry-DaqHsOq8.js';
4
4
 
5
5
  /**
6
6
  * engine/config.ts — settings from the environment, and the gate that refuses
@@ -291,7 +291,9 @@ declare function createOps(tx: TxLike): {
291
291
  * should not silently succeed. Callers who mean every row say so with a
292
292
  * predicate that is true for every row.
293
293
  */
294
- updateMany(table: string, where: Row, set: Row): Promise<Row[]>;
294
+ updateMany(table: string, where: Row, set: Row, opts?: {
295
+ returning?: boolean;
296
+ }): Promise<Row[] | number>;
295
297
  /**
296
298
  * Delete every row the filter matches, in ONE statement; resolves to how
297
299
  * many went. Same filter language, same empty-filter refusal as
@@ -307,6 +309,27 @@ declare function createOps(tx: TxLike): {
307
309
  * An empty filter is legitimate HERE: counting a whole table is a read, and
308
310
  * reads do not destroy anything.
309
311
  */
312
+ /**
313
+ * Toplama — `count`'un yanına `sum`/`avg`/`min`/`max` ve `groupBy`.
314
+ *
315
+ * Var olma nedeni: bunlar olmadan "bu hesabın toplamı" ya `$query`'ye ya da
316
+ * TÜM satırları çekip JS'te toplamaya düşüyor. İkincisi hem N+1 hem de para
317
+ * için YANLIŞ — `numeric` JS `number`'ına uğradığı anda kesinliğini
318
+ * kaybeder.
319
+ *
320
+ * `groupBy` verilirse SATIRLAR döner, verilmezse TEK sonuç. Boş kümede
321
+ * Postgres NULL döndürür, 0 değil — ve bu SDK onu `null` olarak geçiriyor:
322
+ * "hiç satır yoktu" ile "toplam sıfırdı" farklı şeyler.
323
+ */
324
+ aggregate(table: string, q?: {
325
+ where?: Row;
326
+ sum?: readonly string[];
327
+ avg?: readonly string[];
328
+ min?: readonly string[];
329
+ max?: readonly string[];
330
+ count?: boolean;
331
+ groupBy?: readonly string[];
332
+ }): Promise<Record<string, unknown> | Record<string, unknown>[]>;
310
333
  count(table: string, where?: Row): Promise<number>;
311
334
  findById(table: string, id: string): Promise<Row | null>;
312
335
  findMany(table: string, query?: Row, opts?: FindManyOptions): Promise<Row[]>;
@@ -809,6 +832,9 @@ declare class AuthVerifier {
809
832
  interface EffectiveAuth {
810
833
  required: boolean;
811
834
  role?: string;
835
+ /** The permission the route demands, as `resource.action`. Resolved against
836
+ * `auth.has_permission(...)` at the gate — see AuthConfig.permission. */
837
+ permission?: string;
812
838
  verifiedEmail: boolean;
813
839
  }
814
840
  /**
@@ -1028,10 +1054,6 @@ interface App {
1028
1054
  */
1029
1055
  readonly container: Container;
1030
1056
  }
1031
- /**
1032
- * Build the app. Fails fast: the database is reached here, at boot, rather than
1033
- * on the first request that needs it.
1034
- */
1035
1057
  declare function createApp(opts: CreateAppOptions): Promise<App>;
1036
1058
 
1037
1059
  export { type App as A, BootRefused as B, type Container as C, DECLARATION_REFUSAL as D, type EgressPolicy as E, quoteIdent as F, scrubSecrets as G, withTables as H, type ModuleClients as M, RateLimiter as R, type ScrubResult as S, DeclarationRefused as a, DiError as b, type DiKind as c, type ModulePressure as d, assertNoOrphanEntryPoints as e, buildContainer as f, AuthVerifier as g, type CreateAppOptions as h, isDeclarationRefused as i, type EngineConfig as j, type RequestDatabase as k, type RouteEntry as l, type RuntimeHooks as m, type SqlDriver as n, type SqlTx as o, buildRouteTable as p, createApp as q, createLazyTransaction as r, createOps as s, createRequestDatabase as t, effectiveAuth as u, hostAllowed as v, installEgressFence as w, loadConfig as x, makeMemoryCache as y, matchRoute as z };
@@ -1,6 +1,6 @@
1
- import { D as DBClient, a as DBOps, T as TxPlanBody, b as TxPlanResponse, A as AuthSpec, C as CacheClient, c as RuntimeServices, _ as __runWithRuntime, d as __requestALS } from './index-DtISj9QX.cjs';
1
+ import { D as DBClient, a as DBOps, T as TxPlanBody, b as TxPlanResponse, A as AuthSpec, C as CacheClient, c as RuntimeServices, _ as __runWithRuntime, d as __requestALS } from './index-ChvoVczS.cjs';
2
2
  import { T as Token } from './module-Dl1KFVtc.cjs';
3
- import { R as RouteMeta } from './registry-qIPM5BQe.cjs';
3
+ import { R as RouteMeta } from './registry-B2KfKWv1.cjs';
4
4
 
5
5
  /**
6
6
  * engine/config.ts — settings from the environment, and the gate that refuses
@@ -291,7 +291,9 @@ declare function createOps(tx: TxLike): {
291
291
  * should not silently succeed. Callers who mean every row say so with a
292
292
  * predicate that is true for every row.
293
293
  */
294
- updateMany(table: string, where: Row, set: Row): Promise<Row[]>;
294
+ updateMany(table: string, where: Row, set: Row, opts?: {
295
+ returning?: boolean;
296
+ }): Promise<Row[] | number>;
295
297
  /**
296
298
  * Delete every row the filter matches, in ONE statement; resolves to how
297
299
  * many went. Same filter language, same empty-filter refusal as
@@ -307,6 +309,27 @@ declare function createOps(tx: TxLike): {
307
309
  * An empty filter is legitimate HERE: counting a whole table is a read, and
308
310
  * reads do not destroy anything.
309
311
  */
312
+ /**
313
+ * Toplama — `count`'un yanına `sum`/`avg`/`min`/`max` ve `groupBy`.
314
+ *
315
+ * Var olma nedeni: bunlar olmadan "bu hesabın toplamı" ya `$query`'ye ya da
316
+ * TÜM satırları çekip JS'te toplamaya düşüyor. İkincisi hem N+1 hem de para
317
+ * için YANLIŞ — `numeric` JS `number`'ına uğradığı anda kesinliğini
318
+ * kaybeder.
319
+ *
320
+ * `groupBy` verilirse SATIRLAR döner, verilmezse TEK sonuç. Boş kümede
321
+ * Postgres NULL döndürür, 0 değil — ve bu SDK onu `null` olarak geçiriyor:
322
+ * "hiç satır yoktu" ile "toplam sıfırdı" farklı şeyler.
323
+ */
324
+ aggregate(table: string, q?: {
325
+ where?: Row;
326
+ sum?: readonly string[];
327
+ avg?: readonly string[];
328
+ min?: readonly string[];
329
+ max?: readonly string[];
330
+ count?: boolean;
331
+ groupBy?: readonly string[];
332
+ }): Promise<Record<string, unknown> | Record<string, unknown>[]>;
310
333
  count(table: string, where?: Row): Promise<number>;
311
334
  findById(table: string, id: string): Promise<Row | null>;
312
335
  findMany(table: string, query?: Row, opts?: FindManyOptions): Promise<Row[]>;
@@ -809,6 +832,9 @@ declare class AuthVerifier {
809
832
  interface EffectiveAuth {
810
833
  required: boolean;
811
834
  role?: string;
835
+ /** The permission the route demands, as `resource.action`. Resolved against
836
+ * `auth.has_permission(...)` at the gate — see AuthConfig.permission. */
837
+ permission?: string;
812
838
  verifiedEmail: boolean;
813
839
  }
814
840
  /**
@@ -1028,10 +1054,6 @@ interface App {
1028
1054
  */
1029
1055
  readonly container: Container;
1030
1056
  }
1031
- /**
1032
- * Build the app. Fails fast: the database is reached here, at boot, rather than
1033
- * on the first request that needs it.
1034
- */
1035
1057
  declare function createApp(opts: CreateAppOptions): Promise<App>;
1036
1058
 
1037
1059
  export { type App as A, BootRefused as B, type Container as C, DECLARATION_REFUSAL as D, type EgressPolicy as E, quoteIdent as F, scrubSecrets as G, withTables as H, type ModuleClients as M, RateLimiter as R, type ScrubResult as S, DeclarationRefused as a, DiError as b, type DiKind as c, type ModulePressure as d, assertNoOrphanEntryPoints as e, buildContainer as f, AuthVerifier as g, type CreateAppOptions as h, isDeclarationRefused as i, type EngineConfig as j, type RequestDatabase as k, type RouteEntry as l, type RuntimeHooks as m, type SqlDriver as n, type SqlTx as o, buildRouteTable as p, createApp as q, createLazyTransaction as r, createOps as s, createRequestDatabase as t, effectiveAuth as u, hostAllowed as v, installEgressFence as w, loadConfig as x, makeMemoryCache as y, matchRoute as z };
@@ -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. */
@@ -2864,11 +2876,33 @@ interface TypedTable<T extends TableDef> {
2864
2876
  * if (row === undefined) throw new Conflict("yetersiz bakiye");
2865
2877
  * ```
2866
2878
  */
2867
- 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>[]>;
2868
2882
  /** Delete every matching row; resolves to how many. Empty filter refused. */
2869
2883
  deleteMany(q: {
2870
2884
  where: WhereFilter<RowShape<T>> | SqlFragment;
2871
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>>>;
2872
2906
  /** How many rows match. An empty filter is legitimate: counting is a read. */
2873
2907
  count(q?: {
2874
2908
  where?: WhereFilter<RowShape<T>> | SqlFragment;
@@ -3020,7 +3054,26 @@ type ColumnsComparableTo<Row, V> = {
3020
3054
  * `findMany`'nin `Row`'u bilmediği yolların ve jsonb kolonlarının hâli, ve
3021
3055
  * ikisinde de tipin söyleyebileceği bir şey yok.
3022
3056
  */
3023
- 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>>;
3024
3077
  /**
3025
3078
  * Doğrulanmış, GÖMÜLEBİLİR bir SQL parçası (FR-018, FR-019).
3026
3079
  *
@@ -3281,6 +3334,15 @@ type QueryInput<Row, K extends keyof Row = keyof Row, Rels = unknown> = {
3281
3334
  type MutateInput<Row, Insert, Rels = unknown> = {
3282
3335
  where: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels>;
3283
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;
3284
3346
  };
3285
3347
  /**
3286
3348
  * `set`'e yazılabilen değer (FR-012): kolonun kendi tipi ya da — sayısal
@@ -3391,6 +3453,64 @@ type RecommendParamsTyped<T extends TableTypes> = SimilarParamsTyped<T> & {
3391
3453
  type RelsOf<T> = T extends {
3392
3454
  relations: infer R;
3393
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>;
3394
3514
  /** Temel tablo erişimcisi — search'süz beş op. */
3395
3515
  interface EnvTypedTableBase<T extends TableTypes> {
3396
3516
  insert(data: InsertValues<T["insert"]>): Promise<T["row"]>;
@@ -3489,11 +3609,34 @@ interface EnvTypedTableBase<T extends TableTypes> {
3489
3609
  * if (row === undefined) throw new Conflict("yetersiz bakiye");
3490
3610
  * ```
3491
3611
  */
3492
- 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"][]>;
3493
3615
  /** Delete every matching row; resolves to how many. Empty filter refused. */
3494
3616
  deleteMany(q: {
3495
3617
  where: WhereFilter<T["row"]> | SqlFragment | HasBranch<T["row"], RelsOf<T>>;
3496
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"]>>;
3497
3640
  /** How many rows match. An empty filter is legitimate: counting is a read. */
3498
3641
  count(q?: {
3499
3642
  where?: WhereFilter<T["row"]> | SqlFragment | HasBranch<T["row"], RelsOf<T>>;
@@ -4441,6 +4584,15 @@ interface DBOps {
4441
4584
  /** Çok satır, TEK statement. Satırların anahtar kümesi aynı olmalı — kolon
4442
4585
  * listesi paylaşıldığı için farklı şekilli bir satırın kolonu sessizce
4443
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>[]>;
4444
4596
  insertMany(table: string, rows: readonly Record<string, unknown>[], opts?: {
4445
4597
  onConflict: readonly string[];
4446
4598
  action?: "ignore" | "update";
@@ -4518,7 +4670,9 @@ interface DBOps {
4518
4670
  * yetmedi) — ikisini ayıran tek şey dönen dizidir. Çağrı hata fırlatmadı diye
4519
4671
  * işlem oldu SAYILAMAZ; kontrol çağıranın işi.
4520
4672
  */
4521
- 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>;
4522
4676
  /**
4523
4677
  * Bir idempotency anahtarını sahiplen (FR-033).
4524
4678
  *