@palbase/backend 22.1.0 → 23.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/bin/palbase-backend.cjs +439 -18
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -4
  4. package/dist/{chunk-SQC5EIWY.js → chunk-FSGSB42K.js} +11 -6
  5. package/dist/chunk-FSGSB42K.js.map +1 -0
  6. package/dist/{chunk-I3ON7MYF.js → chunk-HAF67F2H.js} +73 -16
  7. package/dist/chunk-HAF67F2H.js.map +1 -0
  8. package/dist/{chunk-QMVK4X3V.js → chunk-OMRTHM4X.js} +98 -98
  9. package/dist/chunk-OMRTHM4X.js.map +1 -0
  10. package/dist/{chunk-74XDEF5J.js → chunk-REZU6UKT.js} +432 -17
  11. package/dist/chunk-REZU6UKT.js.map +1 -0
  12. package/dist/{chunk-YL4C5NRY.js → chunk-Y5HXVUMP.js} +2 -2
  13. package/dist/{chunk-N32VDWKH.js → chunk-ZC6Q2BRD.js} +4 -59
  14. package/dist/chunk-ZC6Q2BRD.js.map +1 -0
  15. package/dist/db/env.cjs.map +1 -1
  16. package/dist/db/env.d.cts +3 -21
  17. package/dist/db/env.d.ts +3 -21
  18. package/dist/db/index.cjs +72 -15
  19. package/dist/db/index.cjs.map +1 -1
  20. package/dist/db/index.d.cts +3 -2
  21. package/dist/db/index.d.ts +3 -2
  22. package/dist/db/index.js +1 -1
  23. package/dist/{endpoint-BVT6jcVW.d.cts → endpoint-BavvbW4P.d.ts} +77 -7
  24. package/dist/{endpoint-BVT6jcVW.d.ts → endpoint-i8TTCohk.d.cts} +77 -7
  25. package/dist/engine/index.cjs +439 -18
  26. package/dist/engine/index.cjs.map +1 -1
  27. package/dist/engine/index.d.cts +6 -5
  28. package/dist/engine/index.d.ts +6 -5
  29. package/dist/engine/index.js +3 -3
  30. package/dist/{index-vwHoS0l2.d.ts → index-B3jmmItD.d.ts} +111 -5
  31. package/dist/{index-CCZqzych.d.ts → index-B7YBEG5w.d.ts} +96 -71
  32. package/dist/{index-BqCiHao8.d.cts → index-Bmvx1EvJ.d.cts} +111 -5
  33. package/dist/{index-BS1gW4nV.d.cts → index-E7OscPJT.d.cts} +96 -71
  34. package/dist/index.cjs +285 -1099
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +89 -1134
  37. package/dist/index.d.ts +89 -1134
  38. package/dist/index.js +107 -902
  39. package/dist/index.js.map +1 -1
  40. package/dist/openapi/index.cjs +3 -57
  41. package/dist/openapi/index.cjs.map +1 -1
  42. package/dist/openapi/index.d.cts +3 -2
  43. package/dist/openapi/index.d.ts +3 -2
  44. package/dist/openapi/index.js +5 -22
  45. package/dist/openapi/index.js.map +1 -1
  46. package/dist/{registry-Bsuf-orT.d.ts → registry-C3H2uPeZ.d.cts} +13 -169
  47. package/dist/{registry-BWttGlaT.d.cts → registry-DY3d9l1k.d.ts} +13 -169
  48. package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
  49. package/dist/stack.cjs.map +1 -0
  50. package/dist/stack.d.cts +76 -0
  51. package/dist/stack.d.ts +76 -0
  52. package/dist/stack.js +1 -0
  53. package/docs/README.md +7 -6
  54. package/docs/llms-full.txt +7 -260
  55. package/docs/llms.txt +0 -2
  56. package/package.json +6 -6
  57. package/template/package.json +1 -1
  58. package/dist/chunk-74XDEF5J.js.map +0 -1
  59. package/dist/chunk-I3ON7MYF.js.map +0 -1
  60. package/dist/chunk-N32VDWKH.js.map +0 -1
  61. package/dist/chunk-QMVK4X3V.js.map +0 -1
  62. package/dist/chunk-SQC5EIWY.js.map +0 -1
  63. package/dist/purchases/keys.cjs.map +0 -1
  64. package/dist/purchases/keys.d.cts +0 -42
  65. package/dist/purchases/keys.d.ts +0 -42
  66. package/dist/purchases/keys.js +0 -1
  67. package/docs/config.md +0 -147
  68. package/docs/resources.md +0 -97
  69. package/template/config/secrets.ts +0 -24
  70. /package/dist/{chunk-YL4C5NRY.js.map → chunk-Y5HXVUMP.js.map} +0 -0
  71. /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
