@palbase/backend 22.1.0 → 23.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/bin/palbase-backend.cjs +750 -40
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +5 -5
  4. package/dist/{chunk-YL4C5NRY.js → chunk-HQRJDARQ.js} +2 -2
  5. package/dist/{chunk-74XDEF5J.js → chunk-M5MCBWJI.js} +723 -37
  6. package/dist/chunk-M5MCBWJI.js.map +1 -0
  7. package/dist/{chunk-W5ODXPY3.js → chunk-NS5V43YQ.js} +14 -1
  8. package/dist/chunk-NS5V43YQ.js.map +1 -0
  9. package/dist/{chunk-SQC5EIWY.js → chunk-OHALWEOG.js} +19 -9
  10. package/dist/chunk-OHALWEOG.js.map +1 -0
  11. package/dist/{chunk-I3ON7MYF.js → chunk-PY7YJDCT.js} +129 -18
  12. package/dist/chunk-PY7YJDCT.js.map +1 -0
  13. package/dist/{chunk-N32VDWKH.js → chunk-R3KN6RHD.js} +4 -59
  14. package/dist/chunk-R3KN6RHD.js.map +1 -0
  15. package/dist/{chunk-QMVK4X3V.js → chunk-RCLNBJCM.js} +98 -98
  16. package/dist/chunk-RCLNBJCM.js.map +1 -0
  17. package/dist/db/env.cjs.map +1 -1
  18. package/dist/db/env.d.cts +3 -21
  19. package/dist/db/env.d.ts +3 -21
  20. package/dist/db/index.cjs +140 -16
  21. package/dist/db/index.cjs.map +1 -1
  22. package/dist/db/index.d.cts +3 -2
  23. package/dist/db/index.d.ts +3 -2
  24. package/dist/db/index.js +2 -2
  25. package/dist/{endpoint-BVT6jcVW.d.cts → endpoint-CVWXh6oG.d.ts} +147 -15
  26. package/dist/{endpoint-BVT6jcVW.d.ts → endpoint-c9h5jriX.d.cts} +147 -15
  27. package/dist/engine/index.cjs +750 -40
  28. package/dist/engine/index.cjs.map +1 -1
  29. package/dist/engine/index.d.cts +6 -5
  30. package/dist/engine/index.d.ts +6 -5
  31. package/dist/engine/index.js +4 -4
  32. package/dist/{index-BS1gW4nV.d.cts → index-BZrJXnVh.d.ts} +142 -72
  33. package/dist/{index-BqCiHao8.d.cts → index-By8Dle5U.d.cts} +196 -21
  34. package/dist/{index-vwHoS0l2.d.ts → index-CwAJ7HEe.d.ts} +196 -21
  35. package/dist/{index-CCZqzych.d.ts → index-CxeQSfJP.d.cts} +142 -72
  36. package/dist/index.cjs +388 -1101
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +89 -1134
  39. package/dist/index.d.ts +89 -1134
  40. package/dist/index.js +133 -902
  41. package/dist/index.js.map +1 -1
  42. package/dist/openapi/index.cjs +32 -61
  43. package/dist/openapi/index.cjs.map +1 -1
  44. package/dist/openapi/index.d.cts +6 -2
  45. package/dist/openapi/index.d.ts +6 -2
  46. package/dist/openapi/index.js +34 -26
  47. package/dist/openapi/index.js.map +1 -1
  48. package/dist/{registry-Bsuf-orT.d.ts → registry-B3niOVYp.d.ts} +108 -170
  49. package/dist/{registry-BWttGlaT.d.cts → registry-CqPK2Qby.d.cts} +108 -170
  50. package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
  51. package/dist/stack.cjs.map +1 -0
  52. package/dist/stack.d.cts +76 -0
  53. package/dist/stack.d.ts +76 -0
  54. package/dist/stack.js +1 -0
  55. package/dist/test/index.cjs +482 -9
  56. package/dist/test/index.cjs.map +1 -1
  57. package/dist/test/index.d.cts +35 -3
  58. package/dist/test/index.d.ts +35 -3
  59. package/dist/test/index.js +480 -8
  60. package/dist/test/index.js.map +1 -1
  61. package/docs/README.md +7 -6
  62. package/docs/llms-full.txt +7 -260
  63. package/docs/llms.txt +0 -2
  64. package/package.json +9 -8
  65. package/stager/return_types.js +23 -0
  66. package/template/package.json +1 -1
  67. package/dist/chunk-74XDEF5J.js.map +0 -1
  68. package/dist/chunk-I3ON7MYF.js.map +0 -1
  69. package/dist/chunk-N32VDWKH.js.map +0 -1
  70. package/dist/chunk-QMVK4X3V.js.map +0 -1
  71. package/dist/chunk-SQC5EIWY.js.map +0 -1
  72. package/dist/chunk-W5ODXPY3.js.map +0 -1
  73. package/dist/purchases/keys.cjs.map +0 -1
  74. package/dist/purchases/keys.d.cts +0 -42
  75. package/dist/purchases/keys.d.ts +0 -42
  76. package/dist/purchases/keys.js +0 -1
  77. package/docs/config.md +0 -147
  78. package/docs/resources.md +0 -97
  79. package/template/config/secrets.ts +0 -24
  80. /package/dist/{chunk-YL4C5NRY.js.map → chunk-HQRJDARQ.js.map} +0 -0
  81. /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
package/dist/index.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,28 @@ __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,
60
+ Signal: () => Signal,
61
+ Sse: () => Sse,
62
+ SseOut: () => SseOut,
75
63
  Storage: () => Storage,
76
- TEST_USERS_CONFIG_KIND: () => TEST_USERS_CONFIG_KIND,
77
64
  TooManyRequests: () => TooManyRequests,
78
65
  TraceId: () => TraceId,
79
66
  TxPlanError: () => TxPlanError,
