@palbase/backend 17.4.0 → 18.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 (72) hide show
  1. package/dist/bin/palbase-backend.cjs +1848 -0
  2. package/dist/bin/palbase-backend.cjs.map +1 -0
  3. package/dist/bin/palbase-backend.d.cts +1 -0
  4. package/dist/bin/palbase-backend.d.ts +1 -0
  5. package/dist/bin/palbase-backend.js +168 -0
  6. package/dist/bin/palbase-backend.js.map +1 -0
  7. package/dist/chunk-7D4SUZUM.js +38 -0
  8. package/dist/chunk-7D4SUZUM.js.map +1 -0
  9. package/dist/chunk-N32VDWKH.js +172 -0
  10. package/dist/chunk-N32VDWKH.js.map +1 -0
  11. package/dist/chunk-POYAFBLF.js +189 -0
  12. package/dist/chunk-POYAFBLF.js.map +1 -0
  13. package/dist/chunk-QMVK4X3V.js +200 -0
  14. package/dist/chunk-QMVK4X3V.js.map +1 -0
  15. package/dist/chunk-SSGAMC26.js +342 -0
  16. package/dist/chunk-SSGAMC26.js.map +1 -0
  17. package/dist/chunk-VYH4U7ZQ.js +1138 -0
  18. package/dist/chunk-VYH4U7ZQ.js.map +1 -0
  19. package/dist/{chunk-AAN642N5.js → chunk-W5ODXPY3.js} +2 -336
  20. package/dist/chunk-W5ODXPY3.js.map +1 -0
  21. package/dist/chunk-YL4C5NRY.js +90 -0
  22. package/dist/chunk-YL4C5NRY.js.map +1 -0
  23. package/dist/db/env.cjs.map +1 -1
  24. package/dist/db/env.d.cts +21 -1
  25. package/dist/db/env.d.ts +21 -1
  26. package/dist/db/index.cjs.map +1 -1
  27. package/dist/db/index.d.cts +2 -1
  28. package/dist/db/index.d.ts +2 -1
  29. package/dist/db/index.js +9 -6
  30. package/dist/{index-VLrU7rSW.d.ts → endpoint-B0LpZixz.d.cts} +124 -685
  31. package/dist/{index-BA_oFAz9.d.cts → endpoint-B0LpZixz.d.ts} +124 -685
  32. package/dist/engine/index.cjs +1797 -0
  33. package/dist/engine/index.cjs.map +1 -0
  34. package/dist/engine/index.d.cts +7 -0
  35. package/dist/engine/index.d.ts +7 -0
  36. package/dist/engine/index.js +43 -0
  37. package/dist/engine/index.js.map +1 -0
  38. package/dist/index-B46CGNvx.d.cts +839 -0
  39. package/dist/index-BGSCWlUa.d.cts +674 -0
  40. package/dist/index-DZDUMth5.d.ts +839 -0
  41. package/dist/index-g-EzitI-.d.ts +674 -0
  42. package/dist/index.cjs +1031 -11
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +290 -532
  45. package/dist/index.d.ts +290 -532
  46. package/dist/index.js +999 -509
  47. package/dist/index.js.map +1 -1
  48. package/dist/openapi/index.cjs +6464 -0
  49. package/dist/openapi/index.cjs.map +1 -0
  50. package/dist/openapi/index.d.cts +170 -0
  51. package/dist/openapi/index.d.ts +170 -0
  52. package/dist/openapi/index.js +6248 -0
  53. package/dist/openapi/index.js.map +1 -0
  54. package/dist/registry-3BLYv4si.d.ts +338 -0
  55. package/dist/registry-Cw0YEYCg.d.cts +338 -0
  56. package/dist/test/index.js +2 -0
  57. package/dist/test/index.js.map +1 -1
  58. package/docs/database.md +16 -3
  59. package/docs/llms-full.txt +16 -3
  60. package/package.json +43 -13
  61. package/stager/package.json +4 -0
  62. package/stager/return_types.js +338 -0
  63. package/stager/stage.js +78 -0
  64. package/stager/throw_analysis.js +726 -0
  65. package/template/AGENTS.md +261 -0
  66. package/template/config/secrets.ts +24 -0
  67. package/template/controllers/health.controller.ts +30 -0
  68. package/template/db/schema.ts +35 -0
  69. package/template/package.json +18 -0
  70. package/template/tsconfig.json +30 -0
  71. package/LICENSE +0 -21
  72. package/dist/chunk-AAN642N5.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,245 +1,16 @@
