@palbase/backend 22.1.0 → 23.1.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 (81) hide show
  1. package/dist/bin/palbase-backend.cjs +750 -40
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +5 -5
  4. package/dist/{chunk-YL4C5NRY.js → chunk-HQRJDARQ.js} +2 -2
  5. package/dist/{chunk-74XDEF5J.js → chunk-M5MCBWJI.js} +723 -37
  6. package/dist/chunk-M5MCBWJI.js.map +1 -0
  7. package/dist/{chunk-W5ODXPY3.js → chunk-NS5V43YQ.js} +14 -1
  8. package/dist/chunk-NS5V43YQ.js.map +1 -0
  9. package/dist/{chunk-SQC5EIWY.js → chunk-OHALWEOG.js} +19 -9
  10. package/dist/chunk-OHALWEOG.js.map +1 -0
  11. package/dist/{chunk-I3ON7MYF.js → chunk-PY7YJDCT.js} +129 -18
  12. package/dist/chunk-PY7YJDCT.js.map +1 -0
  13. package/dist/{chunk-N32VDWKH.js → chunk-R3KN6RHD.js} +4 -59
  14. package/dist/chunk-R3KN6RHD.js.map +1 -0
  15. package/dist/{chunk-QMVK4X3V.js → chunk-RCLNBJCM.js} +98 -98
  16. package/dist/chunk-RCLNBJCM.js.map +1 -0
  17. package/dist/db/env.cjs.map +1 -1
  18. package/dist/db/env.d.cts +3 -21
  19. package/dist/db/env.d.ts +3 -21
  20. package/dist/db/index.cjs +140 -16
  21. package/dist/db/index.cjs.map +1 -1
  22. package/dist/db/index.d.cts +3 -2
  23. package/dist/db/index.d.ts +3 -2
  24. package/dist/db/index.js +2 -2
  25. package/dist/{endpoint-BVT6jcVW.d.cts → endpoint-CVWXh6oG.d.ts} +147 -15
  26. package/dist/{endpoint-BVT6jcVW.d.ts → endpoint-c9h5jriX.d.cts} +147 -15
  27. package/dist/engine/index.cjs +750 -40
  28. package/dist/engine/index.cjs.map +1 -1
  29. package/dist/engine/index.d.cts +6 -5
  30. package/dist/engine/index.d.ts +6 -5
  31. package/dist/engine/index.js +4 -4
  32. package/dist/{index-BS1gW4nV.d.cts → index-BZrJXnVh.d.ts} +142 -72
  33. package/dist/{index-BqCiHao8.d.cts → index-By8Dle5U.d.cts} +196 -21
  34. package/dist/{index-vwHoS0l2.d.ts → index-CwAJ7HEe.d.ts} +196 -21
  35. package/dist/{index-CCZqzych.d.ts → index-CxeQSfJP.d.cts} +142 -72
  36. package/dist/index.cjs +388 -1101
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +89 -1134
  39. package/dist/index.d.ts +89 -1134
  40. package/dist/index.js +133 -902
  41. package/dist/index.js.map +1 -1
  42. package/dist/openapi/index.cjs +32 -61
  43. package/dist/openapi/index.cjs.map +1 -1
  44. package/dist/openapi/index.d.cts +6 -2
  45. package/dist/openapi/index.d.ts +6 -2
  46. package/dist/openapi/index.js +34 -26
  47. package/dist/openapi/index.js.map +1 -1
  48. package/dist/{registry-Bsuf-orT.d.ts → registry-B3niOVYp.d.ts} +108 -170
  49. package/dist/{registry-BWttGlaT.d.cts → registry-CqPK2Qby.d.cts} +108 -170
  50. package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
  51. package/dist/stack.cjs.map +1 -0
  52. package/dist/stack.d.cts +76 -0
  53. package/dist/stack.d.ts +76 -0
  54. package/dist/stack.js +1 -0
  55. package/dist/test/index.cjs +482 -9
  56. package/dist/test/index.cjs.map +1 -1
  57. package/dist/test/index.d.cts +35 -3
  58. package/dist/test/index.d.ts +35 -3
  59. package/dist/test/index.js +480 -8
  60. package/dist/test/index.js.map +1 -1
  61. package/docs/README.md +7 -6
  62. package/docs/llms-full.txt +7 -260
  63. package/docs/llms.txt +0 -2
  64. package/package.json +9 -8
  65. package/stager/return_types.js +23 -0
  66. package/template/package.json +1 -1
  67. package/dist/chunk-74XDEF5J.js.map +0 -1
  68. package/dist/chunk-I3ON7MYF.js.map +0 -1
  69. package/dist/chunk-N32VDWKH.js.map +0 -1
  70. package/dist/chunk-QMVK4X3V.js.map +0 -1
  71. package/dist/chunk-SQC5EIWY.js.map +0 -1
  72. package/dist/chunk-W5ODXPY3.js.map +0 -1
  73. package/dist/purchases/keys.cjs.map +0 -1
  74. package/dist/purchases/keys.d.cts +0 -42
  75. package/dist/purchases/keys.d.ts +0 -42
  76. package/dist/purchases/keys.js +0 -1
  77. package/docs/config.md +0 -147
  78. package/docs/resources.md +0 -97
  79. package/template/config/secrets.ts +0 -24
  80. /package/dist/{chunk-YL4C5NRY.js.map → chunk-HQRJDARQ.js.map} +0 -0
  81. /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
package/dist/index.d.cts CHANGED
@@ -1,59 +1,17 @@
1
- import { g as PalbaseResult, A as AuthSpec, H as HttpError } from './endpoint-BVT6jcVW.cjs';
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-BVT6jcVW.cjs';
3
- import { M as ModuleClients } from './index-BS1gW4nV.cjs';
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-BS1gW4nV.cjs';
5
- import { EntitlementKey, LimitKey } from './purchases/keys.cjs';
6
- import { S as SchemaDef } from './index-BqCiHao8.cjs';
7
- export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, g as EnvTables, E as EnvTypedDatabase, h as EnvTypedTable, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, F as numeric, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from './index-BqCiHao8.cjs';
8
- import { TableTypes, Tables } from './db/env.cjs';
9
- import { a as RouteOptions } from './registry-BWttGlaT.cjs';
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-BWttGlaT.cjs';
1
+ import { h as PalbaseResult, A as AuthSpec, H as HttpError } from './endpoint-c9h5jriX.cjs';
2
+ export { i as AuthConfig, B as BadRequest, C as CacheClient, j as ClientInfo, k as Conflict, D as DBClient, f 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, g 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-c9h5jriX.cjs';
3
+ import { M as ModuleClients } from './index-CxeQSfJP.cjs';
4
+ export { C as Cache, D as Database, a as Documents, F as Flags, L as Log, N as Notifications, R as Realtime, b as RequestStore, c as RuntimeServices, S as Secrets, d as Storage, _ as __getRuntime, e as __requestALS, f as __runWithRuntime, g as __setRuntime } from './index-CxeQSfJP.cjs';
5
+ import { S as SchemaDef } from './index-By8Dle5U.cjs';
6
+ export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EmbeddingModelRef, f as EnvServiceDatabase, g as EnvTables, E as EnvTypedDatabase, h as EnvTypedTable, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, o as SchemaInput, T as TableDef, p as TableInput, q as TxPlan, r as TxTables, s as TypedDB, t as TypedTable, u as TypedTx, v as bigint, w as boolean, x as defineSchema, y as enumType, z as integer, A as isPalbaseExtension, B as jsonb, D as makeTypedDB, F as numeric, G as openai, H as policy, J as raw, K as text, L as timestamp, M as uuid, N as vector } from './index-By8Dle5U.cjs';
7
+ export { TableTypes, Tables } from './db/env.cjs';
8
+ export { PalbaseBucketName, PalbaseFlagKey, PalbaseSecretName } from './stack.cjs';
9
+ import { a as RouteOptions } from './registry-CqPK2Qby.cjs';
10
+ export { H as HttpMethodUpper, b as ParamKind, P as ParamMeta, R as RouteMeta, c as Signal, d as Sse, S as SseConfig, e as SseOut, f as SseWriter, T as ThrowDescriptor, g as Upload, U as UploadConfig, h as UploadedObject, i as getRoutes, r as recordThrows } from './registry-CqPK2Qby.cjs';
11
11
  import { ZodTypeAny, z } from 'zod';
12
12
  export { z } from 'zod';
13
13
  import 'node:async_hooks';
14
14
 
