@palbase/backend 22.0.1 → 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 (73) hide show
  1. package/dist/bin/palbase-backend.cjs +764 -25
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -4
  4. package/dist/{chunk-POYAFBLF.js → chunk-FSGSB42K.js} +13 -6
  5. package/dist/chunk-FSGSB42K.js.map +1 -0
  6. package/dist/{chunk-SSGAMC26.js → chunk-HAF67F2H.js} +114 -5
  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-QYOHMVUW.js → chunk-REZU6UKT.js} +755 -24
  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 +117 -6
  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 +7 -3
  23. package/dist/{endpoint-B0LpZixz.d.cts → endpoint-BavvbW4P.d.ts} +116 -14
  24. package/dist/{endpoint-B0LpZixz.d.ts → endpoint-i8TTCohk.d.cts} +116 -14
  25. package/dist/engine/index.cjs +764 -25
  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-g-EzitI-.d.ts → index-B3jmmItD.d.ts} +203 -7
  31. package/dist/{index-BCNtlG1w.d.ts → index-B7YBEG5w.d.ts} +117 -93
  32. package/dist/{index-BGSCWlUa.d.cts → index-Bmvx1EvJ.d.cts} +203 -7
  33. package/dist/{index-B4W6d2VJ.d.cts → index-E7OscPJT.d.cts} +117 -93
  34. package/dist/index.cjs +509 -1085
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +203 -1133
  37. package/dist/index.d.ts +203 -1133
  38. package/dist/index.js +288 -900
  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-3BLYv4si.d.ts → registry-C3H2uPeZ.d.cts} +13 -169
  47. package/dist/{registry-Cw0YEYCg.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/endpoints.md +1 -1
  55. package/docs/errors.md +9 -0
  56. package/docs/llms-full.txt +17 -261
  57. package/docs/llms.txt +0 -2
  58. package/package.json +7 -7
  59. package/template/package.json +1 -1
  60. package/dist/chunk-N32VDWKH.js.map +0 -1
  61. package/dist/chunk-POYAFBLF.js.map +0 -1
  62. package/dist/chunk-QMVK4X3V.js.map +0 -1
  63. package/dist/chunk-QYOHMVUW.js.map +0 -1
  64. package/dist/chunk-SSGAMC26.js.map +0 -1
  65. package/dist/purchases/keys.cjs.map +0 -1
  66. package/dist/purchases/keys.d.cts +0 -42
  67. package/dist/purchases/keys.d.ts +0 -42
  68. package/dist/purchases/keys.js +0 -1
  69. package/docs/config.md +0 -147
  70. package/docs/resources.md +0 -97
  71. package/template/config/secrets.ts +0 -24
  72. /package/dist/{chunk-YL4C5NRY.js.map → chunk-Y5HXVUMP.js.map} +0 -0
  73. /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
package/dist/index.js CHANGED
@@ -11,20 +11,18 @@ import {
11
11
  jsonb,
12
12
  makeTypedDB,
13
13
  numeric,
14
+ openai,
14
15
  policy,
15
16
  raw,
16
17
  text,
17
18
  timestamp,
18
- uuid
19
- } from "./chunk-SSGAMC26.js";
19
+ uuid,
20
+ vector
21
+ } from "./chunk-HAF67F2H.js";
20
22
  import {
21
23
  defineError,
22
- entitlementFor,
23
- getErrorRegistry,
24
- recordEntitlement,
25
- recordSpend,
26
- spendFor
27
- } from "./chunk-N32VDWKH.js";
24
+ getErrorRegistry
25
+ } from "./chunk-ZC6Q2BRD.js";
28
26
  import {
29
27
  Cache,
30
28
  Database,
@@ -32,7 +30,6 @@ import {
32
30
  Flags,
33
31
  Log,
34
32
  Notifications,
35
- Purchases,
36
33
  Realtime,
37
34
  Secrets,
38
35
  Storage,
@@ -40,7 +37,7 @@ import {
40
37
  __requestALS,
41
38
  __runWithRuntime,
42
39
  __setRuntime
43
- } from "./chunk-POYAFBLF.js";
40
+ } from "./chunk-FSGSB42K.js";
44
41
  import {
45
42
  TxPlanError,
46
43
  TxRefError,
@@ -52,7 +49,7 @@ import {
52
49
  Controller,
53
50
  __resetRegisteredControllers,
54
51
  getRegisteredControllers
55
- } from "./chunk-YL4C5NRY.js";
52
+ } from "./chunk-Y5HXVUMP.js";
56
53
  import {
57
54
  BadRequest,
58
55
  Conflict,
@@ -66,141 +63,9 @@ import {
66
63
  recordParam,
67
64
  recordRoute,
68
65
  recordThrows
69
- } from "./chunk-QMVK4X3V.js";
66
+ } from "./chunk-OMRTHM4X.js";
70
67
  import "./chunk-7D4SUZUM.js";
71
68
 
72
- // src/purchases/errors.ts
73
- function asPalstoreError(err) {
74
- if (!(err instanceof Error)) return null;
75
- const e = err;
76
- if (typeof e.code !== "string" || typeof e.status !== "number") return null;
77
- return { code: e.code, status: e.status, message: e.message };
78
- }
79
- function toHttpError(err) {
80
- if (err instanceof HttpError) return err;
81
- const p = asPalstoreError(err);
82
- if (!p) return err;
83
- switch (p.code) {
84
- case "entitlement_required": {
85
- const entitlement = err.entitlement ?? null;
86
- return new PalError(403, "entitlement_required", p.message, { entitlement });
87
- }
88
- case "quota_exceeded":
89
- case "credit_insufficient": {
90
- const state = err.state;
91
- return new PalError(429, p.code, p.message, state);
92
- }
93
- default:
94
- return err;
95
- }
96
- }
97
-
98
- // src/purchases/subject.ts
99
- var subjectByRequest = /* @__PURE__ */ new WeakMap();
100
- function storeEnv() {
101
- return process.env.PALSTORE_STORE_ENV === "sandbox" ? "sandbox" : "production";
102
- }
103
- async function currentSubjectId() {
104
- const store = __requestALS.getStore();
105
- if (!store) {
106
- throw new Error(
107
- "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."
108
- );
109
- }
110
- const memoized = subjectByRequest.get(store);
111
- if (memoized) return memoized;
112
- const userId = store.userId;
113
- if (!userId) {
114
- throw new Unauthorized(
115
- "This endpoint requires an authenticated user: purchases entitlements and quota are per-user, and the subject is resolved from the request's identity."
116
- );
117
- }
118
- const resolving = Purchases.resolveSubject({ userRef: userId, storeEnv: storeEnv() }).then(
119
- (r) => r.subjectId
120
- );
121
- subjectByRequest.set(store, resolving);
122
- resolving.catch(() => subjectByRequest.delete(store));
123
- return resolving;
124
- }
125
-
126
- // src/purchases/decorators.ts
127
- var WRAPPED = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.wrapped");
128
- function idempotencyKeyFor(key) {
129
- const store = __requestALS.getStore();
130
- const base = store?.idempotencyKey || store?.requestId;
131
- if (!base) {
132
- throw new Error(
133
- "@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."
134
- );
135
- }
136
- return `${base}:${key}`;
137
- }
138
- function ensureWrapper(target, fnName, descriptor) {
139
- const original = descriptor.value;
140
- if (typeof original !== "function") {
141
- throw new Error(
142
- `@RequireEntitlement/@Spend can only decorate a method; "${fnName}" is not one.`
143
- );
144
- }
145
- if (original[WRAPPED]) return;
146
- const wrapper = async function(...args) {
147
- const ctor = this.constructor;
148
- const entitlement = entitlementFor(ctor, fnName);
149
- const spend = spendFor(ctor, fnName);
150
- let subjectId;
151
- try {
152
- subjectId = await currentSubjectId();
153
- } catch (err) {
154
- throw toHttpError(err);
155
- }
156
- if (entitlement !== void 0) {
157
- try {
158
- await Purchases.require(subjectId, entitlement);
159
- } catch (err) {
160
- throw toHttpError(err);
161
- }
162
- }
163
- if (spend === void 0) {
164
- return original.apply(this, args);
165
- }
166
- let handlerFailure;
167
- try {
168
- return await Purchases.withSpend(
169
- subjectId,
170
- spend.key,
171
- { count: spend.count, idempotencyKey: idempotencyKeyFor(spend.key) },
172
- async () => {
173
- try {
174
- return await original.apply(this, args);
175
- } catch (err) {
176
- handlerFailure = { err };
177
- throw err;
178
- }
179
- }
180
- );
181
- } catch (err) {
182
- if (handlerFailure !== void 0 && handlerFailure.err === err) throw err;
183
- throw toHttpError(err);
184
- }
185
- };
186
- wrapper[WRAPPED] = true;
187
- descriptor.value = wrapper;
188
- }
189
- function RequireEntitlement(key) {
190
- return function(target, propertyKey, descriptor) {
191
- const fnName = String(propertyKey);
192
- recordEntitlement(target, fnName, key);
193
- ensureWrapper(target, fnName, descriptor);
194
- };
195
- }
196
- function Spend(key) {
197
- return function(target, propertyKey, descriptor) {
198
- const fnName = String(propertyKey);
199
- recordSpend(target, fnName, { key, count: 1 });
200
- ensureWrapper(target, fnName, descriptor);
201
- };
202
- }
203
-
204
69
  // src/clients/http.ts
205
70
  var PalbaseModuleError = class extends Error {
206
71
  code;
@@ -546,12 +411,12 @@ function buildFlagsClient(http, cfg) {
546
411
  return {
547
412
  data: Object.keys(values).map((name) => {
548
413
  const value = values[name];
549
- const flag2 = {
414
+ const flag = {
550
415
  name,
551
416
  enabled: flagEnabled(value)
552
417
  };
553
- if (typeof value === "string") flag2.variant = { name: value };
554
- return flag2;
418
+ if (typeof value === "string") flag.variant = { name: value };
419
+ return flag;
555
420
  }),
556
421
  error: null,
557
422
  status: res.status
@@ -779,18 +644,99 @@ function buildNotificationsClient(http) {
779
644
 
780
645
  // src/clients/realtime.ts
781
646
  import { createHmac } from "crypto";
782
- function mintToken(secret2) {
647
+ function mintToken(secret) {
783
648
  const now2 = Math.floor(Date.now() / 1e3);
784
649
  const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
785
650
  const payload = Buffer.from(
786
651
  JSON.stringify({ iss: "backend-runtime", role: "service_role", iat: now2, exp: now2 + 60 })
787
652
  ).toString("base64url");
788
- const sig = createHmac("sha256", secret2).update(`${header}.${payload}`).digest("base64url");
653
+ const sig = createHmac("sha256", secret).update(`${header}.${payload}`).digest("base64url");
789
654
  return `${header}.${payload}.${sig}`;
790
655
  }
791
656
  function buildRealtimeClient(cfg) {
792
657
  const url = `${cfg.baseUrl}/realtime/api/broadcast`;
658
+ const stateUrl = `${cfg.baseUrl}/realtime/api/state`;
659
+ async function writeState(topic, key, value, del) {
660
+ if (typeof topic !== "string" || topic.length === 0) {
661
+ return {
662
+ data: null,
663
+ error: new PalbaseModuleError("invalid_argument", "topic must be a non-empty string", 400)
664
+ };
665
+ }
666
+ if (typeof key !== "string" || key.length === 0) {
667
+ return {
668
+ data: null,
669
+ error: new PalbaseModuleError("invalid_argument", "key must be a non-empty string", 400)
670
+ };
671
+ }
672
+ if (!cfg.apiJwtSecret) {
673
+ return {
674
+ data: null,
675
+ error: new PalbaseModuleError(
676
+ "realtime_unconfigured",
677
+ "Realtime.state is unavailable: realtime is not provisioned for this Environment (PALBASE_REALTIME_API_JWT_SECRET unset).",
678
+ 503
679
+ )
680
+ };
681
+ }
682
+ let token;
683
+ try {
684
+ token = mintToken(cfg.apiJwtSecret);
685
+ } catch (err) {
686
+ return {
687
+ data: null,
688
+ error: new PalbaseModuleError(
689
+ "realtime_token_error",
690
+ err instanceof Error ? err.message : "token mint failed",
691
+ 500
692
+ )
693
+ };
694
+ }
695
+ const op = del ? { topic, key, del: true } : { topic, key, value: value ?? {} };
696
+ const doFetch = cfg.fetchImpl ?? globalThis.fetch;
697
+ let response;
698
+ try {
699
+ response = await doFetch(stateUrl, {
700
+ method: "POST",
701
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
702
+ body: JSON.stringify({ ops: [op] })
703
+ });
704
+ } catch (err) {
705
+ return {
706
+ data: null,
707
+ error: new PalbaseModuleError(
708
+ "network_error",
709
+ err instanceof Error ? err.message : "state request failed",
710
+ 0
711
+ )
712
+ };
713
+ }
714
+ if (response.status === 202 || response.status === 200) {
715
+ return { data: void 0, error: null };
716
+ }
717
+ const detail = await response.text().catch(() => "");
718
+ return {
719
+ data: null,
720
+ error: new PalbaseModuleError(
721
+ "realtime_state_failed",
722
+ `realtime state write returned ${response.status}${detail ? `: ${detail}` : ""}`,
723
+ response.status
724
+ )
725
+ };
726
+ }
793
727
  return {
728
+ state: {
729
+ /** Write one DURABLE entry on a channel's shared state.
730
+ *
731
+ * The difference from broadcast is WHO it reaches: a broadcast reaches
732
+ * whoever is listening at that instant, while state is also handed to
733
+ * whoever joins afterwards. "This value is now X" wants the second — a
734
+ * client connecting a second later should not have to wait for the next
735
+ * change to learn X. */
736
+ set: (topic, key, value) => writeState(topic, key, value, false),
737
+ /** Remove one entry. */
738
+ clear: (topic, key) => writeState(topic, key, void 0, true)
739
+ },
794
740
  async broadcast(channel, event, payload) {
795
741
  if (typeof channel !== "string" || channel.length === 0) {
796
742
  return {
@@ -875,11 +821,11 @@ function validateStoragePath(p) {
875
821
  );
876
822
  }
877
823
  }
878
- function toFileObject(bucket2, row) {
824
+ function toFileObject(bucket, row) {
879
825
  return {
880
826
  name: row.path ?? "",
881
827
  path: row.path ?? "",
882
- bucket: row.bucket ?? bucket2,
828
+ bucket: row.bucket ?? bucket,
883
829
  size: typeof row.size === "number" ? row.size : 0,
884
830
  contentType: row.contentType ?? "",
885
831
  checksum: row.checksum ?? "",
@@ -995,35 +941,6 @@ function buildModuleClients(cfg) {
995
941
  };
996
942
  }
997
943
 
998
- // src/purchases/keys-gen.ts
999
- function liveKeys(entries) {
1000
- return Object.entries(entries ?? {}).filter(([, def]) => def?.removed !== true).map(([key]) => key).sort();
1001
- }
1002
- function memberName(key) {
1003
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
1004
- }
1005
- function members(keys) {
1006
- if (keys.length === 0) return "";
1007
- return `
1008
- ${keys.map((k) => ` ${memberName(k)}: true;`).join("\n")}
1009
- `;
1010
- }
1011
- function makePurchasesDts(manifest) {
1012
- const entitlements = liveKeys(manifest.entitlements);
1013
- const limits = liveKeys({ ...manifest.limits, ...manifest.credits });
1014
- return `// palbase-purchases.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
1015
- // Source: the project's palstore catalog revision.
1016
-
1017
- declare module "@palbase/backend/purchases" {
1018
- interface Entitlements {${members(entitlements)}}
1019
-
1020
- interface Limits {${members(limits)}}
1021
- }
1022
-
1023
- export {};
1024
- `;
1025
- }
1026
-
1027
944
  // src/db/schema-json.ts
1028
945
  function defOf(column) {
1029
946
  return "_def" in column ? column._def : column;
@@ -1046,6 +963,7 @@ function columnToJSON(column) {
1046
963
  if (def.enumName !== void 0) out.enumName = def.enumName;
1047
964
  if (def.enumValues !== void 0) out.enumValues = [...def.enumValues];
1048
965
  if (def.unique === true) out.unique = true;
966
+ if (def.dimensions !== void 0) out.dimensions = def.dimensions;
1049
967
  return out;
1050
968
  }
1051
969
  function policyToJSON(policy2) {
@@ -1061,6 +979,75 @@ function policyToJSON(policy2) {
1061
979
  permissive: policy2.permissive !== false
1062
980
  };
1063
981
  }
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
+ }
1019
+ const out = {};
1020
+ if (Array.isArray(search.text) && search.text.length > 0) out.text = { columns: [...search.text] };
1021
+ const legs = search.vector === void 0 ? [] : Array.isArray(search.vector) ? search.vector : [search.vector];
1022
+ if (legs.length > 0) {
1023
+ out.vector = legs.map((leg) => {
1024
+ const v = { metric: leg.metric ?? "cosine" };
1025
+ if (leg.column !== void 0) v.column = leg.column;
1026
+ if (leg.staleness !== void 0) v.staleness = leg.staleness;
1027
+ if (leg.model !== void 0) {
1028
+ v.embed = {
1029
+ provider: leg.model.provider,
1030
+ model: leg.model.model,
1031
+ from: [...leg.from ?? []],
1032
+ ...leg.model.apiKeyName !== void 0 ? { apiKeyName: leg.model.apiKeyName } : {},
1033
+ ...leg.model.dimensions !== void 0 ? { dimensions: leg.model.dimensions } : {},
1034
+ ...leg.model.baseURL !== void 0 ? { baseURL: leg.model.baseURL } : {}
1035
+ };
1036
+ }
1037
+ return v;
1038
+ });
1039
+ }
1040
+ commonSearchFields(search, out);
1041
+ return out;
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
+ }
1064
1051
  function tableToJSON(table) {
1065
1052
  const columns = {};
1066
1053
  for (const [name, column] of Object.entries(table.columns)) {
@@ -1096,6 +1083,14 @@ function tableToJSON(table) {
1096
1083
  if (table.indexes !== void 0 && table.indexes.length > 0) {
1097
1084
  out.indexes = table.indexes.map((i) => ({ name: i.name, columns: [...i.columns] }));
1098
1085
  }
1086
+ if (table.search !== void 0) {
1087
+ const vectorColumn = Object.entries(columns).find(([, c]) => c.dimensions !== void 0)?.[0];
1088
+ const sj = searchToJSON(table.search, vectorColumn);
1089
+ if (sj.text !== void 0 || sj.vector !== void 0) out.search = sj;
1090
+ }
1091
+ if (table.memory !== void 0) {
1092
+ out.memory = memoryToJSON(table.memory);
1093
+ }
1099
1094
  return out;
1100
1095
  }
1101
1096
  function toSchemaJSON(schema) {
@@ -1107,6 +1102,63 @@ function toSchemaJSON(schema) {
1107
1102
  return { tables, extensions: [...new Set(schema.extensions ?? [])] };
1108
1103
  }
1109
1104
 
1105
+ // src/channels.ts
1106
+ var CHANNELS = /* @__PURE__ */ Symbol.for("palbase.backend.channels");
1107
+ function ownerOnly() {
1108
+ return { kind: "owner" };
1109
+ }
1110
+ function publicChannel(opts = {}) {
1111
+ return { kind: "public", publish: opts.publish ?? false, state: opts.state };
1112
+ }
1113
+ function paramCount(pattern) {
1114
+ return pattern.split(":").filter((s) => s.startsWith("{") && s.endsWith("}")).length;
1115
+ }
1116
+ function defineChannels(map) {
1117
+ const entries = [];
1118
+ for (const [pattern, entry] of Object.entries(map)) {
1119
+ if ("kind" in entry && entry.kind === "owner") {
1120
+ if (paramCount(pattern) !== 1) {
1121
+ throw new Error(
1122
+ `defineChannels: ownerOnly() pattern "${pattern}" must carry exactly one {param} (its value is compared to the token subject)`
1123
+ );
1124
+ }
1125
+ entries.push({ pattern, kind: "owner" });
1126
+ } else if ("kind" in entry && entry.kind === "public") {
1127
+ entries.push({ pattern, kind: "public", publish: entry.publish, state: entry.state });
1128
+ } else {
1129
+ const c = entry;
1130
+ entries.push({ pattern, kind: "custom", authorize: c.authorize, handler: c.handler });
1131
+ }
1132
+ }
1133
+ const def = { entries };
1134
+ const carrier = globalThis;
1135
+ const existing = carrier[CHANNELS];
1136
+ if (existing && !sameDeclaration(existing, def)) {
1137
+ throw new Error(
1138
+ `defineChannels: channels were already declared (${existing.entries.length} pattern(s): ${existing.entries.map((e) => e.pattern).join(", ")}). A project declares its channels ONCE \u2014 a second call would overwrite the first, and channels nobody declared are refused.`
1139
+ );
1140
+ }
1141
+ carrier[CHANNELS] = def;
1142
+ return def;
1143
+ }
1144
+ function wireRow(e) {
1145
+ return JSON.stringify([
1146
+ e.pattern,
1147
+ e.kind,
1148
+ e.publish ?? false,
1149
+ e.state?.read ?? false,
1150
+ e.state?.write ?? false,
1151
+ // The handler is compared by SOURCE, like authorize: two evaluations of one
1152
+ // module give the same text, while different logic gives different text.
1153
+ e.handler ? e.handler.toString() : "",
1154
+ e.authorize ? e.authorize.toString() : ""
1155
+ ]);
1156
+ }
1157
+ function sameDeclaration(a, b) {
1158
+ if (a.entries.length !== b.entries.length) return false;
1159
+ return a.entries.every((x, i) => wireRow(x) === wireRow(b.entries[i]));
1160
+ }
1161
+
1110
1162
  // src/db/env-gen.ts
1111
1163
  function baseTsType(def) {
1112
1164
  switch (def.type) {
@@ -1128,6 +1180,11 @@ function baseTsType(def) {
1128
1180
  return "boolean";
1129
1181
  case "jsonb":
1130
1182
  return "unknown";
1183
+ // FR-008: the authoring type of a pgvector column. The default `unknown`
1184
+ // branch below STAYS — an unknown wire type is refused on the Go side
1185
+ // (FR-005), never papered over here.
1186
+ case "vector":
1187
+ return "number[]";
1131
1188
  case "enum": {
1132
1189
  const values = def.enumValues ?? [];
1133
1190
  if (values.length === 0) return "string";
@@ -1161,10 +1218,10 @@ function buildChildEdges(schema) {
1161
1218
  const ref = builder._def.references;
1162
1219
  if (ref === void 0) continue;
1163
1220
  if (ref.table === childName) continue;
1164
- const bucket2 = edges.get(ref.table);
1165
- if (bucket2 === void 0) continue;
1166
- if (bucket2.some((e) => e.child === childName)) continue;
1167
- 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 });
1168
1225
  }
1169
1226
  }
1170
1227
  return edges;
@@ -1209,6 +1266,10 @@ function tableBlock(table, children, indent) {
1209
1266
  `${indent} };`,
1210
1267
  `${indent} owner: ${owner === null ? "null" : JSON.stringify(owner)};`,
1211
1268
  `${indent} children: {${childEntries === "" ? "" : ` ${childEntries} `}};`,
1269
+ // searchable: vector kolonu YA DA search beyanı → EnvTypedTable'da search()
1270
+ // üyesini açan yapısal bayrak (FR-013). Yokken satır hiç üretilmez ki
1271
+ // mevcut şemaların d.ts'i bayt-aynı kalsın.
1272
+ ...cols.some(([, b]) => b._def.type === "vector") || table.search !== void 0 ? [`${indent} searchable: true;`] : [],
1212
1273
  `${indent}};`
1213
1274
  ].join("\n");
1214
1275
  }
@@ -1234,638 +1295,50 @@ export {};
1234
1295
  `;
1235
1296
  }
1236
1297
 
1237
- // src/config/storage.ts
1238
- var STORAGE_CONFIG_KIND = "storage";
1239
- var VARIANT_FORMATS = ["webp", "avif", "jpeg", "png"];
1240
- var VARIANT_FITS = ["cover", "contain", "inside"];
1241
- function normalizeVariants(input) {
1242
- if (input === null || typeof input !== "object" || Array.isArray(input)) {
1243
- throw new Error("bucket variants must be an object of { <name>: { width?, height?, format? } }");
1244
- }
1245
- const out = {};
1246
- for (const [name, spec] of Object.entries(input)) {
1247
- if (!/^[a-z0-9][a-z0-9_-]*$/i.test(name)) {
1248
- throw new Error(
1249
- `bucket variant name "${name}" must be alphanumeric with optional - or _ (e.g. "thumb", "card_2x")`
1250
- );
1251
- }
1252
- if (spec === null || typeof spec !== "object" || Array.isArray(spec)) {
1253
- throw new Error(`bucket variant "${name}" must be an object like { width: 100, height: 100 }`);
1254
- }
1255
- for (const dim of ["width", "height"]) {
1256
- const value = spec[dim];
1257
- if (value === void 0) continue;
1258
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
1259
- throw new Error(`bucket variant "${name}" ${dim} must be a positive integer, got ${String(value)}`);
1260
- }
1261
- }
1262
- if (spec.width === void 0 && spec.height === void 0) {
1263
- throw new Error(`bucket variant "${name}" must set width and/or height`);
1264
- }
1265
- if (spec.format !== void 0 && !VARIANT_FORMATS.includes(spec.format)) {
1266
- throw new Error(
1267
- `bucket variant "${name}" format must be one of ${VARIANT_FORMATS.join(", ")}, got "${String(spec.format)}"`
1268
- );
1269
- }
1270
- if (spec.fit !== void 0 && !VARIANT_FITS.includes(spec.fit)) {
1271
- throw new Error(
1272
- `bucket variant "${name}" fit must be one of ${VARIANT_FITS.join(", ")}, got "${String(spec.fit)}"`
1273
- );
1274
- }
1275
- if (spec.quality !== void 0) {
1276
- const q = spec.quality;
1277
- if (typeof q !== "number" || !Number.isInteger(q) || q < 1 || q > 100) {
1278
- throw new Error(`bucket variant "${name}" quality must be an integer 1\u2013100, got ${String(q)}`);
1279
- }
1280
- }
1281
- if (spec.fit === "cover" && (spec.width === void 0 || spec.height === void 0)) {
1282
- throw new Error(`bucket variant "${name}" uses fit "cover", which needs BOTH width and height`);
1283
- }
1284
- out[name] = {
1285
- ...spec.width !== void 0 ? { width: spec.width } : {},
1286
- ...spec.height !== void 0 ? { height: spec.height } : {},
1287
- ...spec.fit !== void 0 ? { fit: spec.fit } : {},
1288
- ...spec.format !== void 0 ? { format: spec.format } : {},
1289
- ...spec.quality !== void 0 ? { quality: spec.quality } : {}
1290
- };
1291
- }
1292
- return out;
1298
+ // src/stack-gen.ts
1299
+ function liveNames(names) {
1300
+ return [...new Set(names)].sort();
1293
1301
  }
1294
- var UNIT_BYTES = {
1295
- b: 1,
1296
- kb: 1024,
1297
- mb: 1024 ** 2,
1298
- gb: 1024 ** 3,
1299
- tb: 1024 ** 4
1300
- };
1301
- function parseFileSizeLimit(input) {
1302
- if (typeof input === "number") {
1303
- if (!Number.isFinite(input) || input < 0 || !Number.isInteger(input)) {
1304
- throw new Error(
1305
- `bucket fileSizeLimit must be a non-negative integer number of bytes, got ${input}`
1306
- );
1307
- }
1308
- return input;
1309
- }
1310
- const trimmed = input.trim();
1311
- const match = /^(\d+(?:\.\d+)?)\s*([a-zA-Z]+)?$/.exec(trimmed);
1312
- if (!match) {
1313
- throw new Error(
1314
- `bucket fileSizeLimit string must be "<number><unit>" like "5MB" or "1GB", got "${input}"`
1315
- );
1316
- }
1317
- const value = Number.parseFloat(match[1]);
1318
- const unit = (match[2] ?? "b").toLowerCase();
1319
- const multiplier = UNIT_BYTES[unit];
1320
- if (multiplier === void 0) {
1321
- throw new Error(
1322
- `bucket fileSizeLimit has an unknown unit "${match[2]}" \u2014 use B, KB, MB, GB, or TB (e.g. "5MB")`
1323
- );
1324
- }
1325
- const bytes = value * multiplier;
1326
- if (!Number.isFinite(bytes) || bytes < 0) {
1327
- throw new Error(`bucket fileSizeLimit resolved to an invalid byte count: ${bytes}`);
1328
- }
1329
- return Math.round(bytes);
1330
- }
1331
- var MIME_RE = /^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*\/(?:\*|[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*)$/;
1332
- function bucket(opts = {}) {
1333
- const fileSizeLimit = opts.fileSizeLimit === void 0 ? null : parseFileSizeLimit(opts.fileSizeLimit);
1334
- let allowedMimeTypes = null;
1335
- if (opts.allowedMimeTypes !== void 0) {
1336
- if (!Array.isArray(opts.allowedMimeTypes)) {
1337
- throw new Error("bucket allowedMimeTypes must be an array of MIME-type strings");
1338
- }
1339
- for (const mime of opts.allowedMimeTypes) {
1340
- if (typeof mime !== "string" || !MIME_RE.test(mime.trim())) {
1341
- throw new Error(
1342
- `bucket allowedMimeTypes entry "${mime}" is not a valid MIME type (expected "type/subtype", e.g. "image/png")`
1343
- );
1344
- }
1345
- }
1346
- allowedMimeTypes = [...new Set(opts.allowedMimeTypes.map((m) => m.trim()))];
1347
- }
1348
- return {
1349
- public: opts.public ?? false,
1350
- fileSizeLimit,
1351
- allowedMimeTypes,
1352
- variants: opts.variants === void 0 ? null : normalizeVariants(opts.variants)
1353
- };
1302
+ function memberName(name) {
1303
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
1354
1304
  }
1355
- function defineStorage(input) {
1356
- if (input === null || typeof input !== "object" || typeof input.buckets !== "object") {
1357
- throw new Error("defineStorage expects { buckets: { <name>: bucket({...}) } }");
1358
- }
1359
- const buckets = {};
1360
- for (const name of Object.keys(input.buckets)) {
1361
- if (name.length === 0) {
1362
- throw new Error("bucket name must be a non-empty string");
1363
- }
1364
- const def = input.buckets[name];
1365
- if (def === void 0) continue;
1366
- buckets[name] = def;
1367
- }
1368
- 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
+ `;
1369
1310
  }
1370
-
1371
- // src/config/egress.ts
1372
- var EGRESS_CONFIG_KIND = "egress";
1373
- var EGRESS_TIMEOUT_MIN_MS = 1e3;
1374
- var EGRESS_TIMEOUT_MAX_MS = 3e5;
1375
- var EGRESS_TIMEOUT_DEFAULT_MS = 3e4;
1376
- function defineEgress(input) {
1377
- if (input === null || typeof input !== "object" || !Array.isArray(input.hosts)) {
1378
- throw new Error("defineEgress expects { hosts: string[] }");
1379
- }
1380
- const hosts = [];
1381
- for (const h of input.hosts) {
1382
- if (typeof h !== "string" || h.trim().length === 0) {
1383
- throw new Error("defineEgress: every host must be a non-empty string");
1384
- }
1385
- hosts.push(h.trim().toLowerCase());
1386
- }
1387
- if (input.timeoutMs === void 0) {
1388
- return { __config: EGRESS_CONFIG_KIND, hosts };
1389
- }
1390
- const timeoutMs = input.timeoutMs;
1391
- if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs)) {
1392
- throw new Error("defineEgress: timeoutMs must be an integer number of milliseconds");
1393
- }
1394
- if (timeoutMs < EGRESS_TIMEOUT_MIN_MS || timeoutMs > EGRESS_TIMEOUT_MAX_MS) {
1395
- throw new Error(
1396
- `defineEgress: timeoutMs must be between ${EGRESS_TIMEOUT_MIN_MS} and ${EGRESS_TIMEOUT_MAX_MS} (got ${timeoutMs})`
1397
- );
1398
- }
1399
- 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
+ `;
1400
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.
1401
1331
 
1402
- // src/config/notifications.ts
1403
- var NOTIFICATIONS_CONFIG_KIND = "notifications";
1404
- var RESERVED_SECRET_PREFIX = "PB_NOTIFICATIONS";
1405
- var PROVIDER_CATALOG = {
1406
- apns: {
1407
- channel: "push",
1408
- required: ["teamId", "keyId", "bundleId"],
1409
- optional: ["isProduction"],
1410
- secrets: ["p8"]
1411
- },
1412
- fcm: {
1413
- channel: "push",
1414
- required: [],
1415
- optional: [],
1416
- secrets: ["serviceAccount"]
1417
- },
1418
- sendgrid: {
1419
- channel: "email",
1420
- required: ["fromDomain"],
1421
- optional: [],
1422
- secrets: ["apiKey"]
1423
- },
1424
- ses: {
1425
- channel: "email",
1426
- required: ["region", "accessKeyId", "fromDomain"],
1427
- optional: [],
1428
- secrets: ["secretAccessKey"]
1429
- },
1430
- smtp: {
1431
- channel: "email",
1432
- required: ["host", "port", "fromEmail"],
1433
- optional: ["username", "useStarttls"],
1434
- secrets: ["password"]
1435
- },
1436
- acs: {
1437
- channel: "email",
1438
- required: ["fromEmail"],
1439
- optional: ["fromName"],
1440
- secrets: ["connectionString"]
1441
- },
1442
- twilio: {
1443
- channel: "sms",
1444
- // account_sid required; one of from_number / messaging_service_sid required
1445
- // (enforced by the refine in buildProvider, not by the flat `required` list).
1446
- required: ["accountSid"],
1447
- optional: ["fromNumber", "messagingServiceSid"],
1448
- secrets: ["authToken"]
1449
- }
1450
- };
1451
- var DEFAULT_TEMPLATE_LOCALE = "en";
1452
- function extractVariables(...sources) {
1453
- const found = /* @__PURE__ */ new Set();
1454
- for (const src of sources) {
1455
- if (!src) continue;
1456
- for (const match of src.matchAll(/\{\{\{?\s*([^{}]+?)\s*\}?\}\}/g)) {
1457
- const token = (match[1] ?? "").trim();
1458
- if (token === "" || "#/^!>&".includes(token.charAt(0)) || token === "else") continue;
1459
- if (/\s/.test(token)) continue;
1460
- const root = token.split(".")[0] ?? "";
1461
- if (root === "" || root === "this" || root.startsWith("@")) continue;
1462
- found.add(root);
1463
- }
1464
- }
1465
- return [...found].sort();
1466
- }
1467
- function buildEmailTemplate(slug, opts) {
1468
- const row = (locale, c) => {
1469
- if (!c.subject) throw new Error(`notifications: email template "${slug}" (${locale}) needs a subject`);
1470
- if (!c.html) throw new Error(`notifications: email template "${slug}" (${locale}) needs an html body`);
1471
- const def = {
1472
- slug,
1473
- locale,
1474
- subject: c.subject,
1475
- html_body: c.html,
1476
- variables: c.variables ? [...c.variables] : extractVariables(c.subject, c.html, c.text)
1477
- };
1478
- if (c.text !== void 0) def.text_body = c.text;
1479
- return def;
1480
- };
1481
- const rows = [row(opts.locale ?? DEFAULT_TEMPLATE_LOCALE, opts)];
1482
- for (const [locale, content] of Object.entries(opts.locales ?? {})) {
1483
- rows.push(row(locale, content));
1484
- }
1485
- return rows;
1486
- }
1487
- function buildSmsTemplate(slug, opts) {
1488
- const row = (locale, c) => {
1489
- if (!c.body) throw new Error(`notifications: sms template "${slug}" (${locale}) needs a body`);
1490
- return {
1491
- slug,
1492
- locale,
1493
- body: c.body,
1494
- variables: c.variables ? [...c.variables] : extractVariables(c.body)
1495
- };
1496
- };
1497
- const rows = [row(opts.locale ?? DEFAULT_TEMPLATE_LOCALE, opts)];
1498
- for (const [locale, content] of Object.entries(opts.locales ?? {})) {
1499
- rows.push(row(locale, content));
1500
- }
1501
- return rows;
1502
- }
1503
- function buildTemplates(input) {
1504
- const out = { email: [], sms: [] };
1505
- const seen = /* @__PURE__ */ new Set();
1506
- const claim = (channel, slug, locale) => {
1507
- const key = `${channel}/${slug}/${locale}`;
1508
- if (seen.has(key)) {
1509
- throw new Error(`notifications: ${channel} template "${slug}" declares locale "${locale}" twice`);
1510
- }
1511
- seen.add(key);
1512
- };
1513
- for (const [slug, opts] of Object.entries(input.email ?? {})) {
1514
- for (const row of buildEmailTemplate(slug, opts)) {
1515
- claim("email", row.slug, row.locale);
1516
- out.email.push(row);
1517
- }
1518
- }
1519
- for (const [slug, opts] of Object.entries(input.sms ?? {})) {
1520
- for (const row of buildSmsTemplate(slug, opts)) {
1521
- claim("sms", row.slug, row.locale);
1522
- out.sms.push(row);
1523
- }
1524
- }
1525
- return out;
1526
- }
1527
- function buildProvider(name, opts) {
1528
- const entry = PROVIDER_CATALOG[name];
1529
- const src = { ...opts };
1530
- const enabled = src.enabled === void 0 ? false : Boolean(src.enabled);
1531
- if (!enabled) {
1532
- return { enabled: false };
1533
- }
1534
- const def = { enabled: true };
1535
- for (const field of entry.required) {
1536
- const value = src[field];
1537
- if (value === void 0 || value === null || value === "") {
1538
- throw new Error(
1539
- `notifications: provider "${name}" is enabled but missing required field "${field}"`
1540
- );
1541
- }
1542
- def[field] = value;
1543
- }
1544
- for (const field of entry.optional) {
1545
- if (src[field] !== void 0) {
1546
- def[field] = src[field];
1547
- }
1548
- }
1549
- if (name === "twilio") {
1550
- const hasFrom = Boolean(def.fromNumber);
1551
- const hasMsg = Boolean(def.messagingServiceSid);
1552
- if (!hasFrom && !hasMsg) {
1553
- throw new Error(
1554
- 'notifications: provider "twilio" requires one of "fromNumber" or "messagingServiceSid"'
1555
- );
1556
- }
1557
- }
1558
- return def;
1559
- }
1560
- function defineNotifications(input) {
1561
- if (input === null || typeof input !== "object") {
1562
- throw new Error("defineNotifications expects { push?, email?, sms? }");
1563
- }
1564
- const config = {
1565
- __config: NOTIFICATIONS_CONFIG_KIND,
1566
- push: {},
1567
- email: {},
1568
- sms: {},
1569
- templates: buildTemplates(input.templates ?? {})
1570
- };
1571
- const push = input.push ?? {};
1572
- if (push.apns !== void 0) config.push.apns = buildProvider("apns", push.apns);
1573
- if (push.fcm !== void 0) config.push.fcm = buildProvider("fcm", push.fcm);
1574
- const email = input.email ?? {};
1575
- if (email.sendgrid !== void 0) config.email.sendgrid = buildProvider("sendgrid", email.sendgrid);
1576
- if (email.ses !== void 0) config.email.ses = buildProvider("ses", email.ses);
1577
- if (email.smtp !== void 0) config.email.smtp = buildProvider("smtp", email.smtp);
1578
- if (email.acs !== void 0) config.email.acs = buildProvider("acs", email.acs);
1579
- const sms = input.sms ?? {};
1580
- if (sms.twilio !== void 0) config.sms.twilio = buildProvider("twilio", sms.twilio);
1581
- return config;
1582
- }
1583
- function reservedSecretKey(provider, secretField) {
1584
- return `${RESERVED_SECRET_PREFIX}_${camelToUpperSnake(provider)}_${camelToUpperSnake(secretField)}`;
1585
- }
1586
- function camelToUpperSnake(s) {
1587
- return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
1588
- }
1332
+ declare module "@palbase/backend/stack" {
1333
+ interface Secrets {${members(secrets)}}
1589
1334
 
1590
- // src/config/test-users.ts
1591
- var TEST_USERS_CONFIG_KIND = "test-users";
1592
- var TEST_USER_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
1593
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1594
- var E164_RE = /^\+[1-9]\d{1,14}$/;
1595
- var OTP_LENGTH = 6;
1596
- var OTP_RE = /^\d{6}$/;
1597
- var MIN_PASSWORD_LENGTH = 8;
1598
- function isPlainObject(value) {
1599
- return typeof value === "object" && value !== null && !Array.isArray(value);
1600
- }
1601
- function normalizeSeed(seed, userName) {
1602
- if (seed === void 0) return {};
1603
- if (!isPlainObject(seed)) {
1604
- throw new Error(`testUser ${JSON.stringify(userName)}: seed must be { <table>: [ {...} ] }`);
1605
- }
1606
- const out = {};
1607
- for (const [table, rows] of Object.entries(seed)) {
1608
- if (!Array.isArray(rows)) {
1609
- throw new Error(
1610
- `testUser ${JSON.stringify(userName)}: seed.${table} must be an array of rows`
1611
- );
1612
- }
1613
- if (rows.length === 0) {
1614
- throw new Error(
1615
- `testUser ${JSON.stringify(userName)}: seed.${table} is empty \u2014 remove it or add a row`
1616
- );
1617
- }
1618
- for (const row of rows) {
1619
- if (!isPlainObject(row)) {
1620
- throw new Error(
1621
- `testUser ${JSON.stringify(userName)}: every row in seed.${table} must be an object`
1622
- );
1623
- }
1624
- }
1625
- out[table] = rows;
1626
- }
1627
- return out;
1628
- }
1629
- function testUser(opts = {}) {
1630
- if (!isPlainObject(opts)) {
1631
- throw new Error("testUser() expects { email?, password?, phone?, otp?, seed? }");
1632
- }
1633
- const hasEmail = opts.email !== void 0;
1634
- const hasPassword = opts.password !== void 0;
1635
- if (hasEmail !== hasPassword) {
1636
- throw new Error(
1637
- "testUser() needs email AND password together (a fixture with stable credentials) or neither (a template the server generates credentials for)"
1638
- );
1639
- }
1640
- let email = null;
1641
- let password = null;
1642
- if (hasEmail) {
1643
- if (typeof opts.email !== "string" || !EMAIL_RE.test(opts.email)) {
1644
- throw new Error(`testUser email ${JSON.stringify(opts.email)} is not a valid address`);
1645
- }
1646
- if (typeof opts.password !== "string" || opts.password.length < MIN_PASSWORD_LENGTH) {
1647
- throw new Error(
1648
- `testUser password must be at least ${MIN_PASSWORD_LENGTH} characters (got ${typeof opts.password === "string" ? opts.password.length : 0})`
1649
- );
1650
- }
1651
- email = opts.email;
1652
- password = opts.password;
1653
- }
1654
- const hasPhone = opts.phone !== void 0;
1655
- const hasOTP = opts.otp !== void 0;
1656
- if (hasPhone !== hasOTP) {
1657
- throw new Error(
1658
- "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"
1659
- );
1660
- }
1661
- let phone = null;
1662
- let otp = null;
1663
- if (hasPhone) {
1664
- if (typeof opts.phone !== "string" || !E164_RE.test(opts.phone)) {
1665
- throw new Error(
1666
- `testUser phone ${JSON.stringify(opts.phone)} must be E.164, e.g. "+905551112233"`
1667
- );
1668
- }
1669
- if (typeof opts.otp !== "string" || !OTP_RE.test(opts.otp)) {
1670
- throw new Error(
1671
- `testUser otp ${JSON.stringify(opts.otp)} must be ${OTP_LENGTH} digits`
1672
- );
1673
- }
1674
- phone = opts.phone;
1675
- otp = opts.otp;
1676
- }
1677
- return { email, password, phone, otp, seed: normalizeSeed(opts.seed, email ?? "<template>") };
1678
- }
1679
- function defineTestUsers(input) {
1680
- if (!isPlainObject(input) || !isPlainObject(input.users)) {
1681
- throw new Error("defineTestUsers expects { users: { <name>: testUser({...}) } }");
1682
- }
1683
- const users = {};
1684
- const emails = /* @__PURE__ */ new Map();
1685
- for (const name of Object.keys(input.users)) {
1686
- if (!TEST_USER_NAME_RE.test(name)) {
1687
- throw new Error(
1688
- `test user name ${JSON.stringify(name)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
1689
- );
1690
- }
1691
- const def = input.users[name];
1692
- if (def === void 0) continue;
1693
- if (def.email !== null) {
1694
- const lower = def.email.toLowerCase();
1695
- const already = emails.get(lower);
1696
- if (already !== void 0) {
1697
- throw new Error(
1698
- `test users ${JSON.stringify(already)} and ${JSON.stringify(name)} declare the same e-mail ${JSON.stringify(def.email)}`
1699
- );
1700
- }
1701
- emails.set(lower, name);
1702
- }
1703
- users[name] = def;
1704
- }
1705
- return { __config: TEST_USERS_CONFIG_KIND, users };
1706
- }
1335
+ interface Flags {${members(flags)}}
1707
1336
 
1708
- // src/config/flags.ts
1709
- var FLAGS_CONFIG_KIND = "flags";
1710
- var FLAG_KEY_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
1711
- function valueMatchesType(type, value) {
1712
- switch (type) {
1713
- case "boolean":
1714
- return typeof value === "boolean";
1715
- case "number":
1716
- return typeof value === "number" && Number.isFinite(value);
1717
- case "string":
1718
- return typeof value === "string";
1719
- case "json":
1720
- return typeof value === "object" && value !== null && !Array.isArray(value);
1721
- }
1722
- }
1723
- function flag(opts) {
1724
- if (opts === null || typeof opts !== "object") {
1725
- throw new Error("flag() expects { type, default, variants?, description? }");
1726
- }
1727
- const { type } = opts;
1728
- if (type !== "boolean" && type !== "number" && type !== "string" && type !== "json") {
1729
- throw new Error(`flag type must be "boolean", "number", "string", or "json", got ${JSON.stringify(type)}`);
1730
- }
1731
- if (!valueMatchesType(type, opts.default)) {
1732
- throw new Error(
1733
- `flag default ${JSON.stringify(opts.default)} does not match type "${type}"`
1734
- );
1735
- }
1736
- let variants = null;
1737
- if (opts.variants !== void 0) {
1738
- if (opts.type !== "string") {
1739
- throw new Error(`flag variants are only valid for type "string" (got type "${type}")`);
1740
- }
1741
- if (!Array.isArray(opts.variants)) {
1742
- throw new Error("flag variants must be an array of strings");
1743
- }
1744
- for (const v of opts.variants) {
1745
- if (typeof v !== "string" || v.length === 0) {
1746
- throw new Error(`flag variant ${JSON.stringify(v)} must be a non-empty string`);
1747
- }
1748
- }
1749
- variants = [...new Set(opts.variants)];
1750
- if (variants.length === 0) {
1751
- throw new Error("flag variants must be a non-empty list when supplied");
1752
- }
1753
- if (!variants.includes(opts.default)) {
1754
- throw new Error(
1755
- `flag default ${JSON.stringify(opts.default)} is not one of its variants [${variants.map((v) => JSON.stringify(v)).join(", ")}]`
1756
- );
1757
- }
1758
- }
1759
- let conditions = null;
1760
- if (opts.conditions !== void 0) {
1761
- if (!Array.isArray(opts.conditions)) {
1762
- throw new Error("flag conditions must be an array of { when, value }");
1763
- }
1764
- const compiled = [];
1765
- for (const [i, branch] of opts.conditions.entries()) {
1766
- if (branch === null || typeof branch !== "object") {
1767
- throw new Error(`flag condition ${i} must be an object { when, value }`);
1768
- }
1769
- if (typeof branch.when !== "string" || branch.when.trim().length === 0) {
1770
- throw new Error(`flag condition ${i} needs a non-empty "when" expression`);
1771
- }
1772
- if (!valueMatchesType(type, branch.value)) {
1773
- throw new Error(
1774
- `flag condition ${i} value ${JSON.stringify(branch.value)} does not match type "${type}"`
1775
- );
1776
- }
1777
- if (variants !== null && !variants.includes(branch.value)) {
1778
- throw new Error(
1779
- `flag condition ${i} value ${JSON.stringify(branch.value)} is not one of its variants [${variants.map((v) => JSON.stringify(v)).join(", ")}]`
1780
- );
1781
- }
1782
- compiled.push({ when: branch.when.trim(), value: branch.value });
1783
- }
1784
- conditions = compiled.length > 0 ? compiled : null;
1785
- }
1786
- let description = null;
1787
- if (opts.description !== void 0) {
1788
- if (typeof opts.description !== "string") {
1789
- throw new Error("flag description must be a string");
1790
- }
1791
- const trimmed = opts.description.trim();
1792
- description = trimmed.length > 0 ? trimmed : null;
1793
- }
1794
- return {
1795
- type,
1796
- default: opts.default,
1797
- variants,
1798
- conditions,
1799
- description
1800
- };
1801
- }
1802
- function defineFlags(input) {
1803
- if (input === null || typeof input !== "object" || typeof input.flags !== "object") {
1804
- throw new Error("defineFlags expects { flags: { <key>: flag({...}) } }");
1805
- }
1806
- const flags = {};
1807
- for (const key of Object.keys(input.flags)) {
1808
- if (!FLAG_KEY_RE.test(key)) {
1809
- throw new Error(
1810
- `flag key ${JSON.stringify(key)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
1811
- );
1812
- }
1813
- const def = input.flags[key];
1814
- if (def === void 0) continue;
1815
- flags[key] = def;
1816
- }
1817
- return { __config: FLAGS_CONFIG_KIND, flags };
1337
+ interface Buckets {${bucketMembers(names.buckets)}}
1818
1338
  }
1819
1339
 
1820
- // src/config/secrets.ts
1821
- var SECRETS_CONFIG_KIND = "secrets";
1822
- var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
1823
- function secret(name, opts = {}) {
1824
- if (typeof name !== "string" || !SECRET_NAME_RE.test(name)) {
1825
- throw new Error(
1826
- `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")`
1827
- );
1828
- }
1829
- if (opts === null || typeof opts !== "object") {
1830
- throw new Error(`secret("${name}") expects { required?, description? }`);
1831
- }
1832
- let required = true;
1833
- if (opts.required !== void 0) {
1834
- if (typeof opts.required !== "boolean") {
1835
- throw new Error(`secret("${name}") required must be a boolean`);
1836
- }
1837
- required = opts.required;
1838
- }
1839
- let description = null;
1840
- if (opts.description !== void 0) {
1841
- if (typeof opts.description !== "string") {
1842
- throw new Error(`secret("${name}") description must be a string`);
1843
- }
1844
- const trimmed = opts.description.trim();
1845
- description = trimmed.length > 0 ? trimmed : null;
1846
- }
1847
- return { name, required, description };
1848
- }
1849
- function defineSecrets(input) {
1850
- if (input === null || typeof input !== "object" || !Array.isArray(input.secrets)) {
1851
- throw new Error('defineSecrets expects { secrets: [secret("NAME"), ...] }');
1852
- }
1853
- const byName = /* @__PURE__ */ new Map();
1854
- for (const def of input.secrets) {
1855
- if (def === null || typeof def !== "object" || typeof def.name !== "string" || typeof def.required !== "boolean") {
1856
- throw new Error(
1857
- `defineSecrets: every entry must come from secret() \u2014 got ${JSON.stringify(def)}`
1858
- );
1859
- }
1860
- if (byName.has(def.name)) {
1861
- throw new Error(
1862
- `secret "${def.name}" is declared twice in config/secrets.ts \u2014 remove one; a name has exactly one requirement`
1863
- );
1864
- }
1865
- byName.set(def.name, def);
1866
- }
1867
- const secrets = [...byName.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
1868
- return { __config: SECRETS_CONFIG_KIND, secrets };
1340
+ export {};
1341
+ `;
1869
1342
  }
1870
1343
 
1871
1344
  // src/decorators/methods.ts
@@ -2062,22 +1535,14 @@ function validateUploadConfigShape(c) {
2062
1535
  if (c === null || typeof c !== "object") {
2063
1536
  throw new Error("@Upload config must be an object { bucket, pathTemplate, ... }");
2064
1537
  }
2065
- if (typeof c.bucket !== "string" || c.bucket.length === 0) {
1538
+ const bucket = c.bucket;
1539
+ if (typeof bucket !== "string" || bucket.length === 0) {
2066
1540
  throw new Error("@Upload config.bucket must be a non-empty bucket name");
2067
1541
  }
2068
1542
  if (typeof c.pathTemplate !== "string" || c.pathTemplate.length === 0) {
2069
1543
  throw new Error("@Upload config.pathTemplate must be a non-empty key template");
2070
1544
  }
2071
1545
  }
2072
- function validateUploadAgainstStorage(uploadConfig, storage, routeLabel) {
2073
- const def = storage.buckets[uploadConfig.bucket];
2074
- if (def === void 0) {
2075
- const known = Object.keys(storage.buckets);
2076
- throw new Error(
2077
- `@Upload route ${routeLabel} targets bucket "${uploadConfig.bucket}" which is not declared in defineStorage(...). ` + (known.length ? `Known buckets: ${known.join(", ")}.` : "No buckets are declared.")
2078
- );
2079
- }
2080
- }
2081
1546
 
2082
1547
  // src/decorators/params.ts
2083
1548
  function makeParamDecorator(kind, extra) {
@@ -2253,52 +1718,6 @@ function getJobConfig(ctor) {
2253
1718
  return { schedule: meta.schedule, timeout, retry, handler: run };
2254
1719
  }
2255
1720
 
2256
- // src/resource.ts
2257
- var Resource = class {
2258
- /** The env-var names this resource needs. Optional; omit for none. */
2259
- static secrets;
2260
- };
2261
- function hasSecrets(value) {
2262
- if (typeof value !== "object" && typeof value !== "function") return false;
2263
- const secrets = value.secrets;
2264
- return secrets === void 0 || Array.isArray(secrets);
2265
- }
2266
- var registry = [];
2267
- function __registerResource(resource) {
2268
- registry.push({ resource, booted: false });
2269
- }
2270
- function declaredSecrets(resource) {
2271
- const ctor = resource.constructor;
2272
- return hasSecrets(ctor) ? ctor.secrets ?? [] : [];
2273
- }
2274
- async function __runResourceBoot(envMap) {
2275
- for (const entry of registry) {
2276
- if (entry.booted) continue;
2277
- const secrets = declaredSecrets(entry.resource);
2278
- const env = {};
2279
- for (const name of secrets) {
2280
- const value = envMap[name];
2281
- if (value === void 0) {
2282
- throw new Error(
2283
- `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.`
2284
- );
2285
- }
2286
- env[name] = value;
2287
- }
2288
- await entry.resource.init(env);
2289
- entry.booted = true;
2290
- }
2291
- }
2292
- async function __shutdownResources() {
2293
- for (let i = registry.length - 1; i >= 0; i -= 1) {
2294
- const entry = registry[i];
2295
- if (entry.booted && entry.resource.shutdown) {
2296
- await entry.resource.shutdown();
2297
- }
2298
- }
2299
- registry.length = 0;
2300
- }
2301
-
2302
1721
  // src/index.ts
2303
1722
  import { z } from "zod";
2304
1723
  export {
@@ -2308,17 +1727,11 @@ export {
2308
1727
  Client,
2309
1728
  Conflict,
2310
1729
  Controller,
2311
- DEFAULT_TEMPLATE_LOCALE,
2312
1730
  Database,
2313
1731
  Delete,
2314
1732
  Deny,
2315
1733
  Documents,
2316
- EGRESS_CONFIG_KIND,
2317
- EGRESS_TIMEOUT_DEFAULT_MS,
2318
- EGRESS_TIMEOUT_MAX_MS,
2319
- EGRESS_TIMEOUT_MIN_MS,
2320
1734
  EXTENSION_DEPENDENCIES,
2321
- FLAGS_CONFIG_KIND,
2322
1735
  Flags,
2323
1736
  Forbidden,
2324
1737
  Get,
@@ -2327,35 +1740,25 @@ export {
2327
1740
  HttpError,
2328
1741
  Job,
2329
1742
  Log,
2330
- NOTIFICATIONS_CONFIG_KIND,
2331
1743
  NotFound,
2332
1744
  Notifications,
2333
1745
  On,
2334
1746
  OptionalUser,
2335
1747
  PALBASE_EXTENSIONS,
2336
- PROVIDER_CATALOG,
2337
1748
  PalError,
2338
1749
  PalbaseModuleError,
2339
1750
  Param,
2340
1751
  Patch,
2341
1752
  PolicyBuilder,
2342
1753
  Post,
2343
- Purchases,
2344
1754
  Put,
2345
1755
  Query,
2346
1756
  QueryParams,
2347
- RESERVED_SECRET_PREFIX,
2348
1757
  Realtime,
2349
1758
  Req,
2350
1759
  RequestId,
2351
- RequireEntitlement,
2352
- Resource,
2353
- SECRETS_CONFIG_KIND,
2354
- STORAGE_CONFIG_KIND,
2355
1760
  Secrets,
2356
- Spend,
2357
1761
  Storage,
2358
- TEST_USERS_CONFIG_KIND,
2359
1762
  TooManyRequests,
2360
1763
  TraceId,
2361
1764
  TxPlanError,
@@ -2366,32 +1769,19 @@ export {
2366
1769
  User,
2367
1770
  Webhook,
2368
1771
  __getRuntime,
2369
- __registerResource,
2370
1772
  __requestALS,
2371
1773
  __resetRegisteredControllers,
2372
- __runResourceBoot,
2373
1774
  __runWithRuntime,
2374
1775
  __setRuntime,
2375
- __shutdownResources,
2376
1776
  bigint,
2377
1777
  boolean,
2378
- bucket,
2379
1778
  buildModuleClients,
2380
- buildProvider,
2381
1779
  dec,
2382
- defineEgress,
1780
+ defineChannels,
2383
1781
  defineError,
2384
- defineFlags,
2385
1782
  defineMiddleware,
2386
- defineNotifications,
2387
1783
  defineSchema,
2388
- defineSecrets,
2389
- defineStorage,
2390
- defineTestUsers,
2391
- entitlementFor,
2392
1784
  enumType,
2393
- extractVariables,
2394
- flag,
2395
1785
  getErrorRegistry,
2396
1786
  getHookConfig,
2397
1787
  getJobConfig,
@@ -2404,23 +1794,21 @@ export {
2404
1794
  jsonb,
2405
1795
  makeEnvDts,
2406
1796
  makeHttpClient,
2407
- makePurchasesDts,
1797
+ makeStackDts,
2408
1798
  makeTypedDB,
2409
1799
  now,
2410
1800
  numeric,
2411
- parseFileSizeLimit,
1801
+ openai,
1802
+ ownerOnly,
2412
1803
  policy,
1804
+ publicChannel,
2413
1805
  raw,
2414
1806
  recordThrows,
2415
- reservedSecretKey,
2416
- secret,
2417
- spendFor,
2418
- testUser,
2419
1807
  text,
2420
1808
  timestamp,
2421
1809
  toSchemaJSON,
2422
1810
  uuid,
2423
- validateUploadAgainstStorage,
1811
+ vector,
2424
1812
  z
2425
1813
  };
2426
1814
  //# sourceMappingURL=index.js.map