@palbase/backend 10.2.0 → 11.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 (41) hide show
  1. package/dist/chunk-7LAXRLPG.js +418 -0
  2. package/dist/chunk-7LAXRLPG.js.map +1 -0
  3. package/dist/{chunk-4WOQWFUP.js → chunk-LUV36KQU.js} +26 -11
  4. package/dist/{chunk-4WOQWFUP.js.map → chunk-LUV36KQU.js.map} +1 -1
  5. package/dist/{chunk-RGQUB66H.js → chunk-XATG7BRC.js} +22 -2
  6. package/dist/chunk-XATG7BRC.js.map +1 -0
  7. package/dist/db/index.cjs +438 -10
  8. package/dist/db/index.cjs.map +1 -1
  9. package/dist/db/index.d.cts +2 -2
  10. package/dist/db/index.d.ts +2 -2
  11. package/dist/db/index.js +13 -1
  12. package/dist/{endpoint-Cn3ICGTf.d.cts → endpoint-Ck4hER_7.d.cts} +397 -11
  13. package/dist/{endpoint-Cn3ICGTf.d.ts → endpoint-Ck4hER_7.d.ts} +397 -11
  14. package/dist/{index-Bt7UHkAM.d.cts → index-BJAf1uPC.d.cts} +64 -34
  15. package/dist/{index-V0ealeY-.d.ts → index-l7DhBDtn.d.ts} +64 -34
  16. package/dist/index.cjs +776 -108
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +203 -9
  19. package/dist/index.d.ts +203 -9
  20. package/dist/index.js +325 -94
  21. package/dist/index.js.map +1 -1
  22. package/dist/purchases/keys.cjs +19 -0
  23. package/dist/purchases/keys.cjs.map +1 -0
  24. package/dist/purchases/keys.d.cts +42 -0
  25. package/dist/purchases/keys.d.ts +42 -0
  26. package/dist/purchases/keys.js +1 -0
  27. package/dist/purchases/keys.js.map +1 -0
  28. package/dist/test/index.cjs +559 -13
  29. package/dist/test/index.cjs.map +1 -1
  30. package/dist/test/index.d.cts +1 -1
  31. package/dist/test/index.d.ts +1 -1
  32. package/dist/test/index.js +166 -13
  33. package/dist/test/index.js.map +1 -1
  34. package/docs/README.md +7 -6
  35. package/docs/database.md +106 -16
  36. package/docs/getting-started.md +14 -12
  37. package/docs/llms-full.txt +140 -45
  38. package/docs/migrations.md +8 -8
  39. package/docs/schema.md +6 -4
  40. package/package.json +11 -1
  41. package/dist/chunk-RGQUB66H.js.map +0 -1
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  Flags,
6
6
  Log,
7
7
  Notifications,
8
+ Purchases,
8
9
  Queue,
9
10
  Realtime,
10
11
  Storage,
