@palbase/backend 23.1.0 → 24.0.1

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 (73) hide show
  1. package/dist/bin/palbase-backend.cjs +400 -47
  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-OHALWEOG.js → chunk-7Z6MGMXQ.js} +57 -2
  6. package/dist/chunk-7Z6MGMXQ.js.map +1 -0
  7. package/dist/{chunk-RCLNBJCM.js → chunk-H3JAISUY.js} +136 -1
  8. package/dist/chunk-H3JAISUY.js.map +1 -0
  9. package/dist/{chunk-PY7YJDCT.js → chunk-LCL7TUAI.js} +32 -3
  10. package/dist/chunk-LCL7TUAI.js.map +1 -0
  11. package/dist/{chunk-NS5V43YQ.js → chunk-P2Q27SGP.js} +19 -3
  12. package/dist/chunk-P2Q27SGP.js.map +1 -0
  13. package/dist/{chunk-R3KN6RHD.js → chunk-T5IOSOE5.js} +7 -2
  14. package/dist/chunk-T5IOSOE5.js.map +1 -0
  15. package/dist/{chunk-M5MCBWJI.js → chunk-YSQBC2VL.js} +275 -31
  16. package/dist/chunk-YSQBC2VL.js.map +1 -0
  17. package/dist/db/index.cjs +48 -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-CVWXh6oG.d.ts → endpoint-0_DGBajf.d.ts} +100 -3
  23. package/dist/{endpoint-c9h5jriX.d.cts → endpoint-CcQ1a36a.d.cts} +100 -3
  24. package/dist/engine/index.cjs +389 -34
  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-CwAJ7HEe.d.ts → index-BOS_rFBO.d.ts} +125 -21
  30. package/dist/{index-CxeQSfJP.d.cts → index-C84bLgeO.d.cts} +134 -9
  31. package/dist/{index-By8Dle5U.d.cts → index-MoQ31B6M.d.cts} +125 -21
  32. package/dist/{index-BZrJXnVh.d.ts → index-dJNhDZ7j.d.ts} +134 -9
  33. package/dist/index.cjs +166 -5
  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 +28 -11
  38. package/dist/index.js.map +1 -1
  39. package/dist/openapi/index.cjs +16 -1
  40. package/dist/openapi/index.cjs.map +1 -1
  41. package/dist/openapi/index.d.cts +6 -4
  42. package/dist/openapi/index.d.ts +6 -4
  43. package/dist/openapi/index.js +6 -7
  44. package/dist/openapi/index.js.map +1 -1
  45. package/dist/{registry-CqPK2Qby.d.cts → registry-1X-skBNu.d.cts} +1 -1
  46. package/dist/{registry-B3niOVYp.d.ts → registry-CEod_5sz.d.ts} +1 -1
  47. package/dist/test/index.cjs +27 -0
  48. package/dist/test/index.cjs.map +1 -1
  49. package/dist/test/index.d.cts +1 -1
  50. package/dist/test/index.d.ts +1 -1
  51. package/dist/test/index.js +27 -0
  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 +2 -2
  61. package/template/AGENTS.md +121 -41
  62. package/template/controllers/notes.controller.ts +64 -0
  63. package/template/package.json +1 -1
  64. package/template/services/note.service.ts +74 -0
  65. package/template/tsconfig.json +11 -1
  66. package/dist/chunk-HQRJDARQ.js +0 -90
  67. package/dist/chunk-HQRJDARQ.js.map +0 -1
  68. package/dist/chunk-M5MCBWJI.js.map +0 -1
  69. package/dist/chunk-NS5V43YQ.js.map +0 -1
  70. package/dist/chunk-OHALWEOG.js.map +0 -1
  71. package/dist/chunk-PY7YJDCT.js.map +0 -1
  72. package/dist/chunk-R3KN6RHD.js.map +0 -1
  73. package/dist/chunk-RCLNBJCM.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { Tables, TableTypes } from './db/env.cjs';
2
- import { D as DBClient, b8 as TxPlanHandle, bh as TxTable, M as Materialized } from './endpoint-c9h5jriX.cjs';
2
+ import { D as DBClient, b8 as TxPlanHandle, bh as TxTable, M as Materialized } from './endpoint-CcQ1a36a.cjs';
3
3
 