1
- import { C as CacheClient, E as EnvTypedDatabase, P as PalbaseDocsClient, a as PalbaseFlagsClient, L as Logger, b as PalbaseNotificationsClient, c as PalbaseRealtimeClient, D as DBClient, d as PalbaseStorageClient, S as SchemaDef, A as AuthSpec, R as RateLimitConfig, H as HttpError } from './index-VLrU7rSW.js';
2
- export { e as AuthConfig, B as BadRequest, f as ClientInfo, g as ColumnBuilder, h as ColumnDef, i as ColumnMap, j as ColumnType, k as Conflict, l as DBOps, m as EXTENSION_DEPENDENCIES, n as EnvServiceDatabase, o as EnvTables, p as EnvTypedTable, q as ErrorDef, r as ErrorMap, s as ErrorThrowers, F as FileContext, t as Forbidden, u as HttpMethod, I as InsertShape, M as Materialized, v as Middleware, w as MiddlewareContext, x as MiddlewareHandler, N as NotFound, O as OnDeleteAction, y as PALBASE_EXTENSIONS, z as PBRequest, G as PalError, J as PalbaseAnalyticsClient, K as PalbaseAnalyticsManagementNamespace, Q as PalbaseAnalyticsProperties, T as PalbaseAnalyticsQueryNamespace, U as PalbaseAttestAndroidParams, V as PalbaseAttestAndroidResult, W as PalbaseAttestiOSParams, X as PalbaseAttestiOSResult, Y as PalbaseAuthClient, Z as PalbaseBatchOverrideOperation, _ as PalbaseBatchSetOverridesResult, $ as PalbaseBindDeviceParams, a0 as PalbaseBucketClient, a1 as PalbaseClearAllOverridesResult, a2 as PalbaseClearOverrideResult, a3 as PalbaseCohortQueryInput, a4 as PalbaseCohortResult, a5 as PalbaseCollectionRef, a6 as PalbaseCountQueryInput, a7 as PalbaseCountResult, a8 as PalbaseCreateLinkParams, a9 as PalbaseDeviceInfo, aa as PalbaseDeviceTokenView, ab as PalbaseDocumentRef, ac as PalbaseDocumentSnapshot, ad as PalbaseEmailClient, ae as PalbaseEmailSendParams, af as PalbaseEmailSendResponse, ag as PalbaseEventNamesResult, ah as PalbaseEventsQueryInput, ai as PalbaseEventsResult, aj as PalbaseExtension, ak as PalbaseFileObject, al as PalbaseFlag, am as PalbaseFlagContext, an as PalbaseFlagSource, ao as PalbaseFlagValue, ap as PalbaseFlagVariant, aq as PalbaseFlagsServiceClient, ar as PalbaseFunctionsClient, as as PalbaseFunnelQueryInput, at as PalbaseFunnelResult, au as PalbaseIdentifyTraits, av as PalbaseInboxClient, aw as PalbaseInboxListOptions, ax as PalbaseInboxListResult, ay as PalbaseInboxMessage, az as PalbaseInboxSendParams, aA as PalbaseInboxSendResponse, aB as PalbaseInitialLink, aC as PalbaseInvokeOptions, aD as PalbaseLink, aE as PalbaseLinkAnalytics, aF as PalbaseLinkDetails, aG as PalbaseLinksClient, aH as PalbaseListLinksOptions, aI as PalbaseListLinksResult, aJ as PalbaseListOptions, aK as PalbaseMatchParams, aL as PalbaseMultiChannelResponse, aM as PalbaseOverviewResult, aN as PalbasePreferences, aO as PalbasePreferencesClient, aP as PalbasePushClient, aQ as PalbasePushSendParams, aR as PalbasePushSendResponse, aS as PalbaseQrCodeOptions, aT as PalbaseQuerySnapshot, aU as PalbaseRegisterDeviceParams, aV as PalbaseResult, aW as PalbaseRetentionQueryInput, aX as PalbaseRetentionResult, aY as PalbaseSession, aZ as PalbaseSetOverrideResult, a_ as PalbaseSetOverridesResult, a$ as PalbaseSignedUrlResponse, b0 as PalbaseSmsClient, b1 as PalbaseSmsSendParams, b2 as PalbaseSmsSendResponse, b3 as PalbaseTransformOptions, b4 as PalbaseUpdateLinkParams, b5 as PalbaseUploadOptions, b6 as PalbaseUser, b7 as PalbaseUserDetailResult, b8 as PalbaseUsersQueryInput, b9 as PalbaseUsersResult, ba as PalbaseVerifyRequestSignatureParams, bb as PalbaseWhereOperator, bc as PolicyBuilder, bd as PolicyCommand, be as PolicyDef, bf as PolicyMode, bg as RawConstraintDef, bh as Ref, bi as RowShape, bj as SchemaInput, bk as TableDef, bl as TableInput, bm as TooManyRequests, bn as TxColumnExpr, bo as TxInsertShape, bp as TxInsertValue, bq as TxNow, br as TxPlan, bs as TxPlanBody, bt as TxPlanError, bu as TxPlanHandle, bv as TxPlanOpResult, bw as TxPlanRejection, bx as TxPlanResponse, by as TxRefError, bz as TxRow, bA as TxRows, bB as TxSelectOptions, bC as TxSetShape, bD as TxSetValue, bE as TxTable, bF as TxTables, bG as TxWhere, bH as TxWireExpr, bI as TxWireGuard, bJ as TxWireOp, bK as TxWireRef, bL as TxWireValue, bM as TypedDB, bN as TypedTable, bO as TypedTx, bP as Unauthorized, bQ as UserT, bR as VerifiedDevice, bS as bigint, bT as boolean, bU as dec, bV as defineMiddleware, bW as defineSchema, bX as enumType, bY as inc, bZ as integer, b_ as isPalbaseExtension, b$ as jsonb, c0 as makeTypedDB, c1 as now, c2 as numeric, c3 as policy, c4 as raw, c5 as text, c6 as timestamp, c7 as uuid } from './index-VLrU7rSW.js';
3
- import { AsyncLocalStorage } from 'node:async_hooks';
1
+ import { g as PalbaseResult, A as AuthSpec, H as HttpError } from './endpoint-B0LpZixz.js';
2
+ export { h as AuthConfig, B as BadRequest, C as CacheClient, i as ClientInfo, j as Conflict, D as DBClient, k as DBOps, E as ErrorDef, l as ErrorMap, m as ErrorThrowers, F as FileContext, n as Forbidden, o as HttpMethod, L as Logger, M as Materialized, p as Middleware, q as MiddlewareContext, r as MiddlewareHandler, N as NotFound, s as PBRequest, t as PalError, u as PalbaseAnalyticsClient, v as PalbaseAnalyticsManagementNamespace, w as PalbaseAnalyticsProperties, x as PalbaseAnalyticsQueryNamespace, y as PalbaseAttestAndroidParams, z as PalbaseAttestAndroidResult, G as PalbaseAttestiOSParams, I as PalbaseAttestiOSResult, J as PalbaseAuthClient, K as PalbaseBatchOverrideOperation, O as PalbaseBatchSetOverridesResult, Q as PalbaseBindDeviceParams, e as PalbaseBucketClient, U as PalbaseClearAllOverridesResult, V as PalbaseClearOverrideResult, W as PalbaseCohortQueryInput, X as PalbaseCohortResult, Y as PalbaseCollectionRef, Z as PalbaseCountQueryInput, _ as PalbaseCountResult, $ as PalbaseCreateLinkParams, a0 as PalbaseDeviceInfo, a1 as PalbaseDeviceTokenView, P as PalbaseDocsClient, a2 as PalbaseDocumentRef, a3 as PalbaseDocumentSnapshot, a4 as PalbaseEmailClient, a5 as PalbaseEmailSendParams, a6 as PalbaseEmailSendResponse, a7 as PalbaseEventNamesResult, a8 as PalbaseEventsQueryInput, a9 as PalbaseEventsResult, aa as PalbaseFileObject, ab as PalbaseFlag, ac as PalbaseFlagContext, ad as PalbaseFlagSource, ae as PalbaseFlagValue, af as PalbaseFlagVariant, a as PalbaseFlagsClient, ag as PalbaseFlagsServiceClient, ah as PalbaseFunctionsClient, ai as PalbaseFunnelQueryInput, aj as PalbaseFunnelResult, ak as PalbaseIdentifyTraits, al as PalbaseInboxClient, am as PalbaseInboxListOptions, an as PalbaseInboxListResult, ao as PalbaseInboxMessage, ap as PalbaseInboxSendParams, aq as PalbaseInboxSendResponse, ar as PalbaseInitialLink, as as PalbaseInvokeOptions, at as PalbaseLink, au as PalbaseLinkAnalytics, av as PalbaseLinkDetails, aw as PalbaseLinksClient, ax as PalbaseListLinksOptions, ay as PalbaseListLinksResult, az as PalbaseListOptions, aA as PalbaseMatchParams, aB as PalbaseMultiChannelResponse, b as PalbaseNotificationsClient, aC as PalbaseOverviewResult, aD as PalbasePreferences, aE as PalbasePreferencesClient, aF as PalbasePushClient, aG as PalbasePushSendParams, aH as PalbasePushSendResponse, aI as PalbaseQrCodeOptions, aJ as PalbaseQuerySnapshot, c as PalbaseRealtimeClient, aK as PalbaseRegisterDeviceParams, aL as PalbaseRetentionQueryInput, aM as PalbaseRetentionResult, aN as PalbaseSession, aO as PalbaseSetOverrideResult, aP as PalbaseSetOverridesResult, aQ as PalbaseSignedUrlResponse, aR as PalbaseSmsClient, aS as PalbaseSmsSendParams, aT as PalbaseSmsSendResponse, d as PalbaseStorageClient, aU as PalbaseTransformOptions, aV as PalbaseUpdateLinkParams, aW as PalbaseUploadOptions, aX as PalbaseUser, aY as PalbaseUserDetailResult, aZ as PalbaseUsersQueryInput, a_ as PalbaseUsersResult, a$ as PalbaseVerifyRequestSignatureParams, b0 as PalbaseWhereOperator, R as RateLimitConfig, b1 as Ref, S as SecretsService, b2 as TooManyRequests, b3 as TxColumnExpr, b4 as TxInsertShape, b5 as TxInsertValue, 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, bg as TxSetValue, bh as TxTable, bi as TxWhere, bj as TxWireExpr, bk as TxWireGuard, bl as TxWireOp, bm as TxWireRef, bn as TxWireValue, bo as Unauthorized, bp as UserT, bq as VerifiedDevice, br as dec, bs as defineMiddleware, bt as inc, bu as now } from './endpoint-B0LpZixz.js';
3
+ import { M as ModuleClients } from './index-DZDUMth5.js';
4
+ export { C as Cache, D as Database, a as Documents, F as Flags, L as LimitState, b as Log, N as Notifications, P as Purchases, c as PurchasesService, R as Realtime, d as RequestStore, e as RuntimeServices, S as Secrets, f as SpendOptions, g as Storage, h as StoreEnv, _ as __getRuntime, i as __requestALS, j as __runWithRuntime, k as __setRuntime } from './index-DZDUMth5.js';
4
5
  import { EntitlementKey, LimitKey } from './purchases/keys.js';
6
+ import { S as SchemaDef } from './index-g-EzitI-.js';
7
+ export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EnvServiceDatabase, f as EnvTables, E as EnvTypedDatabase, g as EnvTypedTable, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, h as PalbaseExtension, i as PolicyBuilder, j as PolicyCommand, k as PolicyDef, l as PolicyMode, R as RawConstraintDef, m as RowShape, n as SchemaInput, T as TableDef, o as TableInput, p as TxPlan, q as TxTables, r as TypedDB, s as TypedTable, t as TypedTx, u as bigint, v as boolean, w as defineSchema, x as enumType, y as integer, z as isPalbaseExtension, A as jsonb, B as makeTypedDB, D as numeric, F as policy, G as raw, H as text, J as timestamp, K as uuid } from './index-g-EzitI-.js';
5
8
  import { TableTypes, Tables } from './db/env.js';
9
+ import { a as RouteOptions } from './registry-3BLYv4si.js';
10
+ export { B as BucketDef, b as BucketOptions, H as HttpMethodUpper, I as ImageVariant, c as ParamKind, P as ParamMeta, R as RouteMeta, S as STORAGE_CONFIG_KIND, d as StorageConfig, e as StorageInput, T as ThrowDescriptor, f as Upload, U as UploadConfig, g as UploadedObject, h as bucket, i as defineStorage, j as getRoutes, p as parseFileSizeLimit, r as recordThrows, v as validateUploadAgainstStorage } from './registry-3BLYv4si.js';
6
11
  import { ZodTypeAny, z } from 'zod';
7
12
  export { z } from 'zod';
