@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
@@ -1,161 +1,12 @@
1
- import { A as AuthSpec, R as RateLimitConfig } from './endpoint-BVT6jcVW.js';
1
+ import { A as AuthSpec, R as RateLimitConfig } from './endpoint-CVWXh6oG.js';
2
2
  import { ZodTypeAny } from 'zod';
3
-
4
- /**
5
- * storage.ts — the storage-buckets config-as-code DSL.
6
- *
7
- * `defineStorage({ buckets })` is the first MODULE config-as-code surface (the
8
- * sibling of `db/schema.ts`'s `defineSchema`). A `config/storage.ts` file
9
- * default-exports a `defineStorage(...)` result; on deploy the br-pod evaluates
10
- * it to JSON and reconciles the declared buckets against the tenant's live
11
- * buckets via the Storage admin API (create missing, update changed; never
12
- * auto-delete — dropping a bucket is destructive and warned only).
13
- *
14
- * Buckets are DECLARATIVE: name + access + size/mime limits. The FILES inside a
15
- * bucket are runtime state (uploaded via the SDK), never in git.
16
- *
17
- * @example
18
- * import { defineStorage, bucket } from "@palbase/backend";
19
- *
20
- * export default defineStorage({
21
- * buckets: {
22
- * avatars: bucket({
23
- * public: true,
24
- * fileSizeLimit: "5MB",
25
- * allowedMimeTypes: ["image/png", "image/jpeg", "image/webp"],
26
- * }),
27
- * invoices: bucket({
28
- * public: false,
29
- * fileSizeLimit: "20MB",
30
- * allowedMimeTypes: ["application/pdf"],
31
- * }),
32
- * },
33
- * });
34
- *
35
- * The returned value is the EXACT JSON shape the runtime's generic config
36
- * extractor (`config_extract.js`) emits and the Go apply step parses:
37
- * { __config: "storage", buckets: { avatars: { public, fileSizeLimit, allowedMimeTypes }, ... } }
38
- * `fileSizeLimit` is always normalized to a number of bytes (or null when
39
- * omitted, meaning "no limit"); the apply step maps it to the Storage API's
40
- * `file_size_limit` field. `allowedMimeTypes` is the allowlist or null (any).
41
- */
42
- /** The discriminant written under `__config` so the eval/apply can tell which
43
- * module config a `config/*.ts` file describes. Storage is `"storage"`. */
44
- declare const STORAGE_CONFIG_KIND: "storage";
45
- /**
46
- * The author-facing options for a single bucket.
47
- *
48
- * - `public`: when true the bucket is served without a signed URL. Defaults to
49
- * `false` (private — signed URLs only).
50
- * - `fileSizeLimit`: the per-object upload cap. Accepts a human string
51
- * (`"5MB"`, `"20MB"`, `"1GB"`) or a bare number of BYTES. Omit for no limit.
52
- * - `allowedMimeTypes`: an allowlist of MIME types accepted on upload. Omit to
53
- * allow any type. Each entry must look like `type/subtype` (e.g. `image/png`,
54
- * `application/pdf`, or a wildcard `image/*`).
55
- */
56
- interface BucketOptions {
57
- public?: boolean;
58
- fileSizeLimit?: string | number;
59
- allowedMimeTypes?: string[];
60
- variants?: Record<string, ImageVariant>;
61
- }
62
- /**
63
- * One named rendition of an uploaded image — the same three knobs
64
- * `getPublicUrl(path, ...)` takes, given a name so the size lives with the
65
- * bucket instead of being retyped at every call site.
66
- *
67
- * At least one of `width` / `height` must be set (a variant that resizes
68
- * nothing and converts nothing is a typo, not a variant).
69
- */
70
- interface ImageVariant {
71
- width?: number;
72
- height?: number;
73
- /**
74
- * How the source is made to meet the box. Defaults to `"contain"`.
75
- *
76
- * - `cover` fills the box exactly and crops the overflow from the centre —
77
- * what a square avatar or a grid cell needs.
78
- * - `contain` fits the whole picture inside the box, cropping nothing. One
79
- * side may come out smaller than declared.
80
- * - `inside` is `contain` that never ENLARGES. Blowing a 100px image up to
81
- * 1000px produces a blurry picture that is bigger on the wire.
82
- */
83
- fit?: "cover" | "contain" | "inside";
84
- format?: "webp" | "avif" | "jpeg" | "png";
85
- /** Encoder quality, 1–100. Defaults to 85. JPEG only. */
86
- quality?: number;
87
- }
88
- /**
89
- * The compiled, serializable bucket definition — the EXACT shape emitted to
90
- * JSON and consumed by the Go apply step.
91
- *
92
- * - `public`: always present (defaulted to `false`).
93
- * - `fileSizeLimit`: bytes as a number, or `null` for "no limit".
94
- * - `allowedMimeTypes`: the MIME allowlist, or `null` for "any type".
95
- */
96
- interface BucketDef {
97
- public: boolean;
98
- fileSizeLimit: number | null;
99
- allowedMimeTypes: string[] | null;
100
- /** Named renditions, or `null` when none are declared. Rendered AT UPLOAD and
101
- * stored as objects of their own, behind immutable URLs. */
102
- variants: Record<string, ImageVariant> | null;
103
- }
104
- /** A storage config definition: the discriminant + a map of bucket name →
105
- * {@link BucketDef}. This is the value `defineStorage` returns and the runtime
106
- * config extractor serializes. */
107
- interface StorageConfig {
108
- __config: typeof STORAGE_CONFIG_KIND;
109
- buckets: Record<string, BucketDef>;
110
- }
111
- /** The author-facing input to `defineStorage`: a `buckets` map whose keys are
112
- * the bucket names and whose values are `bucket({...})` builders. */
113
- interface StorageInput {
114
- buckets: Record<string, BucketDef>;
115
- }
116
- /**
117
- * Parse a `fileSizeLimit` into a number of bytes.
118
- *
119
- * - A bare number is taken as bytes verbatim (must be a non-negative integer).
120
- * - A string is `<number><unit>` (e.g. `"5MB"`, `"1.5GB"`, `"500kb"`); the unit
121
- * is case-insensitive and optional (a bare numeric string = bytes). Binary
122
- * units (1 MB = 1024 squared bytes) to match the Storage module's parser.
123
- *
124
- * Throws on a negative value, a non-finite number, or an unrecognized unit so a
125
- * bad limit fails at config-author time, not silently at deploy.
126
- */
127
- declare function parseFileSizeLimit(input: string | number): number;
128
- /**
129
- * Define a single bucket. The bucket NAME is supplied by the key in
130
- * `defineStorage({ buckets: { <name>: bucket({...}) } })`, so `bucket()` takes
131
- * only the options.
132
- *
133
- * Validates eagerly (at config-author time):
134
- * - `fileSizeLimit` parses to a valid non-negative byte count.
135
- * - each `allowedMimeTypes` entry is a `type/subtype` MIME string.
136
- *
137
- * Returns a normalized {@link BucketDef}: `public` defaulted to `false`,
138
- * `fileSizeLimit` as bytes-or-null, `allowedMimeTypes` deduped-or-null.
139
- */
140
- declare function bucket(opts?: BucketOptions): BucketDef;
141
- /**
142
- * Define the storage config for a project. The bucket NAME comes from each
143
- * object key (authors never repeat the name). Returns the discriminated
144
- * {@link StorageConfig} the runtime config extractor serializes and the Go
145
- * apply step reconciles.
146
- *
147
- * @example
148
- * export default defineStorage({
149
- * buckets: { avatars: bucket({ public: true, fileSizeLimit: "5MB" }) },
150
- * });
151
- */
152
- declare function defineStorage(input: StorageInput): StorageConfig;
3
+ import { PalbaseBucketName } from './stack.js';
153
4
 
