@palbase/backend 22.1.0 → 23.1.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 (81) hide show
  1. package/dist/bin/palbase-backend.cjs +750 -40
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +5 -5
  4. package/dist/{chunk-YL4C5NRY.js → chunk-HQRJDARQ.js} +2 -2
  5. package/dist/{chunk-74XDEF5J.js → chunk-M5MCBWJI.js} +723 -37
  6. package/dist/chunk-M5MCBWJI.js.map +1 -0
  7. package/dist/{chunk-W5ODXPY3.js → chunk-NS5V43YQ.js} +14 -1
  8. package/dist/chunk-NS5V43YQ.js.map +1 -0
  9. package/dist/{chunk-SQC5EIWY.js → chunk-OHALWEOG.js} +19 -9
  10. package/dist/chunk-OHALWEOG.js.map +1 -0
  11. package/dist/{chunk-I3ON7MYF.js → chunk-PY7YJDCT.js} +129 -18
  12. package/dist/chunk-PY7YJDCT.js.map +1 -0
  13. package/dist/{chunk-N32VDWKH.js → chunk-R3KN6RHD.js} +4 -59
  14. package/dist/chunk-R3KN6RHD.js.map +1 -0
  15. package/dist/{chunk-QMVK4X3V.js → chunk-RCLNBJCM.js} +98 -98
  16. package/dist/chunk-RCLNBJCM.js.map +1 -0
  17. package/dist/db/env.cjs.map +1 -1
  18. package/dist/db/env.d.cts +3 -21
  19. package/dist/db/env.d.ts +3 -21
  20. package/dist/db/index.cjs +140 -16
  21. package/dist/db/index.cjs.map +1 -1
  22. package/dist/db/index.d.cts +3 -2
  23. package/dist/db/index.d.ts +3 -2
  24. package/dist/db/index.js +2 -2
  25. package/dist/{endpoint-BVT6jcVW.d.cts → endpoint-CVWXh6oG.d.ts} +147 -15
  26. package/dist/{endpoint-BVT6jcVW.d.ts → endpoint-c9h5jriX.d.cts} +147 -15
  27. package/dist/engine/index.cjs +750 -40
  28. package/dist/engine/index.cjs.map +1 -1
  29. package/dist/engine/index.d.cts +6 -5
  30. package/dist/engine/index.d.ts +6 -5
  31. package/dist/engine/index.js +4 -4
  32. package/dist/{index-BS1gW4nV.d.cts → index-BZrJXnVh.d.ts} +142 -72
  33. package/dist/{index-BqCiHao8.d.cts → index-By8Dle5U.d.cts} +196 -21
  34. package/dist/{index-vwHoS0l2.d.ts → index-CwAJ7HEe.d.ts} +196 -21
  35. package/dist/{index-CCZqzych.d.ts → index-CxeQSfJP.d.cts} +142 -72
  36. package/dist/index.cjs +388 -1101
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +89 -1134
  39. package/dist/index.d.ts +89 -1134
  40. package/dist/index.js +133 -902
  41. package/dist/index.js.map +1 -1
  42. package/dist/openapi/index.cjs +32 -61
  43. package/dist/openapi/index.cjs.map +1 -1
  44. package/dist/openapi/index.d.cts +6 -2
  45. package/dist/openapi/index.d.ts +6 -2
  46. package/dist/openapi/index.js +34 -26
  47. package/dist/openapi/index.js.map +1 -1
  48. package/dist/{registry-Bsuf-orT.d.ts → registry-B3niOVYp.d.ts} +108 -170
  49. package/dist/{registry-BWttGlaT.d.cts → registry-CqPK2Qby.d.cts} +108 -170
  50. package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
  51. package/dist/stack.cjs.map +1 -0
  52. package/dist/stack.d.cts +76 -0
  53. package/dist/stack.d.ts +76 -0
  54. package/dist/stack.js +1 -0
  55. package/dist/test/index.cjs +482 -9
  56. package/dist/test/index.cjs.map +1 -1
  57. package/dist/test/index.d.cts +35 -3
  58. package/dist/test/index.d.ts +35 -3
  59. package/dist/test/index.js +480 -8
  60. package/dist/test/index.js.map +1 -1
  61. package/docs/README.md +7 -6
  62. package/docs/llms-full.txt +7 -260
  63. package/docs/llms.txt +0 -2
  64. package/package.json +9 -8
  65. package/stager/return_types.js +23 -0
  66. package/template/package.json +1 -1
  67. package/dist/chunk-74XDEF5J.js.map +0 -1
  68. package/dist/chunk-I3ON7MYF.js.map +0 -1
  69. package/dist/chunk-N32VDWKH.js.map +0 -1
  70. package/dist/chunk-QMVK4X3V.js.map +0 -1
  71. package/dist/chunk-SQC5EIWY.js.map +0 -1
  72. package/dist/chunk-W5ODXPY3.js.map +0 -1
  73. package/dist/purchases/keys.cjs.map +0 -1
  74. package/dist/purchases/keys.d.cts +0 -42
  75. package/dist/purchases/keys.d.ts +0 -42
  76. package/dist/purchases/keys.js +0 -1
  77. package/docs/config.md +0 -147
  78. package/docs/resources.md +0 -97
  79. package/template/config/secrets.ts +0 -24
  80. /package/dist/{chunk-YL4C5NRY.js.map → chunk-HQRJDARQ.js.map} +0 -0
  81. /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { Tables, TableTypes } from './db/env.js';
