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
package/dist/index.js
CHANGED
|
@@ -169,10 +169,15 @@ __export(index_exports, {
|
|
|
169
169
|
ParticipantEventPayloadSchema: () => ParticipantEventPayloadSchema,
|
|
170
170
|
ParticipantKindSchema: () => ParticipantKindSchema,
|
|
171
171
|
ProvisionConfigSchema: () => ProvisionConfigSchema,
|
|
172
|
+
QueueContextError: () => QueueContextError,
|
|
173
|
+
QueueNotFoundError: () => QueueNotFoundError,
|
|
174
|
+
QueuedFetchExhaustedError: () => QueuedFetchExhaustedError,
|
|
175
|
+
RateLimitBackendError: () => RateLimitBackendError,
|
|
172
176
|
RelationshipCardinalitySchema: () => RelationshipCardinalitySchema,
|
|
173
177
|
RelationshipDefinitionSchema: () => RelationshipDefinitionSchema,
|
|
174
178
|
RelationshipExtensionSchema: () => RelationshipExtensionSchema,
|
|
175
179
|
RelationshipLinkSchema: () => RelationshipLinkSchema,
|
|
180
|
+
RequeueOutsideContextError: () => RequeueOutsideContextError,
|
|
176
181
|
ResolvedSkillSchema: () => ResolvedSkillSchema,
|
|
177
182
|
ResolvedTriggerSchema: () => ResolvedTriggerSchema,
|
|
178
183
|
ResourceDependencySchema: () => ResourceDependencySchema,
|
|
@@ -250,6 +255,7 @@ __export(index_exports, {
|
|
|
250
255
|
createListResponse: () => createListResponse,
|
|
251
256
|
createNotFoundError: () => createNotFoundError,
|
|
252
257
|
createPermissionError: () => createPermissionError,
|
|
258
|
+
createQueueHandle: () => createQueueHandle,
|
|
253
259
|
createRateLimitError: () => createRateLimitError,
|
|
254
260
|
createServerHookContext: () => createServerHookContext,
|
|
255
261
|
createSuccessResponse: () => createSuccessResponse,
|
|
@@ -260,6 +266,7 @@ __export(index_exports, {
|
|
|
260
266
|
createWorkflowStepContext: () => createWorkflowStepContext,
|
|
261
267
|
cron: () => cron,
|
|
262
268
|
default: () => index_default,
|
|
269
|
+
defaultShouldRetry: () => defaultShouldRetry,
|
|
263
270
|
defineAgent: () => defineAgent,
|
|
264
271
|
defineChannel: () => defineChannel,
|
|
265
272
|
defineConfig: () => defineConfig,
|
|
@@ -282,6 +289,7 @@ __export(index_exports, {
|
|
|
282
289
|
getConfig: () => getConfig,
|
|
283
290
|
getContextByHandle: () => getContextByHandle,
|
|
284
291
|
getContextByModel: () => getContextByModel,
|
|
292
|
+
getRateLimitExecutionContext: () => getRateLimitExecutionContext,
|
|
285
293
|
getRequiredInstallEnvKeys: () => getRequiredInstallEnvKeys,
|
|
286
294
|
getRetryDelay: () => getRetryDelay,
|
|
287
295
|
instance: () => instance,
|
|
@@ -301,10 +309,16 @@ __export(index_exports, {
|
|
|
301
309
|
loadConfig: () => loadConfig,
|
|
302
310
|
matchesTrigger: () => matchesTrigger,
|
|
303
311
|
parseCRMSchema: () => parseCRMSchema,
|
|
312
|
+
queuedFetch: () => queuedFetch,
|
|
313
|
+
queuedFetchResponse: () => queuedFetchResponse,
|
|
314
|
+
registerQueueConfig: () => registerQueueConfig,
|
|
304
315
|
report: () => report,
|
|
316
|
+
requeue: () => requeue,
|
|
305
317
|
resolveInputMappings: () => resolveInputMappings,
|
|
318
|
+
resolveQueue: () => resolveQueue,
|
|
306
319
|
resource: () => resource,
|
|
307
320
|
runWithConfig: () => runWithConfig,
|
|
321
|
+
runWithRateLimitExecutionContext: () => runWithRateLimitExecutionContext,
|
|
308
322
|
safeParseCRMSchema: () => safeParseCRMSchema,
|
|
309
323
|
safeParseConfig: () => safeParseConfig,
|
|
310
324
|
server: () => server,
|
|
@@ -2163,6 +2177,68 @@ var CoreApiService = class {
|
|
|
2163
2177
|
};
|
|
2164
2178
|
var coreApiService = new CoreApiService();
|
|
2165
2179
|
|
|
2180
|
+
// src/ratelimit/config-loader.ts
|
|
2181
|
+
var fs = __toESM(require("fs"));
|
|
2182
|
+
var path = __toESM(require("path"));
|
|
2183
|
+
var cachedRuntimeQueues = null;
|
|
2184
|
+
function getRuntimeConfigPath() {
|
|
2185
|
+
if (process.env.LAMBDA_TASK_ROOT) {
|
|
2186
|
+
return path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json");
|
|
2187
|
+
}
|
|
2188
|
+
return path.join(process.cwd(), ".skedyul", "config.json");
|
|
2189
|
+
}
|
|
2190
|
+
function loadRuntimeConfigFile() {
|
|
2191
|
+
const configPath = getRuntimeConfigPath();
|
|
2192
|
+
if (!fs.existsSync(configPath)) {
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
try {
|
|
2196
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
2197
|
+
return JSON.parse(raw);
|
|
2198
|
+
} catch {
|
|
2199
|
+
return null;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
var registeredServerConfig = null;
|
|
2203
|
+
function registerQueueConfig(config) {
|
|
2204
|
+
registeredServerConfig = config;
|
|
2205
|
+
cachedRuntimeQueues = config.queues ?? null;
|
|
2206
|
+
}
|
|
2207
|
+
function getQueueDefinitions() {
|
|
2208
|
+
if (cachedRuntimeQueues) {
|
|
2209
|
+
return cachedRuntimeQueues;
|
|
2210
|
+
}
|
|
2211
|
+
if (registeredServerConfig?.queues) {
|
|
2212
|
+
cachedRuntimeQueues = stripNonSerializableQueues(registeredServerConfig.queues);
|
|
2213
|
+
return cachedRuntimeQueues;
|
|
2214
|
+
}
|
|
2215
|
+
const fileConfig = loadRuntimeConfigFile();
|
|
2216
|
+
if (fileConfig?.queues) {
|
|
2217
|
+
cachedRuntimeQueues = fileConfig.queues;
|
|
2218
|
+
return cachedRuntimeQueues;
|
|
2219
|
+
}
|
|
2220
|
+
return {};
|
|
2221
|
+
}
|
|
2222
|
+
function stripNonSerializableQueues(queues) {
|
|
2223
|
+
const result = {};
|
|
2224
|
+
for (const [name, config] of Object.entries(queues)) {
|
|
2225
|
+
const { shouldRetry: _shouldRetry, ...serializable } = config;
|
|
2226
|
+
result[name] = serializable;
|
|
2227
|
+
}
|
|
2228
|
+
return result;
|
|
2229
|
+
}
|
|
2230
|
+
function getQueueConfig(queueName) {
|
|
2231
|
+
return getQueueDefinitions()[queueName];
|
|
2232
|
+
}
|
|
2233
|
+
function getQueueConfigWithRetry(queueName) {
|
|
2234
|
+
const fromServer = registeredServerConfig?.queues?.[queueName];
|
|
2235
|
+
if (fromServer) {
|
|
2236
|
+
return fromServer;
|
|
2237
|
+
}
|
|
2238
|
+
const serializable = getQueueConfig(queueName);
|
|
2239
|
+
return serializable;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2166
2242
|
// src/server/utils/schema.ts
|
|
2167
2243
|
var z8 = __toESM(require("zod"));
|
|
2168
2244
|
function normalizeBilling(billing) {
|
|
@@ -3484,6 +3560,35 @@ var report = {
|
|
|
3484
3560
|
}
|
|
3485
3561
|
};
|
|
3486
3562
|
|
|
3563
|
+
// src/ratelimit/context.ts
|
|
3564
|
+
var import_async_hooks2 = require("async_hooks");
|
|
3565
|
+
var executionContextStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
3566
|
+
var activeOperationStorage = new import_async_hooks2.AsyncLocalStorage();
|
|
3567
|
+
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
3568
|
+
return executionContextStorage.run(ctx, fn);
|
|
3569
|
+
}
|
|
3570
|
+
function getRateLimitExecutionContext() {
|
|
3571
|
+
return executionContextStorage.getStore();
|
|
3572
|
+
}
|
|
3573
|
+
function runWithActiveQueuedOperation(operation, fn) {
|
|
3574
|
+
return activeOperationStorage.run(operation, fn);
|
|
3575
|
+
}
|
|
3576
|
+
function getActiveQueuedOperation() {
|
|
3577
|
+
return activeOperationStorage.getStore();
|
|
3578
|
+
}
|
|
3579
|
+
function setActiveQueuedOperationLease(lease) {
|
|
3580
|
+
const op = activeOperationStorage.getStore();
|
|
3581
|
+
if (op) {
|
|
3582
|
+
op.lease = lease;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
function updateActiveQueuedOperationAttempt(attempt) {
|
|
3586
|
+
const op = activeOperationStorage.getStore();
|
|
3587
|
+
if (op) {
|
|
3588
|
+
op.attempt = attempt;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3487
3592
|
// src/errors.ts
|
|
3488
3593
|
var InstallError = class extends Error {
|
|
3489
3594
|
// Optional: which field caused the error
|
|
@@ -3538,8 +3643,8 @@ var AppAuthInvalidError = class extends Error {
|
|
|
3538
3643
|
};
|
|
3539
3644
|
|
|
3540
3645
|
// src/server/context-logger.ts
|
|
3541
|
-
var
|
|
3542
|
-
var logContextStorage = new
|
|
3646
|
+
var import_async_hooks3 = require("async_hooks");
|
|
3647
|
+
var logContextStorage = new import_async_hooks3.AsyncLocalStorage();
|
|
3543
3648
|
function runWithLogContext(context, fn) {
|
|
3544
3649
|
return logContextStorage.run(context, fn);
|
|
3545
3650
|
}
|
|
@@ -3752,9 +3857,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
3752
3857
|
baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
|
|
3753
3858
|
apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
|
|
3754
3859
|
};
|
|
3860
|
+
const rateLimitContext = {
|
|
3861
|
+
app,
|
|
3862
|
+
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
3863
|
+
invocation,
|
|
3864
|
+
isProvisionContext: trigger === "provision"
|
|
3865
|
+
};
|
|
3755
3866
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
3756
|
-
return await
|
|
3757
|
-
return await
|
|
3867
|
+
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
3868
|
+
return await runWithLogContext({ invocation }, async () => {
|
|
3869
|
+
return await fn(inputs, executionContext);
|
|
3870
|
+
});
|
|
3758
3871
|
});
|
|
3759
3872
|
});
|
|
3760
3873
|
const billing = normalizeBilling(functionResult.billing);
|
|
@@ -3798,6 +3911,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
3798
3911
|
};
|
|
3799
3912
|
}
|
|
3800
3913
|
const errorMessage = error instanceof Error ? error.message : String(error ?? "");
|
|
3914
|
+
const errorCode = error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : "TOOL_EXECUTION_ERROR";
|
|
3801
3915
|
return {
|
|
3802
3916
|
output: null,
|
|
3803
3917
|
billing: { credits: 0 },
|
|
@@ -3807,7 +3921,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
3807
3921
|
toolName
|
|
3808
3922
|
},
|
|
3809
3923
|
error: {
|
|
3810
|
-
code:
|
|
3924
|
+
code: errorCode,
|
|
3811
3925
|
message: errorMessage
|
|
3812
3926
|
}
|
|
3813
3927
|
};
|
|
@@ -3891,7 +4005,7 @@ function printStartupLog(config, tools, port) {
|
|
|
3891
4005
|
|
|
3892
4006
|
// src/server/route-handlers/adapters.ts
|
|
3893
4007
|
function fromLambdaEvent(event2) {
|
|
3894
|
-
const
|
|
4008
|
+
const path6 = event2.path || event2.rawPath || "/";
|
|
3895
4009
|
const method = event2.httpMethod || event2.requestContext?.http?.method || "POST";
|
|
3896
4010
|
const forwardedProto = event2.headers?.["x-forwarded-proto"] ?? event2.headers?.["X-Forwarded-Proto"];
|
|
3897
4011
|
const protocol = forwardedProto ?? "https";
|
|
@@ -3899,9 +4013,9 @@ function fromLambdaEvent(event2) {
|
|
|
3899
4013
|
const queryString = event2.queryStringParameters ? "?" + new URLSearchParams(
|
|
3900
4014
|
event2.queryStringParameters
|
|
3901
4015
|
).toString() : "";
|
|
3902
|
-
const url = `${protocol}://${host}${
|
|
4016
|
+
const url = `${protocol}://${host}${path6}${queryString}`;
|
|
3903
4017
|
return {
|
|
3904
|
-
path:
|
|
4018
|
+
path: path6,
|
|
3905
4019
|
method,
|
|
3906
4020
|
headers: event2.headers,
|
|
3907
4021
|
query: event2.queryStringParameters ?? {},
|
|
@@ -3983,8 +4097,8 @@ function parseBodyByContentType(req) {
|
|
|
3983
4097
|
}
|
|
3984
4098
|
|
|
3985
4099
|
// src/server/route-handlers/handlers.ts
|
|
3986
|
-
var
|
|
3987
|
-
var
|
|
4100
|
+
var fs2 = __toESM(require("fs"));
|
|
4101
|
+
var path2 = __toESM(require("path"));
|
|
3988
4102
|
|
|
3989
4103
|
// src/server/core-api-handler.ts
|
|
3990
4104
|
async function handleCoreMethod(method, params) {
|
|
@@ -4152,7 +4266,8 @@ function serializeConfig(config) {
|
|
|
4152
4266
|
type: w.type ?? "WEBHOOK"
|
|
4153
4267
|
})) : [],
|
|
4154
4268
|
provision: isProvisionConfig(config.provision) ? config.provision : void 0,
|
|
4155
|
-
agents: config.agents
|
|
4269
|
+
agents: config.agents,
|
|
4270
|
+
queues: config.queues
|
|
4156
4271
|
};
|
|
4157
4272
|
}
|
|
4158
4273
|
function isProvisionConfig(value) {
|
|
@@ -4448,7 +4563,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
|
|
|
4448
4563
|
}
|
|
4449
4564
|
|
|
4450
4565
|
// src/server/handlers/webhook-handler.ts
|
|
4451
|
-
function parseWebhookRequest(parsedBody, method, url,
|
|
4566
|
+
function parseWebhookRequest(parsedBody, method, url, path6, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
4452
4567
|
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
4453
4568
|
if (isEnvelope) {
|
|
4454
4569
|
const envelope = parsedBody;
|
|
@@ -4502,7 +4617,7 @@ function parseWebhookRequest(parsedBody, method, url, path5, headers, query, raw
|
|
|
4502
4617
|
const webhookRequest = {
|
|
4503
4618
|
method,
|
|
4504
4619
|
url,
|
|
4505
|
-
path:
|
|
4620
|
+
path: path6,
|
|
4506
4621
|
headers,
|
|
4507
4622
|
query,
|
|
4508
4623
|
body: parsedBody,
|
|
@@ -4560,7 +4675,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
|
|
|
4560
4675
|
|
|
4561
4676
|
// src/server/route-handlers/handlers.ts
|
|
4562
4677
|
function getConfigFilePath() {
|
|
4563
|
-
return process.env.LAMBDA_TASK_ROOT ?
|
|
4678
|
+
return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
|
|
4564
4679
|
}
|
|
4565
4680
|
function findToolInRegistry(registry, toolName) {
|
|
4566
4681
|
for (const [key, t] of Object.entries(registry)) {
|
|
@@ -4599,8 +4714,8 @@ function handleHealthRoute(ctx) {
|
|
|
4599
4714
|
function handleConfigRoute(ctx) {
|
|
4600
4715
|
const configFilePath = getConfigFilePath();
|
|
4601
4716
|
try {
|
|
4602
|
-
if (
|
|
4603
|
-
const fileConfig = JSON.parse(
|
|
4717
|
+
if (fs2.existsSync(configFilePath)) {
|
|
4718
|
+
const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
|
|
4604
4719
|
return { status: 200, body: fileConfig };
|
|
4605
4720
|
}
|
|
4606
4721
|
} catch (err) {
|
|
@@ -5285,6 +5400,7 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
5285
5400
|
installContextLogger();
|
|
5286
5401
|
function createSkedyulServer(config) {
|
|
5287
5402
|
mergeRuntimeEnv();
|
|
5403
|
+
registerQueueConfig(config);
|
|
5288
5404
|
const registry = config.tools;
|
|
5289
5405
|
const webhookRegistry = config.webhooks;
|
|
5290
5406
|
if (config.coreApi?.service) {
|
|
@@ -5553,8 +5669,8 @@ function defineNavigation(navigation) {
|
|
|
5553
5669
|
}
|
|
5554
5670
|
|
|
5555
5671
|
// src/config/loader.ts
|
|
5556
|
-
var
|
|
5557
|
-
var
|
|
5672
|
+
var fs3 = __toESM(require("fs"));
|
|
5673
|
+
var path3 = __toESM(require("path"));
|
|
5558
5674
|
var os = __toESM(require("os"));
|
|
5559
5675
|
var CONFIG_FILE_NAMES = [
|
|
5560
5676
|
"skedyul.config.ts",
|
|
@@ -5563,13 +5679,13 @@ var CONFIG_FILE_NAMES = [
|
|
|
5563
5679
|
"skedyul.config.cjs"
|
|
5564
5680
|
];
|
|
5565
5681
|
async function transpileTypeScript(filePath) {
|
|
5566
|
-
const content =
|
|
5567
|
-
const configDir =
|
|
5682
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
5683
|
+
const configDir = path3.dirname(path3.resolve(filePath));
|
|
5568
5684
|
let transpiled = content.replace(/import\s+type\s+\{[^}]+\}\s+from\s+['"][^'"]+['"]\s*;?\n?/g, "").replace(/import\s+\{\s*defineConfig\s*\}\s+from\s+['"]skedyul['"]\s*;?\n?/g, "").replace(/:\s*SkedyulConfig/g, "").replace(/export\s+default\s+/, "module.exports = ").replace(/defineConfig\s*\(\s*\{/, "{").replace(/\}\s*\)\s*;?\s*$/, "}");
|
|
5569
5685
|
transpiled = transpiled.replace(
|
|
5570
5686
|
/import\s+(\w+)\s+from\s+['"](\.[^'"]+)['"]\s*(?:with\s*\{[^}]*\})?/g,
|
|
5571
5687
|
(match, varName, relativePath) => {
|
|
5572
|
-
const absolutePath =
|
|
5688
|
+
const absolutePath = path3.resolve(configDir, relativePath);
|
|
5573
5689
|
return `const ${varName} = require('${absolutePath.replace(/\\/g, "/")}')`;
|
|
5574
5690
|
}
|
|
5575
5691
|
);
|
|
@@ -5577,8 +5693,8 @@ async function transpileTypeScript(filePath) {
|
|
|
5577
5693
|
return transpiled;
|
|
5578
5694
|
}
|
|
5579
5695
|
async function loadConfig(configPath) {
|
|
5580
|
-
const absolutePath =
|
|
5581
|
-
if (!
|
|
5696
|
+
const absolutePath = path3.resolve(configPath);
|
|
5697
|
+
if (!fs3.existsSync(absolutePath)) {
|
|
5582
5698
|
throw new Error(`Config file not found: ${absolutePath}`);
|
|
5583
5699
|
}
|
|
5584
5700
|
const isTypeScript = absolutePath.endsWith(".ts");
|
|
@@ -5587,8 +5703,8 @@ async function loadConfig(configPath) {
|
|
|
5587
5703
|
if (isTypeScript) {
|
|
5588
5704
|
const transpiled = await transpileTypeScript(absolutePath);
|
|
5589
5705
|
const tempDir = os.tmpdir();
|
|
5590
|
-
const tempFile =
|
|
5591
|
-
|
|
5706
|
+
const tempFile = path3.join(tempDir, `skedyul-config-${Date.now()}.cjs`);
|
|
5707
|
+
fs3.writeFileSync(tempFile, transpiled);
|
|
5592
5708
|
moduleToLoad = tempFile;
|
|
5593
5709
|
try {
|
|
5594
5710
|
const module3 = require(moduleToLoad);
|
|
@@ -5602,7 +5718,7 @@ async function loadConfig(configPath) {
|
|
|
5602
5718
|
return config2;
|
|
5603
5719
|
} finally {
|
|
5604
5720
|
try {
|
|
5605
|
-
|
|
5721
|
+
fs3.unlinkSync(tempFile);
|
|
5606
5722
|
} catch {
|
|
5607
5723
|
}
|
|
5608
5724
|
}
|
|
@@ -5637,13 +5753,13 @@ function validateConfig(config) {
|
|
|
5637
5753
|
}
|
|
5638
5754
|
|
|
5639
5755
|
// src/config/schema-loader.ts
|
|
5640
|
-
var
|
|
5641
|
-
var
|
|
5756
|
+
var fs4 = __toESM(require("fs"));
|
|
5757
|
+
var path4 = __toESM(require("path"));
|
|
5642
5758
|
var os2 = __toESM(require("os"));
|
|
5643
5759
|
|
|
5644
5760
|
// src/config/resolver.ts
|
|
5645
|
-
var
|
|
5646
|
-
var
|
|
5761
|
+
var fs5 = __toESM(require("fs"));
|
|
5762
|
+
var path5 = __toESM(require("path"));
|
|
5647
5763
|
|
|
5648
5764
|
// src/config/utils.ts
|
|
5649
5765
|
function getAllEnvKeys(config) {
|
|
@@ -5660,6 +5776,553 @@ function getRequiredInstallEnvKeys(config) {
|
|
|
5660
5776
|
return Object.entries(provision.env).filter(([, def]) => def.required).map(([key]) => key);
|
|
5661
5777
|
}
|
|
5662
5778
|
|
|
5779
|
+
// src/ratelimit/errors.ts
|
|
5780
|
+
var QueueContextError = class extends Error {
|
|
5781
|
+
constructor(message) {
|
|
5782
|
+
super(message);
|
|
5783
|
+
this.code = "QUEUE_CONTEXT_ERROR";
|
|
5784
|
+
this.name = "QueueContextError";
|
|
5785
|
+
}
|
|
5786
|
+
};
|
|
5787
|
+
var QueueNotFoundError = class extends Error {
|
|
5788
|
+
constructor(queueName) {
|
|
5789
|
+
super(`Queue "${queueName}" is not defined in skedyul.config queues`);
|
|
5790
|
+
this.code = "QUEUE_NOT_FOUND";
|
|
5791
|
+
this.name = "QueueNotFoundError";
|
|
5792
|
+
}
|
|
5793
|
+
};
|
|
5794
|
+
var QueuedFetchExhaustedError = class extends Error {
|
|
5795
|
+
constructor(attempts, maxRetries, causeError) {
|
|
5796
|
+
super(
|
|
5797
|
+
`queuedFetch exhausted retries after ${attempts} attempts (maxRetries=${maxRetries})`
|
|
5798
|
+
);
|
|
5799
|
+
this.code = "QUEUED_FETCH_EXHAUSTED";
|
|
5800
|
+
this.name = "QueuedFetchExhaustedError";
|
|
5801
|
+
this.attempts = attempts;
|
|
5802
|
+
this.maxRetries = maxRetries;
|
|
5803
|
+
this.causeError = causeError;
|
|
5804
|
+
}
|
|
5805
|
+
};
|
|
5806
|
+
var RequeueOutsideContextError = class extends Error {
|
|
5807
|
+
constructor() {
|
|
5808
|
+
super("requeue() can only be called inside an active queuedFetch operation");
|
|
5809
|
+
this.code = "REQUEUE_OUTSIDE_CONTEXT";
|
|
5810
|
+
this.name = "RequeueOutsideContextError";
|
|
5811
|
+
}
|
|
5812
|
+
};
|
|
5813
|
+
var RateLimitBackendError = class extends Error {
|
|
5814
|
+
constructor(message, statusCode) {
|
|
5815
|
+
super(message);
|
|
5816
|
+
this.code = "RATE_LIMIT_BACKEND_ERROR";
|
|
5817
|
+
this.name = "RateLimitBackendError";
|
|
5818
|
+
this.statusCode = statusCode;
|
|
5819
|
+
}
|
|
5820
|
+
};
|
|
5821
|
+
|
|
5822
|
+
// src/ratelimit/resolve-queue-key.ts
|
|
5823
|
+
function resolveEndpointHandle(config, invocation) {
|
|
5824
|
+
if (config.endpoint) {
|
|
5825
|
+
return config.endpoint;
|
|
5826
|
+
}
|
|
5827
|
+
if (invocation?.toolHandle) {
|
|
5828
|
+
return invocation.toolHandle;
|
|
5829
|
+
}
|
|
5830
|
+
if (invocation?.serverHookHandle) {
|
|
5831
|
+
return invocation.serverHookHandle;
|
|
5832
|
+
}
|
|
5833
|
+
throw new QueueContextError(
|
|
5834
|
+
`Cannot resolve endpoint handle for queue scope "${config.scope}". Set endpoint in queue config or ensure invocation.toolHandle/serverHookHandle is available.`
|
|
5835
|
+
);
|
|
5836
|
+
}
|
|
5837
|
+
function appendSubKey(base, subKey) {
|
|
5838
|
+
return subKey ? `${base}:${subKey}` : base;
|
|
5839
|
+
}
|
|
5840
|
+
function resolveQueueKey(queueName, config, ctx, subKey) {
|
|
5841
|
+
const { app, appInstallationId, invocation, isProvisionContext: isProvisionContext2 } = ctx;
|
|
5842
|
+
switch (config.scope) {
|
|
5843
|
+
case "provision": {
|
|
5844
|
+
const base = `rl:pv:${app.versionId}:${queueName}`;
|
|
5845
|
+
return appendSubKey(base, subKey);
|
|
5846
|
+
}
|
|
5847
|
+
case "install": {
|
|
5848
|
+
if (!appInstallationId) {
|
|
5849
|
+
throw new QueueContextError(
|
|
5850
|
+
`Queue "${queueName}" with scope "install" requires appInstallationId in context`
|
|
5851
|
+
);
|
|
5852
|
+
}
|
|
5853
|
+
const base = `rl:in:${appInstallationId}:${queueName}`;
|
|
5854
|
+
return appendSubKey(base, subKey);
|
|
5855
|
+
}
|
|
5856
|
+
case "provision_endpoint": {
|
|
5857
|
+
if (!isProvisionContext2 && appInstallationId) {
|
|
5858
|
+
throw new QueueContextError(
|
|
5859
|
+
`Queue "${queueName}" with scope "provision_endpoint" requires provision context (no install)`
|
|
5860
|
+
);
|
|
5861
|
+
}
|
|
5862
|
+
const endpointHandle = resolveEndpointHandle(config, invocation);
|
|
5863
|
+
const base = `rl:pep:${app.versionId}:${endpointHandle}:${queueName}`;
|
|
5864
|
+
return appendSubKey(base, subKey);
|
|
5865
|
+
}
|
|
5866
|
+
case "install_endpoint": {
|
|
5867
|
+
if (!appInstallationId) {
|
|
5868
|
+
throw new QueueContextError(
|
|
5869
|
+
`Queue "${queueName}" with scope "install_endpoint" requires appInstallationId in context`
|
|
5870
|
+
);
|
|
5871
|
+
}
|
|
5872
|
+
const endpointHandle = resolveEndpointHandle(config, invocation);
|
|
5873
|
+
const base = `rl:iep:${appInstallationId}:${endpointHandle}:${queueName}`;
|
|
5874
|
+
return appendSubKey(base, subKey);
|
|
5875
|
+
}
|
|
5876
|
+
case "global": {
|
|
5877
|
+
const base = `rl:gl:${app.versionId}:${queueName}`;
|
|
5878
|
+
return appendSubKey(base, subKey);
|
|
5879
|
+
}
|
|
5880
|
+
default: {
|
|
5881
|
+
const _exhaustive = config.scope;
|
|
5882
|
+
throw new QueueContextError(`Unknown queue scope: ${String(_exhaustive)}`);
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
function toQueueLimits(config) {
|
|
5887
|
+
return {
|
|
5888
|
+
maxConcurrent: config.maxConcurrent,
|
|
5889
|
+
minTime: config.minTime,
|
|
5890
|
+
reservoir: config.reservoir,
|
|
5891
|
+
reservoirRefreshAmount: config.reservoirRefreshAmount,
|
|
5892
|
+
reservoirRefreshInterval: config.reservoirRefreshInterval
|
|
5893
|
+
};
|
|
5894
|
+
}
|
|
5895
|
+
|
|
5896
|
+
// src/ratelimit/backends/platform.ts
|
|
5897
|
+
function getApiBaseUrl() {
|
|
5898
|
+
const config = getConfig();
|
|
5899
|
+
if (config.baseUrl) {
|
|
5900
|
+
return config.baseUrl.replace(/\/+$/, "");
|
|
5901
|
+
}
|
|
5902
|
+
return (process.env.SKEDYUL_API_URL ?? process.env.SKEDYUL_NODE_URL ?? "").replace(/\/+$/, "");
|
|
5903
|
+
}
|
|
5904
|
+
function getApiToken() {
|
|
5905
|
+
const config = getConfig();
|
|
5906
|
+
return config.apiToken || process.env.SKEDYUL_API_TOKEN || "";
|
|
5907
|
+
}
|
|
5908
|
+
async function callRateLimitApi(path6, body) {
|
|
5909
|
+
const baseUrl = getApiBaseUrl();
|
|
5910
|
+
const token2 = getApiToken();
|
|
5911
|
+
if (!baseUrl || !token2) {
|
|
5912
|
+
throw new RateLimitBackendError(
|
|
5913
|
+
"SKEDYUL_API_URL and SKEDYUL_API_TOKEN are required for platform queue coordination"
|
|
5914
|
+
);
|
|
5915
|
+
}
|
|
5916
|
+
const response = await fetch(`${baseUrl}/api/internal/ratelimit/${path6}`, {
|
|
5917
|
+
method: "POST",
|
|
5918
|
+
headers: {
|
|
5919
|
+
Authorization: `Bearer ${token2}`,
|
|
5920
|
+
"Content-Type": "application/json"
|
|
5921
|
+
},
|
|
5922
|
+
body: JSON.stringify(body)
|
|
5923
|
+
});
|
|
5924
|
+
const payload = await response.json().catch(() => null);
|
|
5925
|
+
if (!response.ok || payload?.success === false) {
|
|
5926
|
+
const message = payload?.errors?.[0]?.message ?? `Queue coordination API ${path6} failed with status ${response.status}`;
|
|
5927
|
+
throw new RateLimitBackendError(message, response.status);
|
|
5928
|
+
}
|
|
5929
|
+
if (!payload?.data) {
|
|
5930
|
+
throw new RateLimitBackendError(`Queue coordination API ${path6} returned empty data`);
|
|
5931
|
+
}
|
|
5932
|
+
return payload.data;
|
|
5933
|
+
}
|
|
5934
|
+
var PlatformRateLimitBackend = class {
|
|
5935
|
+
async acquire(queueKey, limits, timeoutMs) {
|
|
5936
|
+
const data = await callRateLimitApi("acquire", {
|
|
5937
|
+
queueKey,
|
|
5938
|
+
limits,
|
|
5939
|
+
timeoutMs
|
|
5940
|
+
});
|
|
5941
|
+
return {
|
|
5942
|
+
leaseId: data.leaseId,
|
|
5943
|
+
acquiredAt: data.acquiredAt,
|
|
5944
|
+
queueKey: data.queueKey
|
|
5945
|
+
};
|
|
5946
|
+
}
|
|
5947
|
+
async release(lease) {
|
|
5948
|
+
await callRateLimitApi("release", {
|
|
5949
|
+
leaseId: lease.leaseId
|
|
5950
|
+
});
|
|
5951
|
+
}
|
|
5952
|
+
};
|
|
5953
|
+
var platformRateLimitBackend = new PlatformRateLimitBackend();
|
|
5954
|
+
|
|
5955
|
+
// src/ratelimit/backends/memory.ts
|
|
5956
|
+
var DEFAULT_MAX_CONCURRENT = 10;
|
|
5957
|
+
function getMaxConcurrent(limits) {
|
|
5958
|
+
return limits.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
5959
|
+
}
|
|
5960
|
+
function getMinTime(limits) {
|
|
5961
|
+
return limits.minTime ?? 0;
|
|
5962
|
+
}
|
|
5963
|
+
function generateLeaseId() {
|
|
5964
|
+
return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
5965
|
+
}
|
|
5966
|
+
function refillTokens(state, limits) {
|
|
5967
|
+
if (limits.reservoir === void 0) {
|
|
5968
|
+
return;
|
|
5969
|
+
}
|
|
5970
|
+
const capacity = limits.reservoir;
|
|
5971
|
+
const refreshAmount = limits.reservoirRefreshAmount ?? limits.reservoir;
|
|
5972
|
+
const refreshInterval = limits.reservoirRefreshInterval ?? 6e4;
|
|
5973
|
+
const now = Date.now();
|
|
5974
|
+
const elapsed = now - state.lastRefillAt;
|
|
5975
|
+
if (elapsed >= refreshInterval) {
|
|
5976
|
+
const intervals = Math.floor(elapsed / refreshInterval);
|
|
5977
|
+
state.tokens = Math.min(capacity, state.tokens + intervals * refreshAmount);
|
|
5978
|
+
state.lastRefillAt = now;
|
|
5979
|
+
}
|
|
5980
|
+
}
|
|
5981
|
+
function canAcquire(state, limits) {
|
|
5982
|
+
if (state.running >= getMaxConcurrent(limits)) {
|
|
5983
|
+
return false;
|
|
5984
|
+
}
|
|
5985
|
+
refillTokens(state, limits);
|
|
5986
|
+
if (limits.reservoir !== void 0 && state.tokens <= 0) {
|
|
5987
|
+
return false;
|
|
5988
|
+
}
|
|
5989
|
+
const minTime = getMinTime(limits);
|
|
5990
|
+
if (minTime > 0 && state.lastStartAt > 0) {
|
|
5991
|
+
if (Date.now() - state.lastStartAt < minTime) {
|
|
5992
|
+
return false;
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5995
|
+
return true;
|
|
5996
|
+
}
|
|
5997
|
+
var MemoryRateLimitBackend = class {
|
|
5998
|
+
constructor() {
|
|
5999
|
+
this.queues = /* @__PURE__ */ new Map();
|
|
6000
|
+
this.leases = /* @__PURE__ */ new Map();
|
|
6001
|
+
}
|
|
6002
|
+
getState(queueKey) {
|
|
6003
|
+
let state = this.queues.get(queueKey);
|
|
6004
|
+
if (!state) {
|
|
6005
|
+
state = {
|
|
6006
|
+
running: 0,
|
|
6007
|
+
waiters: [],
|
|
6008
|
+
lastStartAt: 0,
|
|
6009
|
+
tokens: Number.POSITIVE_INFINITY,
|
|
6010
|
+
lastRefillAt: Date.now()
|
|
6011
|
+
};
|
|
6012
|
+
if (queueKey.includes(":")) {
|
|
6013
|
+
}
|
|
6014
|
+
this.queues.set(queueKey, state);
|
|
6015
|
+
}
|
|
6016
|
+
return state;
|
|
6017
|
+
}
|
|
6018
|
+
grant(state, queueKey, limits) {
|
|
6019
|
+
if (limits.reservoir !== void 0) {
|
|
6020
|
+
state.tokens -= 1;
|
|
6021
|
+
}
|
|
6022
|
+
state.running += 1;
|
|
6023
|
+
state.lastStartAt = Date.now();
|
|
6024
|
+
const leaseId = generateLeaseId();
|
|
6025
|
+
this.leases.set(leaseId, { queueKey, limits });
|
|
6026
|
+
return { leaseId, acquiredAt: Date.now(), queueKey };
|
|
6027
|
+
}
|
|
6028
|
+
drainWaiters(queueKey, state) {
|
|
6029
|
+
let progressed = true;
|
|
6030
|
+
while (progressed && state.waiters.length > 0) {
|
|
6031
|
+
progressed = false;
|
|
6032
|
+
for (let i = 0; i < state.waiters.length; i += 1) {
|
|
6033
|
+
const waiter = state.waiters[i];
|
|
6034
|
+
if (!waiter || !canAcquire(state, waiter.limits)) {
|
|
6035
|
+
continue;
|
|
6036
|
+
}
|
|
6037
|
+
state.waiters.splice(i, 1);
|
|
6038
|
+
if (waiter.timer) {
|
|
6039
|
+
clearTimeout(waiter.timer);
|
|
6040
|
+
}
|
|
6041
|
+
waiter.resolve(this.grant(state, queueKey, waiter.limits));
|
|
6042
|
+
progressed = true;
|
|
6043
|
+
break;
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
6047
|
+
async acquire(queueKey, limits, timeoutMs = 12e4) {
|
|
6048
|
+
const state = this.getState(queueKey);
|
|
6049
|
+
if (limits.reservoir !== void 0 && state.tokens === Number.POSITIVE_INFINITY) {
|
|
6050
|
+
state.tokens = limits.reservoir;
|
|
6051
|
+
}
|
|
6052
|
+
if (canAcquire(state, limits)) {
|
|
6053
|
+
return this.grant(state, queueKey, limits);
|
|
6054
|
+
}
|
|
6055
|
+
return new Promise((resolve4, reject) => {
|
|
6056
|
+
const waiter = {
|
|
6057
|
+
resolve: resolve4,
|
|
6058
|
+
reject,
|
|
6059
|
+
timer: null,
|
|
6060
|
+
limits
|
|
6061
|
+
};
|
|
6062
|
+
if (timeoutMs > 0) {
|
|
6063
|
+
waiter.timer = setTimeout(() => {
|
|
6064
|
+
const idx = state.waiters.indexOf(waiter);
|
|
6065
|
+
if (idx >= 0) {
|
|
6066
|
+
state.waiters.splice(idx, 1);
|
|
6067
|
+
}
|
|
6068
|
+
reject(
|
|
6069
|
+
new Error(`Queue slot acquire timed out after ${timeoutMs}ms for ${queueKey}`)
|
|
6070
|
+
);
|
|
6071
|
+
}, timeoutMs);
|
|
6072
|
+
}
|
|
6073
|
+
state.waiters.push(waiter);
|
|
6074
|
+
const retry = () => {
|
|
6075
|
+
if (!state.waiters.includes(waiter)) {
|
|
6076
|
+
return;
|
|
6077
|
+
}
|
|
6078
|
+
if (canAcquire(state, limits)) {
|
|
6079
|
+
const idx = state.waiters.indexOf(waiter);
|
|
6080
|
+
if (idx >= 0) {
|
|
6081
|
+
state.waiters.splice(idx, 1);
|
|
6082
|
+
}
|
|
6083
|
+
if (waiter.timer) {
|
|
6084
|
+
clearTimeout(waiter.timer);
|
|
6085
|
+
}
|
|
6086
|
+
resolve4(this.grant(state, queueKey, limits));
|
|
6087
|
+
return;
|
|
6088
|
+
}
|
|
6089
|
+
setTimeout(retry, Math.max(getMinTime(limits), 10));
|
|
6090
|
+
};
|
|
6091
|
+
setTimeout(retry, Math.max(getMinTime(limits), 10));
|
|
6092
|
+
});
|
|
6093
|
+
}
|
|
6094
|
+
async release(lease) {
|
|
6095
|
+
const tracked = this.leases.get(lease.leaseId);
|
|
6096
|
+
this.leases.delete(lease.leaseId);
|
|
6097
|
+
const queueKey = tracked?.queueKey ?? lease.queueKey;
|
|
6098
|
+
const state = this.queues.get(queueKey);
|
|
6099
|
+
if (!state) {
|
|
6100
|
+
return;
|
|
6101
|
+
}
|
|
6102
|
+
state.running = Math.max(0, state.running - 1);
|
|
6103
|
+
this.drainWaiters(queueKey, state);
|
|
6104
|
+
}
|
|
6105
|
+
};
|
|
6106
|
+
var memoryRateLimitBackend = new MemoryRateLimitBackend();
|
|
6107
|
+
|
|
6108
|
+
// src/ratelimit/backends/index.ts
|
|
6109
|
+
var cachedBackend = null;
|
|
6110
|
+
function shouldForceMemoryBackend() {
|
|
6111
|
+
const flag = process.env.SKEDYUL_RATE_LIMIT_MEMORY;
|
|
6112
|
+
return flag === "1" || flag === "true";
|
|
6113
|
+
}
|
|
6114
|
+
function getRateLimitBackend() {
|
|
6115
|
+
if (cachedBackend) {
|
|
6116
|
+
return cachedBackend;
|
|
6117
|
+
}
|
|
6118
|
+
if (shouldForceMemoryBackend()) {
|
|
6119
|
+
cachedBackend = memoryRateLimitBackend;
|
|
6120
|
+
return cachedBackend;
|
|
6121
|
+
}
|
|
6122
|
+
cachedBackend = createResilientBackend();
|
|
6123
|
+
return cachedBackend;
|
|
6124
|
+
}
|
|
6125
|
+
function createResilientBackend() {
|
|
6126
|
+
return {
|
|
6127
|
+
async acquire(queueKey, limits, timeoutMs) {
|
|
6128
|
+
try {
|
|
6129
|
+
return await platformRateLimitBackend.acquire(queueKey, limits, timeoutMs);
|
|
6130
|
+
} catch (error) {
|
|
6131
|
+
if (shouldFallbackToMemory(error)) {
|
|
6132
|
+
return memoryRateLimitBackend.acquire(queueKey, limits, timeoutMs);
|
|
6133
|
+
}
|
|
6134
|
+
throw error;
|
|
6135
|
+
}
|
|
6136
|
+
},
|
|
6137
|
+
async release(lease) {
|
|
6138
|
+
if (lease.leaseId.startsWith("mem_")) {
|
|
6139
|
+
return memoryRateLimitBackend.release(lease);
|
|
6140
|
+
}
|
|
6141
|
+
try {
|
|
6142
|
+
await platformRateLimitBackend.release(lease);
|
|
6143
|
+
} catch (error) {
|
|
6144
|
+
if (shouldFallbackToMemory(error)) {
|
|
6145
|
+
return memoryRateLimitBackend.release(lease);
|
|
6146
|
+
}
|
|
6147
|
+
throw error;
|
|
6148
|
+
}
|
|
6149
|
+
}
|
|
6150
|
+
};
|
|
6151
|
+
}
|
|
6152
|
+
function shouldFallbackToMemory(error) {
|
|
6153
|
+
if (shouldForceMemoryBackend()) {
|
|
6154
|
+
return true;
|
|
6155
|
+
}
|
|
6156
|
+
if (error instanceof RateLimitBackendError) {
|
|
6157
|
+
if (error.statusCode === 404 || error.statusCode === 503) {
|
|
6158
|
+
return true;
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
if (error instanceof TypeError) {
|
|
6162
|
+
return true;
|
|
6163
|
+
}
|
|
6164
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
6165
|
+
return nodeEnv === "development" || nodeEnv === "test";
|
|
6166
|
+
}
|
|
6167
|
+
|
|
6168
|
+
// src/ratelimit/should-retry.ts
|
|
6169
|
+
var DEFAULT_RETRY_STATUS_CODES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
6170
|
+
function getErrorStatusCode(error) {
|
|
6171
|
+
if (typeof error !== "object" || error === null) {
|
|
6172
|
+
return void 0;
|
|
6173
|
+
}
|
|
6174
|
+
const record2 = error;
|
|
6175
|
+
if (typeof record2.statusCode === "number") {
|
|
6176
|
+
return record2.statusCode;
|
|
6177
|
+
}
|
|
6178
|
+
if (typeof record2.status === "number") {
|
|
6179
|
+
return record2.status;
|
|
6180
|
+
}
|
|
6181
|
+
return void 0;
|
|
6182
|
+
}
|
|
6183
|
+
function getErrorCode(error) {
|
|
6184
|
+
if (typeof error !== "object" || error === null) {
|
|
6185
|
+
return void 0;
|
|
6186
|
+
}
|
|
6187
|
+
const record2 = error;
|
|
6188
|
+
return typeof record2.code === "string" ? record2.code : void 0;
|
|
6189
|
+
}
|
|
6190
|
+
function defaultShouldRetry(error, _attempt) {
|
|
6191
|
+
const code = getErrorCode(error);
|
|
6192
|
+
if (code === "RATE_LIMITED" || code === "RATE_LIMIT_BACKEND_ERROR") {
|
|
6193
|
+
return true;
|
|
6194
|
+
}
|
|
6195
|
+
const status = getErrorStatusCode(error);
|
|
6196
|
+
if (status !== void 0 && DEFAULT_RETRY_STATUS_CODES.has(status)) {
|
|
6197
|
+
return true;
|
|
6198
|
+
}
|
|
6199
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
6200
|
+
const lower = message.toLowerCase();
|
|
6201
|
+
if (lower.includes("rate limit") || lower.includes("too many requests") || lower.includes("econnreset") || lower.includes("etimedout")) {
|
|
6202
|
+
return true;
|
|
6203
|
+
}
|
|
6204
|
+
return false;
|
|
6205
|
+
}
|
|
6206
|
+
function sleep(ms) {
|
|
6207
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
6208
|
+
}
|
|
6209
|
+
|
|
6210
|
+
// src/ratelimit/queued-fetch.ts
|
|
6211
|
+
function normalizeQueueInput(input) {
|
|
6212
|
+
if (typeof input === "string") {
|
|
6213
|
+
return { name: input };
|
|
6214
|
+
}
|
|
6215
|
+
return { name: input.queue, subKey: input.key };
|
|
6216
|
+
}
|
|
6217
|
+
function resolveQueue(queueInput, ctxOverride) {
|
|
6218
|
+
const ctx = ctxOverride ?? getRateLimitExecutionContext();
|
|
6219
|
+
if (!ctx?.app?.versionId) {
|
|
6220
|
+
throw new QueueContextError(
|
|
6221
|
+
"Cannot resolve queue without app context. Ensure queuedFetch runs inside a tool/hook handler."
|
|
6222
|
+
);
|
|
6223
|
+
}
|
|
6224
|
+
const { name, subKey } = normalizeQueueInput(queueInput);
|
|
6225
|
+
const config = getQueueConfigWithRetry(name);
|
|
6226
|
+
if (!config) {
|
|
6227
|
+
throw new QueueNotFoundError(name);
|
|
6228
|
+
}
|
|
6229
|
+
const queueKey = resolveQueueKey(name, config, ctx, subKey);
|
|
6230
|
+
return {
|
|
6231
|
+
name,
|
|
6232
|
+
queueKey,
|
|
6233
|
+
config,
|
|
6234
|
+
limits: toQueueLimits(config)
|
|
6235
|
+
};
|
|
6236
|
+
}
|
|
6237
|
+
function createQueueHandle(queueInput) {
|
|
6238
|
+
return {
|
|
6239
|
+
run(fn) {
|
|
6240
|
+
return queuedFetch(queueInput, fn);
|
|
6241
|
+
}
|
|
6242
|
+
};
|
|
6243
|
+
}
|
|
6244
|
+
async function queuedFetch(queueInput, fnOrPromise) {
|
|
6245
|
+
const fn = typeof fnOrPromise === "function" ? fnOrPromise : () => fnOrPromise;
|
|
6246
|
+
const resolved = resolveQueue(queueInput);
|
|
6247
|
+
const maxRetries = resolved.config.maxRetries ?? 0;
|
|
6248
|
+
const operation = {
|
|
6249
|
+
queueInput,
|
|
6250
|
+
fn,
|
|
6251
|
+
attempt: 0,
|
|
6252
|
+
resolved,
|
|
6253
|
+
lease: null
|
|
6254
|
+
};
|
|
6255
|
+
return runWithActiveQueuedOperation(
|
|
6256
|
+
operation,
|
|
6257
|
+
() => executeWithRetries(operation, maxRetries)
|
|
6258
|
+
);
|
|
6259
|
+
}
|
|
6260
|
+
async function executeWithRetries(operation, maxRetries) {
|
|
6261
|
+
const backend = getRateLimitBackend();
|
|
6262
|
+
const timeoutMs = operation.resolved.config.timeout ?? 12e4;
|
|
6263
|
+
const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
|
|
6264
|
+
const shouldRetryFn = getQueueConfigWithRetry(operation.resolved.name)?.shouldRetry ?? defaultShouldRetry;
|
|
6265
|
+
const lease = await backend.acquire(
|
|
6266
|
+
operation.resolved.queueKey,
|
|
6267
|
+
operation.resolved.limits,
|
|
6268
|
+
timeoutMs
|
|
6269
|
+
);
|
|
6270
|
+
operation.lease = lease;
|
|
6271
|
+
setActiveQueuedOperationLease(lease);
|
|
6272
|
+
try {
|
|
6273
|
+
return await operation.fn();
|
|
6274
|
+
} catch (error) {
|
|
6275
|
+
await backend.release(lease);
|
|
6276
|
+
operation.lease = null;
|
|
6277
|
+
setActiveQueuedOperationLease(null);
|
|
6278
|
+
if (shouldRetryFn(error, operation.attempt) && operation.attempt < maxRetries) {
|
|
6279
|
+
await sleep(retryDelayMs);
|
|
6280
|
+
operation.attempt += 1;
|
|
6281
|
+
updateActiveQueuedOperationAttempt(operation.attempt);
|
|
6282
|
+
return executeWithRetries(operation, maxRetries);
|
|
6283
|
+
}
|
|
6284
|
+
if (shouldRetryFn(error, operation.attempt) && operation.attempt >= maxRetries) {
|
|
6285
|
+
throw new QueuedFetchExhaustedError(
|
|
6286
|
+
operation.attempt + 1,
|
|
6287
|
+
maxRetries,
|
|
6288
|
+
error
|
|
6289
|
+
);
|
|
6290
|
+
}
|
|
6291
|
+
throw error;
|
|
6292
|
+
} finally {
|
|
6293
|
+
if (operation.lease) {
|
|
6294
|
+
await backend.release(operation.lease);
|
|
6295
|
+
operation.lease = null;
|
|
6296
|
+
setActiveQueuedOperationLease(null);
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
async function requeue() {
|
|
6301
|
+
const operation = getActiveQueuedOperation();
|
|
6302
|
+
if (!operation) {
|
|
6303
|
+
throw new RequeueOutsideContextError();
|
|
6304
|
+
}
|
|
6305
|
+
const maxRetries = operation.resolved.config.maxRetries ?? 0;
|
|
6306
|
+
const nextAttempt = operation.attempt + 1;
|
|
6307
|
+
if (nextAttempt > maxRetries) {
|
|
6308
|
+
throw new QueuedFetchExhaustedError(nextAttempt, maxRetries, void 0);
|
|
6309
|
+
}
|
|
6310
|
+
if (operation.lease) {
|
|
6311
|
+
const backend = getRateLimitBackend();
|
|
6312
|
+
await backend.release(operation.lease);
|
|
6313
|
+
operation.lease = null;
|
|
6314
|
+
setActiveQueuedOperationLease(null);
|
|
6315
|
+
}
|
|
6316
|
+
operation.attempt = nextAttempt;
|
|
6317
|
+
updateActiveQueuedOperationAttempt(nextAttempt);
|
|
6318
|
+
const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
|
|
6319
|
+
await sleep(retryDelayMs);
|
|
6320
|
+
return executeWithRetries(operation, maxRetries);
|
|
6321
|
+
}
|
|
6322
|
+
async function queuedFetchResponse(queueInput, url, init) {
|
|
6323
|
+
return queuedFetch(queueInput, () => fetch(url, init));
|
|
6324
|
+
}
|
|
6325
|
+
|
|
5663
6326
|
// src/triggers/types.ts
|
|
5664
6327
|
var import_v49 = require("zod/v4");
|
|
5665
6328
|
var CRMDataSchema = import_v49.z.object({
|
|
@@ -5764,16 +6427,16 @@ function evaluateTemplate(template, context) {
|
|
|
5764
6427
|
const templateRegex = /\{\{\s*([^}]+)\s*\}\}/g;
|
|
5765
6428
|
const singleMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
|
|
5766
6429
|
if (singleMatch) {
|
|
5767
|
-
const
|
|
5768
|
-
return resolvePath(context,
|
|
6430
|
+
const path6 = singleMatch[1].trim();
|
|
6431
|
+
return resolvePath(context, path6);
|
|
5769
6432
|
}
|
|
5770
|
-
return template.replace(templateRegex, (_,
|
|
5771
|
-
const value = resolvePath(context,
|
|
6433
|
+
return template.replace(templateRegex, (_, path6) => {
|
|
6434
|
+
const value = resolvePath(context, path6.trim());
|
|
5772
6435
|
return value === void 0 || value === null ? "" : String(value);
|
|
5773
6436
|
});
|
|
5774
6437
|
}
|
|
5775
|
-
function resolvePath(obj,
|
|
5776
|
-
const parts =
|
|
6438
|
+
function resolvePath(obj, path6) {
|
|
6439
|
+
const parts = path6.split(".");
|
|
5777
6440
|
let current = obj;
|
|
5778
6441
|
for (const part of parts) {
|
|
5779
6442
|
if (current === null || current === void 0) {
|
|
@@ -5794,14 +6457,14 @@ function evaluateCondition(condition, context) {
|
|
|
5794
6457
|
const expression = match[1].trim();
|
|
5795
6458
|
const eqMatch = expression.match(/^(.+?)\s*==\s*['"](.+)['"]$/);
|
|
5796
6459
|
if (eqMatch) {
|
|
5797
|
-
const [,
|
|
5798
|
-
const actualValue = resolvePath(context,
|
|
6460
|
+
const [, path6, expectedValue] = eqMatch;
|
|
6461
|
+
const actualValue = resolvePath(context, path6.trim());
|
|
5799
6462
|
return actualValue === expectedValue;
|
|
5800
6463
|
}
|
|
5801
6464
|
const neqMatch = expression.match(/^(.+?)\s*!=\s*['"](.+)['"]$/);
|
|
5802
6465
|
if (neqMatch) {
|
|
5803
|
-
const [,
|
|
5804
|
-
const actualValue = resolvePath(context,
|
|
6466
|
+
const [, path6, expectedValue] = neqMatch;
|
|
6467
|
+
const actualValue = resolvePath(context, path6.trim());
|
|
5805
6468
|
return actualValue !== expectedValue;
|
|
5806
6469
|
}
|
|
5807
6470
|
const value = resolvePath(context, expression);
|
|
@@ -6810,6 +7473,9 @@ function isTimeInWindowSlot(date, window) {
|
|
|
6810
7473
|
const tzInfo = getTimezoneInfo(date, tz);
|
|
6811
7474
|
return allowedDays.includes(tzInfo.day) && tzInfo.totalMinutes >= windowStartMinutes && tzInfo.totalMinutes < windowEndMinutes;
|
|
6812
7475
|
}
|
|
7476
|
+
function getLocalDateKey(date, timezone) {
|
|
7477
|
+
return date.toLocaleDateString("en-CA", { timeZone: timezone });
|
|
7478
|
+
}
|
|
6813
7479
|
function calculateWaitTime(step, now) {
|
|
6814
7480
|
const nowTime = now.getTime();
|
|
6815
7481
|
switch (step.mode) {
|
|
@@ -6878,7 +7544,7 @@ function calculateWaitTime(step, now) {
|
|
|
6878
7544
|
}
|
|
6879
7545
|
}
|
|
6880
7546
|
let earliestScheduledTime = null;
|
|
6881
|
-
let
|
|
7547
|
+
let earliestWaitFromNow = Infinity;
|
|
6882
7548
|
for (const window of step.windows) {
|
|
6883
7549
|
if (!window || window.startTime === void 0 || window.endTime === void 0) {
|
|
6884
7550
|
continue;
|
|
@@ -6896,12 +7562,10 @@ function calculateWaitTime(step, now) {
|
|
|
6896
7562
|
const currentHour = targetTzInfo.hour;
|
|
6897
7563
|
const currentMinute = targetTzInfo.minute;
|
|
6898
7564
|
const currentTotalMinutes = targetTzInfo.totalMinutes;
|
|
6899
|
-
let
|
|
6900
|
-
let windowScheduledTime;
|
|
7565
|
+
let msUntilWindowStart;
|
|
6901
7566
|
if (allowedDays.includes(currentDay) && currentTotalMinutes < windowStartMinutes) {
|
|
6902
7567
|
const minutesUntilWindow = windowStartMinutes - currentTotalMinutes;
|
|
6903
|
-
|
|
6904
|
-
windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
|
|
7568
|
+
msUntilWindowStart = minutesUntilWindow * 60 * 1e3;
|
|
6905
7569
|
} else {
|
|
6906
7570
|
let daysToAdd = 7;
|
|
6907
7571
|
for (let i = 1; i <= 7; i++) {
|
|
@@ -6917,17 +7581,20 @@ function calculateWaitTime(step, now) {
|
|
|
6917
7581
|
const daysMs = daysToAdd * millisecondsPerDay;
|
|
6918
7582
|
const hoursAdjustment = (windowStartHour - currentHour) * millisecondsPerHour;
|
|
6919
7583
|
const minutesAdjustment = (windowStartMinute - currentMinute) * millisecondsPerMinute;
|
|
6920
|
-
|
|
6921
|
-
windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
|
|
7584
|
+
msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
|
|
6922
7585
|
}
|
|
6923
|
-
|
|
6924
|
-
|
|
7586
|
+
const windowStartTimeMs = targetDate.getTime() + msUntilWindowStart;
|
|
7587
|
+
const sameLocalDayAsNow = getLocalDateKey(now, tz) === getLocalDateKey(targetDate, tz);
|
|
7588
|
+
const windowScheduledTime = sameLocalDayAsNow ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
|
|
7589
|
+
const waitFromNow = windowScheduledTime.getTime() - nowTime;
|
|
7590
|
+
if (waitFromNow < earliestWaitFromNow) {
|
|
7591
|
+
earliestWaitFromNow = waitFromNow;
|
|
6925
7592
|
earliestScheduledTime = windowScheduledTime;
|
|
6926
7593
|
}
|
|
6927
7594
|
}
|
|
6928
7595
|
if (earliestScheduledTime) {
|
|
6929
7596
|
return {
|
|
6930
|
-
waitTime:
|
|
7597
|
+
waitTime: Math.max(0, earliestScheduledTime.getTime() - nowTime),
|
|
6931
7598
|
scheduledAt: earliestScheduledTime
|
|
6932
7599
|
};
|
|
6933
7600
|
}
|
|
@@ -7166,10 +7833,15 @@ var index_default = { z: import_v413.z };
|
|
|
7166
7833
|
ParticipantEventPayloadSchema,
|
|
7167
7834
|
ParticipantKindSchema,
|
|
7168
7835
|
ProvisionConfigSchema,
|
|
7836
|
+
QueueContextError,
|
|
7837
|
+
QueueNotFoundError,
|
|
7838
|
+
QueuedFetchExhaustedError,
|
|
7839
|
+
RateLimitBackendError,
|
|
7169
7840
|
RelationshipCardinalitySchema,
|
|
7170
7841
|
RelationshipDefinitionSchema,
|
|
7171
7842
|
RelationshipExtensionSchema,
|
|
7172
7843
|
RelationshipLinkSchema,
|
|
7844
|
+
RequeueOutsideContextError,
|
|
7173
7845
|
ResolvedSkillSchema,
|
|
7174
7846
|
ResolvedTriggerSchema,
|
|
7175
7847
|
ResourceDependencySchema,
|
|
@@ -7247,6 +7919,7 @@ var index_default = { z: import_v413.z };
|
|
|
7247
7919
|
createListResponse,
|
|
7248
7920
|
createNotFoundError,
|
|
7249
7921
|
createPermissionError,
|
|
7922
|
+
createQueueHandle,
|
|
7250
7923
|
createRateLimitError,
|
|
7251
7924
|
createServerHookContext,
|
|
7252
7925
|
createSuccessResponse,
|
|
@@ -7256,6 +7929,7 @@ var index_default = { z: import_v413.z };
|
|
|
7256
7929
|
createWebhookContext,
|
|
7257
7930
|
createWorkflowStepContext,
|
|
7258
7931
|
cron,
|
|
7932
|
+
defaultShouldRetry,
|
|
7259
7933
|
defineAgent,
|
|
7260
7934
|
defineChannel,
|
|
7261
7935
|
defineConfig,
|
|
@@ -7278,6 +7952,7 @@ var index_default = { z: import_v413.z };
|
|
|
7278
7952
|
getConfig,
|
|
7279
7953
|
getContextByHandle,
|
|
7280
7954
|
getContextByModel,
|
|
7955
|
+
getRateLimitExecutionContext,
|
|
7281
7956
|
getRequiredInstallEnvKeys,
|
|
7282
7957
|
getRetryDelay,
|
|
7283
7958
|
instance,
|
|
@@ -7297,10 +7972,16 @@ var index_default = { z: import_v413.z };
|
|
|
7297
7972
|
loadConfig,
|
|
7298
7973
|
matchesTrigger,
|
|
7299
7974
|
parseCRMSchema,
|
|
7975
|
+
queuedFetch,
|
|
7976
|
+
queuedFetchResponse,
|
|
7977
|
+
registerQueueConfig,
|
|
7300
7978
|
report,
|
|
7979
|
+
requeue,
|
|
7301
7980
|
resolveInputMappings,
|
|
7981
|
+
resolveQueue,
|
|
7302
7982
|
resource,
|
|
7303
7983
|
runWithConfig,
|
|
7984
|
+
runWithRateLimitExecutionContext,
|
|
7304
7985
|
safeParseCRMSchema,
|
|
7305
7986
|
safeParseConfig,
|
|
7306
7987
|
server,
|