skedyul 1.7.3 → 1.7.6

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/.build-stamp CHANGED
@@ -1 +1 @@
1
- 1785796289915
1
+ 1785913911217
package/dist/cli/index.js CHANGED
@@ -3111,6 +3111,26 @@ function parsePreAcquiredLeases(raw) {
3111
3111
  return void 0;
3112
3112
  }
3113
3113
  }
3114
+ function parseHeldMutexQueueKeys(raw) {
3115
+ if (!raw) {
3116
+ return void 0;
3117
+ }
3118
+ try {
3119
+ const parsed = JSON.parse(raw);
3120
+ if (!Array.isArray(parsed)) {
3121
+ return void 0;
3122
+ }
3123
+ const keys = parsed.filter(
3124
+ (item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.queueName === "string"
3125
+ ).map((item) => ({
3126
+ queueKey: item.queueKey,
3127
+ queueName: item.queueName
3128
+ }));
3129
+ return keys.length > 0 ? keys : void 0;
3130
+ } catch {
3131
+ return void 0;
3132
+ }
3133
+ }
3114
3134
  function buildToolMetadata(registry) {
3115
3135
  return Object.values(registry).map((tool) => {
3116
3136
  const toolConfig = tool.config ?? {};
@@ -3248,7 +3268,10 @@ function createCallToolHandler(registry, state, onMaxRequests) {
3248
3268
  appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
3249
3269
  invocation,
3250
3270
  isProvisionContext: trigger === "provision",
3251
- preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
3271
+ preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES),
3272
+ heldMutexQueueKeys: parseHeldMutexQueueKeys(
3273
+ toolEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
3274
+ )
3252
3275
  };
3253
3276
  const functionResult = await runWithConfig(requestConfig, async () => {
3254
3277
  return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
@@ -441,6 +441,26 @@ function parsePreAcquiredLeases(raw) {
441
441
  return void 0;
442
442
  }
443
443
  }
444
+ function parseHeldMutexQueueKeys(raw) {
445
+ if (!raw) {
446
+ return void 0;
447
+ }
448
+ try {
449
+ const parsed = JSON.parse(raw);
450
+ if (!Array.isArray(parsed)) {
451
+ return void 0;
452
+ }
453
+ const keys = parsed.filter(
454
+ (item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.queueName === "string"
455
+ ).map((item) => ({
456
+ queueKey: item.queueKey,
457
+ queueName: item.queueName
458
+ }));
459
+ return keys.length > 0 ? keys : void 0;
460
+ } catch {
461
+ return void 0;
462
+ }
463
+ }
444
464
  function buildToolMetadata(registry) {
445
465
  return Object.values(registry).map((tool) => {
446
466
  const toolConfig = tool.config ?? {};
@@ -578,7 +598,10 @@ function createCallToolHandler(registry, state, onMaxRequests) {
578
598
  appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
579
599
  invocation,
580
600
  isProvisionContext: trigger === "provision",
581
- preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
601
+ preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES),
602
+ heldMutexQueueKeys: parseHeldMutexQueueKeys(
603
+ toolEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
604
+ )
582
605
  };
583
606
  const functionResult = await runWithConfig(requestConfig, async () => {
584
607
  return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
@@ -3649,6 +3649,14 @@ function getActiveMutexQueueNames() {
3649
3649
  names.add(operation.resolved.name);
3650
3650
  }
3651
3651
  }
3652
+ const held = getRateLimitExecutionContext()?.heldMutexQueueKeys;
3653
+ if (held?.length) {
3654
+ for (const entry of held) {
3655
+ if (entry.queueName) {
3656
+ names.add(entry.queueName);
3657
+ }
3658
+ }
3659
+ }
3652
3660
  return [...names];
3653
3661
  }
3654
3662
  function shouldSkipNestedAcquire(queueName) {
@@ -3887,6 +3895,26 @@ function parsePreAcquiredLeases(raw) {
3887
3895
  return void 0;
3888
3896
  }
3889
3897
  }
3898
+ function parseHeldMutexQueueKeys(raw) {
3899
+ if (!raw) {
3900
+ return void 0;
3901
+ }
3902
+ try {
3903
+ const parsed = JSON.parse(raw);
3904
+ if (!Array.isArray(parsed)) {
3905
+ return void 0;
3906
+ }
3907
+ const keys = parsed.filter(
3908
+ (item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.queueName === "string"
3909
+ ).map((item) => ({
3910
+ queueKey: item.queueKey,
3911
+ queueName: item.queueName
3912
+ }));
3913
+ return keys.length > 0 ? keys : void 0;
3914
+ } catch {
3915
+ return void 0;
3916
+ }
3917
+ }
3890
3918
  function buildToolMetadata(registry) {
3891
3919
  return Object.values(registry).map((tool) => {
3892
3920
  const toolConfig = tool.config ?? {};
@@ -4024,7 +4052,10 @@ function createCallToolHandler(registry, state, onMaxRequests) {
4024
4052
  appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
4025
4053
  invocation,
4026
4054
  isProvisionContext: trigger === "provision",
4027
- preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
4055
+ preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES),
4056
+ heldMutexQueueKeys: parseHeldMutexQueueKeys(
4057
+ toolEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
4058
+ )
4028
4059
  };
4029
4060
  const functionResult = await runWithConfig(requestConfig, async () => {
4030
4061
  return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
@@ -6416,6 +6447,11 @@ async function queuedFetch(queueInput, fnOrPromise) {
6416
6447
  function findPreAcquiredLease(queueKey, ctx) {
6417
6448
  return ctx?.preAcquiredLeases?.find((lease) => lease.queueKey === queueKey);
6418
6449
  }
6450
+ function isHeldMutexQueueKey(queueKey, ctx) {
6451
+ return Boolean(
6452
+ ctx?.heldMutexQueueKeys?.some((entry) => entry.queueKey === queueKey)
6453
+ );
6454
+ }
6419
6455
  function shouldRethrowRateLimitCause(maxRetries, error, attempt, shouldRetryFn) {
6420
6456
  if (maxRetries !== 0 || !shouldRetryFn(error, attempt)) {
6421
6457
  return false;
@@ -6433,10 +6469,14 @@ async function executeWithRetries(operation, maxRetries) {
6433
6469
  operation.resolved.queueKey,
6434
6470
  rateLimitCtx
6435
6471
  );
6472
+ const heldByPlatform = isHeldMutexQueueKey(
6473
+ operation.resolved.queueKey,
6474
+ rateLimitCtx
6475
+ );
6436
6476
  if (shouldSkipNestedAcquire(operation.resolved.name)) {
6437
6477
  return operation.fn();
6438
6478
  }
6439
- if (preAcquired) {
6479
+ if (preAcquired || heldByPlatform) {
6440
6480
  try {
6441
6481
  return await operation.fn();
6442
6482
  } catch (error) {
@@ -7987,7 +8027,15 @@ var WaitInputRelativeSchema = z15.object({
7987
8027
  mode: z15.literal("relative"),
7988
8028
  amount: z15.number(),
7989
8029
  unit: WaitUnitSchema,
7990
- windows: z15.array(TimeWindowSlotSchema).optional()
8030
+ windows: z15.array(TimeWindowSlotSchema).optional(),
8031
+ /**
8032
+ * Optional ISO datetime to use as the base for the relative delay instead of
8033
+ * wall-clock `now`. Used for cadence chaining (schedule relative to a prior
8034
+ * message's scheduledAt). `waitTime` is still measured from wall-clock `now`.
8035
+ */
8036
+ from: z15.union([z15.string(), z15.date()]).optional().describe(
8037
+ "ISO datetime base for the relative delay (e.g. previous message scheduledAt)"
8038
+ )
7991
8039
  });
7992
8040
  var WaitInputAbsoluteSchema = z15.object({
7993
8041
  mode: z15.literal("absolute"),
@@ -8100,10 +8148,12 @@ function calculateWaitTime(step, now) {
8100
8148
  };
8101
8149
  }
8102
8150
  case "relative": {
8151
+ const baseTime = step.from != null ? new Date(step.from).getTime() : nowTime;
8152
+ const baseDate = new Date(baseTime);
8103
8153
  if (!step.amount && (!step.windows || step.windows.length === 0)) {
8104
8154
  return {
8105
- waitTime: 0,
8106
- scheduledAt: now
8155
+ waitTime: Math.max(0, baseTime - nowTime),
8156
+ scheduledAt: baseDate
8107
8157
  };
8108
8158
  }
8109
8159
  let relativeDelay = 0;
@@ -8142,17 +8192,18 @@ function calculateWaitTime(step, now) {
8142
8192
  }
8143
8193
  }
8144
8194
  if (!step.windows || step.windows.length === 0) {
8195
+ const scheduledAt = new Date(baseTime + relativeDelay);
8145
8196
  return {
8146
- waitTime: relativeDelay,
8147
- scheduledAt: new Date(nowTime + relativeDelay)
8197
+ waitTime: Math.max(0, scheduledAt.getTime() - nowTime),
8198
+ scheduledAt
8148
8199
  };
8149
8200
  }
8150
- const targetDate = new Date(nowTime + relativeDelay);
8201
+ const targetDate = new Date(baseTime + relativeDelay);
8151
8202
  for (const window of step.windows) {
8152
8203
  if (isTimeInWindowSlot(targetDate, window)) {
8153
8204
  return {
8154
- waitTime: relativeDelay,
8155
- scheduledAt: new Date(nowTime + relativeDelay)
8205
+ waitTime: Math.max(0, targetDate.getTime() - nowTime),
8206
+ scheduledAt: targetDate
8156
8207
  };
8157
8208
  }
8158
8209
  }
@@ -8197,8 +8248,8 @@ function calculateWaitTime(step, now) {
8197
8248
  msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
8198
8249
  }
8199
8250
  const windowStartTimeMs = targetDate.getTime() + msUntilWindowStart;
8200
- const sameLocalDayAsNow = getLocalDateKey(now, tz) === getLocalDateKey(targetDate, tz);
8201
- const windowScheduledTime = sameLocalDayAsNow ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
8251
+ const sameLocalDayAsBase = getLocalDateKey(baseDate, tz) === getLocalDateKey(targetDate, tz);
8252
+ const windowScheduledTime = sameLocalDayAsBase ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
8202
8253
  const waitFromNow = windowScheduledTime.getTime() - nowTime;
8203
8254
  if (waitFromNow < earliestWaitFromNow) {
8204
8255
  earliestWaitFromNow = waitFromNow;
package/dist/index.js CHANGED
@@ -4023,6 +4023,14 @@ function getActiveMutexQueueNames() {
4023
4023
  names.add(operation.resolved.name);
4024
4024
  }
4025
4025
  }
4026
+ const held = getRateLimitExecutionContext()?.heldMutexQueueKeys;
4027
+ if (held?.length) {
4028
+ for (const entry of held) {
4029
+ if (entry.queueName) {
4030
+ names.add(entry.queueName);
4031
+ }
4032
+ }
4033
+ }
4026
4034
  return [...names];
4027
4035
  }
4028
4036
  function shouldSkipNestedAcquire(queueName) {
@@ -4261,6 +4269,26 @@ function parsePreAcquiredLeases(raw) {
4261
4269
  return void 0;
4262
4270
  }
4263
4271
  }
4272
+ function parseHeldMutexQueueKeys(raw) {
4273
+ if (!raw) {
4274
+ return void 0;
4275
+ }
4276
+ try {
4277
+ const parsed = JSON.parse(raw);
4278
+ if (!Array.isArray(parsed)) {
4279
+ return void 0;
4280
+ }
4281
+ const keys = parsed.filter(
4282
+ (item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.queueName === "string"
4283
+ ).map((item) => ({
4284
+ queueKey: item.queueKey,
4285
+ queueName: item.queueName
4286
+ }));
4287
+ return keys.length > 0 ? keys : void 0;
4288
+ } catch {
4289
+ return void 0;
4290
+ }
4291
+ }
4264
4292
  function buildToolMetadata(registry) {
4265
4293
  return Object.values(registry).map((tool) => {
4266
4294
  const toolConfig = tool.config ?? {};
@@ -4398,7 +4426,10 @@ function createCallToolHandler(registry, state, onMaxRequests) {
4398
4426
  appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
4399
4427
  invocation,
4400
4428
  isProvisionContext: trigger === "provision",
4401
- preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
4429
+ preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES),
4430
+ heldMutexQueueKeys: parseHeldMutexQueueKeys(
4431
+ toolEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
4432
+ )
4402
4433
  };
4403
4434
  const functionResult = await runWithConfig(requestConfig, async () => {
4404
4435
  return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
@@ -6790,6 +6821,11 @@ async function queuedFetch(queueInput, fnOrPromise) {
6790
6821
  function findPreAcquiredLease(queueKey, ctx) {
6791
6822
  return ctx?.preAcquiredLeases?.find((lease) => lease.queueKey === queueKey);
6792
6823
  }
6824
+ function isHeldMutexQueueKey(queueKey, ctx) {
6825
+ return Boolean(
6826
+ ctx?.heldMutexQueueKeys?.some((entry) => entry.queueKey === queueKey)
6827
+ );
6828
+ }
6793
6829
  function shouldRethrowRateLimitCause(maxRetries, error, attempt, shouldRetryFn) {
6794
6830
  if (maxRetries !== 0 || !shouldRetryFn(error, attempt)) {
6795
6831
  return false;
@@ -6807,10 +6843,14 @@ async function executeWithRetries(operation, maxRetries) {
6807
6843
  operation.resolved.queueKey,
6808
6844
  rateLimitCtx
6809
6845
  );
6846
+ const heldByPlatform = isHeldMutexQueueKey(
6847
+ operation.resolved.queueKey,
6848
+ rateLimitCtx
6849
+ );
6810
6850
  if (shouldSkipNestedAcquire(operation.resolved.name)) {
6811
6851
  return operation.fn();
6812
6852
  }
6813
- if (preAcquired) {
6853
+ if (preAcquired || heldByPlatform) {
6814
6854
  try {
6815
6855
  return await operation.fn();
6816
6856
  } catch (error) {
@@ -8361,7 +8401,15 @@ var WaitInputRelativeSchema = import_v413.z.object({
8361
8401
  mode: import_v413.z.literal("relative"),
8362
8402
  amount: import_v413.z.number(),
8363
8403
  unit: WaitUnitSchema,
8364
- windows: import_v413.z.array(TimeWindowSlotSchema).optional()
8404
+ windows: import_v413.z.array(TimeWindowSlotSchema).optional(),
8405
+ /**
8406
+ * Optional ISO datetime to use as the base for the relative delay instead of
8407
+ * wall-clock `now`. Used for cadence chaining (schedule relative to a prior
8408
+ * message's scheduledAt). `waitTime` is still measured from wall-clock `now`.
8409
+ */
8410
+ from: import_v413.z.union([import_v413.z.string(), import_v413.z.date()]).optional().describe(
8411
+ "ISO datetime base for the relative delay (e.g. previous message scheduledAt)"
8412
+ )
8365
8413
  });
8366
8414
  var WaitInputAbsoluteSchema = import_v413.z.object({
8367
8415
  mode: import_v413.z.literal("absolute"),
@@ -8474,10 +8522,12 @@ function calculateWaitTime(step, now) {
8474
8522
  };
8475
8523
  }
8476
8524
  case "relative": {
8525
+ const baseTime = step.from != null ? new Date(step.from).getTime() : nowTime;
8526
+ const baseDate = new Date(baseTime);
8477
8527
  if (!step.amount && (!step.windows || step.windows.length === 0)) {
8478
8528
  return {
8479
- waitTime: 0,
8480
- scheduledAt: now
8529
+ waitTime: Math.max(0, baseTime - nowTime),
8530
+ scheduledAt: baseDate
8481
8531
  };
8482
8532
  }
8483
8533
  let relativeDelay = 0;
@@ -8516,17 +8566,18 @@ function calculateWaitTime(step, now) {
8516
8566
  }
8517
8567
  }
8518
8568
  if (!step.windows || step.windows.length === 0) {
8569
+ const scheduledAt = new Date(baseTime + relativeDelay);
8519
8570
  return {
8520
- waitTime: relativeDelay,
8521
- scheduledAt: new Date(nowTime + relativeDelay)
8571
+ waitTime: Math.max(0, scheduledAt.getTime() - nowTime),
8572
+ scheduledAt
8522
8573
  };
8523
8574
  }
8524
- const targetDate = new Date(nowTime + relativeDelay);
8575
+ const targetDate = new Date(baseTime + relativeDelay);
8525
8576
  for (const window of step.windows) {
8526
8577
  if (isTimeInWindowSlot(targetDate, window)) {
8527
8578
  return {
8528
- waitTime: relativeDelay,
8529
- scheduledAt: new Date(nowTime + relativeDelay)
8579
+ waitTime: Math.max(0, targetDate.getTime() - nowTime),
8580
+ scheduledAt: targetDate
8530
8581
  };
8531
8582
  }
8532
8583
  }
@@ -8571,8 +8622,8 @@ function calculateWaitTime(step, now) {
8571
8622
  msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
8572
8623
  }
8573
8624
  const windowStartTimeMs = targetDate.getTime() + msUntilWindowStart;
8574
- const sameLocalDayAsNow = getLocalDateKey(now, tz) === getLocalDateKey(targetDate, tz);
8575
- const windowScheduledTime = sameLocalDayAsNow ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
8625
+ const sameLocalDayAsBase = getLocalDateKey(baseDate, tz) === getLocalDateKey(targetDate, tz);
8626
+ const windowScheduledTime = sameLocalDayAsBase ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
8576
8627
  const waitFromNow = windowScheduledTime.getTime() - nowTime;
8577
8628
  if (waitFromNow < earliestWaitFromNow) {
8578
8629
  earliestWaitFromNow = waitFromNow;
@@ -4,7 +4,7 @@ export declare function getRateLimitExecutionContext(): RateLimitExecutionContex
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
6
  export declare function getActiveQueuedOperationStack(): ActiveQueuedOperation<unknown>[];
7
- /** Active mutex queue names holding a lease in an outer queuedFetch. */
7
+ /** Active mutex queue names holding a lease in an outer queuedFetch or platform-held. */
8
8
  export declare function getActiveMutexQueueNames(): string[];
9
9
  /** True when an active mutex queue suppresses acquire for the target queue name. */
10
10
  export declare function shouldSkipNestedAcquire(queueName: string): boolean;
@@ -1,6 +1,10 @@
1
1
  import type { SerializableQueueConfig } from '../config/queue-config';
2
2
  import type { AppInfo } from '../types/shared';
3
3
  import type { InvocationContext } from '../types/invocation';
4
+ export interface HeldMutexQueueKey {
5
+ queueKey: string;
6
+ queueName: string;
7
+ }
4
8
  export interface RateLimitExecutionContext {
5
9
  app: AppInfo;
6
10
  appInstallationId?: string;
@@ -11,6 +15,12 @@ export interface RateLimitExecutionContext {
11
15
  queueKey: string;
12
16
  leaseId: string;
13
17
  }>;
18
+ /**
19
+ * Mutex queue keys held by platform Temporal orchestration for this tool call.
20
+ * queuedFetch skips acquire/release for matching keys; queue names count as active mutexes
21
+ * for suppressesQueues.
22
+ */
23
+ heldMutexQueueKeys?: HeldMutexQueueKey[];
14
24
  }
15
25
  export interface QueueSelector {
16
26
  queue: string;
@@ -9,8 +9,10 @@
9
9
  import type { WaitInputType, CalculateWaitTimeResult } from './types-workflow';
10
10
  /**
11
11
  * Calculate the wait time for a wait step
12
- * @param step The input parameters for the wait step
13
- * @param now The current date/time
12
+ * @param step The input parameters for the wait step. Relative mode may include
13
+ * optional `from` (ISO datetime / Date) as the base for the delay instead of `now`.
14
+ * @param now Wall-clock current date/time. Used as the default relative base and
15
+ * always used to compute `waitTime` (ms until `scheduledAt`).
14
16
  * @returns The time to wait in milliseconds and the scheduled date
15
17
  */
16
18
  export declare function calculateWaitTime(step: WaitInputType, now: Date): CalculateWaitTimeResult;
@@ -128,10 +128,12 @@ function calculateWaitTime(step, now) {
128
128
  };
129
129
  }
130
130
  case "relative": {
131
+ const baseTime = step.from != null ? new Date(step.from).getTime() : nowTime;
132
+ const baseDate = new Date(baseTime);
131
133
  if (!step.amount && (!step.windows || step.windows.length === 0)) {
132
134
  return {
133
- waitTime: 0,
134
- scheduledAt: now
135
+ waitTime: Math.max(0, baseTime - nowTime),
136
+ scheduledAt: baseDate
135
137
  };
136
138
  }
137
139
  let relativeDelay = 0;
@@ -170,17 +172,18 @@ function calculateWaitTime(step, now) {
170
172
  }
171
173
  }
172
174
  if (!step.windows || step.windows.length === 0) {
175
+ const scheduledAt = new Date(baseTime + relativeDelay);
173
176
  return {
174
- waitTime: relativeDelay,
175
- scheduledAt: new Date(nowTime + relativeDelay)
177
+ waitTime: Math.max(0, scheduledAt.getTime() - nowTime),
178
+ scheduledAt
176
179
  };
177
180
  }
178
- const targetDate = new Date(nowTime + relativeDelay);
181
+ const targetDate = new Date(baseTime + relativeDelay);
179
182
  for (const window of step.windows) {
180
183
  if (isTimeInWindowSlot(targetDate, window)) {
181
184
  return {
182
- waitTime: relativeDelay,
183
- scheduledAt: new Date(nowTime + relativeDelay)
185
+ waitTime: Math.max(0, targetDate.getTime() - nowTime),
186
+ scheduledAt: targetDate
184
187
  };
185
188
  }
186
189
  }
@@ -225,8 +228,8 @@ function calculateWaitTime(step, now) {
225
228
  msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
226
229
  }
227
230
  const windowStartTimeMs = targetDate.getTime() + msUntilWindowStart;
228
- const sameLocalDayAsNow = getLocalDateKey(now, tz) === getLocalDateKey(targetDate, tz);
229
- const windowScheduledTime = sameLocalDayAsNow ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
231
+ const sameLocalDayAsBase = getLocalDateKey(baseDate, tz) === getLocalDateKey(targetDate, tz);
232
+ const windowScheduledTime = sameLocalDayAsBase ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
230
233
  const waitFromNow = windowScheduledTime.getTime() - nowTime;
231
234
  if (waitFromNow < earliestWaitFromNow) {
232
235
  earliestWaitFromNow = waitFromNow;
@@ -100,10 +100,12 @@ function calculateWaitTime(step, now) {
100
100
  };
101
101
  }
102
102
  case "relative": {
103
+ const baseTime = step.from != null ? new Date(step.from).getTime() : nowTime;
104
+ const baseDate = new Date(baseTime);
103
105
  if (!step.amount && (!step.windows || step.windows.length === 0)) {
104
106
  return {
105
- waitTime: 0,
106
- scheduledAt: now
107
+ waitTime: Math.max(0, baseTime - nowTime),
108
+ scheduledAt: baseDate
107
109
  };
108
110
  }
109
111
  let relativeDelay = 0;
@@ -142,17 +144,18 @@ function calculateWaitTime(step, now) {
142
144
  }
143
145
  }
144
146
  if (!step.windows || step.windows.length === 0) {
147
+ const scheduledAt = new Date(baseTime + relativeDelay);
145
148
  return {
146
- waitTime: relativeDelay,
147
- scheduledAt: new Date(nowTime + relativeDelay)
149
+ waitTime: Math.max(0, scheduledAt.getTime() - nowTime),
150
+ scheduledAt
148
151
  };
149
152
  }
150
- const targetDate = new Date(nowTime + relativeDelay);
153
+ const targetDate = new Date(baseTime + relativeDelay);
151
154
  for (const window of step.windows) {
152
155
  if (isTimeInWindowSlot(targetDate, window)) {
153
156
  return {
154
- waitTime: relativeDelay,
155
- scheduledAt: new Date(nowTime + relativeDelay)
157
+ waitTime: Math.max(0, targetDate.getTime() - nowTime),
158
+ scheduledAt: targetDate
156
159
  };
157
160
  }
158
161
  }
@@ -197,8 +200,8 @@ function calculateWaitTime(step, now) {
197
200
  msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
198
201
  }
199
202
  const windowStartTimeMs = targetDate.getTime() + msUntilWindowStart;
200
- const sameLocalDayAsNow = getLocalDateKey(now, tz) === getLocalDateKey(targetDate, tz);
201
- const windowScheduledTime = sameLocalDayAsNow ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
203
+ const sameLocalDayAsBase = getLocalDateKey(baseDate, tz) === getLocalDateKey(targetDate, tz);
204
+ const windowScheduledTime = sameLocalDayAsBase ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
202
205
  const waitFromNow = windowScheduledTime.getTime() - nowTime;
203
206
  if (waitFromNow < earliestWaitFromNow) {
204
207
  earliestWaitFromNow = waitFromNow;
@@ -33,6 +33,13 @@ export interface WaitInputRelative {
33
33
  amount: number;
34
34
  unit: WaitUnit;
35
35
  windows?: TimeWindowSlot[];
36
+ /**
37
+ * Optional ISO datetime (or Date) to use as the base for the relative delay
38
+ * instead of wall-clock `now`. Used for cadence chaining: schedule message N
39
+ * relative to message N-1's `scheduledAt`.
40
+ * `waitTime` in the result is still measured from wall-clock `now`.
41
+ */
42
+ from?: string | Date;
36
43
  }
37
44
  export interface WaitInputAbsolute {
38
45
  mode: 'absolute';
@@ -107,6 +107,7 @@ export declare const WaitInputRelativeSchema: z.ZodObject<{
107
107
  }>>;
108
108
  timezone: z.ZodOptional<z.ZodString>;
109
109
  }, z.core.$strip>>>;
110
+ from: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodDate]>>;
110
111
  }, z.core.$strip>;
111
112
  export declare const WaitInputAbsoluteSchema: z.ZodObject<{
112
113
  mode: z.ZodLiteral<"absolute">;
@@ -153,6 +154,7 @@ export declare const WaitInputSchema: z.ZodUnion<readonly [z.ZodObject<{
153
154
  }>>;
154
155
  timezone: z.ZodOptional<z.ZodString>;
155
156
  }, z.core.$strip>>>;
157
+ from: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodDate]>>;
156
158
  }, z.core.$strip>, z.ZodObject<{
157
159
  mode: z.ZodLiteral<"absolute">;
158
160
  scheduleAt: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
@@ -4,6 +4,10 @@ export declare function parsePreAcquiredLeases(raw: string | undefined): Array<{
4
4
  queueKey: string;
5
5
  leaseId: string;
6
6
  }> | undefined;
7
+ export declare function parseHeldMutexQueueKeys(raw: string | undefined): Array<{
8
+ queueKey: string;
9
+ queueName: string;
10
+ }> | undefined;
7
11
  /**
8
12
  * Builds tool metadata array from a tool registry
9
13
  */
package/dist/server.js CHANGED
@@ -441,6 +441,26 @@ function parsePreAcquiredLeases(raw) {
441
441
  return void 0;
442
442
  }
443
443
  }
444
+ function parseHeldMutexQueueKeys(raw) {
445
+ if (!raw) {
446
+ return void 0;
447
+ }
448
+ try {
449
+ const parsed = JSON.parse(raw);
450
+ if (!Array.isArray(parsed)) {
451
+ return void 0;
452
+ }
453
+ const keys = parsed.filter(
454
+ (item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.queueName === "string"
455
+ ).map((item) => ({
456
+ queueKey: item.queueKey,
457
+ queueName: item.queueName
458
+ }));
459
+ return keys.length > 0 ? keys : void 0;
460
+ } catch {
461
+ return void 0;
462
+ }
463
+ }
444
464
  function buildToolMetadata(registry) {
445
465
  return Object.values(registry).map((tool) => {
446
466
  const toolConfig = tool.config ?? {};
@@ -578,7 +598,10 @@ function createCallToolHandler(registry, state, onMaxRequests) {
578
598
  appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
579
599
  invocation,
580
600
  isProvisionContext: trigger === "provision",
581
- preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
601
+ preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES),
602
+ heldMutexQueueKeys: parseHeldMutexQueueKeys(
603
+ toolEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
604
+ )
582
605
  };
583
606
  const functionResult = await runWithConfig(requestConfig, async () => {
584
607
  return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
@@ -402,6 +402,26 @@ function parsePreAcquiredLeases(raw) {
402
402
  return void 0;
403
403
  }
404
404
  }
405
+ function parseHeldMutexQueueKeys(raw) {
406
+ if (!raw) {
407
+ return void 0;
408
+ }
409
+ try {
410
+ const parsed = JSON.parse(raw);
411
+ if (!Array.isArray(parsed)) {
412
+ return void 0;
413
+ }
414
+ const keys = parsed.filter(
415
+ (item) => item != null && typeof item === "object" && typeof item.queueKey === "string" && typeof item.queueName === "string"
416
+ ).map((item) => ({
417
+ queueKey: item.queueKey,
418
+ queueName: item.queueName
419
+ }));
420
+ return keys.length > 0 ? keys : void 0;
421
+ } catch {
422
+ return void 0;
423
+ }
424
+ }
405
425
  function buildToolMetadata(registry) {
406
426
  return Object.values(registry).map((tool) => {
407
427
  const toolConfig = tool.config ?? {};
@@ -539,7 +559,10 @@ function createCallToolHandler(registry, state, onMaxRequests) {
539
559
  appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
540
560
  invocation,
541
561
  isProvisionContext: trigger === "provision",
542
- preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES)
562
+ preAcquiredLeases: parsePreAcquiredLeases(toolEnv.SKEDYUL_RATE_LIMIT_LEASES),
563
+ heldMutexQueueKeys: parseHeldMutexQueueKeys(
564
+ toolEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
565
+ )
543
566
  };
544
567
  const functionResult = await runWithConfig(requestConfig, async () => {
545
568
  return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skedyul",
3
- "version": "1.7.3",
3
+ "version": "1.7.6",
4
4
  "description": "The Skedyul SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",