@palbase/backend 4.0.0 → 5.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.
- package/dist/{chunk-EG7TTYHY.js → chunk-2N4YNN6F.js} +1 -1
- package/dist/{chunk-EG7TTYHY.js.map → chunk-2N4YNN6F.js.map} +1 -1
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +1 -1
- package/dist/{endpoint-2d_DpASt.d.cts → endpoint-BEHjfvFH.d.cts} +7 -1
- package/dist/{endpoint-2d_DpASt.d.ts → endpoint-BEHjfvFH.d.ts} +7 -1
- package/dist/{index-DZW9CjiY.d.ts → index-BTVdhfsb.d.ts} +9 -3
- package/dist/{index-DzRFS3Tl.d.cts → index-mr3Co63T.d.cts} +9 -3
- package/dist/index.cjs +304 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +502 -17
- package/dist/index.d.ts +502 -17
- package/dist/index.js +292 -22
- package/dist/index.js.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/docs/README.md +201 -3
- package/docs/config.md +100 -0
- package/docs/endpoints.md +20 -8
- package/docs/getting-started.md +45 -11
- package/docs/llms-full.txt +349 -103
- package/docs/llms.txt +1 -1
- package/docs/migrations.md +75 -73
- package/docs/routing.md +6 -6
- package/docs/schema.md +3 -2
- package/docs/services.md +2 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
text,
|
|
28
28
|
timestamp,
|
|
29
29
|
uuid
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-2N4YNN6F.js";
|
|
31
31
|
|
|
32
32
|
// src/db/env-gen.ts
|
|
33
33
|
function baseTsType(def) {
|
|
@@ -98,6 +98,277 @@ export {};
|
|
|
98
98
|
`;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// src/config/storage.ts
|
|
102
|
+
var STORAGE_CONFIG_KIND = "storage";
|
|
103
|
+
var UNIT_BYTES = {
|
|
104
|
+
b: 1,
|
|
105
|
+
kb: 1024,
|
|
106
|
+
mb: 1024 ** 2,
|
|
107
|
+
gb: 1024 ** 3,
|
|
108
|
+
tb: 1024 ** 4
|
|
109
|
+
};
|
|
110
|
+
function parseFileSizeLimit(input) {
|
|
111
|
+
if (typeof input === "number") {
|
|
112
|
+
if (!Number.isFinite(input) || input < 0 || !Number.isInteger(input)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`bucket fileSizeLimit must be a non-negative integer number of bytes, got ${input}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return input;
|
|
118
|
+
}
|
|
119
|
+
const trimmed = input.trim();
|
|
120
|
+
const match = /^(\d+(?:\.\d+)?)\s*([a-zA-Z]+)?$/.exec(trimmed);
|
|
121
|
+
if (!match) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`bucket fileSizeLimit string must be "<number><unit>" like "5MB" or "1GB", got "${input}"`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const value = Number.parseFloat(match[1]);
|
|
127
|
+
const unit = (match[2] ?? "b").toLowerCase();
|
|
128
|
+
const multiplier = UNIT_BYTES[unit];
|
|
129
|
+
if (multiplier === void 0) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`bucket fileSizeLimit has an unknown unit "${match[2]}" \u2014 use B, KB, MB, GB, or TB (e.g. "5MB")`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const bytes = value * multiplier;
|
|
135
|
+
if (!Number.isFinite(bytes) || bytes < 0) {
|
|
136
|
+
throw new Error(`bucket fileSizeLimit resolved to an invalid byte count: ${bytes}`);
|
|
137
|
+
}
|
|
138
|
+
return Math.round(bytes);
|
|
139
|
+
}
|
|
140
|
+
var MIME_RE = /^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*\/(?:\*|[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]*)$/;
|
|
141
|
+
function bucket(opts = {}) {
|
|
142
|
+
const fileSizeLimit = opts.fileSizeLimit === void 0 ? null : parseFileSizeLimit(opts.fileSizeLimit);
|
|
143
|
+
let allowedMimeTypes = null;
|
|
144
|
+
if (opts.allowedMimeTypes !== void 0) {
|
|
145
|
+
if (!Array.isArray(opts.allowedMimeTypes)) {
|
|
146
|
+
throw new Error("bucket allowedMimeTypes must be an array of MIME-type strings");
|
|
147
|
+
}
|
|
148
|
+
for (const mime of opts.allowedMimeTypes) {
|
|
149
|
+
if (typeof mime !== "string" || !MIME_RE.test(mime.trim())) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`bucket allowedMimeTypes entry "${mime}" is not a valid MIME type (expected "type/subtype", e.g. "image/png")`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
allowedMimeTypes = [...new Set(opts.allowedMimeTypes.map((m) => m.trim()))];
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
public: opts.public ?? false,
|
|
159
|
+
fileSizeLimit,
|
|
160
|
+
allowedMimeTypes
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function defineStorage(input) {
|
|
164
|
+
if (input === null || typeof input !== "object" || typeof input.buckets !== "object") {
|
|
165
|
+
throw new Error("defineStorage expects { buckets: { <name>: bucket({...}) } }");
|
|
166
|
+
}
|
|
167
|
+
const buckets = {};
|
|
168
|
+
for (const name of Object.keys(input.buckets)) {
|
|
169
|
+
if (name.length === 0) {
|
|
170
|
+
throw new Error("bucket name must be a non-empty string");
|
|
171
|
+
}
|
|
172
|
+
const def = input.buckets[name];
|
|
173
|
+
if (def === void 0) continue;
|
|
174
|
+
buckets[name] = def;
|
|
175
|
+
}
|
|
176
|
+
return { __config: STORAGE_CONFIG_KIND, buckets };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/config/notifications.ts
|
|
180
|
+
var NOTIFICATIONS_CONFIG_KIND = "notifications";
|
|
181
|
+
var RESERVED_SECRET_PREFIX = "PB_NOTIFICATIONS";
|
|
182
|
+
var PROVIDER_CATALOG = {
|
|
183
|
+
apns: {
|
|
184
|
+
channel: "push",
|
|
185
|
+
required: ["teamId", "keyId", "bundleId"],
|
|
186
|
+
optional: ["isProduction"],
|
|
187
|
+
secrets: ["p8"]
|
|
188
|
+
},
|
|
189
|
+
fcm: {
|
|
190
|
+
channel: "push",
|
|
191
|
+
required: [],
|
|
192
|
+
optional: [],
|
|
193
|
+
secrets: ["serviceAccount"]
|
|
194
|
+
},
|
|
195
|
+
sendgrid: {
|
|
196
|
+
channel: "email",
|
|
197
|
+
required: ["fromDomain"],
|
|
198
|
+
optional: [],
|
|
199
|
+
secrets: ["apiKey"]
|
|
200
|
+
},
|
|
201
|
+
ses: {
|
|
202
|
+
channel: "email",
|
|
203
|
+
required: ["region", "accessKeyId", "fromDomain"],
|
|
204
|
+
optional: [],
|
|
205
|
+
secrets: ["secretAccessKey"]
|
|
206
|
+
},
|
|
207
|
+
smtp: {
|
|
208
|
+
channel: "email",
|
|
209
|
+
required: ["host", "port", "fromEmail"],
|
|
210
|
+
optional: ["username", "useStarttls"],
|
|
211
|
+
secrets: ["password"]
|
|
212
|
+
},
|
|
213
|
+
acs: {
|
|
214
|
+
channel: "email",
|
|
215
|
+
required: ["fromEmail"],
|
|
216
|
+
optional: ["fromName"],
|
|
217
|
+
secrets: ["connectionString"]
|
|
218
|
+
},
|
|
219
|
+
twilio: {
|
|
220
|
+
channel: "sms",
|
|
221
|
+
// account_sid required; one of from_number / messaging_service_sid required
|
|
222
|
+
// (enforced by the refine in buildProvider, not by the flat `required` list).
|
|
223
|
+
required: ["accountSid"],
|
|
224
|
+
optional: ["fromNumber", "messagingServiceSid"],
|
|
225
|
+
secrets: ["authToken"]
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
function buildProvider(name, opts) {
|
|
229
|
+
const entry = PROVIDER_CATALOG[name];
|
|
230
|
+
const src = { ...opts };
|
|
231
|
+
const enabled = src.enabled === void 0 ? false : Boolean(src.enabled);
|
|
232
|
+
if (!enabled) {
|
|
233
|
+
return { enabled: false };
|
|
234
|
+
}
|
|
235
|
+
const def = { enabled: true };
|
|
236
|
+
for (const field of entry.required) {
|
|
237
|
+
const value = src[field];
|
|
238
|
+
if (value === void 0 || value === null || value === "") {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`notifications: provider "${name}" is enabled but missing required field "${field}"`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
def[field] = value;
|
|
244
|
+
}
|
|
245
|
+
for (const field of entry.optional) {
|
|
246
|
+
if (src[field] !== void 0) {
|
|
247
|
+
def[field] = src[field];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (name === "twilio") {
|
|
251
|
+
const hasFrom = Boolean(def.fromNumber);
|
|
252
|
+
const hasMsg = Boolean(def.messagingServiceSid);
|
|
253
|
+
if (!hasFrom && !hasMsg) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
'notifications: provider "twilio" requires one of "fromNumber" or "messagingServiceSid"'
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return def;
|
|
260
|
+
}
|
|
261
|
+
function defineNotifications(input) {
|
|
262
|
+
if (input === null || typeof input !== "object") {
|
|
263
|
+
throw new Error("defineNotifications expects { push?, email?, sms? }");
|
|
264
|
+
}
|
|
265
|
+
const config = {
|
|
266
|
+
__config: NOTIFICATIONS_CONFIG_KIND,
|
|
267
|
+
push: {},
|
|
268
|
+
email: {},
|
|
269
|
+
sms: {}
|
|
270
|
+
};
|
|
271
|
+
const push = input.push ?? {};
|
|
272
|
+
if (push.apns !== void 0) config.push.apns = buildProvider("apns", push.apns);
|
|
273
|
+
if (push.fcm !== void 0) config.push.fcm = buildProvider("fcm", push.fcm);
|
|
274
|
+
const email = input.email ?? {};
|
|
275
|
+
if (email.sendgrid !== void 0) config.email.sendgrid = buildProvider("sendgrid", email.sendgrid);
|
|
276
|
+
if (email.ses !== void 0) config.email.ses = buildProvider("ses", email.ses);
|
|
277
|
+
if (email.smtp !== void 0) config.email.smtp = buildProvider("smtp", email.smtp);
|
|
278
|
+
if (email.acs !== void 0) config.email.acs = buildProvider("acs", email.acs);
|
|
279
|
+
const sms = input.sms ?? {};
|
|
280
|
+
if (sms.twilio !== void 0) config.sms.twilio = buildProvider("twilio", sms.twilio);
|
|
281
|
+
return config;
|
|
282
|
+
}
|
|
283
|
+
function reservedSecretKey(provider, secretField) {
|
|
284
|
+
return `${RESERVED_SECRET_PREFIX}_${camelToUpperSnake(provider)}_${camelToUpperSnake(secretField)}`;
|
|
285
|
+
}
|
|
286
|
+
function camelToUpperSnake(s) {
|
|
287
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/config/flags.ts
|
|
291
|
+
var FLAGS_CONFIG_KIND = "flags";
|
|
292
|
+
var FLAG_KEY_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
293
|
+
function valueMatchesType(type, value) {
|
|
294
|
+
switch (type) {
|
|
295
|
+
case "boolean":
|
|
296
|
+
return typeof value === "boolean";
|
|
297
|
+
case "number":
|
|
298
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
299
|
+
case "string":
|
|
300
|
+
return typeof value === "string";
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function flag(opts) {
|
|
304
|
+
if (opts === null || typeof opts !== "object") {
|
|
305
|
+
throw new Error("flag() expects { type, default, variants?, description? }");
|
|
306
|
+
}
|
|
307
|
+
const { type } = opts;
|
|
308
|
+
if (type !== "boolean" && type !== "number" && type !== "string") {
|
|
309
|
+
throw new Error(`flag type must be "boolean", "number", or "string", got ${JSON.stringify(type)}`);
|
|
310
|
+
}
|
|
311
|
+
if (!valueMatchesType(type, opts.default)) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`flag default ${JSON.stringify(opts.default)} does not match type "${type}"`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
let variants = null;
|
|
317
|
+
if (opts.variants !== void 0) {
|
|
318
|
+
if (type !== "string") {
|
|
319
|
+
throw new Error(`flag variants are only valid for type "string" (got type "${type}")`);
|
|
320
|
+
}
|
|
321
|
+
if (!Array.isArray(opts.variants)) {
|
|
322
|
+
throw new Error("flag variants must be an array of strings");
|
|
323
|
+
}
|
|
324
|
+
for (const v of opts.variants) {
|
|
325
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
326
|
+
throw new Error(`flag variant ${JSON.stringify(v)} must be a non-empty string`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
variants = [...new Set(opts.variants)];
|
|
330
|
+
if (variants.length === 0) {
|
|
331
|
+
throw new Error("flag variants must be a non-empty list when supplied");
|
|
332
|
+
}
|
|
333
|
+
if (!variants.includes(opts.default)) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`flag default ${JSON.stringify(opts.default)} is not one of its variants [${variants.map((v) => JSON.stringify(v)).join(", ")}]`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
let description = null;
|
|
340
|
+
if (opts.description !== void 0) {
|
|
341
|
+
if (typeof opts.description !== "string") {
|
|
342
|
+
throw new Error("flag description must be a string");
|
|
343
|
+
}
|
|
344
|
+
const trimmed = opts.description.trim();
|
|
345
|
+
description = trimmed.length > 0 ? trimmed : null;
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
type,
|
|
349
|
+
default: opts.default,
|
|
350
|
+
variants,
|
|
351
|
+
description
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function defineFlags(input) {
|
|
355
|
+
if (input === null || typeof input !== "object" || typeof input.flags !== "object") {
|
|
356
|
+
throw new Error("defineFlags expects { flags: { <key>: flag({...}) } }");
|
|
357
|
+
}
|
|
358
|
+
const flags = {};
|
|
359
|
+
for (const key of Object.keys(input.flags)) {
|
|
360
|
+
if (!FLAG_KEY_RE.test(key)) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
`flag key ${JSON.stringify(key)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const def = input.flags[key];
|
|
366
|
+
if (def === void 0) continue;
|
|
367
|
+
flags[key] = def;
|
|
368
|
+
}
|
|
369
|
+
return { __config: FLAGS_CONFIG_KIND, flags };
|
|
370
|
+
}
|
|
371
|
+
|
|
101
372
|
// src/decorators/controller.ts
|
|
102
373
|
var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
|
|
103
374
|
function Controller(basePath, options = {}) {
|
|
@@ -127,6 +398,7 @@ function Controller(basePath, options = {}) {
|
|
|
127
398
|
// src/decorators/registry.ts
|
|
128
399
|
var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
|
|
129
400
|
var PARAM_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.paramBuffer");
|
|
401
|
+
var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
|
|
130
402
|
function carrierOf(target) {
|
|
131
403
|
const ctor = typeof target === "function" ? target : target.constructor ?? target;
|
|
132
404
|
return ctor;
|
|
@@ -148,7 +420,12 @@ function recordRoute(target, fnName, method, subpath, options) {
|
|
|
148
420
|
const routes = ownRoutes(carrier);
|
|
149
421
|
const buffer = ownParamBuffer(carrier);
|
|
150
422
|
const params = (buffer[fnName] ?? []).slice().sort((a, b) => a.index - b.index);
|
|
151
|
-
|
|
423
|
+
const route = { method, subpath, fnName, options, params };
|
|
424
|
+
const returnBuffer = carrier[RETURN_BUFFER];
|
|
425
|
+
if (returnBuffer && returnBuffer[fnName] !== void 0) {
|
|
426
|
+
route.returnSchema = returnBuffer[fnName];
|
|
427
|
+
}
|
|
428
|
+
routes.push(route);
|
|
152
429
|
}
|
|
153
430
|
function recordParam(target, fnName, meta) {
|
|
154
431
|
const carrier = carrierOf(target);
|
|
@@ -163,20 +440,6 @@ function recordParam(target, fnName, meta) {
|
|
|
163
440
|
}
|
|
164
441
|
}
|
|
165
442
|
}
|
|
166
|
-
var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
|
|
167
|
-
function recordReturn(target, fnName, schema) {
|
|
168
|
-
const carrier = carrierOf(target);
|
|
169
|
-
const routes = carrier[ROUTES];
|
|
170
|
-
const route = routes?.find((r) => r.fnName === fnName);
|
|
171
|
-
if (route) {
|
|
172
|
-
route.returnSchema = schema;
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
if (!Object.prototype.hasOwnProperty.call(carrier, RETURN_BUFFER)) {
|
|
176
|
-
carrier[RETURN_BUFFER] = {};
|
|
177
|
-
}
|
|
178
|
-
carrier[RETURN_BUFFER][fnName] = schema;
|
|
179
|
-
}
|
|
180
443
|
|
|
181
444
|
// src/decorators/methods.ts
|
|
182
445
|
function makeMethodDecorator(method) {
|
|
@@ -191,11 +454,6 @@ var Post = makeMethodDecorator("POST");
|
|
|
191
454
|
var Put = makeMethodDecorator("PUT");
|
|
192
455
|
var Patch = makeMethodDecorator("PATCH");
|
|
193
456
|
var Delete = makeMethodDecorator("DELETE");
|
|
194
|
-
function Returns(schema) {
|
|
195
|
-
return function(target, propertyKey) {
|
|
196
|
-
recordReturn(target, String(propertyKey), schema);
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
457
|
|
|
200
458
|
// src/decorators/params.ts
|
|
201
459
|
function makeParamDecorator(kind, extra) {
|
|
@@ -638,16 +896,19 @@ export {
|
|
|
638
896
|
Delete,
|
|
639
897
|
Documents,
|
|
640
898
|
EXTENSION_DEPENDENCIES,
|
|
899
|
+
FLAGS_CONFIG_KIND,
|
|
641
900
|
Flags,
|
|
642
901
|
Forbidden,
|
|
643
902
|
Get,
|
|
644
903
|
Headers,
|
|
645
904
|
HttpError,
|
|
646
905
|
Log,
|
|
906
|
+
NOTIFICATIONS_CONFIG_KIND,
|
|
647
907
|
NotFound,
|
|
648
908
|
Notifications,
|
|
649
909
|
OptionalUser,
|
|
650
910
|
PALBASE_EXTENSIONS,
|
|
911
|
+
PROVIDER_CATALOG,
|
|
651
912
|
PalError,
|
|
652
913
|
Param,
|
|
653
914
|
Patch,
|
|
@@ -656,10 +917,11 @@ export {
|
|
|
656
917
|
Put,
|
|
657
918
|
Query,
|
|
658
919
|
Queue,
|
|
920
|
+
RESERVED_SECRET_PREFIX,
|
|
659
921
|
Req,
|
|
660
922
|
RequestId,
|
|
661
923
|
Resource,
|
|
662
|
-
|
|
924
|
+
STORAGE_CONFIG_KIND,
|
|
663
925
|
Storage,
|
|
664
926
|
TooManyRequests,
|
|
665
927
|
TraceId,
|
|
@@ -674,19 +936,27 @@ export {
|
|
|
674
936
|
__shutdownResources,
|
|
675
937
|
auth,
|
|
676
938
|
boolean,
|
|
939
|
+
bucket,
|
|
940
|
+
buildProvider,
|
|
941
|
+
defineFlags,
|
|
677
942
|
defineJob,
|
|
678
943
|
defineMiddleware,
|
|
944
|
+
defineNotifications,
|
|
679
945
|
defineSchema,
|
|
946
|
+
defineStorage,
|
|
680
947
|
defineWebhook,
|
|
681
948
|
defineWorker,
|
|
682
949
|
documents,
|
|
683
950
|
enumType,
|
|
951
|
+
flag,
|
|
684
952
|
integer,
|
|
685
953
|
isPalbaseExtension,
|
|
686
954
|
jsonb,
|
|
687
955
|
makeEnvDts,
|
|
688
956
|
makeTypedDB,
|
|
957
|
+
parseFileSizeLimit,
|
|
689
958
|
policy,
|
|
959
|
+
reservedSecretKey,
|
|
690
960
|
storage,
|
|
691
961
|
text,
|
|
692
962
|
timestamp,
|