@palbase/backend 23.0.0 → 24.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 (74) hide show
  1. package/dist/bin/palbase-backend.cjs +695 -61
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -5
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/{chunk-FSGSB42K.js → chunk-7Z6MGMXQ.js} +64 -4
  6. package/dist/chunk-7Z6MGMXQ.js.map +1 -0
  7. package/dist/{chunk-OMRTHM4X.js → chunk-H3JAISUY.js} +136 -1
  8. package/dist/chunk-H3JAISUY.js.map +1 -0
  9. package/dist/{chunk-REZU6UKT.js → chunk-NXDH6VQJ.js} +549 -42
  10. package/dist/chunk-NXDH6VQJ.js.map +1 -0
  11. package/dist/{chunk-W5ODXPY3.js → chunk-P2Q27SGP.js} +32 -3
  12. package/dist/chunk-P2Q27SGP.js.map +1 -0
  13. package/dist/{chunk-ZC6Q2BRD.js → chunk-T5IOSOE5.js} +7 -2
  14. package/dist/chunk-T5IOSOE5.js.map +1 -0
  15. package/dist/{chunk-HAF67F2H.js → chunk-ZUGY7RGS.js} +86 -3
  16. package/dist/chunk-ZUGY7RGS.js.map +1 -0
  17. package/dist/db/index.cjs +115 -3
  18. package/dist/db/index.cjs.map +1 -1
  19. package/dist/db/index.d.cts +2 -2
  20. package/dist/db/index.d.ts +2 -2
  21. package/dist/db/index.js +2 -2
  22. package/dist/{endpoint-BavvbW4P.d.ts → endpoint-0_DGBajf.d.ts} +168 -9
  23. package/dist/{endpoint-i8TTCohk.d.cts → endpoint-CcQ1a36a.d.cts} +168 -9
  24. package/dist/engine/index.cjs +684 -48
  25. package/dist/engine/index.cjs.map +1 -1
  26. package/dist/engine/index.d.cts +4 -4
  27. package/dist/engine/index.d.ts +4 -4
  28. package/dist/engine/index.js +4 -4
  29. package/dist/{index-B3jmmItD.d.ts → index-CJiJU9ux.d.ts} +209 -36
  30. package/dist/{index-Bmvx1EvJ.d.cts → index-D-4-PNuQ.d.cts} +209 -36
  31. package/dist/{index-B7YBEG5w.d.ts → index-D17r-MKb.d.ts} +177 -7
  32. package/dist/{index-E7OscPJT.d.cts → index-DRFxf07H.d.cts} +177 -7
  33. package/dist/index.cjs +269 -7
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +54 -12
  36. package/dist/index.d.ts +54 -12
  37. package/dist/index.js +54 -11
  38. package/dist/index.js.map +1 -1
  39. package/dist/openapi/index.cjs +45 -5
  40. package/dist/openapi/index.cjs.map +1 -1
  41. package/dist/openapi/index.d.cts +9 -4
  42. package/dist/openapi/index.d.ts +9 -4
  43. package/dist/openapi/index.js +35 -11
  44. package/dist/openapi/index.js.map +1 -1
  45. package/dist/{registry-C3H2uPeZ.d.cts → registry-1X-skBNu.d.cts} +101 -7
  46. package/dist/{registry-DY3d9l1k.d.ts → registry-CEod_5sz.d.ts} +101 -7
  47. package/dist/test/index.cjs +509 -9
  48. package/dist/test/index.cjs.map +1 -1
  49. package/dist/test/index.d.cts +35 -3
  50. package/dist/test/index.d.ts +35 -3
  51. package/dist/test/index.js +507 -8
  52. package/dist/test/index.js.map +1 -1
  53. package/docs/README.md +4 -4
  54. package/docs/database.md +115 -11
  55. package/docs/getting-started.md +5 -4
  56. package/docs/llms-full.txt +385 -89
  57. package/docs/migrations.md +81 -59
  58. package/docs/schema.md +82 -2
  59. package/docs/services.md +98 -9
  60. package/package.json +3 -2
  61. package/stager/return_types.js +23 -0
  62. package/template/AGENTS.md +121 -41
  63. package/template/controllers/notes.controller.ts +64 -0
  64. package/template/package.json +1 -1
  65. package/template/services/note.service.ts +74 -0
  66. package/template/tsconfig.json +11 -1
  67. package/dist/chunk-FSGSB42K.js.map +0 -1
  68. package/dist/chunk-HAF67F2H.js.map +0 -1
  69. package/dist/chunk-OMRTHM4X.js.map +0 -1
  70. package/dist/chunk-REZU6UKT.js.map +0 -1
  71. package/dist/chunk-W5ODXPY3.js.map +0 -1
  72. package/dist/chunk-Y5HXVUMP.js +0 -90
  73. package/dist/chunk-Y5HXVUMP.js.map +0 -1
  74. package/dist/chunk-ZC6Q2BRD.js.map +0 -1
@@ -1,5 +1,5 @@
1
- export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, E as EnvTypedDatabase, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, S as SchemaDef, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from '../index-Bmvx1EvJ.cjs';
2
- export { M as Materialized, b1 as Ref, b3 as TxColumnExpr, b4 as TxInsertShape, b6 as TxNow, T as TxPlanBody, b7 as TxPlanError, b8 as TxPlanHandle, b9 as TxPlanOpResult, ba as TxPlanRejection, f as TxPlanResponse, bb as TxRefError, bc as TxRow, bd as TxRows, be as TxSelectOptions, bf as TxSetShape, bh as TxTable, bi as TxWhere, br as dec, bt as inc, bu as now } from '../endpoint-i8TTCohk.cjs';
1
+ export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, E as EnvTypedDatabase, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, S as SchemaDef, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from '../index-D-4-PNuQ.cjs';
2
+ export { M as Materialized, b1 as Ref, b3 as TxColumnExpr, b4 as TxInsertShape, b6 as TxNow, T as TxPlanBody, b7 as TxPlanError, b8 as TxPlanHandle, b9 as TxPlanOpResult, ba as TxPlanRejection, g as TxPlanResponse, bb as TxRefError, bc as TxRow, bd as TxRows, be as TxSelectOptions, bf as TxSetShape, bh as TxTable, bi as TxWhere, bs as dec, bu as inc, bv as now } from '../endpoint-CcQ1a36a.cjs';
3
3
  import './env.cjs';
4
4
  import '../stack.cjs';
5
5
  import 'zod';
@@ -1,5 +1,5 @@
1
- export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, E as EnvTypedDatabase, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, S as SchemaDef, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from '../index-B3jmmItD.js';
2
- export { M as Materialized, b1 as Ref, b3 as TxColumnExpr, b4 as TxInsertShape, b6 as TxNow, T as TxPlanBody, b7 as TxPlanError, b8 as TxPlanHandle, b9 as TxPlanOpResult, ba as TxPlanRejection, f as TxPlanResponse, bb as TxRefError, bc as TxRow, bd as TxRows, be as TxSelectOptions, bf as TxSetShape, bh as TxTable, bi as TxWhere, br as dec, bt as inc, bu as now } from '../endpoint-BavvbW4P.js';
1
+ export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, E as EnvTypedDatabase, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, S as SchemaDef, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from '../index-CJiJU9ux.js';
2
+ export { M as Materialized, b1 as Ref, b3 as TxColumnExpr, b4 as TxInsertShape, b6 as TxNow, T as TxPlanBody, b7 as TxPlanError, b8 as TxPlanHandle, b9 as TxPlanOpResult, ba as TxPlanRejection, g as TxPlanResponse, bb as TxRefError, bc as TxRow, bd as TxRows, be as TxSelectOptions, bf as TxSetShape, bh as TxTable, bi as TxWhere, bs as dec, bu as inc, bv as now } from '../endpoint-0_DGBajf.js';
3
3
  import './env.js';