154
5
  /** A legacy method decorator (`experimentalDecorators`): `(prototype, name,
155
6
  * descriptor)`. */
156
- type MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
7
+ type MethodDecorator$1 = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
157
8
  /** A legacy parameter decorator: `(prototype, name, paramIndex)`. */
158
- type ParameterDecorator = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
9
+ type ParameterDecorator$1 = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
159
10
  /**
160
11
  * Direct-storage upload settings for an `@Upload` route. The br-pod validates an
161
12
  * authorize request against these (size/type), then mints a signed upload URL
@@ -164,7 +15,13 @@ type ParameterDecorator = (target: object, propertyKey: string | symbol, paramet
164
15
  */
165
16
  interface UploadConfig {
166
17
  /**
167
- * Target bucket NAME. MUST exist in `config/storage.ts` `defineStorage(...)`.
18
+ * Target bucket NAME one the STACK holds.
19
+ *
20
+ * The union comes from the generated `palbase-stack.d.ts`, so a bucket the
21
+ * stack does not carry is a compile error. It used to say "MUST exist in
22
+ * `config/storage.ts` defineStorage(...)", and that invariant was carried by
23
+ * this sentence plus a cross-check nothing called; it is carried by the type
24
+ * now.
168
25
  *
169
26
  * The bucket is the SINGLE SOURCE OF TRUTH for the size limit + MIME allowlist:
170
27
  * `bucket({ fileSizeLimit, allowedMimeTypes })`. Storage enforces those at the
@@ -174,7 +31,7 @@ interface UploadConfig {
174
31
  * enforce (a real bypass: declare 10 bytes at authorize, then PUT up to the
175
32
  * bucket ceiling straight at the signed URL). One bucket, one limit, enforced.
176
33
  */
177
- bucket: string;
34
+ bucket: PalbaseBucketName;
178
35
  /**
179
36
  * SERVER-side object key template. The client NEVER chooses the path. Tokens:
180
37
  * `{userId}` (authenticated user id), `{uploadId}` (server-minted), and
@@ -191,10 +48,10 @@ interface UploadConfig {
191
48
  * @example
192
49
  * @Upload("/", { bucket: "docs", pathTemplate: "{userId}/{uploadId}-{filename}" })
193
50
  * async upload(@UploadedObject() obj: UploadedObject, @User() user): Promise<DocResult> { ... }
194
- * // The size limit + MIME allowlist come from the "docs" bucket in
195
- * // config/storage.ts — storage enforces them at the PUT.
51
+ * // The size limit + MIME allowlist come from the "docs" bucket ON THE STACK
52
+ * // — storage enforces them at the PUT.
196
53
  */
197
- declare function Upload(subpath: string, config: UploadConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator;
54
+ declare function Upload(subpath: string, config: UploadConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator$1;
198
55
  /**
199
56
  * The uploaded object, injected into an `@Upload` method body by
200
57
  * `@UploadedObject()` once storage confirms the upload. Bytes are NOT present
@@ -249,20 +106,94 @@ interface UploadedObject {
249
106
  * Co-located with the {@link UploadedObject} TYPE so a single exported name
250
107
  * `UploadedObject` carries BOTH the decorator value and the type annotation.
251
108
  */
252
- declare function UploadedObject(): ParameterDecorator;
109
+ declare function UploadedObject(): ParameterDecorator$1;
110
+
111
+ /** A legacy method decorator (`experimentalDecorators`): `(prototype, name,
112
+ * descriptor)`. */
113
+ type MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
114
+ /** A legacy parameter decorator: `(prototype, name, paramIndex)`. */
115
+ type ParameterDecorator = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
116
+ /**
117
+ * Settings for an `@Sse` route.
118
+ *
119
+ * EMPTY in v1, and deliberately so: the pipeline keys off this object's
120
+ * PRESENCE, not its contents (see the file header). Declaring the type now means
121
+ * a later setting arrives as a field on an existing marker rather than as a
122
+ * second marker nothing downstream reads.
123
+ */
124
+ interface SseConfig {
125
+ /**
126
+ * The shape of ONE frame — what a single `out.write(value)` carries.
127
+ *
128
+ * REQUIRED, not optional, and that is the whole point. An `@Sse` handler
129
+ * returns `Promise<void>`, so the codegen-injected `recordReturn` has no type
130
+ * to record and nothing downstream can infer what a frame is. A route without
131
+ * this declares a stream whose element type is unknown, and every generated
132
+ * client from that contract is opaque — which is precisely the failure
133
+ * `x-palbase-sse` exists to prevent. Making it optional would have left that
134
+ * failure one forgotten field away.
135
+ *
136
+ * Measured live on a real pushed stack (2026-08-29): the contract gate refused
137
+ * the deploy with "2 untyped response(s) — every generated client from it is
138
+ * opaque", naming both routes that omitted it.
139
+ */
140
+ frame: ZodTypeAny;
141
+ }
142
+ /**
143
+ * The writer injected by `@SseOut()`. Each `write` emits ONE SSE `data:` frame
144
+ * carrying `value` JSON-encoded.
145
+ *
146
+ * The FIRST `write` is load-bearing beyond its frame: it settles the request's
147
+ * database transaction. A streaming response may run for minutes, and the
148
+ * handler runs inside the request's transaction — holding one open for the life
149
+ * of a stream exhausts the connection pool, a failure invisible to a unit test
150
+ * that only dies under load. So the first frame is the point at which the
151
+ * request phase is declared over, and database access after it is refused by
152
+ * name rather than silently run against a settled transaction.
153
+ *
154
+ * The practical rule for a handler: do the database work BEFORE the first write.
155
+ */
156
+ interface SseWriter {
157
+ write(value: unknown): void;
158
+ }
159
+ /**
160
+ * `@Sse(subpath, config)` — declare a streaming route.
161
+ *
162
+ * @example
163
+ * @Sse("/chat", { frame: ChatFrame })
164
+ * async chat(@Body() b: ChatInput, @SseOut() out: SseWriter, @Signal() signal: AbortSignal) {
165
+ * const stream = await openai.chat.completions.create(
166
+ * { model: "gpt-5.6", messages: b.messages, stream: true },
167
+ * { signal },
168
+ * );
169
+ * for await (const chunk of stream) out.write(chunk);
170
+ * }
171
+ * // The client disconnects → `signal` aborts → the provider stops being pulled.
172
+ * // The signal is a plain AbortSignal, so it goes wherever the provider takes
173
+ * // one: `{ signal }` for the OpenAI and Anthropic SDKs, `abortSignal:` for the
174
+ * // Vercel AI SDK, `fetch(url, { signal })` for a raw call.
175
+ */
176
+ declare function Sse(subpath: string | undefined, config: SseConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator;
177
+ /**
178
+ * `@SseOut()` — inject the frame writer (`: SseWriter`) into an `@Sse` method
179
+ * body. Only meaningful on an `@Sse` route.
180
+ */
181
+ declare function SseOut(): ParameterDecorator;
253
182
  /**
254
- * Cross-check one `@Upload` route's uploadConfig against the project's
255
- * {@link StorageConfig}: the named bucket MUST exist in `defineStorage(...)`.
256
- * Throws a precise error so a typo fails at deploy, not at the first user upload.
183
+ * `@Signal()` — inject the request's `AbortSignal`, which enters the aborted
184
+ * state when the client disconnects.
257
185
  *
258
- * That is the ONLY cross-check: the size limit + MIME allowlist live on the
259
- * bucket (`bucket({ fileSizeLimit, allowedMimeTypes })`) and are enforced by
260
- * storage at the PUT. `@Upload` carries no `maxSize`/`allowedTypes` of its own,
261
- * so there is nothing to compare — one bucket, one limit, no drift.
186
+ * NOT derivable from `@Req()`: `PBRequest` carries only request-scoped data
187
+ * the typed input, route/query params, headers, the authenticated user, calling
188
+ * client metadata, trace ids, and the declared error throwers and no signal
189
+ * (endpoint.ts:358-363).
262
190
  *
263
- * `routeLabel` is used only for error messages (e.g. `docs.upload`).
191
+ * Measured 2026-08-29 on Bun: an infinite producer guarded by this signal
192
+ * stopped four frames after the client was killed, and the handler's `finally`
193
+ * ran. It is the same mechanism a NestJS handler reaches through
194
+ * `req.on("close")`.
264
195
  */
265
- declare function validateUploadAgainstStorage(uploadConfig: UploadConfig, storage: StorageConfig, routeLabel: string): void;
196
+ declare function Signal(): ParameterDecorator;
266
197
 
267
198
  /** The HTTP verbs a route may declare, upper-cased (the runtime router +
268
199
  * OpenAPI lower-case on their own). */
@@ -279,11 +210,18 @@ interface RouteOptions {
279
210
  * openapi → codegen). The bytes go client→storage directly; the method body
280
211
  * runs as the completion handler. See {@link UploadConfig} (decorators/upload.ts). */
281
212
  uploadConfig?: UploadConfig;
213
+ /** Streaming config — present ONLY on `@Sse` routes (the `@Get`/`@Post`/…
214
+ * decorators never set it). Its presence is what MARKS a route as a streaming
215
+ * route through the whole pipeline (registry → flatten → openapi → codegen),
216
+ * exactly as `uploadConfig` does for uploads — never a special HTTP verb. An
217
+ * `@Sse` route registers POST like any input-bearing route, so the verb cannot
218
+ * carry the distinction. See {@link SseConfig} (decorators/sse.ts). */
219
+ sseConfig?: SseConfig;
282
220
  }
283
221
  /** The kind of value a parameter decorator injects. Drives both dispatch
284
222
  * (which request slice to inject) and codegen (which OpenAPI parameter source a
285
223
  * schema-bearing kind maps to). */
286
- type ParamKind = "body" | "query" | "param" | "headers" | "user" | "optionalUser" | "client" | "requestId" | "traceId" | "req" | "uploadedObject";
224
+ type ParamKind = "body" | "query" | "param" | "headers" | "user" | "optionalUser" | "client" | "requestId" | "traceId" | "req" | "uploadedObject" | "sseOut" | "signal";
287
225
  /** One parameter decorator's recorded metadata. `index` is the parameter
288
226
  * position esbuild/tsc preserve; `schema` is present for the schema-bearing
289
227
  * kinds (`body`/`query`/`headers`); `name` is the path-param name for `param`. */
@@ -335,4 +273,4 @@ declare function recordThrows(target: object, fnName: string, throws: ThrowDescr
335
273
  */
336
274
  declare function getRoutes(ctor: object): RouteMeta[];
337
275
 
338
- export { type BucketDef as B, type HttpMethodUpper as H, type ImageVariant as I, type ParamMeta as P, type RouteMeta as R, STORAGE_CONFIG_KIND as S, type ThrowDescriptor as T, type UploadConfig as U, type RouteOptions as a, type BucketOptions as b, type ParamKind as c, type StorageConfig as d, type StorageInput as e, Upload as f, UploadedObject as g, bucket as h, defineStorage as i, getRoutes as j, parseFileSizeLimit as p, recordThrows as r, validateUploadAgainstStorage as v };
276
+ export { type HttpMethodUpper as H, type ParamMeta as P, type RouteMeta as R, type SseConfig as S, type ThrowDescriptor as T, type UploadConfig as U, type RouteOptions as a, type ParamKind as b, Signal as c, Sse as d, SseOut as e, type SseWriter as f, Upload as g, UploadedObject as h, getRoutes as i, recordThrows as r };
@@ -1,161 +1,12 @@
1
- import { A as AuthSpec, R as RateLimitConfig } from './endpoint-BVT6jcVW.cjs';
1
+ import { A as AuthSpec, R as RateLimitConfig } from './endpoint-c9h5jriX.cjs';
2
2
  import { ZodTypeAny } from 'zod';
3
-
4
- /**
5
- * storage.ts — the storage-buckets config-as-code DSL.
6
- *
7
- * `defineStorage({ buckets })` is the first MODULE config-as-code surface (the
8
- * sibling of `db/schema.ts`'s `defineSchema`). A `config/storage.ts` file
9
- * default-exports a `defineStorage(...)` result; on deploy the br-pod evaluates
10
- * it to JSON and reconciles the declared buckets against the tenant's live
11
- * buckets via the Storage admin API (create missing, update changed; never
12
- * auto-delete — dropping a bucket is destructive and warned only).
13
- *
14
- * Buckets are DECLARATIVE: name + access + size/mime limits. The FILES inside a
15
- * bucket are runtime state (uploaded via the SDK), never in git.
16
- *
17
- * @example
18
- * import { defineStorage, bucket } from "@palbase/backend";
19
- *
20
- * export default defineStorage({
21
- * buckets: {
22
- * avatars: bucket({
23
- * public: true,
24
- * fileSizeLimit: "5MB",
25
- * allowedMimeTypes: ["image/png", "image/jpeg", "image/webp"],
26
- * }),
27
- * invoices: bucket({
28
- * public: false,
29
- * fileSizeLimit: "20MB",
30
- * allowedMimeTypes: ["application/pdf"],
31
- * }),
32
- * },
33
- * });
34
- *
35
- * The returned value is the EXACT JSON shape the runtime's generic config
36
- * extractor (`config_extract.js`) emits and the Go apply step parses:
37
- * { __config: "storage", buckets: { avatars: { public, fileSizeLimit, allowedMimeTypes }, ... } }
38
- * `fileSizeLimit` is always normalized to a number of bytes (or null when
39
- * omitted, meaning "no limit"); the apply step maps it to the Storage API's
40
- * `file_size_limit` field. `allowedMimeTypes` is the allowlist or null (any).
41
- */
42
- /** The discriminant written under `__config` so the eval/apply can tell which
43
- * module config a `config/*.ts` file describes. Storage is `"storage"`. */
44
- declare const STORAGE_CONFIG_KIND: "storage";
45
- /**
46
- * The author-facing options for a single bucket.
47
- *
48
- * - `public`: when true the bucket is served without a signed URL. Defaults to
49
- * `false` (private — signed URLs only).
50
- * - `fileSizeLimit`: the per-object upload cap. Accepts a human string
51
- * (`"5MB"`, `"20MB"`, `"1GB"`) or a bare number of BYTES. Omit for no limit.
52
- * - `allowedMimeTypes`: an allowlist of MIME types accepted on upload. Omit to
53
- * allow any type. Each entry must look like `type/subtype` (e.g. `image/png`,
54
- * `application/pdf`, or a wildcard `image/*`).
55
- */
56
- interface BucketOptions {
57
- public?: boolean;
58
- fileSizeLimit?: string | number;
59
- allowedMimeTypes?: string[];
60
- variants?: Record<string, ImageVariant>;
61
- }
62
- /**
63
- * One named rendition of an uploaded image — the same three knobs
64
- * `getPublicUrl(path, ...)` takes, given a name so the size lives with the
65
- * bucket instead of being retyped at every call site.
66
- *
67
- * At least one of `width` / `height` must be set (a variant that resizes
68
- * nothing and converts nothing is a typo, not a variant).
69
- */
70
- interface ImageVariant {
71
- width?: number;
72
- height?: number;
73
- /**
74
- * How the source is made to meet the box. Defaults to `"contain"`.
75
- *
76
- * - `cover` fills the box exactly and crops the overflow from the centre —
77
- * what a square avatar or a grid cell needs.
78
- * - `contain` fits the whole picture inside the box, cropping nothing. One
79
- * side may come out smaller than declared.
80
- * - `inside` is `contain` that never ENLARGES. Blowing a 100px image up to
81
- * 1000px produces a blurry picture that is bigger on the wire.
82
- */
83
- fit?: "cover" | "contain" | "inside";
84
- format?: "webp" | "avif" | "jpeg" | "png";
85
- /** Encoder quality, 1–100. Defaults to 85. JPEG only. */
86
- quality?: number;
87
- }
88
- /**
89
- * The compiled, serializable bucket definition — the EXACT shape emitted to
90
- * JSON and consumed by the Go apply step.
91
- *
92
- * - `public`: always present (defaulted to `false`).
93
- * - `fileSizeLimit`: bytes as a number, or `null` for "no limit".
94
- * - `allowedMimeTypes`: the MIME allowlist, or `null` for "any type".
95
- */
96
- interface BucketDef {
97
- public: boolean;
98
- fileSizeLimit: number | null;
99
- allowedMimeTypes: string[] | null;
100
- /** Named renditions, or `null` when none are declared. Rendered AT UPLOAD and
101
- * stored as objects of their own, behind immutable URLs. */
102
- variants: Record<string, ImageVariant> | null;
103
- }
104
- /** A storage config definition: the discriminant + a map of bucket name →
105
- * {@link BucketDef}. This is the value `defineStorage` returns and the runtime
106
- * config extractor serializes. */
107
- interface StorageConfig {
108
- __config: typeof STORAGE_CONFIG_KIND;
109
- buckets: Record<string, BucketDef>;
110
- }
111
- /** The author-facing input to `defineStorage`: a `buckets` map whose keys are
112
- * the bucket names and whose values are `bucket({...})` builders. */
113
- interface StorageInput {
114
- buckets: Record<string, BucketDef>;
115
- }
116
- /**
117
- * Parse a `fileSizeLimit` into a number of bytes.
118
- *
119
- * - A bare number is taken as bytes verbatim (must be a non-negative integer).
120
- * - A string is `<number><unit>` (e.g. `"5MB"`, `"1.5GB"`, `"500kb"`); the unit
121
- * is case-insensitive and optional (a bare numeric string = bytes). Binary
122
- * units (1 MB = 1024 squared bytes) to match the Storage module's parser.
123
- *
124
- * Throws on a negative value, a non-finite number, or an unrecognized unit so a
125
- * bad limit fails at config-author time, not silently at deploy.
126
- */
127
- declare function parseFileSizeLimit(input: string | number): number;
128
- /**
129
- * Define a single bucket. The bucket NAME is supplied by the key in
130
- * `defineStorage({ buckets: { <name>: bucket({...}) } })`, so `bucket()` takes
131
- * only the options.
132
- *
133
- * Validates eagerly (at config-author time):
134
- * - `fileSizeLimit` parses to a valid non-negative byte count.
135
- * - each `allowedMimeTypes` entry is a `type/subtype` MIME string.
136
- *
137
- * Returns a normalized {@link BucketDef}: `public` defaulted to `false`,
138
- * `fileSizeLimit` as bytes-or-null, `allowedMimeTypes` deduped-or-null.
139
- */
140
- declare function bucket(opts?: BucketOptions): BucketDef;
141
- /**
142
- * Define the storage config for a project. The bucket NAME comes from each
143
- * object key (authors never repeat the name). Returns the discriminated
144
- * {@link StorageConfig} the runtime config extractor serializes and the Go
145
- * apply step reconciles.
146
- *
147
- * @example
148
- * export default defineStorage({
149
- * buckets: { avatars: bucket({ public: true, fileSizeLimit: "5MB" }) },
150
- * });
151
- */
152
- declare function defineStorage(input: StorageInput): StorageConfig;
3
+ import { PalbaseBucketName } from './stack.cjs';
153
4
 
154
5
  /** A legacy method decorator (`experimentalDecorators`): `(prototype, name,
155
6
  * descriptor)`. */
156
- type MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
7
+ type MethodDecorator$1 = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
157
8
  /** A legacy parameter decorator: `(prototype, name, paramIndex)`. */
158
- type ParameterDecorator = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
9
+ type ParameterDecorator$1 = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
159
10
  /**
160
11
  * Direct-storage upload settings for an `@Upload` route. The br-pod validates an
161
12
  * authorize request against these (size/type), then mints a signed upload URL
@@ -164,7 +15,13 @@ type ParameterDecorator = (target: object, propertyKey: string | symbol, paramet
164
15
  */
165
16
  interface UploadConfig {
166
17
  /**
167
- * Target bucket NAME. MUST exist in `config/storage.ts` `defineStorage(...)`.
18
+ * Target bucket NAME one the STACK holds.
19
+ *
20
+ * The union comes from the generated `palbase-stack.d.ts`, so a bucket the
21
+ * stack does not carry is a compile error. It used to say "MUST exist in
22
+ * `config/storage.ts` defineStorage(...)", and that invariant was carried by
23
+ * this sentence plus a cross-check nothing called; it is carried by the type
24
+ * now.
168
25
  *
169
26
  * The bucket is the SINGLE SOURCE OF TRUTH for the size limit + MIME allowlist:
170
27
  * `bucket({ fileSizeLimit, allowedMimeTypes })`. Storage enforces those at the
@@ -174,7 +31,7 @@ interface UploadConfig {
174
31
  * enforce (a real bypass: declare 10 bytes at authorize, then PUT up to the
175
32
  * bucket ceiling straight at the signed URL). One bucket, one limit, enforced.
176
33
  */
177
- bucket: string;
34
+ bucket: PalbaseBucketName;
178
35
  /**
179
36
  * SERVER-side object key template. The client NEVER chooses the path. Tokens:
180
37
  * `{userId}` (authenticated user id), `{uploadId}` (server-minted), and
@@ -191,10 +48,10 @@ interface UploadConfig {
191
48
  * @example
192
49
  * @Upload("/", { bucket: "docs", pathTemplate: "{userId}/{uploadId}-{filename}" })
193
50
  * async upload(@UploadedObject() obj: UploadedObject, @User() user): Promise<DocResult> { ... }
194
- * // The size limit + MIME allowlist come from the "docs" bucket in
195
- * // config/storage.ts — storage enforces them at the PUT.
51
+ * // The size limit + MIME allowlist come from the "docs" bucket ON THE STACK
52
+ * // — storage enforces them at the PUT.
196
53
  */
197
- declare function Upload(subpath: string, config: UploadConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator;
54
+ declare function Upload(subpath: string, config: UploadConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator$1;
198
55
  /**
199
56
  * The uploaded object, injected into an `@Upload` method body by
200
57
  * `@UploadedObject()` once storage confirms the upload. Bytes are NOT present
@@ -249,20 +106,94 @@ interface UploadedObject {
249
106
  * Co-located with the {@link UploadedObject} TYPE so a single exported name
250
107
  * `UploadedObject` carries BOTH the decorator value and the type annotation.
251
108
  */
252
- declare function UploadedObject(): ParameterDecorator;
109
+ declare function UploadedObject(): ParameterDecorator$1;
110
+
111
+ /** A legacy method decorator (`experimentalDecorators`): `(prototype, name,
112
+ * descriptor)`. */
113
+ type MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
114
+ /** A legacy parameter decorator: `(prototype, name, paramIndex)`. */
115
+ type ParameterDecorator = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
116
+ /**
117
+ * Settings for an `@Sse` route.
118
+ *
119
+ * EMPTY in v1, and deliberately so: the pipeline keys off this object's
120
+ * PRESENCE, not its contents (see the file header). Declaring the type now means
121
+ * a later setting arrives as a field on an existing marker rather than as a
122
+ * second marker nothing downstream reads.
123
+ */
124
+ interface SseConfig {
125
+ /**
126
+ * The shape of ONE frame — what a single `out.write(value)` carries.
127
+ *
128
+ * REQUIRED, not optional, and that is the whole point. An `@Sse` handler
129
+ * returns `Promise<void>`, so the codegen-injected `recordReturn` has no type
130
+ * to record and nothing downstream can infer what a frame is. A route without
131
+ * this declares a stream whose element type is unknown, and every generated
132
+ * client from that contract is opaque — which is precisely the failure
133
+ * `x-palbase-sse` exists to prevent. Making it optional would have left that
134
+ * failure one forgotten field away.
135
+ *
136
+ * Measured live on a real pushed stack (2026-08-29): the contract gate refused
137
+ * the deploy with "2 untyped response(s) — every generated client from it is
138
+ * opaque", naming both routes that omitted it.
139
+ */
140
+ frame: ZodTypeAny;
141
+ }
142
+ /**
143
+ * The writer injected by `@SseOut()`. Each `write` emits ONE SSE `data:` frame
144
+ * carrying `value` JSON-encoded.
145
+ *
146
+ * The FIRST `write` is load-bearing beyond its frame: it settles the request's
147
+ * database transaction. A streaming response may run for minutes, and the
148
+ * handler runs inside the request's transaction — holding one open for the life
149
+ * of a stream exhausts the connection pool, a failure invisible to a unit test
150
+ * that only dies under load. So the first frame is the point at which the
151
+ * request phase is declared over, and database access after it is refused by
152
+ * name rather than silently run against a settled transaction.
153
+ *
154
+ * The practical rule for a handler: do the database work BEFORE the first write.
155
+ */
156
+ interface SseWriter {
157
+ write(value: unknown): void;
158
+ }
159
+ /**
160
+ * `@Sse(subpath, config)` — declare a streaming route.
161
+ *
162
+ * @example
163
+ * @Sse("/chat", { frame: ChatFrame })
164
+ * async chat(@Body() b: ChatInput, @SseOut() out: SseWriter, @Signal() signal: AbortSignal) {
165
+ * const stream = await openai.chat.completions.create(
166
+ * { model: "gpt-5.6", messages: b.messages, stream: true },
167
+ * { signal },
168
+ * );
169
+ * for await (const chunk of stream) out.write(chunk);
170
+ * }
171
+ * // The client disconnects → `signal` aborts → the provider stops being pulled.
172
+ * // The signal is a plain AbortSignal, so it goes wherever the provider takes
173
+ * // one: `{ signal }` for the OpenAI and Anthropic SDKs, `abortSignal:` for the
174
+ * // Vercel AI SDK, `fetch(url, { signal })` for a raw call.
175
+ */
176
+ declare function Sse(subpath: string | undefined, config: SseConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator;
177
+ /**
178
+ * `@SseOut()` — inject the frame writer (`: SseWriter`) into an `@Sse` method
179
+ * body. Only meaningful on an `@Sse` route.
180
+ */
181
+ declare function SseOut(): ParameterDecorator;
253
182
  /**
254
- * Cross-check one `@Upload` route's uploadConfig against the project's
255
- * {@link StorageConfig}: the named bucket MUST exist in `defineStorage(...)`.
256
- * Throws a precise error so a typo fails at deploy, not at the first user upload.
183
+ * `@Signal()` — inject the request's `AbortSignal`, which enters the aborted
184
+ * state when the client disconnects.
257
185
  *
258
- * That is the ONLY cross-check: the size limit + MIME allowlist live on the
259
- * bucket (`bucket({ fileSizeLimit, allowedMimeTypes })`) and are enforced by
260
- * storage at the PUT. `@Upload` carries no `maxSize`/`allowedTypes` of its own,
261
- * so there is nothing to compare — one bucket, one limit, no drift.
186
+ * NOT derivable from `@Req()`: `PBRequest` carries only request-scoped data
187
+ * the typed input, route/query params, headers, the authenticated user, calling
188
+ * client metadata, trace ids, and the declared error throwers and no signal
189
+ * (endpoint.ts:358-363).
262
190
  *
263
- * `routeLabel` is used only for error messages (e.g. `docs.upload`).
191
+ * Measured 2026-08-29 on Bun: an infinite producer guarded by this signal
192
+ * stopped four frames after the client was killed, and the handler's `finally`
193
+ * ran. It is the same mechanism a NestJS handler reaches through
194
+ * `req.on("close")`.
264
195
  */
265
- declare function validateUploadAgainstStorage(uploadConfig: UploadConfig, storage: StorageConfig, routeLabel: string): void;
196
+ declare function Signal(): ParameterDecorator;
266
197
 
267
198
  /** The HTTP verbs a route may declare, upper-cased (the runtime router +
268
199
  * OpenAPI lower-case on their own). */
@@ -279,11 +210,18 @@ interface RouteOptions {
279
210
  * openapi → codegen). The bytes go client→storage directly; the method body
280
211
  * runs as the completion handler. See {@link UploadConfig} (decorators/upload.ts). */
281
212
  uploadConfig?: UploadConfig;
213
+ /** Streaming config — present ONLY on `@Sse` routes (the `@Get`/`@Post`/…
214
+ * decorators never set it). Its presence is what MARKS a route as a streaming
215
+ * route through the whole pipeline (registry → flatten → openapi → codegen),
216
+ * exactly as `uploadConfig` does for uploads — never a special HTTP verb. An
217
+ * `@Sse` route registers POST like any input-bearing route, so the verb cannot
218
+ * carry the distinction. See {@link SseConfig} (decorators/sse.ts). */
219
+ sseConfig?: SseConfig;
282
220
  }
283
221
  /** The kind of value a parameter decorator injects. Drives both dispatch
284
222
  * (which request slice to inject) and codegen (which OpenAPI parameter source a
285
223
  * schema-bearing kind maps to). */
286
- type ParamKind = "body" | "query" | "param" | "headers" | "user" | "optionalUser" | "client" | "requestId" | "traceId" | "req" | "uploadedObject";
224
+ type ParamKind = "body" | "query" | "param" | "headers" | "user" | "optionalUser" | "client" | "requestId" | "traceId" | "req" | "uploadedObject" | "sseOut" | "signal";
287
225
  /** One parameter decorator's recorded metadata. `index` is the parameter
288
226
  * position esbuild/tsc preserve; `schema` is present for the schema-bearing
289
227
  * kinds (`body`/`query`/`headers`); `name` is the path-param name for `param`. */
@@ -335,4 +273,4 @@ declare function recordThrows(target: object, fnName: string, throws: ThrowDescr
335
273
  */
336
274
  declare function getRoutes(ctor: object): RouteMeta[];
337
275
 
338
- export { type BucketDef as B, type HttpMethodUpper as H, type ImageVariant as I, type ParamMeta as P, type RouteMeta as R, STORAGE_CONFIG_KIND as S, type ThrowDescriptor as T, type UploadConfig as U, type RouteOptions as a, type BucketOptions as b, type ParamKind as c, type StorageConfig as d, type StorageInput as e, Upload as f, UploadedObject as g, bucket as h, defineStorage as i, getRoutes as j, parseFileSizeLimit as p, recordThrows as r, validateUploadAgainstStorage as v };
276
+ export { type HttpMethodUpper as H, type ParamMeta as P, type RouteMeta as R, type SseConfig as S, type ThrowDescriptor as T, type UploadConfig as U, type RouteOptions as a, type ParamKind as b, Signal as c, Sse as d, SseOut as e, type SseWriter as f, Upload as g, UploadedObject as h, getRoutes as i, recordThrows as r };