8
-
9
- /**
10
- * The purchases surface the decorators need, declared as a NARROW STRUCTURAL
11
- * interface rather than an import of `@palstore/purchases`.
12
- *
13
- * `PurchasesClient` (palstore's `sk_` backend SDK) satisfies this shape as-is,
14
- * so the runtime injects the real client with no adapter — but `@palbase/backend`
15
- * itself gains no dependency on it. That matters: this package is published
16
- * public and baked into the br-pod image from a tarball, so a dependency on an
17
- * unpublished sibling would break `npm install palbase-backend.tgz`. It is also
18
- * the pattern `withSpend` already uses for the same reason (`SpendCapableClient`
19
- * in palstore's own spend.ts: "a narrow structural interface … so this file has
20
- * no dependency on client.ts").
21
- *
22
- * The methods here are a SUBSET of `PurchasesClient` — only what the two
23
- * decorators call. Grants, refunds, credits and customer-info reads stay off
24
- * this interface: a tenant that wants them imports the palstore SDK directly.
25
- */
26
- /** Store environment a subject is fixed to. Mirrors `StoreEnv` in `@palstore/purchases`. */
27
- type StoreEnv = "production" | "sandbox";
28
- /** Quota/credit state carried by a 429. Mirrors `LimitState` in `@palstore/purchases`
29
- * (SPEC-purchases-v1 §11) — re-declared, not imported, for the reason above. */
30
- interface LimitState {
31
- key: string;
32
- scope: string;
33
- window: string;
34
- used: number;
35
- reserved: number;
36
- max: number;
37
- remaining: number;
38
- resetAt: string;
39
- }
40
- /** Options for one spend. `idempotencyKey` is required by the server (§9). */
41
- interface SpendOptions {
42
- /** Defaults to 1. */
43
- count?: number;
44
- idempotencyKey: string;
45
- }
46
- interface PurchasesService {
47
- /** Map a tenant-side user reference to its palstore subject, creating one on
48
- * first sight. Server-authoritative — the caller never names a subject. */
49
- resolveSubject(input: {
50
- userRef: string;
51
- storeEnv: StoreEnv;
52
- }): Promise<{
53
- subjectId: string;
54
- }>;
55
- /** Resolve silently when `entitlementKey` is active for `subjectId`; throw
56
- * `EntitlementRequiredError` otherwise. Consumes nothing. */
57
- require(subjectId: string, entitlementKey: string): Promise<void>;
58
- /** Reserve → run `handler` → commit on success, cancel on throw, always
59
- * rethrowing the handler's own error. The whole reason the decorators are a
60
- * thin layer: this lifecycle is already written and tested in palstore's SDK. */
61
- withSpend<T>(subjectId: string, key: string, opts: SpendOptions, handler: () => Promise<T>): Promise<T>;
62
- }
63
-
64
- /**
65
- * runtime.ts — request-scoped service singletons.
66
- *
67
- * The backend SDK no longer threads a `ctx` god-object through every handler.
68
- * Instead, controller methods import PascalCase service singletons directly:
69
- *
70
- * import { Controller, Post, Body, Database } from "@palbase/backend";
71
- *
72
- * \@Controller("/todos")
73
- * export default class TodosController {
74
- * \@Post("") create(\@Body(CreateTodoBody) body: CreateTodoBody): unknown {
75
- * return Database.insert("todos", { title: body.title });
76
- * }
77
- * }
78
- *
79
- * The singletons are thin Proxies. Every property access forwards to the live
80
- * client for the CURRENT request scope, resolved through {@link __getRuntime}.
81
- *
82
- * # Request-scope resolution (persistent app-server)
83
- *
84
- * The runtime is a long-running Node process that serves many concurrent
85
- * requests on one event loop (NOT a fresh subprocess per request). A single
86
- * module-global slot would let one in-flight request's services bleed into
87
- * another's. So the services are carried in an {@link AsyncLocalStorage} store
88
- * ({@link __requestALS}) that the runtime sets per request with
89
- * {@link __runWithRuntime}; every async continuation of that request reads its
90
- * own store. `__getRuntime` reads the ALS store first; the module-global slot
91
- * (set by {@link __setRuntime}) is only a fallback for callers that run OUTSIDE
92
- * an ALS scope (dev-server, unit tests, the legacy single-shot path). Because
93
- * each `br-<ref>` pod is single-tenant, there is no cross-tenant leakage; the
94
- * ALS store is what prevents cross-REQUEST leakage within the shared process.
95
- *
96
- * The seam that makes `import { Database } from "@palbase/backend"` resolve to
97
- * the runtime-injected client: `@palbase/backend` is marked esbuild-EXTERNAL
98
- * when the tenant bundle is built, and the package is installed globally in the
99
- * pod (NODE_PATH=/usr/local/lib/node_modules). So worker.js's
100
- * `require('@palbase/backend')` and the bundle's `import` resolve to ONE shared
101
- * module instance — the ALS store and `__setRuntime` slot on that instance are
102
- * visible to the singletons the bundle imported.
103
- */
104
-
105
- /** The set of live clients the runtime injects per request scope.
106
- *
107
- * Realtime is BROADCAST-ONLY here (a stateless handler can push an event but
108
- * cannot hold a subscription socket — `subscribe()` lives on the client SDK).
109
- *
110
- * EXCLUDED on purpose: Functions, Links, Analytics, Auth. They are not
111
- * exposed as backend handler singletons (auth lives on the client SDK; the rest
112
- * are out of scope for backend endpoints). */
113
- interface RuntimeServices {
114
- Database: DBClient;
115
- Documents: PalbaseDocsClient;
116
- Storage: PalbaseStorageClient;
117
- Cache: CacheClient;
118
- Log: Logger;
119
- Notifications: PalbaseNotificationsClient;
120
- Flags: PalbaseFlagsClient;
121
- Realtime: PalbaseRealtimeClient;
122
- Purchases: PurchasesService;
123
- }
124
- /**
125
- * The per-request ALS box.
126
- *
127
- * `runtime` is the service bundle. `userId` is the request's authenticated user
128
- * id, written by the runtime immediately after it assembles the request object
129
- * (worker.js: `requestALS.getStore().userId = pbReq.user?.id || null`) and
130
- * `null` on an anonymous request. It was already being written there for the
131
- * Flags client's auto-bind; declaring it here makes the existing contract typed
132
- * instead of implicit, which is what lets `currentSubjectId()` read a
133
- * server-owned identity rather than trusting anything the caller sent.
134
- */
135
- interface RequestStore {
136
- runtime: RuntimeServices;
137
- userId?: string | null;
138
- /** This request's id (`req_…`), written by the runtime. Used as the spend
139
- * idempotency scope when the caller supplied no `Idempotency-Key`. */
140
- requestId?: string;
141
- /** The caller's `Idempotency-Key` header, or `null` when absent. What makes a
142
- * client's retry replay its first result instead of charging twice. */
143
- idempotencyKey?: string | null;
144
- }
145
- /**
146
- * Per-request store. The persistent runtime runs each request inside
147
- * {@link __runWithRuntime}, so every async continuation of that request reads
148
- * its OWN `runtime` (and any other request-scoped fields the runtime adds).
149
- *
150
- * Exported with a `__` prefix so the runtime (worker.js) shares the SAME ALS
151
- * instance across the one module instance — two ALS instances would silently
152
- * not see each other's stores. NOT part of the public author-facing API.
153
- */
154
- declare const __requestALS: AsyncLocalStorage<RequestStore>;
155
- /** Install the live clients in the process-global fallback slot.
156
- *
157
- * Persistent-server requests should use {@link __runWithRuntime} instead; this
158
- * remains for dev-server / tests / the legacy single-shot path that run without
159
- * an ALS scope. NOT part of the public author-facing API. */
160
- declare function __setRuntime(services: RuntimeServices): void;
161
- /** Run `fn` with `services` bound as the request-scoped runtime.
162
- *
163
- * The persistent worker calls this once per request so concurrent requests
164
- * never share a services slot. NOT part of the public author-facing API. */
165
- declare function __runWithRuntime<T>(services: RuntimeServices, fn: () => T): T;
166
- /** Read the live clients, throwing if accessed outside a request scope.
167
- *
168
- * Resolves the ALS store first (persistent server, per-request), then the
169
- * process-global fallback (dev-server / tests). NOT part of the public
170
- * author-facing API — used by the runtime and the singleton Proxies. */
171
- declare function __getRuntime(): RuntimeServices;
172
- /**
173
- * The project's own Postgres (pgx, schema `env_<envId>`).
174
- *
175
- * Typed by default: `Database.tables.<name>.insert({...})` is typed against
176
- * the project's generated `palbase-env.d.ts` with NO import and NO generic.
177
- * The raw string ops (`query`/`insert`/`update`/`delete`/`findById`/`findMany`)
178
- * are also available for dynamic table names and read-only SQL.
179
- *
180
- * RLS is enforced by default (the runtime runs each op as `authenticated` with
181
- * the verified user's claims). To bypass RLS, call `Database.asService()` —
182
- * explicit and greppable — which runs as the `service_role` (BYPASSRLS).
183
- *
184
- * @example
185
- * import { Database } from "@palbase/backend";
186
- *
187
- * const todo = await Database.tables.todos.insert({ title: req.input.title });
188
- * todo.id; // string ✓
189
- * const rows = await Database.query("SELECT id FROM todos WHERE done = $1", [false]);
190
- * const all = await Database.asService().tables.todos.findMany({}); // RLS bypass
191
- */
192
- declare const Database: EnvTypedDatabase;
193
- /** Firestore-like document client (PalDocs). */
194
- declare const Documents: PalbaseDocsClient;
195
- /** Object storage client (buckets, signed URLs). */
196
- declare const Storage: PalbaseStorageClient;
197
- /** JSON-typed cache (get/set/incr/getOrSet). */
198
- declare const Cache: CacheClient;
199
- /** Structured logger. */
200
- declare const Log: Logger;
201
- /** Push / email / SMS / in-app notifications. */
202
- declare const Notifications: PalbaseNotificationsClient;
203
- /**
204
- * Palstore purchases (entitlements + quota/credit spend).
205
- *
206
- * Reached by handlers through the `@RequireEntitlement` / `@Spend` decorators
207
- * rather than called directly in the common case; exposed as a singleton for
208
- * the cases the decorators deliberately do not cover (a dynamic spend count,
209
- * which must run BEFORE the billable side-effect).
210
- */
211
- declare const Purchases: PurchasesService;
212
- /**
213
- * Feature flags.
214
- *
215
- * Mirrors the `Database` / `Database.asService()` model. The default surface is
216
- * RLS-equivalent for flags: reads resolve against the CURRENT request user and
217
- * `Flags.setOverride(key, value)` writes an override for that same signed-in
218
- * user (no userId argument, no admin power). Cross-user admin writes
219
- * (`setOverrideForUser`, …) live behind `Flags.asService()` — explicit and
220
- * greppable, just like `Database.asService()`.
221
- *
222
- * @example
223
- * import { Flags } from "@palbase/backend";
224
- *
225
- * if (await Flags.isEnabled("new_checkout")) { ... } // current user
226
- * await Flags.setOverride("new_checkout", true); // current user
227
- * await Flags.asService().setOverrideForUser("u_9", "x", true); // cross-user
228
- */
229
- declare const Flags: PalbaseFlagsClient;
230
- /**
231
- * The Realtime broadcast singleton for the current request scope. Backend-side
232
- * Realtime is BROADCAST-ONLY (a stateless handler can push but not subscribe —
233
- * `subscribe()` lives on the client SDK's `pb.realtime`). Fire-and-forget:
234
- * `broadcast` resolves once accepted (or with an `error`), never blocking the
235
- * handler on subscribers.
236
- *
237
- * @example
238
- * import { Realtime } from "@palbase/backend";
239
- *
240
- * await Realtime.broadcast("room:42", "message", { text, from: user.id });
241
- */
242
- declare const Realtime: PalbaseRealtimeClient;
13
+ import 'node:async_hooks';
243
14
 
