@palbase/backend 22.1.0 → 23.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/bin/palbase-backend.cjs +750 -40
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +5 -5
  4. package/dist/{chunk-YL4C5NRY.js → chunk-HQRJDARQ.js} +2 -2
  5. package/dist/{chunk-74XDEF5J.js → chunk-M5MCBWJI.js} +723 -37
  6. package/dist/chunk-M5MCBWJI.js.map +1 -0
  7. package/dist/{chunk-W5ODXPY3.js → chunk-NS5V43YQ.js} +14 -1
  8. package/dist/chunk-NS5V43YQ.js.map +1 -0
  9. package/dist/{chunk-SQC5EIWY.js → chunk-OHALWEOG.js} +19 -9
  10. package/dist/chunk-OHALWEOG.js.map +1 -0
  11. package/dist/{chunk-I3ON7MYF.js → chunk-PY7YJDCT.js} +129 -18
  12. package/dist/chunk-PY7YJDCT.js.map +1 -0
  13. package/dist/{chunk-N32VDWKH.js → chunk-R3KN6RHD.js} +4 -59
  14. package/dist/chunk-R3KN6RHD.js.map +1 -0
  15. package/dist/{chunk-QMVK4X3V.js → chunk-RCLNBJCM.js} +98 -98
  16. package/dist/chunk-RCLNBJCM.js.map +1 -0
  17. package/dist/db/env.cjs.map +1 -1
  18. package/dist/db/env.d.cts +3 -21
  19. package/dist/db/env.d.ts +3 -21
  20. package/dist/db/index.cjs +140 -16
  21. package/dist/db/index.cjs.map +1 -1
  22. package/dist/db/index.d.cts +3 -2
  23. package/dist/db/index.d.ts +3 -2
  24. package/dist/db/index.js +2 -2
  25. package/dist/{endpoint-BVT6jcVW.d.cts → endpoint-CVWXh6oG.d.ts} +147 -15
  26. package/dist/{endpoint-BVT6jcVW.d.ts → endpoint-c9h5jriX.d.cts} +147 -15
  27. package/dist/engine/index.cjs +750 -40
  28. package/dist/engine/index.cjs.map +1 -1
  29. package/dist/engine/index.d.cts +6 -5
  30. package/dist/engine/index.d.ts +6 -5
  31. package/dist/engine/index.js +4 -4
  32. package/dist/{index-BS1gW4nV.d.cts → index-BZrJXnVh.d.ts} +142 -72
  33. package/dist/{index-BqCiHao8.d.cts → index-By8Dle5U.d.cts} +196 -21
  34. package/dist/{index-vwHoS0l2.d.ts → index-CwAJ7HEe.d.ts} +196 -21
  35. package/dist/{index-CCZqzych.d.ts → index-CxeQSfJP.d.cts} +142 -72
  36. package/dist/index.cjs +388 -1101
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +89 -1134
  39. package/dist/index.d.ts +89 -1134
  40. package/dist/index.js +133 -902
  41. package/dist/index.js.map +1 -1
  42. package/dist/openapi/index.cjs +32 -61
  43. package/dist/openapi/index.cjs.map +1 -1
  44. package/dist/openapi/index.d.cts +6 -2
  45. package/dist/openapi/index.d.ts +6 -2
  46. package/dist/openapi/index.js +34 -26
  47. package/dist/openapi/index.js.map +1 -1
  48. package/dist/{registry-Bsuf-orT.d.ts → registry-B3niOVYp.d.ts} +108 -170
  49. package/dist/{registry-BWttGlaT.d.cts → registry-CqPK2Qby.d.cts} +108 -170
  50. package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
  51. package/dist/stack.cjs.map +1 -0
  52. package/dist/stack.d.cts +76 -0
  53. package/dist/stack.d.ts +76 -0
  54. package/dist/stack.js +1 -0
  55. package/dist/test/index.cjs +482 -9
  56. package/dist/test/index.cjs.map +1 -1
  57. package/dist/test/index.d.cts +35 -3
  58. package/dist/test/index.d.ts +35 -3
  59. package/dist/test/index.js +480 -8
  60. package/dist/test/index.js.map +1 -1
  61. package/docs/README.md +7 -6
  62. package/docs/llms-full.txt +7 -260
  63. package/docs/llms.txt +0 -2
  64. package/package.json +9 -8
  65. package/stager/return_types.js +23 -0
  66. package/template/package.json +1 -1
  67. package/dist/chunk-74XDEF5J.js.map +0 -1
  68. package/dist/chunk-I3ON7MYF.js.map +0 -1
  69. package/dist/chunk-N32VDWKH.js.map +0 -1
  70. package/dist/chunk-QMVK4X3V.js.map +0 -1
  71. package/dist/chunk-SQC5EIWY.js.map +0 -1
  72. package/dist/chunk-W5ODXPY3.js.map +0 -1
  73. package/dist/purchases/keys.cjs.map +0 -1
  74. package/dist/purchases/keys.d.cts +0 -42
  75. package/dist/purchases/keys.d.ts +0 -42
  76. package/dist/purchases/keys.js +0 -1
  77. package/docs/config.md +0 -147
  78. package/docs/resources.md +0 -97
  79. package/template/config/secrets.ts +0 -24
  80. /package/dist/{chunk-YL4C5NRY.js.map → chunk-HQRJDARQ.js.map} +0 -0
  81. /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
package/dist/index.js CHANGED
@@ -18,15 +18,11 @@ import {
18
18
  timestamp,
19
19
  uuid,
20
20
  vector
21
- } from "./chunk-I3ON7MYF.js";
21
+ } from "./chunk-PY7YJDCT.js";
22
22
  import {
23
23
  defineError,
24
- entitlementFor,
25
- getErrorRegistry,
26
- recordEntitlement,
27
- recordSpend,
28
- spendFor
29
- } from "./chunk-N32VDWKH.js";
24
+ getErrorRegistry
25
+ } from "./chunk-R3KN6RHD.js";
30
26
  import {
31
27
  Cache,
32
28
  Database,
@@ -34,7 +30,6 @@ import {
34
30
  Flags,
35
31
  Log,
36
32
  Notifications,
37
- Purchases,
38
33
  Realtime,
39
34
  Secrets,
40
35
  Storage,
@@ -42,19 +37,19 @@ import {
42
37
  __requestALS,
43
38
  __runWithRuntime,
44
39
  __setRuntime
45
- } from "./chunk-SQC5EIWY.js";
40
+ } from "./chunk-OHALWEOG.js";
46
41
  import {
47
42
  TxPlanError,
48
43
  TxRefError,
49
44
  dec,
50
45
  inc,
51
46
  now
52
- } from "./chunk-W5ODXPY3.js";
47
+ } from "./chunk-NS5V43YQ.js";
53
48
  import {
54
49
  Controller,
55
50
  __resetRegisteredControllers,
56
51
  getRegisteredControllers
57
- } from "./chunk-YL4C5NRY.js";
52
+ } from "./chunk-HQRJDARQ.js";
58
53
  import {
59
54
  BadRequest,
60
55
  Conflict,
@@ -68,141 +63,9 @@ import {
68
63
  recordParam,
69
64
  recordRoute,
70
65
  recordThrows
71
- } from "./chunk-QMVK4X3V.js";
66
+ } from "./chunk-RCLNBJCM.js";
72
67
  import "./chunk-7D4SUZUM.js";
73
68
 