@@ -1,155 +1,6 @@
1
- import { A as AuthSpec, R as RateLimitConfig } from './endpoint-BVT6jcVW.js';
1
+ import { A as AuthSpec, R as RateLimitConfig } from './endpoint-i8TTCohk.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)`. */
@@ -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,8 +48,8 @@ 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
54
  declare function Upload(subpath: string, config: UploadConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator;
198
55
  /**
@@ -250,19 +107,6 @@ interface UploadedObject {
250
107
  * `UploadedObject` carries BOTH the decorator value and the type annotation.
251
108
  */
252
109
  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
110
 
267
111
  /** The HTTP verbs a route may declare, upper-cased (the runtime router +
268
112
  * OpenAPI lower-case on their own). */
@@ -335,4 +179,4 @@ declare function recordThrows(target: object, fnName: string, throws: ThrowDescr
335
179
  */
336
180
  declare function getRoutes(ctor: object): RouteMeta[];
337
181
 
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 };
182
+ export { type HttpMethodUpper as H, type ParamMeta as P, type RouteMeta as R, type ThrowDescriptor as T, type UploadConfig as U, type RouteOptions as a, type ParamKind as b, Upload as c, UploadedObject as d, getRoutes as g, recordThrows as r };
@@ -1,155 +1,6 @@
1
- import { A as AuthSpec, R as RateLimitConfig } from './endpoint-BVT6jcVW.cjs';
1
+ import { A as AuthSpec, R as RateLimitConfig } from './endpoint-BavvbW4P.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)`. */
@@ -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,8 +48,8 @@ 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
54
  declare function Upload(subpath: string, config: UploadConfig & Pick<RouteOptions, "auth" | "rateLimit">): MethodDecorator;
198
55
  /**
@@ -250,19 +107,6 @@ interface UploadedObject {
250
107
  * `UploadedObject` carries BOTH the decorator value and the type annotation.
251
108
  */
252
109
  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
110
 
267
111
  /** The HTTP verbs a route may declare, upper-cased (the runtime router +
268
112
  * OpenAPI lower-case on their own). */
@@ -335,4 +179,4 @@ declare function recordThrows(target: object, fnName: string, throws: ThrowDescr
335
179
  */
336
180
  declare function getRoutes(ctor: object): RouteMeta[];
337
181
 
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 };
182
+ export { type HttpMethodUpper as H, type ParamMeta as P, type RouteMeta as R, type ThrowDescriptor as T, type UploadConfig as U, type RouteOptions as a, type ParamKind as b, Upload as c, UploadedObject as d, getRoutes as g, recordThrows as r };
@@ -13,7 +13,7 @@ var __copyProps = (to, from, except, desc) => {
13
13
  };
14
14
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
15
 
16
- // src/purchases/keys.ts
17
- var keys_exports = {};
18
- module.exports = __toCommonJS(keys_exports);
19
- //# sourceMappingURL=keys.cjs.map
16
+ // src/stack.ts
17
+ var stack_exports = {};
18
+ module.exports = __toCommonJS(stack_exports);
19
+ //# sourceMappingURL=stack.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stack.ts"],"sourcesContent":["/**\n * `@palbase/backend/stack` — the controlled global augmentation target for the\n * names the project's STACK holds.\n *\n * `Secrets.get(...)`, the Flags client's key parameters and `@Upload({ bucket })`\n * take these unions, never a raw string, so a name the stack does not hold is a\n * COMPILE error rather than a 500 in production — or, worse, a silent read of a\n * secret nobody set.\n *\n * Three interfaces in ONE module because the three share a source and a refresh\n * moment: they all come from the linked environment's stack, and any `palbase\n * secret set` / `flags add` / `storage add` moves them together. `@palbase/\n * backend/env` and `@palbase/backend/purchases` stay separate for the opposite\n * reason — their sources are the schema and the catalog.\n *\n * All three are EMPTY by default; the generated `palbase-stack.d.ts` augments\n * them with one member per name:\n *\n * // palbase-stack.d.ts (generated — do not edit)\n * declare module \"@palbase/backend/stack\" {\n * interface Secrets { OPENAI_API_KEY: true }\n * interface Flags { newCheckout: true }\n * interface Buckets { avatars: true }\n * }\n *\n * A project that has not generated the file therefore has NO valid names, and\n * every call fails to compile until it does. That is the intended behaviour,\n * not a rough edge: it is the ONLY shape in which the gate is not optional. A\n * permissive `string` fallback would make the check opt-in, and an opt-in gate\n * is the same as no gate — which is precisely the hole the retired\n * `config/secrets.ts` push-check used to paper over.\n *\n * THIS REPLACED A DECLARATION, and the direction matters. `config/secrets.ts`\n * asked the author to restate, in the repo, names the vault already held; the\n * deploy then compared the two lists. Here the stack is simply read and the\n * result is a type. There is no second list to drift.\n */\n\n/** The secret names this project's vault holds. EMPTY by default; filled by the\n * generated `palbase-stack.d.ts`. */\n// biome-ignore lint/suspicious/noEmptyInterface: augmentation target — filled by generated palbase-stack.d.ts.\nexport interface Secrets {}\n\n/** The flag keys this project's store defines. EMPTY by default; filled by the\n * generated `palbase-stack.d.ts`. */\n// biome-ignore lint/suspicious/noEmptyInterface: augmentation target — filled by generated palbase-stack.d.ts.\nexport interface Flags {}\n\n/**\n * One typed bucket: which renditions the stack declared for it.\n *\n * `variants` is a UNION of the declared names, so asking for one the bucket does\n * not have is a compile error rather than a 404 somebody finds in production.\n * The default `never` means a bucket with no variants accepts no variant name at\n * all, which is exactly right.\n *\n * THIS MOVED HERE FROM `db/env.ts` (2026-08-29). It lived there because\n * `config/storage.ts` fed it through `palbase-env.d.ts`, alongside the schema's\n * tables. Buckets do not come from the schema — they come from the STACK — so\n * they belong in the file generated off the stack. Two `Buckets` interfaces for\n * one concept is how `Storage.buckets.docs` and `@Upload({ bucket: \"docs\" })`\n * came to disagree about whether \"docs\" exists.\n */\nexport interface BucketTypes {\n variants: string;\n}\n\n/** The buckets this project's storage carries, keyed by name. EMPTY by default;\n * filled by the generated `palbase-stack.d.ts`. Drives BOTH `Storage.buckets.*`\n * (through `EnvBuckets`) and `@Upload({ bucket })`. */\n// biome-ignore lint/suspicious/noEmptyInterface: augmentation target — filled by generated palbase-stack.d.ts.\nexport interface Buckets {}\n\n/** Every secret name the stack holds. */\nexport type PalbaseSecretName = keyof Secrets & string;\n\n/** Every flag key the stack defines. */\nexport type PalbaseFlagKey = keyof Flags & string;\n\n/** Every bucket the stack carries. */\nexport type PalbaseBucketName = keyof Buckets & string;\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `@palbase/backend/stack` — the controlled global augmentation target for the
3
+ * names the project's STACK holds.
4
+ *
5
+ * `Secrets.get(...)`, the Flags client's key parameters and `@Upload({ bucket })`
6
+ * take these unions, never a raw string, so a name the stack does not hold is a
7
+ * COMPILE error rather than a 500 in production — or, worse, a silent read of a
8
+ * secret nobody set.
9
+ *
10
+ * Three interfaces in ONE module because the three share a source and a refresh
11
+ * moment: they all come from the linked environment's stack, and any `palbase
12
+ * secret set` / `flags add` / `storage add` moves them together. `@palbase/
13
+ * backend/env` and `@palbase/backend/purchases` stay separate for the opposite
14
+ * reason — their sources are the schema and the catalog.
15
+ *
16
+ * All three are EMPTY by default; the generated `palbase-stack.d.ts` augments
17
+ * them with one member per name:
18
+ *
19
+ * // palbase-stack.d.ts (generated — do not edit)
20
+ * declare module "@palbase/backend/stack" {
21
+ * interface Secrets { OPENAI_API_KEY: true }
22
+ * interface Flags { newCheckout: true }
23
+ * interface Buckets { avatars: true }
24
+ * }
25
+ *
26
+ * A project that has not generated the file therefore has NO valid names, and
27
+ * every call fails to compile until it does. That is the intended behaviour,
28
+ * not a rough edge: it is the ONLY shape in which the gate is not optional. A
29
+ * permissive `string` fallback would make the check opt-in, and an opt-in gate
30
+ * is the same as no gate — which is precisely the hole the retired
31
+ * `config/secrets.ts` push-check used to paper over.
32
+ *
33
+ * THIS REPLACED A DECLARATION, and the direction matters. `config/secrets.ts`
34
+ * asked the author to restate, in the repo, names the vault already held; the
35
+ * deploy then compared the two lists. Here the stack is simply read and the
36
+ * result is a type. There is no second list to drift.
37
+ */
38
+ /** The secret names this project's vault holds. EMPTY by default; filled by the
39
+ * generated `palbase-stack.d.ts`. */
40
+ interface Secrets {
41
+ }
42
+ /** The flag keys this project's store defines. EMPTY by default; filled by the
43
+ * generated `palbase-stack.d.ts`. */
44
+ interface Flags {
45
+ }
46
+ /**
47
+ * One typed bucket: which renditions the stack declared for it.
48
+ *
49
+ * `variants` is a UNION of the declared names, so asking for one the bucket does
50
+ * not have is a compile error rather than a 404 somebody finds in production.
51
+ * The default `never` means a bucket with no variants accepts no variant name at
52
+ * all, which is exactly right.
53
+ *
54
+ * THIS MOVED HERE FROM `db/env.ts` (2026-08-29). It lived there because
55
+ * `config/storage.ts` fed it through `palbase-env.d.ts`, alongside the schema's
56
+ * tables. Buckets do not come from the schema — they come from the STACK — so
57
+ * they belong in the file generated off the stack. Two `Buckets` interfaces for
58
+ * one concept is how `Storage.buckets.docs` and `@Upload({ bucket: "docs" })`
59
+ * came to disagree about whether "docs" exists.
60
+ */
61
+ interface BucketTypes {
62
+ variants: string;
63
+ }
64
+ /** The buckets this project's storage carries, keyed by name. EMPTY by default;
65
+ * filled by the generated `palbase-stack.d.ts`. Drives BOTH `Storage.buckets.*`
66
+ * (through `EnvBuckets`) and `@Upload({ bucket })`. */
67
+ interface Buckets {
68
+ }
69
+ /** Every secret name the stack holds. */
70
+ type PalbaseSecretName = keyof Secrets & string;
71
+ /** Every flag key the stack defines. */
72
+ type PalbaseFlagKey = keyof Flags & string;
73
+ /** Every bucket the stack carries. */
74
+ type PalbaseBucketName = keyof Buckets & string;
75
+
76
+ export type { BucketTypes, Buckets, Flags, PalbaseBucketName, PalbaseFlagKey, PalbaseSecretName, Secrets };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `@palbase/backend/stack` — the controlled global augmentation target for the
3
+ * names the project's STACK holds.
4
+ *
5
+ * `Secrets.get(...)`, the Flags client's key parameters and `@Upload({ bucket })`
6
+ * take these unions, never a raw string, so a name the stack does not hold is a
7
+ * COMPILE error rather than a 500 in production — or, worse, a silent read of a
8
+ * secret nobody set.
9
+ *
10
+ * Three interfaces in ONE module because the three share a source and a refresh
11
+ * moment: they all come from the linked environment's stack, and any `palbase
12
+ * secret set` / `flags add` / `storage add` moves them together. `@palbase/
13
+ * backend/env` and `@palbase/backend/purchases` stay separate for the opposite
14
+ * reason — their sources are the schema and the catalog.
15
+ *
16
+ * All three are EMPTY by default; the generated `palbase-stack.d.ts` augments
17
+ * them with one member per name:
18
+ *
19
+ * // palbase-stack.d.ts (generated — do not edit)
20
+ * declare module "@palbase/backend/stack" {
21
+ * interface Secrets { OPENAI_API_KEY: true }
22
+ * interface Flags { newCheckout: true }
23
+ * interface Buckets { avatars: true }
24
+ * }
25
+ *
26
+ * A project that has not generated the file therefore has NO valid names, and
27
+ * every call fails to compile until it does. That is the intended behaviour,
28
+ * not a rough edge: it is the ONLY shape in which the gate is not optional. A
29
+ * permissive `string` fallback would make the check opt-in, and an opt-in gate
30
+ * is the same as no gate — which is precisely the hole the retired
31
+ * `config/secrets.ts` push-check used to paper over.
32
+ *
33
+ * THIS REPLACED A DECLARATION, and the direction matters. `config/secrets.ts`
34
+ * asked the author to restate, in the repo, names the vault already held; the
35
+ * deploy then compared the two lists. Here the stack is simply read and the
36
+ * result is a type. There is no second list to drift.
37
+ */
38
+ /** The secret names this project's vault holds. EMPTY by default; filled by the
39
+ * generated `palbase-stack.d.ts`. */
40
+ interface Secrets {
41
+ }
42
+ /** The flag keys this project's store defines. EMPTY by default; filled by the
43
+ * generated `palbase-stack.d.ts`. */
44
+ interface Flags {
45
+ }
46
+ /**
47
+ * One typed bucket: which renditions the stack declared for it.
48
+ *
49
+ * `variants` is a UNION of the declared names, so asking for one the bucket does
50
+ * not have is a compile error rather than a 404 somebody finds in production.
51
+ * The default `never` means a bucket with no variants accepts no variant name at
52
+ * all, which is exactly right.
53
+ *
54
+ * THIS MOVED HERE FROM `db/env.ts` (2026-08-29). It lived there because
55
+ * `config/storage.ts` fed it through `palbase-env.d.ts`, alongside the schema's
56
+ * tables. Buckets do not come from the schema — they come from the STACK — so
57
+ * they belong in the file generated off the stack. Two `Buckets` interfaces for
58
+ * one concept is how `Storage.buckets.docs` and `@Upload({ bucket: "docs" })`
59
+ * came to disagree about whether "docs" exists.
60
+ */
61
+ interface BucketTypes {
62
+ variants: string;
63
+ }
64
+ /** The buckets this project's storage carries, keyed by name. EMPTY by default;
65
+ * filled by the generated `palbase-stack.d.ts`. Drives BOTH `Storage.buckets.*`
66
+ * (through `EnvBuckets`) and `@Upload({ bucket })`. */
67
+ interface Buckets {
68
+ }
69
+ /** Every secret name the stack holds. */
70
+ type PalbaseSecretName = keyof Secrets & string;
71
+ /** Every flag key the stack defines. */
72
+ type PalbaseFlagKey = keyof Flags & string;
73
+ /** Every bucket the stack carries. */
74
+ type PalbaseBucketName = keyof Buckets & string;
75
+
76
+ export type { BucketTypes, Buckets, Flags, PalbaseBucketName, PalbaseFlagKey, PalbaseSecretName, Secrets };
package/dist/stack.js ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=stack.js.map
package/docs/README.md CHANGED
@@ -24,8 +24,8 @@ db/schema.ts # config-as-code Postgres schema (tables, co
24
24
  ```
25
25
 
26
26
  The four folders above are the daily surface. These also exist (own docs, linked
27
- below): `resources/` (external connections[resources.md](./resources.md)),
28
- `seeds/` (seed data), `jobs/` (background — [background.md](./background.md)),
27
+ below): `resources/` (plain modules a controller imports a client, a
28
+ formatter, anything shared), `seeds/` (seed data), `jobs/` (background — [background.md](./background.md)),
29
29
  `webhooks/` + `hooks/` (events — [events.md](./events.md)), `middleware/`.