244
15
  /**
245
16
  * `@RequireEntitlement` and `@Spend` — access and metering at the decorator
@@ -260,7 +31,7 @@ declare const Realtime: PalbaseRealtimeClient;
260
31
  * decorators cannot introduce it.
261
32
  */
262
33
 
263
- type MethodDecorator$2 = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
34
+ type MethodDecorator$1 = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
264
35
  /**
265
36
  * Require an active entitlement. Answers 403 `entitlement_required` when it is
266
37
  * absent and consumes nothing.
@@ -268,7 +39,7 @@ type MethodDecorator$2 = (target: object, propertyKey: string | symbol, descript
268
39
  * The key is a member of the generated `EntitlementKey` union, so a typo is a
269
40
  * compile error rather than a gate that silently never matches.
270
41
  */
271
- declare function RequireEntitlement(key: EntitlementKey): MethodDecorator$2;
42
+ declare function RequireEntitlement(key: EntitlementKey): MethodDecorator$1;
272
43
  /**
273
44
  * Consume one unit of a limit or credit for the request's subject.
274
45
  *
@@ -281,7 +52,115 @@ declare function RequireEntitlement(key: EntitlementKey): MethodDecorator$2;
281
52
  * side-effect, which is an explicit `Purchases.withSpend(...)` call, not a
282
53
  * decorator.
283
54
  */
284
- declare function Spend(key: LimitKey): MethodDecorator$2;
55
+ declare function Spend(key: LimitKey): MethodDecorator$1;
56
+
57
+ /**
58
+ * clients/index.ts — the composition root for the module clients.
59
+ *
60
+ * This is what the process calls once at boot. It builds the five clients the
61
+ * engine injects and nothing else.
62
+ *
63
+ * WHAT IS NOT BUILT HERE, and why each one is absent rather than forgotten:
64
+ *
65
+ * - `auth`, `functions`, `analytics`, `links` — the SDK exports no singleton
66
+ * for any of them (`runtime.ts` exports ten, and these four are not among
67
+ * them), so no handler could reach them. They were constructed on every
68
+ * boot and thrown away.
69
+ * - `Purchases` — its client talks to palstore, and v2 contains no palstore
70
+ * at all. The surface was unbacked before it was untyped. Dropped from v2
71
+ * by the user's decision on 2026-08-15; the SDK's `src/purchases/` tree is
72
+ * untouched and that decision is deferred, not silently taken.
73
+ * - The V1 host/executor transport — the isolate it existed for was removed
74
+ * on 2026-08-14. A single-tenant process has nobody to hide its own
75
+ * tenant's credentials from.
76
+ */
77
+
78
+ interface ModuleClientsConfig {
79
+ /** Where the platform answers. Empty means "not configured" — see below. */
80
+ baseUrl: string;
81
+ /** The publishable key. Used only when no service-role key is supplied. */
82
+ apiKey: string;
83
+ /**
84
+ * The service-role key, which is what a backend actually holds. Preferred
85
+ * over `apiKey`: this process IS the project's server.
86
+ */
87
+ serviceRoleKey: string;
88
+ /** The shared Realtime secret. Empty means realtime is not provisioned. */
89
+ realtimeApiJwtSecret: string;
90
+ /** The request's user, read from the SDK's own scope — never from a caller. */
91
+ getCurrentUserId: () => string | null;
92
+ /** Injected in tests; resolved at call time in production. */
93
+ fetchImpl?: typeof fetch;
94
+ }
95
+ /**
96
+ * Build the module clients.
97
+ *
98
+ * Returns an EMPTY bundle when there is no base URL, deliberately: the engine
99
+ * then injects its named stubs, so a handler that reaches for `Storage` gets an
100
+ * error saying which module is unconfigured rather than a crash reading a
101
+ * property of undefined. Booting is not blocked — a project that never touches
102
+ * a module runs fine without one.
103
+ */
104
+ declare function buildModuleClients(cfg: ModuleClientsConfig): ModuleClients;
105
+
106
+ /**
107
+ * http.ts — the transport every module client speaks over.
108
+ *
109
+ * WHY IT IS HERE. This code, and the five clients built on it, spent their life
110
+ * in `v2/runtime/internal/runtime/module-clients.js`: 1,768 lines of untyped
111
+ * CommonJS that entered the SDK through an `@ts-expect-error` and left through
112
+ * `unknown` casts. The interfaces those clients implement were always in this
113
+ * package — only the implementations were outside it, where nothing checked one
114
+ * against the other. Two defects came through that gap on 2026-08-15 (a type
115
+ * that described a different server's columns; an accessor that was `undefined`
116
+ * in a deployed handler), and both were green on each side alone.
117
+ *
118
+ * WHAT IT IS NOT. Nothing Bun-specific lives here. The transport is a `fetch`
119
+ * the caller may supply, so this package stays runtime-agnostic and the process
120
+ * that knows which runtime it is decides what to hand in.
121
+ */
122
+
123
+ /** Per-request options a module client may pass. */
124
+ interface RequestOptions {
125
+ /** JSON-serialised unless it is a string, a Uint8Array, a Blob or FormData. */
126
+ body?: unknown;
127
+ headers?: Record<string, string>;
128
+ signal?: AbortSignal;
129
+ }
130
+ /**
131
+ * What a module client is given. An INTERFACE rather than a class, because the
132
+ * clients depend on this shape and not on the thing that implements it — which
133
+ * is what lets a test drive a whole client without a server.
134
+ */
135
+ interface ModuleTransport {
136
+ /** The platform's base URL, for the rare client that must build a URL. */
137
+ readonly baseUrl: string;
138
+ request<T = unknown>(method: string, path: string, options?: RequestOptions): Promise<PalbaseResult<T>>;
139
+ }
140
+ interface TransportConfig {
141
+ baseUrl: string;
142
+ apiKey: string;
143
+ /**
144
+ * The `fetch` to use. Omitted means the ambient one — resolved at CALL time,
145
+ * never captured here: the egress fence replaces `globalThis.fetch` after the
146
+ * clients are built, and a captured reference would keep calling the
147
+ * un-fenced original.
148
+ */
149
+ fetchImpl?: typeof fetch;
150
+ }
151
+ /**
152
+ * A module call that failed.
153
+ *
154
+ * `code` is the platform's own error code (`forbidden`, `not_found`, …) so a
155
+ * caller can branch on it without matching message text.
156
+ */
157
+ declare class PalbaseModuleError extends Error {
158
+ readonly code: string;
159
+ readonly status: number;
160
+ readonly details: Record<string, unknown>;
161
+ constructor(code: string, message: string, status: number, details?: Record<string, unknown>);
162
+ }
163
+ declare function makeHttpClient(cfg: TransportConfig): ModuleTransport;
285
164
 
