@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.cjs
CHANGED
|
@@ -37,10 +37,12 @@ __export(src_exports, {
|
|
|
37
37
|
Get: () => Get,
|
|
38
38
|
Headers: () => Headers,
|
|
39
39
|
HttpError: () => HttpError,
|
|
40
|
+
Job: () => Job,
|
|
40
41
|
Log: () => Log,
|
|
41
42
|
NOTIFICATIONS_CONFIG_KIND: () => NOTIFICATIONS_CONFIG_KIND,
|
|
42
43
|
NotFound: () => NotFound,
|
|
43
44
|
Notifications: () => Notifications,
|
|
45
|
+
On: () => On,
|
|
44
46
|
OptionalUser: () => OptionalUser,
|
|
45
47
|
PALBASE_EXTENSIONS: () => PALBASE_EXTENSIONS,
|
|
46
48
|
PROVIDER_CATALOG: () => PROVIDER_CATALOG,
|
|
@@ -72,6 +74,7 @@ __export(src_exports, {
|
|
|
72
74
|
Upload: () => Upload,
|
|
73
75
|
UploadedObject: () => UploadedObject,
|
|
74
76
|
User: () => User,
|
|
77
|
+
Webhook: () => Webhook,
|
|
75
78
|
__getRuntime: () => __getRuntime,
|
|
76
79
|
__registerResource: () => __registerResource,
|
|
77
80
|
__requestALS: () => __requestALS,
|
|
@@ -88,20 +91,20 @@ __export(src_exports, {
|
|
|
88
91
|
defineEgress: () => defineEgress,
|
|
89
92
|
defineError: () => defineError,
|
|
90
93
|
defineFlags: () => defineFlags,
|
|
91
|
-
defineJob: () => defineJob,
|
|
92
94
|
defineMiddleware: () => defineMiddleware,
|
|
93
95
|
defineNotifications: () => defineNotifications,
|
|
94
96
|
defineSchema: () => defineSchema,
|
|
95
97
|
defineStorage: () => defineStorage,
|
|
96
98
|
defineTestUsers: () => defineTestUsers,
|
|
97
|
-
defineWebhook: () => defineWebhook,
|
|
98
99
|
defineWorker: () => defineWorker,
|
|
99
100
|
documents: () => documents,
|
|
100
101
|
entitlementFor: () => entitlementFor,
|
|
101
102
|
enumType: () => enumType,
|
|
102
103
|
flag: () => flag,
|
|
103
104
|
getErrorRegistry: () => getErrorRegistry,
|
|
105
|
+
getJobConfig: () => getJobConfig,
|
|
104
106
|
getRoutes: () => getRoutes,
|
|
107
|
+
getWebhookConfig: () => getWebhookConfig,
|
|
105
108
|
inc: () => inc,
|
|
106
109
|
integer: () => integer,
|
|
107
110
|
isPalbaseExtension: () => isPalbaseExtension,
|
|
@@ -1769,6 +1772,12 @@ function defineFlags(input) {
|
|
|
1769
1772
|
var CONTROLLER_META = /* @__PURE__ */ Symbol.for("palbase.backend.controllerMeta");
|
|
1770
1773
|
function Controller(basePath, options = {}) {
|
|
1771
1774
|
return function(ctor) {
|
|
1775
|
+
const normalized = basePath.replace(/\/+$/, "");
|
|
1776
|
+
if (normalized === "/webhooks" || normalized.startsWith("/webhooks/")) {
|
|
1777
|
+
throw new Error(
|
|
1778
|
+
`@Controller("${basePath}") uses the reserved /webhooks path \u2014 inbound webhooks are served there`
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1772
1781
|
const carrier = ctor;
|
|
1773
1782
|
const meta = {
|
|
1774
1783
|
__palbase: "controller",
|
|
@@ -2156,12 +2165,99 @@ function defineWorker(config) {
|
|
|
2156
2165
|
};
|
|
2157
2166
|
}
|
|
2158
2167
|
|
|
2168
|
+
// src/decorators/webhook.ts
|
|
2169
|
+
var WEBHOOK_META = /* @__PURE__ */ Symbol.for("palbase.backend.webhookMeta");
|
|
2170
|
+
var WEBHOOK_EVENTS = /* @__PURE__ */ Symbol.for("palbase.backend.webhookEvents");
|
|
2171
|
+
function carrierOf3(ctor) {
|
|
2172
|
+
return ctor;
|
|
2173
|
+
}
|
|
2174
|
+
function Webhook(options) {
|
|
2175
|
+
return function(ctor) {
|
|
2176
|
+
const carrier = carrierOf3(ctor);
|
|
2177
|
+
Object.defineProperty(carrier, WEBHOOK_META, {
|
|
2178
|
+
value: options,
|
|
2179
|
+
enumerable: false,
|
|
2180
|
+
configurable: true,
|
|
2181
|
+
writable: false
|
|
2182
|
+
});
|
|
2183
|
+
Object.defineProperty(carrier, "__palbase", {
|
|
2184
|
+
value: "webhook",
|
|
2185
|
+
enumerable: false,
|
|
2186
|
+
configurable: true,
|
|
2187
|
+
writable: false
|
|
2188
|
+
});
|
|
2189
|
+
return ctor;
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
function On(event) {
|
|
2193
|
+
return function(target, fnName) {
|
|
2194
|
+
const carrier = carrierOf3(target.constructor);
|
|
2195
|
+
const existing = carrier[WEBHOOK_EVENTS];
|
|
2196
|
+
const entries = existing ? [...existing] : [];
|
|
2197
|
+
entries.push({ event, fnName: String(fnName) });
|
|
2198
|
+
Object.defineProperty(carrier, WEBHOOK_EVENTS, {
|
|
2199
|
+
value: entries,
|
|
2200
|
+
enumerable: false,
|
|
2201
|
+
configurable: true,
|
|
2202
|
+
writable: false
|
|
2203
|
+
});
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
function getWebhookConfig(ctor) {
|
|
2207
|
+
const carrier = carrierOf3(ctor);
|
|
2208
|
+
const meta = carrier[WEBHOOK_META];
|
|
2209
|
+
const entries = carrier[WEBHOOK_EVENTS] ?? [];
|
|
2210
|
+
if (!meta) {
|
|
2211
|
+
throw new Error(
|
|
2212
|
+
`@On used on a class that is not decorated with @Webhook (${ctor.name ?? "anonymous"})`
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
if (!meta.provider && !meta.signature) {
|
|
2216
|
+
throw new Error(
|
|
2217
|
+
"@Webhook requires either a `provider` preset or an explicit `signature` \u2014 an endpoint with no verification would accept forged deliveries"
|
|
2218
|
+
);
|
|
2219
|
+
}
|
|
2220
|
+
if (meta.signature) {
|
|
2221
|
+
const sig = meta.signature;
|
|
2222
|
+
if (!sig.header) {
|
|
2223
|
+
throw new Error("@Webhook signature requires `header` \u2014 the header the signature arrives in");
|
|
2224
|
+
}
|
|
2225
|
+
if (sig.algo !== "hmac-sha256" && sig.algo !== "hmac-sha1") {
|
|
2226
|
+
throw new Error(`@Webhook signature has an unsupported algo "${sig.algo}"`);
|
|
2227
|
+
}
|
|
2228
|
+
if (sig.encoding !== "hex" && sig.encoding !== "base64") {
|
|
2229
|
+
throw new Error(`@Webhook signature has an unsupported encoding "${sig.encoding}"`);
|
|
2230
|
+
}
|
|
2231
|
+
if (!sig.signs?.includes("{body}")) {
|
|
2232
|
+
throw new Error("@Webhook signature `signs` must contain {body} \u2014 signing a constant is not a signature");
|
|
2233
|
+
}
|
|
2234
|
+
if (sig.signs.includes("{ts}") && !sig.timestampHeader) {
|
|
2235
|
+
throw new Error("@Webhook signature uses {ts} but declares no `timestampHeader` to read it from");
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
if (!meta.secret?.env) {
|
|
2239
|
+
throw new Error('@Webhook requires `secret: { env: "VAR_NAME" }`');
|
|
2240
|
+
}
|
|
2241
|
+
if (entries.length === 0) {
|
|
2242
|
+
throw new Error("@Webhook requires at least one @On handler");
|
|
2243
|
+
}
|
|
2244
|
+
const instance = new ctor();
|
|
2245
|
+
const events = /* @__PURE__ */ Object.create(null);
|
|
2246
|
+
for (const entry of entries) {
|
|
2247
|
+
if (Object.prototype.hasOwnProperty.call(events, entry.event)) {
|
|
2248
|
+
throw new Error(`@On("${entry.event}") declared twice on the same webhook`);
|
|
2249
|
+
}
|
|
2250
|
+
events[entry.event] = (event, metaArg) => instance[entry.fnName].call(instance, event, metaArg);
|
|
2251
|
+
}
|
|
2252
|
+
return {
|
|
2253
|
+
...meta.provider ? { provider: meta.provider } : {},
|
|
2254
|
+
...meta.signature ? { signature: meta.signature } : {},
|
|
2255
|
+
secret: meta.secret,
|
|
2256
|
+
events
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2159
2260
|
// src/job.ts
|
|
2160
|
-
var VALID_JOB_NAME = /^[a-zA-Z0-9_-]+$/;
|
|
2161
|
-
var MAX_TIMEOUT_SECONDS = 300;
|
|
2162
|
-
var JOB_DEFAULTS = {
|
|
2163
|
-
timeout: 30
|
|
2164
|
-
};
|
|
2165
2261
|
function validateCronExpression(expression) {
|
|
2166
2262
|
const trimmed = expression.trim();
|
|
2167
2263
|
if (trimmed === "") {
|
|
@@ -2227,115 +2323,56 @@ function validateCronField(field, name, min, max) {
|
|
|
2227
2323
|
}
|
|
2228
2324
|
return null;
|
|
2229
2325
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
}
|
|
2252
|
-
if (config.timeout !== void 0 && !Number.isInteger(config.timeout)) {
|
|
2253
|
-
throw new Error("Job timeout must be an integer");
|
|
2254
|
-
}
|
|
2255
|
-
if (config.timeout !== void 0 && config.timeout > MAX_TIMEOUT_SECONDS) {
|
|
2256
|
-
throw new Error(
|
|
2257
|
-
`Job timeout ${config.timeout}s exceeds maximum ${MAX_TIMEOUT_SECONDS}s`
|
|
2258
|
-
);
|
|
2259
|
-
}
|
|
2260
|
-
return {
|
|
2261
|
-
name: config.name,
|
|
2262
|
-
schedule: config.schedule.trim(),
|
|
2263
|
-
timeout: config.timeout ?? JOB_DEFAULTS.timeout,
|
|
2264
|
-
handler: config.handler
|
|
2326
|
+
|
|
2327
|
+
// src/decorators/job.ts
|
|
2328
|
+
var DEFAULT_TIMEOUT_SECONDS = 30;
|
|
2329
|
+
var MAX_TIMEOUT_SECONDS = 300;
|
|
2330
|
+
var JOB_META = /* @__PURE__ */ Symbol.for("palbase.backend.jobMeta");
|
|
2331
|
+
function Job(options) {
|
|
2332
|
+
return function(ctor) {
|
|
2333
|
+
const carrier = ctor;
|
|
2334
|
+
Object.defineProperty(carrier, JOB_META, {
|
|
2335
|
+
value: options,
|
|
2336
|
+
enumerable: false,
|
|
2337
|
+
configurable: true,
|
|
2338
|
+
writable: false
|
|
2339
|
+
});
|
|
2340
|
+
Object.defineProperty(carrier, "__palbase", {
|
|
2341
|
+
value: "job",
|
|
2342
|
+
enumerable: false,
|
|
2343
|
+
configurable: true,
|
|
2344
|
+
writable: false
|
|
2345
|
+
});
|
|
2346
|
+
return ctor;
|
|
2265
2347
|
};
|
|
2266
2348
|
}
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
function defineWebhook(config) {
|
|
2271
|
-
if ("provider" in config) {
|
|
2272
|
-
return validateProviderWebhook(config);
|
|
2273
|
-
}
|
|
2274
|
-
return validateCustomWebhook(config);
|
|
2275
|
-
}
|
|
2276
|
-
function validateProviderWebhook(config) {
|
|
2277
|
-
if (!config.provider) {
|
|
2278
|
-
throw new Error("Webhook provider is required");
|
|
2279
|
-
}
|
|
2280
|
-
const validProviders = [
|
|
2281
|
-
"stripe",
|
|
2282
|
-
"github",
|
|
2283
|
-
"twilio",
|
|
2284
|
-
"sendgrid",
|
|
2285
|
-
"slack",
|
|
2286
|
-
"discord",
|
|
2287
|
-
"livekit"
|
|
2288
|
-
];
|
|
2289
|
-
if (!validProviders.includes(config.provider)) {
|
|
2349
|
+
function getJobConfig(ctor) {
|
|
2350
|
+
const meta = ctor[JOB_META];
|
|
2351
|
+
if (!meta) {
|
|
2290
2352
|
throw new Error(
|
|
2291
|
-
`
|
|
2353
|
+
`getJobConfig on a class with no @Job decorator (${ctor.name ?? "anonymous"})`
|
|
2292
2354
|
);
|
|
2293
2355
|
}
|
|
2294
|
-
if (!
|
|
2295
|
-
throw new Error(
|
|
2356
|
+
if (!meta.schedule || meta.schedule.trim() === "") {
|
|
2357
|
+
throw new Error("@Job requires a `schedule` cron expression");
|
|
2296
2358
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2359
|
+
const cronError = validateCronExpression(meta.schedule);
|
|
2360
|
+
if (cronError) {
|
|
2361
|
+
throw new Error(`@Job has an invalid cron schedule: ${cronError}`);
|
|
2299
2362
|
}
|
|
2300
|
-
|
|
2301
|
-
|
|
2363
|
+
const timeout = meta.timeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
2364
|
+
if (!Number.isInteger(timeout) || timeout <= 0) {
|
|
2365
|
+
throw new Error("@Job `timeout` must be a positive whole number of seconds");
|
|
2302
2366
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
throw new Error(`Event handler for "${eventName}" must be a function`);
|
|
2306
|
-
}
|
|
2367
|
+
if (timeout > MAX_TIMEOUT_SECONDS) {
|
|
2368
|
+
throw new Error(`@Job \`timeout\` exceeds the ${MAX_TIMEOUT_SECONDS}s sandbox ceiling`);
|
|
2307
2369
|
}
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
secret: config.secret,
|
|
2312
|
-
events: config.events
|
|
2313
|
-
};
|
|
2314
|
-
}
|
|
2315
|
-
function validateCustomWebhook(config) {
|
|
2316
|
-
if (!config.path || config.path.trim() === "") {
|
|
2317
|
-
throw new Error("Webhook path is required");
|
|
2318
|
-
}
|
|
2319
|
-
if (!VALID_WEBHOOK_PATH.test(config.path)) {
|
|
2320
|
-
throw new Error(
|
|
2321
|
-
`Invalid webhook path "${config.path}": must start with / and contain only alphanumeric, hyphen, underscore, slash`
|
|
2322
|
-
);
|
|
2323
|
-
}
|
|
2324
|
-
if (!config.handler) {
|
|
2325
|
-
throw new Error("Webhook handler is required");
|
|
2370
|
+
const instance = new ctor();
|
|
2371
|
+
if (typeof instance.run !== "function") {
|
|
2372
|
+
throw new Error("@Job class must declare an async run() method");
|
|
2326
2373
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
}
|
|
2330
|
-
if (config.verify !== void 0 && typeof config.verify !== "function") {
|
|
2331
|
-
throw new Error("Webhook verify must be a function");
|
|
2332
|
-
}
|
|
2333
|
-
return {
|
|
2334
|
-
type: "custom",
|
|
2335
|
-
path: config.path,
|
|
2336
|
-
verify: config.verify,
|
|
2337
|
-
handler: config.handler
|
|
2338
|
-
};
|
|
2374
|
+
const run = instance.run.bind(instance);
|
|
2375
|
+
return { schedule: meta.schedule, timeout, handler: run };
|
|
2339
2376
|
}
|
|
2340
2377
|
|
|
2341
2378
|
// src/resource.ts
|
|
@@ -2440,10 +2477,12 @@ var import_zod2 = require("zod");
|
|
|
2440
2477
|
Get,
|
|
2441
2478
|
Headers,
|
|
2442
2479
|
HttpError,
|
|
2480
|
+
Job,
|
|
2443
2481
|
Log,
|
|
2444
2482
|
NOTIFICATIONS_CONFIG_KIND,
|
|
2445
2483
|
NotFound,
|
|
2446
2484
|
Notifications,
|
|
2485
|
+
On,
|
|
2447
2486
|
OptionalUser,
|
|
2448
2487
|
PALBASE_EXTENSIONS,
|
|
2449
2488
|
PROVIDER_CATALOG,
|
|
@@ -2475,6 +2514,7 @@ var import_zod2 = require("zod");
|
|
|
2475
2514
|
Upload,
|
|
2476
2515
|
UploadedObject,
|
|
2477
2516
|
User,
|
|
2517
|
+
Webhook,
|
|
2478
2518
|
__getRuntime,
|
|
2479
2519
|
__registerResource,
|
|
2480
2520
|
__requestALS,
|
|
@@ -2491,20 +2531,20 @@ var import_zod2 = require("zod");
|
|
|
2491
2531
|
defineEgress,
|
|
2492
2532
|
defineError,
|
|
2493
2533
|
defineFlags,
|
|
2494
|
-
defineJob,
|
|
2495
2534
|
defineMiddleware,
|
|
2496
2535
|
defineNotifications,
|
|
2497
2536
|
defineSchema,
|
|
2498
2537
|
defineStorage,
|
|
2499
2538
|
defineTestUsers,
|
|
2500
|
-
defineWebhook,
|
|
2501
2539
|
defineWorker,
|
|
2502
2540
|
documents,
|
|
2503
2541
|
entitlementFor,
|
|
2504
2542
|
enumType,
|
|
2505
2543
|
flag,
|
|
2506
2544
|
getErrorRegistry,
|
|
2545
|
+
getJobConfig,
|
|
2507
2546
|
getRoutes,
|
|
2547
|
+
getWebhookConfig,
|
|
2508
2548
|
inc,
|
|
2509
2549
|
integer,
|
|
2510
2550
|
isPalbaseExtension,
|