@palbase/backend 27.1.0 → 29.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/bin/palbase-backend.cjs +284 -50
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -4
  4. package/dist/{chunk-JVZQCC77.js → chunk-3TUJWHC2.js} +8 -3
  5. package/dist/chunk-3TUJWHC2.js.map +1 -0
  6. package/dist/{chunk-EB3TUX5J.js → chunk-DHJ3SYAE.js} +251 -50
  7. package/dist/chunk-DHJ3SYAE.js.map +1 -0
  8. package/dist/{chunk-XABHGMUT.js → chunk-ENZ2RFFJ.js} +2 -2
  9. package/dist/{chunk-BQN723PL.js → chunk-ESSQ3YML.js} +73 -7
  10. package/dist/chunk-ESSQ3YML.js.map +1 -0
  11. package/dist/{chunk-OZKSM3JW.js → chunk-SG4UTNOP.js} +7 -3
  12. package/dist/chunk-SG4UTNOP.js.map +1 -0
  13. package/dist/db/index.cjs +8 -2
  14. package/dist/db/index.cjs.map +1 -1
  15. package/dist/db/index.d.cts +1 -1
  16. package/dist/db/index.d.ts +1 -1
  17. package/dist/db/index.js +3 -3
  18. package/dist/engine/index.cjs +294 -50
  19. package/dist/engine/index.cjs.map +1 -1
  20. package/dist/engine/index.d.cts +3 -3
  21. package/dist/engine/index.d.ts +3 -3
  22. package/dist/engine/index.js +11 -4
  23. package/dist/{index-V7QRh1wg.d.ts → index-BOe82fsK.d.ts} +256 -19
  24. package/dist/{index-CAKOgAlP.d.ts → index-BYDUKexK.d.ts} +139 -10
  25. package/dist/{index-H-0qv5d4.d.cts → index-BZfmdRmh.d.cts} +139 -10
  26. package/dist/{index-CgE4sVhg.d.cts → index-ChvoVczS.d.cts} +256 -19
  27. package/dist/index.cjs +81 -6
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +6 -22
  30. package/dist/index.d.ts +6 -22
  31. package/dist/index.js +10 -4
  32. package/dist/index.js.map +1 -1
  33. package/dist/openapi/index.d.cts +2 -2
  34. package/dist/openapi/index.d.ts +2 -2
  35. package/dist/{registry-DHsPDY0_.d.cts → registry-B2KfKWv1.d.cts} +1 -1
  36. package/dist/{registry-4EI8aaFs.d.ts → registry-DaqHsOq8.d.ts} +1 -1
  37. package/dist/test/index.cjs +129 -6
  38. package/dist/test/index.cjs.map +1 -1
  39. package/dist/test/index.d.cts +1 -1
  40. package/dist/test/index.d.ts +1 -1
  41. package/dist/test/index.js +126 -6
  42. package/dist/test/index.js.map +1 -1
  43. package/docs/README.md +1 -1
  44. package/docs/database.md +118 -1
  45. package/docs/llms-full.txt +119 -2
  46. package/package.json +1 -1
  47. package/template/package.json +1 -1
  48. package/dist/chunk-BQN723PL.js.map +0 -1
  49. package/dist/chunk-EB3TUX5J.js.map +0 -1
  50. package/dist/chunk-JVZQCC77.js.map +0 -1
  51. package/dist/chunk-OZKSM3JW.js.map +0 -1
  52. /package/dist/{chunk-XABHGMUT.js.map → chunk-ENZ2RFFJ.js.map} +0 -0
@@ -1,6 +1,6 @@
1
- import { D as DBClient, a as DBOps, T as TxPlanBody, b as TxPlanResponse, A as AuthSpec, C as CacheClient, c as RuntimeServices, _ as __runWithRuntime, d as __requestALS } from './index-V7QRh1wg.js';
1
+ import { D as DBClient, a as DBOps, T as TxPlanBody, b as TxPlanResponse, A as AuthSpec, C as CacheClient, c as RuntimeServices, _ as __runWithRuntime, d as __requestALS } from './index-BOe82fsK.js';
2
2
  import { T as Token } from './module-Dl1KFVtc.js';