286
165
  /**
287
166
  * keys-gen.ts — generate the `palbase-purchases.d.ts` text from a catalog
@@ -350,6 +229,72 @@ declare function entitlementFor(ctor: object, fnName: string): EntitlementKey |
350
229
  /** What `fnName` spends, or undefined when it spends nothing. */
351
230
  declare function spendFor(ctor: object, fnName: string): SpendMeta | undefined;
352
231
 
232
+ /** One column, flattened. Mirrors Go's `schema.ColumnJSON`. */
233
+ interface ColumnJSON {
234
+ type: string;
235
+ nullable: boolean;
236
+ primaryKey: boolean;
237
+ defaultValue?: unknown;
238
+ defaultRandom?: boolean;
239
+ defaultNow?: boolean;
240
+ renamedFrom?: string;
241
+ references?: {
242
+ table: string;
243
+ column: string;
244
+ };
245
+ onDeleteAction?: string;
246
+ enumName?: string;
247
+ enumValues?: string[];
248
+ unique?: boolean;
249
+ }
250
+ /** One RLS policy. Mirrors Go's `schema.PolicyJSON`. */
251
+ interface PolicyJSON {
252
+ name: string;
253
+ command: string;
254
+ roles: string[];
255
+ using: string | null;
256
+ withCheck: string | null;
257
+ permissive: boolean;
258
+ }
259
+ /** One table. Mirrors Go's `schema.TableJSON`. */
260
+ interface TableJSON {
261
+ name: string;
262
+ columns: Record<string, ColumnJSON>;
263
+ rls: boolean;
264
+ policies: PolicyJSON[];
265
+ primaryKey?: string[];
266
+ uniqueConstraints?: {
267
+ name: string;
268
+ columns: string[];
269
+ }[];
270
+ rawConstraints?: {
271
+ name: string;
272
+ up: string;
273
+ down: string | null;
274
+ }[];
275
+ checks?: {
276
+ name: string;
277
+ expr: string;
278
+ }[];
279
+ indexes?: {
280
+ name: string;
281
+ columns: string[];
282
+ }[];
283
+ }
284
+ /** A whole declaration. Mirrors Go's `schema.SchemaJSON`. */
285
+ interface SchemaJSON {
286
+ tables: Record<string, TableJSON>;
287
+ extensions: string[];
288
+ }
289
+ /**
290
+ * Serialize a `defineSchema(...)` result into the JSON the deploy applies.
291
+ *
292
+ * Keyed by the SQL table name rather than by the author's object key, because
293
+ * that is the name the live database answers to — and the diff on the other side
294
+ * compares against what it introspected.
295
+ */
296
+ declare function toSchemaJSON(schema: SchemaDef): SchemaJSON;
297
+
353
298
  /**
354
299
  * env-gen.ts — generate the `palbase-env.d.ts` text from a `defineSchema()`
355
300
  * result.
@@ -376,143 +321,6 @@ declare function spendFor(ctor: object, fnName: string): SpendMeta | undefined;
376
321
  */
377
322
  declare function makeEnvDts(schema: SchemaDef): string;
378
323
 
379
- /**
380
- * storage.ts — the storage-buckets config-as-code DSL.
381
- *
382
- * `defineStorage({ buckets })` is the first MODULE config-as-code surface (the
383
- * sibling of `db/schema.ts`'s `defineSchema`). A `config/storage.ts` file
384
- * default-exports a `defineStorage(...)` result; on deploy the br-pod evaluates
385
- * it to JSON and reconciles the declared buckets against the tenant's live
386
- * buckets via the Storage admin API (create missing, update changed; never
387
- * auto-delete — dropping a bucket is destructive and warned only).
388
- *
389
- * Buckets are DECLARATIVE: name + access + size/mime limits. The FILES inside a
390
- * bucket are runtime state (uploaded via the SDK), never in git.
391
- *
392
- * @example
393
- * import { defineStorage, bucket } from "@palbase/backend";
394
- *
395
- * export default defineStorage({
396
- * buckets: {
397
- * avatars: bucket({
398
- * public: true,
399
- * fileSizeLimit: "5MB",
400
- * allowedMimeTypes: ["image/png", "image/jpeg", "image/webp"],
401
- * }),
402
- * invoices: bucket({
403
- * public: false,
404
- * fileSizeLimit: "20MB",
405
- * allowedMimeTypes: ["application/pdf"],
406
- * }),
407
- * },
408
- * });
409
- *
410
- * The returned value is the EXACT JSON shape the runtime's generic config
411
- * extractor (`config_extract.js`) emits and the Go apply step parses:
412
- * { __config: "storage", buckets: { avatars: { public, fileSizeLimit, allowedMimeTypes }, ... } }
413
- * `fileSizeLimit` is always normalized to a number of bytes (or null when
414
- * omitted, meaning "no limit"); the apply step maps it to the Storage API's
415
- * `file_size_limit` field. `allowedMimeTypes` is the allowlist or null (any).
416
- */
417
- /** The discriminant written under `__config` so the eval/apply can tell which
418
- * module config a `config/*.ts` file describes. Storage is `"storage"`. */
419
- declare const STORAGE_CONFIG_KIND: "storage";
420
- /**
421
- * The author-facing options for a single bucket.
422
- *
423
- * - `public`: when true the bucket is served without a signed URL. Defaults to
424
- * `false` (private — signed URLs only).
425
- * - `fileSizeLimit`: the per-object upload cap. Accepts a human string
426
- * (`"5MB"`, `"20MB"`, `"1GB"`) or a bare number of BYTES. Omit for no limit.
427
- * - `allowedMimeTypes`: an allowlist of MIME types accepted on upload. Omit to
428
- * allow any type. Each entry must look like `type/subtype` (e.g. `image/png`,
429
- * `application/pdf`, or a wildcard `image/*`).
430
- */
431
- interface BucketOptions {
432
- public?: boolean;
433
- fileSizeLimit?: string | number;
434
- allowedMimeTypes?: string[];
435
- variants?: Record<string, ImageVariant>;
436
- }
437
- /**
438
- * One named rendition of an uploaded image — the same three knobs
439
- * `getPublicUrl(path, ...)` takes, given a name so the size lives with the
440
- * bucket instead of being retyped at every call site.
441
- *
442
- * At least one of `width` / `height` must be set (a variant that resizes
443
- * nothing and converts nothing is a typo, not a variant).
444
- */
445
- interface ImageVariant {
446
- width?: number;
447
- height?: number;
448
- format?: "webp" | "avif" | "jpeg" | "png";
449
- }
450
- /**
451
- * The compiled, serializable bucket definition — the EXACT shape emitted to
452
- * JSON and consumed by the Go apply step.
453
- *
454
- * - `public`: always present (defaulted to `false`).
455
- * - `fileSizeLimit`: bytes as a number, or `null` for "no limit".
456
- * - `allowedMimeTypes`: the MIME allowlist, or `null` for "any type".
457
- */
458
- interface BucketDef {
459
- public: boolean;
460
- fileSizeLimit: number | null;
461
- allowedMimeTypes: string[] | null;
462
- /** Named renditions, or `null` when none are declared. Rendered on request by
463
- * the image-transform route and cached — nothing extra is stored. */
464
- variants: Record<string, ImageVariant> | null;
465
- }
466
- /** A storage config definition: the discriminant + a map of bucket name →
467
- * {@link BucketDef}. This is the value `defineStorage` returns and the runtime
468
- * config extractor serializes. */
469
- interface StorageConfig {
470
- __config: typeof STORAGE_CONFIG_KIND;
471
- buckets: Record<string, BucketDef>;
472
- }
473
- /** The author-facing input to `defineStorage`: a `buckets` map whose keys are
474
- * the bucket names and whose values are `bucket({...})` builders. */
475
- interface StorageInput {
476
- buckets: Record<string, BucketDef>;
477
- }
478
- /**
479
- * Parse a `fileSizeLimit` into a number of bytes.
480
- *
481
- * - A bare number is taken as bytes verbatim (must be a non-negative integer).
482
- * - A string is `<number><unit>` (e.g. `"5MB"`, `"1.5GB"`, `"500kb"`); the unit
483
- * is case-insensitive and optional (a bare numeric string = bytes). Binary
484
- * units (1 MB = 1024 squared bytes) to match the Storage module's parser.
485
- *
486
- * Throws on a negative value, a non-finite number, or an unrecognized unit so a
487
- * bad limit fails at config-author time, not silently at deploy.
488
- */
489
- declare function parseFileSizeLimit(input: string | number): number;
490
- /**
491
- * Define a single bucket. The bucket NAME is supplied by the key in
492
- * `defineStorage({ buckets: { <name>: bucket({...}) } })`, so `bucket()` takes
493
- * only the options.
494
- *
495
- * Validates eagerly (at config-author time):
496
- * - `fileSizeLimit` parses to a valid non-negative byte count.
497
- * - each `allowedMimeTypes` entry is a `type/subtype` MIME string.
498
- *
499
- * Returns a normalized {@link BucketDef}: `public` defaulted to `false`,
500
- * `fileSizeLimit` as bytes-or-null, `allowedMimeTypes` deduped-or-null.
501
- */
502
- declare function bucket(opts?: BucketOptions): BucketDef;
503
- /**
504
- * Define the storage config for a project. The bucket NAME comes from each
505
- * object key (authors never repeat the name). Returns the discriminated
506
- * {@link StorageConfig} the runtime config extractor serializes and the Go
507
- * apply step reconciles.
508
- *
509
- * @example
510
- * export default defineStorage({
511
- * buckets: { avatars: bucket({ public: true, fileSizeLimit: "5MB" }) },
512
- * });
513
- */
514
- declare function defineStorage(input: StorageInput): StorageConfig;
515
-
516
324
  /**
517
325
  * egress.ts — the tenant outbound-HTTP allowlist config-as-code DSL.
518
326
  *
@@ -1344,180 +1152,130 @@ declare function flag(opts: FlagOptions): FlagDef;
1344
1152
  */