30
30
 
31
31
  ### The 7 rules (checklist)
@@ -203,9 +203,11 @@ generated client surface) changes; the verb/path do not affect it.
203
203
  secrets in its environment. There is no `.env` file: nothing writes one and
204
204
  nothing reads one, so the values live in one child process and are gone when it
205
205
  exits.
206
- - `config/secrets.ts` `defineSecrets({ secrets: [secret("NAME")] })` declares
207
- WHICH secrets the backend needs (never a value). `push` refuses the deploy when
208
- a required one is missing from the target Environment, naming it.
206
+ - Secrets are written to the stack (`palbase secret set NAME --stdin`) and read
207
+ with `Secrets.get(name)`. The NAME is typed: `palbase-stack.d.ts` is generated
208
+ from the stack, so a secret it does not hold cannot be spelled a compile
209
+ error rather than a 500. There is no `config/secrets.ts`: it asked you to
210
+ restate names the vault already had, and the deploy compared the two lists.
209
211
  - `tsconfig.json` needs `"experimentalDecorators": true` (the scaffold sets it).
210
212
 
211
213
  ## Mental model
@@ -266,7 +268,6 @@ HTTP endpoints are **not** file-path routed. You author a class controller
266
268
  | Schema & typed DB | [schema.md](./schema.md) |
267
269
  | Migrations (additive vs type-change + drift-gate) | [migrations.md](./migrations.md) |
268
270
  | Services (Cache, Queue, Storage, …) | [services.md](./services.md) |
269
- | Resources (external connections) | [resources.md](./resources.md) |
270
271
  | Errors | [errors.md](./errors.md) |
271
272
  | Workers & Jobs | [background.md](./background.md) |
272
273
  | Hooks & Webhooks | [events.md](./events.md) |