@palbase/backend 23.1.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.
- package/dist/bin/palbase-backend.cjs +389 -44
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +4 -5
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/{chunk-OHALWEOG.js → chunk-7Z6MGMXQ.js} +57 -2
- package/dist/chunk-7Z6MGMXQ.js.map +1 -0
- package/dist/{chunk-RCLNBJCM.js → chunk-H3JAISUY.js} +136 -1
- package/dist/chunk-H3JAISUY.js.map +1 -0
- package/dist/{chunk-M5MCBWJI.js → chunk-NXDH6VQJ.js} +264 -28
- package/dist/chunk-NXDH6VQJ.js.map +1 -0
- package/dist/{chunk-NS5V43YQ.js → chunk-P2Q27SGP.js} +19 -3
- package/dist/chunk-P2Q27SGP.js.map +1 -0
- package/dist/{chunk-R3KN6RHD.js → chunk-T5IOSOE5.js} +7 -2
- package/dist/chunk-T5IOSOE5.js.map +1 -0
- package/dist/{chunk-PY7YJDCT.js → chunk-ZUGY7RGS.js} +32 -3
- package/dist/chunk-ZUGY7RGS.js.map +1 -0
- package/dist/db/index.cjs +48 -3
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +2 -2
- package/dist/{endpoint-CVWXh6oG.d.ts → endpoint-0_DGBajf.d.ts} +100 -3
- package/dist/{endpoint-c9h5jriX.d.cts → endpoint-CcQ1a36a.d.cts} +100 -3
- package/dist/engine/index.cjs +378 -31
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +4 -4
- package/dist/engine/index.d.ts +4 -4
- package/dist/engine/index.js +4 -4
- package/dist/{index-CwAJ7HEe.d.ts → index-CJiJU9ux.d.ts} +125 -21
- package/dist/{index-By8Dle5U.d.cts → index-D-4-PNuQ.d.cts} +125 -21
- package/dist/{index-BZrJXnVh.d.ts → index-D17r-MKb.d.ts} +134 -9
- package/dist/{index-CxeQSfJP.d.cts → index-DRFxf07H.d.cts} +134 -9
- package/dist/index.cjs +166 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -12
- package/dist/index.d.ts +54 -12
- package/dist/index.js +28 -11
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +16 -1
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +6 -4
- package/dist/openapi/index.d.ts +6 -4
- package/dist/openapi/index.js +6 -7
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-CqPK2Qby.d.cts → registry-1X-skBNu.d.cts} +1 -1
- package/dist/{registry-B3niOVYp.d.ts → registry-CEod_5sz.d.ts} +1 -1
- package/dist/test/index.cjs +27 -0
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +27 -0
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +4 -4
- package/docs/database.md +115 -11
- package/docs/getting-started.md +5 -4
- package/docs/llms-full.txt +385 -89
- package/docs/migrations.md +81 -59
- package/docs/schema.md +82 -2
- package/docs/services.md +98 -9
- package/package.json +2 -2
- package/template/AGENTS.md +121 -41
- package/template/controllers/notes.controller.ts +64 -0
- package/template/package.json +1 -1
- package/template/services/note.service.ts +74 -0
- package/template/tsconfig.json +11 -1
- package/dist/chunk-HQRJDARQ.js +0 -90
- package/dist/chunk-HQRJDARQ.js.map +0 -1
- package/dist/chunk-M5MCBWJI.js.map +0 -1
- package/dist/chunk-NS5V43YQ.js.map +0 -1
- package/dist/chunk-OHALWEOG.js.map +0 -1
- package/dist/chunk-PY7YJDCT.js.map +0 -1
- package/dist/chunk-R3KN6RHD.js.map +0 -1
- package/dist/chunk-RCLNBJCM.js.map +0 -1
|
@@ -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-
|
|
4
|
-
import { E as EnvTypedDatabase } from './index-
|
|
5
|
-
import { R as RouteMeta } from './registry-
|
|
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-CJiJU9ux.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
|
|
365
|
-
*
|
|
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
|
|
636
|
-
|
|
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,
|
|
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 };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
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, f as DBOps, T as TxPlanBody, g as TxPlanResponse } from './endpoint-
|
|
4
|
-
import { E as EnvTypedDatabase } from './index-
|
|
5
|
-
import { R as RouteMeta } from './registry-
|
|
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-CcQ1a36a.cjs';
|
|
4
|
+
import { E as EnvTypedDatabase } from './index-D-4-PNuQ.cjs';
|
|
5
|
+
import { R as RouteMeta } from './registry-1X-skBNu.cjs';
|
|
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
|
|
365
|
-
*
|
|
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
|
|
636
|
-
|
|
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,
|
|
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 };
|