1345
1153
  declare function defineFlags(input: FlagsInput): FlagsConfig;
1346
1154
 
1347
- /** Options accepted by `@Controller`. */
1348
- interface ControllerOptions {
1349
- /** Default auth for ALL routes in this controller (route-level overrides). */
1350
- auth?: AuthSpec;
1351
- }
1352
1155
  /**
1353
- * Mark a class as a Palbase backend controller. `basePath` is the mount path
1354
- * for every route the class declares; `options.auth` sets the controller-level
1355
- * default auth (a route's own `auth` overrides it; absent ⇒ secure-by-default).
1156
+ * secrets.ts the tenant SECRET REQUIREMENT config-as-code DSL.
1157
+ *
1158
+ * `defineSecrets({ secrets: [secret("NEO4J_PASSWORD")] })` declares WHICH secrets
1159
+ * a backend needs to run. It declares the requirement, never the value: the value
1160
+ * lives only in the vault, and nothing in this file — or in the repo it is
1161
+ * committed to — ever holds one.
1162
+ *
1163
+ * The gap it closes: today a backend's dependence on a secret is written nowhere
1164
+ * except inside the code that reads it (`process.env.NEO4J_PASSWORD`). Deploy a
1165
+ * project into an environment where that secret was never set and the build
1166
+ * passes, the pod starts, and the failure surfaces as a 500 on the first request
1167
+ * that touches it — measured on todoapp, whose `/graph/*` routes returned 500 for
1168
+ * exactly this reason. With the requirement declared, it travels WITH the code:
1169
+ * `push` compares this list against the environment's vault and refuses the
1170
+ * deploy, naming the missing secret, before anything ships.
1356
1171
  *
1357
1172
  * @example
1358
- * \@Controller("/todos", { auth: false })
1359
- * export class TodosController {
1360
- * \@Get("") list(\@QueryParams(ListTodosQuery) q: ListTodosQuery): TodoSchema[] { … }
1361
- * }
1362
- */
1363
- declare function Controller(basePath: string, options?: ControllerOptions): <T extends abstract new (...args: never[]) => object>(ctor: T) => T;
1364
-
1365
- /** A legacy method decorator (`experimentalDecorators`): `(prototype, name,
1366
- * descriptor)`. */
1367
- type MethodDecorator$1 = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
1368
- /** A legacy parameter decorator: `(prototype, name, paramIndex)`. */
1369
- type ParameterDecorator$1 = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
1370
- /**
1371
- * Direct-storage upload settings for an `@Upload` route. The br-pod validates an
1372
- * authorize request against these (size/type), then mints a signed upload URL
1373
- * that PINS the limits so storage itself rejects an over-limit / wrong-type PUT
1374
- * — the client cannot exceed what it declared.
1173
+ * // config/secrets.ts
1174
+ * import { defineSecrets, secret } from "@palbase/backend";
1175
+ * export default defineSecrets({
1176
+ * secrets: [
1177
+ * secret("NEO4J_PASSWORD", { description: "graph store password" }),
1178
+ * secret("SENTRY_DSN", { required: false, description: "crash reporting" }),
1179
+ * ],
1180
+ * });
1181
+ *
1182
+ * `required` defaults to TRUE. A declaration exists to be checked, so the safe
1183
+ * default is the checked one; `required: false` says the code has a working path
1184
+ * when the secret is absent, and downgrades a missing secret to a warning.
1185
+ *
1186
+ * Emits: { __config: "secrets", secrets: [{ name, required, description }] }
1187
+ * sorted by name, so the same declaration always evaluates to the same bytes and
1188
+ * a config diff shows what an author changed rather than how they reordered it.
1375
1189
  */