2
- import { D as DBClient, b8 as TxPlanHandle, bh as TxTable, M as Materialized } from './endpoint-BVT6jcVW.js';
2
+ import { D as DBClient, b8 as TxPlanHandle, bh as TxTable, M as Materialized } from './endpoint-CVWXh6oG.js';
3
3
 
4
4
  /** On delete action for foreign key references. */
5
5
  type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';
@@ -36,6 +36,17 @@ interface ColumnDef {
36
36
  enumName?: string;
37
37
  enumValues?: string[];
38
38
  unique?: boolean;
39
+ /**
40
+ * The value is written by the DATABASE — a trigger, a rule, an identity — not by
41
+ * the author and not by a DEFAULT this schema declares. It makes the column
42
+ * optional on INSERT without putting a DEFAULT in the DDL.
43
+ *
44
+ * Before this existed the only way to keep a trigger-filled column off the
45
+ * INSERT type was to give it a fake `default()`: a value the schema claimed to
46
+ * write and the trigger immediately overwrote. That made the schema lie about
47
+ * its own data.
48
+ */
49
+ dbAssigned?: boolean;
39
50
  /** vector(n): the declared dimension count — part of the TYPE (typmod), read
40
51
  * by the wire serializer and the deploy's auto-index (FR-001). */
41
52
  dimensions?: number;
@@ -80,6 +91,18 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
80
91
  defaultRandom(): ColumnBuilder<K, N, true, E, P>;
81
92
  /** Timestamp: default to now(). */
82
93
  defaultNow(): ColumnBuilder<K, N, true, E, P>;
94
+ /**
95
+ * The DATABASE assigns this column's value — a trigger, a rule, an identity.
96
+ *
97
+ * The column becomes optional on INSERT (the author has nothing to send) while
98
+ * the DDL stays free of a DEFAULT this schema would not honour. It is NOT
99
+ * `default()`: that declares a value the schema promises to write.
100
+ *
101
+ * Naming: deliberately not `generated()`. Postgres has GENERATED columns and
102
+ * they are a different thing; borrowing the word would send a reader — or a
103
+ * model writing a schema — to the wrong feature.
104
+ */
105
+ dbAssigned(): ColumnBuilder<K, N, true, E, P>;
83
106
  /** Add a foreign key reference. */
84
107
  /**
85
108
  * Declares that this column used to be called `previous`.
@@ -247,22 +270,6 @@ interface PolicyDef {
247
270
  withCheck: string | null;
248
271
  permissive: boolean;
249
272
  }
250
- /**
251
- * Fluent RLS policy builder.
252
- *
253
- * Defaults (documented, applied at construction):
254
- * - `command`: `"all"` — applies to every SQL command unless `.for(...)` narrows it.
255
- * - `roles`: `["authenticated"]` — the common case is "rule applies to signed-in
256
- * users". Call `.to(...)` to override; pass `.to()` with no roles (or never
257
- * call it after a reset) to target PUBLIC.
258
- * - `using` / `withCheck`: `null` — no row filter / write check until set.
259
- * - `permissive`: `true` — `AS PERMISSIVE` (policies OR together).
260
- *
261
- * Each method mutates `_def` in place and returns `this`, so the chain is a
262
- * single builder instance (no per-call allocation, like a tagged-template
263
- * compile target). The terminal `PolicyDef` is read directly off `_def` by
264
- * `schema_extract.js`.
265
- */
266
273
  declare class PolicyBuilder {
267
274
  readonly _def: PolicyDef;
268
275
  constructor(name: string);
@@ -280,6 +287,34 @@ declare class PolicyBuilder {
280
287
  to(...roles: string[]): this;
281
288
  /** Set the `USING (...)` row-visibility expression (raw SQL). */
282
289
  using(sqlExpr: string): this;
290
+ /**
291
+ * "Rows of THIS table whose owner the caller is a member of" — the membership
292
+ * pattern, written so it cannot recurse.
293
+ *
294
+ * THE TRAP IT EXISTS FOR. Written by hand, membership policies point at each
295
+ * other: `channels` is visible to members, so its policy reads
296
+ * `channel_members`; `channel_members` is visible to members, so its policy
297
+ * reads `channels`. Postgres refuses the pair at query time with `infinite
298
+ * recursion detected in policy for relation ...`, and the error names the
299
+ * relation but not the cycle. The way out is asymmetry — the MEMBERSHIP table
300
+ * is protected by `user_id = auth.uid()` and nothing else, and every other
301
+ * table subqueries INTO it. That shape was in the platform's own schema and
302
+ * written down nowhere; a customer recovered it by reading that schema.
303
+ *
304
+ * `(select auth.uid())` rather than a bare call: the scalar subquery is
305
+ * evaluated ONCE per statement instead of per row.
306
+ *
307
+ * @example
308
+ * // channels: visible to members. The membership table gets the simple one.
309
+ * policy("member_read").for("select").to("authenticated")
310
+ * .memberOf("channel_members", "channel_id")
311
+ * // → id IN (SELECT "channel_id" FROM "channel_members"
312
+ * // WHERE "user_id" = (select auth.uid()))
313
+ */
314
+ memberOf(membershipTable: string, foreignKey: string, options?: {
315
+ column?: string;
316
+ userColumn?: string;
317
+ }): this;
283
318
  /** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
284
319
  withCheck(sqlExpr: string): this;
285
320
  /** Set the policy mode: `"permissive"` (default, OR-combined) or
@@ -347,12 +382,20 @@ interface EmbeddingModelRef {
347
382
  apiKeyName?: string;
348
383
  baseURL?: string;
349
384
  }
385
+ /** Chat/damıtma modeli DESKRIPTORU (C-11, D-019) — memory beyanının extract'i.
386
+ * Embedding gibi düz veridir; çağrıyı worker yapar, anahtar vault'taki
387
+ * OPENAI_API_KEY'dir (D-017: aynı sağlayıcı, yeni dış sistem yok). */
388
+ interface ChatModelRef {
389
+ provider: "openai";
390
+ model: string;
391
+ }
350
392
  declare const openai: {
351
393
  embedding(model: string, opts?: {
352
394
  dimensions?: number;
353
395
  apiKeyName?: string;
354
396
  baseURL?: string;
355
397
  }): EmbeddingModelRef;
398
+ chat(model: string): ChatModelRef;
356
399
  };
357
400
 
358
401
  /**
@@ -400,12 +443,44 @@ interface VectorSearchDecl {
400
443
  * eşleşme riski (Confluence-tipi sync yükleri için). */
401
444
  staleness?: "null" | "keep";
402
445
  }
403
- /** Tablonun arama beyanı. `text`: FTS kolonları (sabit 'simple' sözlük, D-4 palbase_fts + GIN türetilir).
404
- * `vector`: bir ya da birden çok ANN kolu. İkisi de opsiyonel; blok yoksa tablo yine
405
- * vector kolonu varlığıyla aranabilir (D-3/FR-013). */
446
+ /** Tablonun arama beyanı İKİ biçim (D-007, tek yüzey):
447
+ *
448
+ * YENİ (önerilen): `{ from, model, ... }` — `from` kolonları hem FTS'e hem
449
+ * embed'e girer. Tabloda vector kolonu declare edilmişse SATIR-modu; yoksa
450
+ * CHUNK-modu otomatiktir (D-010): vektörler türev `__palbase_chunks`
451
+ * tablosunda yaşar, içerik otomatik bölünür. `text: false` FTS'i kapatır,
452
+ * `text: [..]` FTS kolonlarını from'dan ayırır. `chunks` yalnız ince ayar.
453
+ *
454
+ * ESKİ: `text: string[]` + `vector: {...}` — aynen çalışır, wire çıktısı
455
+ * bayt-aynı kalır (NFR-B1). İki biçim KARIŞTIRILAMAZ. */
406
456
  interface SearchDecl {
407
- text?: string[];
457
+ text?: string[] | boolean;
408
458
  vector?: VectorSearchDecl | VectorSearchDecl[];
459
+ /** Yeni biçim: arama kaynağı kolonlar (FTS + embed). Varlığı yeni biçimi seçer. */
460
+ from?: string[];
461
+ /** Yeni biçim: auto-embed modeli (zorunlu — BYO için eski biçimi kullanın). */
462
+ model?: EmbeddingModelRef;
463
+ metric?: SearchMetric;
464
+ staleness?: "null" | "keep";
465
+ /** Chunk-modu ince ayarı (yalnız vector kolonsuz tabloda anlamlı). */
466
+ chunks?: {
467
+ size?: number;
468
+ overlap?: number;
469
+ };
470
+ /** Sorgu-yeniden-yazımı: tek yönlü eş anlamlı haritası (FR-026). */
471
+ synonyms?: Record<string, string[]>;
472
+ /** Geçerlilik kolonları türetilir; arama varsayılan yalnız günceli tarar (FR-029). */
473
+ validity?: boolean;
474
+ }
475
+ /** Hafıza beyanı (FR-032, D-019): kaynak tablonun yazımlarından platform
476
+ * fact damıtır ve `into` tablosuna yazar. Hedef NORMAL declared tablodur —
477
+ * kendi search/unique/validity beyanlarıyla. Okuma = Database.search(into).
478
+ * subject default "owner": fact'in kime ait olduğu kolonu (iki tabloda da). */
479
+ interface MemoryDecl {
480
+ from: string[];
481
+ into: string;
482
+ extract: ChatModelRef;
483
+ subject?: string;
409
484
  }
410
485
  interface TableInput<C extends ColumnMap = ColumnMap> {
411
486
  columns: C;
@@ -448,6 +523,8 @@ interface TableInput<C extends ColumnMap = ColumnMap> {
448
523
  }[];
449
524
  /** Arama beyanı — bkz. SearchDecl. */
450
525
  search?: SearchDecl;
526
+ /** Hafıza beyanı — bkz. MemoryDecl (FR-032). */
527
+ memory?: MemoryDecl;
451
528
  }
452
529
  /**
453
530
  * A table definition — the runtime value the Go runtime's `schema_extract.js`
@@ -485,6 +562,8 @@ interface TableDef<C extends ColumnMap = ColumnMap> {
485
562
  }[];
486
563
  /** Arama beyanı, doğrulanmış ve taşınmış hali. */
487
564
  search?: SearchDecl;
565
+ /** Hafıza beyanı, doğrulanmış ve taşınmış hali (FR-032). */
566
+ memory?: MemoryDecl;
488
567
  }
489
568
  /**
490
569
  * A schema definition containing multiple tables, keyed by table name.
@@ -603,6 +682,9 @@ type RowShape<T extends TableDef> = {
603
682
  /** A typed table accessor that mirrors the runtime DBClient surface. */
604
683
  interface TypedTable<T extends TableDef> {
605
684
  insert(data: InsertShape<T>): Promise<RowShape<T>>;
685
+ upsert(data: InsertShape<T>, opts: {
686
+ onConflict: readonly string[];
687
+ }): Promise<RowShape<T>>;
606
688
  /** Update the row by id; resolves to the updated row, or `null` if no row
607
689
  * matched (absent or RLS-hidden) — an idempotent outcome, mirroring
608
690
  * `findById`. The runtime returns a null row rather than throwing. */
@@ -662,10 +744,65 @@ interface SearchParamsTyped<T extends TableTypes> {
662
744
  /** Birden çok vektör kolonunda hedef seçimi (model geçişi, FR-013/using). */
663
745
  using?: string;
664
746
  mode?: "hybrid" | "text" | "vector";
747
+ /** Nihai (RRF-sonrası) skor alt eşiği — süzme LIMIT'ten önce uygulanır (FR-001). */
748
+ minScore?: number;
749
+ /** Chunk-modunda satır başına en iyi blok sayısı (1..10, vars. 3; FR-015). */
750
+ blocksPerRow?: number;
751
+ /** Tazelik çürümesi: nihai skor RRF-sonrası exp(-ln(2)*yaş/halfLife) ile çarpılır;
752
+ * field bir timestamp kolonu, halfLife "90s" | "15m" | "12h" | "30d" biçiminde (FR-004). */
753
+ recency?: {
754
+ field: Extract<keyof T["row"], string>;
755
+ halfLife: string;
756
+ };
757
+ /** Filtrelenmiş küme üzerinde kolon başına top-20 değer sayacı — dönüş
758
+ * dizisinin `_facets` özelliği (FR-027). */
759
+ facets?: Extract<keyof T["row"], string>[];
760
+ /** Satır-modunda FTS eşleşme vurgusu: sonuç satırına `_highlight` ekler;
761
+ * chunk-modda no-op — bloklar zaten eşleşen kesittir (FR-025). */
762
+ highlight?: boolean;
763
+ /** Validity'li tabloda zaman penceresi: varsayılan yalnız güncel versiyon;
764
+ * "all" tüm versiyonlar; {asOf} o anda geçerli olan (FR-029). */
765
+ validity?: "all" | {
766
+ asOf: string;
767
+ };
768
+ /** Alan-boost (FR-030): skor * (1 + w·x/(1+x)) — sayısal kolonla sınırlı
769
+ * çarpan, dış servissiz; bileşim RRF → boost → recency → minScore. */
770
+ boost?: {
771
+ field: Extract<keyof T["row"], string>;
772
+ weight: number;
773
+ };
665
774
  }
775
+ /** search() dönüş dizisinin sorgu-düzeyi ekleri (FR-027): `_facets` dizinin
776
+ * ÖZELLİĞİDİR, satırlara kopyalanmaz (JSON'a satır başına şişme olmasın). */
777
+ type SearchFacets = Record<string, {
778
+ value: string | null;
779
+ count: number;
780
+ }[]>;
781
+ /** similar()/recommend() taşıyıcı opsiyonları (T018, FR-022): search'ün
782
+ * paramlarından query/vector/mode düşer — hedef vektörü metodun kendisi
783
+ * DB'den kurar; facets/highlight de düşer (T020) — engine bu ikisini
784
+ * similar/recommend'e geçirmez, tip vaadi gerçekle aynı kalır. */
785
+ type SimilarParamsTyped<T extends TableTypes> = Omit<SearchParamsTyped<T>, "query" | "vector" | "mode" | "facets" | "highlight">;
786
+ /** recommend() parametreleri (T018, FR-023). */
787
+ type RecommendParamsTyped<T extends TableTypes> = SimilarParamsTyped<T> & {
788
+ /** Kaynak beğeniler — hedef vektör bunların DB-içi avg'ı; boş olamaz. */
789
+ positive: string[];
790
+ /** İtilen örnekler — hedef pos.v + (pos.v - neg.v) ile yönlenir. */
791
+ negative?: string[];
792
+ };
666
793
  /** Temel tablo erişimcisi — search'süz beş op. */
667
794
  interface EnvTypedTableBase<T extends TableTypes> {
668
795
  insert(data: T["insert"]): Promise<T["row"]>;
796
+ /**
797
+ * Insert the row, or update it when it collides on `onConflict`.
798
+ *
799
+ * The conflict columns must carry a unique constraint or index — that is what
800
+ * Postgres matches on — and they are excluded from the update, since they are
801
+ * what matched.
802
+ */
803
+ upsert(data: T["insert"], opts: {
804
+ onConflict: readonly Extract<keyof T["row"], string>[];
805
+ }): Promise<T["row"]>;
669
806
  /** Update the row by id; resolves to the updated row, or `null` if no row
670
807
  * matched (absent or RLS-hidden) — an idempotent outcome, mirroring
671
808
  * `findById`. The runtime returns a null row rather than throwing. */
@@ -673,6 +810,11 @@ interface EnvTypedTableBase<T extends TableTypes> {
673
810
  delete(id: string): Promise<void>;
674
811
  findById(id: string): Promise<T["row"] | null>;
675
812
  findMany(query?: Partial<T["row"]>): Promise<T["row"][]>;
813
+ /** Validity'li tabloda satırın yeni versiyonu (FR-029, C-9): eski satır
814
+ * kapanır (valid_to/superseded_by), yenisi TEK savepoint'te eklenir; dönüş
815
+ * yeni satır. Validity beyanı olmayan tabloda adlandırılmış çalışma-zamanı
816
+ * hatası — tip düzeyinde ayrım env `Tables` bayrağı taşımadığından yapılamaz. */
817
+ supersede(id: string, row: T["insert"]): Promise<T["row"]>;
676
818
  }
677
819
  /** Tablo erişimcisi: env girdisi `searchable: true` taşıyorsa (vector kolonu ya da
678
820
  * search beyanı — env-gen üretir) `search()` üyesi VARDIR; yoksa üye hiç yoktur ve
@@ -682,6 +824,18 @@ type EnvTypedTable<T extends TableTypes> = EnvTypedTableBase<T> & (T extends {
682
824
  } ? {
683
825
  search(params: SearchParamsTyped<T>): Promise<Array<T["row"] & {
684
826
  _score: number;
827
+ }> & {
828
+ _facets?: SearchFacets;
829
+ }>;
830
+ /** "Bu satıra benzeyenler" (FR-022): hedef vektör DB'den okunur,
831
+ * kaynak satır sonuçta yoktur; id yoksa adlandırılmış hata. */
832
+ similar(id: string, params?: SimilarParamsTyped<T>): Promise<Array<T["row"] & {
833
+ _score: number;
834
+ }>>;
835
+ /** positive/negative beğenilerden öneri (FR-023): hedef vektör DB-içi
836
+ * avg CTE'leriyle; kaynak id'ler sonuçta yoktur. */
837
+ recommend(params: RecommendParamsTyped<T>): Promise<Array<T["row"] & {
838
+ _score: number;
685
839
  }>>;
686
840
  } : Record<never, never>);
687
841
  /** The `tables` map exposed on `Database`/`tx`, keyed by the env `Tables`
@@ -750,6 +904,27 @@ interface EnvTypedDatabase extends Omit<DBClient, "txPlan" | "asService"> {
750
904
  * });
751
905
  */
752
906
  transaction<T>(fn: (tx: TxPlan) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
907
+ /**
908
+ * Run `fn` inside a SAVEPOINT, so a write that fails in it does not poison the
909
+ * rest of the request.
910
+ *
911
+ * A request is ONE Postgres transaction: a failed statement aborts it and
912
+ * every later one answers `current transaction is aborted`. That is why
913
+ * "insert, catch the unique violation, update instead" cannot be written
914
+ * directly — and why {@link EnvTypedTableBase.upsert} exists for the common
915
+ * case. Reach for `attempt` when the recovery is not an upsert.
916
+ *
917
+ * The handle is a parameter, not the ambient `Database`: only what `tx` writes
918
+ * is inside the boundary, so a concurrent branch of the same request cannot be
919
+ * rolled back by someone else's failure.
920
+ *
921
+ * @example
922
+ * const claimed = await Database.attempt(async (tx) => {
923
+ * await tx.insert("seats", { row: 4, seat: 12, user_id: user.id });
924
+ * return true;
925
+ * }).catch(() => false);
926
+ */
927
+ attempt<T>(fn: (tx: Omit<DBClient, "attempt" | "txPlan" | "asService">) => Promise<T>): Promise<T>;
753
928
  /**
754
929
  * Return a sibling that bypasses RLS by running as the `service_role`. Use
755
930
  * sparingly and explicitly — the default `Database.*` path is RLS-enforced.
@@ -1,63 +1,8 @@
1
- import { Buckets, BucketTypes } from './db/env.js';
1
+ import { Buckets, BucketTypes } from './stack.cjs';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
- import { C as CacheClient, P as PalbaseDocsClient, a as PalbaseFlagsClient, L as Logger, b as PalbaseNotificationsClient, c as PalbaseRealtimeClient, D as DBClient, S as SecretsService, d as PalbaseStorageClient, e as PalbaseBucketClient, T as TxPlanBody, f as TxPlanResponse } from './endpoint-BVT6jcVW.js';
4
- import { E as EnvTypedDatabase } from './index-vwHoS0l2.js';
5
- import { R as RouteMeta } from './registry-Bsuf-orT.js';
6
-
7
- /**
8
- * The purchases surface the decorators need, declared as a NARROW STRUCTURAL
9
- * interface rather than an import of `@palstore/purchases`.
10
- *
11
- * `PurchasesClient` (palstore's `sk_` backend SDK) satisfies this shape as-is,
12
- * so the runtime injects the real client with no adapter — but `@palbase/backend`
13
- * itself gains no dependency on it. That matters: this package is published
14
- * public and baked into the br-pod image from a tarball, so a dependency on an
15
- * unpublished sibling would break `npm install palbase-backend.tgz`. It is also
16
- * the pattern `withSpend` already uses for the same reason (`SpendCapableClient`
17
- * in palstore's own spend.ts: "a narrow structural interface … so this file has
18
- * no dependency on client.ts").
19
- *
20
- * The methods here are a SUBSET of `PurchasesClient` — only what the two
21
- * decorators call. Grants, refunds, credits and customer-info reads stay off
22
- * this interface: a tenant that wants them imports the palstore SDK directly.
23
- */
24
- /** Store environment a subject is fixed to. Mirrors `StoreEnv` in `@palstore/purchases`. */
25
- type StoreEnv = "production" | "sandbox";
26
- /** Quota/credit state carried by a 429. Mirrors `LimitState` in `@palstore/purchases`
27
- * (SPEC-purchases-v1 §11) — re-declared, not imported, for the reason above. */
28
- interface LimitState {
29
- key: string;
30
- scope: string;
31
- window: string;
32
- used: number;
33
- reserved: number;
34
- max: number;
35
- remaining: number;
36
- resetAt: string;
37
- }
38
- /** Options for one spend. `idempotencyKey` is required by the server (§9). */
39
- interface SpendOptions {
40
- /** Defaults to 1. */
41
- count?: number;
42
- idempotencyKey: string;
43
- }
44
- interface PurchasesService {
45
- /** Map a tenant-side user reference to its palstore subject, creating one on
46
- * first sight. Server-authoritative — the caller never names a subject. */
47
- resolveSubject(input: {
48
- userRef: string;
49
- storeEnv: StoreEnv;
50
- }): Promise<{
51
- subjectId: string;
52
- }>;
53
- /** Resolve silently when `entitlementKey` is active for `subjectId`; throw
54
- * `EntitlementRequiredError` otherwise. Consumes nothing. */
55
- require(subjectId: string, entitlementKey: string): Promise<void>;
56
- /** Reserve → run `handler` → commit on success, cancel on throw, always
57
- * rethrowing the handler's own error. The whole reason the decorators are a
58
- * thin layer: this lifecycle is already written and tested in palstore's SDK. */
59
- withSpend<T>(subjectId: string, key: string, opts: SpendOptions, handler: () => Promise<T>): Promise<T>;
60
- }
3
+ import { C as CacheClient, P as PalbaseDocsClient, a as PalbaseFlagsClient, L as Logger, b as PalbaseNotificationsClient, c as PalbaseRealtimeClient, D as DBClient, S as SecretsService, d as PalbaseStorageClient, e as PalbaseBucketClient, f as DBOps, T as TxPlanBody, g as TxPlanResponse } from './endpoint-c9h5jriX.cjs';
4
+ import { E as EnvTypedDatabase } from './index-By8Dle5U.cjs';
5
+ import { R as RouteMeta } from './registry-CqPK2Qby.cjs';
61
6
 
62
7
  /**
63
8
  * runtime.ts — request-scoped service singletons.
@@ -118,7 +63,6 @@ interface RuntimeServices {
118
63
  Notifications: PalbaseNotificationsClient;
119
64
  Flags: PalbaseFlagsClient;
120
65
  Realtime: PalbaseRealtimeClient;
121
- Purchases: PurchasesService;
122
66
  }
123
67
  /**
124
68
  * The per-request ALS box.
@@ -239,15 +183,6 @@ declare const Secrets: SecretsService;
239
183
  declare const Log: Logger;
240
184
  /** Push / email / SMS / in-app notifications. */
241
185
  declare const Notifications: PalbaseNotificationsClient;
242
- /**
243
- * Palstore purchases (entitlements + quota/credit spend).
244
- *
245
- * Reached by handlers through the `@RequireEntitlement` / `@Spend` decorators
246
- * rather than called directly in the common case; exposed as a singleton for
247
- * the cases the decorators deliberately do not cover (a dynamic spend count,
248
- * which must run BEFORE the billable side-effect).
249
- */
250
- declare const Purchases: PurchasesService;
251
186
  /**
252
187
  * Feature flags.
253
188
  *
@@ -426,14 +361,41 @@ declare function createLazyTransaction(sql: SqlDriver, role: string, claimsJson:
426
361
  type LazyTransaction = ReturnType<typeof createLazyTransaction>;
427
362
  /** Either a live driver transaction or the lazy holder above. */
428
363
  type TxLike = SqlTx | LazyTransaction;
364
+ /** What `findMany` accepts beside its filter: an ordering and a row ceiling.
365
+ * Both used to require dropping to raw SQL, and the docs said so — which is how
366
+ * a tenant's controllers filled up with hand-written SELECTs. */
367
+ interface FindManyOptions {
368
+ orderBy?: {
369
+ column: string;
370
+ direction?: "asc" | "desc";
371
+ };
372
+ limit?: number;
373
+ }
429
374
  /** The six string-keyed operations, plus an interactive `transaction`. */
430
375
  declare function createOps(tx: TxLike): {
431
376
  query(sql: string, params?: unknown[]): Promise<Row[]>;
432
377
  insert(table: string, data: Row): Promise<Row>;
378
+ /**
379
+ * INSERT the row, or UPDATE it when it collides on `onConflict`.
380
+ *
381
+ * WHY IT IS AN OPERATION rather than a recipe. "Try the insert, catch the
382
+ * unique violation, update instead" does not work here: a request runs in ONE
383
+ * Postgres transaction, so the failed insert aborts it and every later
384
+ * statement answers `current transaction is aborted`. A tenant measured that
385
+ * as 7 of 8 concurrent requests returning 500, gave up on upsert, and had a
386
+ * trigger create the row instead — a workaround that needs a new trigger for
387
+ * every table with a unique row.
388
+ *
389
+ * The conflict columns are excluded from the SET list: they are what MATCHED,
390
+ * so writing them back is at best a no-op and at worst a surprise.
391
+ */
392
+ upsert(table: string, data: Row, opts: {
393
+ onConflict: readonly string[];
394
+ }): Promise<Row>;
433
395
  update(table: string, id: string, data: Row): Promise<Row | null>;
434
396
  delete(table: string, id: string): Promise<void>;
435
397
  findById(table: string, id: string): Promise<Row | null>;
436
- findMany(table: string, query?: Row): Promise<Row[]>;
398
+ findMany(table: string, query?: Row, opts?: FindManyOptions): Promise<Row[]>;
437
399
  /**
438
400
  * Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
439
401
  * (1/(50+rank), CLAIM-N4). Operatör şema-nitelikli (C-10, M-1); GUC
@@ -448,9 +410,117 @@ declare function createOps(tx: TxLike): {
448
410
  limit?: number;
449
411
  using?: string;
450
412
  mode?: "hybrid" | "text" | "vector";
413
+ /** Nihai (RRF-sonrası) skor alt eşiği — süzme LIMIT'ten ÖNCE (FR-001). */
414
+ minScore?: number;
415
+ /** RRF-sonrası üstel tazelik çürümesi: _score * exp(-ln(2)*yaş/halfLife) (FR-004). */
416
+ recency?: {
417
+ field: string;
418
+ halfLife: string;
419
+ };
420
+ /** Chunk-modunda satır başına dönen en iyi blok sayısı (1..10, vars. 3; FR-015). */
421
+ blocksPerRow?: number;
422
+ /** Filtrelenmiş küme üzerinde kolon başına top-20 değer sayacı —
423
+ * dönüş dizisinin `_facets` özelliği (FR-027). */
424
+ facets?: string[];
425
+ /** Satır-modunda FTS eşleşme vurgusu: ts_headline ile `_highlight`
426
+ * alanı; chunk-modda no-op — bloklar zaten eşleşen kesittir (FR-025). */
427
+ highlight?: boolean;
428
+ /** Validity'li tabloda zaman penceresi: varsayılan yalnız güncel;
429
+ * "all" tüm versiyonlar; {asOf} o andaki geçerli versiyon (FR-029). */
430
+ validity?: "all" | {
431
+ asOf: string;
432
+ };
433
+ /** Alan-boost (FR-030): skor * (1 + w·x/(1+x)) — sınırlı, dış servissiz. */
434
+ boost?: {
435
+ field: string;
436
+ weight: number;
437
+ };
438
+ }): Promise<Row[]>;
439
+ /**
440
+ * similar (T018, FR-022): "bu satıra benzeyenler". Hedef vektör DB'den
441
+ * okunur — satır-modu satırın kendi kolonu, chunk-modu parent chunk'larının
442
+ * şema-nitelikli avg'ı — ve ana arama search'ün vector yoluyla koşar;
443
+ * kaynak satır sonuçtan düşer. İKİ tur BİLİNÇLİ (plan T018): target-CTE'li
444
+ * tek SQL'de "id yok" ile "0 komşu" ayrılamazdı; +1 küçük turla id-yokluğu
445
+ * adlandırılmış hataya çevrilir. Sonuç şekli search ile aynı (FR-015).
446
+ */
447
+ similar(table: string, id: string, opts?: {
448
+ where?: Record<string, unknown>;
449
+ limit?: number;
450
+ using?: string;
451
+ minScore?: number;
452
+ recency?: {
453
+ field: string;
454
+ halfLife: string;
455
+ };
456
+ blocksPerRow?: number;
457
+ validity?: "all" | {
458
+ asOf: string;
459
+ };
460
+ boost?: {
461
+ field: string;
462
+ weight: number;
463
+ };
451
464
  }): Promise<Row[]>;
465
+ /**
466
+ * recommend (T018, FR-023): positive/negative id kümelerinden öneri.
467
+ * Hedef vektör DB-İÇİ CTE'lerle türer: pos = avg(embedding of positives),
468
+ * negative varsa hedef = pos.v + (pos.v - neg.v) — pgvector avg agregası
469
+ * ve +/- operatörleri ŞEMA-NİTELİKLİ (M-1: search_path'e güvenilmez).
470
+ * Vektör matematiği istemciye inmez; bulunamayan positive/negative
471
+ * adlandırılmış hatadır ve kaynak id'ler sonuçtan düşer.
472
+ */
473
+ recommend(table: string, opts: {
474
+ positive: unknown[];
475
+ negative?: unknown[];
476
+ where?: Record<string, unknown>;
477
+ limit?: number;
478
+ using?: string;
479
+ minScore?: number;
480
+ recency?: {
481
+ field: string;
482
+ halfLife: string;
483
+ };
484
+ blocksPerRow?: number;
485
+ validity?: "all" | {
486
+ asOf: string;
487
+ };
488
+ boost?: {
489
+ field: string;
490
+ weight: number;
491
+ };
492
+ }): Promise<Row[]>;
493
+ /**
494
+ * supersede (T021, FR-029, C-9): validity'li tabloda satırın YENİ
495
+ * versiyonunu TEK savepoint'te yazar — eski satır kapatılır
496
+ * (valid_to = now(), superseded_by = yeni pk), yeni satır eklenir, dönüş
497
+ * yeni satırdır. Yeni pk CLIENT'ta üretilir (row'da verilmemişse
498
+ * randomUUID — declared şemaların uuid().defaultRandom() standardı) ki
499
+ * kapatma UPDATE'i INSERT'ten ÖNCE koşabilsin: 0 satır = zaten superseded
500
+ * ya da yok → INSERT hiç denenmez; INSERT hatası ise savepoint'le
501
+ * kapatmayı da geri sarar — yarım supersede diye bir durum yoktur.
502
+ */
503
+ supersede(table: string, id: string, row: Row): Promise<Row>;
452
504
  /** A real SAVEPOINT inside the request's transaction. */
453
505
  transaction<T>(cb: (t: unknown) => Promise<T>): Promise<T>;
506
+ /**
507
+ * Run `fn` against a handle bound to a SAVEPOINT, so a failure inside it
508
+ * rolls back only what that handle wrote and the request can keep writing.
509
+ *
510
+ * WHY THE HANDLE IS AN ARGUMENT. The obvious shape — `attempt(async () => {
511
+ * ... Database.insert(...) ... })`, with no parameter — would have to point
512
+ * the ambient `Database` at the savepoint for the duration, and a request is
513
+ * concurrent with itself: `Promise.all([Database.insert(a),
514
+ * Database.attempt(...)])` would put `a` inside the savepoint and roll it
515
+ * back with it. Silent data loss, and the same interleaving this file already
516
+ * refuses for `asService()`. Passing the handle makes the boundary something
517
+ * you can see in the code that crosses it.
518
+ *
519
+ * Postgres, not us: the savepoint is released on success and rolled back on
520
+ * failure by the driver, so an aborted statement inside `fn` does not poison
521
+ * the surrounding transaction.
522
+ */
523
+ attempt<T>(fn: (tx: DBOps) => Promise<T>): Promise<T>;
454
524
  /**
455
525
  * Execute a whole transaction plan — what `Database.transaction(fn)` builds.
456
526
  *
@@ -800,7 +870,7 @@ interface RuntimeHooks {
800
870
  __requestALS: typeof __requestALS;
801
871
  }
802
872
  /** The module singletons the engine injects, minus the two it owns itself. */
803
- type ModuleClients = Partial<Pick<RuntimeServices, "Documents" | "Storage" | "Notifications" | "Flags" | "Realtime" | "Purchases" | "Secrets">>;
873
+ type ModuleClients = Partial<Pick<RuntimeServices, "Documents" | "Storage" | "Notifications" | "Flags" | "Realtime" | "Secrets">>;
804
874
  interface CreateAppOptions {
805
875
  /**
806
876
  * Vault'tan TEK secret okuma (FR-025): sorgu-anı embedding'in anahtarı
@@ -854,4 +924,4 @@ interface App {
854
924
  */
855
925
  declare function createApp(opts: CreateAppOptions): Promise<App>;
856
926
 
857
- export { type App as A, BootRefused as B, Cache as C, Database as D, type EgressPolicy as E, Flags as F, effectiveAuth as G, hostAllowed as H, installEgressFence as I, loadConfig as J, makeMemoryCache as K, type LimitState as L, type ModuleClients as M, Notifications as N, matchRoute as O, Purchases as P, quoteIdent as Q, Realtime as R, Secrets as S, scrubSecrets as T, withTables as U, __getRuntime as _, Documents as a, Log as b, type PurchasesService as c, type RequestStore as d, type RuntimeServices as e, type SpendOptions as f, Storage as g, type StoreEnv as h, __requestALS as i, __runWithRuntime as j, __setRuntime as k, AuthVerifier as l, type CreateAppOptions as m, type EngineConfig as n, RateLimiter as o, type RequestDatabase as p, type RouteEntry as q, type RuntimeHooks as r, type ScrubResult as s, type SqlDriver as t, type SqlTx as u, buildRouteTable as v, createApp as w, createLazyTransaction as x, createOps as y, createRequestDatabase as z };
927
+ export { type App as A, BootRefused as B, Cache as C, Database as D, type EgressPolicy as E, Flags as F, makeMemoryCache as G, matchRoute as H, quoteIdent as I, scrubSecrets as J, withTables as K, Log as L, type ModuleClients as M, Notifications as N, Realtime as R, Secrets as S, __getRuntime as _, Documents as a, type RequestStore as b, type RuntimeServices as c, Storage as d, __requestALS as e, __runWithRuntime as f, __setRuntime as g, AuthVerifier as h, type CreateAppOptions as i, type EngineConfig as j, RateLimiter as k, type RequestDatabase as l, type RouteEntry as m, type RuntimeHooks as n, type ScrubResult as o, type SqlDriver as p, type SqlTx as q, buildRouteTable as r, createApp as s, createLazyTransaction as t, createOps as u, createRequestDatabase as v, effectiveAuth as w, hostAllowed as x, installEgressFence as y, loadConfig as z };