skedyul 1.4.8 → 1.4.11
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/cli/index.js +29 -23
- package/dist/dedicated/server.js +23 -1
- package/dist/esm/index.mjs +85 -27
- package/dist/index.js +85 -27
- package/dist/ratelimit/backends/platform.d.ts +1 -1
- package/dist/ratelimit/context.d.ts +3 -0
- package/dist/ratelimit/types.d.ts +6 -1
- package/dist/server/tool-handler.d.ts +4 -0
- package/dist/server.js +23 -1
- package/dist/serverless/server.mjs +23 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2882,6 +2882,7 @@ function buildToolCallErrorOutput(result) {
|
|
|
2882
2882
|
var import_async_hooks2 = require("async_hooks");
|
|
2883
2883
|
var executionContextStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
2884
2884
|
var activeOperationStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
2885
|
+
var activeOperationStackStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
2885
2886
|
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
2886
2887
|
return executionContextStorage.run(ctx, fn);
|
|
2887
2888
|
}
|
|
@@ -2984,6 +2985,26 @@ function installContextLogger() {
|
|
|
2984
2985
|
}
|
|
2985
2986
|
|
|
2986
2987
|
// src/server/tool-handler.ts
|
|
2988
|
+
function parsePreAcquiredLeases(raw) {
|
|
2989
|
+
if (!raw) {
|
|
2990
|
+
return void 0;
|
|
2991
|
+
}
|
|
2992
|
+
try {
|
|
2993
|
+
const parsed = JSON.parse(raw);
|
|
2994
|
+
if (!Array.isArray(parsed)) {
|
|
2995
|
+
return void 0;
|
|
2996
|
+
}
|
|
2997
|
+
const leases = parsed.filter(
|
|
2998
|
+
(item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.leaseId === "string"
|
|
2999
|
+
).map((item) => ({
|
|
3000
|
+
queueKey: item.queueKey,
|
|
3001
|
+
leaseId: item.leaseId
|
|
3002
|
+
}));
|
|
3003
|
+
return leases.length > 0 ? leases : void 0;
|
|
3004
|
+
} catch {
|
|
3005
|
+
return void 0;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
2987
3008
|
function buildToolMetadata(registry) {
|
|
2988
3009
|
return Object.values(registry).map((tool) => {
|
|
2989
3010
|
const toolConfig = tool.config ?? {};
|
|
@@ -3116,7 +3137,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
3116
3137
|
app,
|
|
3117
3138
|
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
3118
3139
|
invocation,
|
|
3119
|
-
isProvisionContext: trigger === "provision"
|
|
3140
|
+
isProvisionContext: trigger === "provision",
|
|
3141
|
+
preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
|
|
3120
3142
|
};
|
|
3121
3143
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
3122
3144
|
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
@@ -5859,18 +5881,6 @@ var CONFIG_FILE_NAMES = [
|
|
|
5859
5881
|
"skedyul.config.mjs",
|
|
5860
5882
|
"skedyul.config.cjs"
|
|
5861
5883
|
];
|
|
5862
|
-
function loadTypeScriptConfigModule(absolutePath) {
|
|
5863
|
-
try {
|
|
5864
|
-
require("tsx/cjs");
|
|
5865
|
-
delete require.cache[absolutePath];
|
|
5866
|
-
const module2 = require(absolutePath);
|
|
5867
|
-
return module2.default ?? module2;
|
|
5868
|
-
} catch (error) {
|
|
5869
|
-
throw new Error(
|
|
5870
|
-
`Cannot load TypeScript config: ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
5871
|
-
);
|
|
5872
|
-
}
|
|
5873
|
-
}
|
|
5874
5884
|
async function transpileTypeScript(filePath) {
|
|
5875
5885
|
const content = fs10.readFileSync(filePath, "utf-8");
|
|
5876
5886
|
const configDir = path10.dirname(path10.resolve(filePath));
|
|
@@ -5902,15 +5912,6 @@ async function loadConfig(configPath) {
|
|
|
5902
5912
|
try {
|
|
5903
5913
|
if (isTypeScript) {
|
|
5904
5914
|
try {
|
|
5905
|
-
const config2 = loadTypeScriptConfigModule(absolutePath);
|
|
5906
|
-
if (!config2 || typeof config2 !== "object") {
|
|
5907
|
-
throw new Error("Config file must export a configuration object");
|
|
5908
|
-
}
|
|
5909
|
-
if (!config2.name || typeof config2.name !== "string") {
|
|
5910
|
-
throw new Error('Config must have a "name" property');
|
|
5911
|
-
}
|
|
5912
|
-
return config2;
|
|
5913
|
-
} catch (tsxError) {
|
|
5914
5915
|
const transpiled = await transpileTypeScript(absolutePath);
|
|
5915
5916
|
const tempFile = path10.join(os2.tmpdir(), `skedyul-config-${Date.now()}.cjs`);
|
|
5916
5917
|
fs10.writeFileSync(tempFile, transpiled);
|
|
@@ -5930,6 +5931,10 @@ async function loadConfig(configPath) {
|
|
|
5930
5931
|
} catch {
|
|
5931
5932
|
}
|
|
5932
5933
|
}
|
|
5934
|
+
} catch (transpileError) {
|
|
5935
|
+
throw new Error(
|
|
5936
|
+
`Cannot load TypeScript config metadata from ${absolutePath}: ${transpileError instanceof Error ? transpileError.message : String(transpileError)}`
|
|
5937
|
+
);
|
|
5933
5938
|
}
|
|
5934
5939
|
}
|
|
5935
5940
|
const module2 = await import(
|
|
@@ -6424,7 +6429,8 @@ function serializeResolvedConfig(config) {
|
|
|
6424
6429
|
description: tool.description,
|
|
6425
6430
|
// Read timeout/retries from top-level first, then fallback to config
|
|
6426
6431
|
timeout: tool.timeout ?? tool.config?.timeout,
|
|
6427
|
-
retries: tool.retries ?? tool.config?.retries
|
|
6432
|
+
retries: tool.retries ?? tool.config?.retries,
|
|
6433
|
+
queueTouchPoints: tool.queueTouchPoints ?? tool.config?.queueTouchPoints
|
|
6428
6434
|
})) : [],
|
|
6429
6435
|
webhooks: config.webhooks ? Object.values(config.webhooks).map((w) => ({
|
|
6430
6436
|
name: w.name,
|
package/dist/dedicated/server.js
CHANGED
|
@@ -285,6 +285,7 @@ function runWithConfig(config, fn) {
|
|
|
285
285
|
var import_async_hooks2 = require("async_hooks");
|
|
286
286
|
var executionContextStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
287
287
|
var activeOperationStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
288
|
+
var activeOperationStackStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
288
289
|
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
289
290
|
return executionContextStorage.run(ctx, fn);
|
|
290
291
|
}
|
|
@@ -407,6 +408,26 @@ function createContextLogger() {
|
|
|
407
408
|
}
|
|
408
409
|
|
|
409
410
|
// src/server/tool-handler.ts
|
|
411
|
+
function parsePreAcquiredLeases(raw) {
|
|
412
|
+
if (!raw) {
|
|
413
|
+
return void 0;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
const parsed = JSON.parse(raw);
|
|
417
|
+
if (!Array.isArray(parsed)) {
|
|
418
|
+
return void 0;
|
|
419
|
+
}
|
|
420
|
+
const leases = parsed.filter(
|
|
421
|
+
(item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.leaseId === "string"
|
|
422
|
+
).map((item) => ({
|
|
423
|
+
queueKey: item.queueKey,
|
|
424
|
+
leaseId: item.leaseId
|
|
425
|
+
}));
|
|
426
|
+
return leases.length > 0 ? leases : void 0;
|
|
427
|
+
} catch {
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
410
431
|
function buildToolMetadata(registry) {
|
|
411
432
|
return Object.values(registry).map((tool) => {
|
|
412
433
|
const toolConfig = tool.config ?? {};
|
|
@@ -539,7 +560,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
539
560
|
app,
|
|
540
561
|
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
541
562
|
invocation,
|
|
542
|
-
isProvisionContext: trigger === "provision"
|
|
563
|
+
isProvisionContext: trigger === "provision",
|
|
564
|
+
preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
|
|
543
565
|
};
|
|
544
566
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
545
567
|
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
package/dist/esm/index.mjs
CHANGED
|
@@ -3293,6 +3293,7 @@ var report = {
|
|
|
3293
3293
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
3294
3294
|
var executionContextStorage = new AsyncLocalStorage2();
|
|
3295
3295
|
var activeOperationStorage = new AsyncLocalStorage2();
|
|
3296
|
+
var activeOperationStackStorage = new AsyncLocalStorage2();
|
|
3296
3297
|
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
3297
3298
|
return executionContextStorage.run(ctx, fn);
|
|
3298
3299
|
}
|
|
@@ -3300,11 +3301,24 @@ function getRateLimitExecutionContext() {
|
|
|
3300
3301
|
return executionContextStorage.getStore();
|
|
3301
3302
|
}
|
|
3302
3303
|
function runWithActiveQueuedOperation(operation, fn) {
|
|
3303
|
-
|
|
3304
|
+
const parentStack = activeOperationStackStorage.getStore() ?? [];
|
|
3305
|
+
const stack = [...parentStack, operation];
|
|
3306
|
+
return activeOperationStorage.run(
|
|
3307
|
+
operation,
|
|
3308
|
+
() => activeOperationStackStorage.run(stack, fn)
|
|
3309
|
+
);
|
|
3304
3310
|
}
|
|
3305
3311
|
function getActiveQueuedOperation() {
|
|
3306
3312
|
return activeOperationStorage.getStore();
|
|
3307
3313
|
}
|
|
3314
|
+
function getActiveQueuedOperationStack() {
|
|
3315
|
+
return activeOperationStackStorage.getStore() ?? [];
|
|
3316
|
+
}
|
|
3317
|
+
function isInsidePetbooqzCalendarBookingMutex() {
|
|
3318
|
+
return getActiveQueuedOperationStack().some(
|
|
3319
|
+
(operation) => operation.resolved.name === "petbooqz_calendar_booking" && operation.lease !== null
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3308
3322
|
function setActiveQueuedOperationLease(lease) {
|
|
3309
3323
|
const op = activeOperationStorage.getStore();
|
|
3310
3324
|
if (op) {
|
|
@@ -3512,6 +3526,26 @@ function createContextLogger() {
|
|
|
3512
3526
|
}
|
|
3513
3527
|
|
|
3514
3528
|
// src/server/tool-handler.ts
|
|
3529
|
+
function parsePreAcquiredLeases(raw) {
|
|
3530
|
+
if (!raw) {
|
|
3531
|
+
return void 0;
|
|
3532
|
+
}
|
|
3533
|
+
try {
|
|
3534
|
+
const parsed = JSON.parse(raw);
|
|
3535
|
+
if (!Array.isArray(parsed)) {
|
|
3536
|
+
return void 0;
|
|
3537
|
+
}
|
|
3538
|
+
const leases = parsed.filter(
|
|
3539
|
+
(item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.leaseId === "string"
|
|
3540
|
+
).map((item) => ({
|
|
3541
|
+
queueKey: item.queueKey,
|
|
3542
|
+
leaseId: item.leaseId
|
|
3543
|
+
}));
|
|
3544
|
+
return leases.length > 0 ? leases : void 0;
|
|
3545
|
+
} catch {
|
|
3546
|
+
return void 0;
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3515
3549
|
function buildToolMetadata(registry) {
|
|
3516
3550
|
return Object.values(registry).map((tool) => {
|
|
3517
3551
|
const toolConfig = tool.config ?? {};
|
|
@@ -3644,7 +3678,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
3644
3678
|
app,
|
|
3645
3679
|
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
3646
3680
|
invocation,
|
|
3647
|
-
isProvisionContext: trigger === "provision"
|
|
3681
|
+
isProvisionContext: trigger === "provision",
|
|
3682
|
+
preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
|
|
3648
3683
|
};
|
|
3649
3684
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
3650
3685
|
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
@@ -5484,18 +5519,6 @@ var CONFIG_FILE_NAMES = [
|
|
|
5484
5519
|
"skedyul.config.mjs",
|
|
5485
5520
|
"skedyul.config.cjs"
|
|
5486
5521
|
];
|
|
5487
|
-
function loadTypeScriptConfigModule(absolutePath) {
|
|
5488
|
-
try {
|
|
5489
|
-
__require("tsx/cjs");
|
|
5490
|
-
delete __require.cache[absolutePath];
|
|
5491
|
-
const module = __require(absolutePath);
|
|
5492
|
-
return module.default ?? module;
|
|
5493
|
-
} catch (error) {
|
|
5494
|
-
throw new Error(
|
|
5495
|
-
`Cannot load TypeScript config: ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
5496
|
-
);
|
|
5497
|
-
}
|
|
5498
|
-
}
|
|
5499
5522
|
async function transpileTypeScript(filePath) {
|
|
5500
5523
|
const content = fs3.readFileSync(filePath, "utf-8");
|
|
5501
5524
|
const configDir = path3.dirname(path3.resolve(filePath));
|
|
@@ -5527,15 +5550,6 @@ async function loadConfig(configPath) {
|
|
|
5527
5550
|
try {
|
|
5528
5551
|
if (isTypeScript) {
|
|
5529
5552
|
try {
|
|
5530
|
-
const config2 = loadTypeScriptConfigModule(absolutePath);
|
|
5531
|
-
if (!config2 || typeof config2 !== "object") {
|
|
5532
|
-
throw new Error("Config file must export a configuration object");
|
|
5533
|
-
}
|
|
5534
|
-
if (!config2.name || typeof config2.name !== "string") {
|
|
5535
|
-
throw new Error('Config must have a "name" property');
|
|
5536
|
-
}
|
|
5537
|
-
return config2;
|
|
5538
|
-
} catch (tsxError) {
|
|
5539
5553
|
const transpiled = await transpileTypeScript(absolutePath);
|
|
5540
5554
|
const tempFile = path3.join(os.tmpdir(), `skedyul-config-${Date.now()}.cjs`);
|
|
5541
5555
|
fs3.writeFileSync(tempFile, transpiled);
|
|
@@ -5555,6 +5569,10 @@ async function loadConfig(configPath) {
|
|
|
5555
5569
|
} catch {
|
|
5556
5570
|
}
|
|
5557
5571
|
}
|
|
5572
|
+
} catch (transpileError) {
|
|
5573
|
+
throw new Error(
|
|
5574
|
+
`Cannot load TypeScript config metadata from ${absolutePath}: ${transpileError instanceof Error ? transpileError.message : String(transpileError)}`
|
|
5575
|
+
);
|
|
5558
5576
|
}
|
|
5559
5577
|
}
|
|
5560
5578
|
const module = await import(
|
|
@@ -5723,11 +5741,12 @@ async function callRateLimitApi(path6, body) {
|
|
|
5723
5741
|
return payload.data;
|
|
5724
5742
|
}
|
|
5725
5743
|
var PlatformRateLimitBackend = class {
|
|
5726
|
-
async acquire(queueKey, limits, timeoutMs) {
|
|
5744
|
+
async acquire(queueKey, limits, timeoutMs, executionHoldMs) {
|
|
5727
5745
|
const data = await callRateLimitApi("acquire", {
|
|
5728
5746
|
queueKey,
|
|
5729
5747
|
limits,
|
|
5730
|
-
timeoutMs
|
|
5748
|
+
timeoutMs,
|
|
5749
|
+
executionHoldMs
|
|
5731
5750
|
});
|
|
5732
5751
|
return {
|
|
5733
5752
|
leaseId: data.leaseId,
|
|
@@ -5737,7 +5756,8 @@ var PlatformRateLimitBackend = class {
|
|
|
5737
5756
|
}
|
|
5738
5757
|
async release(lease) {
|
|
5739
5758
|
await callRateLimitApi("release", {
|
|
5740
|
-
leaseId: lease.leaseId
|
|
5759
|
+
leaseId: lease.leaseId,
|
|
5760
|
+
queueKey: lease.queueKey
|
|
5741
5761
|
});
|
|
5742
5762
|
}
|
|
5743
5763
|
};
|
|
@@ -6048,17 +6068,52 @@ async function queuedFetch(queueInput, fnOrPromise) {
|
|
|
6048
6068
|
() => executeWithRetries(operation, maxRetries)
|
|
6049
6069
|
);
|
|
6050
6070
|
}
|
|
6071
|
+
function findPreAcquiredLease(queueKey, ctx) {
|
|
6072
|
+
return ctx?.preAcquiredLeases?.find((lease) => lease.queueKey === queueKey);
|
|
6073
|
+
}
|
|
6074
|
+
function shouldRethrowRateLimitCause(maxRetries, error, attempt, shouldRetryFn) {
|
|
6075
|
+
if (maxRetries !== 0 || !shouldRetryFn(error, attempt)) {
|
|
6076
|
+
return false;
|
|
6077
|
+
}
|
|
6078
|
+
return error instanceof RateLimitExceededError || error instanceof RateLimitBackendError;
|
|
6079
|
+
}
|
|
6051
6080
|
async function executeWithRetries(operation, maxRetries) {
|
|
6052
6081
|
const backend = getRateLimitBackend();
|
|
6053
6082
|
const timeoutMs = operation.resolved.config.timeout ?? 12e4;
|
|
6083
|
+
const executionHoldMs = operation.resolved.config.timeout ?? timeoutMs;
|
|
6054
6084
|
const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
|
|
6055
6085
|
const shouldRetryFn = getQueueConfigWithRetry(operation.resolved.name)?.shouldRetry ?? defaultShouldRetry;
|
|
6086
|
+
const rateLimitCtx = getRateLimitExecutionContext();
|
|
6087
|
+
const preAcquired = findPreAcquiredLease(
|
|
6088
|
+
operation.resolved.queueKey,
|
|
6089
|
+
rateLimitCtx
|
|
6090
|
+
);
|
|
6091
|
+
if (operation.resolved.name === "petbooqz_api" && isInsidePetbooqzCalendarBookingMutex()) {
|
|
6092
|
+
return operation.fn();
|
|
6093
|
+
}
|
|
6094
|
+
if (preAcquired) {
|
|
6095
|
+
try {
|
|
6096
|
+
return await operation.fn();
|
|
6097
|
+
} catch (error) {
|
|
6098
|
+
if (shouldRethrowRateLimitCause(maxRetries, error, operation.attempt, shouldRetryFn)) {
|
|
6099
|
+
throw error;
|
|
6100
|
+
}
|
|
6101
|
+
if (shouldRetryFn(error, operation.attempt) && operation.attempt < maxRetries) {
|
|
6102
|
+
await sleep(retryDelayMs);
|
|
6103
|
+
operation.attempt += 1;
|
|
6104
|
+
updateActiveQueuedOperationAttempt(operation.attempt);
|
|
6105
|
+
return executeWithRetries(operation, maxRetries);
|
|
6106
|
+
}
|
|
6107
|
+
throw error;
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6056
6110
|
let lease;
|
|
6057
6111
|
try {
|
|
6058
6112
|
lease = await backend.acquire(
|
|
6059
6113
|
operation.resolved.queueKey,
|
|
6060
6114
|
operation.resolved.limits,
|
|
6061
|
-
timeoutMs
|
|
6115
|
+
timeoutMs,
|
|
6116
|
+
executionHoldMs
|
|
6062
6117
|
);
|
|
6063
6118
|
} catch (acquireError) {
|
|
6064
6119
|
if (acquireError instanceof RateLimitBackendError && acquireError.statusCode === 408) {
|
|
@@ -6085,6 +6140,9 @@ async function executeWithRetries(operation, maxRetries) {
|
|
|
6085
6140
|
await backend.release(lease);
|
|
6086
6141
|
operation.lease = null;
|
|
6087
6142
|
setActiveQueuedOperationLease(null);
|
|
6143
|
+
if (shouldRethrowRateLimitCause(maxRetries, error, operation.attempt, shouldRetryFn)) {
|
|
6144
|
+
throw error;
|
|
6145
|
+
}
|
|
6088
6146
|
if (shouldRetryFn(error, operation.attempt) && operation.attempt < maxRetries) {
|
|
6089
6147
|
await sleep(retryDelayMs);
|
|
6090
6148
|
operation.attempt += 1;
|
package/dist/index.js
CHANGED
|
@@ -3621,6 +3621,7 @@ var report = {
|
|
|
3621
3621
|
var import_async_hooks2 = require("async_hooks");
|
|
3622
3622
|
var executionContextStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
3623
3623
|
var activeOperationStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
3624
|
+
var activeOperationStackStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
3624
3625
|
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
3625
3626
|
return executionContextStorage.run(ctx, fn);
|
|
3626
3627
|
}
|
|
@@ -3628,11 +3629,24 @@ function getRateLimitExecutionContext() {
|
|
|
3628
3629
|
return executionContextStorage.getStore();
|
|
3629
3630
|
}
|
|
3630
3631
|
function runWithActiveQueuedOperation(operation, fn) {
|
|
3631
|
-
|
|
3632
|
+
const parentStack = activeOperationStackStorage.getStore() ?? [];
|
|
3633
|
+
const stack = [...parentStack, operation];
|
|
3634
|
+
return activeOperationStorage.run(
|
|
3635
|
+
operation,
|
|
3636
|
+
() => activeOperationStackStorage.run(stack, fn)
|
|
3637
|
+
);
|
|
3632
3638
|
}
|
|
3633
3639
|
function getActiveQueuedOperation() {
|
|
3634
3640
|
return activeOperationStorage.getStore();
|
|
3635
3641
|
}
|
|
3642
|
+
function getActiveQueuedOperationStack() {
|
|
3643
|
+
return activeOperationStackStorage.getStore() ?? [];
|
|
3644
|
+
}
|
|
3645
|
+
function isInsidePetbooqzCalendarBookingMutex() {
|
|
3646
|
+
return getActiveQueuedOperationStack().some(
|
|
3647
|
+
(operation) => operation.resolved.name === "petbooqz_calendar_booking" && operation.lease !== null
|
|
3648
|
+
);
|
|
3649
|
+
}
|
|
3636
3650
|
function setActiveQueuedOperationLease(lease) {
|
|
3637
3651
|
const op = activeOperationStorage.getStore();
|
|
3638
3652
|
if (op) {
|
|
@@ -3840,6 +3854,26 @@ function createContextLogger() {
|
|
|
3840
3854
|
}
|
|
3841
3855
|
|
|
3842
3856
|
// src/server/tool-handler.ts
|
|
3857
|
+
function parsePreAcquiredLeases(raw) {
|
|
3858
|
+
if (!raw) {
|
|
3859
|
+
return void 0;
|
|
3860
|
+
}
|
|
3861
|
+
try {
|
|
3862
|
+
const parsed = JSON.parse(raw);
|
|
3863
|
+
if (!Array.isArray(parsed)) {
|
|
3864
|
+
return void 0;
|
|
3865
|
+
}
|
|
3866
|
+
const leases = parsed.filter(
|
|
3867
|
+
(item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.leaseId === "string"
|
|
3868
|
+
).map((item) => ({
|
|
3869
|
+
queueKey: item.queueKey,
|
|
3870
|
+
leaseId: item.leaseId
|
|
3871
|
+
}));
|
|
3872
|
+
return leases.length > 0 ? leases : void 0;
|
|
3873
|
+
} catch {
|
|
3874
|
+
return void 0;
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3843
3877
|
function buildToolMetadata(registry) {
|
|
3844
3878
|
return Object.values(registry).map((tool) => {
|
|
3845
3879
|
const toolConfig = tool.config ?? {};
|
|
@@ -3972,7 +4006,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
3972
4006
|
app,
|
|
3973
4007
|
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
3974
4008
|
invocation,
|
|
3975
|
-
isProvisionContext: trigger === "provision"
|
|
4009
|
+
isProvisionContext: trigger === "provision",
|
|
4010
|
+
preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
|
|
3976
4011
|
};
|
|
3977
4012
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
3978
4013
|
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
@@ -5812,18 +5847,6 @@ var CONFIG_FILE_NAMES = [
|
|
|
5812
5847
|
"skedyul.config.mjs",
|
|
5813
5848
|
"skedyul.config.cjs"
|
|
5814
5849
|
];
|
|
5815
|
-
function loadTypeScriptConfigModule(absolutePath) {
|
|
5816
|
-
try {
|
|
5817
|
-
require("tsx/cjs");
|
|
5818
|
-
delete require.cache[absolutePath];
|
|
5819
|
-
const module2 = require(absolutePath);
|
|
5820
|
-
return module2.default ?? module2;
|
|
5821
|
-
} catch (error) {
|
|
5822
|
-
throw new Error(
|
|
5823
|
-
`Cannot load TypeScript config: ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
5824
|
-
);
|
|
5825
|
-
}
|
|
5826
|
-
}
|
|
5827
5850
|
async function transpileTypeScript(filePath) {
|
|
5828
5851
|
const content = fs3.readFileSync(filePath, "utf-8");
|
|
5829
5852
|
const configDir = path3.dirname(path3.resolve(filePath));
|
|
@@ -5855,15 +5878,6 @@ async function loadConfig(configPath) {
|
|
|
5855
5878
|
try {
|
|
5856
5879
|
if (isTypeScript) {
|
|
5857
5880
|
try {
|
|
5858
|
-
const config2 = loadTypeScriptConfigModule(absolutePath);
|
|
5859
|
-
if (!config2 || typeof config2 !== "object") {
|
|
5860
|
-
throw new Error("Config file must export a configuration object");
|
|
5861
|
-
}
|
|
5862
|
-
if (!config2.name || typeof config2.name !== "string") {
|
|
5863
|
-
throw new Error('Config must have a "name" property');
|
|
5864
|
-
}
|
|
5865
|
-
return config2;
|
|
5866
|
-
} catch (tsxError) {
|
|
5867
5881
|
const transpiled = await transpileTypeScript(absolutePath);
|
|
5868
5882
|
const tempFile = path3.join(os.tmpdir(), `skedyul-config-${Date.now()}.cjs`);
|
|
5869
5883
|
fs3.writeFileSync(tempFile, transpiled);
|
|
@@ -5883,6 +5897,10 @@ async function loadConfig(configPath) {
|
|
|
5883
5897
|
} catch {
|
|
5884
5898
|
}
|
|
5885
5899
|
}
|
|
5900
|
+
} catch (transpileError) {
|
|
5901
|
+
throw new Error(
|
|
5902
|
+
`Cannot load TypeScript config metadata from ${absolutePath}: ${transpileError instanceof Error ? transpileError.message : String(transpileError)}`
|
|
5903
|
+
);
|
|
5886
5904
|
}
|
|
5887
5905
|
}
|
|
5888
5906
|
const module2 = await import(
|
|
@@ -6051,11 +6069,12 @@ async function callRateLimitApi(path6, body) {
|
|
|
6051
6069
|
return payload.data;
|
|
6052
6070
|
}
|
|
6053
6071
|
var PlatformRateLimitBackend = class {
|
|
6054
|
-
async acquire(queueKey, limits, timeoutMs) {
|
|
6072
|
+
async acquire(queueKey, limits, timeoutMs, executionHoldMs) {
|
|
6055
6073
|
const data = await callRateLimitApi("acquire", {
|
|
6056
6074
|
queueKey,
|
|
6057
6075
|
limits,
|
|
6058
|
-
timeoutMs
|
|
6076
|
+
timeoutMs,
|
|
6077
|
+
executionHoldMs
|
|
6059
6078
|
});
|
|
6060
6079
|
return {
|
|
6061
6080
|
leaseId: data.leaseId,
|
|
@@ -6065,7 +6084,8 @@ var PlatformRateLimitBackend = class {
|
|
|
6065
6084
|
}
|
|
6066
6085
|
async release(lease) {
|
|
6067
6086
|
await callRateLimitApi("release", {
|
|
6068
|
-
leaseId: lease.leaseId
|
|
6087
|
+
leaseId: lease.leaseId,
|
|
6088
|
+
queueKey: lease.queueKey
|
|
6069
6089
|
});
|
|
6070
6090
|
}
|
|
6071
6091
|
};
|
|
@@ -6376,17 +6396,52 @@ async function queuedFetch(queueInput, fnOrPromise) {
|
|
|
6376
6396
|
() => executeWithRetries(operation, maxRetries)
|
|
6377
6397
|
);
|
|
6378
6398
|
}
|
|
6399
|
+
function findPreAcquiredLease(queueKey, ctx) {
|
|
6400
|
+
return ctx?.preAcquiredLeases?.find((lease) => lease.queueKey === queueKey);
|
|
6401
|
+
}
|
|
6402
|
+
function shouldRethrowRateLimitCause(maxRetries, error, attempt, shouldRetryFn) {
|
|
6403
|
+
if (maxRetries !== 0 || !shouldRetryFn(error, attempt)) {
|
|
6404
|
+
return false;
|
|
6405
|
+
}
|
|
6406
|
+
return error instanceof RateLimitExceededError || error instanceof RateLimitBackendError;
|
|
6407
|
+
}
|
|
6379
6408
|
async function executeWithRetries(operation, maxRetries) {
|
|
6380
6409
|
const backend = getRateLimitBackend();
|
|
6381
6410
|
const timeoutMs = operation.resolved.config.timeout ?? 12e4;
|
|
6411
|
+
const executionHoldMs = operation.resolved.config.timeout ?? timeoutMs;
|
|
6382
6412
|
const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
|
|
6383
6413
|
const shouldRetryFn = getQueueConfigWithRetry(operation.resolved.name)?.shouldRetry ?? defaultShouldRetry;
|
|
6414
|
+
const rateLimitCtx = getRateLimitExecutionContext();
|
|
6415
|
+
const preAcquired = findPreAcquiredLease(
|
|
6416
|
+
operation.resolved.queueKey,
|
|
6417
|
+
rateLimitCtx
|
|
6418
|
+
);
|
|
6419
|
+
if (operation.resolved.name === "petbooqz_api" && isInsidePetbooqzCalendarBookingMutex()) {
|
|
6420
|
+
return operation.fn();
|
|
6421
|
+
}
|
|
6422
|
+
if (preAcquired) {
|
|
6423
|
+
try {
|
|
6424
|
+
return await operation.fn();
|
|
6425
|
+
} catch (error) {
|
|
6426
|
+
if (shouldRethrowRateLimitCause(maxRetries, error, operation.attempt, shouldRetryFn)) {
|
|
6427
|
+
throw error;
|
|
6428
|
+
}
|
|
6429
|
+
if (shouldRetryFn(error, operation.attempt) && operation.attempt < maxRetries) {
|
|
6430
|
+
await sleep(retryDelayMs);
|
|
6431
|
+
operation.attempt += 1;
|
|
6432
|
+
updateActiveQueuedOperationAttempt(operation.attempt);
|
|
6433
|
+
return executeWithRetries(operation, maxRetries);
|
|
6434
|
+
}
|
|
6435
|
+
throw error;
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6384
6438
|
let lease;
|
|
6385
6439
|
try {
|
|
6386
6440
|
lease = await backend.acquire(
|
|
6387
6441
|
operation.resolved.queueKey,
|
|
6388
6442
|
operation.resolved.limits,
|
|
6389
|
-
timeoutMs
|
|
6443
|
+
timeoutMs,
|
|
6444
|
+
executionHoldMs
|
|
6390
6445
|
);
|
|
6391
6446
|
} catch (acquireError) {
|
|
6392
6447
|
if (acquireError instanceof RateLimitBackendError && acquireError.statusCode === 408) {
|
|
@@ -6413,6 +6468,9 @@ async function executeWithRetries(operation, maxRetries) {
|
|
|
6413
6468
|
await backend.release(lease);
|
|
6414
6469
|
operation.lease = null;
|
|
6415
6470
|
setActiveQueuedOperationLease(null);
|
|
6471
|
+
if (shouldRethrowRateLimitCause(maxRetries, error, operation.attempt, shouldRetryFn)) {
|
|
6472
|
+
throw error;
|
|
6473
|
+
}
|
|
6416
6474
|
if (shouldRetryFn(error, operation.attempt) && operation.attempt < maxRetries) {
|
|
6417
6475
|
await sleep(retryDelayMs);
|
|
6418
6476
|
operation.attempt += 1;
|
|
@@ -3,7 +3,7 @@ import type { RateLimitBackend, Lease, QueueLimits } from '../types';
|
|
|
3
3
|
* HTTP backend that delegates acquire/release to the Skedyul platform proxy.
|
|
4
4
|
*/
|
|
5
5
|
export declare class PlatformRateLimitBackend implements RateLimitBackend {
|
|
6
|
-
acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number): Promise<Lease>;
|
|
6
|
+
acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number, executionHoldMs?: number): Promise<Lease>;
|
|
7
7
|
release(lease: Lease): Promise<void>;
|
|
8
8
|
}
|
|
9
9
|
export declare const platformRateLimitBackend: PlatformRateLimitBackend;
|
|
@@ -3,5 +3,8 @@ export declare function runWithRateLimitExecutionContext<T>(ctx: RateLimitExecut
|
|
|
3
3
|
export declare function getRateLimitExecutionContext(): RateLimitExecutionContext | undefined;
|
|
4
4
|
export declare function runWithActiveQueuedOperation<T>(operation: ActiveQueuedOperation<T>, fn: () => Promise<T>): Promise<T>;
|
|
5
5
|
export declare function getActiveQueuedOperation<T>(): ActiveQueuedOperation<T> | undefined;
|
|
6
|
+
export declare function getActiveQueuedOperationStack(): ActiveQueuedOperation<unknown>[];
|
|
7
|
+
/** True when petbooqz_calendar_booking holds a lease in an outer queuedFetch. */
|
|
8
|
+
export declare function isInsidePetbooqzCalendarBookingMutex(): boolean;
|
|
6
9
|
export declare function setActiveQueuedOperationLease(lease: ActiveQueuedOperation<unknown>['lease']): void;
|
|
7
10
|
export declare function updateActiveQueuedOperationAttempt(attempt: number): void;
|
|
@@ -6,6 +6,11 @@ export interface RateLimitExecutionContext {
|
|
|
6
6
|
appInstallationId?: string;
|
|
7
7
|
invocation?: InvocationContext;
|
|
8
8
|
isProvisionContext?: boolean;
|
|
9
|
+
/** Leases acquired by workflow orchestration — queuedFetch skips acquire/release for matching keys */
|
|
10
|
+
preAcquiredLeases?: Array<{
|
|
11
|
+
queueKey: string;
|
|
12
|
+
leaseId: string;
|
|
13
|
+
}>;
|
|
9
14
|
}
|
|
10
15
|
export interface QueueSelector {
|
|
11
16
|
queue: string;
|
|
@@ -31,7 +36,7 @@ export interface ResolvedQueue {
|
|
|
31
36
|
limits: QueueLimits;
|
|
32
37
|
}
|
|
33
38
|
export interface RateLimitBackend {
|
|
34
|
-
acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number): Promise<Lease>;
|
|
39
|
+
acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number, executionHoldMs?: number): Promise<Lease>;
|
|
35
40
|
release(lease: Lease): Promise<void>;
|
|
36
41
|
}
|
|
37
42
|
export interface ActiveQueuedOperation<T> {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { ToolCallResponse, ToolMetadata, ToolRegistry } from '../types';
|
|
2
2
|
import type { RequestState } from './types';
|
|
3
|
+
export declare function parsePreAcquiredLeases(raw: string | undefined): Array<{
|
|
4
|
+
queueKey: string;
|
|
5
|
+
leaseId: string;
|
|
6
|
+
}> | undefined;
|
|
3
7
|
/**
|
|
4
8
|
* Builds tool metadata array from a tool registry
|
|
5
9
|
*/
|
package/dist/server.js
CHANGED
|
@@ -285,6 +285,7 @@ function runWithConfig(config, fn) {
|
|
|
285
285
|
var import_async_hooks2 = require("async_hooks");
|
|
286
286
|
var executionContextStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
287
287
|
var activeOperationStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
288
|
+
var activeOperationStackStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
288
289
|
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
289
290
|
return executionContextStorage.run(ctx, fn);
|
|
290
291
|
}
|
|
@@ -407,6 +408,26 @@ function createContextLogger() {
|
|
|
407
408
|
}
|
|
408
409
|
|
|
409
410
|
// src/server/tool-handler.ts
|
|
411
|
+
function parsePreAcquiredLeases(raw) {
|
|
412
|
+
if (!raw) {
|
|
413
|
+
return void 0;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
const parsed = JSON.parse(raw);
|
|
417
|
+
if (!Array.isArray(parsed)) {
|
|
418
|
+
return void 0;
|
|
419
|
+
}
|
|
420
|
+
const leases = parsed.filter(
|
|
421
|
+
(item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.leaseId === "string"
|
|
422
|
+
).map((item) => ({
|
|
423
|
+
queueKey: item.queueKey,
|
|
424
|
+
leaseId: item.leaseId
|
|
425
|
+
}));
|
|
426
|
+
return leases.length > 0 ? leases : void 0;
|
|
427
|
+
} catch {
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
410
431
|
function buildToolMetadata(registry) {
|
|
411
432
|
return Object.values(registry).map((tool) => {
|
|
412
433
|
const toolConfig = tool.config ?? {};
|
|
@@ -539,7 +560,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
539
560
|
app,
|
|
540
561
|
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
541
562
|
invocation,
|
|
542
|
-
isProvisionContext: trigger === "provision"
|
|
563
|
+
isProvisionContext: trigger === "provision",
|
|
564
|
+
preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
|
|
543
565
|
};
|
|
544
566
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
545
567
|
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
@@ -246,6 +246,7 @@ function runWithConfig(config, fn) {
|
|
|
246
246
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
247
247
|
var executionContextStorage = new AsyncLocalStorage2();
|
|
248
248
|
var activeOperationStorage = new AsyncLocalStorage2();
|
|
249
|
+
var activeOperationStackStorage = new AsyncLocalStorage2();
|
|
249
250
|
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
250
251
|
return executionContextStorage.run(ctx, fn);
|
|
251
252
|
}
|
|
@@ -368,6 +369,26 @@ function createContextLogger() {
|
|
|
368
369
|
}
|
|
369
370
|
|
|
370
371
|
// src/server/tool-handler.ts
|
|
372
|
+
function parsePreAcquiredLeases(raw) {
|
|
373
|
+
if (!raw) {
|
|
374
|
+
return void 0;
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
const parsed = JSON.parse(raw);
|
|
378
|
+
if (!Array.isArray(parsed)) {
|
|
379
|
+
return void 0;
|
|
380
|
+
}
|
|
381
|
+
const leases = parsed.filter(
|
|
382
|
+
(item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.leaseId === "string"
|
|
383
|
+
).map((item) => ({
|
|
384
|
+
queueKey: item.queueKey,
|
|
385
|
+
leaseId: item.leaseId
|
|
386
|
+
}));
|
|
387
|
+
return leases.length > 0 ? leases : void 0;
|
|
388
|
+
} catch {
|
|
389
|
+
return void 0;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
371
392
|
function buildToolMetadata(registry) {
|
|
372
393
|
return Object.values(registry).map((tool) => {
|
|
373
394
|
const toolConfig = tool.config ?? {};
|
|
@@ -500,7 +521,8 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
500
521
|
app,
|
|
501
522
|
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
502
523
|
invocation,
|
|
503
|
-
isProvisionContext: trigger === "provision"
|
|
524
|
+
isProvisionContext: trigger === "provision",
|
|
525
|
+
preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
|
|
504
526
|
};
|
|
505
527
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
506
528
|
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|