4
4
  import '../stack.js';
5
5
  import 'zod';
package/dist/db/index.js CHANGED
@@ -17,14 +17,14 @@ import {
17
17
  timestamp,
18
18
  uuid,
19
19
  vector
20
- } from "../chunk-HAF67F2H.js";
20
+ } from "../chunk-ZUGY7RGS.js";
21
21
  import {
22
22
  TxPlanError,
23
23
  TxRefError,
24
24
  dec,
25
25
  inc,
26
26
  now
27
- } from "../chunk-W5ODXPY3.js";
27
+ } from "../chunk-P2Q27SGP.js";
28
28
  import "../chunk-7D4SUZUM.js";
29
29
  export {
30
30
  EXTENSION_DEPENDENCIES,
@@ -142,7 +142,7 @@ declare function defineMiddleware(fn: MiddlewareHandler): MiddlewareHandler;
142
142
  * # Wire contract
143
143
  *
144
144
  * The JSON this file emits is consumed by
145
- * `modules/backend/internal/management/tx_program.go`. That decoder rejects
145
+ * the plan executor in `engine/db.ts`. That executor rejects
146
146
  * unknown fields at every level, so an op carries EXACTLY the fields its kind
147
147
  * takes. Everything here that looks like a needless restriction is one of the
148
148
  * server's rules made visible early:
@@ -206,7 +206,12 @@ interface TxWireGuard {
206
206
  /** One operation in the wire plan. Fields are omitted, never null: the decoder
207
207
  * rejects a field that does not belong to the op's kind. */
208
208
  interface TxWireOp {
209
- op: "insert" | "insertMany" | "update" | "delete" | "select";
209
+ /** upsert and insertMany: the columns Postgres matches on. */
210
+ onConflict?: readonly string[];
211
+ /** insertMany only: what a collision does. Absent means no ON CONFLICT clause
212
+ * at all, which is what every insertMany did before this option existed. */
213
+ action?: "ignore" | "update";
214
+ op: "insert" | "insertMany" | "upsert" | "update" | "delete" | "select";
210
215
  table: string;
211
216
  values?: Record<string, TxWireValue>;
212
217
  rows?: Record<string, TxWireValue>[];
@@ -387,7 +392,36 @@ interface TxTable<Row, Insert> {
387
392
  *
388
393
  * An empty list writes nothing and sends nothing.
389
394
  */
390
- insertMany(rows: readonly TxInsertShape<Insert>[]): TxRows<Row>;
395
+ /**
396
+ * Insert many rows in ONE statement, optionally choosing what a collision does.
397
+ *
398
+ * Without `opts` this is a plain multi-row INSERT and a collision aborts the
399
+ * transaction — the behaviour every call had before the option existed.
400
+ *
401
+ * `action: "ignore"` emits `ON CONFLICT DO NOTHING`, which is how "insert the
402
+ * ones that are new" becomes one round-trip instead of one per row with a
403
+ * 23505 caught around each. **The returned rows are the ones actually
404
+ * INSERTED**: a row that collided is skipped, so it is absent from the result
405
+ * — Postgres does not return what it did not write.
406
+ *
407
+ * `action: "update"` emits `ON CONFLICT DO UPDATE`, setting every non-conflict
408
+ * column from the incoming row, and every row comes back.
409
+ */
410
+ insertMany(rows: readonly TxInsertShape<Insert>[], opts?: {
411
+ onConflict: readonly Extract<keyof Row, string>[];
412
+ action?: "ignore" | "update";
413
+ }): TxRows<Row>;
414
+ /**
415
+ * Insert the row, or update it when it collides on `onConflict` — inside the
416
+ * plan's savepoint, with the same meaning `tables.<t>.upsert()` has outside it.
417
+ *
418
+ * It is an operation because the alternative is not writable here: a failed
419
+ * insert aborts the whole transaction, so "try, then fall back" cannot be two
420
+ * plan steps.
421
+ */
422
+ upsert(values: TxInsertShape<Insert>, options: {
423
+ onConflict: readonly Extract<keyof Row, string>[];
424
+ }): TxRows<Row>;
391
425
  /**
392
426
  * Update every row matching `where`. The filter comes first because it is the
393
427
  * dangerous half: an update whose `where` you got wrong rewrites rows you
@@ -512,6 +546,57 @@ declare class NotFound extends NamedHttpError {
512
546
  declare class Conflict extends NamedHttpError {
513
547
  constructor(message?: string, code?: string, data?: unknown);
514
548
  }
549
+ /**
550
+ * 409 — a write was refused because it would duplicate an existing row.
551
+ * Carries the NAME of the unique constraint Postgres named (`users_email_key`).
552
+ *
553
+ * The engine produces it: a statement rejected with SQLSTATE `23505` is
554
+ * converted here rather than surfacing as an opaque driver error (see
555
+ * `engine/db.ts`, `diagnosingDriver`). What that removes is the string match —
556
+ * before this, the only way to act on a duplicate was to test the driver
557
+ * message for "duplicate key value violates unique constraint", a contract
558
+ * nobody signed that breaks on a Postgres upgrade, a locale, or a constraint
559
+ * rename, silently and in production.
560
+ *
561
+ * THE NAME IS A FIELD AND STAYS OUT OF THE DEFAULT MESSAGE. The two are not
562
+ * the same audience. `constraint` is read by the code that catches this — the
563
+ * developer, who already knows the schema. `errorDescription` is the HTTP
564
+ * response body, and an UNCAUGHT duplicate puts it in front of the
565
+ * application's end user: `users_email_key` there discloses how the schema is
566
+ * built to whoever sent the request. The platform's own data API took the same
567
+ * decision one surface over and wrote down why —
568
+ * `v2/internal/modules/database/internal/handler/pgerror.go:83-87` collapses
569
+ * every 23xxx to a generic conflict, "never disclose the constraint/column
570
+ * name". A thrower who WANTS the name on the wire passes it deliberately
571
+ * (`new UniqueViolation(c, \`\${c} already exists\`)`, or through `data`).
572
+ *
573
+ * @example
574
+ * try {
575
+ * await Database.tables.users.insert({ email });
576
+ * } catch (e) {
577
+ * if (UniqueViolation.is(e) && e.constraint === "users_email_key") {
578
+ * throw new Conflict("That email is taken", "email_taken");
579
+ * }
580
+ * throw e;
581
+ * }
582
+ */
583
+ declare class UniqueViolation extends Conflict {
584
+ /**
585
+ * Whether `e` is a unique violation — REGARDLESS of which copy of this SDK
586
+ * constructed it.
587
+ *
588
+ * Use this instead of `instanceof`. Measured on a live stack: a controller
589
+ * bundle INLINES its own copy of `@palbase/backend`, and the engine that
590
+ * raises this error is the runtime's copy. Two copies, two class identities,
591
+ * and `e instanceof UniqueViolation` is false in the one place a caller
592
+ * writes it — a check that reads as correct and silently never matches.
593
+ */
594
+ static is(e: unknown): e is UniqueViolation;
595
+ /** The unique constraint the statement violated, as Postgres named it.
596
+ * `""` when the driver did not say which — see `engine/db.ts`. */
597
+ readonly constraint: string;
598
+ constructor(constraint: string, message?: string, code?: string, data?: unknown);
599
+ }
515
600
  /** A single field-level validation failure carried by {@link BadRequest}. */
516
601
  interface FieldError {
517
602
  /** The offending field's name (dotted path for nested fields). */
@@ -1546,6 +1631,13 @@ interface PalbaseFlagsClient {
1546
1631
  /**
1547
1632
  * Realtime broadcast surface (backend handler → subscribed clients).
1548
1633
  *
1634
+ * **Every channel must be DECLARED, or the join is refused.** Channel names are
1635
+ * not free text: `channels.ts` at the project root publishes which names exist
1636
+ * and who may subscribe, publish, or write state, and a client joining a name no
1637
+ * declaration matches is rejected without ever reaching your code. Declare with
1638
+ * `ownerOnly()`, `publicChannel()`, or a custom `authorize(ctx)` — see
1639
+ * {@link defineChannels}. Broadcasting to an undeclared channel reaches nobody.
1640
+ *
1549
1641
  * Backend-side this is BROADCAST-ONLY: a handler pushes an event to a channel
1550
1642
  * and every client subscribed to that channel (via the client SDK's
1551
1643
  * `pb.realtime.channel(...).on(...)`) receives it. There is no `subscribe()` on
@@ -1554,9 +1646,10 @@ interface PalbaseFlagsClient {
1554
1646
  * once the broadcast is accepted (or with an `error` if it could not be sent),
1555
1647
  * but it never blocks the handler waiting on subscribers.
1556
1648
  *
1557
- * Channel naming is app-defined and arbitrary (e.g. `"room:42"`, `"orders"`).
1558
- * The Palbase-managed `flags:<ref>` channels are internal author channels for
1559
- * your own features.
1649
+ * Channel names are yours to choose (e.g. `"room:42"`, `"orders"`) but not yours
1650
+ * to leave undeclared: each must match a pattern in `channels.ts`, which is where
1651
+ * `"room:{roomId}"` is authorized. The Palbase-managed `flags:<ref>` channels are
1652
+ * internal — author channels for your own features.
1560
1653
  *
1561
1654
  * @example
1562
1655
  * // Notify everyone in a chat room that a message landed:
@@ -1816,9 +1909,36 @@ interface RateLimitConfig {
1816
1909
  /** The six raw string-keyed DB operations shared by `DBClient` and the
1817
1910
  * transaction-scoped client. */
1818
1911
  interface DBOps {
1819
- /** Run a read-only SQL query (executes in a READ ONLY transaction). */
1912
+ /**
1913
+ * Run SQL on the REQUEST'S OWN transaction.
1914
+ *
1915
+ * This is **not** read-only. Nothing marks that transaction `READ ONLY`, so a
1916
+ * write passed here executes and commits with the request — a plain `UPDATE`, a
1917
+ * write hidden in a CTE, or a function that writes. What stands between a
1918
+ * statement and your data is RLS and the role the request runs as, exactly as
1919
+ * for every other operation on this client.
1920
+ *
1921
+ * `INSERT … ON CONFLICT DO UPDATE` therefore works here; `tables.<t>.upsert()`
1922
+ * is the same thing with types. Array parameters are encoded as Postgres array
1923
+ * literals, so `= ANY($1::uuid[])` takes a plain JS array.
1924
+ *
1925
+ * This comment used to claim a `READ ONLY` transaction. It was wrong, and the
1926
+ * cost was measured: a tenant read it, concluded an upsert was impossible from
1927
+ * here, abandoned `ON CONFLICT` and wrote a database trigger instead.
1928
+ */
1820
1929
  query(sql: string, params?: unknown[]): Promise<Record<string, unknown>[]>;
1821
1930
  insert(table: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
1931
+ /**
1932
+ * Insert the row, or update it when it collides on `onConflict` — one
1933
+ * statement, so two concurrent callers cannot both lose.
1934
+ *
1935
+ * Read-then-write cannot be made safe here by catching the unique violation: a
1936
+ * request runs in one transaction, so the failed insert aborts it and every
1937
+ * later statement answers `current transaction is aborted`.
1938
+ */
1939
+ upsert(table: string, data: Record<string, unknown>, opts: {
1940
+ onConflict: readonly string[];
1941
+ }): Promise<Record<string, unknown>>;
1822
1942
  /**
1823
1943
  * Update the row with the given id and resolve to the updated row, or `null`
1824
1944
  * if no row matched (the id is absent, or the row is hidden by RLS). This is
@@ -1862,7 +1982,26 @@ interface DBOps {
1862
1982
  /** Validity'li tabloda satırın yeni versiyonu — eski kapanır (valid_to/superseded_by),
1863
1983
  * yenisi eklenir; TEK savepoint, dönüş yeni satır (FR-029, C-9). */
1864
1984
  supersede(table: string, id: string, row: Record<string, unknown>): Promise<Record<string, unknown>>;
1865
- findMany(table: string, query?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
1985
+ /** Filtre + sıralama + sayfa. `offset` yalnız `limit` ile birlikte geçerlidir
1986
+ * (limitsiz offset sayfa değildir; motor adıyla reddeder — FR-004). */
1987
+ /** Update every row the filter matches, in one statement; resolves to them.
1988
+ * An EMPTY filter is refused — a whole-table write is not something to reach
1989
+ * by accident. */
1990
+ updateMany(table: string, where: Record<string, unknown>, set: Record<string, unknown>): Promise<Record<string, unknown>[]>;
1991
+ /** Delete every row the filter matches; resolves to how many. Empty filter
1992
+ * refused, for the same reason and more so. */
1993
+ deleteMany(table: string, where: Record<string, unknown>): Promise<number>;
1994
+ /** How many rows match — the half of pagination limit/offset cannot supply.
1995
+ * An empty filter is legitimate here: counting is a read. */
1996
+ count(table: string, where?: Record<string, unknown>): Promise<number>;
1997
+ findMany(table: string, query?: Record<string, unknown>, opts?: {
1998
+ orderBy?: {
1999
+ column: string;
2000
+ direction?: "asc" | "desc";
2001
+ };
2002
+ limit?: number;
2003
+ offset?: number;
2004
+ }): Promise<Record<string, unknown>[]>;
1866
2005
  /** "Bu satıra benzer satırlar" (FR-022) — satır-modunda kolon vektörüyle,
1867
2006
  * chunk-modunda chunk-ortalamasıyla; kendisi hariç. */
1868
2007
  similar(table: string, id: string, opts?: {
@@ -1878,6 +2017,18 @@ interface DBOps {
1878
2017
  weight: number;
1879
2018
  };
1880
2019
  }): Promise<Record<string, unknown>[]>;
2020
+ /** D-021 (FR-027): kolon sayaçları bağımsız dönüşle — search'ün dizi-üstü
2021
+ * `_facets` özelliği serileşmede kaybolur; ciddi sözleşme budur. */
2022
+ facets(table: string, params: {
2023
+ facets: string[];
2024
+ where?: Record<string, unknown>;
2025
+ validity?: "all" | {
2026
+ asOf: string;
2027
+ };
2028
+ }): Promise<Record<string, {
2029
+ value: string | null;
2030
+ count: number;
2031
+ }[]>>;
1881
2032
  /** Çok-örnekli öneri (FR-023): avg(pos) + (avg(pos) − avg(neg)); positive'ler sonuç dışı. */
1882
2033
  recommend(table: string, opts: {
1883
2034
  positive: string[];
@@ -1913,6 +2064,14 @@ interface DBClient extends DBOps {
1913
2064
  * `.expectOne(…)`; without it a declared expectation degrades to a generic 409.
1914
2065
  */
1915
2066
  txPlan(plan: TxPlanBody): Promise<TxPlanResponse>;
2067
+ /**
2068
+ * Run `fn` inside a SAVEPOINT on this request's transaction, so a failure in
2069
+ * it rolls back only what `fn`'s handle wrote.
2070
+ *
2071
+ * The handle is a PARAMETER on purpose: an ambient swap would capture writes
2072
+ * a concurrent `Promise.all` branch made outside the boundary.
2073
+ */
2074
+ attempt<T>(fn: (tx: DBOps) => Promise<T>): Promise<T>;
1916
2075
  /**
1917
2076
  * Return a sibling DB client that bypasses Row-Level Security by running as
1918
2077
  * the `service_role` (BYPASSRLS). Use sparingly and explicitly — the default
@@ -2213,4 +2372,4 @@ type Middleware = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise
2213
2372
  */
2214
2373
  type AuthSpec = boolean | Partial<AuthConfig>;
2215
2374
 
2216
- export { type PalbaseCreateLinkParams as $, type AuthSpec as A, BadRequest as B, type CacheClient as C, type DBClient as D, type ErrorDef as E, type FileContext as F, type PalbaseAttestiOSParams as G, HttpError as H, type PalbaseAttestiOSResult as I, type PalbaseAuthClient as J, type PalbaseBatchOverrideOperation as K, type Logger as L, type Materialized as M, NotFound as N, type PalbaseBatchSetOverridesResult as O, type PalbaseDocsClient as P, type PalbaseBindDeviceParams as Q, type RateLimitConfig as R, type SecretsService as S, type TxPlanBody as T, type PalbaseClearAllOverridesResult as U, type PalbaseClearOverrideResult as V, type PalbaseCohortQueryInput as W, type PalbaseCohortResult as X, type PalbaseCollectionRef as Y, type PalbaseCountQueryInput as Z, type PalbaseCountResult as _, type PalbaseFlagsClient as a, type PalbaseVerifyRequestSignatureParams as a$, type PalbaseDeviceInfo as a0, type PalbaseDeviceTokenView as a1, type PalbaseDocumentRef as a2, type PalbaseDocumentSnapshot as a3, type PalbaseEmailClient as a4, type PalbaseEmailSendParams as a5, type PalbaseEmailSendResponse as a6, type PalbaseEventNamesResult as a7, type PalbaseEventsQueryInput as a8, type PalbaseEventsResult as a9, type PalbaseMatchParams as aA, type PalbaseMultiChannelResponse as aB, type PalbaseOverviewResult as aC, type PalbasePreferences as aD, type PalbasePreferencesClient as aE, type PalbasePushClient as aF, type PalbasePushSendParams as aG, type PalbasePushSendResponse as aH, type PalbaseQrCodeOptions as aI, type PalbaseQuerySnapshot as aJ, type PalbaseRegisterDeviceParams as aK, type PalbaseRetentionQueryInput as aL, type PalbaseRetentionResult as aM, type PalbaseSession as aN, type PalbaseSetOverrideResult as aO, type PalbaseSetOverridesResult as aP, type PalbaseSignedUrlResponse as aQ, type PalbaseSmsClient as aR, type PalbaseSmsSendParams as aS, type PalbaseSmsSendResponse as aT, type PalbaseTransformOptions as aU, type PalbaseUpdateLinkParams as aV, type PalbaseUploadOptions as aW, type PalbaseUser as aX, type PalbaseUserDetailResult as aY, type PalbaseUsersQueryInput as aZ, type PalbaseUsersResult as a_, type PalbaseFileObject as aa, type PalbaseFlag as ab, type PalbaseFlagContext as ac, type PalbaseFlagSource as ad, type PalbaseFlagValue as ae, type PalbaseFlagVariant as af, type PalbaseFlagsServiceClient as ag, type PalbaseFunctionsClient as ah, type PalbaseFunnelQueryInput as ai, type PalbaseFunnelResult as aj, type PalbaseIdentifyTraits as ak, type PalbaseInboxClient as al, type PalbaseInboxListOptions as am, type PalbaseInboxListResult as an, type PalbaseInboxMessage as ao, type PalbaseInboxSendParams as ap, type PalbaseInboxSendResponse as aq, type PalbaseInitialLink as ar, type PalbaseInvokeOptions as as, type PalbaseLink as at, type PalbaseLinkAnalytics as au, type PalbaseLinkDetails as av, type PalbaseLinksClient as aw, type PalbaseListLinksOptions as ax, type PalbaseListLinksResult as ay, type PalbaseListOptions as az, type PalbaseNotificationsClient as b, type PalbaseWhereOperator as b0, type Ref as b1, TooManyRequests as b2, type TxColumnExpr as b3, type TxInsertShape as b4, type TxInsertValue as b5, type TxNow as b6, TxPlanError as b7, type TxPlanHandle as b8, type TxPlanOpResult as b9, type TxPlanRejection as ba, TxRefError as bb, type TxRow as bc, type TxRows as bd, type TxSelectOptions as be, type TxSetShape as bf, type TxSetValue as bg, type TxTable as bh, type TxWhere as bi, type TxWireExpr as bj, type TxWireGuard as bk, type TxWireOp as bl, type TxWireRef as bm, type TxWireValue as bn, Unauthorized as bo, type User as bp, type VerifiedDevice as bq, dec as br, defineMiddleware as bs, inc as bt, now as bu, type PalbaseRealtimeClient as c, type PalbaseStorageClient as d, type PalbaseBucketClient as e, type TxPlanResponse as f, type PalbaseResult as g, type AuthConfig as h, type ClientInfo as i, Conflict as j, type DBOps as k, type ErrorMap as l, type ErrorThrowers as m, Forbidden as n, type HttpMethod as o, type Middleware as p, type MiddlewareContext as q, type MiddlewareHandler as r, type PBRequest as s, PalError as t, type PalbaseAnalyticsClient as u, type PalbaseAnalyticsManagementNamespace as v, type PalbaseAnalyticsProperties as w, type PalbaseAnalyticsQueryNamespace as x, type PalbaseAttestAndroidParams as y, type PalbaseAttestAndroidResult as z };
2375
+ export { type PalbaseCreateLinkParams as $, type AuthSpec as A, BadRequest as B, type CacheClient as C, type DBClient as D, type ErrorDef as E, type FileContext as F, type PalbaseAttestiOSParams as G, HttpError as H, type PalbaseAttestiOSResult as I, type PalbaseAuthClient as J, type PalbaseBatchOverrideOperation as K, type Logger as L, type Materialized as M, NotFound as N, type PalbaseBatchSetOverridesResult as O, type PalbaseDocsClient as P, type PalbaseBindDeviceParams as Q, type RateLimitConfig as R, type SecretsService as S, type TxPlanBody as T, type PalbaseClearAllOverridesResult as U, type PalbaseClearOverrideResult as V, type PalbaseCohortQueryInput as W, type PalbaseCohortResult as X, type PalbaseCollectionRef as Y, type PalbaseCountQueryInput as Z, type PalbaseCountResult as _, type PalbaseFlagsClient as a, type PalbaseVerifyRequestSignatureParams as a$, type PalbaseDeviceInfo as a0, type PalbaseDeviceTokenView as a1, type PalbaseDocumentRef as a2, type PalbaseDocumentSnapshot as a3, type PalbaseEmailClient as a4, type PalbaseEmailSendParams as a5, type PalbaseEmailSendResponse as a6, type PalbaseEventNamesResult as a7, type PalbaseEventsQueryInput as a8, type PalbaseEventsResult as a9, type PalbaseMatchParams as aA, type PalbaseMultiChannelResponse as aB, type PalbaseOverviewResult as aC, type PalbasePreferences as aD, type PalbasePreferencesClient as aE, type PalbasePushClient as aF, type PalbasePushSendParams as aG, type PalbasePushSendResponse as aH, type PalbaseQrCodeOptions as aI, type PalbaseQuerySnapshot as aJ, type PalbaseRegisterDeviceParams as aK, type PalbaseRetentionQueryInput as aL, type PalbaseRetentionResult as aM, type PalbaseSession as aN, type PalbaseSetOverrideResult as aO, type PalbaseSetOverridesResult as aP, type PalbaseSignedUrlResponse as aQ, type PalbaseSmsClient as aR, type PalbaseSmsSendParams as aS, type PalbaseSmsSendResponse as aT, type PalbaseTransformOptions as aU, type PalbaseUpdateLinkParams as aV, type PalbaseUploadOptions as aW, type PalbaseUser as aX, type PalbaseUserDetailResult as aY, type PalbaseUsersQueryInput as aZ, type PalbaseUsersResult as a_, type PalbaseFileObject as aa, type PalbaseFlag as ab, type PalbaseFlagContext as ac, type PalbaseFlagSource as ad, type PalbaseFlagValue as ae, type PalbaseFlagVariant as af, type PalbaseFlagsServiceClient as ag, type PalbaseFunctionsClient as ah, type PalbaseFunnelQueryInput as ai, type PalbaseFunnelResult as aj, type PalbaseIdentifyTraits as ak, type PalbaseInboxClient as al, type PalbaseInboxListOptions as am, type PalbaseInboxListResult as an, type PalbaseInboxMessage as ao, type PalbaseInboxSendParams as ap, type PalbaseInboxSendResponse as aq, type PalbaseInitialLink as ar, type PalbaseInvokeOptions as as, type PalbaseLink as at, type PalbaseLinkAnalytics as au, type PalbaseLinkDetails as av, type PalbaseLinksClient as aw, type PalbaseListLinksOptions as ax, type PalbaseListLinksResult as ay, type PalbaseListOptions as az, type PalbaseNotificationsClient as b, type PalbaseWhereOperator as b0, type Ref as b1, TooManyRequests as b2, type TxColumnExpr as b3, type TxInsertShape as b4, type TxInsertValue as b5, type TxNow as b6, TxPlanError as b7, type TxPlanHandle as b8, type TxPlanOpResult as b9, type TxPlanRejection as ba, TxRefError as bb, type TxRow as bc, type TxRows as bd, type TxSelectOptions as be, type TxSetShape as bf, type TxSetValue as bg, type TxTable as bh, type TxWhere as bi, type TxWireExpr as bj, type TxWireGuard as bk, type TxWireOp as bl, type TxWireRef as bm, type TxWireValue as bn, Unauthorized as bo, UniqueViolation as bp, type User as bq, type VerifiedDevice as br, dec as bs, defineMiddleware as bt, inc as bu, now as bv, type PalbaseRealtimeClient as c, type PalbaseStorageClient as d, type PalbaseBucketClient as e, type DBOps as f, type TxPlanResponse as g, type PalbaseResult as h, type AuthConfig as i, type ClientInfo as j, Conflict as k, type ErrorMap as l, type ErrorThrowers as m, Forbidden as n, type HttpMethod as o, type Middleware as p, type MiddlewareContext as q, type MiddlewareHandler as r, type PBRequest as s, PalError as t, type PalbaseAnalyticsClient as u, type PalbaseAnalyticsManagementNamespace as v, type PalbaseAnalyticsProperties as w, type PalbaseAnalyticsQueryNamespace as x, type PalbaseAttestAndroidParams as y, type PalbaseAttestAndroidResult as z };
@@ -142,7 +142,7 @@ declare function defineMiddleware(fn: MiddlewareHandler): MiddlewareHandler;
142
142
  * # Wire contract
143
143
  *
144
144
  * The JSON this file emits is consumed by
145
- * `modules/backend/internal/management/tx_program.go`. That decoder rejects
145
+ * the plan executor in `engine/db.ts`. That executor rejects
146
146
  * unknown fields at every level, so an op carries EXACTLY the fields its kind
147
147
  * takes. Everything here that looks like a needless restriction is one of the
148
148
  * server's rules made visible early:
@@ -206,7 +206,12 @@ interface TxWireGuard {
206
206
  /** One operation in the wire plan. Fields are omitted, never null: the decoder
207
207
  * rejects a field that does not belong to the op's kind. */
208
208
  interface TxWireOp {
209
- op: "insert" | "insertMany" | "update" | "delete" | "select";
209
+ /** upsert and insertMany: the columns Postgres matches on. */
210
+ onConflict?: readonly string[];
211
+ /** insertMany only: what a collision does. Absent means no ON CONFLICT clause
212
+ * at all, which is what every insertMany did before this option existed. */
213
+ action?: "ignore" | "update";
214
+ op: "insert" | "insertMany" | "upsert" | "update" | "delete" | "select";
210
215
  table: string;
211
216
  values?: Record<string, TxWireValue>;
212
217
  rows?: Record<string, TxWireValue>[];
@@ -387,7 +392,36 @@ interface TxTable<Row, Insert> {
387
392
  *
388
393
  * An empty list writes nothing and sends nothing.
389
394
  */
390
- insertMany(rows: readonly TxInsertShape<Insert>[]): TxRows<Row>;
395
+ /**
396
+ * Insert many rows in ONE statement, optionally choosing what a collision does.
397
+ *
398
+ * Without `opts` this is a plain multi-row INSERT and a collision aborts the
399
+ * transaction — the behaviour every call had before the option existed.
400
+ *
401
+ * `action: "ignore"` emits `ON CONFLICT DO NOTHING`, which is how "insert the
402
+ * ones that are new" becomes one round-trip instead of one per row with a
403
+ * 23505 caught around each. **The returned rows are the ones actually
404
+ * INSERTED**: a row that collided is skipped, so it is absent from the result
405
+ * — Postgres does not return what it did not write.
406
+ *
407
+ * `action: "update"` emits `ON CONFLICT DO UPDATE`, setting every non-conflict
408
+ * column from the incoming row, and every row comes back.
409
+ */
410
+ insertMany(rows: readonly TxInsertShape<Insert>[], opts?: {
411
+ onConflict: readonly Extract<keyof Row, string>[];
412
+ action?: "ignore" | "update";
413
+ }): TxRows<Row>;
414
+ /**
415
+ * Insert the row, or update it when it collides on `onConflict` — inside the
416
+ * plan's savepoint, with the same meaning `tables.<t>.upsert()` has outside it.
417
+ *
418
+ * It is an operation because the alternative is not writable here: a failed
419
+ * insert aborts the whole transaction, so "try, then fall back" cannot be two
420
+ * plan steps.
421
+ */
422
+ upsert(values: TxInsertShape<Insert>, options: {
423
+ onConflict: readonly Extract<keyof Row, string>[];
424
+ }): TxRows<Row>;
391
425
  /**
392
426
  * Update every row matching `where`. The filter comes first because it is the
393
427
  * dangerous half: an update whose `where` you got wrong rewrites rows you
@@ -512,6 +546,57 @@ declare class NotFound extends NamedHttpError {
512
546
  declare class Conflict extends NamedHttpError {
513
547
  constructor(message?: string, code?: string, data?: unknown);
514
548
  }
549
+ /**
550
+ * 409 — a write was refused because it would duplicate an existing row.
551
+ * Carries the NAME of the unique constraint Postgres named (`users_email_key`).
552
+ *
553
+ * The engine produces it: a statement rejected with SQLSTATE `23505` is
554
+ * converted here rather than surfacing as an opaque driver error (see
555
+ * `engine/db.ts`, `diagnosingDriver`). What that removes is the string match —
556
+ * before this, the only way to act on a duplicate was to test the driver
557
+ * message for "duplicate key value violates unique constraint", a contract
558
+ * nobody signed that breaks on a Postgres upgrade, a locale, or a constraint
559
+ * rename, silently and in production.
560
+ *
561
+ * THE NAME IS A FIELD AND STAYS OUT OF THE DEFAULT MESSAGE. The two are not
562
+ * the same audience. `constraint` is read by the code that catches this — the
563
+ * developer, who already knows the schema. `errorDescription` is the HTTP
564
+ * response body, and an UNCAUGHT duplicate puts it in front of the
565
+ * application's end user: `users_email_key` there discloses how the schema is
566
+ * built to whoever sent the request. The platform's own data API took the same
567
+ * decision one surface over and wrote down why —
568
+ * `v2/internal/modules/database/internal/handler/pgerror.go:83-87` collapses
569
+ * every 23xxx to a generic conflict, "never disclose the constraint/column
570
+ * name". A thrower who WANTS the name on the wire passes it deliberately
571
+ * (`new UniqueViolation(c, \`\${c} already exists\`)`, or through `data`).
572
+ *
573
+ * @example
574
+ * try {
575
+ * await Database.tables.users.insert({ email });
576
+ * } catch (e) {
577
+ * if (UniqueViolation.is(e) && e.constraint === "users_email_key") {
578
+ * throw new Conflict("That email is taken", "email_taken");
579
+ * }
580
+ * throw e;
581
+ * }
582
+ */
583
+ declare class UniqueViolation extends Conflict {
584
+ /**
585
+ * Whether `e` is a unique violation — REGARDLESS of which copy of this SDK
586
+ * constructed it.
587
+ *
588
+ * Use this instead of `instanceof`. Measured on a live stack: a controller
589
+ * bundle INLINES its own copy of `@palbase/backend`, and the engine that
590
+ * raises this error is the runtime's copy. Two copies, two class identities,
591
+ * and `e instanceof UniqueViolation` is false in the one place a caller
592
+ * writes it — a check that reads as correct and silently never matches.
593
+ */
594
+ static is(e: unknown): e is UniqueViolation;
595
+ /** The unique constraint the statement violated, as Postgres named it.
596
+ * `""` when the driver did not say which — see `engine/db.ts`. */
597
+ readonly constraint: string;
598
+ constructor(constraint: string, message?: string, code?: string, data?: unknown);
599
+ }
515
600
  /** A single field-level validation failure carried by {@link BadRequest}. */
516
601
  interface FieldError {
517
602
  /** The offending field's name (dotted path for nested fields). */
@@ -1546,6 +1631,13 @@ interface PalbaseFlagsClient {
1546
1631
  /**
1547
1632
  * Realtime broadcast surface (backend handler → subscribed clients).
1548
1633
  *
1634
+ * **Every channel must be DECLARED, or the join is refused.** Channel names are
1635
+ * not free text: `channels.ts` at the project root publishes which names exist
1636
+ * and who may subscribe, publish, or write state, and a client joining a name no
1637
+ * declaration matches is rejected without ever reaching your code. Declare with
1638
+ * `ownerOnly()`, `publicChannel()`, or a custom `authorize(ctx)` — see
1639
+ * {@link defineChannels}. Broadcasting to an undeclared channel reaches nobody.
1640
+ *
1549
1641
  * Backend-side this is BROADCAST-ONLY: a handler pushes an event to a channel
1550
1642
  * and every client subscribed to that channel (via the client SDK's
1551
1643
  * `pb.realtime.channel(...).on(...)`) receives it. There is no `subscribe()` on
@@ -1554,9 +1646,10 @@ interface PalbaseFlagsClient {
1554
1646
  * once the broadcast is accepted (or with an `error` if it could not be sent),
1555
1647
  * but it never blocks the handler waiting on subscribers.
1556
1648
  *
1557
- * Channel naming is app-defined and arbitrary (e.g. `"room:42"`, `"orders"`).
1558
- * The Palbase-managed `flags:<ref>` channels are internal author channels for
1559
- * your own features.
1649
+ * Channel names are yours to choose (e.g. `"room:42"`, `"orders"`) but not yours
1650
+ * to leave undeclared: each must match a pattern in `channels.ts`, which is where
1651
+ * `"room:{roomId}"` is authorized. The Palbase-managed `flags:<ref>` channels are
1652
+ * internal — author channels for your own features.
1560
1653
  *
1561
1654
  * @example
1562
1655
  * // Notify everyone in a chat room that a message landed:
@@ -1816,9 +1909,36 @@ interface RateLimitConfig {
1816
1909
  /** The six raw string-keyed DB operations shared by `DBClient` and the
1817
1910
  * transaction-scoped client. */
1818
1911
  interface DBOps {
1819
- /** Run a read-only SQL query (executes in a READ ONLY transaction). */
1912
+ /**
1913
+ * Run SQL on the REQUEST'S OWN transaction.
1914
+ *
1915
+ * This is **not** read-only. Nothing marks that transaction `READ ONLY`, so a
1916
+ * write passed here executes and commits with the request — a plain `UPDATE`, a
1917
+ * write hidden in a CTE, or a function that writes. What stands between a
1918
+ * statement and your data is RLS and the role the request runs as, exactly as
1919
+ * for every other operation on this client.
1920
+ *
1921
+ * `INSERT … ON CONFLICT DO UPDATE` therefore works here; `tables.<t>.upsert()`
1922
+ * is the same thing with types. Array parameters are encoded as Postgres array
1923
+ * literals, so `= ANY($1::uuid[])` takes a plain JS array.
1924
+ *
1925
+ * This comment used to claim a `READ ONLY` transaction. It was wrong, and the
1926
+ * cost was measured: a tenant read it, concluded an upsert was impossible from
1927
+ * here, abandoned `ON CONFLICT` and wrote a database trigger instead.
1928
+ */
1820
1929
  query(sql: string, params?: unknown[]): Promise<Record<string, unknown>[]>;
1821
1930
  insert(table: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
1931
+ /**
1932
+ * Insert the row, or update it when it collides on `onConflict` — one
1933
+ * statement, so two concurrent callers cannot both lose.
1934
+ *
1935
+ * Read-then-write cannot be made safe here by catching the unique violation: a
1936
+ * request runs in one transaction, so the failed insert aborts it and every
1937
+ * later statement answers `current transaction is aborted`.
1938
+ */
1939
+ upsert(table: string, data: Record<string, unknown>, opts: {
1940
+ onConflict: readonly string[];
1941
+ }): Promise<Record<string, unknown>>;
1822
1942
  /**
1823
1943
  * Update the row with the given id and resolve to the updated row, or `null`
1824
1944
  * if no row matched (the id is absent, or the row is hidden by RLS). This is
@@ -1862,7 +1982,26 @@ interface DBOps {
1862
1982
  /** Validity'li tabloda satırın yeni versiyonu — eski kapanır (valid_to/superseded_by),
1863
1983
  * yenisi eklenir; TEK savepoint, dönüş yeni satır (FR-029, C-9). */
1864
1984
  supersede(table: string, id: string, row: Record<string, unknown>): Promise<Record<string, unknown>>;
1865
- findMany(table: string, query?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
1985
+ /** Filtre + sıralama + sayfa. `offset` yalnız `limit` ile birlikte geçerlidir
1986
+ * (limitsiz offset sayfa değildir; motor adıyla reddeder — FR-004). */
1987
+ /** Update every row the filter matches, in one statement; resolves to them.
1988
+ * An EMPTY filter is refused — a whole-table write is not something to reach
1989
+ * by accident. */
1990
+ updateMany(table: string, where: Record<string, unknown>, set: Record<string, unknown>): Promise<Record<string, unknown>[]>;
1991
+ /** Delete every row the filter matches; resolves to how many. Empty filter
1992
+ * refused, for the same reason and more so. */
1993
+ deleteMany(table: string, where: Record<string, unknown>): Promise<number>;
1994
+ /** How many rows match — the half of pagination limit/offset cannot supply.
1995
+ * An empty filter is legitimate here: counting is a read. */
1996
+ count(table: string, where?: Record<string, unknown>): Promise<number>;
1997
+ findMany(table: string, query?: Record<string, unknown>, opts?: {
1998
+ orderBy?: {
1999
+ column: string;
2000
+ direction?: "asc" | "desc";
2001
+ };
2002
+ limit?: number;
2003
+ offset?: number;
2004
+ }): Promise<Record<string, unknown>[]>;
1866
2005
  /** "Bu satıra benzer satırlar" (FR-022) — satır-modunda kolon vektörüyle,
1867
2006
  * chunk-modunda chunk-ortalamasıyla; kendisi hariç. */
1868
2007
  similar(table: string, id: string, opts?: {
@@ -1878,6 +2017,18 @@ interface DBOps {
1878
2017
  weight: number;
1879
2018
  };
1880
2019
  }): Promise<Record<string, unknown>[]>;
2020
+ /** D-021 (FR-027): kolon sayaçları bağımsız dönüşle — search'ün dizi-üstü
2021
+ * `_facets` özelliği serileşmede kaybolur; ciddi sözleşme budur. */
2022
+ facets(table: string, params: {
2023
+ facets: string[];
2024
+ where?: Record<string, unknown>;
2025
+ validity?: "all" | {
2026
+ asOf: string;
2027
+ };
2028
+ }): Promise<Record<string, {
2029
+ value: string | null;
2030
+ count: number;
2031
+ }[]>>;
1881
2032
  /** Çok-örnekli öneri (FR-023): avg(pos) + (avg(pos) − avg(neg)); positive'ler sonuç dışı. */
1882
2033
  recommend(table: string, opts: {
1883
2034
  positive: string[];
@@ -1913,6 +2064,14 @@ interface DBClient extends DBOps {
1913
2064
  * `.expectOne(…)`; without it a declared expectation degrades to a generic 409.
1914
2065
  */
1915
2066
  txPlan(plan: TxPlanBody): Promise<TxPlanResponse>;
2067
+ /**
2068
+ * Run `fn` inside a SAVEPOINT on this request's transaction, so a failure in
2069
+ * it rolls back only what `fn`'s handle wrote.
2070
+ *
2071
+ * The handle is a PARAMETER on purpose: an ambient swap would capture writes
2072
+ * a concurrent `Promise.all` branch made outside the boundary.
2073
+ */
2074
+ attempt<T>(fn: (tx: DBOps) => Promise<T>): Promise<T>;
1916
2075
  /**
1917
2076
  * Return a sibling DB client that bypasses Row-Level Security by running as
1918
2077
  * the `service_role` (BYPASSRLS). Use sparingly and explicitly — the default
@@ -2213,4 +2372,4 @@ type Middleware = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise
2213
2372
  */
2214
2373
  type AuthSpec = boolean | Partial<AuthConfig>;
2215
2374
 
2216
- export { type PalbaseCreateLinkParams as $, type AuthSpec as A, BadRequest as B, type CacheClient as C, type DBClient as D, type ErrorDef as E, type FileContext as F, type PalbaseAttestiOSParams as G, HttpError as H, type PalbaseAttestiOSResult as I, type PalbaseAuthClient as J, type PalbaseBatchOverrideOperation as K, type Logger as L, type Materialized as M, NotFound as N, type PalbaseBatchSetOverridesResult as O, type PalbaseDocsClient as P, type PalbaseBindDeviceParams as Q, type RateLimitConfig as R, type SecretsService as S, type TxPlanBody as T, type PalbaseClearAllOverridesResult as U, type PalbaseClearOverrideResult as V, type PalbaseCohortQueryInput as W, type PalbaseCohortResult as X, type PalbaseCollectionRef as Y, type PalbaseCountQueryInput as Z, type PalbaseCountResult as _, type PalbaseFlagsClient as a, type PalbaseVerifyRequestSignatureParams as a$, type PalbaseDeviceInfo as a0, type PalbaseDeviceTokenView as a1, type PalbaseDocumentRef as a2, type PalbaseDocumentSnapshot as a3, type PalbaseEmailClient as a4, type PalbaseEmailSendParams as a5, type PalbaseEmailSendResponse as a6, type PalbaseEventNamesResult as a7, type PalbaseEventsQueryInput as a8, type PalbaseEventsResult as a9, type PalbaseMatchParams as aA, type PalbaseMultiChannelResponse as aB, type PalbaseOverviewResult as aC, type PalbasePreferences as aD, type PalbasePreferencesClient as aE, type PalbasePushClient as aF, type PalbasePushSendParams as aG, type PalbasePushSendResponse as aH, type PalbaseQrCodeOptions as aI, type PalbaseQuerySnapshot as aJ, type PalbaseRegisterDeviceParams as aK, type PalbaseRetentionQueryInput as aL, type PalbaseRetentionResult as aM, type PalbaseSession as aN, type PalbaseSetOverrideResult as aO, type PalbaseSetOverridesResult as aP, type PalbaseSignedUrlResponse as aQ, type PalbaseSmsClient as aR, type PalbaseSmsSendParams as aS, type PalbaseSmsSendResponse as aT, type PalbaseTransformOptions as aU, type PalbaseUpdateLinkParams as aV, type PalbaseUploadOptions as aW, type PalbaseUser as aX, type PalbaseUserDetailResult as aY, type PalbaseUsersQueryInput as aZ, type PalbaseUsersResult as a_, type PalbaseFileObject as aa, type PalbaseFlag as ab, type PalbaseFlagContext as ac, type PalbaseFlagSource as ad, type PalbaseFlagValue as ae, type PalbaseFlagVariant as af, type PalbaseFlagsServiceClient as ag, type PalbaseFunctionsClient as ah, type PalbaseFunnelQueryInput as ai, type PalbaseFunnelResult as aj, type PalbaseIdentifyTraits as ak, type PalbaseInboxClient as al, type PalbaseInboxListOptions as am, type PalbaseInboxListResult as an, type PalbaseInboxMessage as ao, type PalbaseInboxSendParams as ap, type PalbaseInboxSendResponse as aq, type PalbaseInitialLink as ar, type PalbaseInvokeOptions as as, type PalbaseLink as at, type PalbaseLinkAnalytics as au, type PalbaseLinkDetails as av, type PalbaseLinksClient as aw, type PalbaseListLinksOptions as ax, type PalbaseListLinksResult as ay, type PalbaseListOptions as az, type PalbaseNotificationsClient as b, type PalbaseWhereOperator as b0, type Ref as b1, TooManyRequests as b2, type TxColumnExpr as b3, type TxInsertShape as b4, type TxInsertValue as b5, type TxNow as b6, TxPlanError as b7, type TxPlanHandle as b8, type TxPlanOpResult as b9, type TxPlanRejection as ba, TxRefError as bb, type TxRow as bc, type TxRows as bd, type TxSelectOptions as be, type TxSetShape as bf, type TxSetValue as bg, type TxTable as bh, type TxWhere as bi, type TxWireExpr as bj, type TxWireGuard as bk, type TxWireOp as bl, type TxWireRef as bm, type TxWireValue as bn, Unauthorized as bo, type User as bp, type VerifiedDevice as bq, dec as br, defineMiddleware as bs, inc as bt, now as bu, type PalbaseRealtimeClient as c, type PalbaseStorageClient as d, type PalbaseBucketClient as e, type TxPlanResponse as f, type PalbaseResult as g, type AuthConfig as h, type ClientInfo as i, Conflict as j, type DBOps as k, type ErrorMap as l, type ErrorThrowers as m, Forbidden as n, type HttpMethod as o, type Middleware as p, type MiddlewareContext as q, type MiddlewareHandler as r, type PBRequest as s, PalError as t, type PalbaseAnalyticsClient as u, type PalbaseAnalyticsManagementNamespace as v, type PalbaseAnalyticsProperties as w, type PalbaseAnalyticsQueryNamespace as x, type PalbaseAttestAndroidParams as y, type PalbaseAttestAndroidResult as z };
2375
+ export { type PalbaseCreateLinkParams as $, type AuthSpec as A, BadRequest as B, type CacheClient as C, type DBClient as D, type ErrorDef as E, type FileContext as F, type PalbaseAttestiOSParams as G, HttpError as H, type PalbaseAttestiOSResult as I, type PalbaseAuthClient as J, type PalbaseBatchOverrideOperation as K, type Logger as L, type Materialized as M, NotFound as N, type PalbaseBatchSetOverridesResult as O, type PalbaseDocsClient as P, type PalbaseBindDeviceParams as Q, type RateLimitConfig as R, type SecretsService as S, type TxPlanBody as T, type PalbaseClearAllOverridesResult as U, type PalbaseClearOverrideResult as V, type PalbaseCohortQueryInput as W, type PalbaseCohortResult as X, type PalbaseCollectionRef as Y, type PalbaseCountQueryInput as Z, type PalbaseCountResult as _, type PalbaseFlagsClient as a, type PalbaseVerifyRequestSignatureParams as a$, type PalbaseDeviceInfo as a0, type PalbaseDeviceTokenView as a1, type PalbaseDocumentRef as a2, type PalbaseDocumentSnapshot as a3, type PalbaseEmailClient as a4, type PalbaseEmailSendParams as a5, type PalbaseEmailSendResponse as a6, type PalbaseEventNamesResult as a7, type PalbaseEventsQueryInput as a8, type PalbaseEventsResult as a9, type PalbaseMatchParams as aA, type PalbaseMultiChannelResponse as aB, type PalbaseOverviewResult as aC, type PalbasePreferences as aD, type PalbasePreferencesClient as aE, type PalbasePushClient as aF, type PalbasePushSendParams as aG, type PalbasePushSendResponse as aH, type PalbaseQrCodeOptions as aI, type PalbaseQuerySnapshot as aJ, type PalbaseRegisterDeviceParams as aK, type PalbaseRetentionQueryInput as aL, type PalbaseRetentionResult as aM, type PalbaseSession as aN, type PalbaseSetOverrideResult as aO, type PalbaseSetOverridesResult as aP, type PalbaseSignedUrlResponse as aQ, type PalbaseSmsClient as aR, type PalbaseSmsSendParams as aS, type PalbaseSmsSendResponse as aT, type PalbaseTransformOptions as aU, type PalbaseUpdateLinkParams as aV, type PalbaseUploadOptions as aW, type PalbaseUser as aX, type PalbaseUserDetailResult as aY, type PalbaseUsersQueryInput as aZ, type PalbaseUsersResult as a_, type PalbaseFileObject as aa, type PalbaseFlag as ab, type PalbaseFlagContext as ac, type PalbaseFlagSource as ad, type PalbaseFlagValue as ae, type PalbaseFlagVariant as af, type PalbaseFlagsServiceClient as ag, type PalbaseFunctionsClient as ah, type PalbaseFunnelQueryInput as ai, type PalbaseFunnelResult as aj, type PalbaseIdentifyTraits as ak, type PalbaseInboxClient as al, type PalbaseInboxListOptions as am, type PalbaseInboxListResult as an, type PalbaseInboxMessage as ao, type PalbaseInboxSendParams as ap, type PalbaseInboxSendResponse as aq, type PalbaseInitialLink as ar, type PalbaseInvokeOptions as as, type PalbaseLink as at, type PalbaseLinkAnalytics as au, type PalbaseLinkDetails as av, type PalbaseLinksClient as aw, type PalbaseListLinksOptions as ax, type PalbaseListLinksResult as ay, type PalbaseListOptions as az, type PalbaseNotificationsClient as b, type PalbaseWhereOperator as b0, type Ref as b1, TooManyRequests as b2, type TxColumnExpr as b3, type TxInsertShape as b4, type TxInsertValue as b5, type TxNow as b6, TxPlanError as b7, type TxPlanHandle as b8, type TxPlanOpResult as b9, type TxPlanRejection as ba, TxRefError as bb, type TxRow as bc, type TxRows as bd, type TxSelectOptions as be, type TxSetShape as bf, type TxSetValue as bg, type TxTable as bh, type TxWhere as bi, type TxWireExpr as bj, type TxWireGuard as bk, type TxWireOp as bl, type TxWireRef as bm, type TxWireValue as bn, Unauthorized as bo, UniqueViolation as bp, type User as bq, type VerifiedDevice as br, dec as bs, defineMiddleware as bt, inc as bu, now as bv, type PalbaseRealtimeClient as c, type PalbaseStorageClient as d, type PalbaseBucketClient as e, type DBOps as f, type TxPlanResponse as g, type PalbaseResult as h, type AuthConfig as i, type ClientInfo as j, Conflict as k, type ErrorMap as l, type ErrorThrowers as m, Forbidden as n, type HttpMethod as o, type Middleware as p, type MiddlewareContext as q, type MiddlewareHandler as r, type PBRequest as s, PalError as t, type PalbaseAnalyticsClient as u, type PalbaseAnalyticsManagementNamespace as v, type PalbaseAnalyticsProperties as w, type PalbaseAnalyticsQueryNamespace as x, type PalbaseAttestAndroidParams as y, type PalbaseAttestAndroidResult as z };