74
- // src/purchases/errors.ts
75
- function asPalstoreError(err) {
76
- if (!(err instanceof Error)) return null;
77
- const e = err;
78
- if (typeof e.code !== "string" || typeof e.status !== "number") return null;
79
- return { code: e.code, status: e.status, message: e.message };
80
- }
81
- function toHttpError(err) {
82
- if (err instanceof HttpError) return err;
83
- const p = asPalstoreError(err);
84
- if (!p) return err;
85
- switch (p.code) {
86
- case "entitlement_required": {
87
- const entitlement = err.entitlement ?? null;
88
- return new PalError(403, "entitlement_required", p.message, { entitlement });
89
- }
90
- case "quota_exceeded":
91
- case "credit_insufficient": {
92
- const state = err.state;
93
- return new PalError(429, p.code, p.message, state);
94
- }
95
- default:
96
- return err;
97
- }
98
- }
99
-
100
- // src/purchases/subject.ts
101
- var subjectByRequest = /* @__PURE__ */ new WeakMap();
102
- function storeEnv() {
103
- return process.env.PALSTORE_STORE_ENV === "sandbox" ? "sandbox" : "production";
104
- }
105
- async function currentSubjectId() {
106
- const store = __requestALS.getStore();
107
- if (!store) {
108
- throw new Error(
109
- "Purchases decorators used outside a request scope. @RequireEntitlement/@Spend resolve the subject from the request's authenticated user, so they only work inside an endpoint handler."
110
- );
111
- }
112
- const memoized = subjectByRequest.get(store);
113
- if (memoized) return memoized;
114
- const userId = store.userId;
115
- if (!userId) {
116
- throw new Unauthorized(
117
- "This endpoint requires an authenticated user: purchases entitlements and quota are per-user, and the subject is resolved from the request's identity."
118
- );
119
- }
120
- const resolving = Purchases.resolveSubject({ userRef: userId, storeEnv: storeEnv() }).then(
121
- (r) => r.subjectId
122
- );
123
- subjectByRequest.set(store, resolving);
124
- resolving.catch(() => subjectByRequest.delete(store));
125
- return resolving;
126
- }
127
-
128
- // src/purchases/decorators.ts
129
- var WRAPPED = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.wrapped");
130
- function idempotencyKeyFor(key) {
131
- const store = __requestALS.getStore();
132
- const base = store?.idempotencyKey || store?.requestId;
133
- if (!base) {
134
- throw new Error(
135
- "@Spend needs a request-scoped idempotency key, and the runtime supplied neither an Idempotency-Key header nor a request id. Spending without one would let a retried request charge the user twice."
136
- );
137
- }
138
- return `${base}:${key}`;
139
- }
140
- function ensureWrapper(target, fnName, descriptor) {
141
- const original = descriptor.value;
142
- if (typeof original !== "function") {
143
- throw new Error(
144
- `@RequireEntitlement/@Spend can only decorate a method; "${fnName}" is not one.`
145
- );
146
- }
147
- if (original[WRAPPED]) return;
148
- const wrapper = async function(...args) {
149
- const ctor = this.constructor;
150
- const entitlement = entitlementFor(ctor, fnName);
151
- const spend = spendFor(ctor, fnName);
152
- let subjectId;
153
- try {
154
- subjectId = await currentSubjectId();
155
- } catch (err) {
156
- throw toHttpError(err);
157
- }
158
- if (entitlement !== void 0) {
159
- try {
160
- await Purchases.require(subjectId, entitlement);
161
- } catch (err) {
162
- throw toHttpError(err);
163
- }
164
- }
165
- if (spend === void 0) {
166
- return original.apply(this, args);
167
- }
168
- let handlerFailure;
169
- try {
170
- return await Purchases.withSpend(
171
- subjectId,
172
- spend.key,
173
- { count: spend.count, idempotencyKey: idempotencyKeyFor(spend.key) },
174
- async () => {
175
- try {
176
- return await original.apply(this, args);
177
- } catch (err) {
178
- handlerFailure = { err };
179
- throw err;
180
- }
181
- }
182
- );
183
- } catch (err) {
184
- if (handlerFailure !== void 0 && handlerFailure.err === err) throw err;
185
- throw toHttpError(err);
186
- }
187
- };
188
- wrapper[WRAPPED] = true;
189
- descriptor.value = wrapper;
190
- }
191
- function RequireEntitlement(key) {
192
- return function(target, propertyKey, descriptor) {
193
- const fnName = String(propertyKey);
194
- recordEntitlement(target, fnName, key);
195
- ensureWrapper(target, fnName, descriptor);
196
- };
197
- }
198
- function Spend(key) {
199
- return function(target, propertyKey, descriptor) {
200
- const fnName = String(propertyKey);
201
- recordSpend(target, fnName, { key, count: 1 });
202
- ensureWrapper(target, fnName, descriptor);
203
- };
204
- }
205
-
206
69
  // src/clients/http.ts
