@palbase/backend 5.0.0 → 5.2.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/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/{endpoint-BEHjfvFH.d.cts → endpoint-B3uVK6OL.d.cts} +56 -1
- package/dist/{endpoint-BEHjfvFH.d.ts → endpoint-B3uVK6OL.d.ts} +56 -1
- package/dist/{index-mr3Co63T.d.cts → index-BAEWl60b.d.cts} +1 -1
- package/dist/{index-BTVdhfsb.d.ts → index-D9uLjEhB.d.ts} +1 -1
- package/dist/index.cjs +297 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +502 -5
- package/dist/index.d.ts +502 -5
- package/dist/index.js +284 -0
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +6 -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 +6 -1
- package/dist/test/index.js.map +1 -1
- package/docs/config.md +100 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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 = {}) {
|
|
@@ -625,16 +896,19 @@ export {
|
|
|
625
896
|
Delete,
|
|
626
897
|
Documents,
|
|
627
898
|
EXTENSION_DEPENDENCIES,
|
|
899
|
+
FLAGS_CONFIG_KIND,
|
|
628
900
|
Flags,
|
|
629
901
|
Forbidden,
|
|
630
902
|
Get,
|
|
631
903
|
Headers,
|
|
632
904
|
HttpError,
|
|
633
905
|
Log,
|
|
906
|
+
NOTIFICATIONS_CONFIG_KIND,
|
|
634
907
|
NotFound,
|
|
635
908
|
Notifications,
|
|
636
909
|
OptionalUser,
|
|
637
910
|
PALBASE_EXTENSIONS,
|
|
911
|
+
PROVIDER_CATALOG,
|
|
638
912
|
PalError,
|
|
639
913
|
Param,
|
|
640
914
|
Patch,
|
|
@@ -643,9 +917,11 @@ export {
|
|
|
643
917
|
Put,
|
|
644
918
|
Query,
|
|
645
919
|
Queue,
|
|
920
|
+
RESERVED_SECRET_PREFIX,
|
|
646
921
|
Req,
|
|
647
922
|
RequestId,
|
|
648
923
|
Resource,
|
|
924
|
+
STORAGE_CONFIG_KIND,
|
|
649
925
|
Storage,
|
|
650
926
|
TooManyRequests,
|
|
651
927
|
TraceId,
|
|
@@ -660,19 +936,27 @@ export {
|
|
|
660
936
|
__shutdownResources,
|
|
661
937
|
auth,
|
|
662
938
|
boolean,
|
|
939
|
+
bucket,
|
|
940
|
+
buildProvider,
|
|
941
|
+
defineFlags,
|
|
663
942
|
defineJob,
|
|
664
943
|
defineMiddleware,
|
|
944
|
+
defineNotifications,
|
|
665
945
|
defineSchema,
|
|
946
|
+
defineStorage,
|
|
666
947
|
defineWebhook,
|
|
667
948
|
defineWorker,
|
|
668
949
|
documents,
|
|
669
950
|
enumType,
|
|
951
|
+
flag,
|
|
670
952
|
integer,
|
|
671
953
|
isPalbaseExtension,
|
|
672
954
|
jsonb,
|
|
673
955
|
makeEnvDts,
|
|
674
956
|
makeTypedDB,
|
|
957
|
+
parseFileSizeLimit,
|
|
675
958
|
policy,
|
|
959
|
+
reservedSecretKey,
|
|
676
960
|
storage,
|
|
677
961
|
text,
|
|
678
962
|
timestamp,
|