skedyul 1.2.51 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +173 -143
- package/dist/config/app-config.d.ts +6 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/queue-config.d.ts +33 -0
- package/dist/dedicated/server.js +45 -16
- package/dist/esm/index.mjs +717 -50
- package/dist/index.d.ts +2 -0
- package/dist/index.js +731 -50
- package/dist/ratelimit/backends/index.d.ts +10 -0
- package/dist/ratelimit/backends/memory.d.ts +14 -0
- package/dist/ratelimit/backends/platform.d.ts +9 -0
- package/dist/ratelimit/config-loader.d.ts +10 -0
- package/dist/ratelimit/context.d.ts +7 -0
- package/dist/ratelimit/errors.d.ts +27 -0
- package/dist/ratelimit/index.d.ts +9 -0
- package/dist/ratelimit/queued-fetch.d.ts +11 -0
- package/dist/ratelimit/resolve-queue-key.d.ts +14 -0
- package/dist/ratelimit/should-retry.d.ts +5 -0
- package/dist/ratelimit/types.d.ts +43 -0
- package/dist/scheduling/index.js +14 -10
- package/dist/scheduling/index.mjs +14 -10
- package/dist/server.js +45 -16
- package/dist/serverless/server.mjs +45 -16
- package/package.json +3 -3
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { platformRateLimitBackend } from './platform';
|
|
2
|
+
import { memoryRateLimitBackend } from './memory';
|
|
3
|
+
import type { RateLimitBackend } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the active rate limit backend.
|
|
6
|
+
* Uses platform proxy by default; falls back to in-memory when configured or on local errors.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getRateLimitBackend(): RateLimitBackend;
|
|
9
|
+
export declare function resetRateLimitBackendForTests(): void;
|
|
10
|
+
export { platformRateLimitBackend, memoryRateLimitBackend };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RateLimitBackend, Lease, QueueLimits } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* In-process rate limit backend for local development.
|
|
4
|
+
*/
|
|
5
|
+
export declare class MemoryRateLimitBackend implements RateLimitBackend {
|
|
6
|
+
private readonly queues;
|
|
7
|
+
private readonly leases;
|
|
8
|
+
private getState;
|
|
9
|
+
private grant;
|
|
10
|
+
private drainWaiters;
|
|
11
|
+
acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number): Promise<Lease>;
|
|
12
|
+
release(lease: Lease): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare const memoryRateLimitBackend: MemoryRateLimitBackend;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RateLimitBackend, Lease, QueueLimits } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* HTTP backend that delegates acquire/release to the Skedyul platform proxy.
|
|
4
|
+
*/
|
|
5
|
+
export declare class PlatformRateLimitBackend implements RateLimitBackend {
|
|
6
|
+
acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number): Promise<Lease>;
|
|
7
|
+
release(lease: Lease): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare const platformRateLimitBackend: PlatformRateLimitBackend;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SkedyulConfig } from '../config/app-config';
|
|
2
|
+
import type { QueueConfig, SerializableQueueConfig } from '../config/queue-config';
|
|
3
|
+
/**
|
|
4
|
+
* Register in-process server config for queue resolution at runtime.
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerQueueConfig(config: SkedyulConfig): void;
|
|
7
|
+
export declare function clearRegisteredQueueConfig(): void;
|
|
8
|
+
export declare function getQueueDefinitions(): Record<string, SerializableQueueConfig>;
|
|
9
|
+
export declare function getQueueConfig(queueName: string): SerializableQueueConfig | undefined;
|
|
10
|
+
export declare function getQueueConfigWithRetry(queueName: string): QueueConfig | undefined;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RateLimitExecutionContext, ActiveQueuedOperation } from './types';
|
|
2
|
+
export declare function runWithRateLimitExecutionContext<T>(ctx: RateLimitExecutionContext, fn: () => T | Promise<T>): T | Promise<T>;
|
|
3
|
+
export declare function getRateLimitExecutionContext(): RateLimitExecutionContext | undefined;
|
|
4
|
+
export declare function runWithActiveQueuedOperation<T>(operation: ActiveQueuedOperation<T>, fn: () => Promise<T>): Promise<T>;
|
|
5
|
+
export declare function getActiveQueuedOperation<T>(): ActiveQueuedOperation<T> | undefined;
|
|
6
|
+
export declare function setActiveQueuedOperationLease(lease: ActiveQueuedOperation<unknown>['lease']): void;
|
|
7
|
+
export declare function updateActiveQueuedOperationAttempt(attempt: number): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limit errors for queuedFetch.
|
|
3
|
+
*/
|
|
4
|
+
export declare class QueueContextError extends Error {
|
|
5
|
+
readonly code = "QUEUE_CONTEXT_ERROR";
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class QueueNotFoundError extends Error {
|
|
9
|
+
readonly code = "QUEUE_NOT_FOUND";
|
|
10
|
+
constructor(queueName: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class QueuedFetchExhaustedError extends Error {
|
|
13
|
+
readonly code = "QUEUED_FETCH_EXHAUSTED";
|
|
14
|
+
readonly attempts: number;
|
|
15
|
+
readonly maxRetries: number;
|
|
16
|
+
readonly causeError: unknown;
|
|
17
|
+
constructor(attempts: number, maxRetries: number, causeError: unknown);
|
|
18
|
+
}
|
|
19
|
+
export declare class RequeueOutsideContextError extends Error {
|
|
20
|
+
readonly code = "REQUEUE_OUTSIDE_CONTEXT";
|
|
21
|
+
constructor();
|
|
22
|
+
}
|
|
23
|
+
export declare class RateLimitBackendError extends Error {
|
|
24
|
+
readonly code = "RATE_LIMIT_BACKEND_ERROR";
|
|
25
|
+
readonly statusCode?: number;
|
|
26
|
+
constructor(message: string, statusCode?: number);
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { queuedFetch, requeue, resolveQueue, createQueueHandle, queuedFetchResponse, } from './queued-fetch';
|
|
2
|
+
export type { QueueInput, QueueSelector, Lease, QueueLimits, ResolvedQueue, RateLimitExecutionContext, RateLimitBackend, } from './types';
|
|
3
|
+
export { QueueContextError, QueueNotFoundError, QueuedFetchExhaustedError, RequeueOutsideContextError, RateLimitBackendError, } from './errors';
|
|
4
|
+
export { registerQueueConfig, clearRegisteredQueueConfig, getQueueDefinitions, getQueueConfig, } from './config-loader';
|
|
5
|
+
export { runWithRateLimitExecutionContext, getRateLimitExecutionContext, } from './context';
|
|
6
|
+
export { resolveQueueKey, toQueueLimits } from './resolve-queue-key';
|
|
7
|
+
export { defaultShouldRetry } from './should-retry';
|
|
8
|
+
export { getRateLimitBackend, resetRateLimitBackendForTests, memoryRateLimitBackend, platformRateLimitBackend, } from './backends';
|
|
9
|
+
export type { QueueScope, QueueConfig, SerializableQueueConfig, QueueRegistry, } from '../config/queue-config';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { QueueInput, ResolvedQueue } from './types';
|
|
2
|
+
import type { RateLimitExecutionContext } from './types';
|
|
3
|
+
export declare function resolveQueue(queueInput: QueueInput, ctxOverride?: RateLimitExecutionContext): ResolvedQueue;
|
|
4
|
+
export declare function createQueueHandle(queueInput: QueueInput): {
|
|
5
|
+
run<T>(fn: () => Promise<T>): Promise<T>;
|
|
6
|
+
};
|
|
7
|
+
export declare function queuedFetch<T>(queueInput: QueueInput, fnOrPromise: (() => Promise<T>) | Promise<T>): Promise<T>;
|
|
8
|
+
export declare function requeue<T = unknown>(): Promise<T>;
|
|
9
|
+
export declare function queuedFetchResponse(queueInput: QueueInput, url: string, init?: RequestInit): Promise<Response>;
|
|
10
|
+
/** @deprecated Use createQueueHandle */
|
|
11
|
+
export declare const resolveQueueHandle: typeof createQueueHandle;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SerializableQueueConfig } from '../config/queue-config';
|
|
2
|
+
import type { RateLimitExecutionContext } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Build the Redis queue key for a named queue definition.
|
|
5
|
+
* Must stay in sync with skedyul-core rate limit validation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveQueueKey(queueName: string, config: SerializableQueueConfig, ctx: RateLimitExecutionContext, subKey?: string): string;
|
|
8
|
+
export declare function toQueueLimits(config: SerializableQueueConfig): {
|
|
9
|
+
maxConcurrent: number | undefined;
|
|
10
|
+
minTime: number | undefined;
|
|
11
|
+
reservoir: number | undefined;
|
|
12
|
+
reservoirRefreshAmount: number | undefined;
|
|
13
|
+
reservoirRefreshInterval: number | undefined;
|
|
14
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SerializableQueueConfig } from '../config/queue-config';
|
|
2
|
+
import type { AppInfo } from '../types/shared';
|
|
3
|
+
import type { InvocationContext } from '../types/invocation';
|
|
4
|
+
export interface RateLimitExecutionContext {
|
|
5
|
+
app: AppInfo;
|
|
6
|
+
appInstallationId?: string;
|
|
7
|
+
invocation?: InvocationContext;
|
|
8
|
+
isProvisionContext?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface QueueSelector {
|
|
11
|
+
queue: string;
|
|
12
|
+
key?: string;
|
|
13
|
+
}
|
|
14
|
+
export type QueueInput = string | QueueSelector;
|
|
15
|
+
export interface Lease {
|
|
16
|
+
leaseId: string;
|
|
17
|
+
acquiredAt: number;
|
|
18
|
+
queueKey: string;
|
|
19
|
+
}
|
|
20
|
+
export interface QueueLimits {
|
|
21
|
+
maxConcurrent?: number;
|
|
22
|
+
minTime?: number;
|
|
23
|
+
reservoir?: number;
|
|
24
|
+
reservoirRefreshAmount?: number;
|
|
25
|
+
reservoirRefreshInterval?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ResolvedQueue {
|
|
28
|
+
name: string;
|
|
29
|
+
queueKey: string;
|
|
30
|
+
config: SerializableQueueConfig;
|
|
31
|
+
limits: QueueLimits;
|
|
32
|
+
}
|
|
33
|
+
export interface RateLimitBackend {
|
|
34
|
+
acquire(queueKey: string, limits: QueueLimits, timeoutMs?: number): Promise<Lease>;
|
|
35
|
+
release(lease: Lease): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export interface ActiveQueuedOperation<T> {
|
|
38
|
+
queueInput: QueueInput;
|
|
39
|
+
fn: () => Promise<T>;
|
|
40
|
+
attempt: number;
|
|
41
|
+
resolved: ResolvedQueue;
|
|
42
|
+
lease: Lease | null;
|
|
43
|
+
}
|
package/dist/scheduling/index.js
CHANGED
|
@@ -114,6 +114,9 @@ function isTimeInWindowSlot(date, window) {
|
|
|
114
114
|
const tzInfo = getTimezoneInfo(date, tz);
|
|
115
115
|
return allowedDays.includes(tzInfo.day) && tzInfo.totalMinutes >= windowStartMinutes && tzInfo.totalMinutes < windowEndMinutes;
|
|
116
116
|
}
|
|
117
|
+
function getLocalDateKey(date, timezone) {
|
|
118
|
+
return date.toLocaleDateString("en-CA", { timeZone: timezone });
|
|
119
|
+
}
|
|
117
120
|
function calculateWaitTime(step, now) {
|
|
118
121
|
const nowTime = now.getTime();
|
|
119
122
|
switch (step.mode) {
|
|
@@ -182,7 +185,7 @@ function calculateWaitTime(step, now) {
|
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
let earliestScheduledTime = null;
|
|
185
|
-
let
|
|
188
|
+
let earliestWaitFromNow = Infinity;
|
|
186
189
|
for (const window of step.windows) {
|
|
187
190
|
if (!window || window.startTime === void 0 || window.endTime === void 0) {
|
|
188
191
|
continue;
|
|
@@ -200,12 +203,10 @@ function calculateWaitTime(step, now) {
|
|
|
200
203
|
const currentHour = targetTzInfo.hour;
|
|
201
204
|
const currentMinute = targetTzInfo.minute;
|
|
202
205
|
const currentTotalMinutes = targetTzInfo.totalMinutes;
|
|
203
|
-
let
|
|
204
|
-
let windowScheduledTime;
|
|
206
|
+
let msUntilWindowStart;
|
|
205
207
|
if (allowedDays.includes(currentDay) && currentTotalMinutes < windowStartMinutes) {
|
|
206
208
|
const minutesUntilWindow = windowStartMinutes - currentTotalMinutes;
|
|
207
|
-
|
|
208
|
-
windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
|
|
209
|
+
msUntilWindowStart = minutesUntilWindow * 60 * 1e3;
|
|
209
210
|
} else {
|
|
210
211
|
let daysToAdd = 7;
|
|
211
212
|
for (let i = 1; i <= 7; i++) {
|
|
@@ -221,17 +222,20 @@ function calculateWaitTime(step, now) {
|
|
|
221
222
|
const daysMs = daysToAdd * millisecondsPerDay;
|
|
222
223
|
const hoursAdjustment = (windowStartHour - currentHour) * millisecondsPerHour;
|
|
223
224
|
const minutesAdjustment = (windowStartMinute - currentMinute) * millisecondsPerMinute;
|
|
224
|
-
|
|
225
|
-
windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
|
|
225
|
+
msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
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);
|
|
230
|
+
const waitFromNow = windowScheduledTime.getTime() - nowTime;
|
|
231
|
+
if (waitFromNow < earliestWaitFromNow) {
|
|
232
|
+
earliestWaitFromNow = waitFromNow;
|
|
229
233
|
earliestScheduledTime = windowScheduledTime;
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
236
|
if (earliestScheduledTime) {
|
|
233
237
|
return {
|
|
234
|
-
waitTime:
|
|
238
|
+
waitTime: Math.max(0, earliestScheduledTime.getTime() - nowTime),
|
|
235
239
|
scheduledAt: earliestScheduledTime
|
|
236
240
|
};
|
|
237
241
|
}
|
|
@@ -86,6 +86,9 @@ function isTimeInWindowSlot(date, window) {
|
|
|
86
86
|
const tzInfo = getTimezoneInfo(date, tz);
|
|
87
87
|
return allowedDays.includes(tzInfo.day) && tzInfo.totalMinutes >= windowStartMinutes && tzInfo.totalMinutes < windowEndMinutes;
|
|
88
88
|
}
|
|
89
|
+
function getLocalDateKey(date, timezone) {
|
|
90
|
+
return date.toLocaleDateString("en-CA", { timeZone: timezone });
|
|
91
|
+
}
|
|
89
92
|
function calculateWaitTime(step, now) {
|
|
90
93
|
const nowTime = now.getTime();
|
|
91
94
|
switch (step.mode) {
|
|
@@ -154,7 +157,7 @@ function calculateWaitTime(step, now) {
|
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
let earliestScheduledTime = null;
|
|
157
|
-
let
|
|
160
|
+
let earliestWaitFromNow = Infinity;
|
|
158
161
|
for (const window of step.windows) {
|
|
159
162
|
if (!window || window.startTime === void 0 || window.endTime === void 0) {
|
|
160
163
|
continue;
|
|
@@ -172,12 +175,10 @@ function calculateWaitTime(step, now) {
|
|
|
172
175
|
const currentHour = targetTzInfo.hour;
|
|
173
176
|
const currentMinute = targetTzInfo.minute;
|
|
174
177
|
const currentTotalMinutes = targetTzInfo.totalMinutes;
|
|
175
|
-
let
|
|
176
|
-
let windowScheduledTime;
|
|
178
|
+
let msUntilWindowStart;
|
|
177
179
|
if (allowedDays.includes(currentDay) && currentTotalMinutes < windowStartMinutes) {
|
|
178
180
|
const minutesUntilWindow = windowStartMinutes - currentTotalMinutes;
|
|
179
|
-
|
|
180
|
-
windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
|
|
181
|
+
msUntilWindowStart = minutesUntilWindow * 60 * 1e3;
|
|
181
182
|
} else {
|
|
182
183
|
let daysToAdd = 7;
|
|
183
184
|
for (let i = 1; i <= 7; i++) {
|
|
@@ -193,17 +194,20 @@ function calculateWaitTime(step, now) {
|
|
|
193
194
|
const daysMs = daysToAdd * millisecondsPerDay;
|
|
194
195
|
const hoursAdjustment = (windowStartHour - currentHour) * millisecondsPerHour;
|
|
195
196
|
const minutesAdjustment = (windowStartMinute - currentMinute) * millisecondsPerMinute;
|
|
196
|
-
|
|
197
|
-
windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
|
|
197
|
+
msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
|
|
198
198
|
}
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
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);
|
|
202
|
+
const waitFromNow = windowScheduledTime.getTime() - nowTime;
|
|
203
|
+
if (waitFromNow < earliestWaitFromNow) {
|
|
204
|
+
earliestWaitFromNow = waitFromNow;
|
|
201
205
|
earliestScheduledTime = windowScheduledTime;
|
|
202
206
|
}
|
|
203
207
|
}
|
|
204
208
|
if (earliestScheduledTime) {
|
|
205
209
|
return {
|
|
206
|
-
waitTime:
|
|
210
|
+
waitTime: Math.max(0, earliestScheduledTime.getTime() - nowTime),
|
|
207
211
|
scheduledAt: earliestScheduledTime
|
|
208
212
|
};
|
|
209
213
|
}
|
package/dist/server.js
CHANGED
|
@@ -87,6 +87,16 @@ var CoreApiService = class {
|
|
|
87
87
|
};
|
|
88
88
|
var coreApiService = new CoreApiService();
|
|
89
89
|
|
|
90
|
+
// src/ratelimit/config-loader.ts
|
|
91
|
+
var fs = __toESM(require("fs"));
|
|
92
|
+
var path = __toESM(require("path"));
|
|
93
|
+
var cachedRuntimeQueues = null;
|
|
94
|
+
var registeredServerConfig = null;
|
|
95
|
+
function registerQueueConfig(config) {
|
|
96
|
+
registeredServerConfig = config;
|
|
97
|
+
cachedRuntimeQueues = config.queues ?? null;
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
// src/server/utils/schema.ts
|
|
91
101
|
var z = __toESM(require("zod"));
|
|
92
102
|
function normalizeBilling(billing) {
|
|
@@ -265,6 +275,14 @@ function runWithConfig(config, fn) {
|
|
|
265
275
|
return requestConfigStorage.run(config, fn);
|
|
266
276
|
}
|
|
267
277
|
|
|
278
|
+
// src/ratelimit/context.ts
|
|
279
|
+
var import_async_hooks2 = require("async_hooks");
|
|
280
|
+
var executionContextStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
281
|
+
var activeOperationStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
282
|
+
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
283
|
+
return executionContextStorage.run(ctx, fn);
|
|
284
|
+
}
|
|
285
|
+
|
|
268
286
|
// src/errors.ts
|
|
269
287
|
var InstallError = class extends Error {
|
|
270
288
|
// Optional: which field caused the error
|
|
@@ -284,8 +302,8 @@ var AppAuthInvalidError = class extends Error {
|
|
|
284
302
|
};
|
|
285
303
|
|
|
286
304
|
// src/server/context-logger.ts
|
|
287
|
-
var
|
|
288
|
-
var logContextStorage = new
|
|
305
|
+
var import_async_hooks3 = require("async_hooks");
|
|
306
|
+
var logContextStorage = new import_async_hooks3.AsyncLocalStorage();
|
|
289
307
|
function runWithLogContext(context, fn) {
|
|
290
308
|
return logContextStorage.run(context, fn);
|
|
291
309
|
}
|
|
@@ -498,9 +516,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
498
516
|
baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
|
|
499
517
|
apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
|
|
500
518
|
};
|
|
519
|
+
const rateLimitContext = {
|
|
520
|
+
app,
|
|
521
|
+
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
522
|
+
invocation,
|
|
523
|
+
isProvisionContext: trigger === "provision"
|
|
524
|
+
};
|
|
501
525
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
502
|
-
return await
|
|
503
|
-
return await
|
|
526
|
+
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
527
|
+
return await runWithLogContext({ invocation }, async () => {
|
|
528
|
+
return await fn(inputs, executionContext);
|
|
529
|
+
});
|
|
504
530
|
});
|
|
505
531
|
});
|
|
506
532
|
const billing = normalizeBilling(functionResult.billing);
|
|
@@ -544,6 +570,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
544
570
|
};
|
|
545
571
|
}
|
|
546
572
|
const errorMessage = error instanceof Error ? error.message : String(error ?? "");
|
|
573
|
+
const errorCode = error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : "TOOL_EXECUTION_ERROR";
|
|
547
574
|
return {
|
|
548
575
|
output: null,
|
|
549
576
|
billing: { credits: 0 },
|
|
@@ -553,7 +580,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
553
580
|
toolName
|
|
554
581
|
},
|
|
555
582
|
error: {
|
|
556
|
-
code:
|
|
583
|
+
code: errorCode,
|
|
557
584
|
message: errorMessage
|
|
558
585
|
}
|
|
559
586
|
};
|
|
@@ -637,7 +664,7 @@ function printStartupLog(config, tools, port) {
|
|
|
637
664
|
|
|
638
665
|
// src/server/route-handlers/adapters.ts
|
|
639
666
|
function fromLambdaEvent(event) {
|
|
640
|
-
const
|
|
667
|
+
const path3 = event.path || event.rawPath || "/";
|
|
641
668
|
const method = event.httpMethod || event.requestContext?.http?.method || "POST";
|
|
642
669
|
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
643
670
|
const protocol = forwardedProto ?? "https";
|
|
@@ -645,9 +672,9 @@ function fromLambdaEvent(event) {
|
|
|
645
672
|
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(
|
|
646
673
|
event.queryStringParameters
|
|
647
674
|
).toString() : "";
|
|
648
|
-
const url = `${protocol}://${host}${
|
|
675
|
+
const url = `${protocol}://${host}${path3}${queryString}`;
|
|
649
676
|
return {
|
|
650
|
-
path:
|
|
677
|
+
path: path3,
|
|
651
678
|
method,
|
|
652
679
|
headers: event.headers,
|
|
653
680
|
query: event.queryStringParameters ?? {},
|
|
@@ -729,8 +756,8 @@ function parseBodyByContentType(req) {
|
|
|
729
756
|
}
|
|
730
757
|
|
|
731
758
|
// src/server/route-handlers/handlers.ts
|
|
732
|
-
var
|
|
733
|
-
var
|
|
759
|
+
var fs2 = __toESM(require("fs"));
|
|
760
|
+
var path2 = __toESM(require("path"));
|
|
734
761
|
|
|
735
762
|
// src/server/core-api-handler.ts
|
|
736
763
|
async function handleCoreMethod(method, params) {
|
|
@@ -898,7 +925,8 @@ function serializeConfig(config) {
|
|
|
898
925
|
type: w.type ?? "WEBHOOK"
|
|
899
926
|
})) : [],
|
|
900
927
|
provision: isProvisionConfig(config.provision) ? config.provision : void 0,
|
|
901
|
-
agents: config.agents
|
|
928
|
+
agents: config.agents,
|
|
929
|
+
queues: config.queues
|
|
902
930
|
};
|
|
903
931
|
}
|
|
904
932
|
function isProvisionConfig(value) {
|
|
@@ -1194,7 +1222,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
|
|
|
1194
1222
|
}
|
|
1195
1223
|
|
|
1196
1224
|
// src/server/handlers/webhook-handler.ts
|
|
1197
|
-
function parseWebhookRequest(parsedBody, method, url,
|
|
1225
|
+
function parseWebhookRequest(parsedBody, method, url, path3, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
1198
1226
|
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
1199
1227
|
if (isEnvelope) {
|
|
1200
1228
|
const envelope = parsedBody;
|
|
@@ -1248,7 +1276,7 @@ function parseWebhookRequest(parsedBody, method, url, path2, headers, query, raw
|
|
|
1248
1276
|
const webhookRequest = {
|
|
1249
1277
|
method,
|
|
1250
1278
|
url,
|
|
1251
|
-
path:
|
|
1279
|
+
path: path3,
|
|
1252
1280
|
headers,
|
|
1253
1281
|
query,
|
|
1254
1282
|
body: parsedBody,
|
|
@@ -1306,7 +1334,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
|
|
|
1306
1334
|
|
|
1307
1335
|
// src/server/route-handlers/handlers.ts
|
|
1308
1336
|
function getConfigFilePath() {
|
|
1309
|
-
return process.env.LAMBDA_TASK_ROOT ?
|
|
1337
|
+
return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
|
|
1310
1338
|
}
|
|
1311
1339
|
function findToolInRegistry(registry, toolName) {
|
|
1312
1340
|
for (const [key, t] of Object.entries(registry)) {
|
|
@@ -1345,8 +1373,8 @@ function handleHealthRoute(ctx) {
|
|
|
1345
1373
|
function handleConfigRoute(ctx) {
|
|
1346
1374
|
const configFilePath = getConfigFilePath();
|
|
1347
1375
|
try {
|
|
1348
|
-
if (
|
|
1349
|
-
const fileConfig = JSON.parse(
|
|
1376
|
+
if (fs2.existsSync(configFilePath)) {
|
|
1377
|
+
const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
|
|
1350
1378
|
return { status: 200, body: fileConfig };
|
|
1351
1379
|
}
|
|
1352
1380
|
} catch (err) {
|
|
@@ -2031,6 +2059,7 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
2031
2059
|
installContextLogger();
|
|
2032
2060
|
function createSkedyulServer(config) {
|
|
2033
2061
|
mergeRuntimeEnv();
|
|
2062
|
+
registerQueueConfig(config);
|
|
2034
2063
|
const registry = config.tools;
|
|
2035
2064
|
const webhookRegistry = config.webhooks;
|
|
2036
2065
|
if (config.coreApi?.service) {
|
|
@@ -48,6 +48,16 @@ var CoreApiService = class {
|
|
|
48
48
|
};
|
|
49
49
|
var coreApiService = new CoreApiService();
|
|
50
50
|
|
|
51
|
+
// src/ratelimit/config-loader.ts
|
|
52
|
+
import * as fs from "fs";
|
|
53
|
+
import * as path from "path";
|
|
54
|
+
var cachedRuntimeQueues = null;
|
|
55
|
+
var registeredServerConfig = null;
|
|
56
|
+
function registerQueueConfig(config) {
|
|
57
|
+
registeredServerConfig = config;
|
|
58
|
+
cachedRuntimeQueues = config.queues ?? null;
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
// src/server/utils/schema.ts
|
|
52
62
|
import * as z from "zod";
|
|
53
63
|
function normalizeBilling(billing) {
|
|
@@ -226,6 +236,14 @@ function runWithConfig(config, fn) {
|
|
|
226
236
|
return requestConfigStorage.run(config, fn);
|
|
227
237
|
}
|
|
228
238
|
|
|
239
|
+
// src/ratelimit/context.ts
|
|
240
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
241
|
+
var executionContextStorage = new AsyncLocalStorage2();
|
|
242
|
+
var activeOperationStorage = new AsyncLocalStorage2();
|
|
243
|
+
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
244
|
+
return executionContextStorage.run(ctx, fn);
|
|
245
|
+
}
|
|
246
|
+
|
|
229
247
|
// src/errors.ts
|
|
230
248
|
var InstallError = class extends Error {
|
|
231
249
|
// Optional: which field caused the error
|
|
@@ -245,8 +263,8 @@ var AppAuthInvalidError = class extends Error {
|
|
|
245
263
|
};
|
|
246
264
|
|
|
247
265
|
// src/server/context-logger.ts
|
|
248
|
-
import { AsyncLocalStorage as
|
|
249
|
-
var logContextStorage = new
|
|
266
|
+
import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
|
|
267
|
+
var logContextStorage = new AsyncLocalStorage3();
|
|
250
268
|
function runWithLogContext(context, fn) {
|
|
251
269
|
return logContextStorage.run(context, fn);
|
|
252
270
|
}
|
|
@@ -459,9 +477,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
459
477
|
baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
|
|
460
478
|
apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
|
|
461
479
|
};
|
|
480
|
+
const rateLimitContext = {
|
|
481
|
+
app,
|
|
482
|
+
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
483
|
+
invocation,
|
|
484
|
+
isProvisionContext: trigger === "provision"
|
|
485
|
+
};
|
|
462
486
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
463
|
-
return await
|
|
464
|
-
return await
|
|
487
|
+
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
488
|
+
return await runWithLogContext({ invocation }, async () => {
|
|
489
|
+
return await fn(inputs, executionContext);
|
|
490
|
+
});
|
|
465
491
|
});
|
|
466
492
|
});
|
|
467
493
|
const billing = normalizeBilling(functionResult.billing);
|
|
@@ -505,6 +531,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
505
531
|
};
|
|
506
532
|
}
|
|
507
533
|
const errorMessage = error instanceof Error ? error.message : String(error ?? "");
|
|
534
|
+
const errorCode = error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : "TOOL_EXECUTION_ERROR";
|
|
508
535
|
return {
|
|
509
536
|
output: null,
|
|
510
537
|
billing: { credits: 0 },
|
|
@@ -514,7 +541,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
514
541
|
toolName
|
|
515
542
|
},
|
|
516
543
|
error: {
|
|
517
|
-
code:
|
|
544
|
+
code: errorCode,
|
|
518
545
|
message: errorMessage
|
|
519
546
|
}
|
|
520
547
|
};
|
|
@@ -598,7 +625,7 @@ function printStartupLog(config, tools, port) {
|
|
|
598
625
|
|
|
599
626
|
// src/server/route-handlers/adapters.ts
|
|
600
627
|
function fromLambdaEvent(event) {
|
|
601
|
-
const
|
|
628
|
+
const path3 = event.path || event.rawPath || "/";
|
|
602
629
|
const method = event.httpMethod || event.requestContext?.http?.method || "POST";
|
|
603
630
|
const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
|
|
604
631
|
const protocol = forwardedProto ?? "https";
|
|
@@ -606,9 +633,9 @@ function fromLambdaEvent(event) {
|
|
|
606
633
|
const queryString = event.queryStringParameters ? "?" + new URLSearchParams(
|
|
607
634
|
event.queryStringParameters
|
|
608
635
|
).toString() : "";
|
|
609
|
-
const url = `${protocol}://${host}${
|
|
636
|
+
const url = `${protocol}://${host}${path3}${queryString}`;
|
|
610
637
|
return {
|
|
611
|
-
path:
|
|
638
|
+
path: path3,
|
|
612
639
|
method,
|
|
613
640
|
headers: event.headers,
|
|
614
641
|
query: event.queryStringParameters ?? {},
|
|
@@ -690,8 +717,8 @@ function parseBodyByContentType(req) {
|
|
|
690
717
|
}
|
|
691
718
|
|
|
692
719
|
// src/server/route-handlers/handlers.ts
|
|
693
|
-
import * as
|
|
694
|
-
import * as
|
|
720
|
+
import * as fs2 from "fs";
|
|
721
|
+
import * as path2 from "path";
|
|
695
722
|
|
|
696
723
|
// src/server/core-api-handler.ts
|
|
697
724
|
async function handleCoreMethod(method, params) {
|
|
@@ -859,7 +886,8 @@ function serializeConfig(config) {
|
|
|
859
886
|
type: w.type ?? "WEBHOOK"
|
|
860
887
|
})) : [],
|
|
861
888
|
provision: isProvisionConfig(config.provision) ? config.provision : void 0,
|
|
862
|
-
agents: config.agents
|
|
889
|
+
agents: config.agents,
|
|
890
|
+
queues: config.queues
|
|
863
891
|
};
|
|
864
892
|
}
|
|
865
893
|
function isProvisionConfig(value) {
|
|
@@ -1155,7 +1183,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
|
|
|
1155
1183
|
}
|
|
1156
1184
|
|
|
1157
1185
|
// src/server/handlers/webhook-handler.ts
|
|
1158
|
-
function parseWebhookRequest(parsedBody, method, url,
|
|
1186
|
+
function parseWebhookRequest(parsedBody, method, url, path3, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
1159
1187
|
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
1160
1188
|
if (isEnvelope) {
|
|
1161
1189
|
const envelope = parsedBody;
|
|
@@ -1209,7 +1237,7 @@ function parseWebhookRequest(parsedBody, method, url, path2, headers, query, raw
|
|
|
1209
1237
|
const webhookRequest = {
|
|
1210
1238
|
method,
|
|
1211
1239
|
url,
|
|
1212
|
-
path:
|
|
1240
|
+
path: path3,
|
|
1213
1241
|
headers,
|
|
1214
1242
|
query,
|
|
1215
1243
|
body: parsedBody,
|
|
@@ -1267,7 +1295,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
|
|
|
1267
1295
|
|
|
1268
1296
|
// src/server/route-handlers/handlers.ts
|
|
1269
1297
|
function getConfigFilePath() {
|
|
1270
|
-
return process.env.LAMBDA_TASK_ROOT ?
|
|
1298
|
+
return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
|
|
1271
1299
|
}
|
|
1272
1300
|
function findToolInRegistry(registry, toolName) {
|
|
1273
1301
|
for (const [key, t] of Object.entries(registry)) {
|
|
@@ -1306,8 +1334,8 @@ function handleHealthRoute(ctx) {
|
|
|
1306
1334
|
function handleConfigRoute(ctx) {
|
|
1307
1335
|
const configFilePath = getConfigFilePath();
|
|
1308
1336
|
try {
|
|
1309
|
-
if (
|
|
1310
|
-
const fileConfig = JSON.parse(
|
|
1337
|
+
if (fs2.existsSync(configFilePath)) {
|
|
1338
|
+
const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
|
|
1311
1339
|
return { status: 200, body: fileConfig };
|
|
1312
1340
|
}
|
|
1313
1341
|
} catch (err) {
|
|
@@ -1992,6 +2020,7 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
1992
2020
|
installContextLogger();
|
|
1993
2021
|
function createSkedyulServer(config) {
|
|
1994
2022
|
mergeRuntimeEnv();
|
|
2023
|
+
registerQueueConfig(config);
|
|
1995
2024
|
const registry = config.tools;
|
|
1996
2025
|
const webhookRegistry = config.webhooks;
|
|
1997
2026
|
if (config.coreApi?.service) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skedyul",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "The Skedyul SDK for Node.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"build": "rm -rf dist tsconfig.tsbuildinfo && tsc --emitDeclarationOnly && tsup",
|
|
65
65
|
"db:generate": "echo 'No database generation needed for this package'",
|
|
66
66
|
"dev": "npx nodemon --watch src --ext js,ts --exec \"pnpm run build\"",
|
|
67
|
-
"test": "tsc --project tsconfig.tests.json && node --test dist-tests/tests/server.test.js dist-tests/tests/cli.test.js dist-tests/tests/agent-schema-v3.test.js"
|
|
67
|
+
"test": "tsc --project tsconfig.tests.json && node --test dist-tests/tests/server.test.js dist-tests/tests/cli.test.js dist-tests/tests/agent-schema-v3.test.js dist-tests/tests/ratelimit.test.js"
|
|
68
68
|
},
|
|
69
69
|
"keywords": [
|
|
70
70
|
"mcp",
|
|
@@ -90,6 +90,6 @@
|
|
|
90
90
|
"@types/node": "^24.10.1",
|
|
91
91
|
"open": "^10.1.0",
|
|
92
92
|
"tsup": "^8.0.0",
|
|
93
|
-
"typescript": "^
|
|
93
|
+
"typescript": "^6.0.3"
|
|
94
94
|
}
|
|
95
95
|
}
|