15
- /**
16
- * `@RequireEntitlement` and `@Spend` — access and metering at the decorator
17
- * level, so a controller method says what it costs instead of wiring it.
18
- *
19
- * TWO decorators, deliberately, because not every subscription is limit-based.
20
- * Most products just say "pro users may call this" and consume nothing; a
21
- * zero-count spend would express that badly and run the whole reserve/commit
22
- * machine for no reason.
23
- *
24
- * @RequireEntitlement('pro') pure gate → 403, consumes nothing
25
- * @Spend('bookCreate') consumption → 429 when exhausted
26
- *
27
- * ORDER IS FIXED, and not by where you wrote them. Both decorators record
28
- * metadata and share ONE installed wrapper that always runs the entitlement
29
- * gate before it reserves quota. Reserving for someone who is about to get a
30
- * 403 is wasted work and a race window, so the source order of the two
31
- * decorators cannot introduce it.
32
- */
33
-
34
- type MethodDecorator$1 = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
35
- /**
36
- * Require an active entitlement. Answers 403 `entitlement_required` when it is
37
- * absent and consumes nothing.
38
- *
39
- * The key is a member of the generated `EntitlementKey` union, so a typo is a
40
- * compile error rather than a gate that silently never matches.
41
- */
42
- declare function RequireEntitlement(key: EntitlementKey): MethodDecorator$1;
43
- /**
44
- * Consume one unit of a limit or credit for the request's subject.
45
- *
46
- * Reserves before the handler runs and commits only if it returns; a throw
47
- * cancels the hold, so a user is never charged for work that did not complete.
48
- * Answers 429 `quota_exceeded` (carrying the `LimitState`) when exhausted.
49
- *
50
- * The count is fixed at 1. A DYNAMIC amount is deliberately not expressible
51
- * here: it has to be decided inside the handler and spent BEFORE the billable
52
- * side-effect, which is an explicit `Purchases.withSpend(...)` call, not a
53
- * decorator.
54
- */
55
- declare function Spend(key: LimitKey): MethodDecorator$1;
56
-
57
15
  /**
58
16
  * clients/index.ts — the composition root for the module clients.
59
17
  *
@@ -66,10 +24,13 @@ declare function Spend(key: LimitKey): MethodDecorator$1;
66
24
  * for any of them (`runtime.ts` exports ten, and these four are not among
67
25
  * them), so no handler could reach them. They were constructed on every
68
26
  * boot and thrown away.
69
- * - `Purchases` — its client talks to palstore, and v2 contains no palstore
27
+ * - `Purchases` — its client talked to palstore, and v2 contains no palstore
70
28
  * 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.
29
+ * by the user's decision on 2026-08-15, with the SDK's `src/purchases/`
30
+ * tree left in place because that decision was "deferred, not silently
31
+ * taken". It is taken now: the tree is GONE (2026-08-29), because a
32
+ * decorator standing in front of a service nothing serves is the same
33
+ * defect as `Resource` and `config/*`, which went with it.
73
34
  * - The V1 host/executor transport — the isolate it existed for was removed
74
35
  * on 2026-08-14. A single-tenant process has nobody to hide its own
75
36
  * tenant's credentials from.
@@ -167,73 +128,6 @@ declare class PalbaseModuleError extends Error {
167
128
  }
168
129
  declare function makeHttpClient(cfg: TransportConfig): ModuleTransport;
169
130
 
170
- /**
171
- * keys-gen.ts — generate the `palbase-purchases.d.ts` text from a catalog
172
- * manifest.
173
- *
174
- * The twin of `db/env-gen.ts`: the CLI / deploy pipeline calls
175
- * {@link makePurchasesDts} with the project's catalog revision and writes the
176
- * result to `palbase-purchases.d.ts` at the project root. That file augments
177
- * the `@palbase/backend/purchases` `Entitlements` / `Limits` interfaces, so
178
- * `@RequireEntitlement(...)` and `@Spend(...)` accept the project's real keys
179
- * and nothing else.
180
- *
181
- * Same catalog, two languages: this is the TypeScript half of what swiftgen
182
- * already emits for iOS. Neither side is hand-maintained, so the two cannot
183
- * drift from each other or from the manifest the server validated.
184
- *
185
- * The emitted file ends in `export {};` — same as `makeEnvDts`. Without it the
186
- * `.d.ts` is a global script, and `declare module "…"` there DECLARES an
187
- * ambient module (shadowing the real one, so every key silently becomes
188
- * invalid) instead of AUGMENTING it.
189
- */
190
- /** A catalog entry that may be tombstoned. */
191
- interface Removable {
192
- removed?: boolean;
193
- }
194
- /**
195
- * The slice of `pur_catalogs.manifest` this generator reads. Deliberately
196
- * partial and permissive: the server already validated the manifest (its models
197
- * are `extra="forbid"`), so re-checking here would be a second, weaker copy of
198
- * that authority.
199
- */
200
- interface PurchasesManifest {
201
- limits?: Record<string, Removable>;
202
- credits?: Record<string, Removable>;
203
- entitlements?: Record<string, Removable>;
204
- }
205
- /**
206
- * Render the project's `palbase-purchases.d.ts`.
207
- *
208
- * `Limits` carries BOTH limits and credits: `@Spend` consumes either, and the
209
- * server answers both with a 429 (`quota_exceeded` / `credit_insufficient`), so
210
- * splitting them into two unions would make the author pick the right decorator
211
- * for a distinction the spend path does not make.
212
- */
213
- declare function makePurchasesDts(manifest: PurchasesManifest): string;
214
-
215
- /**
216
- * Per-method purchases metadata, stored on the controller class the same way
217
- * `decorators/registry.ts` stores routes: a symbol-keyed static, plain data, no
218
- * `reflect-metadata`.
219
- *
220
- * Its only consumer is the OpenAPI step, which needs to know that a route is
221
- * gated (declare 403) or metered (declare 429) so the iOS client codegen emits
222
- * TYPED errors instead of a bare status number. Enforcement does NOT read this
223
- * — the decorators wrap the method directly — so a stale registry can never
224
- * cause a missed gate.
225
- */
226
-
227
- /** What `@Spend` recorded for one method. */
228
- interface SpendMeta {
229
- key: LimitKey;
230
- count: number;
231
- }
232
- /** The entitlement `fnName` is gated on, or undefined when it is not gated. */
233
- declare function entitlementFor(ctor: object, fnName: string): EntitlementKey | undefined;
234
- /** What `fnName` spends, or undefined when it spends nothing. */
235
- declare function spendFor(ctor: object, fnName: string): SpendMeta | undefined;
236
-
237
131
  /** One column, flattened. Mirrors Go's `schema.ColumnJSON`. */
238
132
  interface ColumnJSON {
239
133
  type: string;
@@ -287,13 +181,27 @@ interface TableJSON {
287
181
  columns: string[];
288
182
  }[];
289
183
  search?: SearchJSON;
184
+ memory?: MemoryJSON;
185
+ }
186
+ /** C-11 wire şekli — Go'nun MemoryJSON'ıyla alan-adı sözleşmesi (D-019).
187
+ * Beyansız tablolarda alan OMIT — eski şemalar bayt-aynı (NFR-B1). */
188
+ interface MemoryJSON {
189
+ from: string[];
190
+ into: string;
191
+ subject?: string;
192
+ extract: {
193
+ provider: string;
194
+ model: string;
195
+ };
290
196
  }
291
197
  /** A whole declaration. Mirrors Go's `schema.SchemaJSON`. */
292
198
  interface SchemaJSON {
293
199
  tables: Record<string, TableJSON>;
294
200
  extensions: string[];
295
201
  }
296
- /** C-4 wire şekli — Go'nun SearchJSON'ıyla ALAN ADI sözleşmesi (C-5). */
202
+ /** C-4 wire şekli — Go'nun SearchJSON'ıyla ALAN ADI sözleşmesi (C-5).
203
+ * `mode`/`chunks` yalnız yeni-biçim chunk-modunda emit edilir (D-010);
204
+ * satır-modu ve eski biçim bayt-aynı kalır (NFR-B1). */
297
205
  interface SearchJSON {
298
206
  text?: {
299
207
  columns: string[];
@@ -310,7 +218,17 @@ interface SearchJSON {
310
218
  baseURL?: string;
311
219
  };
312
220
  staleness?: "null" | "keep";
221
+ mode?: "row" | "chunks";
222
+ chunks?: {
223
+ sizeChars?: number;
224
+ overlapChars?: number;
225
+ };
313
226
  }[];
227
+ /** FR-026: sorgu-yeniden-yazımı haritası — beyan yoksa OMIT (NFR-B1). */
228
+ synonyms?: Record<string, string[]>;
229
+ /** C-1: sonuç yeniden-sıralama beyanı — beyan yoksa OMIT. */
230
+ /** FR-029: geçerlilik türevleri — beyan yoksa OMIT. */
231
+ validity?: boolean;
314
232
  }