@@ -84,33 +71,19 @@ __export(src_exports, {
84
71
  User: () => User,
85
72
  Webhook: () => Webhook,
86
73
  __getRuntime: () => __getRuntime,
87
- __registerResource: () => __registerResource,
88
74
  __requestALS: () => __requestALS,
89
75
  __resetRegisteredControllers: () => __resetRegisteredControllers,
90
- __runResourceBoot: () => __runResourceBoot,
91
76
  __runWithRuntime: () => __runWithRuntime,
92
77
  __setRuntime: () => __setRuntime,
93
- __shutdownResources: () => __shutdownResources,
94
78
  bigint: () => bigint,
95
79
  boolean: () => boolean,
96
- bucket: () => bucket,
97
80
  buildModuleClients: () => buildModuleClients,
98
- buildProvider: () => buildProvider,
99
81
  dec: () => dec,
100
82
  defineChannels: () => defineChannels,
101
- defineEgress: () => defineEgress,
102
83
  defineError: () => defineError,
103
- defineFlags: () => defineFlags,
104
84
  defineMiddleware: () => defineMiddleware,
105
- defineNotifications: () => defineNotifications,
106
85
  defineSchema: () => defineSchema,
107
- defineSecrets: () => defineSecrets,
108
- defineStorage: () => defineStorage,
109
- defineTestUsers: () => defineTestUsers,
110
- entitlementFor: () => entitlementFor,
111
86
  enumType: () => enumType,
112
- extractVariables: () => extractVariables,
113
- flag: () => flag,
114
87
  getErrorRegistry: () => getErrorRegistry,
115
88
  getHookConfig: () => getHookConfig,
116
89
  getJobConfig: () => getJobConfig,
@@ -123,26 +96,20 @@ __export(src_exports, {
123
96
  jsonb: () => jsonb,
124
97
  makeEnvDts: () => makeEnvDts,
125
98
  makeHttpClient: () => makeHttpClient,
126
- makePurchasesDts: () => makePurchasesDts,
99
+ makeStackDts: () => makeStackDts,
127
100
  makeTypedDB: () => makeTypedDB,
128
101
  now: () => now,
129
102
  numeric: () => numeric,
130
103
  openai: () => openai,
131
104
  ownerOnly: () => ownerOnly,
132
- parseFileSizeLimit: () => parseFileSizeLimit,
133
105
  policy: () => policy,
134
106
  publicChannel: () => publicChannel,
135
107
  raw: () => raw,
136
108
  recordThrows: () => recordThrows,
137
- reservedSecretKey: () => reservedSecretKey,
138
- secret: () => secret,
139
- spendFor: () => spendFor,
140
- testUser: () => testUser,
141
109
  text: () => text,
142
110
  timestamp: () => timestamp,
143
111
  toSchemaJSON: () => toSchemaJSON,
144
112
  uuid: () => uuid,
145
- validateUploadAgainstStorage: () => validateUploadAgainstStorage,
146
113
  vector: () => vector,
147
114
  z: () => import_zod2.z
148
115
  });
@@ -390,6 +357,19 @@ var TxPlanBuilder = class {
390
357
  }
391
358
  return this.push({ op: "insert", table: name, values: encoded }, `${name}.insert()`);
392
359
  },
360
+ upsert: (values, options) => {
361
+ const encoded = encodeMap(values, false);
362
+ if (Object.keys(encoded).length === 0) {
363
+ throw new TxPlanError(`${name}.upsert() needs at least one column`);
364
+ }
365
+ if (options.onConflict.length === 0) {
366
+ throw new TxPlanError(`${name}.upsert() needs at least one onConflict column`);
367
+ }
368
+ return this.push(
369
+ { op: "upsert", table: name, values: encoded, onConflict: options.onConflict },
370
+ `${name}.upsert()`
371
+ );
372
+ },
393
373
  insertMany: (rows) => {
394
374
  if (rows.length === 0) {
395
375
  return new TxRowsImpl(this, SKIPPED_OP, `${name}.insertMany()`);
@@ -600,8 +580,12 @@ function makeTablesAccessor(ops) {
600
580
  update: (id, data) => ops().update(name, id, data),
601
581
  delete: (id) => ops().delete(name, id),
602
582
  findById: (id) => ops().findById(name, id),
603
- findMany: (query) => ops().findMany(name, query),
604
- search: (params) => ops().search(name, params)
583
+ findMany: (query, opts) => ops().findMany(name, query, opts),
584
+ upsert: (data, opts) => ops().upsert(name, data, opts),
585
+ search: (params) => ops().search(name, params),
586
+ similar: (id, params) => ops().similar(name, id, params),
587
+ recommend: (params) => ops().recommend(name, params),
588
+ supersede: (id, row) => ops().supersede(name, id, row)
605
589
  };
606
590
  }
607
591
  }
@@ -610,17 +594,25 @@ function makeTablesAccessor(ops) {
610
594
  }
611
595
  var rawDatabase = makeServiceProxy("Database");
612
596
  function makeTypedSurface(raw2) {
597
+ const reco = raw2;
613
598
  const ops = {
614
599
  query: (sql, params) => raw2.query(sql, params),
615
600
  insert: (table, data) => raw2.insert(table, data),
616
601
  update: (table, id, data) => raw2.update(table, id, data),
617
602
  delete: (table, id) => raw2.delete(table, id),
618
603
  findById: (table, id) => raw2.findById(table, id),
619
- findMany: (table, query) => raw2.findMany(table, query),
620
- search: (table, params) => raw2.search(table, params)
604
+ findMany: (table, query, opts) => raw2.findMany(table, query, opts),
605
+ upsert: (table, data, opts) => raw2.upsert(table, data, opts),
606
+ search: (table, params) => raw2.search(table, params),
607
+ similar: (table, id, params) => reco.similar(table, id, params),
608
+ recommend: (table, params) => reco.recommend(table, params),
609
+ supersede: (table, id, row) => raw2.supersede(table, id, row)
621
610
  };
622
611
  return Object.assign(ops, {
623
- tables: makeTablesAccessor(() => raw2),
612
+ // Both surfaces get it: a savepoint on the service transaction is as useful
613
+ // as one on the request's, and each is bound to its own connection.
614
+ attempt: (fn) => raw2.attempt(fn),
615
+ tables: makeTablesAccessor(() => reco),
624
616
  transaction(fn) {
625
617
  const builder = new TxPlanBuilder();
626
618
  return runTxPlan(raw2, makeTxTablesAccessor(builder), builder, fn);
@@ -683,7 +675,6 @@ var Cache = makeServiceProxy("Cache");
683
675
  var Secrets = makeServiceProxy("Secrets");
684
676
  var Log = makeServiceProxy("Log");
685
677
  var Notifications = makeServiceProxy("Notifications");
686
- var Purchases = makeServiceProxy("Purchases");
687
678
  var rawFlags = makeServiceProxy("Flags");
688
679
  var Flags = Object.assign(
689
680
  {
@@ -727,250 +718,6 @@ var Flags = Object.assign(
727
718
  );
728
719
  var Realtime = makeServiceProxy("Realtime");
729
720
 
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
721
  // src/clients/http.ts
975
722
  var PalbaseModuleError = class extends Error {
976
723
  code;
@@ -1316,12 +1063,12 @@ function buildFlagsClient(http, cfg) {
1316
1063
  return {
1317
1064
  data: Object.keys(values).map((name) => {
1318
1065
  const value = values[name];
1319
- const flag2 = {
1066
+ const flag = {
1320
1067
  name,
1321
1068
  enabled: flagEnabled(value)
1322
1069
  };
1323
- if (typeof value === "string") flag2.variant = { name: value };
1324
- return flag2;
1070
+ if (typeof value === "string") flag.variant = { name: value };
1071
+ return flag;
1325
1072
  }),
1326
1073
  error: null,
1327
1074
  status: res.status
@@ -1549,13 +1296,13 @@ function buildNotificationsClient(http) {
1549
1296
 
1550
1297
  // src/clients/realtime.ts
1551
1298
  var import_node_crypto = require("crypto");
1552
- function mintToken(secret2) {
1299
+ function mintToken(secret) {
1553
1300
  const now2 = Math.floor(Date.now() / 1e3);
1554
1301
  const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
1555
1302
  const payload = Buffer.from(
1556
1303
  JSON.stringify({ iss: "backend-runtime", role: "service_role", iat: now2, exp: now2 + 60 })
1557
1304
  ).toString("base64url");
1558
- const sig = (0, import_node_crypto.createHmac)("sha256", secret2).update(`${header}.${payload}`).digest("base64url");
1305
+ const sig = (0, import_node_crypto.createHmac)("sha256", secret).update(`${header}.${payload}`).digest("base64url");
1559
1306
  return `${header}.${payload}.${sig}`;
1560
1307
  }
1561
1308
  function buildRealtimeClient(cfg) {
@@ -1726,11 +1473,11 @@ function validateStoragePath(p) {
1726
1473
  );
1727
1474
  }
1728
1475
  }
1729
- function toFileObject(bucket2, row) {
1476
+ function toFileObject(bucket, row) {
1730
1477
  return {
1731
1478
  name: row.path ?? "",
1732
1479
  path: row.path ?? "",
1733
- bucket: row.bucket ?? bucket2,
1480
+ bucket: row.bucket ?? bucket,
1734
1481
  size: typeof row.size === "number" ? row.size : 0,
1735
1482
  contentType: row.contentType ?? "",
1736
1483
  checksum: row.checksum ?? "",
@@ -1846,36 +1593,10 @@ function buildModuleClients(cfg) {
1846
1593
  };
1847
1594
  }
1848
1595
 
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
1596
  // src/db/policy.ts
1597
+ function quote(name) {
1598
+ return `"${name.replace(/"/g, '""')}"`;
1599
+ }
1879
1600
  var PolicyBuilder = class {
1880
1601
  _def;
1881
1602
  constructor(name) {
@@ -1911,6 +1632,41 @@ var PolicyBuilder = class {
1911
1632
  this._def.using = sqlExpr;
1912
1633
  return this;
1913
1634
  }
1635
+ /**
1636
+ * "Rows of THIS table whose owner the caller is a member of" — the membership
1637
+ * pattern, written so it cannot recurse.
1638
+ *
1639
+ * THE TRAP IT EXISTS FOR. Written by hand, membership policies point at each
1640
+ * other: `channels` is visible to members, so its policy reads
1641
+ * `channel_members`; `channel_members` is visible to members, so its policy
1642
+ * reads `channels`. Postgres refuses the pair at query time with `infinite
1643
+ * recursion detected in policy for relation ...`, and the error names the
1644
+ * relation but not the cycle. The way out is asymmetry — the MEMBERSHIP table
1645
+ * is protected by `user_id = auth.uid()` and nothing else, and every other
1646
+ * table subqueries INTO it. That shape was in the platform's own schema and
1647
+ * written down nowhere; a customer recovered it by reading that schema.
1648
+ *
1649
+ * `(select auth.uid())` rather than a bare call: the scalar subquery is
1650
+ * evaluated ONCE per statement instead of per row.
1651
+ *
1652
+ * @example
1653
+ * // channels: visible to members. The membership table gets the simple one.
1654
+ * policy("member_read").for("select").to("authenticated")
1655
+ * .memberOf("channel_members", "channel_id")
1656
+ * // → id IN (SELECT "channel_id" FROM "channel_members"
1657
+ * // WHERE "user_id" = (select auth.uid()))
1658
+ */
1659
+ memberOf(membershipTable, foreignKey, options = {}) {
1660
+ if (this._def.using !== null) {
1661
+ throw new Error(
1662
+ `policy(${this._def.name}).memberOf(): this policy already has a using() expression. Write one or the other \u2014 memberOf IS the using expression.`
1663
+ );
1664
+ }
1665
+ const column = options.column ?? "id";
1666
+ const userColumn = options.userColumn ?? "user_id";
1667
+ this._def.using = `${quote(column)} IN (SELECT ${quote(foreignKey)} FROM ${quote(membershipTable)} WHERE ${quote(userColumn)} = (select auth.uid()))`;
1668
+ return this;
1669
+ }
1914
1670
  /** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
1915
1671
  withCheck(sqlExpr) {
1916
1672
  this._def.withCheck = sqlExpr;
@@ -1950,27 +1706,81 @@ function defineSchema(input) {
1950
1706
  if (table.checks !== void 0 && table.checks.length > 0) tableDef.checks = table.checks.slice();
1951
1707
  if (table.indexes !== void 0 && table.indexes.length > 0) tableDef.indexes = table.indexes.slice();
1952
1708
  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)`);
1709
+ const s = table.search;
1710
+ if (s.from !== void 0) {
1711
+ if (s.vector !== void 0) {
1712
+ throw new Error(
1713
+ `table ${String(name)}: search beyan\u0131nda tek bi\xE7im kullan\u0131n \u2014 'from' (yeni) ile 'vector' (eski) birlikte olamaz`
1714
+ );
1715
+ }
1716
+ if (s.from.length === 0) {
1717
+ throw new Error(`table ${String(name)}: search.from bo\u015F olamaz`);
1718
+ }
1719
+ if (s.model === void 0) {
1720
+ throw new Error(
1721
+ `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)`
1722
+ );
1965
1723
  }
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`);
1724
+ if (Array.isArray(s.text) && s.text.length === 0) {
1725
+ throw new Error(`table ${String(name)}: search.text bo\u015F dizi olamaz \u2014 FTS istemiyorsan text: false yaz\u0131n`);
1726
+ }
1727
+ } else {
1728
+ if (typeof s.text === "boolean") {
1729
+ throw new Error(`table ${String(name)}: text:${String(s.text)} yaln\u0131z yeni bi\xE7imde ('from' ile) ge\xE7erli`);
1730
+ }
1731
+ if (s.text === void 0 && s.vector === void 0) {
1732
+ throw new Error(
1733
+ `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`
1734
+ );
1735
+ }
1736
+ if (s.text !== void 0 && s.text.length === 0) {
1737
+ throw new Error(`table ${String(name)}: search.text bo\u015F olamaz \u2014 FTS kolu istemiyorsan alan\u0131 hi\xE7 yazma`);
1738
+ }
1739
+ const legs = s.vector === void 0 ? [] : Array.isArray(s.vector) ? s.vector : [s.vector];
1740
+ for (const leg of legs) {
1741
+ if (leg.model !== void 0 && (leg.from === void 0 || leg.from.length === 0)) {
1742
+ throw new Error(`table ${String(name)}: search.vector.model beyan edildi ama 'from' yok \u2014 embed kayna\u011F\u0131 kolonlar zorunlu (C-2)`);
1743
+ }
1744
+ if (leg.model === void 0 && leg.from !== void 0) {
1745
+ throw new Error(`table ${String(name)}: search.vector.from model'siz anlams\u0131z \u2014 auto-embed i\xE7in model verin`);
1746
+ }
1968
1747
  }
1969
1748
  }
1970
- tableDef.search = table.search;
1749
+ tableDef.search = s;
1750
+ }
1751
+ if (table.memory !== void 0) {
1752
+ if (table.memory.from.length === 0) {
1753
+ throw new Error(`table ${String(name)}: memory.from bo\u015F olamaz`);
1754
+ }
1755
+ tableDef.memory = table.memory;
1971
1756
  }
1972
1757
  tables[name] = tableDef;
1973
1758
  }
1759
+ for (const [name, def] of Object.entries(tables)) {
1760
+ const m = def.memory;
1761
+ if (m === void 0) continue;
1762
+ const target = Object.values(tables).find((t) => t.name === m.into);
1763
+ if (target === void 0) {
1764
+ throw new Error(`table ${name}: memory.into "${m.into}" \u015Femada declared de\u011Fil`);
1765
+ }
1766
+ const subject = m.subject ?? "owner";
1767
+ if (!(subject in def.columns)) {
1768
+ throw new Error(`table ${name}: memory.subject "${subject}" kolonu kaynak tabloda yok`);
1769
+ }
1770
+ if (!(subject in target.columns)) {
1771
+ throw new Error(`table ${name}: memory.subject "${subject}" kolonu hedef "${m.into}" tablosunda yok`);
1772
+ }
1773
+ const factCol = target.columns["fact"];
1774
+ const factDef = factCol !== void 0 && "_def" in factCol ? factCol._def : factCol;
1775
+ if (factDef === void 0 || factDef.type !== "text") {
1776
+ throw new Error(`table ${name}: memory.into "${m.into}" tablosunda "fact" (text) kolonu zorunlu`);
1777
+ }
1778
+ for (const c of m.from) {
1779
+ if (!(c in def.columns)) {
1780
+ throw new Error(`table ${name}: memory.from kolonu "${c}" kaynak tabloda yok`);
1781
+ }
1782
+ }
1783
+ }
1974
1784
  const extensions = [...new Set(input.extensions ?? [])];
1975
1785
  return { tables, extensions };
1976
1786
  }
@@ -2013,9 +1823,45 @@ function policyToJSON(policy2) {
2013
1823
  permissive: policy2.permissive !== false
2014
1824
  };
2015
1825
  }
2016
- function searchToJSON(search) {
1826
+ function commonSearchFields(search, out) {
1827
+ if (search.synonyms !== void 0 && Object.keys(search.synonyms).length > 0) {
1828
+ out.synonyms = Object.fromEntries(
1829
+ Object.entries(search.synonyms).map(([word, alts]) => [word, [...alts]])
1830
+ );
1831
+ }
1832
+ if (search.validity === true) out.validity = true;
1833
+ }
1834
+ function searchToJSON(search, vectorColumn) {
1835
+ if (search.from !== void 0 && search.model !== void 0) {
1836
+ const out2 = {};
1837
+ const textCols = search.text === false ? void 0 : Array.isArray(search.text) && search.text.length > 0 ? search.text : search.from;
1838
+ if (textCols !== void 0) out2.text = { columns: [...textCols] };
1839
+ const v = { metric: search.metric ?? "cosine" };
1840
+ if (vectorColumn !== void 0) v.column = vectorColumn;
1841
+ v.embed = {
1842
+ provider: search.model.provider,
1843
+ model: search.model.model,
1844
+ from: [...search.from],
1845
+ ...search.model.apiKeyName !== void 0 ? { apiKeyName: search.model.apiKeyName } : {},
1846
+ ...search.model.dimensions !== void 0 ? { dimensions: search.model.dimensions } : {},
1847
+ ...search.model.baseURL !== void 0 ? { baseURL: search.model.baseURL } : {}
1848
+ };
1849
+ if (search.staleness !== void 0) v.staleness = search.staleness;
1850
+ if (vectorColumn === void 0) {
1851
+ v.mode = "chunks";
1852
+ if (search.chunks !== void 0) {
1853
+ const c = {};
1854
+ if (search.chunks.size !== void 0 && search.chunks.size > 0) c.sizeChars = search.chunks.size;
1855
+ if (search.chunks.overlap !== void 0 && search.chunks.overlap > 0) c.overlapChars = search.chunks.overlap;
1856
+ if (Object.keys(c).length > 0) v.chunks = c;
1857
+ }
1858
+ }
1859
+ out2.vector = [v];
1860
+ commonSearchFields(search, out2);
1861
+ return out2;
1862
+ }
2017
1863
  const out = {};
2018
- if (search.text !== void 0 && search.text.length > 0) out.text = { columns: [...search.text] };
1864
+ if (Array.isArray(search.text) && search.text.length > 0) out.text = { columns: [...search.text] };
2019
1865
  const legs = search.vector === void 0 ? [] : Array.isArray(search.vector) ? search.vector : [search.vector];
2020
1866
  if (legs.length > 0) {
2021
1867
  out.vector = legs.map((leg) => {
@@ -2035,8 +1881,17 @@ function searchToJSON(search) {
2035
1881
  return v;
2036
1882
  });
2037
1883
  }
1884
+ commonSearchFields(search, out);
2038
1885
  return out;
2039
1886
  }
1887
+ function memoryToJSON(m) {
1888
+ return {
1889
+ from: [...m.from],
1890
+ into: m.into,
1891
+ ...m.subject !== void 0 ? { subject: m.subject } : {},
1892
+ extract: { provider: m.extract.provider, model: m.extract.model }
1893
+ };
1894
+ }
2040
1895
  function tableToJSON(table) {
2041
1896
  const columns = {};
2042
1897
  for (const [name, column] of Object.entries(table.columns)) {
@@ -2073,9 +1928,13 @@ function tableToJSON(table) {
2073
1928
  out.indexes = table.indexes.map((i) => ({ name: i.name, columns: [...i.columns] }));
2074
1929
  }
2075
1930
  if (table.search !== void 0) {
2076
- const sj = searchToJSON(table.search);
1931
+ const vectorColumn = Object.entries(columns).find(([, c]) => c.dimensions !== void 0)?.[0];
1932
+ const sj = searchToJSON(table.search, vectorColumn);
2077
1933
  if (sj.text !== void 0 || sj.vector !== void 0) out.search = sj;
2078
1934
  }
1935
+ if (table.memory !== void 0) {
1936
+ out.memory = memoryToJSON(table.memory);
1937
+ }
2079
1938
  return out;
2080
1939
  }
2081
1940
  function toSchemaJSON(schema) {
@@ -2233,6 +2092,21 @@ var ColumnBuilder = class _ColumnBuilder {
2233
2092
  this._def.defaultNow = true;
2234
2093
  return new _ColumnBuilder(this._def.type, this._def);
2235
2094
  }
2095
+ /**
2096
+ * The DATABASE assigns this column's value — a trigger, a rule, an identity.
2097
+ *
2098
+ * The column becomes optional on INSERT (the author has nothing to send) while
2099
+ * the DDL stays free of a DEFAULT this schema would not honour. It is NOT
2100
+ * `default()`: that declares a value the schema promises to write.
2101
+ *
2102
+ * Naming: deliberately not `generated()`. Postgres has GENERATED columns and
2103
+ * they are a different thing; borrowing the word would send a reader — or a
2104
+ * model writing a schema — to the wrong feature.
2105
+ */
2106
+ dbAssigned() {
2107
+ this._def.dbAssigned = true;
2108
+ return new _ColumnBuilder(this._def.type, this._def);
2109
+ }
2236
2110
  /** Add a foreign key reference. */
2237
2111
  /**
2238
2112
  * Declares that this column used to be called `previous`.
@@ -2370,6 +2244,9 @@ var openai = {
2370
2244
  ...opts?.dimensions !== void 0 ? { dimensions: opts.dimensions } : {},
2371
2245
  ...opts?.baseURL !== void 0 ? { baseURL: opts.baseURL } : {}
2372
2246
  };
2247
+ },
2248
+ chat(model) {
2249
+ return { provider: "openai", model };
2373
2250
  }
2374
2251
  };
2375
2252
 
@@ -2377,10 +2254,11 @@ var openai = {
2377
2254
  function makeTypedTable(name, raw2) {
2378
2255
  return {
2379
2256
  insert: (data) => raw2.insert(name, data),
2257
+ upsert: (data, opts) => raw2.upsert(name, data, opts),
2380
2258
  update: (id, data) => raw2.update(name, id, data),
2381
2259
  delete: (id) => raw2.delete(name, id),
2382
2260
  findById: (id) => raw2.findById(name, id),
2383
- findMany: (query) => raw2.findMany(name, query)
2261
+ findMany: (query, opts) => raw2.findMany(name, query, opts)
2384
2262
  };
2385
2263
  }
2386
2264
  function makeTypedDB(schema, raw2) {
@@ -2470,10 +2348,10 @@ function buildChildEdges(schema) {
2470
2348
  const ref = builder._def.references;
2471
2349
  if (ref === void 0) continue;
2472
2350
  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 });
2351
+ const bucket = edges.get(ref.table);
2352
+ if (bucket === void 0) continue;
2353
+ if (bucket.some((e) => e.child === childName)) continue;
2354
+ bucket.push({ child: childName, column: col });
2477
2355
  }
2478
2356
  }
2479
2357
  return edges;
@@ -2547,638 +2425,50 @@ export {};
2547
2425
  `;
2548
2426
  }
2549
2427
 
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;
2428
+ // src/stack-gen.ts
2429
+ function liveNames(names) {
2430
+ return [...new Set(names)].sort();
2606
2431
  }
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);
2432
+ function memberName(name) {
2433
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
2643
2434
  }
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
- };
2435
+ function members(names) {
2436
+ if (names.length === 0) return "";
2437
+ return `
2438
+ ${names.map((n) => ` ${memberName(n)}: true;`).join("\n")}
2439
+ `;
2667
2440
  }
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 };
2682
- }
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 };
2441
+ function bucketMembers(buckets) {
2442
+ const normalized = buckets.map((b) => typeof b === "string" ? { name: b, variants: [] } : b);
2443
+ const byName = /* @__PURE__ */ new Map();
2444
+ for (const b of normalized) byName.set(b.name, b.variants);
2445
+ const names = [...byName.keys()].sort();
2446
+ if (names.length === 0) return "";
2447
+ const rows = names.map((name) => {
2448
+ const variants = [...new Set(byName.get(name) ?? [])].sort();
2449
+ const union = variants.length === 0 ? "never" : variants.map((v) => JSON.stringify(v)).join(" | ");
2450
+ return ` ${memberName(name)}: { variants: ${union} };`;
2451
+ });
2452
+ return `
2453
+ ${rows.join("\n")}
2454
+ `;
2713
2455
  }
2456
+ function makeStackDts(names) {
2457
+ const secrets = liveNames(names.secrets);
2458
+ const flags = liveNames(names.flags);
2459
+ return `// palbase-stack.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
2460
+ // Source: the linked environment's stack.
2714
2461
 
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
- }
2462
+ declare module "@palbase/backend/stack" {
2463
+ interface Secrets {${members(secrets)}}
2902
2464
 
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
- }
2465
+ interface Flags {${members(flags)}}
3020
2466
 
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 };
2467
+ interface Buckets {${bucketMembers(names.buckets)}}
3131
2468
  }
3132
2469
 
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 };
2470
+ export {};
2471
+ `;
3182
2472
  }
3183
2473
 
3184
2474
  // src/decorators/registry.ts
@@ -3186,7 +2476,7 @@ var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
3186
2476
  var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
3187
2477
  var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
3188
2478
  var THROWS_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.throwsBuffer");
3189
- function carrierOf2(target) {
2479
+ function carrierOf(target) {
3190
2480
  const ctor = typeof target === "function" ? target : target.constructor ?? target;
3191
2481
  return ctor;
3192
2482
  }
@@ -3203,7 +2493,7 @@ function ownParamBuffer(carrier) {
3203
2493
  return carrier[PARAM_BUFFER];
3204
2494
  }
3205
2495
  function recordRoute(target, fnName, method, subpath, options) {
3206
- const carrier = carrierOf2(target);
2496
+ const carrier = carrierOf(target);
3207
2497
  const routes = ownRoutes(carrier);
3208
2498
  const buffer = ownParamBuffer(carrier);
3209
2499
  const params = (buffer[fnName] ?? []).slice().sort((a, b) => a.index - b.index);
@@ -3219,7 +2509,7 @@ function recordRoute(target, fnName, method, subpath, options) {
3219
2509
  routes.push(route);
3220
2510
  }
3221
2511
  function recordParam(target, fnName, meta) {
3222
- const carrier = carrierOf2(target);
2512
+ const carrier = carrierOf(target);
3223
2513
  const buffer = ownParamBuffer(carrier);
3224
2514
  (buffer[fnName] ??= []).push(meta);
3225
2515
  const routes = carrier[ROUTES];
@@ -3232,7 +2522,7 @@ function recordParam(target, fnName, meta) {
3232
2522
  }
3233
2523
  }
3234
2524
  function recordThrows(target, fnName, throws) {
3235
- const carrier = carrierOf2(target);
2525
+ const carrier = carrierOf(target);
3236
2526
  const routes = carrier[ROUTES];
3237
2527
  const route = routes?.find((r) => r.fnName === fnName);
3238
2528
  if (route) {
@@ -3246,7 +2536,7 @@ function recordThrows(target, fnName, throws) {
3246
2536
  if (throwsBuffer) throwsBuffer[fnName] = throws;
3247
2537
  }
3248
2538
  function getRoutes(ctor) {
3249
- const carrier = carrierOf2(ctor);
2539
+ const carrier = carrierOf(ctor);
3250
2540
  const routes = carrier[ROUTES] ?? [];
3251
2541
  const returnBuffer = carrier[RETURN_BUFFER];
3252
2542
  if (returnBuffer) {
@@ -3355,12 +2645,12 @@ var Query = makeMethodDecorator("QUERY");
3355
2645
  // src/decorators/webhook.ts
3356
2646
  var WEBHOOK_META = /* @__PURE__ */ Symbol.for("palbase.backend.webhookMeta");
3357
2647
  var WEBHOOK_EVENTS = /* @__PURE__ */ Symbol.for("palbase.backend.webhookEvents");
3358
- function carrierOf3(ctor) {
2648
+ function carrierOf2(ctor) {
3359
2649
  return ctor;
3360
2650
  }
3361
2651
  function Webhook(options) {
3362
2652
  return function(ctor) {
3363
- const carrier = carrierOf3(ctor);
2653
+ const carrier = carrierOf2(ctor);
3364
2654
  Object.defineProperty(carrier, WEBHOOK_META, {
3365
2655
  value: options,
3366
2656
  enumerable: false,
@@ -3378,7 +2668,7 @@ function Webhook(options) {
3378
2668
  }
3379
2669
  function On(event) {
3380
2670
  return function(target, fnName) {
3381
- const carrier = carrierOf3(target.constructor);
2671
+ const carrier = carrierOf2(target.constructor);
3382
2672
  const existing = carrier[WEBHOOK_EVENTS];
3383
2673
  const entries = existing ? [...existing] : [];
3384
2674
  entries.push({ event, fnName: String(fnName) });
@@ -3391,7 +2681,7 @@ function On(event) {
3391
2681
  };
3392
2682
  }
3393
2683
  function getWebhookConfig(ctor) {
3394
- const carrier = carrierOf3(ctor);
2684
+ const carrier = carrierOf2(ctor);
3395
2685
  const meta = carrier[WEBHOOK_META];
3396
2686
  const entries = carrier[WEBHOOK_EVENTS] ?? [];
3397
2687
  if (!meta) {
@@ -3526,21 +2816,36 @@ function validateUploadConfigShape(c) {
3526
2816
  if (c === null || typeof c !== "object") {
3527
2817
  throw new Error("@Upload config must be an object { bucket, pathTemplate, ... }");
3528
2818
  }
3529
- if (typeof c.bucket !== "string" || c.bucket.length === 0) {
2819
+ const bucket = c.bucket;
2820
+ if (typeof bucket !== "string" || bucket.length === 0) {
3530
2821
  throw new Error("@Upload config.bucket must be a non-empty bucket name");
3531
2822
  }
3532
2823
  if (typeof c.pathTemplate !== "string" || c.pathTemplate.length === 0) {
3533
2824
  throw new Error("@Upload config.pathTemplate must be a non-empty key template");
3534
2825
  }
3535
2826
  }
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
- }
2827
+
2828
+ // src/decorators/sse.ts
2829
+ function Sse(subpath = "", config) {
2830
+ const { auth, rateLimit, ...sseConfig } = config;
2831
+ const options = {
2832
+ sseConfig,
2833
+ ...auth !== void 0 ? { auth } : {},
2834
+ ...rateLimit !== void 0 ? { rateLimit } : {}
2835
+ };
2836
+ return function(target, propertyKey) {
2837
+ recordRoute(target, String(propertyKey), "POST", subpath, options);
2838
+ };
2839
+ }
2840
+ function SseOut() {
2841
+ return function(target, propertyKey, parameterIndex) {
2842
+ recordParam(target, String(propertyKey), { index: parameterIndex, kind: "sseOut" });
2843
+ };
2844
+ }
2845
+ function Signal() {
2846
+ return function(target, propertyKey, parameterIndex) {
2847
+ recordParam(target, String(propertyKey), { index: parameterIndex, kind: "signal" });
2848
+ };
3544
2849
  }
3545
2850
 
3546
2851
  // src/decorators/params.ts
@@ -3590,6 +2895,93 @@ function defineMiddleware(fn) {
3590
2895
  return fn;
3591
2896
  }
3592
2897
 
2898
+ // src/errors.ts
2899
+ var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
2900
+ var HttpError = class extends Error {
2901
+ status;
2902
+ error;
2903
+ errorDescription;
2904
+ data;
2905
+ /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
2906
+ [HTTP_ERROR_BRAND] = true;
2907
+ constructor(status, error, errorDescription, data) {
2908
+ super(errorDescription);
2909
+ this.name = "HttpError";
2910
+ this.status = status;
2911
+ this.error = error;
2912
+ this.errorDescription = errorDescription;
2913
+ if (data !== void 0) {
2914
+ this.data = data;
2915
+ }
2916
+ }
2917
+ /**
2918
+ * Serialize to the standard Palbase error response format.
2919
+ * The `requestId` is injected by the runtime layer from the request context.
2920
+ * When called without arguments (e.g. JSON.stringify), request_id is omitted.
2921
+ * When `data` is set, it is appended as a strict-superset field.
2922
+ */
2923
+ toJSON(requestId) {
2924
+ const result = {
2925
+ error: this.error,
2926
+ error_description: this.errorDescription,
2927
+ status: this.status
2928
+ };
2929
+ if (requestId) {
2930
+ result.request_id = requestId;
2931
+ }
2932
+ if (this.data !== void 0) {
2933
+ result.data = this.data;
2934
+ }
2935
+ return result;
2936
+ }
2937
+ };
2938
+ var PalError = class extends HttpError {
2939
+ constructor(status, code, description, data) {
2940
+ super(status, code, description, data);
2941
+ this.name = "PalError";
2942
+ }
2943
+ };
2944
+ var NamedHttpError = class extends HttpError {
2945
+ constructor(status, defaultCode, name, message, code, data) {
2946
+ super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
2947
+ this.name = name;
2948
+ }
2949
+ };
2950
+ function defaultMessage(name) {
2951
+ const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
2952
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
2953
+ }
2954
+ var BadRequest = class extends NamedHttpError {
2955
+ constructor(data, message) {
2956
+ super(400, "bad_request", "BadRequest", message, void 0, data);
2957
+ }
2958
+ };
2959
+ var Unauthorized = class extends NamedHttpError {
2960
+ constructor(message, code, data) {
2961
+ super(401, "unauthorized", "Unauthorized", message, code, data);
2962
+ }
2963
+ };
2964
+ var Forbidden = class extends NamedHttpError {
2965
+ constructor(message, code, data) {
2966
+ super(403, "forbidden", "Forbidden", message, code, data);
2967
+ }
2968
+ };
2969
+ var NotFound = class extends NamedHttpError {
2970
+ constructor(message, code, data) {
2971
+ super(404, "not_found", "NotFound", message, code, data);
2972
+ }
2973
+ };
2974
+ var Conflict = class extends NamedHttpError {
2975
+ constructor(message, code, data) {
2976
+ super(409, "conflict", "Conflict", message, code, data);
2977
+ }
2978
+ };
2979
+ var TooManyRequests = class extends NamedHttpError {
2980
+ constructor(data, message) {
2981
+ super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
2982
+ }
2983
+ };
2984
+
3593
2985
  // src/error-registry.ts
3594
2986
  var import_zod_to_openapi = require("@asteasolutions/zod-to-openapi");
3595
2987
  var import_zod = require("zod");
@@ -3599,27 +2991,7 @@ var BUILTIN_DATA_SCHEMAS = {
3599
2991
  bad_request: import_zod.z.object({
3600
2992
  fields: import_zod.z.array(import_zod.z.object({ field: import_zod.z.string(), message: import_zod.z.string() }))
3601
2993
  }),
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
- })
2994
+ too_many_requests: import_zod.z.object({ retryAfter: import_zod.z.number().int() })
3623
2995
  };
3624
2996
  function getErrorRegistry() {
3625
2997
  const g = globalThis;
@@ -3631,13 +3003,7 @@ function getErrorRegistry() {
3631
3003
  ["forbidden", 403, "Forbidden"],
3632
3004
  ["not_found", 404, "NotFound"],
3633
3005
  ["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"]
3006
+ ["too_many_requests", 429, "TooManyRequests"]
3641
3007
  ]) {
3642
3008
  const dataSchema = BUILTIN_DATA_SCHEMAS[code];
3643
3009
  m.set(code, {
@@ -3658,8 +3024,8 @@ function defineError(code, status, dataSchema) {
3658
3024
  `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
3025
  );
3660
3026
  }
3661
- const registry3 = getErrorRegistry();
3662
- const existing = registry3.get(code);
3027
+ const registry2 = getErrorRegistry();
3028
+ const existing = registry2.get(code);
3663
3029
  const className = defaultClassName(code);
3664
3030
  const dataDigest = dataSchema ? digestOf(code, dataSchema) : void 0;
3665
3031
  if (existing) {
@@ -3688,7 +3054,7 @@ function defineError(code, status, dataSchema) {
3688
3054
  };
3689
3055
  const cls = dataSchema ? makeWithData(dataSchema) : makeWithoutData();
3690
3056
  Object.defineProperty(cls, "name", { value: className });
3691
- registry3.set(code, {
3057
+ registry2.set(code, {
3692
3058
  code,
3693
3059
  status,
3694
3060
  className,
@@ -3847,52 +3213,6 @@ function getJobConfig(ctor) {
3847
3213
  return { schedule: meta.schedule, timeout, retry, handler: run };
3848
3214
  }
3849
3215
 
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
3216
  // src/index.ts
3897
3217
  var import_zod2 = require("zod");
3898
3218
  // Annotate the CommonJS export names for ESM import in node:
@@ -3903,17 +3223,11 @@ var import_zod2 = require("zod");
3903
3223
  Client,
3904
3224
  Conflict,
3905
3225
  Controller,
3906
- DEFAULT_TEMPLATE_LOCALE,
3907
3226
  Database,
3908
3227
  Delete,
3909
3228
  Deny,
3910
3229
  Documents,
3911
- EGRESS_CONFIG_KIND,
3912
- EGRESS_TIMEOUT_DEFAULT_MS,
3913
- EGRESS_TIMEOUT_MAX_MS,
3914
- EGRESS_TIMEOUT_MIN_MS,
3915
3230
  EXTENSION_DEPENDENCIES,
3916
- FLAGS_CONFIG_KIND,
3917
3231
  Flags,
3918
3232
  Forbidden,
3919
3233
  Get,
@@ -3922,35 +3236,28 @@ var import_zod2 = require("zod");
3922
3236
  HttpError,
3923
3237
  Job,
3924
3238
  Log,
3925
- NOTIFICATIONS_CONFIG_KIND,
3926
3239
  NotFound,
3927
3240
  Notifications,
3928
3241
  On,
3929
3242
  OptionalUser,
3930
3243
  PALBASE_EXTENSIONS,
3931
- PROVIDER_CATALOG,
3932
3244
  PalError,
3933
3245
  PalbaseModuleError,
3934
3246
  Param,
3935
3247
  Patch,
3936
3248
  PolicyBuilder,
3937
3249
  Post,
3938
- Purchases,
3939
3250
  Put,
3940
3251
  Query,
3941
3252
  QueryParams,
3942
- RESERVED_SECRET_PREFIX,
3943
3253
  Realtime,
3944
3254
  Req,
3945
3255
  RequestId,
3946
- RequireEntitlement,
3947
- Resource,
3948
- SECRETS_CONFIG_KIND,
3949
- STORAGE_CONFIG_KIND,
3950
3256
  Secrets,
3951
- Spend,
3257
+ Signal,
3258
+ Sse,
3259
+ SseOut,
3952
3260
  Storage,
3953
- TEST_USERS_CONFIG_KIND,
3954
3261
  TooManyRequests,
3955
3262
  TraceId,
3956
3263
  TxPlanError,
@@ -3961,33 +3268,19 @@ var import_zod2 = require("zod");
3961
3268
  User,
3962
3269
  Webhook,
3963
3270
  __getRuntime,
3964
- __registerResource,
3965
3271
  __requestALS,
3966
3272
  __resetRegisteredControllers,
3967
- __runResourceBoot,
3968
3273
  __runWithRuntime,
3969
3274
  __setRuntime,
3970
- __shutdownResources,
3971
3275
  bigint,
3972
3276
  boolean,
3973
- bucket,
3974
3277
  buildModuleClients,
3975
- buildProvider,
3976
3278
  dec,
3977
3279
  defineChannels,
3978
- defineEgress,
3979
3280
  defineError,
3980
- defineFlags,
3981
3281
  defineMiddleware,
3982
- defineNotifications,
3983
3282
  defineSchema,
3984
- defineSecrets,
3985
- defineStorage,
3986
- defineTestUsers,
3987
- entitlementFor,
3988
3283
  enumType,
3989
- extractVariables,
3990
- flag,
3991
3284
  getErrorRegistry,
3992
3285
  getHookConfig,
3993
3286
  getJobConfig,
@@ -4000,26 +3293,20 @@ var import_zod2 = require("zod");
4000
3293
  jsonb,
4001
3294
  makeEnvDts,
4002
3295
  makeHttpClient,
4003
- makePurchasesDts,
3296
+ makeStackDts,
4004
3297
  makeTypedDB,
4005
3298
  now,
4006
3299
  numeric,
4007
3300
  openai,
4008
3301
  ownerOnly,
4009
- parseFileSizeLimit,
4010
3302
  policy,
4011
3303
  publicChannel,
4012
3304
  raw,
4013
3305
  recordThrows,
4014
- reservedSecretKey,
4015
- secret,
4016
- spendFor,
4017
- testUser,
4018
3306
  text,
4019
3307
  timestamp,
4020
3308
  toSchemaJSON,
4021
3309
  uuid,
4022
- validateUploadAgainstStorage,
4023
3310
  vector,
4024
3311
  z
4025
3312
  });