@@ -12,7 +13,7 @@ import {
12
13
  __requestALS,
13
14
  __runWithRuntime,
14
15
  __setRuntime
15
- } from "./chunk-RGQUB66H.js";
16
+ } from "./chunk-XATG7BRC.js";
16
17
  import {
17
18
  EXTENSION_DEPENDENCIES,
18
19
  PALBASE_EXTENSIONS,
@@ -31,7 +32,284 @@ import {
31
32
  text,
32
33
  timestamp,
33
34
  uuid
34
- } from "./chunk-4WOQWFUP.js";
35
+ } from "./chunk-LUV36KQU.js";
36
+ import {
37
+ TxPlanError,
38
+ TxRefError,
39
+ dec,
40
+ inc,
41
+ now
42
+ } from "./chunk-7LAXRLPG.js";
43
+
44
+ // src/errors.ts
45
+ var HttpError = class extends Error {
46
+ status;
47
+ error;
48
+ errorDescription;
49
+ data;
50
+ constructor(status, error, errorDescription, data) {
51
+ super(errorDescription);
52
+ this.name = "HttpError";
53
+ this.status = status;
54
+ this.error = error;
55
+ this.errorDescription = errorDescription;
56
+ if (data !== void 0) {
57
+ this.data = data;
58
+ }
59
+ }
60
+ /**
61
+ * Serialize to the standard Palbase error response format.
62
+ * The `requestId` is injected by the runtime layer from the request context.
63
+ * When called without arguments (e.g. JSON.stringify), request_id is omitted.
64
+ * When `data` is set, it is appended as a strict-superset field.
65
+ */
66
+ toJSON(requestId) {
67
+ const result = {
68
+ error: this.error,
69
+ error_description: this.errorDescription,
70
+ status: this.status
71
+ };
72
+ if (requestId) {
73
+ result.request_id = requestId;
74
+ }
75
+ if (this.data !== void 0) {
76
+ result.data = this.data;
77
+ }
78
+ return result;
79
+ }
80
+ };
81
+ var PalError = class extends HttpError {
82
+ constructor(status, code, description, data) {
83
+ super(status, code, description, data);
84
+ this.name = "PalError";
85
+ }
86
+ };
87
+ var NamedHttpError = class extends HttpError {
88
+ constructor(status, defaultCode, name, message, code, data) {
89
+ super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
90
+ this.name = name;
91
+ }
92
+ };
93
+ function defaultMessage(name) {
94
+ const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
95
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
96
+ }
97
+ var BadRequest = class extends NamedHttpError {
98
+ constructor(data, message) {
99
+ super(400, "bad_request", "BadRequest", message, void 0, data);
100
+ }
101
+ };
102
+ var Unauthorized = class extends NamedHttpError {
103
+ constructor(message, code, data) {
104
+ super(401, "unauthorized", "Unauthorized", message, code, data);
105
+ }
106
+ };
107
+ var Forbidden = class extends NamedHttpError {
108
+ constructor(message, code, data) {
109
+ super(403, "forbidden", "Forbidden", message, code, data);
110
+ }
111
+ };
112
+ var NotFound = class extends NamedHttpError {
113
+ constructor(message, code, data) {
114
+ super(404, "not_found", "NotFound", message, code, data);
115
+ }
116
+ };
117
+ var Conflict = class extends NamedHttpError {
118
+ constructor(message, code, data) {
119
+ super(409, "conflict", "Conflict", message, code, data);
120
+ }
121
+ };
122
+ var TooManyRequests = class extends NamedHttpError {
123
+ constructor(data, message) {
124
+ super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
125
+ }
126
+ };
127
+
128
+ // src/purchases/errors.ts
129
+ function asPalstoreError(err) {
130
+ if (!(err instanceof Error)) return null;
131
+ const e = err;
132
+ if (typeof e.code !== "string" || typeof e.status !== "number") return null;
133
+ return { code: e.code, status: e.status, message: e.message };
134
+ }
135
+ function toHttpError(err) {
136
+ if (err instanceof HttpError) return err;
137
+ const p = asPalstoreError(err);
138
+ if (!p) return err;
139
+ switch (p.code) {
140
+ case "entitlement_required": {
141
+ const entitlement = err.entitlement ?? null;
142
+ return new PalError(403, "entitlement_required", p.message, { entitlement });
143
+ }
144
+ case "quota_exceeded":
145
+ case "credit_insufficient": {
146
+ const state = err.state;
147
+ return new PalError(429, p.code, p.message, state);
148
+ }
149
+ default:
150
+ return err;
151
+ }
152
+ }
153
+
154
+ // src/purchases/registry.ts
155
+ var ENTITLEMENTS = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.entitlements");
156
+ var SPENDS = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.spends");
157
+ function carrierOf(target) {
158
+ return typeof target === "function" ? target : target.constructor ?? target;
159
+ }
160
+ function own(carrier, slot) {
161
+ if (!Object.prototype.hasOwnProperty.call(carrier, slot)) {
162
+ carrier[slot] = {};
163
+ }
164
+ return carrier[slot];
165
+ }
166
+ function recordEntitlement(target, fnName, key) {
167
+ own(carrierOf(target), ENTITLEMENTS)[fnName] = key;
168
+ }
169
+ function recordSpend(target, fnName, meta) {
170
+ own(carrierOf(target), SPENDS)[fnName] = meta;
171
+ }
172
+ function entitlementFor(ctor, fnName) {
173
+ return carrierOf(ctor)[ENTITLEMENTS]?.[fnName];
174
+ }
175
+ function spendFor(ctor, fnName) {
176
+ return carrierOf(ctor)[SPENDS]?.[fnName];
177
+ }
178
+
179
+ // src/purchases/subject.ts
180
+ var subjectByRequest = /* @__PURE__ */ new WeakMap();
181
+ function storeEnv() {
182
+ return process.env.PALSTORE_STORE_ENV === "sandbox" ? "sandbox" : "production";
183
+ }
184
+ async function currentSubjectId() {
185
+ const store = __requestALS.getStore();
186
+ if (!store) {
187
+ throw new Error(
188
+ "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."
189
+ );
190
+ }
191
+ const memoized = subjectByRequest.get(store);
192
+ if (memoized) return memoized;
193
+ const userId = store.userId;
194
+ if (!userId) {
195
+ throw new Unauthorized(
196
+ "This endpoint requires an authenticated user: purchases entitlements and quota are per-user, and the subject is resolved from the request's identity."
197
+ );
198
+ }
199
+ const resolving = Purchases.resolveSubject({ userRef: userId, storeEnv: storeEnv() }).then(
200
+ (r) => r.subjectId
201
+ );
202
+ subjectByRequest.set(store, resolving);
203
+ resolving.catch(() => subjectByRequest.delete(store));
204
+ return resolving;
205
+ }
206
+
207
+ // src/purchases/decorators.ts
208
+ var WRAPPED = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.wrapped");
209
+ function idempotencyKeyFor(key) {
210
+ const store = __requestALS.getStore();
211
+ const base = store?.idempotencyKey || store?.requestId;
212
+ if (!base) {
213
+ throw new Error(
214
+ "@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."
215
+ );
216
+ }
217
+ return `${base}:${key}`;
218
+ }
219
+ function ensureWrapper(target, fnName, descriptor) {
220
+ const original = descriptor.value;
221
+ if (typeof original !== "function") {
222
+ throw new Error(
223
+ `@RequireEntitlement/@Spend can only decorate a method; "${fnName}" is not one.`
224
+ );
225
+ }
226
+ if (original[WRAPPED]) return;
227
+ const wrapper = async function(...args) {
228
+ const ctor = this.constructor;
229
+ const entitlement = entitlementFor(ctor, fnName);
230
+ const spend = spendFor(ctor, fnName);
231
+ let subjectId;
232
+ try {
233
+ subjectId = await currentSubjectId();
234
+ } catch (err) {
235
+ throw toHttpError(err);
236
+ }
237
+ if (entitlement !== void 0) {
238
+ try {
239
+ await Purchases.require(subjectId, entitlement);
240
+ } catch (err) {
241
+ throw toHttpError(err);
242
+ }
243
+ }
244
+ if (spend === void 0) {
245
+ return original.apply(this, args);
246
+ }
247
+ let handlerFailure;
248
+ try {
249
+ return await Purchases.withSpend(
250
+ subjectId,
251
+ spend.key,
252
+ { count: spend.count, idempotencyKey: idempotencyKeyFor(spend.key) },
253
+ async () => {
254
+ try {
255
+ return await original.apply(this, args);
256
+ } catch (err) {
257
+ handlerFailure = { err };
258
+ throw err;
259
+ }
260
+ }
261
+ );
262
+ } catch (err) {
263
+ if (handlerFailure !== void 0 && handlerFailure.err === err) throw err;
264
+ throw toHttpError(err);
265
+ }
266
+ };
267
+ wrapper[WRAPPED] = true;
268
+ descriptor.value = wrapper;
269
+ }
270
+ function RequireEntitlement(key) {
271
+ return function(target, propertyKey, descriptor) {
272
+ const fnName = String(propertyKey);
273
+ recordEntitlement(target, fnName, key);
274
+ ensureWrapper(target, fnName, descriptor);
275
+ };
276
+ }
277
+ function Spend(key) {
278
+ return function(target, propertyKey, descriptor) {
279
+ const fnName = String(propertyKey);
280
+ recordSpend(target, fnName, { key, count: 1 });
281
+ ensureWrapper(target, fnName, descriptor);
282
+ };
283
+ }
284
+
285
+ // src/purchases/keys-gen.ts
286
+ function liveKeys(entries) {
287
+ return Object.entries(entries ?? {}).filter(([, def]) => def?.removed !== true).map(([key]) => key).sort();
288
+ }
289
+ function memberName(key) {
290
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
291
+ }
292
+ function members(keys) {
293
+ if (keys.length === 0) return "";
294
+ return `
295
+ ${keys.map((k) => ` ${memberName(k)}: true;`).join("\n")}
296
+ `;
297
+ }
298
+ function makePurchasesDts(manifest) {
299
+ const entitlements = liveKeys(manifest.entitlements);
300
+ const limits = liveKeys({ ...manifest.limits, ...manifest.credits });
301
+ return `// palbase-purchases.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
302
+ // Source: the project's palstore catalog revision.
303
+
304
+ declare module "@palbase/backend/purchases" {
305
+ interface Entitlements {${members(entitlements)}}
306
+
307
+ interface Limits {${members(limits)}}
308
+ }
309
+
310
+ export {};
311
+ `;
312
+ }
35
313
 