4
4
  /** On delete action for foreign key references. */
5
5
  type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';
@@ -50,12 +50,33 @@ interface ColumnDef {
50
50
  /** vector(n): the declared dimension count — part of the TYPE (typmod), read
51
51
  * by the wire serializer and the deploy's auto-index (FR-001). */
52
52
  dimensions?: number;
53
+ /**
54
+ * How the stored value is projected in and out of this process (FR-009).
55
+ *
56
+ * NOT part of the DDL: the column's Postgres type is unchanged and this pair
57
+ * is never serialized into a migration. It exists so the row surface can hand
58
+ * back the type the application actually works with.
59
+ */
60
+ transform?: ColumnTransform;
61
+ }
62
+ /**
63
+ * The read/write pair a column may declare (FR-009).
64
+ *
65
+ * `fromDb` takes whatever the driver produced for this column and returns the
66
+ * value the application sees; `toDb` is its inverse on the way out. Kept
67
+ * deliberately unexported — a column declares one inline, nobody needs to name
68
+ * the shape.
69
+ */
70
+ interface ColumnTransform<T = unknown> {
71
+ fromDb: (value: unknown) => T;
72
+ toDb: (value: T) => unknown;
53
73
  }
54
74
  declare const __colKind: unique symbol;
55
75
  declare const __colNullable: unique symbol;
56
76
  declare const __colHasDefault: unique symbol;
57
77
  declare const __colEnumValues: unique symbol;
58
78
  declare const __colPayload: unique symbol;
79
+ declare const __colTransform: unique symbol;
59
80
  /**
60
81
  * Fluent column builder with phantom type params:
61
82
  * K — ColumnType literal (e.g. "text", "integer")
@@ -63,34 +84,38 @@ declare const __colPayload: unique symbol;
63
84
  * D — boolean: true when a default has been set
64
85
  * E — enum value union (never for non-enum columns)
65
86
  * P — jsonb payload shape (unknown unless jsonb<T>() supplied one)
87
+ * T — transform target type (`never` when the column declares no transform;
88
+ * `never` is the sentinel because it is the only type that survives
89
+ * `[T] extends [never]` and never collides with a real target type)
66
90
  *
67
- * All five params have defaults so bare `ColumnBuilder` (no args) still
91
+ * All six params have defaults so bare `ColumnBuilder` (no args) still
68
92
  * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.
69
93
  *
70
- * The five `declare readonly` brand fields carry the phantom types into the
94
+ * The six `declare readonly` brand fields carry the phantom types into the
71
95
  * structural shape so that conditional types like ColValue<C> can discriminate
72
96
  * on K without requiring runtime values on those fields.
73
97
  */
