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 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,
@@ -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 () => {
@@ -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
- return activeOperationStorage.run(operation, fn);
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
- return activeOperationStorage.run(operation, fn);
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 () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skedyul",
3
- "version": "1.4.8",
3
+ "version": "1.4.11",
4
4
  "description": "The Skedyul SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",