36
314
  // src/db/env-gen.ts
37
315
  function baseTsType(def) {
@@ -142,7 +420,7 @@ function makeEnvDts(schema) {
142
420
  ${blocks.join("\n")}
143
421
  ` : "";
144
422
  return `// AUTO-GENERATED by @palbase/backend \u2014 DO NOT EDIT.
145
- // Regenerated from db/schema.ts on every \`palbase serve\` / deploy.
423
+ // Regenerated from db/schema.ts on every \`palbase db types\` / deploy.
146
424
  // Augments the @palbase/backend/env \`Tables\` interface so \`Database.tables.*\`
147
425
  // is typed with no import and no generic.
148
426
 
@@ -592,7 +870,7 @@ var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
592
870
  var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
593
871
  var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
594
872
  var THROWS_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.throwsBuffer");
595
- function carrierOf(target) {
873
+ function carrierOf2(target) {
596
874
  const ctor = typeof target === "function" ? target : target.constructor ?? target;
597
875
  return ctor;
598
876
  }
@@ -609,7 +887,7 @@ function ownParamBuffer(carrier) {
609
887
  return carrier[PARAM_BUFFER];
610
888
  }
611
889
  function recordRoute(target, fnName, method, subpath, options) {
612
- const carrier = carrierOf(target);
890
+ const carrier = carrierOf2(target);
613
891
  const routes = ownRoutes(carrier);
614
892
  const buffer = ownParamBuffer(carrier);
615
893
  const params = (buffer[fnName] ?? []).slice().sort((a, b) => a.index - b.index);
@@ -625,7 +903,7 @@ function recordRoute(target, fnName, method, subpath, options) {
625
903
  routes.push(route);
626
904
  }
627
905
  function recordParam(target, fnName, meta) {
628
- const carrier = carrierOf(target);
906
+ const carrier = carrierOf2(target);
629
907
  const buffer = ownParamBuffer(carrier);
630
908
  (buffer[fnName] ??= []).push(meta);
631
909
  const routes = carrier[ROUTES];
@@ -638,7 +916,7 @@ function recordParam(target, fnName, meta) {
638
916
  }
639
917
  }
640
918
  function recordThrows(target, fnName, throws) {
641
- const carrier = carrierOf(target);
919
+ const carrier = carrierOf2(target);
642
920
  const routes = carrier[ROUTES];
643
921
  const route = routes?.find((r) => r.fnName === fnName);
644
922
  if (route) {
@@ -652,7 +930,7 @@ function recordThrows(target, fnName, throws) {
652
930
  if (throwsBuffer) throwsBuffer[fnName] = throws;
653
931
  }
654
932
  function getRoutes(ctor) {
655
- const carrier = carrierOf(ctor);
933
+ const carrier = carrierOf2(ctor);
656
934
  const routes = carrier[ROUTES] ?? [];
657
935
  const returnBuffer = carrier[RETURN_BUFFER];
658
936
  if (returnBuffer) {
@@ -788,90 +1066,6 @@ function defineMiddleware(fn) {
788
1066
  return fn;
789
1067
  }
790
1068
 
791
- // src/errors.ts
792
- var HttpError = class extends Error {
793
- status;
794
- error;
795
- errorDescription;
796
- data;
797
- constructor(status, error, errorDescription, data) {
798
- super(errorDescription);
799
- this.name = "HttpError";
800
- this.status = status;
801
- this.error = error;
802
- this.errorDescription = errorDescription;
803
- if (data !== void 0) {
804
- this.data = data;
805
- }
806
- }
807
- /**
808
- * Serialize to the standard Palbase error response format.
809
- * The `requestId` is injected by the runtime layer from the request context.
810
- * When called without arguments (e.g. JSON.stringify), request_id is omitted.
811
- * When `data` is set, it is appended as a strict-superset field.
812
- */
813
- toJSON(requestId) {
814
- const result = {
815
- error: this.error,
816
- error_description: this.errorDescription,
817
- status: this.status
818
- };
819
- if (requestId) {
820
- result.request_id = requestId;
821
- }
822
- if (this.data !== void 0) {
823
- result.data = this.data;
824
- }
825
- return result;
826
- }
827
- };
828
- var PalError = class extends HttpError {
829
- constructor(status, code, description, data) {
830
- super(status, code, description, data);
831
- this.name = "PalError";
832
- }
833
- };
834
- var NamedHttpError = class extends HttpError {
835
- constructor(status, defaultCode, name, message, code, data) {
836
- super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
837
- this.name = name;
838
- }
839
- };
840
- function defaultMessage(name) {
841
- const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
842
- return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
843
- }
844
- var BadRequest = class extends NamedHttpError {
845
- constructor(data, message) {
846
- super(400, "bad_request", "BadRequest", message, void 0, data);
847
- }
848
- };
849
- var Unauthorized = class extends NamedHttpError {
850
- constructor(message, code, data) {
851
- super(401, "unauthorized", "Unauthorized", message, code, data);
852
- }
853
- };
854
- var Forbidden = class extends NamedHttpError {
855
- constructor(message, code, data) {
856
- super(403, "forbidden", "Forbidden", message, code, data);
857
- }
858
- };
859
- var NotFound = class extends NamedHttpError {
860
- constructor(message, code, data) {
861
- super(404, "not_found", "NotFound", message, code, data);
862
- }
863
- };
864
- var Conflict = class extends NamedHttpError {
865
- constructor(message, code, data) {
866
- super(409, "conflict", "Conflict", message, code, data);
867
- }
868
- };
869
- var TooManyRequests = class extends NamedHttpError {
870
- constructor(data, message) {
871
- super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
872
- }
873
- };
874
-
875
1069
  // src/error-registry.ts
876
1070
  import {
877
1071
  OpenAPIRegistry,
@@ -885,7 +1079,27 @@ var BUILTIN_DATA_SCHEMAS = {
885
1079
  bad_request: z.object({
886
1080
  fields: z.array(z.object({ field: z.string(), message: z.string() }))
887
1081
  }),
888
- too_many_requests: z.object({ retryAfter: z.number().int() })
1082
+ too_many_requests: z.object({ retryAfter: z.number().int() }),
1083
+ // Purchases (@RequireEntitlement / @Spend). Their payloads are what make a
1084
+ // paywall renderable on the client: WHICH entitlement was missing, and for a
1085
+ // spend the real ceiling and reset time rather than a guessed retry delay.
1086
+ entitlement_required: z.object({ entitlement: z.string().nullable() }),
1087
+ quota_exceeded: z.object({
1088
+ key: z.string(),
1089
+ scope: z.string(),
1090
+ window: z.string(),
1091
+ used: z.number().int(),
1092
+ reserved: z.number().int(),
1093
+ max: z.number().int(),
1094
+ remaining: z.number().int(),
1095
+ resetAt: z.string()
1096
+ }),
1097
+ credit_insufficient: z.object({
1098
+ key: z.string(),
1099
+ balance: z.number().int(),
1100
+ reserved: z.number().int(),
1101
+ remaining: z.number().int()
1102
+ })
889
1103
  };
890
1104
  function getErrorRegistry() {
891
1105
  const g = globalThis;
@@ -897,7 +1111,13 @@ function getErrorRegistry() {
897
1111
  ["forbidden", 403, "Forbidden"],
898
1112
  ["not_found", 404, "NotFound"],
899
1113
  ["conflict", 409, "Conflict"],
900
- ["too_many_requests", 429, "TooManyRequests"]
1114
+ ["too_many_requests", 429, "TooManyRequests"],
1115
+ // Thrown by the purchases decorators, so they are pre-seeded like the
1116
+ // other built-ins: a project never declares them, but every gated or
1117
+ // metered route must surface them as TYPED errors on the client.
1118
+ ["entitlement_required", 403, "EntitlementRequired"],
1119
+ ["quota_exceeded", 429, "QuotaExceeded"],
1120
+ ["credit_insufficient", 429, "CreditInsufficient"]
901
1121
  ]) {
902
1122
  const dataSchema = BUILTIN_DATA_SCHEMAS[code];
903
1123
  m.set(code, {
@@ -1309,6 +1529,7 @@ export {
1309
1529
  Patch,
1310
1530
  PolicyBuilder,
1311
1531
  Post,
1532
+ Purchases,
1312
1533
  Put,
1313
1534
  Query,
1314
1535
  QueryParams,
@@ -1317,12 +1538,16 @@ export {
1317
1538
  Realtime,
1318
1539
  Req,
1319
1540
  RequestId,
1541
+ RequireEntitlement,
1320
1542
  Resource,
1321
1543
  STORAGE_CONFIG_KIND,
1544
+ Spend,
1322
1545
  Storage,
1323
1546
  TEST_USERS_CONFIG_KIND,
1324
1547
  TooManyRequests,
1325
1548
  TraceId,
1549
+ TxPlanError,
1550
+ TxRefError,
1326
1551
  Unauthorized,
1327
1552
  Upload,
1328
1553
  UploadedObject,
@@ -1339,6 +1564,7 @@ export {
1339
1564
  boolean,
1340
1565
  bucket,
1341
1566
  buildProvider,
1567
+ dec,
1342
1568
  defineEgress,
1343
1569
  defineError,
1344
1570
  defineFlags,
@@ -1351,21 +1577,26 @@ export {
1351
1577
  defineWebhook,
1352
1578
  defineWorker,
1353
1579
  documents,
1580
+ entitlementFor,
1354
1581
  enumType,
1355
1582
  flag,
1356
1583
  getErrorRegistry,
1357
1584
  getRoutes,
1585
+ inc,
1358
1586
  integer,
1359
1587
  isPalbaseExtension,
1360
1588
  jsonb,
1361
1589
  makeEnvDts,
1590
+ makePurchasesDts,
1362
1591
  makeTypedDB,
1592
+ now,
1363
1593
  numeric,
1364
1594
  parseFileSizeLimit,
1365
1595
  policy,
1366
1596
  raw,
1367
1597
  recordThrows,
1368
1598
  reservedSecretKey,
1599
+ spendFor,
1369
1600
  storage,
1370
1601
  testUser,
1371
1602
  text,