@palbase/backend 17.4.0 → 18.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/bin/palbase-backend.cjs +1848 -0
  2. package/dist/bin/palbase-backend.cjs.map +1 -0
  3. package/dist/bin/palbase-backend.d.cts +1 -0
  4. package/dist/bin/palbase-backend.d.ts +1 -0
  5. package/dist/bin/palbase-backend.js +168 -0
  6. package/dist/bin/palbase-backend.js.map +1 -0
  7. package/dist/chunk-7D4SUZUM.js +38 -0
  8. package/dist/chunk-7D4SUZUM.js.map +1 -0
  9. package/dist/chunk-N32VDWKH.js +172 -0
  10. package/dist/chunk-N32VDWKH.js.map +1 -0
  11. package/dist/chunk-POYAFBLF.js +189 -0
  12. package/dist/chunk-POYAFBLF.js.map +1 -0
  13. package/dist/chunk-QMVK4X3V.js +200 -0
  14. package/dist/chunk-QMVK4X3V.js.map +1 -0
  15. package/dist/chunk-SSGAMC26.js +342 -0
  16. package/dist/chunk-SSGAMC26.js.map +1 -0
  17. package/dist/chunk-VYH4U7ZQ.js +1138 -0
  18. package/dist/chunk-VYH4U7ZQ.js.map +1 -0
  19. package/dist/{chunk-AAN642N5.js → chunk-W5ODXPY3.js} +2 -336
  20. package/dist/chunk-W5ODXPY3.js.map +1 -0
  21. package/dist/chunk-YL4C5NRY.js +90 -0
  22. package/dist/chunk-YL4C5NRY.js.map +1 -0
  23. package/dist/db/env.cjs.map +1 -1
  24. package/dist/db/env.d.cts +21 -1
  25. package/dist/db/env.d.ts +21 -1
  26. package/dist/db/index.cjs.map +1 -1
  27. package/dist/db/index.d.cts +2 -1
  28. package/dist/db/index.d.ts +2 -1
  29. package/dist/db/index.js +9 -6
  30. package/dist/{index-VLrU7rSW.d.ts → endpoint-B0LpZixz.d.cts} +124 -685
  31. package/dist/{index-BA_oFAz9.d.cts → endpoint-B0LpZixz.d.ts} +124 -685
  32. package/dist/engine/index.cjs +1797 -0
  33. package/dist/engine/index.cjs.map +1 -0
  34. package/dist/engine/index.d.cts +7 -0
  35. package/dist/engine/index.d.ts +7 -0
  36. package/dist/engine/index.js +43 -0
  37. package/dist/engine/index.js.map +1 -0
  38. package/dist/index-B46CGNvx.d.cts +839 -0
  39. package/dist/index-BGSCWlUa.d.cts +674 -0
  40. package/dist/index-DZDUMth5.d.ts +839 -0
  41. package/dist/index-g-EzitI-.d.ts +674 -0
  42. package/dist/index.cjs +1031 -11
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +290 -532
  45. package/dist/index.d.ts +290 -532
  46. package/dist/index.js +999 -509
  47. package/dist/index.js.map +1 -1
  48. package/dist/openapi/index.cjs +6464 -0
  49. package/dist/openapi/index.cjs.map +1 -0
  50. package/dist/openapi/index.d.cts +170 -0
  51. package/dist/openapi/index.d.ts +170 -0
  52. package/dist/openapi/index.js +6248 -0
  53. package/dist/openapi/index.js.map +1 -0
  54. package/dist/registry-3BLYv4si.d.ts +338 -0
  55. package/dist/registry-Cw0YEYCg.d.cts +338 -0
  56. package/dist/test/index.js +2 -0
  57. package/dist/test/index.js.map +1 -1
  58. package/docs/database.md +16 -3
  59. package/docs/llms-full.txt +16 -3
  60. package/package.json +43 -13
  61. package/stager/package.json +4 -0
  62. package/stager/return_types.js +338 -0
  63. package/stager/stage.js +78 -0
  64. package/stager/throw_analysis.js +726 -0
  65. package/template/AGENTS.md +261 -0
  66. package/template/config/secrets.ts +24 -0
  67. package/template/controllers/health.controller.ts +30 -0
  68. package/template/db/schema.ts +35 -0
  69. package/template/package.json +18 -0
  70. package/template/tsconfig.json +30 -0
  71. package/LICENSE +0 -21
  72. package/dist/chunk-AAN642N5.js.map +0 -1