315
233
  /**
316
234
  * Serialize a `defineSchema(...)` result into the JSON the deploy applies.
@@ -442,931 +360,60 @@ declare function defineChannels(map: ChannelsInput): ChannelsDef;
442
360
  declare function makeEnvDts(schema: SchemaDef): string;
443
361
 
444
362
  /**
445
- * egress.ts — the tenant outbound-HTTP allowlist config-as-code DSL.
446
- *
447
- * `defineEgress({ hosts, timeoutMs })` declares the external hosts a backend may
448
- * fetch(), and how long a single call may take. config/egress.ts default-exports
449
- * it; the deploy evals it to JSON (SchemaExtractor.EvaluateConfigFile
450
- * config_extract.js) and bakes both into the artifact manifest the isolate
451
- * loader Tenant.egressAllow / Tenant.egressTimeoutMs. The tenantFetch
452
- * capability rejects any host NOT on the list. NO config/egress.ts (or an empty
453
- * list) ⇒ the backend has NO outbound network — fail-closed.
454
- *
455
- * @example
456
- * // config/egress.ts
457
- * import { defineEgress } from "@palbase/backend";
458
- * export default defineEgress({
459
- * hosts: ["api.openai.com"],
460
- * timeoutMs: 90_000, // a slow LLM call; default 30_000
461
- * });
462
- *
463
- * Each host is a bare hostname (no scheme/port/path/wildcard). A leading dot,
464
- * `.example.com`, also covers subdomains (Squid dstdomain semantics). Egress is
465
- * https-only on :443; the deploy REJECTS a malformed / IP-literal / internal host
466
- * (fail-closed a security allowlist is never best-effort).
467
- *
468
- * `timeoutMs` is a CEILING, not a grant: the call still ends when the INVOCATION
469
- * ends. A job may run up to its own `@Job({ timeout })` (max 300s), while a
470
- * request is additionally bounded by the gateway. Setting 300_000 on a route
471
- * whose request dies earlier buys nothing — size it to the caller.
472
- *
473
- * Response bodies are BUFFERED whole (5 MB cap) before your fetch() resolves, so
474
- * a streaming/SSE upstream gives you no partial output and no earlier
475
- * first-byte: the entire stream must complete inside `timeoutMs`.
476
- *
477
- * Emits: { __config: "egress", hosts: [...], timeoutMs?: number }
478
- */
479
- /** The discriminant written under `__config` so the deploy eval knows the kind. */
480
- declare const EGRESS_CONFIG_KIND: "egress";
481
- /**
482
- * Bounds on `timeoutMs`, duplicated (deliberately) in the deploy's
483
- * ParseEgressConfig. This copy shapes the authoring error; that one is
484
- * authoritative and fail-closed. They must agree — a value this accepts and the
485
- * deploy rejects is a broken deploy the author cannot see coming.
486
- */
487
- declare const EGRESS_TIMEOUT_MIN_MS = 1000;
488
- declare const EGRESS_TIMEOUT_MAX_MS = 300000;
489
- /** Applied when `timeoutMs` is omitted. Also spelled in the isolate capability. */
490
- declare const EGRESS_TIMEOUT_DEFAULT_MS = 30000;
491
- /** The author-facing input to defineEgress. */
492
- interface EgressInput {
493
- hosts: string[];
494
- /**
495
- * Per-call ceiling for an outbound fetch, in milliseconds.
496
- * 1_000..300_000; omitted ⇒ 30_000.
497
- */
498
- timeoutMs?: number;
499
- }
500
- /** The evaluated config/egress.ts default export (what the deploy reads). */
501
- interface EgressConfig {
502
- __config: typeof EGRESS_CONFIG_KIND;
503
- hosts: string[];
504
- timeoutMs?: number;
505
- }
506
- /**
507
- * Declare the backend's outbound-HTTP allowlist. Client-side this only shapes +
508
- * validates the list; the authoritative fail-closed validation runs at deploy.
509
- */
510
- declare function defineEgress(input: EgressInput): EgressConfig;
511
-
512
- /**
513
- * notifications.ts — the notification-providers config-as-code DSL.
514
- *
515
- * `defineNotifications({ push, email, sms })` is the second MODULE config-as-code
516
- * surface (the sibling of `config/storage.ts`'s `defineStorage`). A
517
- * `config/notifications.ts` file default-exports a `defineNotifications(...)`
518
- * result; on deploy the br-pod evaluates it to JSON and reconciles the declared
519
- * providers against the tenant's live providers via the PalNotify admin API
520
- * (create missing; never auto-delete — dropping a provider is destructive and
521
- * warned only).
522
- *
523
- * SECRETS ARE NEVER IN THE FILE. Each provider's NON-SECRET fields (team id, key
524
- * id, bundle id, region, host, port, from address, account sid, …) are literals
525
- * in the config. Each provider's SECRET material (the APNs .p8 key, the FCM
526
- * service-account JSON, an api_key, an auth token, the SMTP/ACS password, …) is
527
- * NOT a field here — it is bound BY CONVENTION to a RESERVED encrypted env var
528
- * and resolved at deploy from control-pg. The env-key convention is:
529
- *
530
- * PB_NOTIFICATIONS_<PROVIDER>_<FIELD> (UPPER_SNAKE_CASE)
531
- *
532
- * e.g. `PB_NOTIFICATIONS_APNS_P8`, `PB_NOTIFICATIONS_FCM_SERVICE_ACCOUNT`,
533
- * `PB_NOTIFICATIONS_TWILIO_AUTH_TOKEN`. The `PB_` namespace is reserved (the CLI
534
- * refuses a hand-set `palbase secret set PB_*`); the CLI's `palbase notifications
535
- * add <provider>` derives the key and uploads the secret for you, so an author
536
- * never types either the secret or the env-key name into git.
537
- *
538
- * @example
539
- * import { defineNotifications } from "@palbase/backend";
540
- *
541
- * export default defineNotifications({
542
- * push: {
543
- * apns: { enabled: true, teamId: "ABCDE12345", keyId: "KEY1234567", bundleId: "com.acme.app" },
544
- * fcm: { enabled: true },
545
- * },
546
- * email: {
547
- * sendgrid: { enabled: true, fromDomain: "mail.acme.com" },
548
- * },
549
- * sms: {
550
- * twilio: { enabled: true, accountSid: "ACxxxxxxxx", messagingServiceSid: "MGxxxxxxxx" },
551
- * },
552
- * });
553
- *
554
- * The returned value is the EXACT JSON shape the runtime's generic config
555
- * extractor (`config_extract.js`) emits and the Go apply step parses:
556
- * { __config: "notifications", push: {...}, email: {...}, sms: {...} }
557
- * Only ENABLED providers carry their non-secret fields; a disabled (or absent)
558
- * provider serializes as `{ enabled: false }` so the apply step skips it.
559
- */
560
- /** The `__config` discriminant the eval/apply reads to confirm a config/*.ts is
561
- * a notifications config. Notifications is `"notifications"`. */
562
- declare const NOTIFICATIONS_CONFIG_KIND: "notifications";
563
- /** The reserved env-var prefix that backs provider secrets. A `palbase secret
564
- * set` of a key under this prefix is REFUSED by the CLI (it is managed by
565
- * `palbase notifications add`). Both the CLI and the br-pod apply step derive a
566
- * provider's secret env key as `${RESERVED_SECRET_PREFIX}_<PROVIDER>_<FIELD>`. */
567
- declare const RESERVED_SECRET_PREFIX: "PB_NOTIFICATIONS";
568
- /** One provider's catalog entry: which non-secret fields are required, which are
569
- * optional, and which secret field-name(s) bind to reserved env vars. */
570
- interface ProviderCatalogEntry {
571
- /** The channel this provider serves (`push` | `email` | `sms`). */
572
- readonly channel: "push" | "email" | "sms";
573
- /** Non-secret config fields the author MUST supply (validated eagerly). */
574
- readonly required: readonly string[];
575
- /** Non-secret config fields the author MAY supply. */
576
- readonly optional: readonly string[];
577
- /** Secret field name(s) — each backed by `PB_NOTIFICATIONS_<PROVIDER>_<FIELD>`
578
- * (FIELD is the UPPER_SNAKE of the name here). NOT a config field. */
579
- readonly secrets: readonly string[];
580
- }
581
- /**
582
- * The provider catalog: provider key → fields. The non-secret field names are
583
- * the camelCase keys an author writes in `config/notifications.ts`; the secret
584
- * names are the snake-ish tokens that become the reserved env-var suffix.
585
- *
586
- * apns: team_id / key_id / bundle_id (+ optional is_production); secret = p8.
587
- * fcm: no non-secret fields; secret = service_account (the full JSON).
588
- * sendgrid: from_domain; secret = api_key.
589
- * ses: region / from_domain / access_key_id; secret = secret_access_key.
590
- * smtp: host / port / from_email (+ optional username, use_starttls); secret = password.
591
- * acs: from_email (+ optional from_name); secret = connection_string.
592
- * twilio: account_sid + (from_number OR messaging_service_sid); secret = auth_token.
593
- */
594
- declare const PROVIDER_CATALOG: {
595
- readonly apns: {
596
- readonly channel: "push";
597
- readonly required: readonly ["teamId", "keyId", "bundleId"];
598
- readonly optional: readonly ["isProduction"];
599
- readonly secrets: readonly ["p8"];
600
- };
601
- readonly fcm: {
602
- readonly channel: "push";
603
- readonly required: readonly [];
604
- readonly optional: readonly [];
605
- readonly secrets: readonly ["serviceAccount"];
606
- };
607
- readonly sendgrid: {
608
- readonly channel: "email";
609
- readonly required: readonly ["fromDomain"];
610
- readonly optional: readonly [];
611
- readonly secrets: readonly ["apiKey"];
612
- };
613
- readonly ses: {
614
- readonly channel: "email";
615
- readonly required: readonly ["region", "accessKeyId", "fromDomain"];
616
- readonly optional: readonly [];
617
- readonly secrets: readonly ["secretAccessKey"];
618
- };
619
- readonly smtp: {
620
- readonly channel: "email";
621
- readonly required: readonly ["host", "port", "fromEmail"];
622
- readonly optional: readonly ["username", "useStarttls"];
623
- readonly secrets: readonly ["password"];
624
- };
625
- readonly acs: {
626
- readonly channel: "email";
627
- readonly required: readonly ["fromEmail"];
628
- readonly optional: readonly ["fromName"];
629
- readonly secrets: readonly ["connectionString"];
630
- };
631
- readonly twilio: {
632
- readonly channel: "sms";
633
- readonly required: readonly ["accountSid"];
634
- readonly optional: readonly ["fromNumber", "messagingServiceSid"];
635
- readonly secrets: readonly ["authToken"];
636
- };
637
- };
638
- /** A provider key (`"apns" | "fcm" | "sendgrid" | …`). */
639
- type ProviderName = keyof typeof PROVIDER_CATALOG;
640
- /** APNs (Apple Push) — non-secret fields. The `.p8` key is the reserved secret
641
- * `PB_NOTIFICATIONS_APNS_P8` (uploaded via `palbase notifications add apns`). */
642
- interface ApnsOptions {
643
- enabled?: boolean;
644
- teamId: string;
645
- keyId: string;
646
- bundleId: string;
647
- /** APNs production gateway vs sandbox. Defaults to true (production). */
648
- isProduction?: boolean;
649
- }
650
- /** FCM (Firebase Cloud Messaging) — no non-secret fields; the service-account
651
- * JSON is the reserved secret `PB_NOTIFICATIONS_FCM_SERVICE_ACCOUNT`. */
652
- interface FcmOptions {
653
- enabled?: boolean;
654
- }
655
- /** SendGrid email — non-secret `fromDomain`; api_key is the reserved secret. */
656
- interface SendgridOptions {
657
- enabled?: boolean;
658
- fromDomain: string;
659
- }
660
- /** Amazon SES email — secret_access_key is the reserved secret. */
661
- interface SesOptions {
662
- enabled?: boolean;
663
- region: string;
664
- accessKeyId: string;
665
- fromDomain: string;
666
- }
667
- /** SMTP email — password is the reserved secret. */
668
- interface SmtpOptions {
669
- enabled?: boolean;
670
- host: string;
671
- port: number;
672
- fromEmail: string;
673
- username?: string;
674
- useStarttls?: boolean;
675
- }
676
- /** Azure Communication Services email — connection_string is the reserved secret. */
677
- interface AcsOptions {
678
- enabled?: boolean;
679
- fromEmail: string;
680
- fromName?: string;
681
- }
682
- /** Twilio SMS — auth_token is the reserved secret. Exactly one of `fromNumber`
683
- * or `messagingServiceSid` must be supplied. */
684
- interface TwilioOptions {
685
- enabled?: boolean;
686
- accountSid: string;
687
- fromNumber?: string;
688
- messagingServiceSid?: string;
689
- }
690
- /** The union of every provider's author-facing options. `buildProvider` accepts
691
- * this so each provider's typed options pass without a cast. */
692
- type ProviderOptions = ApnsOptions | FcmOptions | SendgridOptions | SesOptions | SmtpOptions | AcsOptions | TwilioOptions;
693
- /** The default locale a template row lands on when the author names none. Must
694
- * match PalNotify's `model.DefaultLocale`. */
695
- declare const DEFAULT_TEMPLATE_LOCALE: "en";
696
- /** One locale's email content. */
697
- interface EmailTemplateContent {
698
- /** Subject line. Handlebars placeholders (`{{name}}`) are rendered at send. */
699
- subject: string;
700
- /** HTML body. */
701
- html: string;
702
- /** Plain-text body. Omit and the server derives one from `html` at render. */
703
- text?: string;
704
- /**
705
- * Variables the sender MUST supply. Omit and they are DERIVED from the
706
- * `{{placeholders}}` in this locale's content — see {@link extractVariables}.
707
- * Supply explicitly to make a placeholder optional (leave it out of the list)
708
- * or to require something a conditional references.
709
- */
710
- variables?: readonly string[];
711
- }
712
- /** An email template: base content, plus optional per-locale overrides. */
713
- interface EmailTemplateOptions extends EmailTemplateContent {
714
- /** Locale of the base content. Defaults to `"en"`. */
715
- locale?: string;
716
- /** Additional locales, keyed by BCP47 short tag (`tr`, `de`, …). */
717
- locales?: Record<string, EmailTemplateContent>;
718
- }
719
- /** One locale's SMS content. */
720
- interface SmsTemplateContent {
721
- /** Message body. Handlebars placeholders are rendered at send. */
722
- body: string;
723
- /** Variables the sender must supply. Derived from `body` when omitted. */
724
- variables?: readonly string[];
725
- }
726
- /** An SMS template: base content, plus optional per-locale overrides. */
727
- interface SmsTemplateOptions extends SmsTemplateContent {
728
- /** Locale of the base content. Defaults to `"en"`. */
729
- locale?: string;
730
- /** Additional locales, keyed by BCP47 short tag. */
731
- locales?: Record<string, SmsTemplateContent>;
732
- }
733
- /** Author-facing templates block: slug → template. */
734
- interface TemplatesInput {
735
- email?: Record<string, EmailTemplateOptions>;
736
- sms?: Record<string, SmsTemplateOptions>;
737
- }
738
- /** A compiled email-template row — one (slug, locale) pair, in the snake_case
739
- * wire shape PalNotify's templates API takes. */
740
- interface EmailTemplateDef {
741
- slug: string;
742
- locale: string;
743
- subject: string;
744
- html_body: string;
745
- text_body?: string;
746
- variables: string[];
747
- }
748
- /** A compiled SMS-template row — one (slug, locale) pair. */
749
- interface SmsTemplateDef {
750
- slug: string;
751
- locale: string;
752
- body: string;
753
- variables: string[];
754
- }
755
- /** The compiled templates block: flat, per-channel row lists. Flat rows (rather
756
- * than a nested locale map) are what the apply step iterates — one row is one
757
- * upsert against the (slug, locale) unique key. */
758
- interface TemplatesConfig {
759
- email: EmailTemplateDef[];
760
- sms: SmsTemplateDef[];
761
- }
762
- /**
763
- * Pull the variable names out of Handlebars content.
764
- *
765
- * Only PLAIN mustaches count: `{{name}}` and `{{{name}}}`. Deliberately skipped:
766
- * - block/section/partial/comment tokens (`{{#if}}`, `{{/if}}`, `{{else}}`,
767
- * `{{> partial}}`, `{{! comment}}`) — structure, not data the sender passes;
768
- * - helper calls (`{{formatDate at}}`) — the first token is a helper name;
769
- * - `this` and `@index`-style frame references.
770
- *
771
- * A dotted path contributes its ROOT (`{{user.name}}` → `user`), because that is
772
- * what the sender actually has to hand over.
773
- *
774
- * The list is what the server ENFORCES as required at render time, so keeping it
775
- * to the unambiguous cases is the point: an over-eager derivation would turn an
776
- * optional conditional into a mandatory argument and 400 real sends.
777
- */
778
- declare function extractVariables(...sources: (string | undefined)[]): string[];
779
- /** The author-facing input to `defineNotifications`. Every provider + every
780
- * channel is optional — declare only what you use. */
781
- interface NotificationsInput {
782
- push?: {
783
- apns?: ApnsOptions;
784
- fcm?: FcmOptions;
785
- };
786
- email?: {
787
- sendgrid?: SendgridOptions;
788
- ses?: SesOptions;
789
- smtp?: SmtpOptions;
790
- acs?: AcsOptions;
791
- };
792
- sms?: {
793
- twilio?: TwilioOptions;
794
- };
795
- /** Email + SMS templates, keyed by slug. Applied on every deploy. */
796
- templates?: TemplatesInput;
797
- }
798
- /** A compiled provider def. `enabled` is always present; the remaining keys are
799
- * the provider's non-secret fields (verbatim from the catalog). A disabled
800
- * provider is `{ enabled: false }` with no other fields. */
801
- type ProviderDef = {
802
- enabled: boolean;
803
- } & Record<string, unknown>;
804
- /** The compiled notifications config — the discriminant + per-channel maps of
805
- * provider name → {@link ProviderDef}. This is what `defineNotifications`
806
- * returns and the runtime extractor serializes. */
807
- interface NotificationsConfig {
808
- __config: typeof NOTIFICATIONS_CONFIG_KIND;
809
- push: Record<string, ProviderDef>;
810
- email: Record<string, ProviderDef>;
811
- sms: Record<string, ProviderDef>;
812
- templates: TemplatesConfig;
813
- }
814
- /**
815
- * Compile + validate one provider's author options into a {@link ProviderDef}.
816
- *
817
- * - A provider with `enabled === false` (or omitted) compiles to `{ enabled:
818
- * false }` and its required fields are NOT enforced (you can declare a disabled
819
- * provider as a placeholder without filling it in).
820
- * - An ENABLED provider must supply every `required` non-secret field from the
821
- * catalog; a missing one throws at config-author time (not silently at deploy).
822
- * - Only the catalog's non-secret fields (required + optional) are copied into
823
- * the def — an unknown extra key is ignored (it would have no effect on the
824
- * live provider). Secrets are never read here.
825
- */
826
- declare function buildProvider(name: ProviderName, opts: ProviderOptions): ProviderDef;
827
- /**
828
- * Define the notification-provider config for a project. Returns the
829
- * discriminated {@link NotificationsConfig} the runtime config extractor
830
- * serializes and the Go apply step reconciles. Validates eagerly: an enabled
831
- * provider missing a required non-secret field throws here (config-author time),
832
- * not at deploy.
833
- *
834
- * @example
835
- * export default defineNotifications({
836
- * push: { apns: { enabled: true, teamId: "T", keyId: "K", bundleId: "com.x" } },
837
- * });
838
- */
839
- declare function defineNotifications(input: NotificationsInput): NotificationsConfig;
840
- /**
841
- * Derive the RESERVED env-var key that backs a provider's secret field, e.g.
842
- * `reservedSecretKey("apns", "p8")` → `"PB_NOTIFICATIONS_APNS_P8"`. The CLI uses
843
- * this to upload the secret and the br-pod apply step uses the same derivation
844
- * to resolve it — they never hand-type the key, so they cannot drift.
845
- */
846
- declare function reservedSecretKey(provider: ProviderName, secretField: string): string;
847
-
848
- /**
849
- * test-users.ts — the test-user fixtures config-as-code DSL.
850
- *
851
- * `defineTestUsers({ users })` is a MODULE config-as-code surface (a sibling of
852
- * `config/storage.ts`'s `defineStorage` and `config/flags.ts`'s `defineFlags`).
853
- * A `config/test-users.ts` file default-exports a `defineTestUsers(...)` result;
854
- * on deploy the runtime evaluates it to JSON and hands it to Studio, which owns
855
- * the single apply engine (mint + seed). Studio, the CLI and the deploy all go
856
- * through that one engine — there is no second interpreter of this JSON.
857
- *
858
- * TWO SHAPES, ONE DSL:
859
- *
860
- * - `email` GIVEN → a FIXTURE. Deploy materializes it create-if-missing, so
861
- * the app can always be signed into with the same credentials. Deliberately
862
- * NOT applied to a production Environment (the password lives in git).
863
- * - `email` OMITTED → a TEMPLATE. Deploy creates nothing; Studio and the CLI
864
- * mint as many fresh instances from it as you want (random credentials).
865
- *
866
- * Every minted user is an `is_test` user: excluded from MAU/billing, carrying
867
- * `test: true` in its token, and refused by the Test Data write path if it ever
868
- * turns out not to be one.
869
- *
870
- * @example
871
- * import { defineTestUsers, testUser } from "@palbase/backend";
872
- *
873
- * export default defineTestUsers({
874
- * users: {
875
- * demo: testUser({
876
- * email: "demo@test.local",
877
- * password: "demo-password-1234",
878
- * seed: {
879
- * profiles: [{ display_name: "Demo", tier: "pro" }],
880
- * lists: [
881
- * { title: "Groceries", todos: [{ title: "Milk" }, { title: "Eggs", done: true }] },
882
- * ],
883
- * },
884
- * }),
885
- * heavy_user: testUser({ seed: { lists: [{ title: "L", todos: [{ title: "t" }] }] } }),
886
- * },
887
- * });
888
- *
889
- * The returned value is the EXACT JSON shape the runtime's generic config
890
- * extractor emits and the apply engine consumes:
891
- * { __config: "test-users", users: { <name>: { email, password, seed } } }
892
- * `email` / `password` are the string or `null`; `seed` is `{}` when omitted.
893
- */
894
-
895
- /** The `__config` discriminant the eval/apply reads to confirm a config/*.ts is
896
- * a test-users config. */
897
- declare const TEST_USERS_CONFIG_KIND: "test-users";
898
- /**
899
- * A table graph in the shape the env `Tables` interface carries. The
900
- * derivations below are parameterized over it (rather than reading the global
901
- * `Tables` directly) so they can be type-tested against a fixture graph without
902
- * augmenting the global interface — augmentation is program-wide and would leak
903
- * into every other type test.
904
- *
905
- * The `Record<keyof G, …>` constraint is deliberate: a plain
906
- * `Record<string, TableTypes>` would reject the `Tables` INTERFACE, which has
907
- * no index signature.
908
- */
909
- type TableGraph = Record<string, TableTypes>;
910
- /** The user-rooted tables of `G` — the ones a test user owns rows in directly. */
911
- type OwnedTablesOf<G extends Record<keyof G, TableTypes>> = {
912
- [T in keyof G]: G[T]["owner"] extends string ? T : never;
913
- }[keyof G];
914
- /**
915
- * One authored seed row for table `T` of graph `G`.
916
- *
917
- * Starts from the table's INSERT shape and removes the two columns the engine
918
- * owns: the owner column (set to the minted user) and `Via` — the FK column
919
- * this row hangs off its parent by, when the row is nested under one. What
920
- * remains is extended with the table's children, each an optional array of
921
- * their own seed rows.
922
- *
923
- * Recursion terminates because `makeEnvDts` drops self-FKs and cycle
924
- * back-edges when it emits `children`.
925
- *
926
- * Name collision note: if a table has a COLUMN whose name equals a CHILD
927
- * table's name, the intersection makes that key unusable (its type collapses).
928
- * That is a schema-naming problem the author sees at compile time; the apply
929
- * engine resolves the same ambiguity live in favour of the column, because it
930
- * introspects the real table.
931
- */
932
- type SeedRowFor<G extends Record<keyof G, TableTypes>, T extends keyof G, Via extends string = never> = Omit<G[T]["insert"], (G[T]["owner"] & string) | Via> & {
933
- [C in keyof G[T]["children"]]?: Array<SeedRowFor<G, C & keyof G, G[T]["children"][C] & string>>;
934
- };
935
- /** The typed seed over graph `G`: top-level keys are user-rooted tables only;
936
- * everything else is reached by nesting under its parent. */
937
- type SeedFor<G extends Record<keyof G, TableTypes>> = {
938
- [T in OwnedTablesOf<G>]?: Array<SeedRowFor<G, T>>;
939
- };
940
- /** The serialized seed — what travels as JSON and what the engine receives. */
941
- type SeedJson = Record<string, Array<Record<string, unknown>>>;
942
- /**
943
- * The author-facing seed type. Falls back to the loose shape when `Tables` is
944
- * still empty (no `db/schema.ts`, or `palbase-env.d.ts` not generated yet) so
945
- * an unseeded project gets a usable DSL instead of an inscrutable `{}` error.
946
- */
947
- type Seed = [keyof Tables] extends [never] ? SeedJson : SeedFor<Tables>;
948
- /**
949
- * The options for one declared test user.
950
- *
951
- * - `email` / `password`: supply BOTH to make this a fixture with stable
952
- * credentials (deploy materializes it, the app signs in with them), or
953
- * NEITHER to make it a template the server generates credentials for.
954
- * - `seed`: the user's data tree, derived from `db/schema.ts`. Top-level keys
955
- * are user-rooted tables; FK children nest inside their parent's rows.
956
- */
957
- interface TestUserOptions {
958
- email?: string;
959
- password?: string;
960
- /**
961
- * A verified phone for this fixture, E.164 (`+905551112233`). Requires
962
- * {@link TestUserOptions.otp}: nothing can receive an SMS at a fabricated
963
- * number, so a phone fixture without a knowable code is one no test could
964
- * ever sign in as.
965
- */
966
- phone?: string;
967
- /**
968
- * The fixed code this fixture's phone accepts. Honoured ONLY for the
969
- * `is_test` user it belongs to — the real rail is untouched, and no
970
- * verification is ever sent (or billed) for a fixture.
971
- */
972
- otp?: string;
973
- seed?: Seed;
974
- }
975
- /** The compiled, serializable test-user definition — the EXACT shape emitted to
976
- * JSON and consumed by the apply engine. */
977
- interface TestUserDef {
978
- /** Fixed login e-mail (fixture), or `null` when the server should generate one. */
979
- email: string | null;
980
- /** Fixed password (fixture), or `null` when the server should generate one. */
981
- password: string | null;
982
- /** Verified phone in E.164, or `null` for an e-mail-only fixture. */
983
- phone: string | null;
984
- /** The code that phone accepts, or `null` when there is no phone. */
985
- otp: string | null;
986
- /** The user's data tree; `{}` when nothing is seeded. */
987
- seed: SeedJson;
988
- }
989
- /** A test-users config: the discriminant + the declared users by name. */
990
- interface TestUsersConfig {
991
- __config: typeof TEST_USERS_CONFIG_KIND;
992
- users: Record<string, TestUserDef>;
993
- }
994
- /** The author-facing input to `defineTestUsers`. */
995
- interface TestUsersInput {
996
- users: Record<string, TestUserDef>;
997
- }
998
- /**
999
- * Define a single test user. The NAME is supplied by the object key in
1000
- * `defineTestUsers({ users: { <name>: testUser({...}) } })`, so `testUser()`
1001
- * takes only the options.
1002
- *
1003
- * Validates eagerly (at config-author time):
1004
- * - `email` and `password` are supplied together or not at all — half a
1005
- * credential pair would silently degrade a fixture into a template.
1006
- * - `email` looks like an address; `password` is at least
1007
- * {@link MIN_PASSWORD_LENGTH} characters.
1008
- * - `seed` is structurally `{ <table>: [ {...}, ... ] }` with non-empty arrays.
1009
- *
1010
- * The user NAME is validated by `defineTestUsers`, which is where it is known.
1011
- */
1012
- declare function testUser(opts?: TestUserOptions): TestUserDef;
1013
- /**
1014
- * Define a project's test users. The user NAME comes from each object key
1015
- * (authors never repeat it). Returns the discriminated {@link TestUsersConfig}
1016
- * the runtime config extractor serializes and the apply engine consumes.
1017
- *
1018
- * Validates eagerly:
1019
- * - each name matches `^[a-zA-Z][a-zA-Z0-9_]*$` (the CLI's `--template <name>`
1020
- * and control-pg's scenario name share this vocabulary).
1021
- * - no two fixtures declare the SAME e-mail — they would race to
1022
- * create-if-missing the one account and the second would silently adopt the
1023
- * first one's data tree.
1024
- *
1025
- * @example
1026
- * export default defineTestUsers({
1027
- * users: { demo: testUser({ email: "demo@test.local", password: "demo-password-1234" }) },
1028
- * });
1029
- */
1030
- declare function defineTestUsers(input: TestUsersInput): TestUsersConfig;
1031
-
1032
- /**
1033
- * flags.ts — the feature-flag-definitions config-as-code DSL.
1034
- *
1035
- * `defineFlags({ flags })` is the third MODULE config-as-code surface (a sibling
1036
- * of `config/storage.ts`'s `defineStorage` and `config/notifications.ts`'s
1037
- * `defineNotifications`). A `config/flags.ts` file default-exports a
1038
- * `defineFlags(...)` result; on deploy the br-pod evaluates it to JSON and
1039
- * UPSERTS the declared flag DEFINITIONS into PalFlags (the user-flags module's
1040
- * system-flags admin API) — create-or-update, idempotent. A live flag NOT in
1041
- * config is NEVER auto-deleted (an orphan flag is harmless; upsert-only).
1042
- *
1043
- * Flags are DECLARATIVE: a flag is a typed project-wide DEFAULT (its key, type,
1044
- * default value, an optional description, and — for string flags — an optional
1045
- * list of allowed `variants`). The VALUE of a flag for a specific USER (a
1046
- * per-user override / A-B assignment) is runtime state set via the SDK, NEVER in
1047
- * git. `variants` here is part of the DEFINITION (the allowed values a string
1048
- * flag may take), not a per-user assignment.
1049
- *
1050
- * NO SECRETS. Unlike notifications, a flag carries no credentials — this is the
1051
- * simplest module config (pure declarative data).
1052
- *
1053
- * @example
1054
- * import { defineFlags, flag } from "@palbase/backend";
1055
- *
1056
- * export default defineFlags({
1057
- * flags: {
1058
- * new_dashboard: flag({ type: "boolean", default: false, description: "Roll out the new dashboard" }),
1059
- * max_uploads: flag({ type: "number", default: 10 }),
1060
- * theme: flag({ type: "string", default: "light", variants: ["light", "dark", "system"] }),
1061
- * limits: flag({ type: "json", default: { daily: 10, burst: 50 } }),
1062
- * },
1063
- * });
1064
- *
1065
- * The returned value is the EXACT JSON shape the runtime's generic config
1066
- * extractor (`config_extract.js`) emits and the Go apply step parses:
1067
- * { __config: "flags", flags: { <key>: { type, default, variants, description }, ... } }
1068
- * `variants` is the allowed-values list for a string flag, or `null` (any
1069
- * string). `description` is the doc string, or `null`. The apply step maps the
1070
- * author-facing `type` to PalFlags' `value_type`: "boolean" → "bool" and
1071
- * "json" → "object"; "number"/"string" pass through.
1072
- */
1073
- /** The `__config` discriminant the eval/apply reads to confirm a config/*.ts is
1074
- * a flags config. Flags is `"flags"`. */
1075
- declare const FLAGS_CONFIG_KIND: "flags";
1076
- /**
1077
- * A flag's type. The author-facing vocabulary is `"boolean" | "number" |
1078
- * "string" | "json"`; the apply step maps it to PalFlags' `value_type` enum —
1079
- * `"boolean"` → `"bool"`, `"json"` → `"object"`, the other two pass through. The
1080
- * SDK keeps the JS-natural names so the DSL reads cleanly.
1081
- *
1082
- * `"json"` is a structured (object) flag. It is named for what an author types —
1083
- * a JSON object literal — and matches the `--type json` the CLI's per-user
1084
- * override commands already use for this same server type.
1085
- */
1086
- type FlagType = "boolean" | "number" | "string" | "json";
1087
- /** Any value expressible in JSON — what may appear INSIDE a `"json"` flag's default. */
1088
- type FlagJsonValue = boolean | number | string | null | FlagJsonValue[] | {
1089
- [key: string]: FlagJsonValue;
1090
- };
1091
- /**
1092
- * The default of a `"json"` flag: a JSON OBJECT. Not an array and not a scalar —
1093
- * PalFlags' `object` value type accepts a JSON object at the top level only
1094
- * (`validate.ValidateValueTypeMatch`), and it caps nesting depth at 3.
1095
- */
1096
- type FlagJsonObject = {
1097
- [key: string]: FlagJsonValue;
1098
- };
1099
- /** The runtime JSON value a flag's `default` may hold (matches {@link FlagType}). */
1100
- type FlagValue = boolean | number | string | FlagJsonObject;
1101
- /**
1102
- * The author-facing options for a single flag — a UNION keyed on `type`, so
1103
- * `default` is checked against the declared type at compile time and `variants`
1104
- * is only accepted where it means something.
1105
- *
1106
- * - `type`: the flag's type — `"boolean" | "number" | "string" | "json"`.
1107
- * - `default`: the project-wide default value. MUST match `type` (a `number`
1108
- * default with `type: "boolean"` is a type error, and throws at runtime).
1109
- * - `variants`: ONLY valid for `type: "string"` — the allowed values the string
1110
- * flag may take (a DEFINITION, not a per-user assignment). When given, the
1111
- * `default` MUST be one of the variants. Supplying `variants` on any other
1112
- * type is a type error, and throws at runtime. (PalFlags rejects variants for
1113
- * every non-string `value_type`, so accepting them here would only produce a
1114
- * config the server refuses.)
1115
- * - `description`: an optional human description of what the flag controls.
1116
- */
1117
- type FlagOptions = {
1118
- type: "boolean";
1119
- default: boolean;
1120
- variants?: never;
1121
- conditions?: FlagCondition<boolean>[];
1122
- description?: string;
1123
- } | {
1124
- type: "number";
1125
- default: number;
1126
- variants?: never;
1127
- conditions?: FlagCondition<number>[];
1128
- description?: string;
1129
- } | {
1130
- type: "string";
1131
- default: string;
1132
- variants?: string[];
1133
- conditions?: FlagCondition<string>[];
1134
- description?: string;
1135
- } | {
1136
- type: "json";
1137
- default: FlagJsonObject;
1138
- variants?: never;
1139
- conditions?: FlagCondition<FlagJsonObject>[];
1140
- description?: string;
1141
- };
1142
- /**
1143
- * One targeting branch: the value this flag resolves to when `when` matches the
1144
- * caller's client context. Branch ORDER is priority — the first match wins, and
1145
- * a flag with no matching branch falls back to its `default`.
1146
- *
1147
- * `when` is a small expression over what the client declares about itself:
1148
- *
1149
- * ```
1150
- * client.platform == 'ios' && client.app_version >= '2.0.0'
1151
- * client.locale startsWith 'tr'
1152
- * client.platform in ['ios', 'android']
1153
- * ```
1154
- *
1155
- * Those are CLAIMS, not proof — the server treats them as attacker-controlled.
1156
- * They select WHICH value a caller sees, never WHETHER they may see it, so a
1157
- * condition must not be the thing standing between a user and something paid.
1158
- */
1159
- /**
1160
- * The variables the server exposes. Mirrors `declaredVars` in
1161
- * `modules/user-flags/internal/conditions/conditions.go`; the server rejects
1162
- * anything it does not know, so the worst a stale copy here does is offer too
1163
- * little.
1164
- *
1165
- * `client.*` is what the SDK reports about the caller. `percentile` and
1166
- * `server.now` are DERIVED server-side and cannot be moved by a lying client,
1167
- * which is what makes a rollout or a time window mean anything.
1168
- */
1169
- type StringVar = "client.platform" | "client.locale";
1170
- type SemverVar = "client.app_version" | "client.os_version";
1171
- type EqualityOp = "==" | "!=";
1172
- /** Ordering only where order exists — a platform string has none. */
1173
- type OrderedOp = EqualityOp | "<" | "<=" | ">" | ">=";
1174
- type TextOp = EqualityOp | "startsWith" | "endsWith" | "contains";
1175
- /** A single-quoted literal. Quoting is the mistake people make most. */
1176
- type Quoted = `'${string}'`;
1177
- type Comparison = `${StringVar} ${TextOp} ${Quoted}` | `${StringVar} in [${string}]` | `${SemverVar} ${OrderedOp} ${Quoted}` | `percentile ${OrderedOp} ${number}` | `server.now ${OrderedOp} ${Quoted}`;
1178
- /**
1179
- * One expression: a comparison, optionally chained with `&&` / `||`, or a
1180
- * parenthesised group.
1181
- *
1182
- * ONE level, and the limit is TypeScript's, not a preference. Unrolling a second
1183
- * level produced `TS2590: union type too complex`, and the failure mode there is
1184
- * the worst possible one: the checker gives up and the type silently degrades to
1185
- * `string`, so NOTHING is checked while the code still compiles. Measured, not
1186
- * assumed — the two-level version accepted `client.app_verison` without
1187
- * complaint.
1188
- *
1189
- * So the FIRST term is fully checked and the rest of a chain is not. That is
1190
- * where the mistakes are: a rule is usually one comparison, and a typo in the
1191
- * first one is the common case. A typo after an `&&` still reaches the server,
1192
- * which parses the real grammar and rejects it.
1193
- *
1194
- * A group's contents go unchecked for the same reason.
1195
- */
1196
- type FlagConditionExpression = Comparison | `${Comparison} && ${string}` | `${Comparison} || ${string}` | `(${string})`;
1197
- interface FlagCondition<V> {
1198
- when: FlagConditionExpression;
1199
- value: V;
1200
- }
1201
- /**
1202
- * The COMPILED shape of a condition — the JSON actually emitted and parsed by
1203
- * the Go apply step.
1204
- *
1205
- * `when` widens back to `string` here on purpose. The narrow type is an
1206
- * authoring aid; once the expression has been trimmed and serialised it is just
1207
- * text on the wire, and keeping the template-literal type would force a cast on
1208
- * every string operation without checking anything the author had not already
1209
- * been checked on.
1210
- */
1211
- interface CompiledFlagCondition<V> {
1212
- when: string;
1213
- value: V;
1214
- }
1215
- /**
1216
- * The compiled, serializable flag definition — the EXACT shape emitted to JSON
1217
- * and consumed by the Go apply step.
1218
- *
1219
- * - `type`: the author-facing type verbatim (`"boolean" | "number" | "string" |
1220
- * "json"`) — the apply step maps it to PalFlags' `value_type`.
1221
- * - `default`: the default value (matches `type`).
1222
- * - `variants`: the allowed-values list for a string flag, or `null` (any).
1223
- * - `description`: the doc string, or `null`.
1224
- */
1225
- interface FlagDef {
1226
- type: FlagType;
1227
- default: FlagValue;
1228
- variants: string[] | null;
1229
- /** Ordered targeting branches, or `null` when the flag is unconditional. */
1230
- conditions: CompiledFlagCondition<FlagValue>[] | null;
1231
- description: string | null;
1232
- }
1233
- /** A flags config definition: the discriminant + a map of flag key →
1234
- * {@link FlagDef}. This is the value `defineFlags` returns and the runtime
1235
- * config extractor serializes. */
1236
- interface FlagsConfig {
1237
- __config: typeof FLAGS_CONFIG_KIND;
1238
- flags: Record<string, FlagDef>;
1239
- }
1240
- /** The author-facing input to `defineFlags`: a `flags` map whose keys are the
1241
- * flag keys and whose values are `flag({...})` builders. */
1242
- interface FlagsInput {
1243
- flags: Record<string, FlagDef>;
363
+ * stack-gen.ts — generate the `palbase-stack.d.ts` text from the names the
364
+ * linked environment's stack holds.
365
+ *
366
+ * The twin of `db/env-gen.ts`: the CLI calls
367
+ * {@link makeStackDts} with the three name sets it read off the stack and
368
+ * writes the result to `palbase-stack.d.ts` at the project root. That file
369
+ * augments the `@palbase/backend/stack` interfaces, so `Secrets.get(...)`, the
370
+ * Flags client and `@Upload({ bucket })` accept the project's real names and
371
+ * nothing else.
372
+ *
373
+ * The generator does NOT reach the network and does NOT decide what a name is:
374
+ * it renders what it is handed. The stack is the authority on which names exist
375
+ * (`GET /v1/management/secrets`, `/flags`, `/storage/buckets`); re-deciding that
376
+ * here would be a second, weaker copy of it.
377
+ *
378
+ * The emitted file ends in `export {};` — same as `makeEnvDts` and
379
+ * `makePurchasesDts`. Without it the `.d.ts` is a global script, and
380
+ * `declare module "…"` there DECLARES an ambient module (shadowing the real one,
381
+ * so every name silently becomes invalid) instead of AUGMENTING it.
382
+ */
383
+ /** The three name sets read off the stack. Order is irrelevant each set is
384
+ * sorted here so the same stack always renders the same bytes and a diff shows
385
+ * what CHANGED rather than how the API happened to order its answer. */
386
+ /** One bucket and the renditions it declares. */
387
+ interface BucketInput {
388
+ name: string;
389
+ variants: readonly string[];
1244
390
  }
1245
- /**
1246
- * Define a single flag. The flag KEY is supplied by the object key in
1247
- * `defineFlags({ flags: { <key>: flag({...}) } })`, so `flag()` takes only the
1248
- * options.
1249
- *
1250
- * Validates eagerly (at config-author time):
1251
- * - `type` is one of `"boolean" | "number" | "string" | "json"`.
1252
- * - `default` matches `type` (e.g. a `number` default with `type: "boolean"`
1253
- * throws; a `"json"` default must be a plain object, not an array or scalar).
1254
- * - `variants` is ONLY allowed for a string flag (variants on any other type
1255
- * throws); each entry is a non-empty string; the `default` must be one of the
1256
- * variants.
1257
- *
1258
- * Returns a normalized {@link FlagDef}: `variants` deduped-or-null,
1259
- * `description` trimmed-or-null.
1260
- */
1261
- declare function flag(opts: FlagOptions): FlagDef;
1262
- /**
1263
- * Define the feature-flag definitions for a project. The flag KEY comes from
1264
- * each object key (authors never repeat the key). Returns the discriminated
1265
- * {@link FlagsConfig} the runtime config extractor serializes and the Go apply
1266
- * step UPSERTS into PalFlags.
1267
- *
1268
- * @example
1269
- * export default defineFlags({
1270
- * flags: { dark_mode: flag({ type: "boolean", default: false }) },
1271
- * });
1272
- */
1273
- declare function defineFlags(input: FlagsInput): FlagsConfig;
1274
-
1275
- /**
1276
- * secrets.ts — the tenant SECRET REQUIREMENT config-as-code DSL.
1277
- *
1278
- * `defineSecrets({ secrets: [secret("NEO4J_PASSWORD")] })` declares WHICH secrets
1279
- * a backend needs to run. It declares the requirement, never the value: the value
1280
- * lives only in the vault, and nothing in this file — or in the repo it is
1281
- * committed to — ever holds one.
1282
- *
1283
- * The gap it closes: today a backend's dependence on a secret is written nowhere
1284
- * except inside the code that reads it (`process.env.NEO4J_PASSWORD`). Deploy a
1285
- * project into an environment where that secret was never set and the build
1286
- * passes, the pod starts, and the failure surfaces as a 500 on the first request
1287
- * that touches it — measured on todoapp, whose `/graph/*` routes returned 500 for
1288
- * exactly this reason. With the requirement declared, it travels WITH the code:
1289
- * `push` compares this list against the environment's vault and refuses the
1290
- * deploy, naming the missing secret, before anything ships.
1291
- *
1292
- * @example
1293
- * // config/secrets.ts
1294
- * import { defineSecrets, secret } from "@palbase/backend";
1295
- * export default defineSecrets({
1296
- * secrets: [
1297
- * secret("NEO4J_PASSWORD", { description: "graph store password" }),
1298
- * secret("SENTRY_DSN", { required: false, description: "crash reporting" }),
1299
- * ],
1300
- * });
1301
- *
1302
- * `required` defaults to TRUE. A declaration exists to be checked, so the safe
1303
- * default is the checked one; `required: false` says the code has a working path
1304
- * when the secret is absent, and downgrades a missing secret to a warning.
1305
- *
1306
- * Emits: { __config: "secrets", secrets: [{ name, required, description }] }
1307
- * sorted by name, so the same declaration always evaluates to the same bytes and
1308
- * a config diff shows what an author changed rather than how they reordered it.
1309
- */
1310
- /** The discriminant written under `__config` so the deploy eval knows the kind. */
1311
- declare const SECRETS_CONFIG_KIND: "secrets";
1312
- /** The author-facing options for a single `secret()`. */
1313
- interface SecretOptions {
391
+ interface StackNames {
392
+ /** Secret names from the project's vault. */
393
+ secrets: readonly string[];
394
+ /** Flag keys from the project's flag store. */
395
+ flags: readonly string[];
1314
396
  /**
1315
- * Whether the deploy must REFUSE when this secret is absent from the target
1316
- * environment's vault. Omitted ⇒ `true`.
397
+ * Buckets from the project's storage a bare name, or a name with the
398
+ * renditions it declares.
399
+ *
400
+ * A bucket is not only a NAME: `Storage.buckets.docs.getPublicUrl(p, {
401
+ * variant })` refuses a rendition the bucket does not have, so the variant
402
+ * union has to travel with it. The bare-string form is shorthand for "no
403
+ * variants", which renders `never` and accepts none.
1317
404
  */
1318
- required?: boolean;
1319
- /** What the secret is for — shown when the deploy reports it missing. */
1320
- description?: string;
405
+ buckets: readonly (string | BucketInput)[];
1321
406
  }
1322
407
  /**
1323
- * The compiled, serializable secret declaration — the EXACT shape emitted to
1324
- * JSON and consumed by the Go deploy step.
408
+ * Render the project's `palbase-stack.d.ts`.
1325
409
  *
1326
- * - `name`: the environment-variable name the code reads, verbatim.
1327
- * - `required`: always present (defaulted to `true`).
1328
- * - `description`: the doc string, or `null`.
410
+ * An empty set renders an empty interface, which is a MEANINGFUL value: the
411
+ * stack holds no such names, so the corresponding union is `never` and every
412
+ * call spelling one fails to compile. It is not the same as "the file was never
413
+ * generated" only in that the file exists — both states refuse every name, and
414
+ * both are correct.
1329
415
  */
1330
- interface SecretDef {
1331
- name: string;
1332
- required: boolean;
1333
- description: string | null;
1334
- }
1335
- /** A secrets config definition: the discriminant + the name-sorted declarations.
1336
- * This is the value `defineSecrets` returns and the build's config extractor
1337
- * serializes into `.palbase/config.json`. */
1338
- interface SecretsConfig {
1339
- __config: typeof SECRETS_CONFIG_KIND;
1340
- secrets: SecretDef[];
1341
- }
1342
- /** The author-facing input to `defineSecrets`: a list of `secret(...)` builders. */
1343
- interface SecretsInput {
1344
- secrets: SecretDef[];
1345
- }
1346
- /**
1347
- * Declare a single required (or optional) secret. Unlike `flag()` / `bucket()`,
1348
- * the NAME is an argument rather than an object key: a secret's name is not a
1349
- * label an author chooses, it is the exact env-var identifier the code already
1350
- * reads, so it belongs where it can be validated as one.
1351
- *
1352
- * Validates eagerly (at config-author time):
1353
- * - `name` is `[A-Z][A-Z0-9_]*` — anything else throws.
1354
- * - `required` is a boolean; `description` is a string.
1355
- *
1356
- * Returns a normalized {@link SecretDef}: `required` defaulted to `true`,
1357
- * `description` trimmed-or-null.
1358
- */
1359
- declare function secret(name: string, opts?: SecretOptions): SecretDef;
1360
- /**
1361
- * Define the secrets a backend needs. Returns the discriminated
1362
- * {@link SecretsConfig} the build's config extractor serializes and the deploy
1363
- * checks against the target environment's vault BEFORE shipping anything.
1364
- *
1365
- * A name declared twice throws: two declarations of one name disagree about
1366
- * something (usually `required`), and silently keeping either one is how a
1367
- * secret stops being checked without anybody editing the check.
1368
- */
1369
- declare function defineSecrets(input: SecretsInput): SecretsConfig;
416
+ declare function makeStackDts(names: StackNames): string;
1370
417
 
1371
418
  /** Options accepted by `@Controller`. */
1372
419
  interface ControllerOptions {
@@ -1712,96 +759,4 @@ interface ResolvedJob {
1712
759
  declare function Job(options: JobOptions): <T extends abstract new (...args: never[]) => object>(ctor: T) => T;
1713
760
  declare function getJobConfig(ctor: object): ResolvedJob;
1714
761
 
1715
- /**
1716
- * resource.ts — external connections as lifecycle-managed classes.
1717
- *
1718
- * A `Resource` subclass models one external connection (a pooled datastore, a
1719
- * stateless API client, or a per-user factory). The framework discovers each
1720
- * instance, calls `init(env)` ONCE at boot with the declared secret subset, and
1721
- * `shutdown()` (reverse order) on SIGTERM. On top of that lifecycle the author
1722
- * exposes their own clean facade methods.
1723
- *
1724
- * // resources/neo4j.ts
1725
- * import { Resource } from "@palbase/backend";
1726
- * import neo4j, { type Driver, type Session } from "neo4j-driver";
1727
- *
1728
- * export class Neo4jResource extends Resource {
1729
- * static secrets = ["NEO4J_URL", "NEO4J_USER", "NEO4J_PASSWORD"] as const;
1730
- * private driver!: Driver;
1731
- * async init(env: { NEO4J_URL: string; NEO4J_USER: string; NEO4J_PASSWORD: string }) {
1732
- * this.driver = neo4j.driver(env.NEO4J_URL, neo4j.auth.basic(env.NEO4J_USER, env.NEO4J_PASSWORD));
1733
- * }
1734
- * async shutdown() { await this.driver.close(); }
1735
- * session(): Session { return this.driver.session(); }
1736
- * }
1737
- * export const neo4j = new Neo4jResource(); // framework finds + manages it
1738
- *
1739
- * # Boot scope (NOT request-ALS)
1740
- *
1741
- * Resources are instantiated once at process boot. This is deliberately NOT the
1742
- * per-request {@link AsyncLocalStorage} scope used for `Database`/`Cache`/… — a
1743
- * connection pool must outlive a single request. The runtime discovers
1744
- * resources from the project's `resources/` directory, registers each via
1745
- * {@link __registerResource}, then calls {@link __runResourceBoot} before
1746
- * serving and {@link __shutdownResources} on SIGTERM. Discovery is a runtime
1747
- * concern; the SDK provides the base class + registry + boot/shutdown hooks.
1748
- */
1749
- /** Map a declared `secrets` tuple to the `init(env)` argument type — a record
1750
- * over the secret names, each `string`. An empty tuple maps to an empty
1751
- * record. */
1752
- type ResourceEnv<Secrets extends readonly string[]> = {
1753
- [K in Secrets[number]]: string;
1754
- };
1755
- /**
1756
- * Base class for external connections.
1757
- *
1758
- * - `static secrets` — OPTIONAL readonly tuple of env-var names this resource
1759
- * needs. Drives (a) the `env` type passed to `init` and (b) the
1760
- * missing-secret check at boot (deploy fails naming the absent secret).
1761
- * - `init(env)` — called ONCE at boot with the declared secret subset. May be
1762
- * sync (`void`) or async (`Promise<void>`).
1763
- * - `shutdown()` — OPTIONAL drain hook, called on SIGTERM in reverse boot
1764
- * order.
1765
- *
1766
- * @example
1767
- * import { Resource } from "@palbase/backend";
1768
- * import { Client } from "@googlemaps/google-maps-services-js";
1769
- *
1770
- * export class GoogleResource extends Resource {
1771
- * static secrets = ["GOOGLE_MAPS_KEY"] as const;
1772
- * private client = new Client();
1773
- * private key = "";
1774
- * init(env: { GOOGLE_MAPS_KEY: string }) { this.key = env.GOOGLE_MAPS_KEY; }
1775
- * }
1776
- * export const google = new GoogleResource();
1777
- */
1778
- declare abstract class Resource {
1779
- /** The env-var names this resource needs. Optional; omit for none. */
1780
- static secrets?: readonly string[];
1781
- /** Set up the connection from the declared secrets. Called once at boot. */
1782
- abstract init(env: Record<string, string>): void | Promise<void>;
1783
- /** Drain/close the connection on SIGTERM. Optional. */
1784
- shutdown?(): void | Promise<void>;
1785
- }
1786
- /**
1787
- * Register a resource instance with the boot registry. The runtime calls this
1788
- * for each instance discovered under the project's `resources/` directory.
1789
- * NOT part of the public author-facing API (prefixed `__`).
1790
- */
1791
- declare function __registerResource(resource: Resource): void;
1792
- /**
1793
- * Boot every registered resource that has not yet been booted: resolve its
1794
- * declared secret subset from `envMap`, then await its `init(env)`. Idempotent
1795
- * — an already-booted resource is skipped. Throws (failing deploy/boot) when a
1796
- * declared secret is absent, naming the missing secret. NOT part of the public
1797
- * author-facing API.
1798
- */
1799
- declare function __runResourceBoot(envMap: Record<string, string>): Promise<void>;
1800
- /**
1801
- * Shut down every booted resource in REVERSE registration order, awaiting each
1802
- * `shutdown()` (a no-op when undefined). Clears the registry afterwards so a
1803
- * second call is a no-op. NOT part of the public author-facing API.
1804
- */
1805
- declare function __shutdownResources(): Promise<void>;
1806
-
1807
- export { type AcsOptions, type ApnsOptions, type AuthHookEvent, Body, type ChannelAuthorizeCtx, type ChannelEntry, type ChannelGrant, type ChannelsDef, type ChannelsInput, Client, type ColumnJSON, Controller, type ControllerOptions, DEFAULT_TEMPLATE_LOCALE, type DefinedError, type DefinedErrorWithData, Delete, Deny, type DocumentHookEvent, 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, Hook, type HookFn, 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, 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 ResolvedHookClass, 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 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, Webhook, type WebhookEventHandler, type WebhookMeta, type WebhookOptions, type WebhookProvider, type WebhookRequest, __registerResource, __resetRegisteredControllers, __runResourceBoot, __shutdownResources, buildModuleClients, buildProvider, defineChannels, defineEgress, defineError, defineFlags, defineNotifications, defineSecrets, defineTestUsers, entitlementFor, extractVariables, flag, getErrorRegistry, getHookConfig, getJobConfig, getRegisteredControllers, getWebhookConfig, makeEnvDts, makeHttpClient, makePurchasesDts, ownerOnly, publicChannel, reservedSecretKey, secret, spendFor, testUser, toSchemaJSON };
762
+ export { type AuthHookEvent, Body, type ChannelAuthorizeCtx, type ChannelEntry, type ChannelGrant, type ChannelsDef, type ChannelsInput, Client, type ColumnJSON, Controller, type ControllerOptions, type DefinedError, type DefinedErrorWithData, Delete, Deny, type DocumentHookEvent, type FileDeletedEvent, type FileUploadedEvent, Get, Headers, Hook, type HookFn, type HookMeta, HttpError, Job, type JobMeta, type JobOptions, type ModuleClientsConfig, type ModuleTransport, On, OptionalUser, PalbaseModuleError, PalbaseResult, Param, Patch, type PolicyJSON, Post, Put, Query, QueryParams, type RegisteredError, Req, RequestId, type RequestOptions, type ResolvedHookClass, type ResolvedJob, type ResolvedWebhook, RouteOptions, SchemaDef, type SchemaJSON, type SignatureSpec, type StackNames, type TableJSON, TraceId, type TransportConfig, User, Webhook, type WebhookEventHandler, type WebhookMeta, type WebhookOptions, type WebhookProvider, type WebhookRequest, __resetRegisteredControllers, buildModuleClients, defineChannels, defineError, getErrorRegistry, getHookConfig, getJobConfig, getRegisteredControllers, getWebhookConfig, makeEnvDts, makeHttpClient, makeStackDts, ownerOnly, publicChannel, toSchemaJSON };