@palbase/backend 11.0.0 → 12.0.1
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/index.cjs +148 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -158
- package/dist/index.d.ts +56 -158
- package/dist/index.js +143 -106
- package/dist/index.js.map +1 -1
- package/docs/background.md +8 -9
- package/docs/events.md +20 -17
- package/docs/llms-full.txt +29 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -843,6 +843,12 @@ function defineFlags(input) {
|
|
|
843
843
|
var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
|
|
844
844
|
function Controller(basePath, options = {}) {
|
|
845
845
|
return function(ctor) {
|
|
846
|
+
const normalized = basePath.replace(/\/+$/, "");
|
|
847
|
+
if (normalized === "/webhooks" || normalized.startsWith("/webhooks/")) {
|
|
848
|
+
throw new Error(
|
|
849
|
+
`@Controller("${basePath}") uses the reserved /webhooks path \u2014 inbound webhooks are served there`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
846
852
|
const carrier = ctor;
|
|
847
853
|
const meta = {
|
|
848
854
|
__palbase: "controller",
|
|
@@ -1234,12 +1240,99 @@ function defineWorker(config) {
|
|
|
1234
1240
|
};
|
|
1235
1241
|
}
|
|
1236
1242
|
|
|
1243
|
+
// src/decorators/webhook.ts
|
|
1244
|
+
var WEBHOOK_META = /* @__PURE__ */ Symbol.for("palbase.backend.webhookMeta");
|
|
1245
|
+
var WEBHOOK_EVENTS = /* @__PURE__ */ Symbol.for("palbase.backend.webhookEvents");
|
|
1246
|
+
function carrierOf3(ctor) {
|
|
1247
|
+
return ctor;
|
|
1248
|
+
}
|
|
1249
|
+
function Webhook(options) {
|
|
1250
|
+
return function(ctor) {
|
|
1251
|
+
const carrier = carrierOf3(ctor);
|
|
1252
|
+
Object.defineProperty(carrier, WEBHOOK_META, {
|
|
1253
|
+
value: options,
|
|
1254
|
+
enumerable: false,
|
|
1255
|
+
configurable: true,
|
|
1256
|
+
writable: false
|
|
1257
|
+
});
|
|
1258
|
+
Object.defineProperty(carrier, "__palbase", {
|
|
1259
|
+
value: "webhook",
|
|
1260
|
+
enumerable: false,
|
|
1261
|
+
configurable: true,
|
|
1262
|
+
writable: false
|
|
1263
|
+
});
|
|
1264
|
+
return ctor;
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
function On(event) {
|
|
1268
|
+
return function(target, fnName) {
|
|
1269
|
+
const carrier = carrierOf3(target.constructor);
|
|
1270
|
+
const existing = carrier[WEBHOOK_EVENTS];
|
|
1271
|
+
const entries = existing ? [...existing] : [];
|
|
1272
|
+
entries.push({ event, fnName: String(fnName) });
|
|
1273
|
+
Object.defineProperty(carrier, WEBHOOK_EVENTS, {
|
|
1274
|
+
value: entries,
|
|
1275
|
+
enumerable: false,
|
|
1276
|
+
configurable: true,
|
|
1277
|
+
writable: false
|
|
1278
|
+
});
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
function getWebhookConfig(ctor) {
|
|
1282
|
+
const carrier = carrierOf3(ctor);
|
|
1283
|
+
const meta = carrier[WEBHOOK_META];
|
|
1284
|
+
const entries = carrier[WEBHOOK_EVENTS] ?? [];
|
|
1285
|
+
if (!meta) {
|
|
1286
|
+
throw new Error(
|
|
1287
|
+
`@On used on a class that is not decorated with @Webhook (${ctor.name ?? "anonymous"})`
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
if (!meta.provider && !meta.signature) {
|
|
1291
|
+
throw new Error(
|
|
1292
|
+
"@Webhook requires either a `provider` preset or an explicit `signature` \u2014 an endpoint with no verification would accept forged deliveries"
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
if (meta.signature) {
|
|
1296
|
+
const sig = meta.signature;
|
|
1297
|
+
if (!sig.header) {
|
|
1298
|
+
throw new Error("@Webhook signature requires `header` \u2014 the header the signature arrives in");
|
|
1299
|
+
}
|
|
1300
|
+
if (sig.algo !== "hmac-sha256" && sig.algo !== "hmac-sha1") {
|
|
1301
|
+
throw new Error(`@Webhook signature has an unsupported algo "${sig.algo}"`);
|
|
1302
|
+
}
|
|
1303
|
+
if (sig.encoding !== "hex" && sig.encoding !== "base64") {
|
|
1304
|
+
throw new Error(`@Webhook signature has an unsupported encoding "${sig.encoding}"`);
|
|
1305
|
+
}
|
|
1306
|
+
if (!sig.signs?.includes("{body}")) {
|
|
1307
|
+
throw new Error("@Webhook signature `signs` must contain {body} \u2014 signing a constant is not a signature");
|
|
1308
|
+
}
|
|
1309
|
+
if (sig.signs.includes("{ts}") && !sig.timestampHeader) {
|
|
1310
|
+
throw new Error("@Webhook signature uses {ts} but declares no `timestampHeader` to read it from");
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (!meta.secret?.env) {
|
|
1314
|
+
throw new Error('@Webhook requires `secret: { env: "VAR_NAME" }`');
|
|
1315
|
+
}
|
|
1316
|
+
if (entries.length === 0) {
|
|
1317
|
+
throw new Error("@Webhook requires at least one @On handler");
|
|
1318
|
+
}
|
|
1319
|
+
const instance = new ctor();
|
|
1320
|
+
const events = /* @__PURE__ */ Object.create(null);
|
|
1321
|
+
for (const entry of entries) {
|
|
1322
|
+
if (Object.prototype.hasOwnProperty.call(events, entry.event)) {
|
|
1323
|
+
throw new Error(`@On("${entry.event}") declared twice on the same webhook`);
|
|
1324
|
+
}
|
|
1325
|
+
events[entry.event] = (event, metaArg) => instance[entry.fnName].call(instance, event, metaArg);
|
|
1326
|
+
}
|
|
1327
|
+
return {
|
|
1328
|
+
...meta.provider ? { provider: meta.provider } : {},
|
|
1329
|
+
...meta.signature ? { signature: meta.signature } : {},
|
|
1330
|
+
secret: meta.secret,
|
|
1331
|
+
events
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1237
1335
|
// src/job.ts
|
|
1238
|
-
var VALID_JOB_NAME = /^[a-zA-Z0-9_-]+$/;
|
|
1239
|
-
var MAX_TIMEOUT_SECONDS = 300;
|
|
1240
|
-
var JOB_DEFAULTS = {
|
|
1241
|
-
timeout: 30
|
|
1242
|
-
};
|
|
1243
1336
|
function validateCronExpression(expression) {
|
|
1244
1337
|
const trimmed = expression.trim();
|
|
1245
1338
|
if (trimmed === "") {
|
|
@@ -1305,115 +1398,56 @@ function validateCronField(field, name, min, max) {
|
|
|
1305
1398
|
}
|
|
1306
1399
|
return null;
|
|
1307
1400
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
}
|
|
1330
|
-
if (config.timeout !== void 0 && !Number.isInteger(config.timeout)) {
|
|
1331
|
-
throw new Error("Job timeout must be an integer");
|
|
1332
|
-
}
|
|
1333
|
-
if (config.timeout !== void 0 && config.timeout > MAX_TIMEOUT_SECONDS) {
|
|
1334
|
-
throw new Error(
|
|
1335
|
-
`Job timeout ${config.timeout}s exceeds maximum ${MAX_TIMEOUT_SECONDS}s`
|
|
1336
|
-
);
|
|
1337
|
-
}
|
|
1338
|
-
return {
|
|
1339
|
-
name: config.name,
|
|
1340
|
-
schedule: config.schedule.trim(),
|
|
1341
|
-
timeout: config.timeout ?? JOB_DEFAULTS.timeout,
|
|
1342
|
-
handler: config.handler
|
|
1401
|
+
|
|
1402
|
+
// src/decorators/job.ts
|
|
1403
|
+
var DEFAULT_TIMEOUT_SECONDS = 30;
|
|
1404
|
+
var MAX_TIMEOUT_SECONDS = 300;
|
|
1405
|
+
var JOB_META = /* @__PURE__ */ Symbol.for("palbase.backend.jobMeta");
|
|
1406
|
+
function Job(options) {
|
|
1407
|
+
return function(ctor) {
|
|
1408
|
+
const carrier = ctor;
|
|
1409
|
+
Object.defineProperty(carrier, JOB_META, {
|
|
1410
|
+
value: options,
|
|
1411
|
+
enumerable: false,
|
|
1412
|
+
configurable: true,
|
|
1413
|
+
writable: false
|
|
1414
|
+
});
|
|
1415
|
+
Object.defineProperty(carrier, "__palbase", {
|
|
1416
|
+
value: "job",
|
|
1417
|
+
enumerable: false,
|
|
1418
|
+
configurable: true,
|
|
1419
|
+
writable: false
|
|
1420
|
+
});
|
|
1421
|
+
return ctor;
|
|
1343
1422
|
};
|
|
1344
1423
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
function defineWebhook(config) {
|
|
1349
|
-
if ("provider" in config) {
|
|
1350
|
-
return validateProviderWebhook(config);
|
|
1351
|
-
}
|
|
1352
|
-
return validateCustomWebhook(config);
|
|
1353
|
-
}
|
|
1354
|
-
function validateProviderWebhook(config) {
|
|
1355
|
-
if (!config.provider) {
|
|
1356
|
-
throw new Error("Webhook provider is required");
|
|
1357
|
-
}
|
|
1358
|
-
const validProviders = [
|
|
1359
|
-
"stripe",
|
|
1360
|
-
"github",
|
|
1361
|
-
"twilio",
|
|
1362
|
-
"sendgrid",
|
|
1363
|
-
"slack",
|
|
1364
|
-
"discord",
|
|
1365
|
-
"livekit"
|
|
1366
|
-
];
|
|
1367
|
-
if (!validProviders.includes(config.provider)) {
|
|
1424
|
+
function getJobConfig(ctor) {
|
|
1425
|
+
const meta = ctor[JOB_META];
|
|
1426
|
+
if (!meta) {
|
|
1368
1427
|
throw new Error(
|
|
1369
|
-
`
|
|
1428
|
+
`getJobConfig on a class with no @Job decorator (${ctor.name ?? "anonymous"})`
|
|
1370
1429
|
);
|
|
1371
1430
|
}
|
|
1372
|
-
if (!
|
|
1373
|
-
throw new Error(
|
|
1431
|
+
if (!meta.schedule || meta.schedule.trim() === "") {
|
|
1432
|
+
throw new Error("@Job requires a `schedule` cron expression");
|
|
1374
1433
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1434
|
+
const cronError = validateCronExpression(meta.schedule);
|
|
1435
|
+
if (cronError) {
|
|
1436
|
+
throw new Error(`@Job has an invalid cron schedule: ${cronError}`);
|
|
1377
1437
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1438
|
+
const timeout = meta.timeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
1439
|
+
if (!Number.isInteger(timeout) || timeout <= 0) {
|
|
1440
|
+
throw new Error("@Job `timeout` must be a positive whole number of seconds");
|
|
1380
1441
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
throw new Error(`Event handler for "${eventName}" must be a function`);
|
|
1384
|
-
}
|
|
1442
|
+
if (timeout > MAX_TIMEOUT_SECONDS) {
|
|
1443
|
+
throw new Error(`@Job \`timeout\` exceeds the ${MAX_TIMEOUT_SECONDS}s sandbox ceiling`);
|
|
1385
1444
|
}
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
secret: config.secret,
|
|
1390
|
-
events: config.events
|
|
1391
|
-
};
|
|
1392
|
-
}
|
|
1393
|
-
function validateCustomWebhook(config) {
|
|
1394
|
-
if (!config.path || config.path.trim() === "") {
|
|
1395
|
-
throw new Error("Webhook path is required");
|
|
1396
|
-
}
|
|
1397
|
-
if (!VALID_WEBHOOK_PATH.test(config.path)) {
|
|
1398
|
-
throw new Error(
|
|
1399
|
-
`Invalid webhook path "${config.path}": must start with / and contain only alphanumeric, hyphen, underscore, slash`
|
|
1400
|
-
);
|
|
1401
|
-
}
|
|
1402
|
-
if (!config.handler) {
|
|
1403
|
-
throw new Error("Webhook handler is required");
|
|
1445
|
+
const instance = new ctor();
|
|
1446
|
+
if (typeof instance.run !== "function") {
|
|
1447
|
+
throw new Error("@Job class must declare an async run() method");
|
|
1404
1448
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1408
|
-
if (config.verify !== void 0 && typeof config.verify !== "function") {
|
|
1409
|
-
throw new Error("Webhook verify must be a function");
|
|
1410
|
-
}
|
|
1411
|
-
return {
|
|
1412
|
-
type: "custom",
|
|
1413
|
-
path: config.path,
|
|
1414
|
-
verify: config.verify,
|
|
1415
|
-
handler: config.handler
|
|
1416
|
-
};
|
|
1449
|
+
const run = instance.run.bind(instance);
|
|
1450
|
+
return { schedule: meta.schedule, timeout, handler: run };
|
|
1417
1451
|
}
|
|
1418
1452
|
|
|
1419
1453
|
// src/resource.ts
|
|
@@ -1517,10 +1551,12 @@ export {
|
|
|
1517
1551
|
Get,
|
|
1518
1552
|
Headers,
|
|
1519
1553
|
HttpError,
|
|
1554
|
+
Job,
|
|
1520
1555
|
Log,
|
|
1521
1556
|
NOTIFICATIONS_CONFIG_KIND,
|
|
1522
1557
|
NotFound,
|
|
1523
1558
|
Notifications,
|
|
1559
|
+
On,
|
|
1524
1560
|
OptionalUser,
|
|
1525
1561
|
PALBASE_EXTENSIONS,
|
|
1526
1562
|
PROVIDER_CATALOG,
|
|
@@ -1552,6 +1588,7 @@ export {
|
|
|
1552
1588
|
Upload,
|
|
1553
1589
|
UploadedObject,
|
|
1554
1590
|
User,
|
|
1591
|
+
Webhook,
|
|
1555
1592
|
__getRuntime,
|
|
1556
1593
|
__registerResource,
|
|
1557
1594
|
__requestALS,
|
|
@@ -1568,20 +1605,20 @@ export {
|
|
|
1568
1605
|
defineEgress,
|
|
1569
1606
|
defineError,
|
|
1570
1607
|
defineFlags,
|
|
1571
|
-
defineJob,
|
|
1572
1608
|
defineMiddleware,
|
|
1573
1609
|
defineNotifications,
|
|
1574
1610
|
defineSchema,
|
|
1575
1611
|
defineStorage,
|
|
1576
1612
|
defineTestUsers,
|
|
1577
|
-
defineWebhook,
|
|
1578
1613
|
defineWorker,
|
|
1579
1614
|
documents,
|
|
1580
1615
|
entitlementFor,
|
|
1581
1616
|
enumType,
|
|
1582
1617
|
flag,
|
|
1583
1618
|
getErrorRegistry,
|
|
1619
|
+
getJobConfig,
|
|
1584
1620
|
getRoutes,
|
|
1621
|
+
getWebhookConfig,
|
|
1585
1622
|
inc,
|
|
1586
1623
|
integer,
|
|
1587
1624
|
isPalbaseExtension,
|