1376
- interface UploadConfig {
1377
- /**
1378
- * Target bucket NAME. MUST exist in `config/storage.ts` `defineStorage(...)`.
1379
- *
1380
- * The bucket is the SINGLE SOURCE OF TRUTH for the size limit + MIME allowlist:
1381
- * `bucket({ fileSizeLimit, allowedMimeTypes })`. Storage enforces those at the
1382
- * actual PUT (the only guard a client cannot skip), so `@Upload` deliberately
1383
- * does NOT take its own `maxSize`/`allowedTypes` — duplicating them here would
1384
- * let a route declare a tighter limit than its bucket that storage would not
1385
- * enforce (a real bypass: declare 10 bytes at authorize, then PUT up to the
1386
- * bucket ceiling straight at the signed URL). One bucket, one limit, enforced.
1387
- */
1388
- bucket: string;
1190
+ /** The discriminant written under `__config` so the deploy eval knows the kind. */
1191
+ declare const SECRETS_CONFIG_KIND: "secrets";
1192
+ /** The author-facing options for a single `secret()`. */
1193
+ interface SecretOptions {
1389
1194
  /**
1390
- * SERVER-side object key template. The client NEVER chooses the path. Tokens:
1391
- * `{userId}` (authenticated user id), `{uploadId}` (server-minted), and
1392
- * `{filename}` (the client-declared filename, sanitized). e.g.
1393
- * `"{userId}/{uploadId}-{filename}"`.
1195
+ * Whether the deploy must REFUSE when this secret is absent from the target
1196
+ * environment's vault. Omitted `true`.
1394
1197
  */
1395
- pathTemplate: string;
1198
+ required?: boolean;
1199
+ /** What the secret is for — shown when the deploy reports it missing. */
1200
+ description?: string;
1396
1201
  }
1397
1202
  /**
1398
- * `@Upload(subpath, config)` declare a direct-storage upload route. The method
1399
- * body is the completion handler; `config.uploadConfig` drives the authorize
1400
- * guard + signed-URL pinning.
1203
+ * The compiled, serializable secret declaration the EXACT shape emitted to
1204
+ * JSON and consumed by the Go deploy step.
1401
1205
  *
1402
- * @example
1403
- * @Upload("/", { bucket: "docs", pathTemplate: "{userId}/{uploadId}-{filename}" })
1404
- * async upload(@UploadedObject() obj: UploadedObject, @User() user): Promise<DocResult> { ... }
1405
- * // The size limit + MIME allowlist come from the "docs" bucket in
1406
- * // config/storage.ts — storage enforces them at the PUT.
1407
- */
1408
- declare function Upload(subpath: string, config: UploadConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator$1;
1409
- /**
1410
- * The uploaded object, injected into an `@Upload` method body by
1411
- * `@UploadedObject()` once storage confirms the upload. Bytes are NOT present
1412
- * (they went straight to storage) — this is the metadata the completion handler
1413
- * persists.
1206
+ * - `name`: the environment-variable name the code reads, verbatim.
1207
+ * - `required`: always present (defaulted to `true`).
1208
+ * - `description`: the doc string, or `null`.
1414
1209
  */
1415
- interface UploadedObject {
1416
- /** Server-minted id correlating authorize ↔ completion (idempotency key). */
1417
- uploadId: string;
1418
- /** Final object key in the bucket (rendered from `pathTemplate`). */
1419
- path: string;
1420
- /** Bucket the object landed in. */
1421
- bucket: string;
1422
- /** Object size in bytes, as reported by storage. */
1423
- size: number;
1424
- /** Object MIME type, as reported by storage. */
1425
- contentType: string;
1210
+ interface SecretDef {
1211
+ name: string;
1212
+ required: boolean;
1213
+ description: string | null;
1214
+ }
1215
+ /** A secrets config definition: the discriminant + the name-sorted declarations.
1216
+ * This is the value `defineSecrets` returns and the build's config extractor
1217
+ * serializes into `.palbase/config.json`. */
1218
+ interface SecretsConfig {
1219
+ __config: typeof SECRETS_CONFIG_KIND;
1220
+ secrets: SecretDef[];
1221
+ }
1222
+ /** The author-facing input to `defineSecrets`: a list of `secret(...)` builders. */
1223
+ interface SecretsInput {
1224
+ secrets: SecretDef[];
1426
1225
  }
1427
1226
  /**
1428
- * `@UploadedObject()` inject the uploaded object (`: UploadedObject`) into an
1429
- * `@Upload` method body (the completion input). Only valid on an `@Upload`
1430
- * route; the bytes are NOT present (they went directly to storage), this is the
1431
- * confirmed object's metadata.
1227
+ * Declare a single required (or optional) secret. Unlike `flag()` / `bucket()`,
1228
+ * the NAME is an argument rather than an object key: a secret's name is not a
1229
+ * label an author chooses, it is the exact env-var identifier the code already
1230
+ * reads, so it belongs where it can be validated as one.
1432
1231
  *
1433
- * Co-located with the {@link UploadedObject} TYPE so a single exported name
1434
- * `UploadedObject` carries BOTH the decorator value and the type annotation.
1232
+ * Validates eagerly (at config-author time):
1233
+ * - `name` is `[A-Z][A-Z0-9_]*` anything else throws.
1234
+ * - `required` is a boolean; `description` is a string.
1235
+ *
1236
+ * Returns a normalized {@link SecretDef}: `required` defaulted to `true`,
1237
+ * `description` trimmed-or-null.
1435
1238
  */
1436
- declare function UploadedObject(): ParameterDecorator$1;
1239
+ declare function secret(name: string, opts?: SecretOptions): SecretDef;
1437
1240
  /**
1438
- * Cross-check one `@Upload` route's uploadConfig against the project's
1439
- * {@link StorageConfig}: the named bucket MUST exist in `defineStorage(...)`.
1440
- * Throws a precise error so a typo fails at deploy, not at the first user upload.
1441
- *
1442
- * That is the ONLY cross-check: the size limit + MIME allowlist live on the
1443
- * bucket (`bucket({ fileSizeLimit, allowedMimeTypes })`) and are enforced by
1444
- * storage at the PUT. `@Upload` carries no `maxSize`/`allowedTypes` of its own,
1445
- * so there is nothing to compare — one bucket, one limit, no drift.
1241
+ * Define the secrets a backend needs. Returns the discriminated
1242
+ * {@link SecretsConfig} the build's config extractor serializes and the deploy
1243
+ * checks against the target environment's vault BEFORE shipping anything.
1446
1244
  *
1447
- * `routeLabel` is used only for error messages (e.g. `docs.upload`).
1245
+ * A name declared twice throws: two declarations of one name disagree about
1246
+ * something (usually `required`), and silently keeping either one is how a
1247
+ * secret stops being checked without anybody editing the check.
1448
1248
  */
1449
- declare function validateUploadAgainstStorage(uploadConfig: UploadConfig, storage: StorageConfig, routeLabel: string): void;
1249
+ declare function defineSecrets(input: SecretsInput): SecretsConfig;
1450
1250
 
1451
- /** The HTTP verbs a route may declare, upper-cased (the runtime router +
1452
- * OpenAPI lower-case on their own). */
1453
- type HttpMethodUpper = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "QUERY";
1454
- /** Route-level options accepted by the method decorators (`@Get`/`@Post`/…). */
1455
- interface RouteOptions {
1456
- /** OVERRIDES the controller-level default auth for this one route. */
1251
+ /** Options accepted by `@Controller`. */
1252
+ interface ControllerOptions {
1253
+ /** Default auth for ALL routes in this controller (route-level overrides). */
1457
1254
  auth?: AuthSpec;
1458
- /** Per-route rate limit. */
1459
- rateLimit?: RateLimitConfig;
1460
- /** Direct-storage upload config — present ONLY on `@Upload` routes (the
1461
- * `@Get`/`@Post`/… decorators never set it). Its presence is what MARKS a
1462
- * route as an upload route through the whole pipeline (registry → flatten →
1463
- * openapi → codegen). The bytes go client→storage directly; the method body
1464
- * runs as the completion handler. See {@link UploadConfig} (decorators/upload.ts). */
1465
- uploadConfig?: UploadConfig;
1466
- }
1467
- /** The kind of value a parameter decorator injects. Drives both dispatch
1468
- * (which request slice to inject) and codegen (which OpenAPI parameter source a
1469
- * schema-bearing kind maps to). */
1470
- type ParamKind = "body" | "query" | "param" | "headers" | "user" | "optionalUser" | "client" | "requestId" | "traceId" | "req" | "uploadedObject";
1471
- /** One parameter decorator's recorded metadata. `index` is the parameter
1472
- * position esbuild/tsc preserve; `schema` is present for the schema-bearing
1473
- * kinds (`body`/`query`/`headers`); `name` is the path-param name for `param`. */
1474
- interface ParamMeta {
1475
- index: number;
1476
- kind: ParamKind;
1477
- /** Zod schema for `body`/`query`/`headers` (validation + codegen source). */
1478
- schema?: ZodTypeAny;
1479
- /** Path-param name for `@Param("id")`. */
1480
- name?: string;
1481
- }
1482
- /** One inferred throw site: the error CLASS name (e.g. "TodoLocked") and its
1483
- * wire code (e.g. "todo_locked"). `status`, `hasData`, and the data JSON schema
1484
- * are NOT carried here — they resolve from the error registry by `code` at
1485
- * extract/openapi time (single source of truth). */
1486
- interface ThrowDescriptor {
1487
- name: string;
1488
- code: string;
1489
1255
  }
1490
- /** One route's recorded metadata: the verb + subpath + method name + options,
1491
- * the ordered parameter metas, and the resolved return schema (injected by the
1492
- * codegen step — see `returnSchema`). */
1493
- interface RouteMeta {
1494
- method: HttpMethodUpper;
1495
- subpath: string;
1496
- fnName: string;
1497
- options: RouteOptions;
1498
- params: ParamMeta[];
1499
- /** Response schema for the route, if any. Derived from the method's RETURN
1500
- * TYPE by codegen and written here via `recordReturn` (a generated top-level
1501
- * IIFE injected per controller), not by an author-written decorator. */
1502
- returnSchema?: ZodTypeAny;
1503
- /** Error classes this route can throw, if inferred. Derived from the method
1504
- * body + service call graph by the deploy stager's throw analysis and written
1505
- * here via `recordThrows` (a generated top-level IIFE injected per controller,
1506
- * the `recordReturn` twin), not by an author-written decorator. */
1507
- throws?: ThrowDescriptor[];
1508
- }
1509
- /** Attach the inferred throw descriptors to the route for `fnName` (called by
1510
- * the stager-injected IIFE that carries the throw analysis result — the
1511
- * `recordReturn` twin). If the route does not exist yet, the descriptors are
1512
- * buffered (THROWS_BUFFER) and drained into the route by `recordRoute` when the
1513
- * method decorator runs. */
1514
- declare function recordThrows(target: object, fnName: string, throws: ThrowDescriptor[]): void;
1515
- /** Read the route metadata for a controller class (the deploy/dispatch entry
1516
- * point). Applies any buffered return schemas + throw descriptors (for the
1517
- * recordReturn/recordThrows-runs-before orderings) and returns a defensive copy
1518
- * so callers cannot mutate the registry.
1256
+ /**
1257
+ * The controller classes this process has loaded, in decoration order.
1258
+ *
1259
+ * Decoration order is import order, which the bundler fixes by sorting the
1260
+ * files it emits imports for — so two builds of one tree produce the same
1261
+ * route table, and route precedence is not a function of module-resolution
1262
+ * accidents.
1263
+ */
1264
+ declare function getRegisteredControllers(): readonly unknown[];
1265
+ /** Empty the registry. For tests, which load controllers repeatedly. */
1266
+ declare function __resetRegisteredControllers(): void;
1267
+ /**
1268
+ * Mark a class as a Palbase backend controller. `basePath` is the mount path
1269
+ * for every route the class declares; `options.auth` sets the controller-level
1270
+ * default auth (a route's own `auth` overrides it; absent secure-by-default).
1271
+ *
1272
+ * @example
1273
+ * \@Controller("/todos", { auth: false })
1274
+ * export class TodosController {
1275
+ * \@Get("") list(\@QueryParams(ListTodosQuery) q: ListTodosQuery): TodoSchema[] { }
1276
+ * }
1519
1277
  */
1520
- declare function getRoutes(ctor: object): RouteMeta[];
1278
+ declare function Controller(basePath: string, options?: ControllerOptions): <T extends abstract new (...args: never[]) => object>(ctor: T) => T;
1521
1279
 
1522
1280
  /** A legacy method decorator. */
1523
1281
  type MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
@@ -1898,4 +1656,4 @@ declare const documents: {
1898
1656
  onDocumentDeleted(handler: HookHandler<DocumentDeletedEvent>): ResolvedHook<DocumentDeletedEvent>;
1899
1657
  };
1900
1658
 
1901
- export { type AcsOptions, type ApnsOptions, Body, type BucketDef, type BucketOptions, Cache, CacheClient, Client, Controller, type ControllerOptions, DBClient, DEFAULT_TEMPLATE_LOCALE, Database, type DefinedError, type DefinedErrorWithData, Delete, type DocumentCreatedEvent, type DocumentDeletedEvent, type DocumentUpdatedEvent, Documents, EGRESS_CONFIG_KIND, EGRESS_TIMEOUT_DEFAULT_MS, EGRESS_TIMEOUT_MAX_MS, EGRESS_TIMEOUT_MIN_MS, type EgressConfig, type EgressInput, type EmailTemplateContent, type EmailTemplateDef, type EmailTemplateOptions, EntitlementKey, EnvTypedDatabase, FLAGS_CONFIG_KIND, type FcmOptions, type FileDeletedEvent, type FileUploadedEvent, type FlagDef, type FlagJsonObject, type FlagJsonValue, type FlagOptions, type FlagType, type FlagValue, Flags, type FlagsConfig, type FlagsInput, Get, Headers, type HookHandler, type HookMeta, HttpError, type HttpMethodUpper, type ImageVariant, Job, type JobMeta, type JobOptions, LimitKey, type LimitState, Log, Logger, NOTIFICATIONS_CONFIG_KIND, Notifications, type NotificationsConfig, type NotificationsInput, On, OptionalUser, type OwnedTablesOf, PROVIDER_CATALOG, PalbaseDocsClient, PalbaseFlagsClient, PalbaseNotificationsClient, PalbaseRealtimeClient, PalbaseStorageClient, Param, type ParamKind, type ParamMeta, type PasswordResetEvent, Patch, Post, type ProviderCatalogEntry, type ProviderDef, type ProviderName, type ProviderOptions, Purchases, type PurchasesManifest, type PurchasesService, Put, Query, QueryParams, RESERVED_SECRET_PREFIX, RateLimitConfig, Realtime, type RegisteredError, Req, RequestId, type RequestStore, RequireEntitlement, type ResolvedHook, type ResolvedJob, type ResolvedWebhook, Resource, type ResourceEnv, type RouteMeta, type RouteOptions, type RuntimeServices, STORAGE_CONFIG_KIND, SchemaDef, type Seed, type SeedFor, type SeedJson, type SeedRowFor, type SendgridOptions, type SesOptions, type SignInEvent, type SignOutEvent, type SignatureSpec, type SmsTemplateContent, type SmsTemplateDef, type SmsTemplateOptions, type SmtpOptions, Spend, type SpendMeta, type SpendOptions, Storage, type StorageConfig, type StorageInput, type StoreEnv, TEST_USERS_CONFIG_KIND, type TableGraph, TableTypes, Tables, type TemplatesConfig, type TemplatesInput, type TestUserDef, type TestUserOptions, type TestUsersConfig, type TestUsersInput, type ThrowDescriptor, TraceId, type TwilioOptions, Upload, type UploadConfig, UploadedObject, User, type UserCreatedEvent, Webhook, type WebhookEventHandler, type WebhookMeta, type WebhookOptions, type WebhookProvider, type WebhookRequest, __getRuntime, __registerResource, __requestALS, __runResourceBoot, __runWithRuntime, __setRuntime, __shutdownResources, auth, bucket, buildProvider, defineEgress, defineError, defineFlags, defineNotifications, defineStorage, defineTestUsers, documents, entitlementFor, extractVariables, flag, getErrorRegistry, getJobConfig, getRoutes, getWebhookConfig, makeEnvDts, makePurchasesDts, parseFileSizeLimit, recordThrows, reservedSecretKey, spendFor, storage, testUser, validateUploadAgainstStorage };
1659
+ export { type AcsOptions, type ApnsOptions, Body, Client, type ColumnJSON, Controller, type ControllerOptions, DEFAULT_TEMPLATE_LOCALE, type DefinedError, type DefinedErrorWithData, Delete, type DocumentCreatedEvent, type DocumentDeletedEvent, type DocumentUpdatedEvent, EGRESS_CONFIG_KIND, EGRESS_TIMEOUT_DEFAULT_MS, EGRESS_TIMEOUT_MAX_MS, EGRESS_TIMEOUT_MIN_MS, type EgressConfig, type EgressInput, type EmailTemplateContent, type EmailTemplateDef, type EmailTemplateOptions, EntitlementKey, FLAGS_CONFIG_KIND, type FcmOptions, type FileDeletedEvent, type FileUploadedEvent, type FlagDef, type FlagJsonObject, type FlagJsonValue, type FlagOptions, type FlagType, type FlagValue, type FlagsConfig, type FlagsInput, Get, Headers, type HookHandler, type HookMeta, HttpError, Job, type JobMeta, type JobOptions, LimitKey, type ModuleClientsConfig, type ModuleTransport, NOTIFICATIONS_CONFIG_KIND, type NotificationsConfig, type NotificationsInput, On, OptionalUser, type OwnedTablesOf, PROVIDER_CATALOG, PalbaseModuleError, PalbaseResult, Param, type PasswordResetEvent, Patch, type PolicyJSON, Post, type ProviderCatalogEntry, type ProviderDef, type ProviderName, type ProviderOptions, type PurchasesManifest, Put, Query, QueryParams, RESERVED_SECRET_PREFIX, type RegisteredError, Req, RequestId, type RequestOptions, RequireEntitlement, type ResolvedHook, type ResolvedJob, type ResolvedWebhook, Resource, type ResourceEnv, RouteOptions, SECRETS_CONFIG_KIND, SchemaDef, type SchemaJSON, type SecretDef, type SecretOptions, type SecretsConfig, type SecretsInput, type Seed, type SeedFor, type SeedJson, type SeedRowFor, type SendgridOptions, type SesOptions, type SignInEvent, type SignOutEvent, type SignatureSpec, type SmsTemplateContent, type SmsTemplateDef, type SmsTemplateOptions, type SmtpOptions, Spend, type SpendMeta, TEST_USERS_CONFIG_KIND, type TableGraph, type TableJSON, TableTypes, Tables, type TemplatesConfig, type TemplatesInput, type TestUserDef, type TestUserOptions, type TestUsersConfig, type TestUsersInput, TraceId, type TransportConfig, type TwilioOptions, User, type UserCreatedEvent, Webhook, type WebhookEventHandler, type WebhookMeta, type WebhookOptions, type WebhookProvider, type WebhookRequest, __registerResource, __resetRegisteredControllers, __runResourceBoot, __shutdownResources, auth, buildModuleClients, buildProvider, defineEgress, defineError, defineFlags, defineNotifications, defineSecrets, defineTestUsers, documents, entitlementFor, extractVariables, flag, getErrorRegistry, getJobConfig, getRegisteredControllers, getWebhookConfig, makeEnvDts, makeHttpClient, makePurchasesDts, reservedSecretKey, secret, spendFor, storage, testUser, toSchemaJSON };