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/esm/index.mjs
CHANGED
|
@@ -1852,6 +1852,68 @@ var CoreApiService = class {
|
|
|
1852
1852
|
};
|
|
1853
1853
|
var coreApiService = new CoreApiService();
|
|
1854
1854
|
|
|
1855
|
+
// src/ratelimit/config-loader.ts
|
|
1856
|
+
import * as fs from "fs";
|
|
1857
|
+
import * as path from "path";
|
|
1858
|
+
var cachedRuntimeQueues = null;
|
|
1859
|
+
function getRuntimeConfigPath() {
|
|
1860
|
+
if (process.env.LAMBDA_TASK_ROOT) {
|
|
1861
|
+
return path.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json");
|
|
1862
|
+
}
|
|
1863
|
+
return path.join(process.cwd(), ".skedyul", "config.json");
|
|
1864
|
+
}
|
|
1865
|
+
function loadRuntimeConfigFile() {
|
|
1866
|
+
const configPath = getRuntimeConfigPath();
|
|
1867
|
+
if (!fs.existsSync(configPath)) {
|
|
1868
|
+
return null;
|
|
1869
|
+
}
|
|
1870
|
+
try {
|
|
1871
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
1872
|
+
return JSON.parse(raw);
|
|
1873
|
+
} catch {
|
|
1874
|
+
return null;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
var registeredServerConfig = null;
|
|
1878
|
+
function registerQueueConfig(config) {
|
|
1879
|
+
registeredServerConfig = config;
|
|
1880
|
+
cachedRuntimeQueues = config.queues ?? null;
|
|
1881
|
+
}
|
|
1882
|
+
function getQueueDefinitions() {
|
|
1883
|
+
if (cachedRuntimeQueues) {
|
|
1884
|
+
return cachedRuntimeQueues;
|
|
1885
|
+
}
|
|
1886
|
+
if (registeredServerConfig?.queues) {
|
|
1887
|
+
cachedRuntimeQueues = stripNonSerializableQueues(registeredServerConfig.queues);
|
|
1888
|
+
return cachedRuntimeQueues;
|
|
1889
|
+
}
|
|
1890
|
+
const fileConfig = loadRuntimeConfigFile();
|
|
1891
|
+
if (fileConfig?.queues) {
|
|
1892
|
+
cachedRuntimeQueues = fileConfig.queues;
|
|
1893
|
+
return cachedRuntimeQueues;
|
|
1894
|
+
}
|
|
1895
|
+
return {};
|
|
1896
|
+
}
|
|
1897
|
+
function stripNonSerializableQueues(queues) {
|
|
1898
|
+
const result = {};
|
|
1899
|
+
for (const [name, config] of Object.entries(queues)) {
|
|
1900
|
+
const { shouldRetry: _shouldRetry, ...serializable } = config;
|
|
1901
|
+
result[name] = serializable;
|
|
1902
|
+
}
|
|
1903
|
+
return result;
|
|
1904
|
+
}
|
|
1905
|
+
function getQueueConfig(queueName) {
|
|
1906
|
+
return getQueueDefinitions()[queueName];
|
|
1907
|
+
}
|
|
1908
|
+
function getQueueConfigWithRetry(queueName) {
|
|
1909
|
+
const fromServer = registeredServerConfig?.queues?.[queueName];
|
|
1910
|
+
if (fromServer) {
|
|
1911
|
+
return fromServer;
|
|
1912
|
+
}
|
|
1913
|
+
const serializable = getQueueConfig(queueName);
|
|
1914
|
+
return serializable;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1855
1917
|
// src/server/utils/schema.ts
|
|
1856
1918
|
import * as z8 from "zod";
|
|
1857
1919
|
function normalizeBilling(billing) {
|
|
@@ -3173,6 +3235,35 @@ var report = {
|
|
|
3173
3235
|
}
|
|
3174
3236
|
};
|
|
3175
3237
|
|
|
3238
|
+
// src/ratelimit/context.ts
|
|
3239
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
3240
|
+
var executionContextStorage = new AsyncLocalStorage2();
|
|
3241
|
+
var activeOperationStorage = new AsyncLocalStorage2();
|
|
3242
|
+
function runWithRateLimitExecutionContext(ctx, fn) {
|
|
3243
|
+
return executionContextStorage.run(ctx, fn);
|
|
3244
|
+
}
|
|
3245
|
+
function getRateLimitExecutionContext() {
|
|
3246
|
+
return executionContextStorage.getStore();
|
|
3247
|
+
}
|
|
3248
|
+
function runWithActiveQueuedOperation(operation, fn) {
|
|
3249
|
+
return activeOperationStorage.run(operation, fn);
|
|
3250
|
+
}
|
|
3251
|
+
function getActiveQueuedOperation() {
|
|
3252
|
+
return activeOperationStorage.getStore();
|
|
3253
|
+
}
|
|
3254
|
+
function setActiveQueuedOperationLease(lease) {
|
|
3255
|
+
const op = activeOperationStorage.getStore();
|
|
3256
|
+
if (op) {
|
|
3257
|
+
op.lease = lease;
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
function updateActiveQueuedOperationAttempt(attempt) {
|
|
3261
|
+
const op = activeOperationStorage.getStore();
|
|
3262
|
+
if (op) {
|
|
3263
|
+
op.attempt = attempt;
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3176
3267
|
// src/errors.ts
|
|
3177
3268
|
var InstallError = class extends Error {
|
|
3178
3269
|
// Optional: which field caused the error
|
|
@@ -3227,8 +3318,8 @@ var AppAuthInvalidError = class extends Error {
|
|
|
3227
3318
|
};
|
|
3228
3319
|
|
|
3229
3320
|
// src/server/context-logger.ts
|
|
3230
|
-
import { AsyncLocalStorage as
|
|
3231
|
-
var logContextStorage = new
|
|
3321
|
+
import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
|
|
3322
|
+
var logContextStorage = new AsyncLocalStorage3();
|
|
3232
3323
|
function runWithLogContext(context, fn) {
|
|
3233
3324
|
return logContextStorage.run(context, fn);
|
|
3234
3325
|
}
|
|
@@ -3441,9 +3532,17 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
3441
3532
|
baseUrl: toolEnv.SKEDYUL_API_URL ?? "",
|
|
3442
3533
|
apiToken: toolEnv.SKEDYUL_API_TOKEN ?? ""
|
|
3443
3534
|
};
|
|
3535
|
+
const rateLimitContext = {
|
|
3536
|
+
app,
|
|
3537
|
+
appInstallationId: trigger === "provision" ? void 0 : rawContext.appInstallationId,
|
|
3538
|
+
invocation,
|
|
3539
|
+
isProvisionContext: trigger === "provision"
|
|
3540
|
+
};
|
|
3444
3541
|
const functionResult = await runWithConfig(requestConfig, async () => {
|
|
3445
|
-
return await
|
|
3446
|
-
return await
|
|
3542
|
+
return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
3543
|
+
return await runWithLogContext({ invocation }, async () => {
|
|
3544
|
+
return await fn(inputs, executionContext);
|
|
3545
|
+
});
|
|
3447
3546
|
});
|
|
3448
3547
|
});
|
|
3449
3548
|
const billing = normalizeBilling(functionResult.billing);
|
|
@@ -3487,6 +3586,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
3487
3586
|
};
|
|
3488
3587
|
}
|
|
3489
3588
|
const errorMessage = error instanceof Error ? error.message : String(error ?? "");
|
|
3589
|
+
const errorCode = error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : "TOOL_EXECUTION_ERROR";
|
|
3490
3590
|
return {
|
|
3491
3591
|
output: null,
|
|
3492
3592
|
billing: { credits: 0 },
|
|
@@ -3496,7 +3596,7 @@ function createCallToolHandler(registry, state, onMaxRequests) {
|
|
|
3496
3596
|
toolName
|
|
3497
3597
|
},
|
|
3498
3598
|
error: {
|
|
3499
|
-
code:
|
|
3599
|
+
code: errorCode,
|
|
3500
3600
|
message: errorMessage
|
|
3501
3601
|
}
|
|
3502
3602
|
};
|
|
@@ -3580,7 +3680,7 @@ function printStartupLog(config, tools, port) {
|
|
|
3580
3680
|
|
|
3581
3681
|
// src/server/route-handlers/adapters.ts
|
|
3582
3682
|
function fromLambdaEvent(event2) {
|
|
3583
|
-
const
|
|
3683
|
+
const path6 = event2.path || event2.rawPath || "/";
|
|
3584
3684
|
const method = event2.httpMethod || event2.requestContext?.http?.method || "POST";
|
|
3585
3685
|
const forwardedProto = event2.headers?.["x-forwarded-proto"] ?? event2.headers?.["X-Forwarded-Proto"];
|
|
3586
3686
|
const protocol = forwardedProto ?? "https";
|
|
@@ -3588,9 +3688,9 @@ function fromLambdaEvent(event2) {
|
|
|
3588
3688
|
const queryString = event2.queryStringParameters ? "?" + new URLSearchParams(
|
|
3589
3689
|
event2.queryStringParameters
|
|
3590
3690
|
).toString() : "";
|
|
3591
|
-
const url = `${protocol}://${host}${
|
|
3691
|
+
const url = `${protocol}://${host}${path6}${queryString}`;
|
|
3592
3692
|
return {
|
|
3593
|
-
path:
|
|
3693
|
+
path: path6,
|
|
3594
3694
|
method,
|
|
3595
3695
|
headers: event2.headers,
|
|
3596
3696
|
query: event2.queryStringParameters ?? {},
|
|
@@ -3672,8 +3772,8 @@ function parseBodyByContentType(req) {
|
|
|
3672
3772
|
}
|
|
3673
3773
|
|
|
3674
3774
|
// src/server/route-handlers/handlers.ts
|
|
3675
|
-
import * as
|
|
3676
|
-
import * as
|
|
3775
|
+
import * as fs2 from "fs";
|
|
3776
|
+
import * as path2 from "path";
|
|
3677
3777
|
|
|
3678
3778
|
// src/server/core-api-handler.ts
|
|
3679
3779
|
async function handleCoreMethod(method, params) {
|
|
@@ -3841,7 +3941,8 @@ function serializeConfig(config) {
|
|
|
3841
3941
|
type: w.type ?? "WEBHOOK"
|
|
3842
3942
|
})) : [],
|
|
3843
3943
|
provision: isProvisionConfig(config.provision) ? config.provision : void 0,
|
|
3844
|
-
agents: config.agents
|
|
3944
|
+
agents: config.agents,
|
|
3945
|
+
queues: config.queues
|
|
3845
3946
|
};
|
|
3846
3947
|
}
|
|
3847
3948
|
function isProvisionConfig(value) {
|
|
@@ -4137,7 +4238,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
|
|
|
4137
4238
|
}
|
|
4138
4239
|
|
|
4139
4240
|
// src/server/handlers/webhook-handler.ts
|
|
4140
|
-
function parseWebhookRequest(parsedBody, method, url,
|
|
4241
|
+
function parseWebhookRequest(parsedBody, method, url, path6, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
4141
4242
|
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
4142
4243
|
if (isEnvelope) {
|
|
4143
4244
|
const envelope = parsedBody;
|
|
@@ -4191,7 +4292,7 @@ function parseWebhookRequest(parsedBody, method, url, path5, headers, query, raw
|
|
|
4191
4292
|
const webhookRequest = {
|
|
4192
4293
|
method,
|
|
4193
4294
|
url,
|
|
4194
|
-
path:
|
|
4295
|
+
path: path6,
|
|
4195
4296
|
headers,
|
|
4196
4297
|
query,
|
|
4197
4298
|
body: parsedBody,
|
|
@@ -4249,7 +4350,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
|
|
|
4249
4350
|
|
|
4250
4351
|
// src/server/route-handlers/handlers.ts
|
|
4251
4352
|
function getConfigFilePath() {
|
|
4252
|
-
return process.env.LAMBDA_TASK_ROOT ?
|
|
4353
|
+
return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
|
|
4253
4354
|
}
|
|
4254
4355
|
function findToolInRegistry(registry, toolName) {
|
|
4255
4356
|
for (const [key, t] of Object.entries(registry)) {
|
|
@@ -4288,8 +4389,8 @@ function handleHealthRoute(ctx) {
|
|
|
4288
4389
|
function handleConfigRoute(ctx) {
|
|
4289
4390
|
const configFilePath = getConfigFilePath();
|
|
4290
4391
|
try {
|
|
4291
|
-
if (
|
|
4292
|
-
const fileConfig = JSON.parse(
|
|
4392
|
+
if (fs2.existsSync(configFilePath)) {
|
|
4393
|
+
const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
|
|
4293
4394
|
return { status: 200, body: fileConfig };
|
|
4294
4395
|
}
|
|
4295
4396
|
} catch (err) {
|
|
@@ -4974,6 +5075,7 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer) {
|
|
|
4974
5075
|
installContextLogger();
|
|
4975
5076
|
function createSkedyulServer(config) {
|
|
4976
5077
|
mergeRuntimeEnv();
|
|
5078
|
+
registerQueueConfig(config);
|
|
4977
5079
|
const registry = config.tools;
|
|
4978
5080
|
const webhookRegistry = config.webhooks;
|
|
4979
5081
|
if (config.coreApi?.service) {
|
|
@@ -5242,8 +5344,8 @@ function defineNavigation(navigation) {
|
|
|
5242
5344
|
}
|
|
5243
5345
|
|
|
5244
5346
|
// src/config/loader.ts
|
|
5245
|
-
import * as
|
|
5246
|
-
import * as
|
|
5347
|
+
import * as fs3 from "fs";
|
|
5348
|
+
import * as path3 from "path";
|
|
5247
5349
|
import * as os from "os";
|
|
5248
5350
|
var CONFIG_FILE_NAMES = [
|
|
5249
5351
|
"skedyul.config.ts",
|
|
@@ -5252,13 +5354,13 @@ var CONFIG_FILE_NAMES = [
|
|
|
5252
5354
|
"skedyul.config.cjs"
|
|
5253
5355
|
];
|
|
5254
5356
|
async function transpileTypeScript(filePath) {
|
|
5255
|
-
const content =
|
|
5256
|
-
const configDir =
|
|
5357
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
5358
|
+
const configDir = path3.dirname(path3.resolve(filePath));
|
|
5257
5359
|
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*$/, "}");
|
|
5258
5360
|
transpiled = transpiled.replace(
|
|
5259
5361
|
/import\s+(\w+)\s+from\s+['"](\.[^'"]+)['"]\s*(?:with\s*\{[^}]*\})?/g,
|
|
5260
5362
|
(match, varName, relativePath) => {
|
|
5261
|
-
const absolutePath =
|
|
5363
|
+
const absolutePath = path3.resolve(configDir, relativePath);
|
|
5262
5364
|
return `const ${varName} = require('${absolutePath.replace(/\\/g, "/")}')`;
|
|
5263
5365
|
}
|
|
5264
5366
|
);
|
|
@@ -5266,8 +5368,8 @@ async function transpileTypeScript(filePath) {
|
|
|
5266
5368
|
return transpiled;
|
|
5267
5369
|
}
|
|
5268
5370
|
async function loadConfig(configPath) {
|
|
5269
|
-
const absolutePath =
|
|
5270
|
-
if (!
|
|
5371
|
+
const absolutePath = path3.resolve(configPath);
|
|
5372
|
+
if (!fs3.existsSync(absolutePath)) {
|
|
5271
5373
|
throw new Error(`Config file not found: ${absolutePath}`);
|
|
5272
5374
|
}
|
|
5273
5375
|
const isTypeScript = absolutePath.endsWith(".ts");
|
|
@@ -5276,8 +5378,8 @@ async function loadConfig(configPath) {
|
|
|
5276
5378
|
if (isTypeScript) {
|
|
5277
5379
|
const transpiled = await transpileTypeScript(absolutePath);
|
|
5278
5380
|
const tempDir = os.tmpdir();
|
|
5279
|
-
const tempFile =
|
|
5280
|
-
|
|
5381
|
+
const tempFile = path3.join(tempDir, `skedyul-config-${Date.now()}.cjs`);
|
|
5382
|
+
fs3.writeFileSync(tempFile, transpiled);
|
|
5281
5383
|
moduleToLoad = tempFile;
|
|
5282
5384
|
try {
|
|
5283
5385
|
const module2 = __require(moduleToLoad);
|
|
@@ -5291,7 +5393,7 @@ async function loadConfig(configPath) {
|
|
|
5291
5393
|
return config2;
|
|
5292
5394
|
} finally {
|
|
5293
5395
|
try {
|
|
5294
|
-
|
|
5396
|
+
fs3.unlinkSync(tempFile);
|
|
5295
5397
|
} catch {
|
|
5296
5398
|
}
|
|
5297
5399
|
}
|
|
@@ -5326,13 +5428,13 @@ function validateConfig(config) {
|
|
|
5326
5428
|
}
|
|
5327
5429
|
|
|
5328
5430
|
// src/config/schema-loader.ts
|
|
5329
|
-
import * as
|
|
5330
|
-
import * as
|
|
5431
|
+
import * as fs4 from "fs";
|
|
5432
|
+
import * as path4 from "path";
|
|
5331
5433
|
import * as os2 from "os";
|
|
5332
5434
|
|
|
5333
5435
|
// src/config/resolver.ts
|
|
5334
|
-
import * as
|
|
5335
|
-
import * as
|
|
5436
|
+
import * as fs5 from "fs";
|
|
5437
|
+
import * as path5 from "path";
|
|
5336
5438
|
|
|
5337
5439
|
// src/config/utils.ts
|
|
5338
5440
|
function getAllEnvKeys(config) {
|
|
@@ -5349,6 +5451,553 @@ function getRequiredInstallEnvKeys(config) {
|
|
|
5349
5451
|
return Object.entries(provision.env).filter(([, def]) => def.required).map(([key]) => key);
|
|
5350
5452
|
}
|
|
5351
5453
|
|
|
5454
|
+
// src/ratelimit/errors.ts
|
|
5455
|
+
var QueueContextError = class extends Error {
|
|
5456
|
+
constructor(message) {
|
|
5457
|
+
super(message);
|
|
5458
|
+
this.code = "QUEUE_CONTEXT_ERROR";
|
|
5459
|
+
this.name = "QueueContextError";
|
|
5460
|
+
}
|
|
5461
|
+
};
|
|
5462
|
+
var QueueNotFoundError = class extends Error {
|
|
5463
|
+
constructor(queueName) {
|
|
5464
|
+
super(`Queue "${queueName}" is not defined in skedyul.config queues`);
|
|
5465
|
+
this.code = "QUEUE_NOT_FOUND";
|
|
5466
|
+
this.name = "QueueNotFoundError";
|
|
5467
|
+
}
|
|
5468
|
+
};
|
|
5469
|
+
var QueuedFetchExhaustedError = class extends Error {
|
|
5470
|
+
constructor(attempts, maxRetries, causeError) {
|
|
5471
|
+
super(
|
|
5472
|
+
`queuedFetch exhausted retries after ${attempts} attempts (maxRetries=${maxRetries})`
|
|
5473
|
+
);
|
|
5474
|
+
this.code = "QUEUED_FETCH_EXHAUSTED";
|
|
5475
|
+
this.name = "QueuedFetchExhaustedError";
|
|
5476
|
+
this.attempts = attempts;
|
|
5477
|
+
this.maxRetries = maxRetries;
|
|
5478
|
+
this.causeError = causeError;
|
|
5479
|
+
}
|
|
5480
|
+
};
|
|
5481
|
+
var RequeueOutsideContextError = class extends Error {
|
|
5482
|
+
constructor() {
|
|
5483
|
+
super("requeue() can only be called inside an active queuedFetch operation");
|
|
5484
|
+
this.code = "REQUEUE_OUTSIDE_CONTEXT";
|
|
5485
|
+
this.name = "RequeueOutsideContextError";
|
|
5486
|
+
}
|
|
5487
|
+
};
|
|
5488
|
+
var RateLimitBackendError = class extends Error {
|
|
5489
|
+
constructor(message, statusCode) {
|
|
5490
|
+
super(message);
|
|
5491
|
+
this.code = "RATE_LIMIT_BACKEND_ERROR";
|
|
5492
|
+
this.name = "RateLimitBackendError";
|
|
5493
|
+
this.statusCode = statusCode;
|
|
5494
|
+
}
|
|
5495
|
+
};
|
|
5496
|
+
|
|
5497
|
+
// src/ratelimit/resolve-queue-key.ts
|
|
5498
|
+
function resolveEndpointHandle(config, invocation) {
|
|
5499
|
+
if (config.endpoint) {
|
|
5500
|
+
return config.endpoint;
|
|
5501
|
+
}
|
|
5502
|
+
if (invocation?.toolHandle) {
|
|
5503
|
+
return invocation.toolHandle;
|
|
5504
|
+
}
|
|
5505
|
+
if (invocation?.serverHookHandle) {
|
|
5506
|
+
return invocation.serverHookHandle;
|
|
5507
|
+
}
|
|
5508
|
+
throw new QueueContextError(
|
|
5509
|
+
`Cannot resolve endpoint handle for queue scope "${config.scope}". Set endpoint in queue config or ensure invocation.toolHandle/serverHookHandle is available.`
|
|
5510
|
+
);
|
|
5511
|
+
}
|
|
5512
|
+
function appendSubKey(base, subKey) {
|
|
5513
|
+
return subKey ? `${base}:${subKey}` : base;
|
|
5514
|
+
}
|
|
5515
|
+
function resolveQueueKey(queueName, config, ctx, subKey) {
|
|
5516
|
+
const { app, appInstallationId, invocation, isProvisionContext: isProvisionContext2 } = ctx;
|
|
5517
|
+
switch (config.scope) {
|
|
5518
|
+
case "provision": {
|
|
5519
|
+
const base = `rl:pv:${app.versionId}:${queueName}`;
|
|
5520
|
+
return appendSubKey(base, subKey);
|
|
5521
|
+
}
|
|
5522
|
+
case "install": {
|
|
5523
|
+
if (!appInstallationId) {
|
|
5524
|
+
throw new QueueContextError(
|
|
5525
|
+
`Queue "${queueName}" with scope "install" requires appInstallationId in context`
|
|
5526
|
+
);
|
|
5527
|
+
}
|
|
5528
|
+
const base = `rl:in:${appInstallationId}:${queueName}`;
|
|
5529
|
+
return appendSubKey(base, subKey);
|
|
5530
|
+
}
|
|
5531
|
+
case "provision_endpoint": {
|
|
5532
|
+
if (!isProvisionContext2 && appInstallationId) {
|
|
5533
|
+
throw new QueueContextError(
|
|
5534
|
+
`Queue "${queueName}" with scope "provision_endpoint" requires provision context (no install)`
|
|
5535
|
+
);
|
|
5536
|
+
}
|
|
5537
|
+
const endpointHandle = resolveEndpointHandle(config, invocation);
|
|
5538
|
+
const base = `rl:pep:${app.versionId}:${endpointHandle}:${queueName}`;
|
|
5539
|
+
return appendSubKey(base, subKey);
|
|
5540
|
+
}
|
|
5541
|
+
case "install_endpoint": {
|
|
5542
|
+
if (!appInstallationId) {
|
|
5543
|
+
throw new QueueContextError(
|
|
5544
|
+
`Queue "${queueName}" with scope "install_endpoint" requires appInstallationId in context`
|
|
5545
|
+
);
|
|
5546
|
+
}
|
|
5547
|
+
const endpointHandle = resolveEndpointHandle(config, invocation);
|
|
5548
|
+
const base = `rl:iep:${appInstallationId}:${endpointHandle}:${queueName}`;
|
|
5549
|
+
return appendSubKey(base, subKey);
|
|
5550
|
+
}
|
|
5551
|
+
case "global": {
|
|
5552
|
+
const base = `rl:gl:${app.versionId}:${queueName}`;
|
|
5553
|
+
return appendSubKey(base, subKey);
|
|
5554
|
+
}
|
|
5555
|
+
default: {
|
|
5556
|
+
const _exhaustive = config.scope;
|
|
5557
|
+
throw new QueueContextError(`Unknown queue scope: ${String(_exhaustive)}`);
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
function toQueueLimits(config) {
|
|
5562
|
+
return {
|
|
5563
|
+
maxConcurrent: config.maxConcurrent,
|
|
5564
|
+
minTime: config.minTime,
|
|
5565
|
+
reservoir: config.reservoir,
|
|
5566
|
+
reservoirRefreshAmount: config.reservoirRefreshAmount,
|
|
5567
|
+
reservoirRefreshInterval: config.reservoirRefreshInterval
|
|
5568
|
+
};
|
|
5569
|
+
}
|
|
5570
|
+
|
|
5571
|
+
// src/ratelimit/backends/platform.ts
|
|
5572
|
+
function getApiBaseUrl() {
|
|
5573
|
+
const config = getConfig();
|
|
5574
|
+
if (config.baseUrl) {
|
|
5575
|
+
return config.baseUrl.replace(/\/+$/, "");
|
|
5576
|
+
}
|
|
5577
|
+
return (process.env.SKEDYUL_API_URL ?? process.env.SKEDYUL_NODE_URL ?? "").replace(/\/+$/, "");
|
|
5578
|
+
}
|
|
5579
|
+
function getApiToken() {
|
|
5580
|
+
const config = getConfig();
|
|
5581
|
+
return config.apiToken || process.env.SKEDYUL_API_TOKEN || "";
|
|
5582
|
+
}
|
|
5583
|
+
async function callRateLimitApi(path6, body) {
|
|
5584
|
+
const baseUrl = getApiBaseUrl();
|
|
5585
|
+
const token2 = getApiToken();
|
|
5586
|
+
if (!baseUrl || !token2) {
|
|
5587
|
+
throw new RateLimitBackendError(
|
|
5588
|
+
"SKEDYUL_API_URL and SKEDYUL_API_TOKEN are required for platform queue coordination"
|
|
5589
|
+
);
|
|
5590
|
+
}
|
|
5591
|
+
const response = await fetch(`${baseUrl}/api/internal/ratelimit/${path6}`, {
|
|
5592
|
+
method: "POST",
|
|
5593
|
+
headers: {
|
|
5594
|
+
Authorization: `Bearer ${token2}`,
|
|
5595
|
+
"Content-Type": "application/json"
|
|
5596
|
+
},
|
|
5597
|
+
body: JSON.stringify(body)
|
|
5598
|
+
});
|
|
5599
|
+
const payload = await response.json().catch(() => null);
|
|
5600
|
+
if (!response.ok || payload?.success === false) {
|
|
5601
|
+
const message = payload?.errors?.[0]?.message ?? `Queue coordination API ${path6} failed with status ${response.status}`;
|
|
5602
|
+
throw new RateLimitBackendError(message, response.status);
|
|
5603
|
+
}
|
|
5604
|
+
if (!payload?.data) {
|
|
5605
|
+
throw new RateLimitBackendError(`Queue coordination API ${path6} returned empty data`);
|
|
5606
|
+
}
|
|
5607
|
+
return payload.data;
|
|
5608
|
+
}
|
|
5609
|
+
var PlatformRateLimitBackend = class {
|
|
5610
|
+
async acquire(queueKey, limits, timeoutMs) {
|
|
5611
|
+
const data = await callRateLimitApi("acquire", {
|
|
5612
|
+
queueKey,
|
|
5613
|
+
limits,
|
|
5614
|
+
timeoutMs
|
|
5615
|
+
});
|
|
5616
|
+
return {
|
|
5617
|
+
leaseId: data.leaseId,
|
|
5618
|
+
acquiredAt: data.acquiredAt,
|
|
5619
|
+
queueKey: data.queueKey
|
|
5620
|
+
};
|
|
5621
|
+
}
|
|
5622
|
+
async release(lease) {
|
|
5623
|
+
await callRateLimitApi("release", {
|
|
5624
|
+
leaseId: lease.leaseId
|
|
5625
|
+
});
|
|
5626
|
+
}
|
|
5627
|
+
};
|
|
5628
|
+
var platformRateLimitBackend = new PlatformRateLimitBackend();
|
|
5629
|
+
|
|
5630
|
+
// src/ratelimit/backends/memory.ts
|
|
5631
|
+
var DEFAULT_MAX_CONCURRENT = 10;
|
|
5632
|
+
function getMaxConcurrent(limits) {
|
|
5633
|
+
return limits.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
5634
|
+
}
|
|
5635
|
+
function getMinTime(limits) {
|
|
5636
|
+
return limits.minTime ?? 0;
|
|
5637
|
+
}
|
|
5638
|
+
function generateLeaseId() {
|
|
5639
|
+
return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
5640
|
+
}
|
|
5641
|
+
function refillTokens(state, limits) {
|
|
5642
|
+
if (limits.reservoir === void 0) {
|
|
5643
|
+
return;
|
|
5644
|
+
}
|
|
5645
|
+
const capacity = limits.reservoir;
|
|
5646
|
+
const refreshAmount = limits.reservoirRefreshAmount ?? limits.reservoir;
|
|
5647
|
+
const refreshInterval = limits.reservoirRefreshInterval ?? 6e4;
|
|
5648
|
+
const now = Date.now();
|
|
5649
|
+
const elapsed = now - state.lastRefillAt;
|
|
5650
|
+
if (elapsed >= refreshInterval) {
|
|
5651
|
+
const intervals = Math.floor(elapsed / refreshInterval);
|
|
5652
|
+
state.tokens = Math.min(capacity, state.tokens + intervals * refreshAmount);
|
|
5653
|
+
state.lastRefillAt = now;
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
function canAcquire(state, limits) {
|
|
5657
|
+
if (state.running >= getMaxConcurrent(limits)) {
|
|
5658
|
+
return false;
|
|
5659
|
+
}
|
|
5660
|
+
refillTokens(state, limits);
|
|
5661
|
+
if (limits.reservoir !== void 0 && state.tokens <= 0) {
|
|
5662
|
+
return false;
|
|
5663
|
+
}
|
|
5664
|
+
const minTime = getMinTime(limits);
|
|
5665
|
+
if (minTime > 0 && state.lastStartAt > 0) {
|
|
5666
|
+
if (Date.now() - state.lastStartAt < minTime) {
|
|
5667
|
+
return false;
|
|
5668
|
+
}
|
|
5669
|
+
}
|
|
5670
|
+
return true;
|
|
5671
|
+
}
|
|
5672
|
+
var MemoryRateLimitBackend = class {
|
|
5673
|
+
constructor() {
|
|
5674
|
+
this.queues = /* @__PURE__ */ new Map();
|
|
5675
|
+
this.leases = /* @__PURE__ */ new Map();
|
|
5676
|
+
}
|
|
5677
|
+
getState(queueKey) {
|
|
5678
|
+
let state = this.queues.get(queueKey);
|
|
5679
|
+
if (!state) {
|
|
5680
|
+
state = {
|
|
5681
|
+
running: 0,
|
|
5682
|
+
waiters: [],
|
|
5683
|
+
lastStartAt: 0,
|
|
5684
|
+
tokens: Number.POSITIVE_INFINITY,
|
|
5685
|
+
lastRefillAt: Date.now()
|
|
5686
|
+
};
|
|
5687
|
+
if (queueKey.includes(":")) {
|
|
5688
|
+
}
|
|
5689
|
+
this.queues.set(queueKey, state);
|
|
5690
|
+
}
|
|
5691
|
+
return state;
|
|
5692
|
+
}
|
|
5693
|
+
grant(state, queueKey, limits) {
|
|
5694
|
+
if (limits.reservoir !== void 0) {
|
|
5695
|
+
state.tokens -= 1;
|
|
5696
|
+
}
|
|
5697
|
+
state.running += 1;
|
|
5698
|
+
state.lastStartAt = Date.now();
|
|
5699
|
+
const leaseId = generateLeaseId();
|
|
5700
|
+
this.leases.set(leaseId, { queueKey, limits });
|
|
5701
|
+
return { leaseId, acquiredAt: Date.now(), queueKey };
|
|
5702
|
+
}
|
|
5703
|
+
drainWaiters(queueKey, state) {
|
|
5704
|
+
let progressed = true;
|
|
5705
|
+
while (progressed && state.waiters.length > 0) {
|
|
5706
|
+
progressed = false;
|
|
5707
|
+
for (let i = 0; i < state.waiters.length; i += 1) {
|
|
5708
|
+
const waiter = state.waiters[i];
|
|
5709
|
+
if (!waiter || !canAcquire(state, waiter.limits)) {
|
|
5710
|
+
continue;
|
|
5711
|
+
}
|
|
5712
|
+
state.waiters.splice(i, 1);
|
|
5713
|
+
if (waiter.timer) {
|
|
5714
|
+
clearTimeout(waiter.timer);
|
|
5715
|
+
}
|
|
5716
|
+
waiter.resolve(this.grant(state, queueKey, waiter.limits));
|
|
5717
|
+
progressed = true;
|
|
5718
|
+
break;
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
}
|
|
5722
|
+
async acquire(queueKey, limits, timeoutMs = 12e4) {
|
|
5723
|
+
const state = this.getState(queueKey);
|
|
5724
|
+
if (limits.reservoir !== void 0 && state.tokens === Number.POSITIVE_INFINITY) {
|
|
5725
|
+
state.tokens = limits.reservoir;
|
|
5726
|
+
}
|
|
5727
|
+
if (canAcquire(state, limits)) {
|
|
5728
|
+
return this.grant(state, queueKey, limits);
|
|
5729
|
+
}
|
|
5730
|
+
return new Promise((resolve4, reject) => {
|
|
5731
|
+
const waiter = {
|
|
5732
|
+
resolve: resolve4,
|
|
5733
|
+
reject,
|
|
5734
|
+
timer: null,
|
|
5735
|
+
limits
|
|
5736
|
+
};
|
|
5737
|
+
if (timeoutMs > 0) {
|
|
5738
|
+
waiter.timer = setTimeout(() => {
|
|
5739
|
+
const idx = state.waiters.indexOf(waiter);
|
|
5740
|
+
if (idx >= 0) {
|
|
5741
|
+
state.waiters.splice(idx, 1);
|
|
5742
|
+
}
|
|
5743
|
+
reject(
|
|
5744
|
+
new Error(`Queue slot acquire timed out after ${timeoutMs}ms for ${queueKey}`)
|
|
5745
|
+
);
|
|
5746
|
+
}, timeoutMs);
|
|
5747
|
+
}
|
|
5748
|
+
state.waiters.push(waiter);
|
|
5749
|
+
const retry = () => {
|
|
5750
|
+
if (!state.waiters.includes(waiter)) {
|
|
5751
|
+
return;
|
|
5752
|
+
}
|
|
5753
|
+
if (canAcquire(state, limits)) {
|
|
5754
|
+
const idx = state.waiters.indexOf(waiter);
|
|
5755
|
+
if (idx >= 0) {
|
|
5756
|
+
state.waiters.splice(idx, 1);
|
|
5757
|
+
}
|
|
5758
|
+
if (waiter.timer) {
|
|
5759
|
+
clearTimeout(waiter.timer);
|
|
5760
|
+
}
|
|
5761
|
+
resolve4(this.grant(state, queueKey, limits));
|
|
5762
|
+
return;
|
|
5763
|
+
}
|
|
5764
|
+
setTimeout(retry, Math.max(getMinTime(limits), 10));
|
|
5765
|
+
};
|
|
5766
|
+
setTimeout(retry, Math.max(getMinTime(limits), 10));
|
|
5767
|
+
});
|
|
5768
|
+
}
|
|
5769
|
+
async release(lease) {
|
|
5770
|
+
const tracked = this.leases.get(lease.leaseId);
|
|
5771
|
+
this.leases.delete(lease.leaseId);
|
|
5772
|
+
const queueKey = tracked?.queueKey ?? lease.queueKey;
|
|
5773
|
+
const state = this.queues.get(queueKey);
|
|
5774
|
+
if (!state) {
|
|
5775
|
+
return;
|
|
5776
|
+
}
|
|
5777
|
+
state.running = Math.max(0, state.running - 1);
|
|
5778
|
+
this.drainWaiters(queueKey, state);
|
|
5779
|
+
}
|
|
5780
|
+
};
|
|
5781
|
+
var memoryRateLimitBackend = new MemoryRateLimitBackend();
|
|
5782
|
+
|
|
5783
|
+
// src/ratelimit/backends/index.ts
|
|
5784
|
+
var cachedBackend = null;
|
|
5785
|
+
function shouldForceMemoryBackend() {
|
|
5786
|
+
const flag = process.env.SKEDYUL_RATE_LIMIT_MEMORY;
|
|
5787
|
+
return flag === "1" || flag === "true";
|
|
5788
|
+
}
|
|
5789
|
+
function getRateLimitBackend() {
|
|
5790
|
+
if (cachedBackend) {
|
|
5791
|
+
return cachedBackend;
|
|
5792
|
+
}
|
|
5793
|
+
if (shouldForceMemoryBackend()) {
|
|
5794
|
+
cachedBackend = memoryRateLimitBackend;
|
|
5795
|
+
return cachedBackend;
|
|
5796
|
+
}
|
|
5797
|
+
cachedBackend = createResilientBackend();
|
|
5798
|
+
return cachedBackend;
|
|
5799
|
+
}
|
|
5800
|
+
function createResilientBackend() {
|
|
5801
|
+
return {
|
|
5802
|
+
async acquire(queueKey, limits, timeoutMs) {
|
|
5803
|
+
try {
|
|
5804
|
+
return await platformRateLimitBackend.acquire(queueKey, limits, timeoutMs);
|
|
5805
|
+
} catch (error) {
|
|
5806
|
+
if (shouldFallbackToMemory(error)) {
|
|
5807
|
+
return memoryRateLimitBackend.acquire(queueKey, limits, timeoutMs);
|
|
5808
|
+
}
|
|
5809
|
+
throw error;
|
|
5810
|
+
}
|
|
5811
|
+
},
|
|
5812
|
+
async release(lease) {
|
|
5813
|
+
if (lease.leaseId.startsWith("mem_")) {
|
|
5814
|
+
return memoryRateLimitBackend.release(lease);
|
|
5815
|
+
}
|
|
5816
|
+
try {
|
|
5817
|
+
await platformRateLimitBackend.release(lease);
|
|
5818
|
+
} catch (error) {
|
|
5819
|
+
if (shouldFallbackToMemory(error)) {
|
|
5820
|
+
return memoryRateLimitBackend.release(lease);
|
|
5821
|
+
}
|
|
5822
|
+
throw error;
|
|
5823
|
+
}
|
|
5824
|
+
}
|
|
5825
|
+
};
|
|
5826
|
+
}
|
|
5827
|
+
function shouldFallbackToMemory(error) {
|
|
5828
|
+
if (shouldForceMemoryBackend()) {
|
|
5829
|
+
return true;
|
|
5830
|
+
}
|
|
5831
|
+
if (error instanceof RateLimitBackendError) {
|
|
5832
|
+
if (error.statusCode === 404 || error.statusCode === 503) {
|
|
5833
|
+
return true;
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
if (error instanceof TypeError) {
|
|
5837
|
+
return true;
|
|
5838
|
+
}
|
|
5839
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
5840
|
+
return nodeEnv === "development" || nodeEnv === "test";
|
|
5841
|
+
}
|
|
5842
|
+
|
|
5843
|
+
// src/ratelimit/should-retry.ts
|
|
5844
|
+
var DEFAULT_RETRY_STATUS_CODES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
5845
|
+
function getErrorStatusCode(error) {
|
|
5846
|
+
if (typeof error !== "object" || error === null) {
|
|
5847
|
+
return void 0;
|
|
5848
|
+
}
|
|
5849
|
+
const record2 = error;
|
|
5850
|
+
if (typeof record2.statusCode === "number") {
|
|
5851
|
+
return record2.statusCode;
|
|
5852
|
+
}
|
|
5853
|
+
if (typeof record2.status === "number") {
|
|
5854
|
+
return record2.status;
|
|
5855
|
+
}
|
|
5856
|
+
return void 0;
|
|
5857
|
+
}
|
|
5858
|
+
function getErrorCode(error) {
|
|
5859
|
+
if (typeof error !== "object" || error === null) {
|
|
5860
|
+
return void 0;
|
|
5861
|
+
}
|
|
5862
|
+
const record2 = error;
|
|
5863
|
+
return typeof record2.code === "string" ? record2.code : void 0;
|
|
5864
|
+
}
|
|
5865
|
+
function defaultShouldRetry(error, _attempt) {
|
|
5866
|
+
const code = getErrorCode(error);
|
|
5867
|
+
if (code === "RATE_LIMITED" || code === "RATE_LIMIT_BACKEND_ERROR") {
|
|
5868
|
+
return true;
|
|
5869
|
+
}
|
|
5870
|
+
const status = getErrorStatusCode(error);
|
|
5871
|
+
if (status !== void 0 && DEFAULT_RETRY_STATUS_CODES.has(status)) {
|
|
5872
|
+
return true;
|
|
5873
|
+
}
|
|
5874
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
5875
|
+
const lower = message.toLowerCase();
|
|
5876
|
+
if (lower.includes("rate limit") || lower.includes("too many requests") || lower.includes("econnreset") || lower.includes("etimedout")) {
|
|
5877
|
+
return true;
|
|
5878
|
+
}
|
|
5879
|
+
return false;
|
|
5880
|
+
}
|
|
5881
|
+
function sleep(ms) {
|
|
5882
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
5883
|
+
}
|
|
5884
|
+
|
|
5885
|
+
// src/ratelimit/queued-fetch.ts
|
|
5886
|
+
function normalizeQueueInput(input) {
|
|
5887
|
+
if (typeof input === "string") {
|
|
5888
|
+
return { name: input };
|
|
5889
|
+
}
|
|
5890
|
+
return { name: input.queue, subKey: input.key };
|
|
5891
|
+
}
|
|
5892
|
+
function resolveQueue(queueInput, ctxOverride) {
|
|
5893
|
+
const ctx = ctxOverride ?? getRateLimitExecutionContext();
|
|
5894
|
+
if (!ctx?.app?.versionId) {
|
|
5895
|
+
throw new QueueContextError(
|
|
5896
|
+
"Cannot resolve queue without app context. Ensure queuedFetch runs inside a tool/hook handler."
|
|
5897
|
+
);
|
|
5898
|
+
}
|
|
5899
|
+
const { name, subKey } = normalizeQueueInput(queueInput);
|
|
5900
|
+
const config = getQueueConfigWithRetry(name);
|
|
5901
|
+
if (!config) {
|
|
5902
|
+
throw new QueueNotFoundError(name);
|
|
5903
|
+
}
|
|
5904
|
+
const queueKey = resolveQueueKey(name, config, ctx, subKey);
|
|
5905
|
+
return {
|
|
5906
|
+
name,
|
|
5907
|
+
queueKey,
|
|
5908
|
+
config,
|
|
5909
|
+
limits: toQueueLimits(config)
|
|
5910
|
+
};
|
|
5911
|
+
}
|
|
5912
|
+
function createQueueHandle(queueInput) {
|
|
5913
|
+
return {
|
|
5914
|
+
run(fn) {
|
|
5915
|
+
return queuedFetch(queueInput, fn);
|
|
5916
|
+
}
|
|
5917
|
+
};
|
|
5918
|
+
}
|
|
5919
|
+
async function queuedFetch(queueInput, fnOrPromise) {
|
|
5920
|
+
const fn = typeof fnOrPromise === "function" ? fnOrPromise : () => fnOrPromise;
|
|
5921
|
+
const resolved = resolveQueue(queueInput);
|
|
5922
|
+
const maxRetries = resolved.config.maxRetries ?? 0;
|
|
5923
|
+
const operation = {
|
|
5924
|
+
queueInput,
|
|
5925
|
+
fn,
|
|
5926
|
+
attempt: 0,
|
|
5927
|
+
resolved,
|
|
5928
|
+
lease: null
|
|
5929
|
+
};
|
|
5930
|
+
return runWithActiveQueuedOperation(
|
|
5931
|
+
operation,
|
|
5932
|
+
() => executeWithRetries(operation, maxRetries)
|
|
5933
|
+
);
|
|
5934
|
+
}
|
|
5935
|
+
async function executeWithRetries(operation, maxRetries) {
|
|
5936
|
+
const backend = getRateLimitBackend();
|
|
5937
|
+
const timeoutMs = operation.resolved.config.timeout ?? 12e4;
|
|
5938
|
+
const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
|
|
5939
|
+
const shouldRetryFn = getQueueConfigWithRetry(operation.resolved.name)?.shouldRetry ?? defaultShouldRetry;
|
|
5940
|
+
const lease = await backend.acquire(
|
|
5941
|
+
operation.resolved.queueKey,
|
|
5942
|
+
operation.resolved.limits,
|
|
5943
|
+
timeoutMs
|
|
5944
|
+
);
|
|
5945
|
+
operation.lease = lease;
|
|
5946
|
+
setActiveQueuedOperationLease(lease);
|
|
5947
|
+
try {
|
|
5948
|
+
return await operation.fn();
|
|
5949
|
+
} catch (error) {
|
|
5950
|
+
await backend.release(lease);
|
|
5951
|
+
operation.lease = null;
|
|
5952
|
+
setActiveQueuedOperationLease(null);
|
|
5953
|
+
if (shouldRetryFn(error, operation.attempt) && operation.attempt < maxRetries) {
|
|
5954
|
+
await sleep(retryDelayMs);
|
|
5955
|
+
operation.attempt += 1;
|
|
5956
|
+
updateActiveQueuedOperationAttempt(operation.attempt);
|
|
5957
|
+
return executeWithRetries(operation, maxRetries);
|
|
5958
|
+
}
|
|
5959
|
+
if (shouldRetryFn(error, operation.attempt) && operation.attempt >= maxRetries) {
|
|
5960
|
+
throw new QueuedFetchExhaustedError(
|
|
5961
|
+
operation.attempt + 1,
|
|
5962
|
+
maxRetries,
|
|
5963
|
+
error
|
|
5964
|
+
);
|
|
5965
|
+
}
|
|
5966
|
+
throw error;
|
|
5967
|
+
} finally {
|
|
5968
|
+
if (operation.lease) {
|
|
5969
|
+
await backend.release(operation.lease);
|
|
5970
|
+
operation.lease = null;
|
|
5971
|
+
setActiveQueuedOperationLease(null);
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
async function requeue() {
|
|
5976
|
+
const operation = getActiveQueuedOperation();
|
|
5977
|
+
if (!operation) {
|
|
5978
|
+
throw new RequeueOutsideContextError();
|
|
5979
|
+
}
|
|
5980
|
+
const maxRetries = operation.resolved.config.maxRetries ?? 0;
|
|
5981
|
+
const nextAttempt = operation.attempt + 1;
|
|
5982
|
+
if (nextAttempt > maxRetries) {
|
|
5983
|
+
throw new QueuedFetchExhaustedError(nextAttempt, maxRetries, void 0);
|
|
5984
|
+
}
|
|
5985
|
+
if (operation.lease) {
|
|
5986
|
+
const backend = getRateLimitBackend();
|
|
5987
|
+
await backend.release(operation.lease);
|
|
5988
|
+
operation.lease = null;
|
|
5989
|
+
setActiveQueuedOperationLease(null);
|
|
5990
|
+
}
|
|
5991
|
+
operation.attempt = nextAttempt;
|
|
5992
|
+
updateActiveQueuedOperationAttempt(nextAttempt);
|
|
5993
|
+
const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
|
|
5994
|
+
await sleep(retryDelayMs);
|
|
5995
|
+
return executeWithRetries(operation, maxRetries);
|
|
5996
|
+
}
|
|
5997
|
+
async function queuedFetchResponse(queueInput, url, init) {
|
|
5998
|
+
return queuedFetch(queueInput, () => fetch(url, init));
|
|
5999
|
+
}
|
|
6000
|
+
|
|
5352
6001
|
// src/triggers/types.ts
|
|
5353
6002
|
import { z as z11 } from "zod/v4";
|
|
5354
6003
|
var CRMDataSchema = z11.object({
|
|
@@ -5453,16 +6102,16 @@ function evaluateTemplate(template, context) {
|
|
|
5453
6102
|
const templateRegex = /\{\{\s*([^}]+)\s*\}\}/g;
|
|
5454
6103
|
const singleMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
|
|
5455
6104
|
if (singleMatch) {
|
|
5456
|
-
const
|
|
5457
|
-
return resolvePath(context,
|
|
6105
|
+
const path6 = singleMatch[1].trim();
|
|
6106
|
+
return resolvePath(context, path6);
|
|
5458
6107
|
}
|
|
5459
|
-
return template.replace(templateRegex, (_,
|
|
5460
|
-
const value = resolvePath(context,
|
|
6108
|
+
return template.replace(templateRegex, (_, path6) => {
|
|
6109
|
+
const value = resolvePath(context, path6.trim());
|
|
5461
6110
|
return value === void 0 || value === null ? "" : String(value);
|
|
5462
6111
|
});
|
|
5463
6112
|
}
|
|
5464
|
-
function resolvePath(obj,
|
|
5465
|
-
const parts =
|
|
6113
|
+
function resolvePath(obj, path6) {
|
|
6114
|
+
const parts = path6.split(".");
|
|
5466
6115
|
let current = obj;
|
|
5467
6116
|
for (const part of parts) {
|
|
5468
6117
|
if (current === null || current === void 0) {
|
|
@@ -5483,14 +6132,14 @@ function evaluateCondition(condition, context) {
|
|
|
5483
6132
|
const expression = match[1].trim();
|
|
5484
6133
|
const eqMatch = expression.match(/^(.+?)\s*==\s*['"](.+)['"]$/);
|
|
5485
6134
|
if (eqMatch) {
|
|
5486
|
-
const [,
|
|
5487
|
-
const actualValue = resolvePath(context,
|
|
6135
|
+
const [, path6, expectedValue] = eqMatch;
|
|
6136
|
+
const actualValue = resolvePath(context, path6.trim());
|
|
5488
6137
|
return actualValue === expectedValue;
|
|
5489
6138
|
}
|
|
5490
6139
|
const neqMatch = expression.match(/^(.+?)\s*!=\s*['"](.+)['"]$/);
|
|
5491
6140
|
if (neqMatch) {
|
|
5492
|
-
const [,
|
|
5493
|
-
const actualValue = resolvePath(context,
|
|
6141
|
+
const [, path6, expectedValue] = neqMatch;
|
|
6142
|
+
const actualValue = resolvePath(context, path6.trim());
|
|
5494
6143
|
return actualValue !== expectedValue;
|
|
5495
6144
|
}
|
|
5496
6145
|
const value = resolvePath(context, expression);
|
|
@@ -6499,6 +7148,9 @@ function isTimeInWindowSlot(date, window) {
|
|
|
6499
7148
|
const tzInfo = getTimezoneInfo(date, tz);
|
|
6500
7149
|
return allowedDays.includes(tzInfo.day) && tzInfo.totalMinutes >= windowStartMinutes && tzInfo.totalMinutes < windowEndMinutes;
|
|
6501
7150
|
}
|
|
7151
|
+
function getLocalDateKey(date, timezone) {
|
|
7152
|
+
return date.toLocaleDateString("en-CA", { timeZone: timezone });
|
|
7153
|
+
}
|
|
6502
7154
|
function calculateWaitTime(step, now) {
|
|
6503
7155
|
const nowTime = now.getTime();
|
|
6504
7156
|
switch (step.mode) {
|
|
@@ -6567,7 +7219,7 @@ function calculateWaitTime(step, now) {
|
|
|
6567
7219
|
}
|
|
6568
7220
|
}
|
|
6569
7221
|
let earliestScheduledTime = null;
|
|
6570
|
-
let
|
|
7222
|
+
let earliestWaitFromNow = Infinity;
|
|
6571
7223
|
for (const window of step.windows) {
|
|
6572
7224
|
if (!window || window.startTime === void 0 || window.endTime === void 0) {
|
|
6573
7225
|
continue;
|
|
@@ -6585,12 +7237,10 @@ function calculateWaitTime(step, now) {
|
|
|
6585
7237
|
const currentHour = targetTzInfo.hour;
|
|
6586
7238
|
const currentMinute = targetTzInfo.minute;
|
|
6587
7239
|
const currentTotalMinutes = targetTzInfo.totalMinutes;
|
|
6588
|
-
let
|
|
6589
|
-
let windowScheduledTime;
|
|
7240
|
+
let msUntilWindowStart;
|
|
6590
7241
|
if (allowedDays.includes(currentDay) && currentTotalMinutes < windowStartMinutes) {
|
|
6591
7242
|
const minutesUntilWindow = windowStartMinutes - currentTotalMinutes;
|
|
6592
|
-
|
|
6593
|
-
windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
|
|
7243
|
+
msUntilWindowStart = minutesUntilWindow * 60 * 1e3;
|
|
6594
7244
|
} else {
|
|
6595
7245
|
let daysToAdd = 7;
|
|
6596
7246
|
for (let i = 1; i <= 7; i++) {
|
|
@@ -6606,17 +7256,20 @@ function calculateWaitTime(step, now) {
|
|
|
6606
7256
|
const daysMs = daysToAdd * millisecondsPerDay;
|
|
6607
7257
|
const hoursAdjustment = (windowStartHour - currentHour) * millisecondsPerHour;
|
|
6608
7258
|
const minutesAdjustment = (windowStartMinute - currentMinute) * millisecondsPerMinute;
|
|
6609
|
-
|
|
6610
|
-
windowScheduledTime = new Date(targetDate.getTime() + windowWaitTime);
|
|
7259
|
+
msUntilWindowStart = daysMs + hoursAdjustment + minutesAdjustment;
|
|
6611
7260
|
}
|
|
6612
|
-
|
|
6613
|
-
|
|
7261
|
+
const windowStartTimeMs = targetDate.getTime() + msUntilWindowStart;
|
|
7262
|
+
const sameLocalDayAsNow = getLocalDateKey(now, tz) === getLocalDateKey(targetDate, tz);
|
|
7263
|
+
const windowScheduledTime = sameLocalDayAsNow ? new Date(windowStartTimeMs + relativeDelay) : new Date(windowStartTimeMs);
|
|
7264
|
+
const waitFromNow = windowScheduledTime.getTime() - nowTime;
|
|
7265
|
+
if (waitFromNow < earliestWaitFromNow) {
|
|
7266
|
+
earliestWaitFromNow = waitFromNow;
|
|
6614
7267
|
earliestScheduledTime = windowScheduledTime;
|
|
6615
7268
|
}
|
|
6616
7269
|
}
|
|
6617
7270
|
if (earliestScheduledTime) {
|
|
6618
7271
|
return {
|
|
6619
|
-
waitTime:
|
|
7272
|
+
waitTime: Math.max(0, earliestScheduledTime.getTime() - nowTime),
|
|
6620
7273
|
scheduledAt: earliestScheduledTime
|
|
6621
7274
|
};
|
|
6622
7275
|
}
|
|
@@ -6854,10 +7507,15 @@ export {
|
|
|
6854
7507
|
ParticipantEventPayloadSchema,
|
|
6855
7508
|
ParticipantKindSchema,
|
|
6856
7509
|
ProvisionConfigSchema,
|
|
7510
|
+
QueueContextError,
|
|
7511
|
+
QueueNotFoundError,
|
|
7512
|
+
QueuedFetchExhaustedError,
|
|
7513
|
+
RateLimitBackendError,
|
|
6857
7514
|
RelationshipCardinalitySchema,
|
|
6858
7515
|
RelationshipDefinitionSchema,
|
|
6859
7516
|
RelationshipExtensionSchema,
|
|
6860
7517
|
RelationshipLinkSchema,
|
|
7518
|
+
RequeueOutsideContextError,
|
|
6861
7519
|
ResolvedSkillSchema,
|
|
6862
7520
|
ResolvedTriggerSchema,
|
|
6863
7521
|
ResourceDependencySchema,
|
|
@@ -6935,6 +7593,7 @@ export {
|
|
|
6935
7593
|
createListResponse,
|
|
6936
7594
|
createNotFoundError,
|
|
6937
7595
|
createPermissionError,
|
|
7596
|
+
createQueueHandle,
|
|
6938
7597
|
createRateLimitError,
|
|
6939
7598
|
createServerHookContext,
|
|
6940
7599
|
createSuccessResponse,
|
|
@@ -6945,6 +7604,7 @@ export {
|
|
|
6945
7604
|
createWorkflowStepContext,
|
|
6946
7605
|
cron,
|
|
6947
7606
|
src_default as default,
|
|
7607
|
+
defaultShouldRetry,
|
|
6948
7608
|
defineAgent,
|
|
6949
7609
|
defineChannel,
|
|
6950
7610
|
defineConfig,
|
|
@@ -6967,6 +7627,7 @@ export {
|
|
|
6967
7627
|
getConfig,
|
|
6968
7628
|
getContextByHandle,
|
|
6969
7629
|
getContextByModel,
|
|
7630
|
+
getRateLimitExecutionContext,
|
|
6970
7631
|
getRequiredInstallEnvKeys,
|
|
6971
7632
|
getRetryDelay,
|
|
6972
7633
|
instance,
|
|
@@ -6986,10 +7647,16 @@ export {
|
|
|
6986
7647
|
loadConfig,
|
|
6987
7648
|
matchesTrigger,
|
|
6988
7649
|
parseCRMSchema,
|
|
7650
|
+
queuedFetch,
|
|
7651
|
+
queuedFetchResponse,
|
|
7652
|
+
registerQueueConfig,
|
|
6989
7653
|
report,
|
|
7654
|
+
requeue,
|
|
6990
7655
|
resolveInputMappings,
|
|
7656
|
+
resolveQueue,
|
|
6991
7657
|
resource,
|
|
6992
7658
|
runWithConfig,
|
|
7659
|
+
runWithRateLimitExecutionContext,
|
|
6993
7660
|
safeParseCRMSchema,
|
|
6994
7661
|
safeParseConfig,
|
|
6995
7662
|
server,
|