3
- import { R as RouteMeta } from './registry-4EI8aaFs.js';
3
+ import { R as RouteMeta } from './registry-DaqHsOq8.js';
4
4
 
5
5
  /**
6
6
  * engine/config.ts — settings from the environment, and the gate that refuses
@@ -66,7 +66,15 @@ interface EngineConfig {
66
66
  dbServiceRole: string;
67
67
  poolMax: number;
68
68
  }
69
- /** Thrown when a mandatory module is unconfigured. Carries the missing names. */
69
+ /**
70
+ * THE ENVIRONMENT THIS STACK WAS HANDED IS UNUSABLE. Carries the missing names.
71
+ *
72
+ * ONLY the environment. A refusal about the author's own declarations is
73
+ * `DeclarationRefused` (see `src/refusals.ts`) and the difference is not
74
+ * severity, it is the CURE: no push fixes a missing DATABASE_URL, so the
75
+ * runtime must keep treating this one as fatal rather than waiting for an
76
+ * artifact that would change nothing.
77
+ */
70
78
  declare class BootRefused extends Error {
71
79
  readonly missing: readonly string[];
72
80
  constructor(missing: readonly string[], message: string);
@@ -171,10 +179,27 @@ interface FindManyOptions {
171
179
  * refused without it — see `offsetClause`. */
172
180
  offset?: number;
173
181
  }
174
- /** The six string-keyed operations, plus an interactive `transaction`. */
175
182
  declare function createOps(tx: TxLike): {
176
183
  query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
177
184
  insert(table: string, data: Row): Promise<Row>;
185
+ /**
186
+ * Çok satırı TEK statement'la ekle.
187
+ *
188
+ * Motor bunu zaten yapıyordu ve yalnız PLAN yolundan erişilebiliyordu
189
+ * (`tx.public.x.insertMany`). Dışarıda tek kapı `insert(row)` olduğu için
190
+ * "şu 200 satırı yaz" ya 200 gidiş-dönüş ya da ham SQL oluyordu — ölçüldü:
191
+ * bir müşteri projesinde bir seed rutini satırları tek tek siliyor
192
+ * (`deleteMany` VARKEN) ve tek tek ekliyor.
193
+ *
194
+ * SATIRLARIN ANAHTAR KÜMESİ AYNI OLMAK ZORUNDA. Kolon listesi tek bir
195
+ * statement'ta paylaşıldığı için ilk satırdan türer; farklı anahtar taşıyan
196
+ * bir satırın fazla kolonu SESSİZCE YAZILMAZDI. Eksik kolon için `null`
197
+ * yazın ya da varsayılanı istiyorsanız o satırları ayrı çağrıda ekleyin.
198
+ */
199
+ insertMany(table: string, rows: readonly Row[], opts?: {
200
+ onConflict: readonly string[];
201
+ action?: "ignore" | "update";
202
+ }): Promise<Row[]>;
178
203
  /**
179
204
  * Bir ADI kilitle — satırı değil (FR-054).
180
205
  *
@@ -266,7 +291,9 @@ declare function createOps(tx: TxLike): {
266
291
  * should not silently succeed. Callers who mean every row say so with a
267
292
  * predicate that is true for every row.
268
293
  */
269
- updateMany(table: string, where: Row, set: Row): Promise<Row[]>;
294
+ updateMany(table: string, where: Row, set: Row, opts?: {
295
+ returning?: boolean;
296
+ }): Promise<Row[] | number>;
270
297
  /**
271
298
  * Delete every row the filter matches, in ONE statement; resolves to how
272
299
  * many went. Same filter language, same empty-filter refusal as
@@ -282,6 +309,27 @@ declare function createOps(tx: TxLike): {
282
309
  * An empty filter is legitimate HERE: counting a whole table is a read, and
283
310
  * reads do not destroy anything.
284
311
  */
312
+ /**
313
+ * Toplama — `count`'un yanına `sum`/`avg`/`min`/`max` ve `groupBy`.
314
+ *
315
+ * Var olma nedeni: bunlar olmadan "bu hesabın toplamı" ya `$query`'ye ya da
316
+ * TÜM satırları çekip JS'te toplamaya düşüyor. İkincisi hem N+1 hem de para
317
+ * için YANLIŞ — `numeric` JS `number`'ına uğradığı anda kesinliğini
318
+ * kaybeder.
319
+ *
320
+ * `groupBy` verilirse SATIRLAR döner, verilmezse TEK sonuç. Boş kümede
321
+ * Postgres NULL döndürür, 0 değil — ve bu SDK onu `null` olarak geçiriyor:
322
+ * "hiç satır yoktu" ile "toplam sıfırdı" farklı şeyler.
323
+ */
324
+ aggregate(table: string, q?: {
325
+ where?: Row;
326
+ sum?: readonly string[];
327
+ avg?: readonly string[];
328
+ min?: readonly string[];
329
+ max?: readonly string[];
330
+ count?: boolean;
331
+ groupBy?: readonly string[];
332
+ }): Promise<Record<string, unknown> | Record<string, unknown>[]>;
285
333
  count(table: string, where?: Row): Promise<number>;
286
334
  findById(table: string, id: string): Promise<Row | null>;
287
335
  findMany(table: string, query?: Row, opts?: FindManyOptions): Promise<Row[]>;
@@ -527,6 +575,80 @@ declare function createRequestDatabase(sql: SqlDriver, identity: {
527
575
  claimsJson: string;
528
576
  }): RequestDatabase;
529
577
 
578
+ /**
579
+ * The two ways a stack refuses to boot — and they are two because their CURES
580
+ * are two.
581
+ *
582
+ * A LEAF MODULE ON PURPOSE. `db/` does not import from `engine/` and must not
583
+ * start: the schema layer is below the engine, and a refusal both of them raise
584
+ * cannot live in either. Anything imported here would invert that.
585
+ *
586
+ * WHY THE SPLIT EXISTS AT ALL — measured live on 2026-09-04.
587
+ *
588
+ * `8qitbtucm` ran for days on an image whose `setSchema` did not build
589
+ * relations. Moved onto one that does, the same artifact hit
590
+ * `buildRelations` at boot, threw a bare `Error`, and the runtime — which waits
591
+ * only on its own `ArtifactRefused` — treated it as fatal. The supervisor took
592
+ * the pod down, palsvc with it. CrashLoopBackOff, back-off 5m, and palsvc is
593
+ * THE PROCESS THAT ACCEPTS THE PUSH the message asks for.
594
+ *
595
+ * That is verbatim the lesson `v2/runtime/src/loader.ts` already carries for
596
+ * ABI refusals: *"the cure was named in the error and made impossible by the
597
+ * exit that carried it."* It cost a tenant a third time because the second
598
+ * refusal had no type to be recognised by.
599
+ *
600
+ * SO THE RULE IS THE CURE, NOT THE SEVERITY:
601
+ *
602
+ * {@link DeclarationRefused} — what was PUSHED cannot be built. Restarting
603
+ * re-reads the same bytes and fails identically; only a new artifact
604
+ * changes the answer. The runtime WAITS on this, keeps palsvc alive, and
605
+ * says the reason out loud.
606
+ *
607
+ * {@link BootRefused} — the ENVIRONMENT this stack was handed is unusable
608
+ * (no DATABASE_URL, an unparseable PORT, no SQL driver). No push fixes it;
609
+ * waiting for one would be a lie told in a log line forever. Stays fatal.
610
+ *
611
+ * NOT A MESSAGE PATTERN. The runtime's loader already says why: *"The
612
+ * distinction is the loader's TYPE, not a pattern over its wording."* A gate
613
+ * that reads wording drifts from the thing it claims to measure the first time
614
+ * someone improves an error message.
615
+ */
616
+ /**
617
+ * The tag that survives a realm boundary.
618
+ *
619
+ * `instanceof` compares constructor identity, and identity is per module
620
+ * instance. In the pod today the runtime and the bundle share one install
621
+ * (measured: both frames resolve to `/app/node_modules/@palbase/backend`), so
622
+ * `instanceof` would work — but it works by a coincidence of packaging, and a
623
+ * bundle that ever carries its own copy would make every declaration refusal
624
+ * silently fatal again. `Symbol.for` reads from the process-wide registry, so
625
+ * the tag means the same thing in every copy.
626
+ *
627
+ * Exported so the runtime can recognise the refusal without importing the
628
+ * class — see {@link isDeclarationRefused}.
629
+ */
630
+ declare const DECLARATION_REFUSAL: unique symbol;
631
+ /**
632
+ * What was pushed cannot be built. The cure is a NEW ARTIFACT.
633
+ *
634
+ * Thrown by the declaration layer — relation naming, ownership, entry points —
635
+ * wherever a fact about the author's code makes the app impossible to
636
+ * construct. Never thrown for anything the environment could change.
637
+ */
638
+ declare class DeclarationRefused extends Error {
639
+ /** @see DECLARATION_REFUSAL — realm-safe, unlike `instanceof`. */
640
+ readonly [DECLARATION_REFUSAL]: true;
641
+ constructor(message: string);
642
+ }
643
+ /**
644
+ * True for a refusal whose only cure is a new artifact — across realms.
645
+ *
646
+ * Takes `unknown` because every caller is a `catch`. A non-object, a null, a
647
+ * plain `Error`: all false, and false is the safe answer — it means "keep
648
+ * treating this as fatal", which is what the code did before this type existed.
649
+ */
650
+ declare function isDeclarationRefused(e: unknown): e is DeclarationRefused;
651
+
530
652
  /**
531
653
  * How a container refuses.
532
654
  *
@@ -550,6 +672,14 @@ declare class DiError extends Error {
550
672
  readonly path: string[];
551
673
  readonly at: string;
552
674
  readonly fixes: string[];
675
+ /**
676
+ * EVERY `DiKind` IS A FACT ABOUT THE AUTHOR'S DECLARATIONS — an unresolvable
677
+ * dependency, a cycle, a class no module owns. Not one of them is something
678
+ * the environment could change, so restarting re-reads the same bytes and
679
+ * fails identically. The cure is a new artifact, and the tag is how the
680
+ * runtime learns that without matching on wording.
681
+ */
682
+ readonly [DECLARATION_REFUSAL]: true;
553
683
  constructor(kind: DiKind, path: string[], at: string, detail: string, fixes: string[]);
554
684
  }
555
685
  /** How often a module appears in the OTHERS' `imports`. */
@@ -702,6 +832,9 @@ declare class AuthVerifier {
702
832
  interface EffectiveAuth {
703
833
  required: boolean;
704
834
  role?: string;
835
+ /** The permission the route demands, as `resource.action`. Resolved against
836
+ * `auth.has_permission(...)` at the gate — see AuthConfig.permission. */
837
+ permission?: string;
705
838
  verifiedEmail: boolean;
706
839
  }
707
840
  /**
@@ -921,10 +1054,6 @@ interface App {
921
1054
  */
922
1055
  readonly container: Container;
923
1056
  }
924
- /**
925
- * Build the app. Fails fast: the database is reached here, at boot, rather than
926
- * on the first request that needs it.
927
- */
928
1057
  declare function createApp(opts: CreateAppOptions): Promise<App>;
929
1058
 
930
- export { type App as A, BootRefused as B, type Container as C, DiError as D, type EgressPolicy as E, type ModuleClients as M, RateLimiter as R, type ScrubResult as S, type DiKind as a, type ModulePressure as b, assertNoOrphanEntryPoints as c, buildContainer as d, AuthVerifier as e, type CreateAppOptions as f, type EngineConfig as g, type RequestDatabase as h, type RouteEntry as i, type RuntimeHooks as j, type SqlDriver as k, type SqlTx as l, buildRouteTable as m, createApp as n, createLazyTransaction as o, createOps as p, createRequestDatabase as q, effectiveAuth as r, hostAllowed as s, installEgressFence as t, loadConfig as u, makeMemoryCache as v, matchRoute as w, quoteIdent as x, scrubSecrets as y, withTables as z };
1059
+ export { type App as A, BootRefused as B, type Container as C, DECLARATION_REFUSAL as D, type EgressPolicy as E, quoteIdent as F, scrubSecrets as G, withTables as H, type ModuleClients as M, RateLimiter as R, type ScrubResult as S, DeclarationRefused as a, DiError as b, type DiKind as c, type ModulePressure as d, assertNoOrphanEntryPoints as e, buildContainer as f, AuthVerifier as g, type CreateAppOptions as h, isDeclarationRefused as i, type EngineConfig as j, type RequestDatabase as k, type RouteEntry as l, type RuntimeHooks as m, type SqlDriver as n, type SqlTx as o, buildRouteTable as p, createApp as q, createLazyTransaction as r, createOps as s, createRequestDatabase as t, effectiveAuth as u, hostAllowed as v, installEgressFence as w, loadConfig as x, makeMemoryCache as y, matchRoute as z };
@@ -1,6 +1,6 @@
1
- import { D as DBClient, a as DBOps, T as TxPlanBody, b as TxPlanResponse, A as AuthSpec, C as CacheClient, c as RuntimeServices, _ as __runWithRuntime, d as __requestALS } from './index-CgE4sVhg.cjs';
1
+ import { D as DBClient, a as DBOps, T as TxPlanBody, b as TxPlanResponse, A as AuthSpec, C as CacheClient, c as RuntimeServices, _ as __runWithRuntime, d as __requestALS } from './index-ChvoVczS.cjs';
2
2
  import { T as Token } from './module-Dl1KFVtc.cjs';
3
- import { R as RouteMeta } from './registry-DHsPDY0_.cjs';
3
+ import { R as RouteMeta } from './registry-B2KfKWv1.cjs';
4
4
 
5
5
  /**
6
6
  * engine/config.ts — settings from the environment, and the gate that refuses
@@ -66,7 +66,15 @@ interface EngineConfig {
66
66
  dbServiceRole: string;
67
67
  poolMax: number;
68
68
  }
69
- /** Thrown when a mandatory module is unconfigured. Carries the missing names. */
69
+ /**
70
+ * THE ENVIRONMENT THIS STACK WAS HANDED IS UNUSABLE. Carries the missing names.
71
+ *
72
+ * ONLY the environment. A refusal about the author's own declarations is
73
+ * `DeclarationRefused` (see `src/refusals.ts`) and the difference is not
74
+ * severity, it is the CURE: no push fixes a missing DATABASE_URL, so the
75
+ * runtime must keep treating this one as fatal rather than waiting for an
76
+ * artifact that would change nothing.
77
+ */
70
78
  declare class BootRefused extends Error {
71
79
  readonly missing: readonly string[];
72
80
  constructor(missing: readonly string[], message: string);
@@ -171,10 +179,27 @@ interface FindManyOptions {
171
179
  * refused without it — see `offsetClause`. */
172
180
  offset?: number;
173
181
  }
174
- /** The six string-keyed operations, plus an interactive `transaction`. */
175
182
  declare function createOps(tx: TxLike): {
176
183
  query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
177
184
  insert(table: string, data: Row): Promise<Row>;
185
+ /**
186
+ * Çok satırı TEK statement'la ekle.
187
+ *
188
+ * Motor bunu zaten yapıyordu ve yalnız PLAN yolundan erişilebiliyordu
189
+ * (`tx.public.x.insertMany`). Dışarıda tek kapı `insert(row)` olduğu için
190
+ * "şu 200 satırı yaz" ya 200 gidiş-dönüş ya da ham SQL oluyordu — ölçüldü:
191
+ * bir müşteri projesinde bir seed rutini satırları tek tek siliyor
192
+ * (`deleteMany` VARKEN) ve tek tek ekliyor.
193
+ *
194
+ * SATIRLARIN ANAHTAR KÜMESİ AYNI OLMAK ZORUNDA. Kolon listesi tek bir
195
+ * statement'ta paylaşıldığı için ilk satırdan türer; farklı anahtar taşıyan
196
+ * bir satırın fazla kolonu SESSİZCE YAZILMAZDI. Eksik kolon için `null`
197
+ * yazın ya da varsayılanı istiyorsanız o satırları ayrı çağrıda ekleyin.
198
+ */
199
+ insertMany(table: string, rows: readonly Row[], opts?: {
200
+ onConflict: readonly string[];
201
+ action?: "ignore" | "update";
202
+ }): Promise<Row[]>;
178
203
  /**
179
204
  * Bir ADI kilitle — satırı değil (FR-054).
180
205
  *
@@ -266,7 +291,9 @@ declare function createOps(tx: TxLike): {
266
291
  * should not silently succeed. Callers who mean every row say so with a
267
292
  * predicate that is true for every row.
268
293
  */
269
- updateMany(table: string, where: Row, set: Row): Promise<Row[]>;
294
+ updateMany(table: string, where: Row, set: Row, opts?: {
295
+ returning?: boolean;
296
+ }): Promise<Row[] | number>;
270
297
  /**
271
298
  * Delete every row the filter matches, in ONE statement; resolves to how
272
299
  * many went. Same filter language, same empty-filter refusal as
@@ -282,6 +309,27 @@ declare function createOps(tx: TxLike): {
282
309
  * An empty filter is legitimate HERE: counting a whole table is a read, and
283
310
  * reads do not destroy anything.
284
311
  */
312
+ /**
313
+ * Toplama — `count`'un yanına `sum`/`avg`/`min`/`max` ve `groupBy`.
314
+ *
315
+ * Var olma nedeni: bunlar olmadan "bu hesabın toplamı" ya `$query`'ye ya da
316
+ * TÜM satırları çekip JS'te toplamaya düşüyor. İkincisi hem N+1 hem de para
317
+ * için YANLIŞ — `numeric` JS `number`'ına uğradığı anda kesinliğini
318
+ * kaybeder.
319
+ *
320
+ * `groupBy` verilirse SATIRLAR döner, verilmezse TEK sonuç. Boş kümede
321
+ * Postgres NULL döndürür, 0 değil — ve bu SDK onu `null` olarak geçiriyor:
322
+ * "hiç satır yoktu" ile "toplam sıfırdı" farklı şeyler.
323
+ */
324
+ aggregate(table: string, q?: {
325
+ where?: Row;
326
+ sum?: readonly string[];
327
+ avg?: readonly string[];
328
+ min?: readonly string[];
329
+ max?: readonly string[];
330
+ count?: boolean;
331
+ groupBy?: readonly string[];
332
+ }): Promise<Record<string, unknown> | Record<string, unknown>[]>;
285
333
  count(table: string, where?: Row): Promise<number>;
286
334
  findById(table: string, id: string): Promise<Row | null>;
287
335
  findMany(table: string, query?: Row, opts?: FindManyOptions): Promise<Row[]>;
@@ -527,6 +575,80 @@ declare function createRequestDatabase(sql: SqlDriver, identity: {
527
575
  claimsJson: string;
528
576
  }): RequestDatabase;
529
577
 
578
+ /**
579
+ * The two ways a stack refuses to boot — and they are two because their CURES
580
+ * are two.
581
+ *
582
+ * A LEAF MODULE ON PURPOSE. `db/` does not import from `engine/` and must not
583
+ * start: the schema layer is below the engine, and a refusal both of them raise
584
+ * cannot live in either. Anything imported here would invert that.
585
+ *
586
+ * WHY THE SPLIT EXISTS AT ALL — measured live on 2026-09-04.
587
+ *
588
+ * `8qitbtucm` ran for days on an image whose `setSchema` did not build
589
+ * relations. Moved onto one that does, the same artifact hit
590
+ * `buildRelations` at boot, threw a bare `Error`, and the runtime — which waits
591
+ * only on its own `ArtifactRefused` — treated it as fatal. The supervisor took
592
+ * the pod down, palsvc with it. CrashLoopBackOff, back-off 5m, and palsvc is
593
+ * THE PROCESS THAT ACCEPTS THE PUSH the message asks for.
594
+ *
595
+ * That is verbatim the lesson `v2/runtime/src/loader.ts` already carries for
596
+ * ABI refusals: *"the cure was named in the error and made impossible by the
597
+ * exit that carried it."* It cost a tenant a third time because the second
598
+ * refusal had no type to be recognised by.
599
+ *
600
+ * SO THE RULE IS THE CURE, NOT THE SEVERITY:
601
+ *
602
+ * {@link DeclarationRefused} — what was PUSHED cannot be built. Restarting
603
+ * re-reads the same bytes and fails identically; only a new artifact
604
+ * changes the answer. The runtime WAITS on this, keeps palsvc alive, and
605
+ * says the reason out loud.
606
+ *
607
+ * {@link BootRefused} — the ENVIRONMENT this stack was handed is unusable
608
+ * (no DATABASE_URL, an unparseable PORT, no SQL driver). No push fixes it;
609
+ * waiting for one would be a lie told in a log line forever. Stays fatal.
610
+ *
611
+ * NOT A MESSAGE PATTERN. The runtime's loader already says why: *"The
612
+ * distinction is the loader's TYPE, not a pattern over its wording."* A gate
613
+ * that reads wording drifts from the thing it claims to measure the first time
614
+ * someone improves an error message.
615
+ */
616
+ /**
617
+ * The tag that survives a realm boundary.
618
+ *
619
+ * `instanceof` compares constructor identity, and identity is per module
620
+ * instance. In the pod today the runtime and the bundle share one install
621
+ * (measured: both frames resolve to `/app/node_modules/@palbase/backend`), so
622
+ * `instanceof` would work — but it works by a coincidence of packaging, and a
623
+ * bundle that ever carries its own copy would make every declaration refusal
624
+ * silently fatal again. `Symbol.for` reads from the process-wide registry, so
625
+ * the tag means the same thing in every copy.
626
+ *
627
+ * Exported so the runtime can recognise the refusal without importing the
628
+ * class — see {@link isDeclarationRefused}.
629
+ */
630
+ declare const DECLARATION_REFUSAL: unique symbol;
631
+ /**
632
+ * What was pushed cannot be built. The cure is a NEW ARTIFACT.
633
+ *
634
+ * Thrown by the declaration layer — relation naming, ownership, entry points —
635
+ * wherever a fact about the author's code makes the app impossible to
636
+ * construct. Never thrown for anything the environment could change.
637
+ */
638
+ declare class DeclarationRefused extends Error {
639
+ /** @see DECLARATION_REFUSAL — realm-safe, unlike `instanceof`. */
640
+ readonly [DECLARATION_REFUSAL]: true;
641
+ constructor(message: string);
642
+ }
643
+ /**
644
+ * True for a refusal whose only cure is a new artifact — across realms.
645
+ *
646
+ * Takes `unknown` because every caller is a `catch`. A non-object, a null, a
647
+ * plain `Error`: all false, and false is the safe answer — it means "keep
648
+ * treating this as fatal", which is what the code did before this type existed.
649
+ */
650
+ declare function isDeclarationRefused(e: unknown): e is DeclarationRefused;
651
+
530
652
  /**
531
653
  * How a container refuses.
532
654
  *
@@ -550,6 +672,14 @@ declare class DiError extends Error {
550
672
  readonly path: string[];
551
673
  readonly at: string;
552
674
  readonly fixes: string[];
675
+ /**
676
+ * EVERY `DiKind` IS A FACT ABOUT THE AUTHOR'S DECLARATIONS — an unresolvable
677
+ * dependency, a cycle, a class no module owns. Not one of them is something
678
+ * the environment could change, so restarting re-reads the same bytes and
679
+ * fails identically. The cure is a new artifact, and the tag is how the
680
+ * runtime learns that without matching on wording.
681
+ */
682
+ readonly [DECLARATION_REFUSAL]: true;
553
683
  constructor(kind: DiKind, path: string[], at: string, detail: string, fixes: string[]);
554
684
  }
555
685
  /** How often a module appears in the OTHERS' `imports`. */
@@ -702,6 +832,9 @@ declare class AuthVerifier {
702
832
  interface EffectiveAuth {
703
833
  required: boolean;
704
834
  role?: string;
835
+ /** The permission the route demands, as `resource.action`. Resolved against
836
+ * `auth.has_permission(...)` at the gate — see AuthConfig.permission. */
837
+ permission?: string;
705
838
  verifiedEmail: boolean;
706
839
  }
707
840
  /**
@@ -921,10 +1054,6 @@ interface App {
921
1054
  */
922
1055
  readonly container: Container;
923
1056
  }
924
- /**
925
- * Build the app. Fails fast: the database is reached here, at boot, rather than
926
- * on the first request that needs it.
927
- */
928
1057
  declare function createApp(opts: CreateAppOptions): Promise<App>;
929
1058
 
930
- export { type App as A, BootRefused as B, type Container as C, DiError as D, type EgressPolicy as E, type ModuleClients as M, RateLimiter as R, type ScrubResult as S, type DiKind as a, type ModulePressure as b, assertNoOrphanEntryPoints as c, buildContainer as d, AuthVerifier as e, type CreateAppOptions as f, type EngineConfig as g, type RequestDatabase as h, type RouteEntry as i, type RuntimeHooks as j, type SqlDriver as k, type SqlTx as l, buildRouteTable as m, createApp as n, createLazyTransaction as o, createOps as p, createRequestDatabase as q, effectiveAuth as r, hostAllowed as s, installEgressFence as t, loadConfig as u, makeMemoryCache as v, matchRoute as w, quoteIdent as x, scrubSecrets as y, withTables as z };
1059
+ export { type App as A, BootRefused as B, type Container as C, DECLARATION_REFUSAL as D, type EgressPolicy as E, quoteIdent as F, scrubSecrets as G, withTables as H, type ModuleClients as M, RateLimiter as R, type ScrubResult as S, DeclarationRefused as a, DiError as b, type DiKind as c, type ModulePressure as d, assertNoOrphanEntryPoints as e, buildContainer as f, AuthVerifier as g, type CreateAppOptions as h, isDeclarationRefused as i, type EngineConfig as j, type RequestDatabase as k, type RouteEntry as l, type RuntimeHooks as m, type SqlDriver as n, type SqlTx as o, buildRouteTable as p, createApp as q, createLazyTransaction as r, createOps as s, createRequestDatabase as t, effectiveAuth as u, hostAllowed as v, installEgressFence as w, loadConfig as x, makeMemoryCache as y, matchRoute as z };