74
- declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean = boolean, D extends boolean = boolean, E = unknown, P = unknown> {
98
+ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean = boolean, D extends boolean = boolean, E = unknown, P = unknown, T = unknown> {
75
99
  readonly [__colKind]: K;
76
100
  readonly [__colNullable]: N;
77
101
  readonly [__colHasDefault]: D;
78
102
  readonly [__colEnumValues]: E;
79
103
  readonly [__colPayload]: P;
104
+ readonly [__colTransform]: T;
80
105
  readonly _def: ColumnDef;
81
106
  constructor(type: K, existingDef?: ColumnDef);
82
107
  /** Mark this column as the primary key. */
83
- primaryKey(): ColumnBuilder<K, N, D, E, P>;
108
+ primaryKey(): ColumnBuilder<K, N, D, E, P, T>;
84
109
  /** Mark this column as NOT NULL (default). */
85
- notNull(): ColumnBuilder<K, false, D, E, P>;
110
+ notNull(): ColumnBuilder<K, false, D, E, P, T>;
86
111
  /** Allow NULL values. */
87
- nullable(): ColumnBuilder<K, true, D, E, P>;
112
+ nullable(): ColumnBuilder<K, true, D, E, P, T>;
88
113
  /** Set a default value. */
89
- default(value: unknown): ColumnBuilder<K, N, true, E, P>;
114
+ default(value: unknown): ColumnBuilder<K, N, true, E, P, T>;
90
115
  /** UUID: generate a random default (gen_random_uuid()). */
91
- defaultRandom(): ColumnBuilder<K, N, true, E, P>;
116
+ defaultRandom(): ColumnBuilder<K, N, true, E, P, T>;
92
117
  /** Timestamp: default to now(). */
93
- defaultNow(): ColumnBuilder<K, N, true, E, P>;
118
+ defaultNow(): ColumnBuilder<K, N, true, E, P, T>;
94
119
  /**
95
120
  * The DATABASE assigns this column's value — a trigger, a rule, an identity.
96
121
  *
@@ -102,7 +127,7 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
102
127
  * they are a different thing; borrowing the word would send a reader — or a
103
128
  * model writing a schema — to the wrong feature.
104
129
  */
105
- dbAssigned(): ColumnBuilder<K, N, true, E, P>;
130
+ dbAssigned(): ColumnBuilder<K, N, true, E, P, T>;
106
131
  /** Add a foreign key reference. */
107
132
  /**
108
133
  * Declares that this column used to be called `previous`.
@@ -115,8 +140,8 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
115
140
  * Once the rename has been applied the annotation is inert (the old name is no
116
141
  * longer there to rename), so it can be deleted at your leisure.
117
142
  */
118
- renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P>;
119
- references(table: string, column: string): ColumnBuilder<K, N, D, E, P>;
143
+ renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P, T>;
144
+ references(table: string, column: string): ColumnBuilder<K, N, D, E, P, T>;
120
145
  /**
121
146
  * Add a real DB-level foreign key to the built-in auth users
122
147
  * (`REFERENCES auth.users(id)`), so a column like `user_id` gets true
@@ -137,7 +162,7 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
137
162
  * express (referencing column is text, `set null` needs a nullable column) —
138
163
  * as the real boundary; this signature is the compile-time DX mirror.
139
164
  */
140
- referencesAuthUser(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P>;
165
+ referencesAuthUser(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P, T>;
141
166
  /**
142
167
  * Add a real DB-level foreign key to the canonical, server-minted installation
143
168
  * anchor (`REFERENCES auth.installations(id)`) — the app-scoped verified-device
@@ -156,11 +181,30 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
156
181
  * blocked by a lingering FK. The server (validateAuthAnchorFK) is the real
157
182
  * boundary; this signature is the compile-time DX mirror.
158
183
  */
159
- referencesInstallation(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P>;
184
+ referencesInstallation(onDelete: AuthUserOnDelete): ColumnBuilder<K, N, D, E, P, T>;
160
185
  /** Set the ON DELETE action for a foreign key reference. */
161
- onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P>;
186
+ onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P, T>;
162
187
  /** Add a single-column UNIQUE constraint. */
163
- unique(): ColumnBuilder<K, N, D, E, P>;
188
+ unique(): ColumnBuilder<K, N, D, E, P, T>;
189
+ /**
190
+ * Declare how this column's value is projected in and out of the process.
191
+ *
192
+ * The DDL does not move: `numeric` stays `numeric`, and the driver still hands
193
+ * back what Postgres sent. What changes is the type the row surface exposes —
194
+ * it becomes `Target`:
195
+ *
196
+ * amount: numeric().transform<number>({ fromDb: Number, toDb: String })
197
+ *
198
+ * `numeric` surfacing as `string` is CORRECT (a JS number cannot hold
199
+ * arbitrary precision), and that is exactly why this exists: application code
200
+ * that does arithmetic on the column otherwise rewrites the same
201
+ * `Number(row.amount)` / `String(x)` pair in every controller that touches it,
202
+ * and each rewrite is a place the two directions can drift apart.
203
+ *
204
+ * A transform is a PROJECTION, never a constraint: it lives only in this
205
+ * process, so it can neither validate nor migrate what is stored.
206
+ */
207
+ transform<Target>(fns: ColumnTransform<Target>): ColumnBuilder<K, N, D, E, P, Target>;
164
208
  }
165
209
  /**
166
210
  * Extracts the TypeScript value type for a column, respecting nullability.
@@ -172,14 +216,22 @@ declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean
172
216
  * - "boolean" → boolean
173
217
  * - "jsonb" → P (the dev-supplied payload shape from jsonb<T>(), else unknown)
174
218
  * - "enum" → E (the union of literal values)
219
+ *
220
+ * A declared `.transform<T>()` OVERRIDES the table above: the column then
221
+ * surfaces as T (or T | null when nullable), because that is the value the
222
+ * application is handed. Nullability is still the column's, not the
223
+ * transform's — `fromDb` is not called for a NULL.
175
224
  */
176
- type ColValue<C> = C extends ColumnBuilder<'uuid' | 'text' | 'timestamp' | 'bigint' | 'numeric', infer N, infer _D, infer _E, infer _P> ? N extends true ? string | null : string : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E, infer _P> ? N extends true ? number | null : number : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E, infer _P> ? N extends true ? boolean | null : boolean : C extends ColumnBuilder<'jsonb', infer N, infer _D, infer _E, infer P> ? N extends true ? P | null : P : C extends ColumnBuilder<'vector', infer N, infer _D, infer _E, infer _P> ? N extends true ? number[] | null : number[] : C extends ColumnBuilder<'enum', infer N, infer _D, infer E, infer _P> ? N extends true ? E | null : E : never;
225
+ type ColValue<C> = C extends ColumnBuilder<ColumnType, infer N, boolean, unknown, unknown, infer T> ? [unknown] extends [T] ? ColStoredValue<C> : N extends true ? T | null : T : never;
226
+ /** The value as the DATABASE hands it over — the branch table above, before any
227
+ * transform. This is what a column's `fromDb` receives. */
228
+ type ColStoredValue<C> = C extends ColumnBuilder<'uuid' | 'text' | 'timestamp' | 'bigint' | 'numeric', infer N, infer _D, infer _E, infer _P> ? N extends true ? string | null : string : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E, infer _P> ? N extends true ? number | null : number : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E, infer _P> ? N extends true ? boolean | null : boolean : C extends ColumnBuilder<'jsonb', infer N, infer _D, infer _E, infer P> ? N extends true ? P | null : P : C extends ColumnBuilder<'vector', infer N, infer _D, infer _E, infer _P> ? N extends true ? number[] | null : number[] : C extends ColumnBuilder<'enum', infer N, infer _D, infer E, infer _P> ? N extends true ? E | null : E : never;
177
229
  /**
178
230
  * True when a column is optional on INSERT:
179
231
  * - nullable columns (N = true) — the DB allows NULL so the field may be omitted
180
232
  * - columns with a default (D = true) — the DB fills in the value when absent
181
233
  */
182
- type ColIsOptionalOnInsert<C> = C extends ColumnBuilder<infer _K, true, infer _D, infer _E> ? true : C extends ColumnBuilder<infer _K, infer _N, true, infer _E> ? true : false;
234
+ type ColIsOptionalOnInsert<C> = C extends ColumnBuilder<ColumnType, true, boolean, unknown, unknown, unknown> ? true : C extends ColumnBuilder<ColumnType, boolean, true, unknown, unknown, unknown> ? true : false;
183
235
  /** Create a UUID column. */
184
236
  declare function uuid(): ColumnBuilder<'uuid', false, false, never>;
185
237
  /** Create a TEXT column. */
@@ -691,7 +743,15 @@ interface TypedTable<T extends TableDef> {
691
743
  update(id: string, data: Partial<InsertShape<T>>): Promise<RowShape<T> | null>;
692
744
  delete(id: string): Promise<void>;
693
745
  findById(id: string): Promise<RowShape<T> | null>;
694
- findMany(query?: Partial<RowShape<T>>): Promise<RowShape<T>[]>;
746
+ /** Rows matching the filter. Operators, ordering and paging are the ENGINE's
747
+ * surface — this declaration is what makes them callable. */
748
+ findMany(query?: WhereFilter<RowShape<T>>, opts?: FindManyOpts<RowShape<T>>): Promise<RowShape<T>[]>;
749
+ /** Update every matching row in one statement; an empty filter is refused. */
750
+ updateMany(where: WhereFilter<RowShape<T>>, set: Partial<InsertShape<T>>): Promise<RowShape<T>[]>;
751
+ /** Delete every matching row; resolves to how many. Empty filter refused. */
752
+ deleteMany(where: WhereFilter<RowShape<T>>): Promise<number>;
753
+ /** How many rows match. An empty filter is legitimate: counting is a read. */
754
+ count(where?: WhereFilter<RowShape<T>>): Promise<number>;
695
755
  }
696
756
  /** A typed DB facade covering all tables declared in schema `S`. */
697
757
  interface TypedDB<S extends SchemaDef> {
@@ -730,6 +790,30 @@ type WhereOp<V> = V | {
730
790
  neq?: V;
731
791
  in?: V[];
732
792
  };
793
+ /**
794
+ * A filter over a row: every field optional, each one a plain value (equality)
795
+ * or an operator object. THE filter language — `findMany`, `updateMany`,
796
+ * `deleteMany` and `count` all take this one, because two spellings of a filter
797
+ * is how the two come to disagree.
798
+ */
799
+ type WhereFilter<Row> = {
800
+ [K in keyof Row]?: WhereOp<Row[K]>;
801
+ };
802
+ /**
803
+ * Ordering and paging for a read.
804
+ *
805
+ * `column` is `keyof Row`, not `string`: a mistyped column name is a compile
806
+ * error here rather than a runtime rejection three layers down. `offset`
807
+ * without `limit` is refused by the engine — a page with no size is not a page.
808
+ */
809
+ type FindManyOpts<Row> = {
810
+ orderBy?: {
811
+ column: Extract<keyof Row, string>;
812
+ direction?: "asc" | "desc";
813
+ };
814
+ limit?: number;
815
+ offset?: number;
816
+ };
733
817
  /** search() parametreleri, satır tipiyle koşullanmış (FR-013). `offset` BİLEREK yok (UD-013). */
734
818
  interface SearchParamsTyped<T extends TableTypes> {
735
819
  /** Metin sorgusu: FTS kolunu besler; embed beyanlıysa sorgu vektörü de bundan üretilir. */
@@ -809,7 +893,15 @@ interface EnvTypedTableBase<T extends TableTypes> {
809
893
  update(id: string, data: Partial<T["insert"]>): Promise<T["row"] | null>;
810
894
  delete(id: string): Promise<void>;
811
895
  findById(id: string): Promise<T["row"] | null>;
812
- findMany(query?: Partial<T["row"]>): Promise<T["row"][]>;
896
+ /** Rows matching the filter. See {@link WhereFilter} / {@link FindManyOpts} —
897
+ * this declaration is what makes the engine's operators callable. */
898
+ findMany(query?: WhereFilter<T["row"]>, opts?: FindManyOpts<T["row"]>): Promise<T["row"][]>;
899
+ /** Update every matching row in one statement; an empty filter is refused. */
900
+ updateMany(where: WhereFilter<T["row"]>, set: Partial<T["insert"]>): Promise<T["row"][]>;
901
+ /** Delete every matching row; resolves to how many. Empty filter refused. */
902
+ deleteMany(where: WhereFilter<T["row"]>): Promise<number>;
903
+ /** How many rows match. An empty filter is legitimate: counting is a read. */
904
+ count(where?: WhereFilter<T["row"]>): Promise<number>;
813
905
  /** Validity'li tabloda satırın yeni versiyonu (FR-029, C-9): eski satır
814
906
  * kapanır (valid_to/superseded_by), yenisi TEK savepoint'te eklenir; dönüş
815
907
  * yeni satır. Validity beyanı olmayan tabloda adlandırılmış çalışma-zamanı
@@ -832,6 +924,18 @@ type EnvTypedTable<T extends TableTypes> = EnvTypedTableBase<T> & (T extends {
832
924
  similar(id: string, params?: SimilarParamsTyped<T>): Promise<Array<T["row"] & {
833
925
  _score: number;
834
926
  }>>;
927
+ /** D-021: sayaçlar bağımsız dönüşle — search'ün dizi-üstü _facets'i
928
+ * JSON.stringify'da kaybolur; ciddi sözleşme budur. */
929
+ facets(params: {
930
+ facets: Array<keyof T["row"] & string>;
931
+ where?: Partial<T["row"]>;
932
+ validity?: "all" | {
933
+ asOf: string;
934
+ };
935
+ }): Promise<Record<string, {
936
+ value: string | null;
937
+ count: number;
938
+ }[]>>;
835
939
  /** positive/negative beğenilerden öneri (FR-023): hedef vektör DB-içi
836
940
  * avg CTE'leriyle; kaynak id'ler sonuçta yoktur. */
837
941
  recommend(params: RecommendParamsTyped<T>): Promise<Array<T["row"] & {
@@ -1,8 +1,8 @@
1
1
  import { Buckets, BucketTypes } from './stack.js';
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, f as DBOps, T as TxPlanBody, g as TxPlanResponse } from './endpoint-CVWXh6oG.js';
4
- import { E as EnvTypedDatabase } from './index-CwAJ7HEe.js';
5
- import { R as RouteMeta } from './registry-B3niOVYp.js';
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, A as AuthSpec } from './endpoint-0_DGBajf.js';
4
+ import { E as EnvTypedDatabase } from './index-BOS_rFBO.js';
5
+ import { R as RouteMeta } from './registry-CEod_5sz.js';
6
6
 
7
7
  /**
8
8
  * runtime.ts — request-scoped service singletons.
@@ -112,6 +112,72 @@ declare function __runWithRuntime<T>(services: RuntimeServices, fn: () => T): T;
112
112
  * process-global fallback (dev-server / tests). NOT part of the public
113
113
  * author-facing API — used by the runtime and the singleton Proxies. */
114
114
  declare function __getRuntime(): RuntimeServices;
115
+ /** A lifecycle hook. Sync or async; the runtime awaits what it returns. */
116
+ type LifecycleHook = () => void | Promise<void>;
117
+ /** Runs one release's shutdown hooks. Handed back by {@link __runStartHooks}
118
+ * and called by the engine's `app.shutdown()`. Idempotent. */
119
+ type ShutdownRunner = () => Promise<void>;
120
+ /**
121
+ * Run `hook` ONCE while the application comes up, before it serves anything.
122
+ *
123
+ * Call it at MODULE SCOPE in a file the application imports — the same rule
124
+ * `defineDefaultAuth` and `@Controller` follow, and for the same reason: the
125
+ * declaration is claimed when the app boots, which is after module loading and
126
+ * before the first request. `name` is not decoration: a hook that throws is
127
+ * reported by that name and the boot is REFUSED, so it is what tells an
128
+ * operator which resource did not come up.
129
+ *
130
+ * There is no request scope yet, so the `Database`/`Secrets`/… singletons are
131
+ * NOT available inside a start hook. A secret is read from `process.env` here
132
+ * (the runtime mirrors the vault into it at boot).
133
+ *
134
+ * @example
135
+ * // resources/graph.ts
136
+ * import neo4j from "neo4j-driver";
137
+ * import { onStart, onShutdown } from "@palbase/backend";
138
+ *
139
+ * export let graph: Driver;
140
+ * onStart("graph", () => {
141
+ * graph = neo4j.driver(process.env.NEO4J_URL!, neo4j.auth.basic("neo4j", process.env.NEO4J_PASSWORD!));
142
+ * });
143
+ * onShutdown("graph", () => graph.close());
144
+ */
145
+ declare function onStart(name: string, hook: LifecycleHook): void;
146
+ /**
147
+ * Run `hook` while the application shuts down — the place a pool opened in
148
+ * {@link onStart} is closed.
149
+ *
150
+ * Shutdown is BEST-EFFORT by design: a hook that throws is reported by name and
151
+ * the rest still run. A drain that abandoned the remaining hooks on the first
152
+ * failure would leak exactly what this exists to release, and the process is
153
+ * leaving anyway.
154
+ *
155
+ * Hooks run in REVERSE declaration order, so a resource is released before what
156
+ * it was built on.
157
+ */
158
+ declare function onShutdown(name: string, hook: LifecycleHook): void;
159
+ /**
160
+ * CLAIM what has been declared, run the start hooks, and hand back the runner
161
+ * for this release's shutdown hooks. Called by the engine's `createApp`; the
162
+ * `App.shutdown()` it builds calls what comes back. NOT part of the public
163
+ * author-facing API.
164
+ *
165
+ * IT CLAIMS RATHER THAN READS, which is what makes it correct in this runtime:
166
+ * a candidate release is loaded BESIDE the live one in one process
167
+ * (`v2/runtime/src/registry-scope.ts`), and both bundles append to the one
168
+ * shared slot above. If each app read the whole list, the live app's shutdown
169
+ * would close the candidate's pool and the candidate's would close the live
170
+ * app's. Taking the declarations leaves each app holding exactly its own.
171
+ *
172
+ * A start hook that throws REFUSES THE BOOT — with the hook's name in the
173
+ * message — after releasing whatever the earlier hooks already opened. Serving
174
+ * from a half-initialised app is the silence this whole surface replaces, and a
175
+ * boot that dies holding an open pool is the leak it replaces.
176
+ */
177
+ declare function __runStartHooks(): Promise<ShutdownRunner>;
178
+ /** Drop every declaration. For tests, which declare repeatedly in one process.
179
+ * NOT part of the public author-facing API. */
180
+ declare function __resetLifecycleHooks(): void;
115
181
  /**
116
182
  * The project's own Postgres (pgx, schema `env_<envId>`).
117
183
  *
@@ -361,15 +427,18 @@ declare function createLazyTransaction(sql: SqlDriver, role: string, claimsJson:
361
427
  type LazyTransaction = ReturnType<typeof createLazyTransaction>;
362
428
  /** Either a live driver transaction or the lazy holder above. */
363
429
  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. */
430
+ /** What `findMany` accepts beside its filter: an ordering, a row ceiling and a
431
+ * page offset. All three used to require dropping to raw SQL, and the docs said
432
+ * so — which is how a tenant's controllers filled up with hand-written SELECTs. */
367
433
  interface FindManyOptions {
368
434
  orderBy?: {
369
435
  column: string;
370
436
  direction?: "asc" | "desc";
371
437
  };
372
438
  limit?: number;
439
+ /** Rows to skip before the page starts. Only meaningful with `limit`, and
440
+ * refused without it — see `offsetClause`. */
441
+ offset?: number;
373
442
  }
374
443
  /** The six string-keyed operations, plus an interactive `transaction`. */
375
444
  declare function createOps(tx: TxLike): {
@@ -394,6 +463,41 @@ declare function createOps(tx: TxLike): {
394
463
  }): Promise<Row>;
395
464
  update(table: string, id: string, data: Row): Promise<Row | null>;
396
465
  delete(table: string, id: string): Promise<void>;
466
+ /**
467
+ * Update every row the filter matches, in ONE statement.
468
+ *
469
+ * The capability was already here and only reachable from inside a
470
+ * transaction plan (`tx.tables.x.updateWhere`, since 11.0.0). Outside it the
471
+ * only door was `update(id, …)`, so "mark every unapproved row in this
472
+ * household" was an N+1 loop or hand-written SQL — and hand-written SQL is
473
+ * where the typed surface and RLS both stop helping.
474
+ *
475
+ * The filter language is `findMany`'s, compiled by the same `compileWhere`:
476
+ * one filter language, or the two spellings drift.
477
+ *
478
+ * AN EMPTY FILTER IS REFUSED. `UPDATE … WHERE true` is a whole-table write,
479
+ * and the shape that produces it by accident — a filter object built from
480
+ * request input that happened to come back empty — is exactly the shape that
481
+ * should not silently succeed. Callers who mean every row say so with a
482
+ * predicate that is true for every row.
483
+ */
484
+ updateMany(table: string, where: Row, set: Row): Promise<Row[]>;
485
+ /**
486
+ * Delete every row the filter matches, in ONE statement; resolves to how
487
+ * many went. Same filter language, same empty-filter refusal as
488
+ * {@link updateMany} — and here the accident is worse.
489
+ */
490
+ deleteMany(table: string, where: Row): Promise<number>;
491
+ /**
492
+ * How many rows match — the half of pagination `limit`/`offset` cannot
493
+ * supply. Without it a page count is either a guess or "fetch everything and
494
+ * read .length", and the second one is the scale risk this surface exists to
495
+ * remove.
496
+ *
497
+ * An empty filter is legitimate HERE: counting a whole table is a read, and
498
+ * reads do not destroy anything.
499
+ */
500
+ count(table: string, where?: Row): Promise<number>;
397
501
  findById(table: string, id: string): Promise<Row | null>;
398
502
  findMany(table: string, query?: Row, opts?: FindManyOptions): Promise<Row[]>;
399
503
  /**
@@ -444,6 +548,21 @@ declare function createOps(tx: TxLike): {
444
548
  * tek SQL'de "id yok" ile "0 komşu" ayrılamazdı; +1 küçük turla id-yokluğu
445
549
  * adlandırılmış hataya çevrilir. Sonuç şekli search ile aynı (FR-015).
446
550
  */
551
+ /** D-021 (FR-027 DX): sayaçlar BAĞIMSIZ op'la — search'ün dizi-üstü
552
+ * `_facets` özelliği JSON.stringify'da kaybolur (dizi özelliği), tenant
553
+ * yanıtına koyunca sessizce yok olurdu. Ayrı dönüş ciddi bir sözleşmedir;
554
+ * search'teki alan geriye-uyum için DURUR. where + validity default'u
555
+ * sayaçlara da uygulanır (sayaç, kullanıcının gördüğü kümeyi anlatır). */
556
+ facets(table: string, params: {
557
+ facets: string[];
558
+ where?: Record<string, unknown>;
559
+ validity?: "all" | {
560
+ asOf: string;
561
+ };
562
+ }): Promise<Record<string, {
563
+ value: string | null;
564
+ count: number;
565
+ }[]>>;
447
566
  similar(table: string, id: string, opts?: {
448
567
  where?: Record<string, unknown>;
449
568
  limit?: number;
@@ -632,8 +751,11 @@ interface RouteEntry {
632
751
  instance: Record<string, (...args: unknown[]) => unknown>;
633
752
  /** `GET /todos/{id}` — stable, human-readable, used as the rate-limit key. */
634
753
  id: string;
635
- /** The controller's `auth` default, if it declared one. */
636
- controllerAuth: unknown;
754
+ /** The auth spec that applies when the route itself declares none —
755
+ * controller default ?? application default ?? `true`, resolved once at boot
756
+ * by `resolveEffectiveAuth`. `engine/index.ts` reconciles the route's own
757
+ * spec against it per request. */
758
+ controllerAuth: AuthSpec;
637
759
  }
638
760
  /**
639
761
  * Build the table from controller classes.
@@ -641,6 +763,9 @@ interface RouteEntry {
641
763
  * @throws when a class carries no routes — a controller that collected zero
642
764
  * endpoints is the silent failure this whole runtime is built to refuse, and
643
765
  * it must be loud at boot rather than a 404 in production.
766
+ * @throws when a class declares constructor parameters — the same class of
767
+ * silence one level down (FR-010): nothing here has an argument to pass, so
768
+ * the field would simply be `undefined` in production.
644
769
  */
645
770
  declare function buildRouteTable(controllers: readonly unknown[]): RouteEntry[];
646
771
  interface RouteMatch {
@@ -924,4 +1049,4 @@ interface App {
924
1049
  */
925
1050
  declare function createApp(opts: CreateAppOptions): Promise<App>;
926
1051
 
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 };
1052
+ export { type App as A, BootRefused as B, Cache as C, Database as D, type EgressPolicy as E, Flags as F, createOps as G, createRequestDatabase as H, effectiveAuth as I, hostAllowed as J, installEgressFence as K, type LifecycleHook as L, type ModuleClients as M, Notifications as N, loadConfig as O, makeMemoryCache as P, matchRoute as Q, Realtime as R, Secrets as S, quoteIdent as T, scrubSecrets as U, withTables as V, __getRuntime as _, Documents as a, Log as b, type RequestStore as c, type RuntimeServices as d, type ShutdownRunner as e, Storage as f, __requestALS as g, __resetLifecycleHooks as h, __runStartHooks as i, __runWithRuntime as j, __setRuntime as k, onStart as l, AuthVerifier as m, type CreateAppOptions as n, onShutdown as o, type EngineConfig as p, RateLimiter as q, type RequestDatabase as r, type RouteEntry as s, type RuntimeHooks as t, type ScrubResult as u, type SqlDriver as v, type SqlTx as w, buildRouteTable as x, createApp as y, createLazyTransaction as z };