@palbase/backend 22.1.0 → 23.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/bin/palbase-backend.cjs +439 -18
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +4 -4
  4. package/dist/{chunk-SQC5EIWY.js → chunk-FSGSB42K.js} +11 -6
  5. package/dist/chunk-FSGSB42K.js.map +1 -0
  6. package/dist/{chunk-I3ON7MYF.js → chunk-HAF67F2H.js} +73 -16
  7. package/dist/chunk-HAF67F2H.js.map +1 -0
  8. package/dist/{chunk-QMVK4X3V.js → chunk-OMRTHM4X.js} +98 -98
  9. package/dist/chunk-OMRTHM4X.js.map +1 -0
  10. package/dist/{chunk-74XDEF5J.js → chunk-REZU6UKT.js} +432 -17
  11. package/dist/chunk-REZU6UKT.js.map +1 -0
  12. package/dist/{chunk-YL4C5NRY.js → chunk-Y5HXVUMP.js} +2 -2
  13. package/dist/{chunk-N32VDWKH.js → chunk-ZC6Q2BRD.js} +4 -59
  14. package/dist/chunk-ZC6Q2BRD.js.map +1 -0
  15. package/dist/db/env.cjs.map +1 -1
  16. package/dist/db/env.d.cts +3 -21
  17. package/dist/db/env.d.ts +3 -21
  18. package/dist/db/index.cjs +72 -15
  19. package/dist/db/index.cjs.map +1 -1
  20. package/dist/db/index.d.cts +3 -2
  21. package/dist/db/index.d.ts +3 -2
  22. package/dist/db/index.js +1 -1
  23. package/dist/{endpoint-BVT6jcVW.d.cts → endpoint-BavvbW4P.d.ts} +77 -7
  24. package/dist/{endpoint-BVT6jcVW.d.ts → endpoint-i8TTCohk.d.cts} +77 -7
  25. package/dist/engine/index.cjs +439 -18
  26. package/dist/engine/index.cjs.map +1 -1
  27. package/dist/engine/index.d.cts +6 -5
  28. package/dist/engine/index.d.ts +6 -5
  29. package/dist/engine/index.js +3 -3
  30. package/dist/{index-vwHoS0l2.d.ts → index-B3jmmItD.d.ts} +111 -5
  31. package/dist/{index-CCZqzych.d.ts → index-B7YBEG5w.d.ts} +96 -71
  32. package/dist/{index-BqCiHao8.d.cts → index-Bmvx1EvJ.d.cts} +111 -5
  33. package/dist/{index-BS1gW4nV.d.cts → index-E7OscPJT.d.cts} +96 -71
  34. package/dist/index.cjs +285 -1099
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +89 -1134
  37. package/dist/index.d.ts +89 -1134
  38. package/dist/index.js +107 -902
  39. package/dist/index.js.map +1 -1
  40. package/dist/openapi/index.cjs +3 -57
  41. package/dist/openapi/index.cjs.map +1 -1
  42. package/dist/openapi/index.d.cts +3 -2
  43. package/dist/openapi/index.d.ts +3 -2
  44. package/dist/openapi/index.js +5 -22
  45. package/dist/openapi/index.js.map +1 -1
  46. package/dist/{registry-Bsuf-orT.d.ts → registry-C3H2uPeZ.d.cts} +13 -169
  47. package/dist/{registry-BWttGlaT.d.cts → registry-DY3d9l1k.d.ts} +13 -169
  48. package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
  49. package/dist/stack.cjs.map +1 -0
  50. package/dist/stack.d.cts +76 -0
  51. package/dist/stack.d.ts +76 -0
  52. package/dist/stack.js +1 -0
  53. package/docs/README.md +7 -6
  54. package/docs/llms-full.txt +7 -260
  55. package/docs/llms.txt +0 -2
  56. package/package.json +6 -6
  57. package/template/package.json +1 -1
  58. package/dist/chunk-74XDEF5J.js.map +0 -1
  59. package/dist/chunk-I3ON7MYF.js.map +0 -1
  60. package/dist/chunk-N32VDWKH.js.map +0 -1
  61. package/dist/chunk-QMVK4X3V.js.map +0 -1
  62. package/dist/chunk-SQC5EIWY.js.map +0 -1
  63. package/dist/purchases/keys.cjs.map +0 -1
  64. package/dist/purchases/keys.d.cts +0 -42
  65. package/dist/purchases/keys.d.ts +0 -42
  66. package/dist/purchases/keys.js +0 -1
  67. package/docs/config.md +0 -147
  68. package/docs/resources.md +0 -97
  69. package/template/config/secrets.ts +0 -24
  70. /package/dist/{chunk-YL4C5NRY.js.map → chunk-Y5HXVUMP.js.map} +0 -0
  71. /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -26,17 +26,11 @@ __export(src_exports, {
26
26
  Client: () => Client,
27
27
  Conflict: () => Conflict,
28
28
  Controller: () => Controller,
29
- DEFAULT_TEMPLATE_LOCALE: () => DEFAULT_TEMPLATE_LOCALE,
30
29
  Database: () => Database,
31
30
  Delete: () => Delete,
32
31
  Deny: () => Deny,
33
32
  Documents: () => Documents,
34
- EGRESS_CONFIG_KIND: () => EGRESS_CONFIG_KIND,
35
- EGRESS_TIMEOUT_DEFAULT_MS: () => EGRESS_TIMEOUT_DEFAULT_MS,
36
- EGRESS_TIMEOUT_MAX_MS: () => EGRESS_TIMEOUT_MAX_MS,
37
- EGRESS_TIMEOUT_MIN_MS: () => EGRESS_TIMEOUT_MIN_MS,
38
33
  EXTENSION_DEPENDENCIES: () => EXTENSION_DEPENDENCIES,
39
- FLAGS_CONFIG_KIND: () => FLAGS_CONFIG_KIND,
40
34
  Flags: () => Flags,
41
35
  Forbidden: () => Forbidden,
42
36
  Get: () => Get,
@@ -45,35 +39,25 @@ __export(src_exports, {
45
39
  HttpError: () => HttpError,
46
40
  Job: () => Job,
47
41
  Log: () => Log,
48
- NOTIFICATIONS_CONFIG_KIND: () => NOTIFICATIONS_CONFIG_KIND,
49
42
  NotFound: () => NotFound,
50
43
  Notifications: () => Notifications,
51
44
  On: () => On,
52
45
  OptionalUser: () => OptionalUser,
53
46
  PALBASE_EXTENSIONS: () => PALBASE_EXTENSIONS,
54
- PROVIDER_CATALOG: () => PROVIDER_CATALOG,
55
47
  PalError: () => PalError,
56
48
  PalbaseModuleError: () => PalbaseModuleError,
57
49
  Param: () => Param,
58
50
  Patch: () => Patch,
59
51
  PolicyBuilder: () => PolicyBuilder,
60
52
  Post: () => Post,
61
- Purchases: () => Purchases,
62
53
  Put: () => Put,
63
54
  Query: () => Query,
64
55
  QueryParams: () => QueryParams,
65
- RESERVED_SECRET_PREFIX: () => RESERVED_SECRET_PREFIX,
66
56
  Realtime: () => Realtime,
67
57
  Req: () => Req,
68
58
  RequestId: () => RequestId,
69
- RequireEntitlement: () => RequireEntitlement,
70
- Resource: () => Resource,
71
- SECRETS_CONFIG_KIND: () => SECRETS_CONFIG_KIND,
72
- STORAGE_CONFIG_KIND: () => STORAGE_CONFIG_KIND,
73
59
  Secrets: () => Secrets,
74
- Spend: () => Spend,
75
60
  Storage: () => Storage,
76
- TEST_USERS_CONFIG_KIND: () => TEST_USERS_CONFIG_KIND,
77
61
  TooManyRequests: () => TooManyRequests,
78
62
  TraceId: () => TraceId,
79
63
  TxPlanError: () => TxPlanError,
@@ -84,33 +68,19 @@ __export(src_exports, {
84
68
  User: () => User,
85
69
  Webhook: () => Webhook,
86
70
  __getRuntime: () => __getRuntime,
87
- __registerResource: () => __registerResource,
88
71
  __requestALS: () => __requestALS,
89
72
  __resetRegisteredControllers: () => __resetRegisteredControllers,
90
- __runResourceBoot: () => __runResourceBoot,
91
73
  __runWithRuntime: () => __runWithRuntime,
92
74
  __setRuntime: () => __setRuntime,
93
- __shutdownResources: () => __shutdownResources,
94
75
  bigint: () => bigint,
95
76
  boolean: () => boolean,
96
- bucket: () => bucket,
97
77
  buildModuleClients: () => buildModuleClients,
98
- buildProvider: () => buildProvider,
99
78
  dec: () => dec,
100
79
  defineChannels: () => defineChannels,
101
- defineEgress: () => defineEgress,
102
80
  defineError: () => defineError,
103
- defineFlags: () => defineFlags,
104
81
  defineMiddleware: () => defineMiddleware,
105
- defineNotifications: () => defineNotifications,
106
82
  defineSchema: () => defineSchema,
107
- defineSecrets: () => defineSecrets,
108
- defineStorage: () => defineStorage,
109
- defineTestUsers: () => defineTestUsers,
110
- entitlementFor: () => entitlementFor,
111
83
  enumType: () => enumType,
112
- extractVariables: () => extractVariables,
113
- flag: () => flag,
114
84
  getErrorRegistry: () => getErrorRegistry,
115
85
  getHookConfig: () => getHookConfig,
116
86
  getJobConfig: () => getJobConfig,
@@ -123,26 +93,20 @@ __export(src_exports, {
123
93
  jsonb: () => jsonb,
124
94
  makeEnvDts: () => makeEnvDts,
125
95
  makeHttpClient: () => makeHttpClient,
126
- makePurchasesDts: () => makePurchasesDts,
96
+ makeStackDts: () => makeStackDts,
127
97
  makeTypedDB: () => makeTypedDB,
128
98
  now: () => now,
129
99
  numeric: () => numeric,
130
100
  openai: () => openai,
131
101
  ownerOnly: () => ownerOnly,
132
- parseFileSizeLimit: () => parseFileSizeLimit,
133
102
  policy: () => policy,
134
103
  publicChannel: () => publicChannel,
135
104
  raw: () => raw,
136
105
  recordThrows: () => recordThrows,
137
- reservedSecretKey: () => reservedSecretKey,
138
- secret: () => secret,
139
- spendFor: () => spendFor,
140
- testUser: () => testUser,
141
106
  text: () => text,
142
107
  timestamp: () => timestamp,
143
108
  toSchemaJSON: () => toSchemaJSON,
144
109
  uuid: () => uuid,
145
- validateUploadAgainstStorage: () => validateUploadAgainstStorage,
146
110
  vector: () => vector,
147
111
  z: () => import_zod2.z
148
112
  });
@@ -601,7 +565,10 @@ function makeTablesAccessor(ops) {
601
565
  delete: (id) => ops().delete(name, id),
602
566
  findById: (id) => ops().findById(name, id),
603
567
  findMany: (query) => ops().findMany(name, query),
604
- search: (params) => ops().search(name, params)
568
+ search: (params) => ops().search(name, params),
569
+ similar: (id, params) => ops().similar(name, id, params),
570
+ recommend: (params) => ops().recommend(name, params),
571
+ supersede: (id, row) => ops().supersede(name, id, row)
605
572
  };
606
573
  }
607
574
  }
@@ -610,6 +577,7 @@ function makeTablesAccessor(ops) {
610
577
  }
611
578
  var rawDatabase = makeServiceProxy("Database");
612
579
  function makeTypedSurface(raw2) {
580
+ const reco = raw2;
613
581
  const ops = {
614
582
  query: (sql, params) => raw2.query(sql, params),
615
583
  insert: (table, data) => raw2.insert(table, data),
@@ -617,10 +585,13 @@ function makeTypedSurface(raw2) {
617
585
  delete: (table, id) => raw2.delete(table, id),
618
586
  findById: (table, id) => raw2.findById(table, id),
619
587
  findMany: (table, query) => raw2.findMany(table, query),
620
- search: (table, params) => raw2.search(table, params)
588
+ search: (table, params) => raw2.search(table, params),
589
+ similar: (table, id, params) => reco.similar(table, id, params),
590
+ recommend: (table, params) => reco.recommend(table, params),
591
+ supersede: (table, id, row) => raw2.supersede(table, id, row)
621
592
  };
622
593
  return Object.assign(ops, {
623
- tables: makeTablesAccessor(() => raw2),
594
+ tables: makeTablesAccessor(() => reco),
624
595
  transaction(fn) {
625
596
  const builder = new TxPlanBuilder();
626
597
  return runTxPlan(raw2, makeTxTablesAccessor(builder), builder, fn);
@@ -683,7 +654,6 @@ var Cache = makeServiceProxy("Cache");
683
654
  var Secrets = makeServiceProxy("Secrets");
684
655
  var Log = makeServiceProxy("Log");
685
656
  var Notifications = makeServiceProxy("Notifications");
686
- var Purchases = makeServiceProxy("Purchases");
687
657
  var rawFlags = makeServiceProxy("Flags");
688
658
  var Flags = Object.assign(
689
659
  {
@@ -727,250 +697,6 @@ var Flags = Object.assign(
727
697
  );
728
698
  var Realtime = makeServiceProxy("Realtime");
729
699
 
730
- // src/errors.ts
731
- var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
732
- var HttpError = class extends Error {
733
- status;
734
- error;
735
- errorDescription;
736
- data;
737
- /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
738
- [HTTP_ERROR_BRAND] = true;
739
- constructor(status, error, errorDescription, data) {
740
- super(errorDescription);
741
- this.name = "HttpError";
742
- this.status = status;
743
- this.error = error;
744
- this.errorDescription = errorDescription;
745
- if (data !== void 0) {
746
- this.data = data;
747
- }
748
- }
749
- /**
750
- * Serialize to the standard Palbase error response format.
751
- * The `requestId` is injected by the runtime layer from the request context.
752
- * When called without arguments (e.g. JSON.stringify), request_id is omitted.
753
- * When `data` is set, it is appended as a strict-superset field.
754
- */
755
- toJSON(requestId) {
756
- const result = {
757
- error: this.error,
758
- error_description: this.errorDescription,
759
- status: this.status
760
- };
761
- if (requestId) {
762
- result.request_id = requestId;
763
- }
764
- if (this.data !== void 0) {
765
- result.data = this.data;
766
- }
767
- return result;
768
- }
769
- };
770
- var PalError = class extends HttpError {
771
- constructor(status, code, description, data) {
772
- super(status, code, description, data);
773
- this.name = "PalError";
774
- }
775
- };
776
- var NamedHttpError = class extends HttpError {
777
- constructor(status, defaultCode, name, message, code, data) {
778
- super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
779
- this.name = name;
780
- }
781
- };
782
- function defaultMessage(name) {
783
- const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
784
- return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
785
- }
786
- var BadRequest = class extends NamedHttpError {
787
- constructor(data, message) {
788
- super(400, "bad_request", "BadRequest", message, void 0, data);
789
- }
790
- };
791
- var Unauthorized = class extends NamedHttpError {
792
- constructor(message, code, data) {
793
- super(401, "unauthorized", "Unauthorized", message, code, data);
794
- }
795
- };
796
- var Forbidden = class extends NamedHttpError {
797
- constructor(message, code, data) {
798
- super(403, "forbidden", "Forbidden", message, code, data);
799
- }
800
- };
801
- var NotFound = class extends NamedHttpError {
802
- constructor(message, code, data) {
803
- super(404, "not_found", "NotFound", message, code, data);
804
- }
805
- };
806
- var Conflict = class extends NamedHttpError {
807
- constructor(message, code, data) {
808
- super(409, "conflict", "Conflict", message, code, data);
809
- }
810
- };
811
- var TooManyRequests = class extends NamedHttpError {
812
- constructor(data, message) {
813
- super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
814
- }
815
- };
816
-
817
- // src/purchases/errors.ts
818
- function asPalstoreError(err) {
819
- if (!(err instanceof Error)) return null;
820
- const e = err;
821
- if (typeof e.code !== "string" || typeof e.status !== "number") return null;
822
- return { code: e.code, status: e.status, message: e.message };
823
- }
824
- function toHttpError(err) {
825
- if (err instanceof HttpError) return err;
826
- const p = asPalstoreError(err);
827
- if (!p) return err;
828
- switch (p.code) {
829
- case "entitlement_required": {
830
- const entitlement = err.entitlement ?? null;
831
- return new PalError(403, "entitlement_required", p.message, { entitlement });
832
- }
833
- case "quota_exceeded":
834
- case "credit_insufficient": {
835
- const state = err.state;
836
- return new PalError(429, p.code, p.message, state);
837
- }
838
- default:
839
- return err;
840
- }
841
- }
842
-
843
- // src/purchases/registry.ts
844
- var ENTITLEMENTS = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.entitlements");
845
- var SPENDS = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.spends");
846
- function carrierOf(target) {
847
- return typeof target === "function" ? target : target.constructor ?? target;
848
- }
849
- function own(carrier, slot) {
850
- if (!Object.prototype.hasOwnProperty.call(carrier, slot)) {
851
- carrier[slot] = {};
852
- }
853
- return carrier[slot];
854
- }
855
- function recordEntitlement(target, fnName, key) {
856
- own(carrierOf(target), ENTITLEMENTS)[fnName] = key;
857
- }
858
- function recordSpend(target, fnName, meta) {
859
- own(carrierOf(target), SPENDS)[fnName] = meta;
860
- }
861
- function entitlementFor(ctor, fnName) {
862
- return carrierOf(ctor)[ENTITLEMENTS]?.[fnName];
863
- }
864
- function spendFor(ctor, fnName) {
865
- return carrierOf(ctor)[SPENDS]?.[fnName];
866
- }
867
-
868
- // src/purchases/subject.ts
869
- var subjectByRequest = /* @__PURE__ */ new WeakMap();
870
- function storeEnv() {
871
- return process.env.PALSTORE_STORE_ENV === "sandbox" ? "sandbox" : "production";
872
- }
873
- async function currentSubjectId() {
874
- const store = __requestALS.getStore();
875
- if (!store) {
876
- throw new Error(
877
- "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."
878
- );
879
- }
880
- const memoized = subjectByRequest.get(store);
881
- if (memoized) return memoized;
882
- const userId = store.userId;
883
- if (!userId) {
884
- throw new Unauthorized(
885
- "This endpoint requires an authenticated user: purchases entitlements and quota are per-user, and the subject is resolved from the request's identity."
886
- );
887
- }
888
- const resolving = Purchases.resolveSubject({ userRef: userId, storeEnv: storeEnv() }).then(
889
- (r) => r.subjectId
890
- );
891
- subjectByRequest.set(store, resolving);
892
- resolving.catch(() => subjectByRequest.delete(store));
893
- return resolving;
894
- }
895
-
896
- // src/purchases/decorators.ts
897
- var WRAPPED = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.wrapped");
898
- function idempotencyKeyFor(key) {
899
- const store = __requestALS.getStore();
900
- const base = store?.idempotencyKey || store?.requestId;
901
- if (!base) {
902
- throw new Error(
903
- "@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."
904
- );
905
- }
906
- return `${base}:${key}`;
907
- }
908
- function ensureWrapper(target, fnName, descriptor) {
909
- const original = descriptor.value;
910
- if (typeof original !== "function") {
911
- throw new Error(
912
- `@RequireEntitlement/@Spend can only decorate a method; "${fnName}" is not one.`
913
- );
914
- }
915
- if (original[WRAPPED]) return;
916
- const wrapper = async function(...args) {
917
- const ctor = this.constructor;
918
- const entitlement = entitlementFor(ctor, fnName);
919
- const spend = spendFor(ctor, fnName);
920
- let subjectId;
921
- try {
922
- subjectId = await currentSubjectId();
923
- } catch (err) {
924
- throw toHttpError(err);
925
- }
926
- if (entitlement !== void 0) {
927
- try {
928
- await Purchases.require(subjectId, entitlement);
929
- } catch (err) {
930
- throw toHttpError(err);
931
- }
932
- }
933
- if (spend === void 0) {
934
- return original.apply(this, args);
935
- }
936
- let handlerFailure;
937
- try {
938
- return await Purchases.withSpend(
939
- subjectId,
940
- spend.key,
941
- { count: spend.count, idempotencyKey: idempotencyKeyFor(spend.key) },
942
- async () => {
943
- try {
944
- return await original.apply(this, args);
945
- } catch (err) {
946
- handlerFailure = { err };
947
- throw err;
948
- }
949
- }
950
- );
951
- } catch (err) {
952
- if (handlerFailure !== void 0 && handlerFailure.err === err) throw err;
953
- throw toHttpError(err);
954
- }
955
- };
956
- wrapper[WRAPPED] = true;
957
- descriptor.value = wrapper;
958
- }
959
- function RequireEntitlement(key) {
960
- return function(target, propertyKey, descriptor) {
961
- const fnName = String(propertyKey);
962
- recordEntitlement(target, fnName, key);
963
- ensureWrapper(target, fnName, descriptor);
964
- };
965
- }
966
- function Spend(key) {
967
- return function(target, propertyKey, descriptor) {
968
- const fnName = String(propertyKey);
969
- recordSpend(target, fnName, { key, count: 1 });
970
- ensureWrapper(target, fnName, descriptor);
971
- };
972
- }
973
-
974
700
  // src/clients/http.ts
975
701
  var PalbaseModuleError = class extends Error {
976
702
  code;
@@ -1316,12 +1042,12 @@ function buildFlagsClient(http, cfg) {
1316
1042
  return {
1317
1043
  data: Object.keys(values).map((name) => {
1318
1044
  const value = values[name];
1319
- const flag2 = {
1045
+ const flag = {
1320
1046
  name,
1321
1047
  enabled: flagEnabled(value)
1322
1048
  };
1323
- if (typeof value === "string") flag2.variant = { name: value };
1324
- return flag2;
1049
+ if (typeof value === "string") flag.variant = { name: value };
1050
+ return flag;
1325
1051
  }),
1326
1052
  error: null,
1327
1053
  status: res.status
@@ -1549,13 +1275,13 @@ function buildNotificationsClient(http) {
1549
1275
 
1550
1276
  // src/clients/realtime.ts
1551
1277
  var import_node_crypto = require("crypto");
1552
- function mintToken(secret2) {
1278
+ function mintToken(secret) {
1553
1279
  const now2 = Math.floor(Date.now() / 1e3);
1554
1280
  const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
1555
1281
  const payload = Buffer.from(
1556
1282
  JSON.stringify({ iss: "backend-runtime", role: "service_role", iat: now2, exp: now2 + 60 })
1557
1283
  ).toString("base64url");
1558
- const sig = (0, import_node_crypto.createHmac)("sha256", secret2).update(`${header}.${payload}`).digest("base64url");
1284
+ const sig = (0, import_node_crypto.createHmac)("sha256", secret).update(`${header}.${payload}`).digest("base64url");
1559
1285
  return `${header}.${payload}.${sig}`;
1560
1286
  }
1561
1287
  function buildRealtimeClient(cfg) {
@@ -1726,11 +1452,11 @@ function validateStoragePath(p) {
1726
1452
  );
1727
1453
  }
1728
1454
  }
1729
- function toFileObject(bucket2, row) {
1455
+ function toFileObject(bucket, row) {
1730
1456
  return {
1731
1457
  name: row.path ?? "",
1732
1458
  path: row.path ?? "",
1733
- bucket: row.bucket ?? bucket2,
1459
+ bucket: row.bucket ?? bucket,
1734
1460
  size: typeof row.size === "number" ? row.size : 0,
1735
1461
  contentType: row.contentType ?? "",
1736
1462
  checksum: row.checksum ?? "",
@@ -1846,35 +1572,6 @@ function buildModuleClients(cfg) {
1846
1572
  };
1847
1573
  }
1848
1574
 
1849
- // src/purchases/keys-gen.ts
1850
- function liveKeys(entries) {
1851
- return Object.entries(entries ?? {}).filter(([, def]) => def?.removed !== true).map(([key]) => key).sort();
1852
- }
1853
- function memberName(key) {
1854
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
1855
- }
1856
- function members(keys) {
1857
- if (keys.length === 0) return "";
1858
- return `
1859
- ${keys.map((k) => ` ${memberName(k)}: true;`).join("\n")}
1860
- `;
1861
- }
1862
- function makePurchasesDts(manifest) {
1863
- const entitlements = liveKeys(manifest.entitlements);
1864
- const limits = liveKeys({ ...manifest.limits, ...manifest.credits });
1865
- return `// palbase-purchases.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
1866
- // Source: the project's palstore catalog revision.
1867
-
1868
- declare module "@palbase/backend/purchases" {
1869
- interface Entitlements {${members(entitlements)}}
1870
-
1871
- interface Limits {${members(limits)}}
1872
- }
1873
-
1874
- export {};
1875
- `;
1876
- }
1877
-
1878
1575
  // src/db/policy.ts
1879
1576
  var PolicyBuilder = class {
1880
1577
  _def;
@@ -1950,27 +1647,81 @@ function defineSchema(input) {
1950
1647
  if (table.checks !== void 0 && table.checks.length > 0) tableDef.checks = table.checks.slice();
1951
1648
  if (table.indexes !== void 0 && table.indexes.length > 0) tableDef.indexes = table.indexes.slice();
1952
1649
  if (table.search !== void 0) {
1953
- if (table.search.text === void 0 && table.search.vector === void 0) {
1954
- throw new Error(
1955
- `table ${String(name)}: search beyan\u0131 bo\u015F \u2014 en az bir kol (text ya da vector) verin, yoksa alan\u0131 hi\xE7 yazmay\u0131n`
1956
- );
1957
- }
1958
- if (table.search.text !== void 0 && table.search.text.length === 0) {
1959
- throw new Error(`table ${String(name)}: search.text bo\u015F olamaz \u2014 FTS kolu istemiyorsan alan\u0131 hi\xE7 yazma`);
1960
- }
1961
- const legs = table.search.vector === void 0 ? [] : Array.isArray(table.search.vector) ? table.search.vector : [table.search.vector];
1962
- for (const leg of legs) {
1963
- if (leg.model !== void 0 && (leg.from === void 0 || leg.from.length === 0)) {
1964
- throw new Error(`table ${String(name)}: search.vector.model beyan edildi ama 'from' yok \u2014 embed kayna\u011F\u0131 kolonlar zorunlu (C-2)`);
1650
+ const s = table.search;
1651
+ if (s.from !== void 0) {
1652
+ if (s.vector !== void 0) {
1653
+ throw new Error(
1654
+ `table ${String(name)}: search beyan\u0131nda tek bi\xE7im kullan\u0131n \u2014 'from' (yeni) ile 'vector' (eski) birlikte olamaz`
1655
+ );
1965
1656
  }
1966
- if (leg.model === void 0 && leg.from !== void 0) {
1967
- throw new Error(`table ${String(name)}: search.vector.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin`);
1657
+ if (s.from.length === 0) {
1658
+ throw new Error(`table ${String(name)}: search.from bo\u015F olamaz`);
1659
+ }
1660
+ if (s.model === void 0) {
1661
+ throw new Error(
1662
+ `table ${String(name)}: search.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin (BYO i\xE7in eski 'vector' bi\xE7imini kullan\u0131n)`
1663
+ );
1664
+ }
1665
+ if (Array.isArray(s.text) && s.text.length === 0) {
1666
+ throw new Error(`table ${String(name)}: search.text bo\u015F dizi olamaz \u2014 FTS istemiyorsan text: false yaz\u0131n`);
1667
+ }
1668
+ } else {
1669
+ if (typeof s.text === "boolean") {
1670
+ throw new Error(`table ${String(name)}: text:${String(s.text)} yaln\u0131z yeni bi\xE7imde ('from' ile) ge\xE7erli`);
1671
+ }
1672
+ if (s.text === void 0 && s.vector === void 0) {
1673
+ throw new Error(
1674
+ `table ${String(name)}: search beyan\u0131 bo\u015F \u2014 en az bir kol (text ya da vector) verin, yoksa alan\u0131 hi\xE7 yazmay\u0131n`
1675
+ );
1676
+ }
1677
+ if (s.text !== void 0 && s.text.length === 0) {
1678
+ throw new Error(`table ${String(name)}: search.text bo\u015F olamaz \u2014 FTS kolu istemiyorsan alan\u0131 hi\xE7 yazma`);
1679
+ }
1680
+ const legs = s.vector === void 0 ? [] : Array.isArray(s.vector) ? s.vector : [s.vector];
1681
+ for (const leg of legs) {
1682
+ if (leg.model !== void 0 && (leg.from === void 0 || leg.from.length === 0)) {
1683
+ throw new Error(`table ${String(name)}: search.vector.model beyan edildi ama 'from' yok \u2014 embed kayna\u011F\u0131 kolonlar zorunlu (C-2)`);
1684
+ }
1685
+ if (leg.model === void 0 && leg.from !== void 0) {
1686
+ throw new Error(`table ${String(name)}: search.vector.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin`);
1687
+ }
1968
1688
  }
1969
1689
  }
1970
- tableDef.search = table.search;
1690
+ tableDef.search = s;
1691
+ }
1692
+ if (table.memory !== void 0) {
1693
+ if (table.memory.from.length === 0) {
1694
+ throw new Error(`table ${String(name)}: memory.from bo\u015F olamaz`);
1695
+ }
1696
+ tableDef.memory = table.memory;
1971
1697
  }
1972
1698
  tables[name] = tableDef;
1973
1699
  }
1700
+ for (const [name, def] of Object.entries(tables)) {
1701
+ const m = def.memory;
1702
+ if (m === void 0) continue;
1703
+ const target = Object.values(tables).find((t) => t.name === m.into);
1704
+ if (target === void 0) {
1705
+ throw new Error(`table ${name}: memory.into "${m.into}" \u015Femada declared de\u011Fil`);
1706
+ }
1707
+ const subject = m.subject ?? "owner";
1708
+ if (!(subject in def.columns)) {
1709
+ throw new Error(`table ${name}: memory.subject "${subject}" kolonu kaynak tabloda yok`);
1710
+ }
1711
+ if (!(subject in target.columns)) {
1712
+ throw new Error(`table ${name}: memory.subject "${subject}" kolonu hedef "${m.into}" tablosunda yok`);
1713
+ }
1714
+ const factCol = target.columns["fact"];
1715
+ const factDef = factCol !== void 0 && "_def" in factCol ? factCol._def : factCol;
1716
+ if (factDef === void 0 || factDef.type !== "text") {
1717
+ throw new Error(`table ${name}: memory.into "${m.into}" tablosunda "fact" (text) kolonu zorunlu`);
1718
+ }
1719
+ for (const c of m.from) {
1720
+ if (!(c in def.columns)) {
1721
+ throw new Error(`table ${name}: memory.from kolonu "${c}" kaynak tabloda yok`);
1722
+ }
1723
+ }
1724
+ }
1974
1725
  const extensions = [...new Set(input.extensions ?? [])];
1975
1726
  return { tables, extensions };
1976
1727
  }
@@ -2013,9 +1764,45 @@ function policyToJSON(policy2) {
2013
1764
  permissive: policy2.permissive !== false
2014
1765
  };
2015
1766
  }
2016
- function searchToJSON(search) {
1767
+ function commonSearchFields(search, out) {
1768
+ if (search.synonyms !== void 0 && Object.keys(search.synonyms).length > 0) {
1769
+ out.synonyms = Object.fromEntries(
1770
+ Object.entries(search.synonyms).map(([word, alts]) => [word, [...alts]])
1771
+ );
1772
+ }
1773
+ if (search.validity === true) out.validity = true;
1774
+ }
1775
+ function searchToJSON(search, vectorColumn) {
1776
+ if (search.from !== void 0 && search.model !== void 0) {
1777
+ const out2 = {};
1778
+ const textCols = search.text === false ? void 0 : Array.isArray(search.text) && search.text.length > 0 ? search.text : search.from;
1779
+ if (textCols !== void 0) out2.text = { columns: [...textCols] };
1780
+ const v = { metric: search.metric ?? "cosine" };
1781
+ if (vectorColumn !== void 0) v.column = vectorColumn;
1782
+ v.embed = {
1783
+ provider: search.model.provider,
1784
+ model: search.model.model,
1785
+ from: [...search.from],
1786
+ ...search.model.apiKeyName !== void 0 ? { apiKeyName: search.model.apiKeyName } : {},
1787
+ ...search.model.dimensions !== void 0 ? { dimensions: search.model.dimensions } : {},
1788
+ ...search.model.baseURL !== void 0 ? { baseURL: search.model.baseURL } : {}
1789
+ };
1790
+ if (search.staleness !== void 0) v.staleness = search.staleness;
1791
+ if (vectorColumn === void 0) {
1792
+ v.mode = "chunks";
1793
+ if (search.chunks !== void 0) {
1794
+ const c = {};
1795
+ if (search.chunks.size !== void 0 && search.chunks.size > 0) c.sizeChars = search.chunks.size;
1796
+ if (search.chunks.overlap !== void 0 && search.chunks.overlap > 0) c.overlapChars = search.chunks.overlap;
1797
+ if (Object.keys(c).length > 0) v.chunks = c;
1798
+ }
1799
+ }
1800
+ out2.vector = [v];
1801
+ commonSearchFields(search, out2);
1802
+ return out2;
1803
+ }
2017
1804
  const out = {};
2018
- if (search.text !== void 0 && search.text.length > 0) out.text = { columns: [...search.text] };
1805
+ if (Array.isArray(search.text) && search.text.length > 0) out.text = { columns: [...search.text] };
2019
1806
  const legs = search.vector === void 0 ? [] : Array.isArray(search.vector) ? search.vector : [search.vector];
2020
1807
  if (legs.length > 0) {
2021
1808
  out.vector = legs.map((leg) => {
@@ -2035,8 +1822,17 @@ function searchToJSON(search) {
2035
1822
  return v;
2036
1823
  });
2037
1824
  }
1825
+ commonSearchFields(search, out);
2038
1826
  return out;
2039
1827
  }
1828
+ function memoryToJSON(m) {
1829
+ return {
1830
+ from: [...m.from],
1831
+ into: m.into,
1832
+ ...m.subject !== void 0 ? { subject: m.subject } : {},
1833
+ extract: { provider: m.extract.provider, model: m.extract.model }
1834
+ };
1835
+ }
2040
1836
  function tableToJSON(table) {
2041
1837
  const columns = {};
2042
1838
  for (const [name, column] of Object.entries(table.columns)) {
@@ -2073,9 +1869,13 @@ function tableToJSON(table) {
2073
1869
  out.indexes = table.indexes.map((i) => ({ name: i.name, columns: [...i.columns] }));
2074
1870
  }
2075
1871
  if (table.search !== void 0) {
2076
- const sj = searchToJSON(table.search);
1872
+ const vectorColumn = Object.entries(columns).find(([, c]) => c.dimensions !== void 0)?.[0];
1873
+ const sj = searchToJSON(table.search, vectorColumn);
2077
1874
  if (sj.text !== void 0 || sj.vector !== void 0) out.search = sj;
2078
1875
  }
1876
+ if (table.memory !== void 0) {
1877
+ out.memory = memoryToJSON(table.memory);
1878
+ }
2079
1879
  return out;
2080
1880
  }
2081
1881
  function toSchemaJSON(schema) {
@@ -2370,6 +2170,9 @@ var openai = {
2370
2170
  ...opts?.dimensions !== void 0 ? { dimensions: opts.dimensions } : {},
2371
2171
  ...opts?.baseURL !== void 0 ? { baseURL: opts.baseURL } : {}
2372
2172
  };
2173
+ },
2174
+ chat(model) {
2175
+ return { provider: "openai", model };
2373
2176
  }
2374
2177
  };
2375
2178
 
@@ -2470,10 +2273,10 @@ function buildChildEdges(schema) {
2470
2273
  const ref = builder._def.references;
2471
2274
  if (ref === void 0) continue;
2472
2275
  if (ref.table === childName) continue;
2473
- const bucket2 = edges.get(ref.table);
2474
- if (bucket2 === void 0) continue;
2475
- if (bucket2.some((e) => e.child === childName)) continue;
2476
- bucket2.push({ child: childName, column: col });
2276
+ const bucket = edges.get(ref.table);
2277
+ if (bucket === void 0) continue;
2278
+ if (bucket.some((e) => e.child === childName)) continue;
2279
+ bucket.push({ child: childName, column: col });
2477
2280
  }
2478
2281
  }
2479
2282
  return edges;
@@ -2547,638 +2350,50 @@ export {};
2547
2350
  `;
2548
2351
  }
2549
2352
 
2550
- // src/config/storage.ts
2551
- var STORAGE_CONFIG_KIND = "storage";
2552
- var VARIANT_FORMATS = ["webp", "avif", "jpeg", "png"];
2553
- var VARIANT_FITS = ["cover", "contain", "inside"];
2554
- function normalizeVariants(input) {
2555
- if (input === null || typeof input !== "object" || Array.isArray(input)) {
2556
- throw new Error("bucket variants must be an object of { <name>: { width?, height?, format? } }");
2557
- }
2558
- const out = {};
2559
- for (const [name, spec] of Object.entries(input)) {
2560
- if (!/^[a-z0-9][a-z0-9_-]*$/i.test(name)) {
2561
- throw new Error(
2562
- `bucket variant name "${name}" must be alphanumeric with optional - or _ (e.g. "thumb", "card_2x")`
2563
- );
2564
- }
2565
- if (spec === null || typeof spec !== "object" || Array.isArray(spec)) {
2566
- throw new Error(`bucket variant "${name}" must be an object like { width: 100, height: 100 }`);
2567
- }
2568
- for (const dim of ["width", "height"]) {
2569
- const value = spec[dim];
2570
- if (value === void 0) continue;
2571
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
2572
- throw new Error(`bucket variant "${name}" ${dim} must be a positive integer, got ${String(value)}`);
2573
- }
2574
- }
2575
- if (spec.width === void 0 && spec.height === void 0) {
2576
- throw new Error(`bucket variant "${name}" must set width and/or height`);
2577
- }
2578
- if (spec.format !== void 0 && !VARIANT_FORMATS.includes(spec.format)) {
2579
- throw new Error(
2580
- `bucket variant "${name}" format must be one of ${VARIANT_FORMATS.join(", ")}, got "${String(spec.format)}"`
2581
- );
2582
- }
2583
- if (spec.fit !== void 0 && !VARIANT_FITS.includes(spec.fit)) {
2584
- throw new Error(
2585
- `bucket variant "${name}" fit must be one of ${VARIANT_FITS.join(", ")}, got "${String(spec.fit)}"`
2586
- );
2587
- }
2588
- if (spec.quality !== void 0) {
2589
- const q = spec.quality;
2590
- if (typeof q !== "number" || !Number.isInteger(q) || q < 1 || q > 100) {
2591
- throw new Error(`bucket variant "${name}" quality must be an integer 1\u2013100, got ${String(q)}`);
2592
- }
2593
- }
2594
- if (spec.fit === "cover" && (spec.width === void 0 || spec.height === void 0)) {
2595
- throw new Error(`bucket variant "${name}" uses fit "cover", which needs BOTH width and height`);
2596
- }
2597
- out[name] = {
2598
- ...spec.width !== void 0 ? { width: spec.width } : {},
2599
- ...spec.height !== void 0 ? { height: spec.height } : {},
2600
- ...spec.fit !== void 0 ? { fit: spec.fit } : {},
2601
- ...spec.format !== void 0 ? { format: spec.format } : {},
2602
- ...spec.quality !== void 0 ? { quality: spec.quality } : {}
2603
- };
2604
- }
2605
- return out;
2353
+ // src/stack-gen.ts
2354
+ function liveNames(names) {
2355
+ return [...new Set(names)].sort();
2606
2356
  }
2607
- var UNIT_BYTES = {
2608
- b: 1,
2609
- kb: 1024,
2610
- mb: 1024 ** 2,
2611
- gb: 1024 ** 3,
2612
- tb: 1024 ** 4
2613
- };
2614
- function parseFileSizeLimit(input) {
2615
- if (typeof input === "number") {
2616
- if (!Number.isFinite(input) || input < 0 || !Number.isInteger(input)) {
2617
- throw new Error(
2618
- `bucket fileSizeLimit must be a non-negative integer number of bytes, got ${input}`
2619
- );
2620
- }
2621
- return input;
2622
- }
2623
- const trimmed = input.trim();
2624
- const match = /^(\d+(?:\.\d+)?)\s*([a-zA-Z]+)?$/.exec(trimmed);
2625
- if (!match) {
2626
- throw new Error(
2627
- `bucket fileSizeLimit string must be "<number><unit>" like "5MB" or "1GB", got "${input}"`
2628
- );
2629
- }
2630
- const value = Number.parseFloat(match[1]);
2631
- const unit = (match[2] ?? "b").toLowerCase();
2632
- const multiplier = UNIT_BYTES[unit];
2633
- if (multiplier === void 0) {
2634
- throw new Error(
2635
- `bucket fileSizeLimit has an unknown unit "${match[2]}" \u2014 use B, KB, MB, GB, or TB (e.g. "5MB")`
2636
- );
2637
- }
2638
- const bytes = value * multiplier;
2639
- if (!Number.isFinite(bytes) || bytes < 0) {
2640
- throw new Error(`bucket fileSizeLimit resolved to an invalid byte count: ${bytes}`);
2641
- }
2642
- return Math.round(bytes);
2357
+ function memberName(name) {
2358
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
2643
2359
  }
2644
- var MIME_RE = /^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*\/(?:\*|[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*)$/;
2645
- function bucket(opts = {}) {
2646
- const fileSizeLimit = opts.fileSizeLimit === void 0 ? null : parseFileSizeLimit(opts.fileSizeLimit);
2647
- let allowedMimeTypes = null;
2648
- if (opts.allowedMimeTypes !== void 0) {
2649
- if (!Array.isArray(opts.allowedMimeTypes)) {
2650
- throw new Error("bucket allowedMimeTypes must be an array of MIME-type strings");
2651
- }
2652
- for (const mime of opts.allowedMimeTypes) {
2653
- if (typeof mime !== "string" || !MIME_RE.test(mime.trim())) {
2654
- throw new Error(
2655
- `bucket allowedMimeTypes entry "${mime}" is not a valid MIME type (expected "type/subtype", e.g. "image/png")`
2656
- );
2657
- }
2658
- }
2659
- allowedMimeTypes = [...new Set(opts.allowedMimeTypes.map((m) => m.trim()))];
2660
- }
2661
- return {
2662
- public: opts.public ?? false,
2663
- fileSizeLimit,
2664
- allowedMimeTypes,
2665
- variants: opts.variants === void 0 ? null : normalizeVariants(opts.variants)
2666
- };
2667
- }
2668
- function defineStorage(input) {
2669
- if (input === null || typeof input !== "object" || typeof input.buckets !== "object") {
2670
- throw new Error("defineStorage expects { buckets: { <name>: bucket({...}) } }");
2671
- }
2672
- const buckets = {};
2673
- for (const name of Object.keys(input.buckets)) {
2674
- if (name.length === 0) {
2675
- throw new Error("bucket name must be a non-empty string");
2676
- }
2677
- const def = input.buckets[name];
2678
- if (def === void 0) continue;
2679
- buckets[name] = def;
2680
- }
2681
- return { __config: STORAGE_CONFIG_KIND, buckets };
2360
+ function members(names) {
2361
+ if (names.length === 0) return "";
2362
+ return `
2363
+ ${names.map((n) => ` ${memberName(n)}: true;`).join("\n")}
2364
+ `;
2682
2365
  }
2683
-
2684
- // src/config/egress.ts
2685
- var EGRESS_CONFIG_KIND = "egress";
2686
- var EGRESS_TIMEOUT_MIN_MS = 1e3;
2687
- var EGRESS_TIMEOUT_MAX_MS = 3e5;
2688
- var EGRESS_TIMEOUT_DEFAULT_MS = 3e4;
2689
- function defineEgress(input) {
2690
- if (input === null || typeof input !== "object" || !Array.isArray(input.hosts)) {
2691
- throw new Error("defineEgress expects { hosts: string[] }");
2692
- }
2693
- const hosts = [];
2694
- for (const h of input.hosts) {
2695
- if (typeof h !== "string" || h.trim().length === 0) {
2696
- throw new Error("defineEgress: every host must be a non-empty string");
2697
- }
2698
- hosts.push(h.trim().toLowerCase());
2699
- }
2700
- if (input.timeoutMs === void 0) {
2701
- return { __config: EGRESS_CONFIG_KIND, hosts };
2702
- }
2703
- const timeoutMs = input.timeoutMs;
2704
- if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs)) {
2705
- throw new Error("defineEgress: timeoutMs must be an integer number of milliseconds");
2706
- }
2707
- if (timeoutMs < EGRESS_TIMEOUT_MIN_MS || timeoutMs > EGRESS_TIMEOUT_MAX_MS) {
2708
- throw new Error(
2709
- `defineEgress: timeoutMs must be between ${EGRESS_TIMEOUT_MIN_MS} and ${EGRESS_TIMEOUT_MAX_MS} (got ${timeoutMs})`
2710
- );
2711
- }
2712
- return { __config: EGRESS_CONFIG_KIND, hosts, timeoutMs };
2366
+ function bucketMembers(buckets) {
2367
+ const normalized = buckets.map((b) => typeof b === "string" ? { name: b, variants: [] } : b);
2368
+ const byName = /* @__PURE__ */ new Map();
2369
+ for (const b of normalized) byName.set(b.name, b.variants);
2370
+ const names = [...byName.keys()].sort();
2371
+ if (names.length === 0) return "";
2372
+ const rows = names.map((name) => {
2373
+ const variants = [...new Set(byName.get(name) ?? [])].sort();
2374
+ const union = variants.length === 0 ? "never" : variants.map((v) => JSON.stringify(v)).join(" | ");
2375
+ return ` ${memberName(name)}: { variants: ${union} };`;
2376
+ });
2377
+ return `
2378
+ ${rows.join("\n")}
2379
+ `;
2713
2380
  }
2381
+ function makeStackDts(names) {
2382
+ const secrets = liveNames(names.secrets);
2383
+ const flags = liveNames(names.flags);
2384
+ return `// palbase-stack.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
2385
+ // Source: the linked environment's stack.
2714
2386
 
2715
- // src/config/notifications.ts
2716
- var NOTIFICATIONS_CONFIG_KIND = "notifications";
2717
- var RESERVED_SECRET_PREFIX = "PB_NOTIFICATIONS";
2718
- var PROVIDER_CATALOG = {
2719
- apns: {
2720
- channel: "push",
2721
- required: ["teamId", "keyId", "bundleId"],
2722
- optional: ["isProduction"],
2723
- secrets: ["p8"]
2724
- },
2725
- fcm: {
2726
- channel: "push",
2727
- required: [],
2728
- optional: [],
2729
- secrets: ["serviceAccount"]
2730
- },
2731
- sendgrid: {
2732
- channel: "email",
2733
- required: ["fromDomain"],
2734
- optional: [],
2735
- secrets: ["apiKey"]
2736
- },
2737
- ses: {
2738
- channel: "email",
2739
- required: ["region", "accessKeyId", "fromDomain"],
2740
- optional: [],
2741
- secrets: ["secretAccessKey"]
2742
- },
2743
- smtp: {
2744
- channel: "email",
2745
- required: ["host", "port", "fromEmail"],
2746
- optional: ["username", "useStarttls"],
2747
- secrets: ["password"]
2748
- },
2749
- acs: {
2750
- channel: "email",
2751
- required: ["fromEmail"],
2752
- optional: ["fromName"],
2753
- secrets: ["connectionString"]
2754
- },
2755
- twilio: {
2756
- channel: "sms",
2757
- // account_sid required; one of from_number / messaging_service_sid required
2758
- // (enforced by the refine in buildProvider, not by the flat `required` list).
2759
- required: ["accountSid"],
2760
- optional: ["fromNumber", "messagingServiceSid"],
2761
- secrets: ["authToken"]
2762
- }
2763
- };
2764
- var DEFAULT_TEMPLATE_LOCALE = "en";
2765
- function extractVariables(...sources) {
2766
- const found = /* @__PURE__ */ new Set();
2767
- for (const src of sources) {
2768
- if (!src) continue;
2769
- for (const match of src.matchAll(/\{\{\{?\s*([^{}]+?)\s*\}?\}\}/g)) {
2770
- const token = (match[1] ?? "").trim();
2771
- if (token === "" || "#/^!>&".includes(token.charAt(0)) || token === "else") continue;
2772
- if (/\s/.test(token)) continue;
2773
- const root = token.split(".")[0] ?? "";
2774
- if (root === "" || root === "this" || root.startsWith("@")) continue;
2775
- found.add(root);
2776
- }
2777
- }
2778
- return [...found].sort();
2779
- }
2780
- function buildEmailTemplate(slug, opts) {
2781
- const row = (locale, c) => {
2782
- if (!c.subject) throw new Error(`notifications: email template "${slug}" (${locale}) needs a subject`);
2783
- if (!c.html) throw new Error(`notifications: email template "${slug}" (${locale}) needs an html body`);
2784
- const def = {
2785
- slug,
2786
- locale,
2787
- subject: c.subject,
2788
- html_body: c.html,
2789
- variables: c.variables ? [...c.variables] : extractVariables(c.subject, c.html, c.text)
2790
- };
2791
- if (c.text !== void 0) def.text_body = c.text;
2792
- return def;
2793
- };
2794
- const rows = [row(opts.locale ?? DEFAULT_TEMPLATE_LOCALE, opts)];
2795
- for (const [locale, content] of Object.entries(opts.locales ?? {})) {
2796
- rows.push(row(locale, content));
2797
- }
2798
- return rows;
2799
- }
2800
- function buildSmsTemplate(slug, opts) {
2801
- const row = (locale, c) => {
2802
- if (!c.body) throw new Error(`notifications: sms template "${slug}" (${locale}) needs a body`);
2803
- return {
2804
- slug,
2805
- locale,
2806
- body: c.body,
2807
- variables: c.variables ? [...c.variables] : extractVariables(c.body)
2808
- };
2809
- };
2810
- const rows = [row(opts.locale ?? DEFAULT_TEMPLATE_LOCALE, opts)];
2811
- for (const [locale, content] of Object.entries(opts.locales ?? {})) {
2812
- rows.push(row(locale, content));
2813
- }
2814
- return rows;
2815
- }
2816
- function buildTemplates(input) {
2817
- const out = { email: [], sms: [] };
2818
- const seen = /* @__PURE__ */ new Set();
2819
- const claim = (channel, slug, locale) => {
2820
- const key = `${channel}/${slug}/${locale}`;
2821
- if (seen.has(key)) {
2822
- throw new Error(`notifications: ${channel} template "${slug}" declares locale "${locale}" twice`);
2823
- }
2824
- seen.add(key);
2825
- };
2826
- for (const [slug, opts] of Object.entries(input.email ?? {})) {
2827
- for (const row of buildEmailTemplate(slug, opts)) {
2828
- claim("email", row.slug, row.locale);
2829
- out.email.push(row);
2830
- }
2831
- }
2832
- for (const [slug, opts] of Object.entries(input.sms ?? {})) {
2833
- for (const row of buildSmsTemplate(slug, opts)) {
2834
- claim("sms", row.slug, row.locale);
2835
- out.sms.push(row);
2836
- }
2837
- }
2838
- return out;
2839
- }
2840
- function buildProvider(name, opts) {
2841
- const entry = PROVIDER_CATALOG[name];
2842
- const src = { ...opts };
2843
- const enabled = src.enabled === void 0 ? false : Boolean(src.enabled);
2844
- if (!enabled) {
2845
- return { enabled: false };
2846
- }
2847
- const def = { enabled: true };
2848
- for (const field of entry.required) {
2849
- const value = src[field];
2850
- if (value === void 0 || value === null || value === "") {
2851
- throw new Error(
2852
- `notifications: provider "${name}" is enabled but missing required field "${field}"`
2853
- );
2854
- }
2855
- def[field] = value;
2856
- }
2857
- for (const field of entry.optional) {
2858
- if (src[field] !== void 0) {
2859
- def[field] = src[field];
2860
- }
2861
- }
2862
- if (name === "twilio") {
2863
- const hasFrom = Boolean(def.fromNumber);
2864
- const hasMsg = Boolean(def.messagingServiceSid);
2865
- if (!hasFrom && !hasMsg) {
2866
- throw new Error(
2867
- 'notifications: provider "twilio" requires one of "fromNumber" or "messagingServiceSid"'
2868
- );
2869
- }
2870
- }
2871
- return def;
2872
- }
2873
- function defineNotifications(input) {
2874
- if (input === null || typeof input !== "object") {
2875
- throw new Error("defineNotifications expects { push?, email?, sms? }");
2876
- }
2877
- const config = {
2878
- __config: NOTIFICATIONS_CONFIG_KIND,
2879
- push: {},
2880
- email: {},
2881
- sms: {},
2882
- templates: buildTemplates(input.templates ?? {})
2883
- };
2884
- const push = input.push ?? {};
2885
- if (push.apns !== void 0) config.push.apns = buildProvider("apns", push.apns);
2886
- if (push.fcm !== void 0) config.push.fcm = buildProvider("fcm", push.fcm);
2887
- const email = input.email ?? {};
2888
- if (email.sendgrid !== void 0) config.email.sendgrid = buildProvider("sendgrid", email.sendgrid);
2889
- if (email.ses !== void 0) config.email.ses = buildProvider("ses", email.ses);
2890
- if (email.smtp !== void 0) config.email.smtp = buildProvider("smtp", email.smtp);
2891
- if (email.acs !== void 0) config.email.acs = buildProvider("acs", email.acs);
2892
- const sms = input.sms ?? {};
2893
- if (sms.twilio !== void 0) config.sms.twilio = buildProvider("twilio", sms.twilio);
2894
- return config;
2895
- }
2896
- function reservedSecretKey(provider, secretField) {
2897
- return `${RESERVED_SECRET_PREFIX}_${camelToUpperSnake(provider)}_${camelToUpperSnake(secretField)}`;
2898
- }
2899
- function camelToUpperSnake(s) {
2900
- return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
2901
- }
2387
+ declare module "@palbase/backend/stack" {
2388
+ interface Secrets {${members(secrets)}}
2902
2389
 
2903
- // src/config/test-users.ts
2904
- var TEST_USERS_CONFIG_KIND = "test-users";
2905
- var TEST_USER_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
2906
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2907
- var E164_RE = /^\+[1-9]\d{1,14}$/;
2908
- var OTP_LENGTH = 6;
2909
- var OTP_RE = /^\d{6}$/;
2910
- var MIN_PASSWORD_LENGTH = 8;
2911
- function isPlainObject2(value) {
2912
- return typeof value === "object" && value !== null && !Array.isArray(value);
2913
- }
2914
- function normalizeSeed(seed, userName) {
2915
- if (seed === void 0) return {};
2916
- if (!isPlainObject2(seed)) {
2917
- throw new Error(`testUser ${JSON.stringify(userName)}: seed must be { <table>: [ {...} ] }`);
2918
- }
2919
- const out = {};
2920
- for (const [table, rows] of Object.entries(seed)) {
2921
- if (!Array.isArray(rows)) {
2922
- throw new Error(
2923
- `testUser ${JSON.stringify(userName)}: seed.${table} must be an array of rows`
2924
- );
2925
- }
2926
- if (rows.length === 0) {
2927
- throw new Error(
2928
- `testUser ${JSON.stringify(userName)}: seed.${table} is empty \u2014 remove it or add a row`
2929
- );
2930
- }
2931
- for (const row of rows) {
2932
- if (!isPlainObject2(row)) {
2933
- throw new Error(
2934
- `testUser ${JSON.stringify(userName)}: every row in seed.${table} must be an object`
2935
- );
2936
- }
2937
- }
2938
- out[table] = rows;
2939
- }
2940
- return out;
2941
- }
2942
- function testUser(opts = {}) {
2943
- if (!isPlainObject2(opts)) {
2944
- throw new Error("testUser() expects { email?, password?, phone?, otp?, seed? }");
2945
- }
2946
- const hasEmail = opts.email !== void 0;
2947
- const hasPassword = opts.password !== void 0;
2948
- if (hasEmail !== hasPassword) {
2949
- throw new Error(
2950
- "testUser() needs email AND password together (a fixture with stable credentials) or neither (a template the server generates credentials for)"
2951
- );
2952
- }
2953
- let email = null;
2954
- let password = null;
2955
- if (hasEmail) {
2956
- if (typeof opts.email !== "string" || !EMAIL_RE.test(opts.email)) {
2957
- throw new Error(`testUser email ${JSON.stringify(opts.email)} is not a valid address`);
2958
- }
2959
- if (typeof opts.password !== "string" || opts.password.length < MIN_PASSWORD_LENGTH) {
2960
- throw new Error(
2961
- `testUser password must be at least ${MIN_PASSWORD_LENGTH} characters (got ${typeof opts.password === "string" ? opts.password.length : 0})`
2962
- );
2963
- }
2964
- email = opts.email;
2965
- password = opts.password;
2966
- }
2967
- const hasPhone = opts.phone !== void 0;
2968
- const hasOTP = opts.otp !== void 0;
2969
- if (hasPhone !== hasOTP) {
2970
- throw new Error(
2971
- "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"
2972
- );
2973
- }
2974
- let phone = null;
2975
- let otp = null;
2976
- if (hasPhone) {
2977
- if (typeof opts.phone !== "string" || !E164_RE.test(opts.phone)) {
2978
- throw new Error(
2979
- `testUser phone ${JSON.stringify(opts.phone)} must be E.164, e.g. "+905551112233"`
2980
- );
2981
- }
2982
- if (typeof opts.otp !== "string" || !OTP_RE.test(opts.otp)) {
2983
- throw new Error(
2984
- `testUser otp ${JSON.stringify(opts.otp)} must be ${OTP_LENGTH} digits`
2985
- );
2986
- }
2987
- phone = opts.phone;
2988
- otp = opts.otp;
2989
- }
2990
- return { email, password, phone, otp, seed: normalizeSeed(opts.seed, email ?? "<template>") };
2991
- }
2992
- function defineTestUsers(input) {
2993
- if (!isPlainObject2(input) || !isPlainObject2(input.users)) {
2994
- throw new Error("defineTestUsers expects { users: { <name>: testUser({...}) } }");
2995
- }
2996
- const users = {};
2997
- const emails = /* @__PURE__ */ new Map();
2998
- for (const name of Object.keys(input.users)) {
2999
- if (!TEST_USER_NAME_RE.test(name)) {
3000
- throw new Error(
3001
- `test user name ${JSON.stringify(name)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
3002
- );
3003
- }
3004
- const def = input.users[name];
3005
- if (def === void 0) continue;
3006
- if (def.email !== null) {
3007
- const lower = def.email.toLowerCase();
3008
- const already = emails.get(lower);
3009
- if (already !== void 0) {
3010
- throw new Error(
3011
- `test users ${JSON.stringify(already)} and ${JSON.stringify(name)} declare the same e-mail ${JSON.stringify(def.email)}`
3012
- );
3013
- }
3014
- emails.set(lower, name);
3015
- }
3016
- users[name] = def;
3017
- }
3018
- return { __config: TEST_USERS_CONFIG_KIND, users };
3019
- }
2390
+ interface Flags {${members(flags)}}
3020
2391
 
3021
- // src/config/flags.ts
3022
- var FLAGS_CONFIG_KIND = "flags";
3023
- var FLAG_KEY_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
3024
- function valueMatchesType(type, value) {
3025
- switch (type) {
3026
- case "boolean":
3027
- return typeof value === "boolean";
3028
- case "number":
3029
- return typeof value === "number" && Number.isFinite(value);
3030
- case "string":
3031
- return typeof value === "string";
3032
- case "json":
3033
- return typeof value === "object" && value !== null && !Array.isArray(value);
3034
- }
3035
- }
3036
- function flag(opts) {
3037
- if (opts === null || typeof opts !== "object") {
3038
- throw new Error("flag() expects { type, default, variants?, description? }");
3039
- }
3040
- const { type } = opts;
3041
- if (type !== "boolean" && type !== "number" && type !== "string" && type !== "json") {
3042
- throw new Error(`flag type must be "boolean", "number", "string", or "json", got ${JSON.stringify(type)}`);
3043
- }
3044
- if (!valueMatchesType(type, opts.default)) {
3045
- throw new Error(
3046
- `flag default ${JSON.stringify(opts.default)} does not match type "${type}"`
3047
- );
3048
- }
3049
- let variants = null;
3050
- if (opts.variants !== void 0) {
3051
- if (opts.type !== "string") {
3052
- throw new Error(`flag variants are only valid for type "string" (got type "${type}")`);
3053
- }
3054
- if (!Array.isArray(opts.variants)) {
3055
- throw new Error("flag variants must be an array of strings");
3056
- }
3057
- for (const v of opts.variants) {
3058
- if (typeof v !== "string" || v.length === 0) {
3059
- throw new Error(`flag variant ${JSON.stringify(v)} must be a non-empty string`);
3060
- }
3061
- }
3062
- variants = [...new Set(opts.variants)];
3063
- if (variants.length === 0) {
3064
- throw new Error("flag variants must be a non-empty list when supplied");
3065
- }
3066
- if (!variants.includes(opts.default)) {
3067
- throw new Error(
3068
- `flag default ${JSON.stringify(opts.default)} is not one of its variants [${variants.map((v) => JSON.stringify(v)).join(", ")}]`
3069
- );
3070
- }
3071
- }
3072
- let conditions = null;
3073
- if (opts.conditions !== void 0) {
3074
- if (!Array.isArray(opts.conditions)) {
3075
- throw new Error("flag conditions must be an array of { when, value }");
3076
- }
3077
- const compiled = [];
3078
- for (const [i, branch] of opts.conditions.entries()) {
3079
- if (branch === null || typeof branch !== "object") {
3080
- throw new Error(`flag condition ${i} must be an object { when, value }`);
3081
- }
3082
- if (typeof branch.when !== "string" || branch.when.trim().length === 0) {
3083
- throw new Error(`flag condition ${i} needs a non-empty "when" expression`);
3084
- }
3085
- if (!valueMatchesType(type, branch.value)) {
3086
- throw new Error(
3087
- `flag condition ${i} value ${JSON.stringify(branch.value)} does not match type "${type}"`
3088
- );
3089
- }
3090
- if (variants !== null && !variants.includes(branch.value)) {
3091
- throw new Error(
3092
- `flag condition ${i} value ${JSON.stringify(branch.value)} is not one of its variants [${variants.map((v) => JSON.stringify(v)).join(", ")}]`
3093
- );
3094
- }
3095
- compiled.push({ when: branch.when.trim(), value: branch.value });
3096
- }
3097
- conditions = compiled.length > 0 ? compiled : null;
3098
- }
3099
- let description = null;
3100
- if (opts.description !== void 0) {
3101
- if (typeof opts.description !== "string") {
3102
- throw new Error("flag description must be a string");
3103
- }
3104
- const trimmed = opts.description.trim();
3105
- description = trimmed.length > 0 ? trimmed : null;
3106
- }
3107
- return {
3108
- type,
3109
- default: opts.default,
3110
- variants,
3111
- conditions,
3112
- description
3113
- };
3114
- }
3115
- function defineFlags(input) {
3116
- if (input === null || typeof input !== "object" || typeof input.flags !== "object") {
3117
- throw new Error("defineFlags expects { flags: { <key>: flag({...}) } }");
3118
- }
3119
- const flags = {};
3120
- for (const key of Object.keys(input.flags)) {
3121
- if (!FLAG_KEY_RE.test(key)) {
3122
- throw new Error(
3123
- `flag key ${JSON.stringify(key)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
3124
- );
3125
- }
3126
- const def = input.flags[key];
3127
- if (def === void 0) continue;
3128
- flags[key] = def;
3129
- }
3130
- return { __config: FLAGS_CONFIG_KIND, flags };
2392
+ interface Buckets {${bucketMembers(names.buckets)}}
3131
2393
  }
3132
2394
 
3133
- // src/config/secrets.ts
3134
- var SECRETS_CONFIG_KIND = "secrets";
3135
- var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
3136
- function secret(name, opts = {}) {
3137
- if (typeof name !== "string" || !SECRET_NAME_RE.test(name)) {
3138
- throw new Error(
3139
- `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")`
3140
- );
3141
- }
3142
- if (opts === null || typeof opts !== "object") {
3143
- throw new Error(`secret("${name}") expects { required?, description? }`);
3144
- }
3145
- let required = true;
3146
- if (opts.required !== void 0) {
3147
- if (typeof opts.required !== "boolean") {
3148
- throw new Error(`secret("${name}") required must be a boolean`);
3149
- }
3150
- required = opts.required;
3151
- }
3152
- let description = null;
3153
- if (opts.description !== void 0) {
3154
- if (typeof opts.description !== "string") {
3155
- throw new Error(`secret("${name}") description must be a string`);
3156
- }
3157
- const trimmed = opts.description.trim();
3158
- description = trimmed.length > 0 ? trimmed : null;
3159
- }
3160
- return { name, required, description };
3161
- }
3162
- function defineSecrets(input) {
3163
- if (input === null || typeof input !== "object" || !Array.isArray(input.secrets)) {
3164
- throw new Error('defineSecrets expects { secrets: [secret("NAME"), ...] }');
3165
- }
3166
- const byName = /* @__PURE__ */ new Map();
3167
- for (const def of input.secrets) {
3168
- if (def === null || typeof def !== "object" || typeof def.name !== "string" || typeof def.required !== "boolean") {
3169
- throw new Error(
3170
- `defineSecrets: every entry must come from secret() \u2014 got ${JSON.stringify(def)}`
3171
- );
3172
- }
3173
- if (byName.has(def.name)) {
3174
- throw new Error(
3175
- `secret "${def.name}" is declared twice in config/secrets.ts \u2014 remove one; a name has exactly one requirement`
3176
- );
3177
- }
3178
- byName.set(def.name, def);
3179
- }
3180
- const secrets = [...byName.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
3181
- return { __config: SECRETS_CONFIG_KIND, secrets };
2395
+ export {};
2396
+ `;
3182
2397
  }
3183
2398
 
3184
2399
  // src/decorators/registry.ts
@@ -3186,7 +2401,7 @@ var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
3186
2401
  var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
3187
2402
  var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
3188
2403
  var THROWS_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.throwsBuffer");
3189
- function carrierOf2(target) {
2404
+ function carrierOf(target) {
3190
2405
  const ctor = typeof target === "function" ? target : target.constructor ?? target;
3191
2406
  return ctor;
3192
2407
  }
@@ -3203,7 +2418,7 @@ function ownParamBuffer(carrier) {
3203
2418
  return carrier[PARAM_BUFFER];
3204
2419
  }
3205
2420
  function recordRoute(target, fnName, method, subpath, options) {
3206
- const carrier = carrierOf2(target);
2421
+ const carrier = carrierOf(target);
3207
2422
  const routes = ownRoutes(carrier);
3208
2423
  const buffer = ownParamBuffer(carrier);
3209
2424
  const params = (buffer[fnName] ?? []).slice().sort((a, b) => a.index - b.index);
@@ -3219,7 +2434,7 @@ function recordRoute(target, fnName, method, subpath, options) {
3219
2434
  routes.push(route);
3220
2435
  }
3221
2436
  function recordParam(target, fnName, meta) {
3222
- const carrier = carrierOf2(target);
2437
+ const carrier = carrierOf(target);
3223
2438
  const buffer = ownParamBuffer(carrier);
3224
2439
  (buffer[fnName] ??= []).push(meta);
3225
2440
  const routes = carrier[ROUTES];
@@ -3232,7 +2447,7 @@ function recordParam(target, fnName, meta) {
3232
2447
  }
3233
2448
  }
3234
2449
  function recordThrows(target, fnName, throws) {
3235
- const carrier = carrierOf2(target);
2450
+ const carrier = carrierOf(target);
3236
2451
  const routes = carrier[ROUTES];
3237
2452
  const route = routes?.find((r) => r.fnName === fnName);
3238
2453
  if (route) {
@@ -3246,7 +2461,7 @@ function recordThrows(target, fnName, throws) {
3246
2461
  if (throwsBuffer) throwsBuffer[fnName] = throws;
3247
2462
  }
3248
2463
  function getRoutes(ctor) {
3249
- const carrier = carrierOf2(ctor);
2464
+ const carrier = carrierOf(ctor);
3250
2465
  const routes = carrier[ROUTES] ?? [];
3251
2466
  const returnBuffer = carrier[RETURN_BUFFER];
3252
2467
  if (returnBuffer) {
@@ -3355,12 +2570,12 @@ var Query = makeMethodDecorator("QUERY");
3355
2570
  // src/decorators/webhook.ts
3356
2571
  var WEBHOOK_META = /* @__PURE__ */ Symbol.for("palbase.backend.webhookMeta");
3357
2572
  var WEBHOOK_EVENTS = /* @__PURE__ */ Symbol.for("palbase.backend.webhookEvents");
3358
- function carrierOf3(ctor) {
2573
+ function carrierOf2(ctor) {
3359
2574
  return ctor;
3360
2575
  }
3361
2576
  function Webhook(options) {
3362
2577
  return function(ctor) {
3363
- const carrier = carrierOf3(ctor);
2578
+ const carrier = carrierOf2(ctor);
3364
2579
  Object.defineProperty(carrier, WEBHOOK_META, {
3365
2580
  value: options,
3366
2581
  enumerable: false,
@@ -3378,7 +2593,7 @@ function Webhook(options) {
3378
2593
  }
3379
2594
  function On(event) {
3380
2595
  return function(target, fnName) {
3381
- const carrier = carrierOf3(target.constructor);
2596
+ const carrier = carrierOf2(target.constructor);
3382
2597
  const existing = carrier[WEBHOOK_EVENTS];
3383
2598
  const entries = existing ? [...existing] : [];
3384
2599
  entries.push({ event, fnName: String(fnName) });
@@ -3391,7 +2606,7 @@ function On(event) {
3391
2606
  };
3392
2607
  }
3393
2608
  function getWebhookConfig(ctor) {
3394
- const carrier = carrierOf3(ctor);
2609
+ const carrier = carrierOf2(ctor);
3395
2610
  const meta = carrier[WEBHOOK_META];
3396
2611
  const entries = carrier[WEBHOOK_EVENTS] ?? [];
3397
2612
  if (!meta) {
@@ -3526,22 +2741,14 @@ function validateUploadConfigShape(c) {
3526
2741
  if (c === null || typeof c !== "object") {
3527
2742
  throw new Error("@Upload config must be an object { bucket, pathTemplate, ... }");
3528
2743
  }
3529
- if (typeof c.bucket !== "string" || c.bucket.length === 0) {
2744
+ const bucket = c.bucket;
2745
+ if (typeof bucket !== "string" || bucket.length === 0) {
3530
2746
  throw new Error("@Upload config.bucket must be a non-empty bucket name");
3531
2747
  }
3532
2748
  if (typeof c.pathTemplate !== "string" || c.pathTemplate.length === 0) {
3533
2749
  throw new Error("@Upload config.pathTemplate must be a non-empty key template");
3534
2750
  }
3535
2751
  }
3536
- function validateUploadAgainstStorage(uploadConfig, storage, routeLabel) {
3537
- const def = storage.buckets[uploadConfig.bucket];
3538
- if (def === void 0) {
3539
- const known = Object.keys(storage.buckets);
3540
- throw new Error(
3541
- `@Upload route ${routeLabel} targets bucket "${uploadConfig.bucket}" which is not declared in defineStorage(...). ` + (known.length ? `Known buckets: ${known.join(", ")}.` : "No buckets are declared.")
3542
- );
3543
- }
3544
- }
3545
2752
 
3546
2753
  // src/decorators/params.ts
3547
2754
  function makeParamDecorator(kind, extra) {
@@ -3590,6 +2797,93 @@ function defineMiddleware(fn) {
3590
2797
  return fn;
3591
2798
  }
3592
2799
 
2800
+ // src/errors.ts
2801
+ var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
2802
+ var HttpError = class extends Error {
2803
+ status;
2804
+ error;
2805
+ errorDescription;
2806
+ data;
2807
+ /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
2808
+ [HTTP_ERROR_BRAND] = true;
2809
+ constructor(status, error, errorDescription, data) {
2810
+ super(errorDescription);
2811
+ this.name = "HttpError";
2812
+ this.status = status;
2813
+ this.error = error;
2814
+ this.errorDescription = errorDescription;
2815
+ if (data !== void 0) {
2816
+ this.data = data;
2817
+ }
2818
+ }
2819
+ /**
2820
+ * Serialize to the standard Palbase error response format.
2821
+ * The `requestId` is injected by the runtime layer from the request context.
2822
+ * When called without arguments (e.g. JSON.stringify), request_id is omitted.
2823
+ * When `data` is set, it is appended as a strict-superset field.
2824
+ */
2825
+ toJSON(requestId) {
2826
+ const result = {
2827
+ error: this.error,
2828
+ error_description: this.errorDescription,
2829
+ status: this.status
2830
+ };
2831
+ if (requestId) {
2832
+ result.request_id = requestId;
2833
+ }
2834
+ if (this.data !== void 0) {
2835
+ result.data = this.data;
2836
+ }
2837
+ return result;
2838
+ }
2839
+ };
2840
+ var PalError = class extends HttpError {
2841
+ constructor(status, code, description, data) {
2842
+ super(status, code, description, data);
2843
+ this.name = "PalError";
2844
+ }
2845
+ };
2846
+ var NamedHttpError = class extends HttpError {
2847
+ constructor(status, defaultCode, name, message, code, data) {
2848
+ super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
2849
+ this.name = name;
2850
+ }
2851
+ };
2852
+ function defaultMessage(name) {
2853
+ const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
2854
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
2855
+ }
2856
+ var BadRequest = class extends NamedHttpError {
2857
+ constructor(data, message) {
2858
+ super(400, "bad_request", "BadRequest", message, void 0, data);
2859
+ }
2860
+ };
2861
+ var Unauthorized = class extends NamedHttpError {
2862
+ constructor(message, code, data) {
2863
+ super(401, "unauthorized", "Unauthorized", message, code, data);
2864
+ }
2865
+ };
2866
+ var Forbidden = class extends NamedHttpError {
2867
+ constructor(message, code, data) {
2868
+ super(403, "forbidden", "Forbidden", message, code, data);
2869
+ }
2870
+ };
2871
+ var NotFound = class extends NamedHttpError {
2872
+ constructor(message, code, data) {
2873
+ super(404, "not_found", "NotFound", message, code, data);
2874
+ }
2875
+ };
2876
+ var Conflict = class extends NamedHttpError {
2877
+ constructor(message, code, data) {
2878
+ super(409, "conflict", "Conflict", message, code, data);
2879
+ }
2880
+ };
2881
+ var TooManyRequests = class extends NamedHttpError {
2882
+ constructor(data, message) {
2883
+ super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
2884
+ }
2885
+ };
2886
+
3593
2887
  // src/error-registry.ts
3594
2888
  var import_zod_to_openapi = require("@asteasolutions/zod-to-openapi");
3595
2889
  var import_zod = require("zod");
@@ -3599,27 +2893,7 @@ var BUILTIN_DATA_SCHEMAS = {
3599
2893
  bad_request: import_zod.z.object({
3600
2894
  fields: import_zod.z.array(import_zod.z.object({ field: import_zod.z.string(), message: import_zod.z.string() }))
3601
2895
  }),
3602
- too_many_requests: import_zod.z.object({ retryAfter: import_zod.z.number().int() }),
3603
- // Purchases (@RequireEntitlement / @Spend). Their payloads are what make a
3604
- // paywall renderable on the client: WHICH entitlement was missing, and for a
3605
- // spend the real ceiling and reset time rather than a guessed retry delay.
3606
- entitlement_required: import_zod.z.object({ entitlement: import_zod.z.string().nullable() }),
3607
- quota_exceeded: import_zod.z.object({
3608
- key: import_zod.z.string(),
3609
- scope: import_zod.z.string(),
3610
- window: import_zod.z.string(),
3611
- used: import_zod.z.number().int(),
3612
- reserved: import_zod.z.number().int(),
3613
- max: import_zod.z.number().int(),
3614
- remaining: import_zod.z.number().int(),
3615
- resetAt: import_zod.z.string()
3616
- }),
3617
- credit_insufficient: import_zod.z.object({
3618
- key: import_zod.z.string(),
3619
- balance: import_zod.z.number().int(),
3620
- reserved: import_zod.z.number().int(),
3621
- remaining: import_zod.z.number().int()
3622
- })
2896
+ too_many_requests: import_zod.z.object({ retryAfter: import_zod.z.number().int() })
3623
2897
  };
3624
2898
  function getErrorRegistry() {
3625
2899
  const g = globalThis;
@@ -3631,13 +2905,7 @@ function getErrorRegistry() {
3631
2905
  ["forbidden", 403, "Forbidden"],
3632
2906
  ["not_found", 404, "NotFound"],
3633
2907
  ["conflict", 409, "Conflict"],
3634
- ["too_many_requests", 429, "TooManyRequests"],
3635
- // Thrown by the purchases decorators, so they are pre-seeded like the
3636
- // other built-ins: a project never declares them, but every gated or
3637
- // metered route must surface them as TYPED errors on the client.
3638
- ["entitlement_required", 403, "EntitlementRequired"],
3639
- ["quota_exceeded", 429, "QuotaExceeded"],
3640
- ["credit_insufficient", 429, "CreditInsufficient"]
2908
+ ["too_many_requests", 429, "TooManyRequests"]
3641
2909
  ]) {
3642
2910
  const dataSchema = BUILTIN_DATA_SCHEMAS[code];
3643
2911
  m.set(code, {
@@ -3658,8 +2926,8 @@ function defineError(code, status, dataSchema) {
3658
2926
  `defineError: status for "${code}" must be a 4xx/5xx integer, got ${status} \u2014 error responses must not clobber success responses in the project spec.`
3659
2927
  );
3660
2928
  }
3661
- const registry3 = getErrorRegistry();
3662
- const existing = registry3.get(code);
2929
+ const registry2 = getErrorRegistry();
2930
+ const existing = registry2.get(code);
3663
2931
  const className = defaultClassName(code);
3664
2932
  const dataDigest = dataSchema ? digestOf(code, dataSchema) : void 0;
3665
2933
  if (existing) {
@@ -3688,7 +2956,7 @@ function defineError(code, status, dataSchema) {
3688
2956
  };
3689
2957
  const cls = dataSchema ? makeWithData(dataSchema) : makeWithoutData();
3690
2958
  Object.defineProperty(cls, "name", { value: className });
3691
- registry3.set(code, {
2959
+ registry2.set(code, {
3692
2960
  code,
3693
2961
  status,
3694
2962
  className,
@@ -3847,52 +3115,6 @@ function getJobConfig(ctor) {
3847
3115
  return { schedule: meta.schedule, timeout, retry, handler: run };
3848
3116
  }
3849
3117
 
3850
- // src/resource.ts
3851
- var Resource = class {
3852
- /** The env-var names this resource needs. Optional; omit for none. */
3853
- static secrets;
3854
- };
3855
- function hasSecrets(value) {
3856
- if (typeof value !== "object" && typeof value !== "function") return false;
3857
- const secrets = value.secrets;
3858
- return secrets === void 0 || Array.isArray(secrets);
3859
- }
3860
- var registry2 = [];
3861
- function __registerResource(resource) {
3862
- registry2.push({ resource, booted: false });
3863
- }
3864
- function declaredSecrets(resource) {
3865
- const ctor = resource.constructor;
3866
- return hasSecrets(ctor) ? ctor.secrets ?? [] : [];
3867
- }
3868
- async function __runResourceBoot(envMap) {
3869
- for (const entry of registry2) {
3870
- if (entry.booted) continue;
3871
- const secrets = declaredSecrets(entry.resource);
3872
- const env = {};
3873
- for (const name of secrets) {
3874
- const value = envMap[name];
3875
- if (value === void 0) {
3876
- throw new Error(
3877
- `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.`
3878
- );
3879
- }
3880
- env[name] = value;
3881
- }
3882
- await entry.resource.init(env);
3883
- entry.booted = true;
3884
- }
3885
- }
3886
- async function __shutdownResources() {
3887
- for (let i = registry2.length - 1; i >= 0; i -= 1) {
3888
- const entry = registry2[i];
3889
- if (entry.booted && entry.resource.shutdown) {
3890
- await entry.resource.shutdown();
3891
- }
3892
- }
3893
- registry2.length = 0;
3894
- }
3895
-
3896
3118
  // src/index.ts
3897
3119
  var import_zod2 = require("zod");
3898
3120
  // Annotate the CommonJS export names for ESM import in node:
@@ -3903,17 +3125,11 @@ var import_zod2 = require("zod");
3903
3125
  Client,
3904
3126
  Conflict,
3905
3127
  Controller,
3906
- DEFAULT_TEMPLATE_LOCALE,
3907
3128
  Database,
3908
3129
  Delete,
3909
3130
  Deny,
3910
3131
  Documents,
3911
- EGRESS_CONFIG_KIND,
3912
- EGRESS_TIMEOUT_DEFAULT_MS,
3913
- EGRESS_TIMEOUT_MAX_MS,
3914
- EGRESS_TIMEOUT_MIN_MS,
3915
3132
  EXTENSION_DEPENDENCIES,
3916
- FLAGS_CONFIG_KIND,
3917
3133
  Flags,
3918
3134
  Forbidden,
3919
3135
  Get,
@@ -3922,35 +3138,25 @@ var import_zod2 = require("zod");
3922
3138
  HttpError,
3923
3139
  Job,
3924
3140
  Log,
3925
- NOTIFICATIONS_CONFIG_KIND,
3926
3141
  NotFound,
3927
3142
  Notifications,
3928
3143
  On,
3929
3144
  OptionalUser,
3930
3145
  PALBASE_EXTENSIONS,
3931
- PROVIDER_CATALOG,
3932
3146
  PalError,
3933
3147
  PalbaseModuleError,
3934
3148
  Param,
3935
3149
  Patch,
3936
3150
  PolicyBuilder,
3937
3151
  Post,
3938
- Purchases,
3939
3152
  Put,
3940
3153
  Query,
3941
3154
  QueryParams,
3942
- RESERVED_SECRET_PREFIX,
3943
3155
  Realtime,
3944
3156
  Req,
3945
3157
  RequestId,
3946
- RequireEntitlement,
3947
- Resource,
3948
- SECRETS_CONFIG_KIND,
3949
- STORAGE_CONFIG_KIND,
3950
3158
  Secrets,
3951
- Spend,
3952
3159
  Storage,
3953
- TEST_USERS_CONFIG_KIND,
3954
3160
  TooManyRequests,
3955
3161
  TraceId,
3956
3162
  TxPlanError,
@@ -3961,33 +3167,19 @@ var import_zod2 = require("zod");
3961
3167
  User,
3962
3168
  Webhook,
3963
3169
  __getRuntime,
3964
- __registerResource,
3965
3170
  __requestALS,
3966
3171
  __resetRegisteredControllers,
3967
- __runResourceBoot,
3968
3172
  __runWithRuntime,
3969
3173
  __setRuntime,
3970
- __shutdownResources,
3971
3174
  bigint,
3972
3175
  boolean,
3973
- bucket,
3974
3176
  buildModuleClients,
3975
- buildProvider,
3976
3177
  dec,
3977
3178
  defineChannels,
3978
- defineEgress,
3979
3179
  defineError,
3980
- defineFlags,
3981
3180
  defineMiddleware,
3982
- defineNotifications,
3983
3181
  defineSchema,
3984
- defineSecrets,
3985
- defineStorage,
3986
- defineTestUsers,
3987
- entitlementFor,
3988
3182
  enumType,
3989
- extractVariables,
3990
- flag,
3991
3183
  getErrorRegistry,
3992
3184
  getHookConfig,
3993
3185
  getJobConfig,
@@ -4000,26 +3192,20 @@ var import_zod2 = require("zod");
4000
3192
  jsonb,
4001
3193
  makeEnvDts,
4002
3194
  makeHttpClient,
4003
- makePurchasesDts,
3195
+ makeStackDts,
4004
3196
  makeTypedDB,
4005
3197
  now,
4006
3198
  numeric,
4007
3199
  openai,
4008
3200
  ownerOnly,
4009
- parseFileSizeLimit,
4010
3201
  policy,
4011
3202
  publicChannel,
4012
3203
  raw,
4013
3204
  recordThrows,
4014
- reservedSecretKey,
4015
- secret,
4016
- spendFor,
4017
- testUser,
4018
3205
  text,
4019
3206
  timestamp,
4020
3207
  toSchemaJSON,
4021
3208
  uuid,
4022
- validateUploadAgainstStorage,
4023
3209
  vector,
4024
3210
  z
4025
3211
  });