@@ -0,0 +1,338 @@
1
+ import { A as AuthSpec, R as RateLimitConfig } from './endpoint-B0LpZixz.js';
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;
153
+
154
+ /** A legacy method decorator (`experimentalDecorators`): `(prototype, name,
155
+ * descriptor)`. */
156
+ type MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
157
+ /** A legacy parameter decorator: `(prototype, name, paramIndex)`. */
158
+ type ParameterDecorator = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
159
+ /**
160
+ * Direct-storage upload settings for an `@Upload` route. The br-pod validates an
161
+ * authorize request against these (size/type), then mints a signed upload URL
162
+ * that PINS the limits so storage itself rejects an over-limit / wrong-type PUT
163
+ * — the client cannot exceed what it declared.
164
+ */
165
+ interface UploadConfig {
166
+ /**
167
+ * Target bucket NAME. MUST exist in `config/storage.ts` `defineStorage(...)`.
168
+ *
169
+ * The bucket is the SINGLE SOURCE OF TRUTH for the size limit + MIME allowlist:
170
+ * `bucket({ fileSizeLimit, allowedMimeTypes })`. Storage enforces those at the
171
+ * actual PUT (the only guard a client cannot skip), so `@Upload` deliberately
172
+ * does NOT take its own `maxSize`/`allowedTypes` — duplicating them here would
173
+ * let a route declare a tighter limit than its bucket that storage would not
174
+ * enforce (a real bypass: declare 10 bytes at authorize, then PUT up to the
175
+ * bucket ceiling straight at the signed URL). One bucket, one limit, enforced.
176
+ */
177
+ bucket: string;
178
+ /**
179
+ * SERVER-side object key template. The client NEVER chooses the path. Tokens:
180
+ * `{userId}` (authenticated user id), `{uploadId}` (server-minted), and
181
+ * `{filename}` (the client-declared filename, sanitized). e.g.
182
+ * `"{userId}/{uploadId}-{filename}"`.
183
+ */
184
+ pathTemplate: string;
185
+ }
186
+ /**
187
+ * `@Upload(subpath, config)` — declare a direct-storage upload route. The method
188
+ * body is the completion handler; `config.uploadConfig` drives the authorize
189
+ * guard + signed-URL pinning.
190
+ *
191
+ * @example
192
+ * @Upload("/", { bucket: "docs", pathTemplate: "{userId}/{uploadId}-{filename}" })
193
+ * 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.
196
+ */
197
+ declare function Upload(subpath: string, config: UploadConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator;
198
+ /**
199
+ * The uploaded object, injected into an `@Upload` method body by
200
+ * `@UploadedObject()` once storage confirms the upload. Bytes are NOT present
201
+ * (they went straight to storage) — this is the metadata the completion handler
202
+ * persists.
203
+ */
204
+ interface UploadedObject {
205
+ /** Server-minted id correlating authorize ↔ completion (idempotency key). */
206
+ uploadId: string;
207
+ /** Final object key in the bucket (rendered from `pathTemplate`). */
208
+ path: string;
209
+ /** Bucket the object landed in. */
210
+ bucket: string;
211
+ /** Object size in bytes, as reported by storage. */
212
+ size: number;
213
+ /** Object MIME type, as reported by storage — detected from the BYTES, not
214
+ * from the filename or from what the client claimed. */
215
+ contentType: string;
216
+ /** SHA-256 of the stored bytes, hex. The same value the object's ETag is
217
+ * derived from, so a client that has it can tell whether it already holds
218
+ * these bytes. */
219
+ checksum: string;
220
+ /** Pixel width, for an image. Absent otherwise — a PDF has no dimensions,
221
+ * and reporting 0 would be a measurement rather than an absence. */
222
+ width?: number;
223
+ /** Pixel height, for an image. */
224
+ height?: number;
225
+ /**
226
+ * A ~25-byte placeholder the client paints INSTANTLY while the real image
227
+ * downloads — the thing that replaces a grey skeleton with something already
228
+ * shaped like the picture. Absent for non-images.
229
+ *
230
+ * Persist it beside the object's path: it costs a column and saves a request
231
+ * per picture on every gallery render.
232
+ */
233
+ thumbhash?: string;
234
+ /**
235
+ * The renditions the bucket declared, by name, as URLs ready to use.
236
+ *
237
+ * Present on the completion input so the handler that stores the row has
238
+ * everything it needs in one place — asking for them afterwards would be a
239
+ * second call per upload, and per picture on every read.
240
+ */
241
+ variants: Record<string, string>;
242
+ }
243
+ /**
244
+ * `@UploadedObject()` — inject the uploaded object (`: UploadedObject`) into an
245
+ * `@Upload` method body (the completion input). Only valid on an `@Upload`
246
+ * route; the bytes are NOT present (they went directly to storage), this is the
247
+ * confirmed object's metadata.
248
+ *
249
+ * Co-located with the {@link UploadedObject} TYPE so a single exported name
250
+ * `UploadedObject` carries BOTH the decorator value and the type annotation.
251
+ */
252
+ declare function UploadedObject(): ParameterDecorator;
253
+ /**
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.
257
+ *
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.
262
+ *
263
+ * `routeLabel` is used only for error messages (e.g. `docs.upload`).
264
+ */
265
+ declare function validateUploadAgainstStorage(uploadConfig: UploadConfig, storage: StorageConfig, routeLabel: string): void;
266
+
267
+ /** The HTTP verbs a route may declare, upper-cased (the runtime router +
268
+ * OpenAPI lower-case on their own). */
269
+ type HttpMethodUpper = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "QUERY";
270
+ /** Route-level options accepted by the method decorators (`@Get`/`@Post`/…). */
271
+ interface RouteOptions {
272
+ /** OVERRIDES the controller-level default auth for this one route. */
273
+ auth?: AuthSpec;
274
+ /** Per-route rate limit. */
275
+ rateLimit?: RateLimitConfig;
276
+ /** Direct-storage upload config — present ONLY on `@Upload` routes (the
277
+ * `@Get`/`@Post`/… decorators never set it). Its presence is what MARKS a
278
+ * route as an upload route through the whole pipeline (registry → flatten →
279
+ * openapi → codegen). The bytes go client→storage directly; the method body
280
+ * runs as the completion handler. See {@link UploadConfig} (decorators/upload.ts). */
281
+ uploadConfig?: UploadConfig;
282
+ }
283
+ /** The kind of value a parameter decorator injects. Drives both dispatch
284
+ * (which request slice to inject) and codegen (which OpenAPI parameter source a
285
+ * schema-bearing kind maps to). */
286
+ type ParamKind = "body" | "query" | "param" | "headers" | "user" | "optionalUser" | "client" | "requestId" | "traceId" | "req" | "uploadedObject";
287
+ /** One parameter decorator's recorded metadata. `index` is the parameter
288
+ * position esbuild/tsc preserve; `schema` is present for the schema-bearing
289
+ * kinds (`body`/`query`/`headers`); `name` is the path-param name for `param`. */
290
+ interface ParamMeta {
291
+ index: number;
292
+ kind: ParamKind;
293
+ /** Zod schema for `body`/`query`/`headers` (validation + codegen source). */
294
+ schema?: ZodTypeAny;
295
+ /** Path-param name for `@Param("id")`. */
296
+ name?: string;
297
+ }
298
+ /** One inferred throw site: the error CLASS name (e.g. "TodoLocked") and its
299
+ * wire code (e.g. "todo_locked"). `status`, `hasData`, and the data JSON schema
300
+ * are NOT carried here — they resolve from the error registry by `code` at
301
+ * extract/openapi time (single source of truth). */
302
+ interface ThrowDescriptor {
303
+ name: string;
304
+ code: string;
305
+ }
306
+ /** One route's recorded metadata: the verb + subpath + method name + options,
307
+ * the ordered parameter metas, and the resolved return schema (injected by the
308
+ * codegen step — see `returnSchema`). */
309
+ interface RouteMeta {
310
+ method: HttpMethodUpper;
311
+ subpath: string;
312
+ fnName: string;
313
+ options: RouteOptions;
314
+ params: ParamMeta[];
315
+ /** Response schema for the route, if any. Derived from the method's RETURN
316
+ * TYPE by codegen and written here via `recordReturn` (a generated top-level
317
+ * IIFE injected per controller), not by an author-written decorator. */
318
+ returnSchema?: ZodTypeAny;
319
+ /** Error classes this route can throw, if inferred. Derived from the method
320
+ * body + service call graph by the deploy stager's throw analysis and written
321
+ * here via `recordThrows` (a generated top-level IIFE injected per controller,
322
+ * the `recordReturn` twin), not by an author-written decorator. */
323
+ throws?: ThrowDescriptor[];
324
+ }
325
+ /** Attach the inferred throw descriptors to the route for `fnName` (called by
326
+ * the stager-injected IIFE that carries the throw analysis result — the
327
+ * `recordReturn` twin). If the route does not exist yet, the descriptors are
328
+ * buffered (THROWS_BUFFER) and drained into the route by `recordRoute` when the
329
+ * method decorator runs. */
330
+ declare function recordThrows(target: object, fnName: string, throws: ThrowDescriptor[]): void;
331
+ /** Read the route metadata for a controller class (the deploy/dispatch entry
332
+ * point). Applies any buffered return schemas + throw descriptors (for the
333
+ * recordReturn/recordThrows-runs-before orderings) and returns a defensive copy
334
+ * so callers cannot mutate the registry.
335
+ */
336
+ declare function getRoutes(ctor: object): RouteMeta[];
337
+
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 };
@@ -0,0 +1,338 @@
1
+ import { A as AuthSpec, R as RateLimitConfig } from './endpoint-B0LpZixz.cjs';
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;
153
+
154
+ /** A legacy method decorator (`experimentalDecorators`): `(prototype, name,
155
+ * descriptor)`. */
156
+ type MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
157
+ /** A legacy parameter decorator: `(prototype, name, paramIndex)`. */
158
+ type ParameterDecorator = (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
159
+ /**
160
+ * Direct-storage upload settings for an `@Upload` route. The br-pod validates an
161
+ * authorize request against these (size/type), then mints a signed upload URL
162
+ * that PINS the limits so storage itself rejects an over-limit / wrong-type PUT
163
+ * — the client cannot exceed what it declared.
164
+ */
165
+ interface UploadConfig {
166
+ /**
167
+ * Target bucket NAME. MUST exist in `config/storage.ts` `defineStorage(...)`.
168
+ *
169
+ * The bucket is the SINGLE SOURCE OF TRUTH for the size limit + MIME allowlist:
170
+ * `bucket({ fileSizeLimit, allowedMimeTypes })`. Storage enforces those at the
171
+ * actual PUT (the only guard a client cannot skip), so `@Upload` deliberately
172
+ * does NOT take its own `maxSize`/`allowedTypes` — duplicating them here would
173
+ * let a route declare a tighter limit than its bucket that storage would not
174
+ * enforce (a real bypass: declare 10 bytes at authorize, then PUT up to the
175
+ * bucket ceiling straight at the signed URL). One bucket, one limit, enforced.
176
+ */
177
+ bucket: string;
178
+ /**
179
+ * SERVER-side object key template. The client NEVER chooses the path. Tokens:
180
+ * `{userId}` (authenticated user id), `{uploadId}` (server-minted), and
181
+ * `{filename}` (the client-declared filename, sanitized). e.g.
182
+ * `"{userId}/{uploadId}-{filename}"`.
183
+ */
184
+ pathTemplate: string;
185
+ }
186
+ /**
187
+ * `@Upload(subpath, config)` — declare a direct-storage upload route. The method
188
+ * body is the completion handler; `config.uploadConfig` drives the authorize
189
+ * guard + signed-URL pinning.
190
+ *
191
+ * @example
192
+ * @Upload("/", { bucket: "docs", pathTemplate: "{userId}/{uploadId}-{filename}" })
193
+ * 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.
196
+ */
197
+ declare function Upload(subpath: string, config: UploadConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator;
198
+ /**
199
+ * The uploaded object, injected into an `@Upload` method body by
200
+ * `@UploadedObject()` once storage confirms the upload. Bytes are NOT present
201
+ * (they went straight to storage) — this is the metadata the completion handler
202
+ * persists.
203
+ */
204
+ interface UploadedObject {
205
+ /** Server-minted id correlating authorize ↔ completion (idempotency key). */
206
+ uploadId: string;
207
+ /** Final object key in the bucket (rendered from `pathTemplate`). */
208
+ path: string;
209
+ /** Bucket the object landed in. */
210
+ bucket: string;
211
+ /** Object size in bytes, as reported by storage. */
212
+ size: number;
213
+ /** Object MIME type, as reported by storage — detected from the BYTES, not
214
+ * from the filename or from what the client claimed. */
215
+ contentType: string;
216
+ /** SHA-256 of the stored bytes, hex. The same value the object's ETag is
217
+ * derived from, so a client that has it can tell whether it already holds
218
+ * these bytes. */
219
+ checksum: string;
220
+ /** Pixel width, for an image. Absent otherwise — a PDF has no dimensions,
221
+ * and reporting 0 would be a measurement rather than an absence. */
222
+ width?: number;
223
+ /** Pixel height, for an image. */
224
+ height?: number;
225
+ /**
226
+ * A ~25-byte placeholder the client paints INSTANTLY while the real image
227
+ * downloads — the thing that replaces a grey skeleton with something already
228
+ * shaped like the picture. Absent for non-images.
229
+ *
230
+ * Persist it beside the object's path: it costs a column and saves a request
231
+ * per picture on every gallery render.
232
+ */
233
+ thumbhash?: string;
234
+ /**
235
+ * The renditions the bucket declared, by name, as URLs ready to use.
236
+ *
237
+ * Present on the completion input so the handler that stores the row has
238
+ * everything it needs in one place — asking for them afterwards would be a
239
+ * second call per upload, and per picture on every read.
240
+ */
241
+ variants: Record<string, string>;
242
+ }
243
+ /**
244
+ * `@UploadedObject()` — inject the uploaded object (`: UploadedObject`) into an
245
+ * `@Upload` method body (the completion input). Only valid on an `@Upload`
246
+ * route; the bytes are NOT present (they went directly to storage), this is the
247
+ * confirmed object's metadata.
248
+ *
249
+ * Co-located with the {@link UploadedObject} TYPE so a single exported name
250
+ * `UploadedObject` carries BOTH the decorator value and the type annotation.
251
+ */
252
+ declare function UploadedObject(): ParameterDecorator;
253
+ /**
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.
257
+ *
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.
262
+ *
263
+ * `routeLabel` is used only for error messages (e.g. `docs.upload`).
264
+ */
265
+ declare function validateUploadAgainstStorage(uploadConfig: UploadConfig, storage: StorageConfig, routeLabel: string): void;
266
+
267
+ /** The HTTP verbs a route may declare, upper-cased (the runtime router +
268
+ * OpenAPI lower-case on their own). */
269
+ type HttpMethodUpper = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "QUERY";
270
+ /** Route-level options accepted by the method decorators (`@Get`/`@Post`/…). */
271
+ interface RouteOptions {
272
+ /** OVERRIDES the controller-level default auth for this one route. */
273
+ auth?: AuthSpec;
274
+ /** Per-route rate limit. */
275
+ rateLimit?: RateLimitConfig;
276
+ /** Direct-storage upload config — present ONLY on `@Upload` routes (the
277
+ * `@Get`/`@Post`/… decorators never set it). Its presence is what MARKS a
278
+ * route as an upload route through the whole pipeline (registry → flatten →
279
+ * openapi → codegen). The bytes go client→storage directly; the method body
280
+ * runs as the completion handler. See {@link UploadConfig} (decorators/upload.ts). */
281
+ uploadConfig?: UploadConfig;
282
+ }
283
+ /** The kind of value a parameter decorator injects. Drives both dispatch
284
+ * (which request slice to inject) and codegen (which OpenAPI parameter source a
285
+ * schema-bearing kind maps to). */
286
+ type ParamKind = "body" | "query" | "param" | "headers" | "user" | "optionalUser" | "client" | "requestId" | "traceId" | "req" | "uploadedObject";
287
+ /** One parameter decorator's recorded metadata. `index` is the parameter
288
+ * position esbuild/tsc preserve; `schema` is present for the schema-bearing
289
+ * kinds (`body`/`query`/`headers`); `name` is the path-param name for `param`. */
290
+ interface ParamMeta {
291
+ index: number;
292
+ kind: ParamKind;
293
+ /** Zod schema for `body`/`query`/`headers` (validation + codegen source). */
294
+ schema?: ZodTypeAny;
295
+ /** Path-param name for `@Param("id")`. */
296
+ name?: string;
297
+ }
298
+ /** One inferred throw site: the error CLASS name (e.g. "TodoLocked") and its
299
+ * wire code (e.g. "todo_locked"). `status`, `hasData`, and the data JSON schema
300
+ * are NOT carried here — they resolve from the error registry by `code` at
301
+ * extract/openapi time (single source of truth). */
302
+ interface ThrowDescriptor {
303
+ name: string;
304
+ code: string;
305
+ }
306
+ /** One route's recorded metadata: the verb + subpath + method name + options,
307
+ * the ordered parameter metas, and the resolved return schema (injected by the
308
+ * codegen step — see `returnSchema`). */
309
+ interface RouteMeta {
310
+ method: HttpMethodUpper;
311
+ subpath: string;
312
+ fnName: string;
313
+ options: RouteOptions;
314
+ params: ParamMeta[];
315
+ /** Response schema for the route, if any. Derived from the method's RETURN
316
+ * TYPE by codegen and written here via `recordReturn` (a generated top-level
317
+ * IIFE injected per controller), not by an author-written decorator. */
318
+ returnSchema?: ZodTypeAny;
319
+ /** Error classes this route can throw, if inferred. Derived from the method
320
+ * body + service call graph by the deploy stager's throw analysis and written
321
+ * here via `recordThrows` (a generated top-level IIFE injected per controller,
322
+ * the `recordReturn` twin), not by an author-written decorator. */
323
+ throws?: ThrowDescriptor[];
324
+ }
325
+ /** Attach the inferred throw descriptors to the route for `fnName` (called by
326
+ * the stager-injected IIFE that carries the throw analysis result — the
327
+ * `recordReturn` twin). If the route does not exist yet, the descriptors are
328
+ * buffered (THROWS_BUFFER) and drained into the route by `recordRoute` when the
329
+ * method decorator runs. */
330
+ declare function recordThrows(target: object, fnName: string, throws: ThrowDescriptor[]): void;
331
+ /** Read the route metadata for a controller class (the deploy/dispatch entry
332
+ * point). Applies any buffered return schemas + throw descriptors (for the
333
+ * recordReturn/recordThrows-runs-before orderings) and returns a defensive copy
334
+ * so callers cannot mutate the registry.
335
+ */
336
+ declare function getRoutes(ctor: object): RouteMeta[];
337
+
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 };
@@ -1,3 +1,5 @@
1
+ import "../chunk-7D4SUZUM.js";
2
+
1
3
  // src/test/api.ts
2
4
  var TestApiError = class extends Error {
3
5
  status;