207
70
  var PalbaseModuleError = class extends Error {
208
71
  code;
@@ -548,12 +411,12 @@ function buildFlagsClient(http, cfg) {
548
411
  return {
549
412
  data: Object.keys(values).map((name) => {
550
413
  const value = values[name];
551
- const flag2 = {
414
+ const flag = {
552
415
  name,
553
416
  enabled: flagEnabled(value)
554
417
  };
555
- if (typeof value === "string") flag2.variant = { name: value };
556
- return flag2;
418
+ if (typeof value === "string") flag.variant = { name: value };
419
+ return flag;
557
420
  }),
558
421
  error: null,
559
422
  status: res.status
@@ -781,13 +644,13 @@ function buildNotificationsClient(http) {
781
644
 
782
645
  // src/clients/realtime.ts
783
646
  import { createHmac } from "crypto";
784
- function mintToken(secret2) {
647
+ function mintToken(secret) {
785
648
  const now2 = Math.floor(Date.now() / 1e3);
786
649
  const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
787
650
  const payload = Buffer.from(
788
651
  JSON.stringify({ iss: "backend-runtime", role: "service_role", iat: now2, exp: now2 + 60 })
789
652
  ).toString("base64url");
790
- const sig = createHmac("sha256", secret2).update(`${header}.${payload}`).digest("base64url");
653
+ const sig = createHmac("sha256", secret).update(`${header}.${payload}`).digest("base64url");
791
654
  return `${header}.${payload}.${sig}`;
792
655
  }
793
656
  function buildRealtimeClient(cfg) {
@@ -958,11 +821,11 @@ function validateStoragePath(p) {
958
821
  );
959
822
  }
960
823
  }
961
- function toFileObject(bucket2, row) {
824
+ function toFileObject(bucket, row) {
962
825
  return {
963
826
  name: row.path ?? "",
964
827
  path: row.path ?? "",
965
- bucket: row.bucket ?? bucket2,
828
+ bucket: row.bucket ?? bucket,
966
829
  size: typeof row.size === "number" ? row.size : 0,
967
830
  contentType: row.contentType ?? "",
968
831
  checksum: row.checksum ?? "",
@@ -1078,35 +941,6 @@ function buildModuleClients(cfg) {
1078
941
  };
1079
942
  }
1080
943
 
1081
- // src/purchases/keys-gen.ts
1082
- function liveKeys(entries) {
1083
- return Object.entries(entries ?? {}).filter(([, def]) => def?.removed !== true).map(([key]) => key).sort();
1084
- }
1085
- function memberName(key) {
1086
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
1087
- }
1088
- function members(keys) {
1089
- if (keys.length === 0) return "";
1090
- return `
1091
- ${keys.map((k) => ` ${memberName(k)}: true;`).join("\n")}
1092
- `;
1093
- }
1094
- function makePurchasesDts(manifest) {
1095
- const entitlements = liveKeys(manifest.entitlements);
1096
- const limits = liveKeys({ ...manifest.limits, ...manifest.credits });
1097
- return `// palbase-purchases.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
1098
- // Source: the project's palstore catalog revision.
1099
-
1100
- declare module "@palbase/backend/purchases" {
1101
- interface Entitlements {${members(entitlements)}}
1102
-
1103
- interface Limits {${members(limits)}}
1104
- }
1105
-
1106
- export {};
1107
- `;
1108
- }
1109
-
1110
944
  // src/db/schema-json.ts
1111
945
  function defOf(column) {
1112
946
  return "_def" in column ? column._def : column;
@@ -1145,9 +979,45 @@ function policyToJSON(policy2) {
1145
979
  permissive: policy2.permissive !== false
1146
980
  };
1147
981
  }
1148
- function searchToJSON(search) {
982
+ function commonSearchFields(search, out) {
983
+ if (search.synonyms !== void 0 && Object.keys(search.synonyms).length > 0) {
984
+ out.synonyms = Object.fromEntries(
985
+ Object.entries(search.synonyms).map(([word, alts]) => [word, [...alts]])
986
+ );
987
+ }
988
+ if (search.validity === true) out.validity = true;
989
+ }
990
+ function searchToJSON(search, vectorColumn) {
991
+ if (search.from !== void 0 && search.model !== void 0) {
992
+ const out2 = {};
993
+ const textCols = search.text === false ? void 0 : Array.isArray(search.text) && search.text.length > 0 ? search.text : search.from;
994
+ if (textCols !== void 0) out2.text = { columns: [...textCols] };
995
+ const v = { metric: search.metric ?? "cosine" };
996
+ if (vectorColumn !== void 0) v.column = vectorColumn;
997
+ v.embed = {
998
+ provider: search.model.provider,
999
+ model: search.model.model,
1000
+ from: [...search.from],
1001
+ ...search.model.apiKeyName !== void 0 ? { apiKeyName: search.model.apiKeyName } : {},
1002
+ ...search.model.dimensions !== void 0 ? { dimensions: search.model.dimensions } : {},
1003
+ ...search.model.baseURL !== void 0 ? { baseURL: search.model.baseURL } : {}
1004
+ };
1005
+ if (search.staleness !== void 0) v.staleness = search.staleness;
1006
+ if (vectorColumn === void 0) {
1007
+ v.mode = "chunks";
1008
+ if (search.chunks !== void 0) {
1009
+ const c = {};
1010
+ if (search.chunks.size !== void 0 && search.chunks.size > 0) c.sizeChars = search.chunks.size;
1011
+ if (search.chunks.overlap !== void 0 && search.chunks.overlap > 0) c.overlapChars = search.chunks.overlap;
1012
+ if (Object.keys(c).length > 0) v.chunks = c;
1013
+ }
1014
+ }
1015
+ out2.vector = [v];
1016
+ commonSearchFields(search, out2);
1017
+ return out2;
1018
+ }
1149
1019
  const out = {};
1150
- if (search.text !== void 0 && search.text.length > 0) out.text = { columns: [...search.text] };
1020
+ if (Array.isArray(search.text) && search.text.length > 0) out.text = { columns: [...search.text] };
1151
1021
  const legs = search.vector === void 0 ? [] : Array.isArray(search.vector) ? search.vector : [search.vector];
1152
1022
  if (legs.length > 0) {
1153
1023
  out.vector = legs.map((leg) => {
@@ -1167,8 +1037,17 @@ function searchToJSON(search) {
1167
1037
  return v;
1168
1038
  });
1169
1039
  }
1040
+ commonSearchFields(search, out);
1170
1041
  return out;
1171
1042
  }
1043
+ function memoryToJSON(m) {
1044
+ return {
1045
+ from: [...m.from],
1046
+ into: m.into,
1047
+ ...m.subject !== void 0 ? { subject: m.subject } : {},
1048
+ extract: { provider: m.extract.provider, model: m.extract.model }
1049
+ };
1050
+ }
1172
1051
  function tableToJSON(table) {
1173
1052
  const columns = {};
1174
1053
  for (const [name, column] of Object.entries(table.columns)) {
@@ -1205,9 +1084,13 @@ function tableToJSON(table) {
1205
1084
  out.indexes = table.indexes.map((i) => ({ name: i.name, columns: [...i.columns] }));
1206
1085
  }
1207
1086
  if (table.search !== void 0) {
1208
- const sj = searchToJSON(table.search);
1087
+ const vectorColumn = Object.entries(columns).find(([, c]) => c.dimensions !== void 0)?.[0];
1088
+ const sj = searchToJSON(table.search, vectorColumn);
1209
1089
  if (sj.text !== void 0 || sj.vector !== void 0) out.search = sj;
1210
1090
  }
1091
+ if (table.memory !== void 0) {
1092
+ out.memory = memoryToJSON(table.memory);
1093
+ }
1211
1094
  return out;
1212
1095
  }
1213
1096
  function toSchemaJSON(schema) {
@@ -1335,10 +1218,10 @@ function buildChildEdges(schema) {
1335
1218
  const ref = builder._def.references;
1336
1219
  if (ref === void 0) continue;
1337
1220
  if (ref.table === childName) continue;
1338
- const bucket2 = edges.get(ref.table);
1339
- if (bucket2 === void 0) continue;
1340
- if (bucket2.some((e) => e.child === childName)) continue;
1341
- bucket2.push({ child: childName, column: col });
1221
+ const bucket = edges.get(ref.table);
1222
+ if (bucket === void 0) continue;
1223
+ if (bucket.some((e) => e.child === childName)) continue;
1224
+ bucket.push({ child: childName, column: col });
1342
1225
  }
1343
1226
  }
1344
1227
  return edges;
@@ -1412,638 +1295,50 @@ export {};
1412
1295
  `;
1413
1296
  }
1414
1297
 
1415
- // src/config/storage.ts
1416
- var STORAGE_CONFIG_KIND = "storage";
1417
- var VARIANT_FORMATS = ["webp", "avif", "jpeg", "png"];
1418
- var VARIANT_FITS = ["cover", "contain", "inside"];
1419
- function normalizeVariants(input) {
1420
- if (input === null || typeof input !== "object" || Array.isArray(input)) {
1421
- throw new Error("bucket variants must be an object of { <name>: { width?, height?, format? } }");
1422
- }
1423
- const out = {};
1424
- for (const [name, spec] of Object.entries(input)) {
1425
- if (!/^[a-z0-9][a-z0-9_-]*$/i.test(name)) {
1426
- throw new Error(
1427
- `bucket variant name "${name}" must be alphanumeric with optional - or _ (e.g. "thumb", "card_2x")`
1428
- );
1429
- }
1430
- if (spec === null || typeof spec !== "object" || Array.isArray(spec)) {
1431
- throw new Error(`bucket variant "${name}" must be an object like { width: 100, height: 100 }`);
1432
- }
1433
- for (const dim of ["width", "height"]) {
1434
- const value = spec[dim];
1435
- if (value === void 0) continue;
1436
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
1437
- throw new Error(`bucket variant "${name}" ${dim} must be a positive integer, got ${String(value)}`);
1438
- }
1439
- }
1440
- if (spec.width === void 0 && spec.height === void 0) {
1441
- throw new Error(`bucket variant "${name}" must set width and/or height`);
1442
- }
1443
- if (spec.format !== void 0 && !VARIANT_FORMATS.includes(spec.format)) {
1444
- throw new Error(
1445
- `bucket variant "${name}" format must be one of ${VARIANT_FORMATS.join(", ")}, got "${String(spec.format)}"`
1446
- );
1447
- }
1448
- if (spec.fit !== void 0 && !VARIANT_FITS.includes(spec.fit)) {
1449
- throw new Error(
1450
- `bucket variant "${name}" fit must be one of ${VARIANT_FITS.join(", ")}, got "${String(spec.fit)}"`
1451
- );
1452
- }
1453
- if (spec.quality !== void 0) {
1454
- const q = spec.quality;
1455
- if (typeof q !== "number" || !Number.isInteger(q) || q < 1 || q > 100) {
1456
- throw new Error(`bucket variant "${name}" quality must be an integer 1\u2013100, got ${String(q)}`);
1457
- }
1458
- }
1459
- if (spec.fit === "cover" && (spec.width === void 0 || spec.height === void 0)) {
1460
- throw new Error(`bucket variant "${name}" uses fit "cover", which needs BOTH width and height`);
1461
- }
1462
- out[name] = {
1463
- ...spec.width !== void 0 ? { width: spec.width } : {},
1464
- ...spec.height !== void 0 ? { height: spec.height } : {},
1465
- ...spec.fit !== void 0 ? { fit: spec.fit } : {},
1466
- ...spec.format !== void 0 ? { format: spec.format } : {},
1467
- ...spec.quality !== void 0 ? { quality: spec.quality } : {}
1468
- };
1469
- }
1470
- return out;
1298
+ // src/stack-gen.ts
1299
+ function liveNames(names) {
1300
+ return [...new Set(names)].sort();
1471
1301
  }
1472
- var UNIT_BYTES = {
1473
- b: 1,
1474
- kb: 1024,
1475
- mb: 1024 ** 2,
1476
- gb: 1024 ** 3,
1477
- tb: 1024 ** 4
1478
- };
1479
- function parseFileSizeLimit(input) {
1480
- if (typeof input === "number") {
1481
- if (!Number.isFinite(input) || input < 0 || !Number.isInteger(input)) {
1482
- throw new Error(
1483
- `bucket fileSizeLimit must be a non-negative integer number of bytes, got ${input}`
1484
- );
1485
- }
1486
- return input;
1487
- }
1488
- const trimmed = input.trim();
1489
- const match = /^(\d+(?:\.\d+)?)\s*([a-zA-Z]+)?$/.exec(trimmed);
1490
- if (!match) {
1491
- throw new Error(
1492
- `bucket fileSizeLimit string must be "<number><unit>" like "5MB" or "1GB", got "${input}"`
1493
- );
1494
- }
1495
- const value = Number.parseFloat(match[1]);
1496
- const unit = (match[2] ?? "b").toLowerCase();
1497
- const multiplier = UNIT_BYTES[unit];
1498
- if (multiplier === void 0) {
1499
- throw new Error(
1500
- `bucket fileSizeLimit has an unknown unit "${match[2]}" \u2014 use B, KB, MB, GB, or TB (e.g. "5MB")`
1501
- );
1502
- }
1503
- const bytes = value * multiplier;
1504
- if (!Number.isFinite(bytes) || bytes < 0) {
1505
- throw new Error(`bucket fileSizeLimit resolved to an invalid byte count: ${bytes}`);
1506
- }
1507
- return Math.round(bytes);
1508
- }
1509
- var MIME_RE = /^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*\/(?:\*|[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*)$/;
1510
- function bucket(opts = {}) {
1511
- const fileSizeLimit = opts.fileSizeLimit === void 0 ? null : parseFileSizeLimit(opts.fileSizeLimit);
1512
- let allowedMimeTypes = null;
1513
- if (opts.allowedMimeTypes !== void 0) {
1514
- if (!Array.isArray(opts.allowedMimeTypes)) {
1515
- throw new Error("bucket allowedMimeTypes must be an array of MIME-type strings");
1516
- }
1517
- for (const mime of opts.allowedMimeTypes) {
1518
- if (typeof mime !== "string" || !MIME_RE.test(mime.trim())) {
1519
- throw new Error(
1520
- `bucket allowedMimeTypes entry "${mime}" is not a valid MIME type (expected "type/subtype", e.g. "image/png")`
1521
- );
1522
- }
1523
- }
1524
- allowedMimeTypes = [...new Set(opts.allowedMimeTypes.map((m) => m.trim()))];
1525
- }
1526
- return {
1527
- public: opts.public ?? false,
1528
- fileSizeLimit,
1529
- allowedMimeTypes,
1530
- variants: opts.variants === void 0 ? null : normalizeVariants(opts.variants)
1531
- };
1302
+ function memberName(name) {
1303
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
1532
1304
  }
1533
- function defineStorage(input) {
1534
- if (input === null || typeof input !== "object" || typeof input.buckets !== "object") {
1535
- throw new Error("defineStorage expects { buckets: { <name>: bucket({...}) } }");
1536
- }
1537
- const buckets = {};
1538
- for (const name of Object.keys(input.buckets)) {
1539
- if (name.length === 0) {
1540
- throw new Error("bucket name must be a non-empty string");
1541
- }
1542
- const def = input.buckets[name];
1543
- if (def === void 0) continue;
1544
- buckets[name] = def;
1545
- }
1546
- return { __config: STORAGE_CONFIG_KIND, buckets };
1305
+ function members(names) {
1306
+ if (names.length === 0) return "";
1307
+ return `
1308
+ ${names.map((n) => ` ${memberName(n)}: true;`).join("\n")}
1309
+ `;
1547
1310
  }
1548
-
1549
- // src/config/egress.ts
1550
- var EGRESS_CONFIG_KIND = "egress";
1551
- var EGRESS_TIMEOUT_MIN_MS = 1e3;
1552
- var EGRESS_TIMEOUT_MAX_MS = 3e5;
1553
- var EGRESS_TIMEOUT_DEFAULT_MS = 3e4;
1554
- function defineEgress(input) {
1555
- if (input === null || typeof input !== "object" || !Array.isArray(input.hosts)) {
1556
- throw new Error("defineEgress expects { hosts: string[] }");
1557
- }
1558
- const hosts = [];
1559
- for (const h of input.hosts) {
1560
- if (typeof h !== "string" || h.trim().length === 0) {
1561
- throw new Error("defineEgress: every host must be a non-empty string");
1562
- }
1563
- hosts.push(h.trim().toLowerCase());
1564
- }
1565
- if (input.timeoutMs === void 0) {
1566
- return { __config: EGRESS_CONFIG_KIND, hosts };
1567
- }
1568
- const timeoutMs = input.timeoutMs;
1569
- if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs)) {
1570
- throw new Error("defineEgress: timeoutMs must be an integer number of milliseconds");
1571
- }
1572
- if (timeoutMs < EGRESS_TIMEOUT_MIN_MS || timeoutMs > EGRESS_TIMEOUT_MAX_MS) {
1573
- throw new Error(
1574
- `defineEgress: timeoutMs must be between ${EGRESS_TIMEOUT_MIN_MS} and ${EGRESS_TIMEOUT_MAX_MS} (got ${timeoutMs})`
1575
- );
1576
- }
1577
- return { __config: EGRESS_CONFIG_KIND, hosts, timeoutMs };
1311
+ function bucketMembers(buckets) {
1312
+ const normalized = buckets.map((b) => typeof b === "string" ? { name: b, variants: [] } : b);
1313
+ const byName = /* @__PURE__ */ new Map();
1314
+ for (const b of normalized) byName.set(b.name, b.variants);
1315
+ const names = [...byName.keys()].sort();
1316
+ if (names.length === 0) return "";
1317
+ const rows = names.map((name) => {
1318
+ const variants = [...new Set(byName.get(name) ?? [])].sort();
1319
+ const union = variants.length === 0 ? "never" : variants.map((v) => JSON.stringify(v)).join(" | ");
1320
+ return ` ${memberName(name)}: { variants: ${union} };`;
1321
+ });
1322
+ return `
1323
+ ${rows.join("\n")}
1324
+ `;
1578
1325
  }
1326
+ function makeStackDts(names) {
1327
+ const secrets = liveNames(names.secrets);
1328
+ const flags = liveNames(names.flags);
1329
+ return `// palbase-stack.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
1330
+ // Source: the linked environment's stack.
1579
1331
 
1580
- // src/config/notifications.ts
1581
- var NOTIFICATIONS_CONFIG_KIND = "notifications";
1582
- var RESERVED_SECRET_PREFIX = "PB_NOTIFICATIONS";
1583
- var PROVIDER_CATALOG = {
1584
- apns: {
1585
- channel: "push",
1586
- required: ["teamId", "keyId", "bundleId"],
1587
- optional: ["isProduction"],
1588
- secrets: ["p8"]
1589
- },
1590
- fcm: {
1591
- channel: "push",
1592
- required: [],
1593
- optional: [],
1594
- secrets: ["serviceAccount"]
1595
- },
1596
- sendgrid: {
1597
- channel: "email",
1598
- required: ["fromDomain"],
1599
- optional: [],
1600
- secrets: ["apiKey"]
1601
- },
1602
- ses: {
1603
- channel: "email",
1604
- required: ["region", "accessKeyId", "fromDomain"],
1605
- optional: [],
1606
- secrets: ["secretAccessKey"]
1607
- },
1608
- smtp: {
1609
- channel: "email",
1610
- required: ["host", "port", "fromEmail"],
1611
- optional: ["username", "useStarttls"],
1612
- secrets: ["password"]
1613
- },
1614
- acs: {
1615
- channel: "email",
1616
- required: ["fromEmail"],
1617
- optional: ["fromName"],
1618
- secrets: ["connectionString"]
1619
- },
1620
- twilio: {
1621
- channel: "sms",
1622
- // account_sid required; one of from_number / messaging_service_sid required
1623
- // (enforced by the refine in buildProvider, not by the flat `required` list).
1624
- required: ["accountSid"],
1625
- optional: ["fromNumber", "messagingServiceSid"],
1626
- secrets: ["authToken"]
1627
- }
1628
- };
1629
- var DEFAULT_TEMPLATE_LOCALE = "en";
1630
- function extractVariables(...sources) {
1631
- const found = /* @__PURE__ */ new Set();
1632
- for (const src of sources) {
1633
- if (!src) continue;
1634
- for (const match of src.matchAll(/\{\{\{?\s*([^{}]+?)\s*\}?\}\}/g)) {
1635
- const token = (match[1] ?? "").trim();
1636
- if (token === "" || "#/^!>&".includes(token.charAt(0)) || token === "else") continue;
1637
- if (/\s/.test(token)) continue;
1638
- const root = token.split(".")[0] ?? "";
1639
- if (root === "" || root === "this" || root.startsWith("@")) continue;
1640
- found.add(root);
1641
- }
1642
- }
1643
- return [...found].sort();
1644
- }
1645
- function buildEmailTemplate(slug, opts) {
1646
- const row = (locale, c) => {
1647
- if (!c.subject) throw new Error(`notifications: email template "${slug}" (${locale}) needs a subject`);
1648
- if (!c.html) throw new Error(`notifications: email template "${slug}" (${locale}) needs an html body`);
1649
- const def = {
1650
- slug,
1651
- locale,
1652
- subject: c.subject,
1653
- html_body: c.html,
1654
- variables: c.variables ? [...c.variables] : extractVariables(c.subject, c.html, c.text)
1655
- };
1656
- if (c.text !== void 0) def.text_body = c.text;
1657
- return def;
1658
- };
1659
- const rows = [row(opts.locale ?? DEFAULT_TEMPLATE_LOCALE, opts)];
1660
- for (const [locale, content] of Object.entries(opts.locales ?? {})) {
1661
- rows.push(row(locale, content));
1662
- }
1663
- return rows;
1664
- }
1665
- function buildSmsTemplate(slug, opts) {
1666
- const row = (locale, c) => {
1667
- if (!c.body) throw new Error(`notifications: sms template "${slug}" (${locale}) needs a body`);
1668
- return {
1669
- slug,
1670
- locale,
1671
- body: c.body,
1672
- variables: c.variables ? [...c.variables] : extractVariables(c.body)
1673
- };
1674
- };
1675
- const rows = [row(opts.locale ?? DEFAULT_TEMPLATE_LOCALE, opts)];
1676
- for (const [locale, content] of Object.entries(opts.locales ?? {})) {
1677
- rows.push(row(locale, content));
1678
- }
1679
- return rows;
1680
- }
1681
- function buildTemplates(input) {
1682
- const out = { email: [], sms: [] };
1683
- const seen = /* @__PURE__ */ new Set();
1684
- const claim = (channel, slug, locale) => {
1685
- const key = `${channel}/${slug}/${locale}`;
1686
- if (seen.has(key)) {
1687
- throw new Error(`notifications: ${channel} template "${slug}" declares locale "${locale}" twice`);
1688
- }
1689
- seen.add(key);
1690
- };
1691
- for (const [slug, opts] of Object.entries(input.email ?? {})) {
1692
- for (const row of buildEmailTemplate(slug, opts)) {
1693
- claim("email", row.slug, row.locale);
1694
- out.email.push(row);
1695
- }
1696
- }
1697
- for (const [slug, opts] of Object.entries(input.sms ?? {})) {
1698
- for (const row of buildSmsTemplate(slug, opts)) {
1699
- claim("sms", row.slug, row.locale);
1700
- out.sms.push(row);
1701
- }
1702
- }
1703
- return out;
1704
- }
1705
- function buildProvider(name, opts) {
1706
- const entry = PROVIDER_CATALOG[name];
1707
- const src = { ...opts };
1708
- const enabled = src.enabled === void 0 ? false : Boolean(src.enabled);
1709
- if (!enabled) {
1710
- return { enabled: false };
1711
- }
1712
- const def = { enabled: true };
1713
- for (const field of entry.required) {
1714
- const value = src[field];
1715
- if (value === void 0 || value === null || value === "") {
1716
- throw new Error(
1717
- `notifications: provider "${name}" is enabled but missing required field "${field}"`
1718
- );
1719
- }
1720
- def[field] = value;
1721
- }
1722
- for (const field of entry.optional) {
1723
- if (src[field] !== void 0) {
1724
- def[field] = src[field];
1725
- }
1726
- }
1727
- if (name === "twilio") {
1728
- const hasFrom = Boolean(def.fromNumber);
1729
- const hasMsg = Boolean(def.messagingServiceSid);
1730
- if (!hasFrom && !hasMsg) {
1731
- throw new Error(
1732
- 'notifications: provider "twilio" requires one of "fromNumber" or "messagingServiceSid"'
1733
- );
1734
- }
1735
- }
1736
- return def;
1737
- }
1738
- function defineNotifications(input) {
1739
- if (input === null || typeof input !== "object") {
1740
- throw new Error("defineNotifications expects { push?, email?, sms? }");
1741
- }
1742
- const config = {
1743
- __config: NOTIFICATIONS_CONFIG_KIND,
1744
- push: {},
1745
- email: {},
1746
- sms: {},
1747
- templates: buildTemplates(input.templates ?? {})
1748
- };
1749
- const push = input.push ?? {};
1750
- if (push.apns !== void 0) config.push.apns = buildProvider("apns", push.apns);
1751
- if (push.fcm !== void 0) config.push.fcm = buildProvider("fcm", push.fcm);
1752
- const email = input.email ?? {};
1753
- if (email.sendgrid !== void 0) config.email.sendgrid = buildProvider("sendgrid", email.sendgrid);
1754
- if (email.ses !== void 0) config.email.ses = buildProvider("ses", email.ses);
1755
- if (email.smtp !== void 0) config.email.smtp = buildProvider("smtp", email.smtp);
1756
- if (email.acs !== void 0) config.email.acs = buildProvider("acs", email.acs);
1757
- const sms = input.sms ?? {};
1758
- if (sms.twilio !== void 0) config.sms.twilio = buildProvider("twilio", sms.twilio);
1759
- return config;
1760
- }
1761
- function reservedSecretKey(provider, secretField) {
1762
- return `${RESERVED_SECRET_PREFIX}_${camelToUpperSnake(provider)}_${camelToUpperSnake(secretField)}`;
1763
- }
1764
- function camelToUpperSnake(s) {
1765
- return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
1766
- }
1332
+ declare module "@palbase/backend/stack" {
1333
+ interface Secrets {${members(secrets)}}
1767
1334
 
1768
- // src/config/test-users.ts
1769
- var TEST_USERS_CONFIG_KIND = "test-users";
1770
- var TEST_USER_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
1771
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1772
- var E164_RE = /^\+[1-9]\d{1,14}$/;
1773
- var OTP_LENGTH = 6;
1774
- var OTP_RE = /^\d{6}$/;
1775
- var MIN_PASSWORD_LENGTH = 8;
1776
- function isPlainObject(value) {
1777
- return typeof value === "object" && value !== null && !Array.isArray(value);
1778
- }
1779
- function normalizeSeed(seed, userName) {
1780
- if (seed === void 0) return {};
1781
- if (!isPlainObject(seed)) {
1782
- throw new Error(`testUser ${JSON.stringify(userName)}: seed must be { <table>: [ {...} ] }`);
1783
- }
1784
- const out = {};
1785
- for (const [table, rows] of Object.entries(seed)) {
1786
- if (!Array.isArray(rows)) {
1787
- throw new Error(
1788
- `testUser ${JSON.stringify(userName)}: seed.${table} must be an array of rows`
1789
- );
1790
- }
1791
- if (rows.length === 0) {
1792
- throw new Error(
1793
- `testUser ${JSON.stringify(userName)}: seed.${table} is empty \u2014 remove it or add a row`
1794
- );
1795
- }
1796
- for (const row of rows) {
1797
- if (!isPlainObject(row)) {
1798
- throw new Error(
1799
- `testUser ${JSON.stringify(userName)}: every row in seed.${table} must be an object`
1800
- );
1801
- }
1802
- }
1803
- out[table] = rows;
1804
- }
1805
- return out;
1806
- }
1807
- function testUser(opts = {}) {
1808
- if (!isPlainObject(opts)) {
1809
- throw new Error("testUser() expects { email?, password?, phone?, otp?, seed? }");
1810
- }
1811
- const hasEmail = opts.email !== void 0;
1812
- const hasPassword = opts.password !== void 0;
1813
- if (hasEmail !== hasPassword) {
1814
- throw new Error(
1815
- "testUser() needs email AND password together (a fixture with stable credentials) or neither (a template the server generates credentials for)"
1816
- );
1817
- }
1818
- let email = null;
1819
- let password = null;
1820
- if (hasEmail) {
1821
- if (typeof opts.email !== "string" || !EMAIL_RE.test(opts.email)) {
1822
- throw new Error(`testUser email ${JSON.stringify(opts.email)} is not a valid address`);
1823
- }
1824
- if (typeof opts.password !== "string" || opts.password.length < MIN_PASSWORD_LENGTH) {
1825
- throw new Error(
1826
- `testUser password must be at least ${MIN_PASSWORD_LENGTH} characters (got ${typeof opts.password === "string" ? opts.password.length : 0})`
1827
- );
1828
- }
1829
- email = opts.email;
1830
- password = opts.password;
1831
- }
1832
- const hasPhone = opts.phone !== void 0;
1833
- const hasOTP = opts.otp !== void 0;
1834
- if (hasPhone !== hasOTP) {
1835
- throw new Error(
1836
- "testUser() needs phone AND otp together \u2014 nothing can receive an SMS at a fabricated number, so a phone fixture without its fixed code can never be signed in as"
1837
- );
1838
- }
1839
- let phone = null;
1840
- let otp = null;
1841
- if (hasPhone) {
1842
- if (typeof opts.phone !== "string" || !E164_RE.test(opts.phone)) {
1843
- throw new Error(
1844
- `testUser phone ${JSON.stringify(opts.phone)} must be E.164, e.g. "+905551112233"`
1845
- );
1846
- }
1847
- if (typeof opts.otp !== "string" || !OTP_RE.test(opts.otp)) {
1848
- throw new Error(
1849
- `testUser otp ${JSON.stringify(opts.otp)} must be ${OTP_LENGTH} digits`
1850
- );
1851
- }
1852
- phone = opts.phone;
1853
- otp = opts.otp;
1854
- }
1855
- return { email, password, phone, otp, seed: normalizeSeed(opts.seed, email ?? "<template>") };
1856
- }
1857
- function defineTestUsers(input) {
1858
- if (!isPlainObject(input) || !isPlainObject(input.users)) {
1859
- throw new Error("defineTestUsers expects { users: { <name>: testUser({...}) } }");
1860
- }
1861
- const users = {};
1862
- const emails = /* @__PURE__ */ new Map();
1863
- for (const name of Object.keys(input.users)) {
1864
- if (!TEST_USER_NAME_RE.test(name)) {
1865
- throw new Error(
1866
- `test user name ${JSON.stringify(name)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
1867
- );
1868
- }
1869
- const def = input.users[name];
1870
- if (def === void 0) continue;
1871
- if (def.email !== null) {
1872
- const lower = def.email.toLowerCase();
1873
- const already = emails.get(lower);
1874
- if (already !== void 0) {
1875
- throw new Error(
1876
- `test users ${JSON.stringify(already)} and ${JSON.stringify(name)} declare the same e-mail ${JSON.stringify(def.email)}`
1877
- );
1878
- }
1879
- emails.set(lower, name);
1880
- }
1881
- users[name] = def;
1882
- }
1883
- return { __config: TEST_USERS_CONFIG_KIND, users };
1884
- }
1335
+ interface Flags {${members(flags)}}
1885
1336
 
1886
- // src/config/flags.ts
1887
- var FLAGS_CONFIG_KIND = "flags";
1888
- var FLAG_KEY_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
1889
- function valueMatchesType(type, value) {
1890
- switch (type) {
1891
- case "boolean":
1892
- return typeof value === "boolean";
1893
- case "number":
1894
- return typeof value === "number" && Number.isFinite(value);
1895
- case "string":
1896
- return typeof value === "string";
1897
- case "json":
1898
- return typeof value === "object" && value !== null && !Array.isArray(value);
1899
- }
1900
- }
1901
- function flag(opts) {
1902
- if (opts === null || typeof opts !== "object") {
1903
- throw new Error("flag() expects { type, default, variants?, description? }");
1904
- }
1905
- const { type } = opts;
1906
- if (type !== "boolean" && type !== "number" && type !== "string" && type !== "json") {
1907
- throw new Error(`flag type must be "boolean", "number", "string", or "json", got ${JSON.stringify(type)}`);
1908
- }
1909
- if (!valueMatchesType(type, opts.default)) {
1910
- throw new Error(
1911
- `flag default ${JSON.stringify(opts.default)} does not match type "${type}"`
1912
- );
1913
- }
1914
- let variants = null;
1915
- if (opts.variants !== void 0) {
1916
- if (opts.type !== "string") {
1917
- throw new Error(`flag variants are only valid for type "string" (got type "${type}")`);
1918
- }
1919
- if (!Array.isArray(opts.variants)) {
1920
- throw new Error("flag variants must be an array of strings");
1921
- }
1922
- for (const v of opts.variants) {
1923
- if (typeof v !== "string" || v.length === 0) {
1924
- throw new Error(`flag variant ${JSON.stringify(v)} must be a non-empty string`);
1925
- }
1926
- }
1927
- variants = [...new Set(opts.variants)];
1928
- if (variants.length === 0) {
1929
- throw new Error("flag variants must be a non-empty list when supplied");
1930
- }
1931
- if (!variants.includes(opts.default)) {
1932
- throw new Error(
1933
- `flag default ${JSON.stringify(opts.default)} is not one of its variants [${variants.map((v) => JSON.stringify(v)).join(", ")}]`
1934
- );
1935
- }
1936
- }
1937
- let conditions = null;
1938
- if (opts.conditions !== void 0) {
1939
- if (!Array.isArray(opts.conditions)) {
1940
- throw new Error("flag conditions must be an array of { when, value }");
1941
- }
1942
- const compiled = [];
1943
- for (const [i, branch] of opts.conditions.entries()) {
1944
- if (branch === null || typeof branch !== "object") {
1945
- throw new Error(`flag condition ${i} must be an object { when, value }`);
1946
- }
1947
- if (typeof branch.when !== "string" || branch.when.trim().length === 0) {
1948
- throw new Error(`flag condition ${i} needs a non-empty "when" expression`);
1949
- }
1950
- if (!valueMatchesType(type, branch.value)) {
1951
- throw new Error(
1952
- `flag condition ${i} value ${JSON.stringify(branch.value)} does not match type "${type}"`
1953
- );
1954
- }
1955
- if (variants !== null && !variants.includes(branch.value)) {
1956
- throw new Error(
1957
- `flag condition ${i} value ${JSON.stringify(branch.value)} is not one of its variants [${variants.map((v) => JSON.stringify(v)).join(", ")}]`
1958
- );
1959
- }
1960
- compiled.push({ when: branch.when.trim(), value: branch.value });
1961
- }
1962
- conditions = compiled.length > 0 ? compiled : null;
1963
- }
1964
- let description = null;
1965
- if (opts.description !== void 0) {
1966
- if (typeof opts.description !== "string") {
1967
- throw new Error("flag description must be a string");
1968
- }
1969
- const trimmed = opts.description.trim();
1970
- description = trimmed.length > 0 ? trimmed : null;
1971
- }
1972
- return {
1973
- type,
1974
- default: opts.default,
1975
- variants,
1976
- conditions,
1977
- description
1978
- };
1979
- }
1980
- function defineFlags(input) {
1981
- if (input === null || typeof input !== "object" || typeof input.flags !== "object") {
1982
- throw new Error("defineFlags expects { flags: { <key>: flag({...}) } }");
1983
- }
1984
- const flags = {};
1985
- for (const key of Object.keys(input.flags)) {
1986
- if (!FLAG_KEY_RE.test(key)) {
1987
- throw new Error(
1988
- `flag key ${JSON.stringify(key)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
1989
- );
1990
- }
1991
- const def = input.flags[key];
1992
- if (def === void 0) continue;
1993
- flags[key] = def;
1994
- }
1995
- return { __config: FLAGS_CONFIG_KIND, flags };
1337
+ interface Buckets {${bucketMembers(names.buckets)}}
1996
1338
  }
1997
1339
 
1998
- // src/config/secrets.ts
1999
- var SECRETS_CONFIG_KIND = "secrets";
2000
- var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
2001
- function secret(name, opts = {}) {
2002
- if (typeof name !== "string" || !SECRET_NAME_RE.test(name)) {
2003
- throw new Error(
2004
- `secret name ${JSON.stringify(name)} is invalid \u2014 must be an environment-variable name: an uppercase letter, then uppercase letters, digits, and underscores (e.g. "NEO4J_PASSWORD")`
2005
- );
2006
- }
2007
- if (opts === null || typeof opts !== "object") {
2008
- throw new Error(`secret("${name}") expects { required?, description? }`);
2009
- }
2010
- let required = true;
2011
- if (opts.required !== void 0) {
2012
- if (typeof opts.required !== "boolean") {
2013
- throw new Error(`secret("${name}") required must be a boolean`);
2014
- }
2015
- required = opts.required;
2016
- }
2017
- let description = null;
2018
- if (opts.description !== void 0) {
2019
- if (typeof opts.description !== "string") {
2020
- throw new Error(`secret("${name}") description must be a string`);
2021
- }
2022
- const trimmed = opts.description.trim();
2023
- description = trimmed.length > 0 ? trimmed : null;
2024
- }
2025
- return { name, required, description };
2026
- }
2027
- function defineSecrets(input) {
2028
- if (input === null || typeof input !== "object" || !Array.isArray(input.secrets)) {
2029
- throw new Error('defineSecrets expects { secrets: [secret("NAME"), ...] }');
2030
- }
2031
- const byName = /* @__PURE__ */ new Map();
2032
- for (const def of input.secrets) {
2033
- if (def === null || typeof def !== "object" || typeof def.name !== "string" || typeof def.required !== "boolean") {
2034
- throw new Error(
2035
- `defineSecrets: every entry must come from secret() \u2014 got ${JSON.stringify(def)}`
2036
- );
2037
- }
2038
- if (byName.has(def.name)) {
2039
- throw new Error(
2040
- `secret "${def.name}" is declared twice in config/secrets.ts \u2014 remove one; a name has exactly one requirement`
2041
- );
2042
- }
2043
- byName.set(def.name, def);
2044
- }
2045
- const secrets = [...byName.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
2046
- return { __config: SECRETS_CONFIG_KIND, secrets };
1340
+ export {};
1341
+ `;
2047
1342
  }
2048
1343
 
2049
1344
  // src/decorators/methods.ts
@@ -2240,21 +1535,36 @@ function validateUploadConfigShape(c) {
2240
1535
  if (c === null || typeof c !== "object") {
2241
1536
  throw new Error("@Upload config must be an object { bucket, pathTemplate, ... }");
2242
1537
  }
2243
- if (typeof c.bucket !== "string" || c.bucket.length === 0) {
1538
+ const bucket = c.bucket;
1539
+ if (typeof bucket !== "string" || bucket.length === 0) {
2244
1540
  throw new Error("@Upload config.bucket must be a non-empty bucket name");
2245
1541
  }
2246
1542
  if (typeof c.pathTemplate !== "string" || c.pathTemplate.length === 0) {
2247
1543
  throw new Error("@Upload config.pathTemplate must be a non-empty key template");
2248
1544
  }
2249
1545
  }
2250
- function validateUploadAgainstStorage(uploadConfig, storage, routeLabel) {
2251
- const def = storage.buckets[uploadConfig.bucket];
2252
- if (def === void 0) {
2253
- const known = Object.keys(storage.buckets);
2254
- throw new Error(
2255
- `@Upload route ${routeLabel} targets bucket "${uploadConfig.bucket}" which is not declared in defineStorage(...). ` + (known.length ? `Known buckets: ${known.join(", ")}.` : "No buckets are declared.")
2256
- );
2257
- }
1546
+
1547
+ // src/decorators/sse.ts
1548
+ function Sse(subpath = "", config) {
1549
+ const { auth, rateLimit, ...sseConfig } = config;
1550
+ const options = {
1551
+ sseConfig,
1552
+ ...auth !== void 0 ? { auth } : {},
1553
+ ...rateLimit !== void 0 ? { rateLimit } : {}
1554
+ };
1555
+ return function(target, propertyKey) {
1556
+ recordRoute(target, String(propertyKey), "POST", subpath, options);
1557
+ };
1558
+ }
1559
+ function SseOut() {
1560
+ return function(target, propertyKey, parameterIndex) {
1561
+ recordParam(target, String(propertyKey), { index: parameterIndex, kind: "sseOut" });
1562
+ };
1563
+ }
1564
+ function Signal() {
1565
+ return function(target, propertyKey, parameterIndex) {
1566
+ recordParam(target, String(propertyKey), { index: parameterIndex, kind: "signal" });
1567
+ };
2258
1568
  }
2259
1569
 
2260
1570
  // src/decorators/params.ts
@@ -2431,52 +1741,6 @@ function getJobConfig(ctor) {
2431
1741
  return { schedule: meta.schedule, timeout, retry, handler: run };
2432
1742
  }
2433
1743
 
2434
- // src/resource.ts
2435
- var Resource = class {
2436
- /** The env-var names this resource needs. Optional; omit for none. */
2437
- static secrets;
2438
- };
2439
- function hasSecrets(value) {
2440
- if (typeof value !== "object" && typeof value !== "function") return false;
2441
- const secrets = value.secrets;
2442
- return secrets === void 0 || Array.isArray(secrets);
2443
- }
2444
- var registry = [];
2445
- function __registerResource(resource) {
2446
- registry.push({ resource, booted: false });
2447
- }
2448
- function declaredSecrets(resource) {
2449
- const ctor = resource.constructor;
2450
- return hasSecrets(ctor) ? ctor.secrets ?? [] : [];
2451
- }
2452
- async function __runResourceBoot(envMap) {
2453
- for (const entry of registry) {
2454
- if (entry.booted) continue;
2455
- const secrets = declaredSecrets(entry.resource);
2456
- const env = {};
2457
- for (const name of secrets) {
2458
- const value = envMap[name];
2459
- if (value === void 0) {
2460
- throw new Error(
2461
- `Resource ${entry.resource.constructor.name} requires secret "${name}" but it is not set. Set it with \`palbase secret set ${name} ...\` (or in Studio) and redeploy.`
2462
- );
2463
- }
2464
- env[name] = value;
2465
- }
2466
- await entry.resource.init(env);
2467
- entry.booted = true;
2468
- }
2469
- }
2470
- async function __shutdownResources() {
2471
- for (let i = registry.length - 1; i >= 0; i -= 1) {
2472
- const entry = registry[i];
2473
- if (entry.booted && entry.resource.shutdown) {
2474
- await entry.resource.shutdown();
2475
- }
2476
- }
2477
- registry.length = 0;
2478
- }
2479
-
2480
1744
  // src/index.ts
2481
1745
  import { z } from "zod";
2482
1746
  export {
@@ -2486,17 +1750,11 @@ export {
2486
1750
  Client,
2487
1751
  Conflict,
2488
1752
  Controller,
2489
- DEFAULT_TEMPLATE_LOCALE,
2490
1753
  Database,
2491
1754
  Delete,
2492
1755
  Deny,
2493
1756
  Documents,
2494
- EGRESS_CONFIG_KIND,
2495
- EGRESS_TIMEOUT_DEFAULT_MS,
2496
- EGRESS_TIMEOUT_MAX_MS,
2497
- EGRESS_TIMEOUT_MIN_MS,
2498
1757
  EXTENSION_DEPENDENCIES,
2499
- FLAGS_CONFIG_KIND,
2500
1758
  Flags,
2501
1759
  Forbidden,
2502
1760
  Get,
@@ -2505,35 +1763,28 @@ export {
2505
1763
  HttpError,
2506
1764
  Job,
2507
1765
  Log,
2508
- NOTIFICATIONS_CONFIG_KIND,
2509
1766
  NotFound,
2510
1767
  Notifications,
2511
1768
  On,
2512
1769
  OptionalUser,
2513
1770
  PALBASE_EXTENSIONS,
2514
- PROVIDER_CATALOG,
2515
1771
  PalError,
2516
1772
  PalbaseModuleError,
2517
1773
  Param,
2518
1774
  Patch,
2519
1775
  PolicyBuilder,
2520
1776
  Post,
2521
- Purchases,
2522
1777
  Put,
2523
1778
  Query,
2524
1779
  QueryParams,
2525
- RESERVED_SECRET_PREFIX,
2526
1780
  Realtime,
2527
1781
  Req,
2528
1782
  RequestId,
2529
- RequireEntitlement,
2530
- Resource,
2531
- SECRETS_CONFIG_KIND,
2532
- STORAGE_CONFIG_KIND,
2533
1783
  Secrets,
2534
- Spend,
1784
+ Signal,
1785
+ Sse,
1786
+ SseOut,
2535
1787
  Storage,
2536
- TEST_USERS_CONFIG_KIND,
2537
1788
  TooManyRequests,
2538
1789
  TraceId,
2539
1790
  TxPlanError,
@@ -2544,33 +1795,19 @@ export {
2544
1795
  User,
2545
1796
  Webhook,
2546
1797
  __getRuntime,
2547
- __registerResource,
2548
1798
  __requestALS,
2549
1799
  __resetRegisteredControllers,
2550
- __runResourceBoot,
2551
1800
  __runWithRuntime,
2552
1801
  __setRuntime,
2553
- __shutdownResources,
2554
1802
  bigint,
2555
1803
  boolean,
2556
- bucket,
2557
1804
  buildModuleClients,
2558
- buildProvider,
2559
1805
  dec,
2560
1806
  defineChannels,
2561
- defineEgress,
2562
1807
  defineError,
2563
- defineFlags,
2564
1808
  defineMiddleware,
2565
- defineNotifications,
2566
1809
  defineSchema,
2567
- defineSecrets,
2568
- defineStorage,
2569
- defineTestUsers,
2570
- entitlementFor,
2571
1810
  enumType,
2572
- extractVariables,
2573
- flag,
2574
1811
  getErrorRegistry,
2575
1812
  getHookConfig,
2576
1813
  getJobConfig,
@@ -2583,26 +1820,20 @@ export {
2583
1820
  jsonb,
2584
1821
  makeEnvDts,
2585
1822
  makeHttpClient,
2586
- makePurchasesDts,
1823
+ makeStackDts,
2587
1824
  makeTypedDB,
2588
1825
  now,
2589
1826
  numeric,
2590
1827
  openai,
2591
1828
  ownerOnly,
2592
- parseFileSizeLimit,
2593
1829
  policy,
2594
1830
  publicChannel,
2595
1831
  raw,
2596
1832
  recordThrows,
2597
- reservedSecretKey,
2598
- secret,
2599
- spendFor,
2600
- testUser,
2601
1833
  text,
2602
1834
  timestamp,
2603
1835
  toSchemaJSON,
2604
1836
  uuid,
2605
- validateUploadAgainstStorage,
2606
1837
  vector,
2607
1838
  z
2608
1839
  };