@palbase/backend 10.3.0 → 12.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.
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  __requestALS,
14
14
  __runWithRuntime,
15
15
  __setRuntime
16
- } from "./chunk-2PCNZBNZ.js";
16
+ } from "./chunk-XATG7BRC.js";
17
17
  import {
18
18
  EXTENSION_DEPENDENCIES,
19
19
  PALBASE_EXTENSIONS,
@@ -32,7 +32,14 @@ import {
32
32
  text,
33
33
  timestamp,
34
34
  uuid
35
- } 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";
36
43
 
37
44
  // src/errors.ts
38
45
  var HttpError = class extends Error {
@@ -836,6 +843,12 @@ function defineFlags(input) {
836
843
  var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
837
844
  function Controller(basePath, options = {}) {
838
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
+ }
839
852
  const carrier = ctor;
840
853
  const meta = {
841
854
  __palbase: "controller",
@@ -1227,12 +1240,81 @@ function defineWorker(config) {
1227
1240
  };
1228
1241
  }
1229
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.secret?.env) {
1296
+ throw new Error('@Webhook requires `secret: { env: "VAR_NAME" }`');
1297
+ }
1298
+ if (entries.length === 0) {
1299
+ throw new Error("@Webhook requires at least one @On handler");
1300
+ }
1301
+ const instance = new ctor();
1302
+ const events = /* @__PURE__ */ Object.create(null);
1303
+ for (const entry of entries) {
1304
+ if (Object.prototype.hasOwnProperty.call(events, entry.event)) {
1305
+ throw new Error(`@On("${entry.event}") declared twice on the same webhook`);
1306
+ }
1307
+ events[entry.event] = (event, metaArg) => instance[entry.fnName].call(instance, event, metaArg);
1308
+ }
1309
+ return {
1310
+ ...meta.provider ? { provider: meta.provider } : {},
1311
+ ...meta.signature ? { signature: meta.signature } : {},
1312
+ secret: meta.secret,
1313
+ events
1314
+ };
1315
+ }
1316
+
1230
1317
  // src/job.ts
1231
- var VALID_JOB_NAME = /^[a-zA-Z0-9_-]+$/;
1232
- var MAX_TIMEOUT_SECONDS = 300;
1233
- var JOB_DEFAULTS = {
1234
- timeout: 30
1235
- };
1236
1318
  function validateCronExpression(expression) {
1237
1319
  const trimmed = expression.trim();
1238
1320
  if (trimmed === "") {
@@ -1298,115 +1380,56 @@ function validateCronField(field, name, min, max) {
1298
1380
  }
1299
1381
  return null;
1300
1382
  }
1301
- function defineJob(config) {
1302
- if (!config.name || config.name.trim() === "") {
1303
- throw new Error("Job name is required");
1304
- }
1305
- if (!VALID_JOB_NAME.test(config.name)) {
1306
- throw new Error(
1307
- `Invalid job name "${config.name}": must match [a-zA-Z0-9_-]+`
1308
- );
1309
- }
1310
- if (!config.schedule || config.schedule.trim() === "") {
1311
- throw new Error("Job schedule is required");
1312
- }
1313
- const cronError = validateCronExpression(config.schedule);
1314
- if (cronError !== null) {
1315
- throw new Error(cronError);
1316
- }
1317
- if (!config.handler) {
1318
- throw new Error("Job handler is required");
1319
- }
1320
- if (config.timeout !== void 0 && config.timeout <= 0) {
1321
- throw new Error("Job timeout must be a positive number");
1322
- }
1323
- if (config.timeout !== void 0 && !Number.isInteger(config.timeout)) {
1324
- throw new Error("Job timeout must be an integer");
1325
- }
1326
- if (config.timeout !== void 0 && config.timeout > MAX_TIMEOUT_SECONDS) {
1327
- throw new Error(
1328
- `Job timeout ${config.timeout}s exceeds maximum ${MAX_TIMEOUT_SECONDS}s`
1329
- );
1330
- }
1331
- return {
1332
- name: config.name,
1333
- schedule: config.schedule.trim(),
1334
- timeout: config.timeout ?? JOB_DEFAULTS.timeout,
1335
- handler: config.handler
1383
+
1384
+ // src/decorators/job.ts
1385
+ var DEFAULT_TIMEOUT_SECONDS = 30;
1386
+ var MAX_TIMEOUT_SECONDS = 300;
1387
+ var JOB_META = /* @__PURE__ */ Symbol.for("palbase.backend.jobMeta");
1388
+ function Job(options) {
1389
+ return function(ctor) {
1390
+ const carrier = ctor;
1391
+ Object.defineProperty(carrier, JOB_META, {
1392
+ value: options,
1393
+ enumerable: false,
1394
+ configurable: true,
1395
+ writable: false
1396
+ });
1397
+ Object.defineProperty(carrier, "__palbase", {
1398
+ value: "job",
1399
+ enumerable: false,
1400
+ configurable: true,
1401
+ writable: false
1402
+ });
1403
+ return ctor;
1336
1404
  };
1337
1405
  }
1338
-
1339
- // src/webhook.ts
1340
- var VALID_WEBHOOK_PATH = /^\/[a-zA-Z0-9/_-]+$/;
1341
- function defineWebhook(config) {
1342
- if ("provider" in config) {
1343
- return validateProviderWebhook(config);
1344
- }
1345
- return validateCustomWebhook(config);
1346
- }
1347
- function validateProviderWebhook(config) {
1348
- if (!config.provider) {
1349
- throw new Error("Webhook provider is required");
1350
- }
1351
- const validProviders = [
1352
- "stripe",
1353
- "github",
1354
- "twilio",
1355
- "sendgrid",
1356
- "slack",
1357
- "discord",
1358
- "livekit"
1359
- ];
1360
- if (!validProviders.includes(config.provider)) {
1406
+ function getJobConfig(ctor) {
1407
+ const meta = ctor[JOB_META];
1408
+ if (!meta) {
1361
1409
  throw new Error(
1362
- `Invalid webhook provider "${config.provider}": must be one of ${validProviders.join(", ")}`
1410
+ `getJobConfig on a class with no @Job decorator (${ctor.name ?? "anonymous"})`
1363
1411
  );
1364
1412
  }
1365
- if (!config.secret) {
1366
- throw new Error('Webhook secret is required (use { env: "SECRET_NAME" })');
1413
+ if (!meta.schedule || meta.schedule.trim() === "") {
1414
+ throw new Error("@Job requires a `schedule` cron expression");
1367
1415
  }
1368
- if (typeof config.secret.env !== "string" || config.secret.env.trim() === "") {
1369
- throw new Error("Webhook secret env name must be a non-empty string");
1416
+ const cronError = validateCronExpression(meta.schedule);
1417
+ if (cronError) {
1418
+ throw new Error(`@Job has an invalid cron schedule: ${cronError}`);
1370
1419
  }
1371
- if (!config.events || Object.keys(config.events).length === 0) {
1372
- throw new Error("At least one event handler is required");
1420
+ const timeout = meta.timeout ?? DEFAULT_TIMEOUT_SECONDS;
1421
+ if (!Number.isInteger(timeout) || timeout <= 0) {
1422
+ throw new Error("@Job `timeout` must be a positive whole number of seconds");
1373
1423
  }
1374
- for (const [eventName, handler] of Object.entries(config.events)) {
1375
- if (typeof handler !== "function") {
1376
- throw new Error(`Event handler for "${eventName}" must be a function`);
1377
- }
1424
+ if (timeout > MAX_TIMEOUT_SECONDS) {
1425
+ throw new Error(`@Job \`timeout\` exceeds the ${MAX_TIMEOUT_SECONDS}s sandbox ceiling`);
1378
1426
  }
1379
- return {
1380
- type: "provider",
1381
- provider: config.provider,
1382
- secret: config.secret,
1383
- events: config.events
1384
- };
1385
- }
1386
- function validateCustomWebhook(config) {
1387
- if (!config.path || config.path.trim() === "") {
1388
- throw new Error("Webhook path is required");
1389
- }
1390
- if (!VALID_WEBHOOK_PATH.test(config.path)) {
1391
- throw new Error(
1392
- `Invalid webhook path "${config.path}": must start with / and contain only alphanumeric, hyphen, underscore, slash`
1393
- );
1394
- }
1395
- if (!config.handler) {
1396
- throw new Error("Webhook handler is required");
1427
+ const instance = new ctor();
1428
+ if (typeof instance.run !== "function") {
1429
+ throw new Error("@Job class must declare an async run() method");
1397
1430
  }
1398
- if (typeof config.handler !== "function") {
1399
- throw new Error("Webhook handler must be a function");
1400
- }
1401
- if (config.verify !== void 0 && typeof config.verify !== "function") {
1402
- throw new Error("Webhook verify must be a function");
1403
- }
1404
- return {
1405
- type: "custom",
1406
- path: config.path,
1407
- verify: config.verify,
1408
- handler: config.handler
1409
- };
1431
+ const run = instance.run.bind(instance);
1432
+ return { schedule: meta.schedule, timeout, handler: run };
1410
1433
  }
1411
1434
 
1412
1435
  // src/resource.ts
@@ -1510,10 +1533,12 @@ export {
1510
1533
  Get,
1511
1534
  Headers,
1512
1535
  HttpError,
1536
+ Job,
1513
1537
  Log,
1514
1538
  NOTIFICATIONS_CONFIG_KIND,
1515
1539
  NotFound,
1516
1540
  Notifications,
1541
+ On,
1517
1542
  OptionalUser,
1518
1543
  PALBASE_EXTENSIONS,
1519
1544
  PROVIDER_CATALOG,
@@ -1539,10 +1564,13 @@ export {
1539
1564
  TEST_USERS_CONFIG_KIND,
1540
1565
  TooManyRequests,
1541
1566
  TraceId,
1567
+ TxPlanError,
1568
+ TxRefError,
1542
1569
  Unauthorized,
1543
1570
  Upload,
1544
1571
  UploadedObject,
1545
1572
  User,
1573
+ Webhook,
1546
1574
  __getRuntime,
1547
1575
  __registerResource,
1548
1576
  __requestALS,
@@ -1555,29 +1583,32 @@ export {
1555
1583
  boolean,
1556
1584
  bucket,
1557
1585
  buildProvider,
1586
+ dec,
1558
1587
  defineEgress,
1559
1588
  defineError,
1560
1589
  defineFlags,
1561
- defineJob,
1562
1590
  defineMiddleware,
1563
1591
  defineNotifications,
1564
1592
  defineSchema,
1565
1593
  defineStorage,
1566
1594
  defineTestUsers,
1567
- defineWebhook,
1568
1595
  defineWorker,
1569
1596
  documents,
1570
1597
  entitlementFor,
1571
1598
  enumType,
1572
1599
  flag,
1573
1600
  getErrorRegistry,
1601
+ getJobConfig,
1574
1602
  getRoutes,
1603
+ getWebhookConfig,
1604
+ inc,
1575
1605
  integer,
1576
1606
  isPalbaseExtension,
1577
1607
  jsonb,
1578
1608
  makeEnvDts,
1579
1609
  makePurchasesDts,
1580
1610
  makeTypedDB,
1611
+ now,
1581
1612
  numeric,
1582
1613
  parseFileSizeLimit,
1583
1614
  policy,