@palbase/backend 10.1.0 → 10.3.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.
- package/dist/{chunk-RGQUB66H.js → chunk-2PCNZBNZ.js} +3 -1
- package/dist/chunk-2PCNZBNZ.js.map +1 -0
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/{endpoint-Cn3ICGTf.d.cts → endpoint-92kVepng.d.cts} +54 -0
- package/dist/{endpoint-Cn3ICGTf.d.ts → endpoint-92kVepng.d.ts} +54 -0
- package/dist/{index-Bt7UHkAM.d.cts → index-BaHs33jr.d.cts} +1 -1
- package/dist/{index-V0ealeY-.d.ts → index-DfryIw9U.d.ts} +1 -1
- package/dist/index.cjs +322 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +261 -33
- package/dist/index.d.ts +261 -33
- package/dist/index.js +317 -96
- package/dist/index.js.map +1 -1
- package/dist/purchases/keys.cjs +19 -0
- package/dist/purchases/keys.cjs.map +1 -0
- package/dist/purchases/keys.d.cts +42 -0
- package/dist/purchases/keys.d.ts +42 -0
- package/dist/purchases/keys.js +1 -0
- package/dist/purchases/keys.js.map +1 -0
- package/dist/test/index.cjs +14 -1
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +14 -2
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +7 -6
- package/docs/config.md +7 -1
- package/docs/getting-started.md +14 -12
- package/docs/llms-full.txt +28 -25
- package/docs/migrations.md +8 -8
- package/package.json +11 -1
- 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-
|
|
16
|
+
} from "./chunk-2PCNZBNZ.js";
|
|
16
17
|
import {
|
|
17
18
|
EXTENSION_DEPENDENCIES,
|
|
18
19
|
PALBASE_EXTENSIONS,
|
|
@@ -33,6 +34,276 @@ import {
|
|
|
33
34
|
uuid
|
|
34
35
|
} from "./chunk-4WOQWFUP.js";
|
|
35
36
|
|
|
37
|
+
// src/errors.ts
|
|
38
|
+
var HttpError = class extends Error {
|
|
39
|
+
status;
|
|
40
|
+
error;
|
|
41
|
+
errorDescription;
|
|
42
|
+
data;
|
|
43
|
+
constructor(status, error, errorDescription, data) {
|
|
44
|
+
super(errorDescription);
|
|
45
|
+
this.name = "HttpError";
|
|
46
|
+
this.status = status;
|
|
47
|
+
this.error = error;
|
|
48
|
+
this.errorDescription = errorDescription;
|
|
49
|
+
if (data !== void 0) {
|
|
50
|
+
this.data = data;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Serialize to the standard Palbase error response format.
|
|
55
|
+
* The `requestId` is injected by the runtime layer from the request context.
|
|
56
|
+
* When called without arguments (e.g. JSON.stringify), request_id is omitted.
|
|
57
|
+
* When `data` is set, it is appended as a strict-superset field.
|
|
58
|
+
*/
|
|
59
|
+
toJSON(requestId) {
|
|
60
|
+
const result = {
|
|
61
|
+
error: this.error,
|
|
62
|
+
error_description: this.errorDescription,
|
|
63
|
+
status: this.status
|
|
64
|
+
};
|
|
65
|
+
if (requestId) {
|
|
66
|
+
result.request_id = requestId;
|
|
67
|
+
}
|
|
68
|
+
if (this.data !== void 0) {
|
|
69
|
+
result.data = this.data;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var PalError = class extends HttpError {
|
|
75
|
+
constructor(status, code, description, data) {
|
|
76
|
+
super(status, code, description, data);
|
|
77
|
+
this.name = "PalError";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var NamedHttpError = class extends HttpError {
|
|
81
|
+
constructor(status, defaultCode, name, message, code, data) {
|
|
82
|
+
super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
|
|
83
|
+
this.name = name;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
function defaultMessage(name) {
|
|
87
|
+
const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
88
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
|
|
89
|
+
}
|
|
90
|
+
var BadRequest = class extends NamedHttpError {
|
|
91
|
+
constructor(data, message) {
|
|
92
|
+
super(400, "bad_request", "BadRequest", message, void 0, data);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var Unauthorized = class extends NamedHttpError {
|
|
96
|
+
constructor(message, code, data) {
|
|
97
|
+
super(401, "unauthorized", "Unauthorized", message, code, data);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var Forbidden = class extends NamedHttpError {
|
|
101
|
+
constructor(message, code, data) {
|
|
102
|
+
super(403, "forbidden", "Forbidden", message, code, data);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var NotFound = class extends NamedHttpError {
|
|
106
|
+
constructor(message, code, data) {
|
|
107
|
+
super(404, "not_found", "NotFound", message, code, data);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var Conflict = class extends NamedHttpError {
|
|
111
|
+
constructor(message, code, data) {
|
|
112
|
+
super(409, "conflict", "Conflict", message, code, data);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var TooManyRequests = class extends NamedHttpError {
|
|
116
|
+
constructor(data, message) {
|
|
117
|
+
super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/purchases/errors.ts
|
|
122
|
+
function asPalstoreError(err) {
|
|
123
|
+
if (!(err instanceof Error)) return null;
|
|
124
|
+
const e = err;
|
|
125
|
+
if (typeof e.code !== "string" || typeof e.status !== "number") return null;
|
|
126
|
+
return { code: e.code, status: e.status, message: e.message };
|
|
127
|
+
}
|
|
128
|
+
function toHttpError(err) {
|
|
129
|
+
if (err instanceof HttpError) return err;
|
|
130
|
+
const p = asPalstoreError(err);
|
|
131
|
+
if (!p) return err;
|
|
132
|
+
switch (p.code) {
|
|
133
|
+
case "entitlement_required": {
|
|
134
|
+
const entitlement = err.entitlement ?? null;
|
|
135
|
+
return new PalError(403, "entitlement_required", p.message, { entitlement });
|
|
136
|
+
}
|
|
137
|
+
case "quota_exceeded":
|
|
138
|
+
case "credit_insufficient": {
|
|
139
|
+
const state = err.state;
|
|
140
|
+
return new PalError(429, p.code, p.message, state);
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
return err;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/purchases/registry.ts
|
|
148
|
+
var ENTITLEMENTS = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.entitlements");
|
|
149
|
+
var SPENDS = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.spends");
|
|
150
|
+
function carrierOf(target) {
|
|
151
|
+
return typeof target === "function" ? target : target.constructor ?? target;
|
|
152
|
+
}
|
|
153
|
+
function own(carrier, slot) {
|
|
154
|
+
if (!Object.prototype.hasOwnProperty.call(carrier, slot)) {
|
|
155
|
+
carrier[slot] = {};
|
|
156
|
+
}
|
|
157
|
+
return carrier[slot];
|
|
158
|
+
}
|
|
159
|
+
function recordEntitlement(target, fnName, key) {
|
|
160
|
+
own(carrierOf(target), ENTITLEMENTS)[fnName] = key;
|
|
161
|
+
}
|
|
162
|
+
function recordSpend(target, fnName, meta) {
|
|
163
|
+
own(carrierOf(target), SPENDS)[fnName] = meta;
|
|
164
|
+
}
|
|
165
|
+
function entitlementFor(ctor, fnName) {
|
|
166
|
+
return carrierOf(ctor)[ENTITLEMENTS]?.[fnName];
|
|
167
|
+
}
|
|
168
|
+
function spendFor(ctor, fnName) {
|
|
169
|
+
return carrierOf(ctor)[SPENDS]?.[fnName];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/purchases/subject.ts
|
|
173
|
+
var subjectByRequest = /* @__PURE__ */ new WeakMap();
|
|
174
|
+
function storeEnv() {
|
|
175
|
+
return process.env.PALSTORE_STORE_ENV === "sandbox" ? "sandbox" : "production";
|
|
176
|
+
}
|
|
177
|
+
async function currentSubjectId() {
|
|
178
|
+
const store = __requestALS.getStore();
|
|
179
|
+
if (!store) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
"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."
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const memoized = subjectByRequest.get(store);
|
|
185
|
+
if (memoized) return memoized;
|
|
186
|
+
const userId = store.userId;
|
|
187
|
+
if (!userId) {
|
|
188
|
+
throw new Unauthorized(
|
|
189
|
+
"This endpoint requires an authenticated user: purchases entitlements and quota are per-user, and the subject is resolved from the request's identity."
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const resolving = Purchases.resolveSubject({ userRef: userId, storeEnv: storeEnv() }).then(
|
|
193
|
+
(r) => r.subjectId
|
|
194
|
+
);
|
|
195
|
+
subjectByRequest.set(store, resolving);
|
|
196
|
+
resolving.catch(() => subjectByRequest.delete(store));
|
|
197
|
+
return resolving;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/purchases/decorators.ts
|
|
201
|
+
var WRAPPED = /* @__PURE__ */ Symbol.for("palbase.backend.purchases.wrapped");
|
|
202
|
+
function idempotencyKeyFor(key) {
|
|
203
|
+
const store = __requestALS.getStore();
|
|
204
|
+
const base = store?.idempotencyKey || store?.requestId;
|
|
205
|
+
if (!base) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"@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."
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return `${base}:${key}`;
|
|
211
|
+
}
|
|
212
|
+
function ensureWrapper(target, fnName, descriptor) {
|
|
213
|
+
const original = descriptor.value;
|
|
214
|
+
if (typeof original !== "function") {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`@RequireEntitlement/@Spend can only decorate a method; "${fnName}" is not one.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (original[WRAPPED]) return;
|
|
220
|
+
const wrapper = async function(...args) {
|
|
221
|
+
const ctor = this.constructor;
|
|
222
|
+
const entitlement = entitlementFor(ctor, fnName);
|
|
223
|
+
const spend = spendFor(ctor, fnName);
|
|
224
|
+
let subjectId;
|
|
225
|
+
try {
|
|
226
|
+
subjectId = await currentSubjectId();
|
|
227
|
+
} catch (err) {
|
|
228
|
+
throw toHttpError(err);
|
|
229
|
+
}
|
|
230
|
+
if (entitlement !== void 0) {
|
|
231
|
+
try {
|
|
232
|
+
await Purchases.require(subjectId, entitlement);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
throw toHttpError(err);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (spend === void 0) {
|
|
238
|
+
return original.apply(this, args);
|
|
239
|
+
}
|
|
240
|
+
let handlerFailure;
|
|
241
|
+
try {
|
|
242
|
+
return await Purchases.withSpend(
|
|
243
|
+
subjectId,
|
|
244
|
+
spend.key,
|
|
245
|
+
{ count: spend.count, idempotencyKey: idempotencyKeyFor(spend.key) },
|
|
246
|
+
async () => {
|
|
247
|
+
try {
|
|
248
|
+
return await original.apply(this, args);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
handlerFailure = { err };
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
if (handlerFailure !== void 0 && handlerFailure.err === err) throw err;
|
|
257
|
+
throw toHttpError(err);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
wrapper[WRAPPED] = true;
|
|
261
|
+
descriptor.value = wrapper;
|
|
262
|
+
}
|
|
263
|
+
function RequireEntitlement(key) {
|
|
264
|
+
return function(target, propertyKey, descriptor) {
|
|
265
|
+
const fnName = String(propertyKey);
|
|
266
|
+
recordEntitlement(target, fnName, key);
|
|
267
|
+
ensureWrapper(target, fnName, descriptor);
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function Spend(key) {
|
|
271
|
+
return function(target, propertyKey, descriptor) {
|
|
272
|
+
const fnName = String(propertyKey);
|
|
273
|
+
recordSpend(target, fnName, { key, count: 1 });
|
|
274
|
+
ensureWrapper(target, fnName, descriptor);
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/purchases/keys-gen.ts
|
|
279
|
+
function liveKeys(entries) {
|
|
280
|
+
return Object.entries(entries ?? {}).filter(([, def]) => def?.removed !== true).map(([key]) => key).sort();
|
|
281
|
+
}
|
|
282
|
+
function memberName(key) {
|
|
283
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
284
|
+
}
|
|
285
|
+
function members(keys) {
|
|
286
|
+
if (keys.length === 0) return "";
|
|
287
|
+
return `
|
|
288
|
+
${keys.map((k) => ` ${memberName(k)}: true;`).join("\n")}
|
|
289
|
+
`;
|
|
290
|
+
}
|
|
291
|
+
function makePurchasesDts(manifest) {
|
|
292
|
+
const entitlements = liveKeys(manifest.entitlements);
|
|
293
|
+
const limits = liveKeys({ ...manifest.limits, ...manifest.credits });
|
|
294
|
+
return `// palbase-purchases.d.ts \u2014 GENERATED by @palbase/backend. Do not edit.
|
|
295
|
+
// Source: the project's palstore catalog revision.
|
|
296
|
+
|
|
297
|
+
declare module "@palbase/backend/purchases" {
|
|
298
|
+
interface Entitlements {${members(entitlements)}}
|
|
299
|
+
|
|
300
|
+
interface Limits {${members(limits)}}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export {};
|
|
304
|
+
`;
|
|
305
|
+
}
|
|
306
|
+
|
|
36
307
|
// src/db/env-gen.ts
|
|
37
308
|
function baseTsType(def) {
|
|
38
309
|
switch (def.type) {
|
|
@@ -142,7 +413,7 @@ function makeEnvDts(schema) {
|
|
|
142
413
|
${blocks.join("\n")}
|
|
143
414
|
` : "";
|
|
144
415
|
return `// AUTO-GENERATED by @palbase/backend \u2014 DO NOT EDIT.
|
|
145
|
-
// Regenerated from db/schema.ts on every \`palbase
|
|
416
|
+
// Regenerated from db/schema.ts on every \`palbase db types\` / deploy.
|
|
146
417
|
// Augments the @palbase/backend/env \`Tables\` interface so \`Database.tables.*\`
|
|
147
418
|
// is typed with no import and no generic.
|
|
148
419
|
|
|
@@ -488,6 +759,8 @@ function valueMatchesType(type, value) {
|
|
|
488
759
|
return typeof value === "number" && Number.isFinite(value);
|
|
489
760
|
case "string":
|
|
490
761
|
return typeof value === "string";
|
|
762
|
+
case "json":
|
|
763
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
491
764
|
}
|
|
492
765
|
}
|
|
493
766
|
function flag(opts) {
|
|
@@ -495,8 +768,8 @@ function flag(opts) {
|
|
|
495
768
|
throw new Error("flag() expects { type, default, variants?, description? }");
|
|
496
769
|
}
|
|
497
770
|
const { type } = opts;
|
|
498
|
-
if (type !== "boolean" && type !== "number" && type !== "string") {
|
|
499
|
-
throw new Error(`flag type must be "boolean", "number", or "
|
|
771
|
+
if (type !== "boolean" && type !== "number" && type !== "string" && type !== "json") {
|
|
772
|
+
throw new Error(`flag type must be "boolean", "number", "string", or "json", got ${JSON.stringify(type)}`);
|
|
500
773
|
}
|
|
501
774
|
if (!valueMatchesType(type, opts.default)) {
|
|
502
775
|
throw new Error(
|
|
@@ -505,7 +778,7 @@ function flag(opts) {
|
|
|
505
778
|
}
|
|
506
779
|
let variants = null;
|
|
507
780
|
if (opts.variants !== void 0) {
|
|
508
|
-
if (type !== "string") {
|
|
781
|
+
if (opts.type !== "string") {
|
|
509
782
|
throw new Error(`flag variants are only valid for type "string" (got type "${type}")`);
|
|
510
783
|
}
|
|
511
784
|
if (!Array.isArray(opts.variants)) {
|
|
@@ -590,7 +863,7 @@ var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
|
|
|
590
863
|
var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
|
|
591
864
|
var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
|
|
592
865
|
var THROWS_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.throwsBuffer");
|
|
593
|
-
function
|
|
866
|
+
function carrierOf2(target) {
|
|
594
867
|
const ctor = typeof target === "function" ? target : target.constructor ?? target;
|
|
595
868
|
return ctor;
|
|
596
869
|
}
|
|
@@ -607,7 +880,7 @@ function ownParamBuffer(carrier) {
|
|
|
607
880
|
return carrier[PARAM_BUFFER];
|
|
608
881
|
}
|
|
609
882
|
function recordRoute(target, fnName, method, subpath, options) {
|
|
610
|
-
const carrier =
|
|
883
|
+
const carrier = carrierOf2(target);
|
|
611
884
|
const routes = ownRoutes(carrier);
|
|
612
885
|
const buffer = ownParamBuffer(carrier);
|
|
613
886
|
const params = (buffer[fnName] ?? []).slice().sort((a, b) => a.index - b.index);
|
|
@@ -623,7 +896,7 @@ function recordRoute(target, fnName, method, subpath, options) {
|
|
|
623
896
|
routes.push(route);
|
|
624
897
|
}
|
|
625
898
|
function recordParam(target, fnName, meta) {
|
|
626
|
-
const carrier =
|
|
899
|
+
const carrier = carrierOf2(target);
|
|
627
900
|
const buffer = ownParamBuffer(carrier);
|
|
628
901
|
(buffer[fnName] ??= []).push(meta);
|
|
629
902
|
const routes = carrier[ROUTES];
|
|
@@ -636,7 +909,7 @@ function recordParam(target, fnName, meta) {
|
|
|
636
909
|
}
|
|
637
910
|
}
|
|
638
911
|
function recordThrows(target, fnName, throws) {
|
|
639
|
-
const carrier =
|
|
912
|
+
const carrier = carrierOf2(target);
|
|
640
913
|
const routes = carrier[ROUTES];
|
|
641
914
|
const route = routes?.find((r) => r.fnName === fnName);
|
|
642
915
|
if (route) {
|
|
@@ -650,7 +923,7 @@ function recordThrows(target, fnName, throws) {
|
|
|
650
923
|
if (throwsBuffer) throwsBuffer[fnName] = throws;
|
|
651
924
|
}
|
|
652
925
|
function getRoutes(ctor) {
|
|
653
|
-
const carrier =
|
|
926
|
+
const carrier = carrierOf2(ctor);
|
|
654
927
|
const routes = carrier[ROUTES] ?? [];
|
|
655
928
|
const returnBuffer = carrier[RETURN_BUFFER];
|
|
656
929
|
if (returnBuffer) {
|
|
@@ -786,90 +1059,6 @@ function defineMiddleware(fn) {
|
|
|
786
1059
|
return fn;
|
|
787
1060
|
}
|
|
788
1061
|
|
|
789
|
-
// src/errors.ts
|
|
790
|
-
var HttpError = class extends Error {
|
|
791
|
-
status;
|
|
792
|
-
error;
|
|
793
|
-
errorDescription;
|
|
794
|
-
data;
|
|
795
|
-
constructor(status, error, errorDescription, data) {
|
|
796
|
-
super(errorDescription);
|
|
797
|
-
this.name = "HttpError";
|
|
798
|
-
this.status = status;
|
|
799
|
-
this.error = error;
|
|
800
|
-
this.errorDescription = errorDescription;
|
|
801
|
-
if (data !== void 0) {
|
|
802
|
-
this.data = data;
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Serialize to the standard Palbase error response format.
|
|
807
|
-
* The `requestId` is injected by the runtime layer from the request context.
|
|
808
|
-
* When called without arguments (e.g. JSON.stringify), request_id is omitted.
|
|
809
|
-
* When `data` is set, it is appended as a strict-superset field.
|
|
810
|
-
*/
|
|
811
|
-
toJSON(requestId) {
|
|
812
|
-
const result = {
|
|
813
|
-
error: this.error,
|
|
814
|
-
error_description: this.errorDescription,
|
|
815
|
-
status: this.status
|
|
816
|
-
};
|
|
817
|
-
if (requestId) {
|
|
818
|
-
result.request_id = requestId;
|
|
819
|
-
}
|
|
820
|
-
if (this.data !== void 0) {
|
|
821
|
-
result.data = this.data;
|
|
822
|
-
}
|
|
823
|
-
return result;
|
|
824
|
-
}
|
|
825
|
-
};
|
|
826
|
-
var PalError = class extends HttpError {
|
|
827
|
-
constructor(status, code, description, data) {
|
|
828
|
-
super(status, code, description, data);
|
|
829
|
-
this.name = "PalError";
|
|
830
|
-
}
|
|
831
|
-
};
|
|
832
|
-
var NamedHttpError = class extends HttpError {
|
|
833
|
-
constructor(status, defaultCode, name, message, code, data) {
|
|
834
|
-
super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
|
|
835
|
-
this.name = name;
|
|
836
|
-
}
|
|
837
|
-
};
|
|
838
|
-
function defaultMessage(name) {
|
|
839
|
-
const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
840
|
-
return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
|
|
841
|
-
}
|
|
842
|
-
var BadRequest = class extends NamedHttpError {
|
|
843
|
-
constructor(data, message) {
|
|
844
|
-
super(400, "bad_request", "BadRequest", message, void 0, data);
|
|
845
|
-
}
|
|
846
|
-
};
|
|
847
|
-
var Unauthorized = class extends NamedHttpError {
|
|
848
|
-
constructor(message, code, data) {
|
|
849
|
-
super(401, "unauthorized", "Unauthorized", message, code, data);
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
var Forbidden = class extends NamedHttpError {
|
|
853
|
-
constructor(message, code, data) {
|
|
854
|
-
super(403, "forbidden", "Forbidden", message, code, data);
|
|
855
|
-
}
|
|
856
|
-
};
|
|
857
|
-
var NotFound = class extends NamedHttpError {
|
|
858
|
-
constructor(message, code, data) {
|
|
859
|
-
super(404, "not_found", "NotFound", message, code, data);
|
|
860
|
-
}
|
|
861
|
-
};
|
|
862
|
-
var Conflict = class extends NamedHttpError {
|
|
863
|
-
constructor(message, code, data) {
|
|
864
|
-
super(409, "conflict", "Conflict", message, code, data);
|
|
865
|
-
}
|
|
866
|
-
};
|
|
867
|
-
var TooManyRequests = class extends NamedHttpError {
|
|
868
|
-
constructor(data, message) {
|
|
869
|
-
super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
|
|
870
|
-
}
|
|
871
|
-
};
|
|
872
|
-
|
|
873
1062
|
// src/error-registry.ts
|
|
874
1063
|
import {
|
|
875
1064
|
OpenAPIRegistry,
|
|
@@ -883,7 +1072,27 @@ var BUILTIN_DATA_SCHEMAS = {
|
|
|
883
1072
|
bad_request: z.object({
|
|
884
1073
|
fields: z.array(z.object({ field: z.string(), message: z.string() }))
|
|
885
1074
|
}),
|
|
886
|
-
too_many_requests: z.object({ retryAfter: z.number().int() })
|
|
1075
|
+
too_many_requests: z.object({ retryAfter: z.number().int() }),
|
|
1076
|
+
// Purchases (@RequireEntitlement / @Spend). Their payloads are what make a
|
|
1077
|
+
// paywall renderable on the client: WHICH entitlement was missing, and for a
|
|
1078
|
+
// spend the real ceiling and reset time rather than a guessed retry delay.
|
|
1079
|
+
entitlement_required: z.object({ entitlement: z.string().nullable() }),
|
|
1080
|
+
quota_exceeded: z.object({
|
|
1081
|
+
key: z.string(),
|
|
1082
|
+
scope: z.string(),
|
|
1083
|
+
window: z.string(),
|
|
1084
|
+
used: z.number().int(),
|
|
1085
|
+
reserved: z.number().int(),
|
|
1086
|
+
max: z.number().int(),
|
|
1087
|
+
remaining: z.number().int(),
|
|
1088
|
+
resetAt: z.string()
|
|
1089
|
+
}),
|
|
1090
|
+
credit_insufficient: z.object({
|
|
1091
|
+
key: z.string(),
|
|
1092
|
+
balance: z.number().int(),
|
|
1093
|
+
reserved: z.number().int(),
|
|
1094
|
+
remaining: z.number().int()
|
|
1095
|
+
})
|
|
887
1096
|
};
|
|
888
1097
|
function getErrorRegistry() {
|
|
889
1098
|
const g = globalThis;
|
|
@@ -895,7 +1104,13 @@ function getErrorRegistry() {
|
|
|
895
1104
|
["forbidden", 403, "Forbidden"],
|
|
896
1105
|
["not_found", 404, "NotFound"],
|
|
897
1106
|
["conflict", 409, "Conflict"],
|
|
898
|
-
["too_many_requests", 429, "TooManyRequests"]
|
|
1107
|
+
["too_many_requests", 429, "TooManyRequests"],
|
|
1108
|
+
// Thrown by the purchases decorators, so they are pre-seeded like the
|
|
1109
|
+
// other built-ins: a project never declares them, but every gated or
|
|
1110
|
+
// metered route must surface them as TYPED errors on the client.
|
|
1111
|
+
["entitlement_required", 403, "EntitlementRequired"],
|
|
1112
|
+
["quota_exceeded", 429, "QuotaExceeded"],
|
|
1113
|
+
["credit_insufficient", 429, "CreditInsufficient"]
|
|
899
1114
|
]) {
|
|
900
1115
|
const dataSchema = BUILTIN_DATA_SCHEMAS[code];
|
|
901
1116
|
m.set(code, {
|
|
@@ -1307,6 +1522,7 @@ export {
|
|
|
1307
1522
|
Patch,
|
|
1308
1523
|
PolicyBuilder,
|
|
1309
1524
|
Post,
|
|
1525
|
+
Purchases,
|
|
1310
1526
|
Put,
|
|
1311
1527
|
Query,
|
|
1312
1528
|
QueryParams,
|
|
@@ -1315,8 +1531,10 @@ export {
|
|
|
1315
1531
|
Realtime,
|
|
1316
1532
|
Req,
|
|
1317
1533
|
RequestId,
|
|
1534
|
+
RequireEntitlement,
|
|
1318
1535
|
Resource,
|
|
1319
1536
|
STORAGE_CONFIG_KIND,
|
|
1537
|
+
Spend,
|
|
1320
1538
|
Storage,
|
|
1321
1539
|
TEST_USERS_CONFIG_KIND,
|
|
1322
1540
|
TooManyRequests,
|
|
@@ -1349,6 +1567,7 @@ export {
|
|
|
1349
1567
|
defineWebhook,
|
|
1350
1568
|
defineWorker,
|
|
1351
1569
|
documents,
|
|
1570
|
+
entitlementFor,
|
|
1352
1571
|
enumType,
|
|
1353
1572
|
flag,
|
|
1354
1573
|
getErrorRegistry,
|
|
@@ -1357,6 +1576,7 @@ export {
|
|
|
1357
1576
|
isPalbaseExtension,
|
|
1358
1577
|
jsonb,
|
|
1359
1578
|
makeEnvDts,
|
|
1579
|
+
makePurchasesDts,
|
|
1360
1580
|
makeTypedDB,
|
|
1361
1581
|
numeric,
|
|
1362
1582
|
parseFileSizeLimit,
|
|
@@ -1364,6 +1584,7 @@ export {
|
|
|
1364
1584
|
raw,
|
|
1365
1585
|
recordThrows,
|
|
1366
1586
|
reservedSecretKey,
|
|
1587
|
+
spendFor,
|
|
1367
1588
|
storage,
|
|
1368
1589
|
testUser,
|
|
1369
1590
|
text,
|