skedyul 1.4.11 → 1.5.0
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 +249 -189
- package/dist/config/transpileConfigMetadata.d.ts +1 -0
- package/dist/esm/index.mjs +128 -68
- package/dist/index.js +128 -68
- package/package.json +3 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function transpileConfigMetadata(configPath: string): Promise<string>;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -2025,13 +2025,13 @@ function mergeRuntimeEnv() {
|
|
|
2025
2025
|
|
|
2026
2026
|
// src/server/utils/http.ts
|
|
2027
2027
|
function readRawRequestBody(req) {
|
|
2028
|
-
return new Promise((
|
|
2028
|
+
return new Promise((resolve5, reject) => {
|
|
2029
2029
|
let body = "";
|
|
2030
2030
|
req.on("data", (chunk) => {
|
|
2031
2031
|
body += chunk.toString();
|
|
2032
2032
|
});
|
|
2033
2033
|
req.on("end", () => {
|
|
2034
|
-
|
|
2034
|
+
resolve5(body);
|
|
2035
2035
|
});
|
|
2036
2036
|
req.on("error", reject);
|
|
2037
2037
|
});
|
|
@@ -3844,7 +3844,7 @@ function printStartupLog(config, tools, port) {
|
|
|
3844
3844
|
|
|
3845
3845
|
// src/server/route-handlers/adapters.ts
|
|
3846
3846
|
function fromLambdaEvent(event2) {
|
|
3847
|
-
const
|
|
3847
|
+
const path7 = event2.path || event2.rawPath || "/";
|
|
3848
3848
|
const method = event2.httpMethod || event2.requestContext?.http?.method || "POST";
|
|
3849
3849
|
const forwardedProto = event2.headers?.["x-forwarded-proto"] ?? event2.headers?.["X-Forwarded-Proto"];
|
|
3850
3850
|
const protocol = forwardedProto ?? "https";
|
|
@@ -3852,9 +3852,9 @@ function fromLambdaEvent(event2) {
|
|
|
3852
3852
|
const queryString = event2.queryStringParameters ? "?" + new URLSearchParams(
|
|
3853
3853
|
event2.queryStringParameters
|
|
3854
3854
|
).toString() : "";
|
|
3855
|
-
const url = `${protocol}://${host}${
|
|
3855
|
+
const url = `${protocol}://${host}${path7}${queryString}`;
|
|
3856
3856
|
return {
|
|
3857
|
-
path:
|
|
3857
|
+
path: path7,
|
|
3858
3858
|
method,
|
|
3859
3859
|
headers: event2.headers,
|
|
3860
3860
|
query: event2.queryStringParameters ?? {},
|
|
@@ -4404,7 +4404,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
|
|
|
4404
4404
|
}
|
|
4405
4405
|
|
|
4406
4406
|
// src/server/handlers/webhook-handler.ts
|
|
4407
|
-
function parseWebhookRequest(parsedBody, method, url,
|
|
4407
|
+
function parseWebhookRequest(parsedBody, method, url, path7, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
|
|
4408
4408
|
const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
|
|
4409
4409
|
if (isEnvelope) {
|
|
4410
4410
|
const envelope = parsedBody;
|
|
@@ -4458,7 +4458,7 @@ function parseWebhookRequest(parsedBody, method, url, path6, headers, query, raw
|
|
|
4458
4458
|
const webhookRequest = {
|
|
4459
4459
|
method,
|
|
4460
4460
|
url,
|
|
4461
|
-
path:
|
|
4461
|
+
path: path7,
|
|
4462
4462
|
headers,
|
|
4463
4463
|
query,
|
|
4464
4464
|
body: parsedBody,
|
|
@@ -4907,9 +4907,9 @@ async function handleMcpBatchRoute(req, ctx) {
|
|
|
4907
4907
|
}
|
|
4908
4908
|
const perCallTimeoutMs = deadline ? Math.min(deadline, 25e3) : 25e3;
|
|
4909
4909
|
const executeWithTimeout = async (call2, timeoutMs) => {
|
|
4910
|
-
return new Promise(async (
|
|
4910
|
+
return new Promise(async (resolve5) => {
|
|
4911
4911
|
const timeoutHandle = setTimeout(() => {
|
|
4912
|
-
|
|
4912
|
+
resolve5({
|
|
4913
4913
|
id: call2.id,
|
|
4914
4914
|
success: false,
|
|
4915
4915
|
error: "Timeout",
|
|
@@ -4922,7 +4922,7 @@ async function handleMcpBatchRoute(req, ctx) {
|
|
|
4922
4922
|
const found = findToolInRegistry(ctx.registry, toolName);
|
|
4923
4923
|
if (!found) {
|
|
4924
4924
|
clearTimeout(timeoutHandle);
|
|
4925
|
-
|
|
4925
|
+
resolve5({
|
|
4926
4926
|
id: call2.id,
|
|
4927
4927
|
success: false,
|
|
4928
4928
|
error: `Tool "${toolName}" not found`
|
|
@@ -4940,13 +4940,13 @@ async function handleMcpBatchRoute(req, ctx) {
|
|
|
4940
4940
|
clearTimeout(timeoutHandle);
|
|
4941
4941
|
const isFailure2 = "success" in toolResult && toolResult.success === false || "error" in toolResult && toolResult.error != null;
|
|
4942
4942
|
if (isFailure2) {
|
|
4943
|
-
|
|
4943
|
+
resolve5({
|
|
4944
4944
|
id: call2.id,
|
|
4945
4945
|
success: false,
|
|
4946
4946
|
error: "error" in toolResult && toolResult.error ? typeof toolResult.error === "string" ? toolResult.error : JSON.stringify(toolResult.error) : "Tool execution failed"
|
|
4947
4947
|
});
|
|
4948
4948
|
} else {
|
|
4949
|
-
|
|
4949
|
+
resolve5({
|
|
4950
4950
|
id: call2.id,
|
|
4951
4951
|
success: true,
|
|
4952
4952
|
result: "output" in toolResult ? toolResult.output : toolResult
|
|
@@ -4954,7 +4954,7 @@ async function handleMcpBatchRoute(req, ctx) {
|
|
|
4954
4954
|
}
|
|
4955
4955
|
} catch (err) {
|
|
4956
4956
|
clearTimeout(timeoutHandle);
|
|
4957
|
-
|
|
4957
|
+
resolve5({
|
|
4958
4958
|
id: call2.id,
|
|
4959
4959
|
success: false,
|
|
4960
4960
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -5140,21 +5140,21 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
|
|
|
5140
5140
|
return {
|
|
5141
5141
|
async listen(listenPort) {
|
|
5142
5142
|
const finalPort = listenPort ?? port;
|
|
5143
|
-
return new Promise((
|
|
5143
|
+
return new Promise((resolve5, reject) => {
|
|
5144
5144
|
httpServer.listen(finalPort, () => {
|
|
5145
5145
|
printStartupLog(config, tools, finalPort);
|
|
5146
|
-
|
|
5146
|
+
resolve5();
|
|
5147
5147
|
});
|
|
5148
5148
|
httpServer.once("error", reject);
|
|
5149
5149
|
});
|
|
5150
5150
|
},
|
|
5151
5151
|
async close() {
|
|
5152
|
-
return new Promise((
|
|
5152
|
+
return new Promise((resolve5, reject) => {
|
|
5153
5153
|
const closable = httpServer;
|
|
5154
5154
|
closable.closeAllConnections?.();
|
|
5155
5155
|
httpServer.close((err) => {
|
|
5156
5156
|
if (err) reject(err);
|
|
5157
|
-
else
|
|
5157
|
+
else resolve5();
|
|
5158
5158
|
});
|
|
5159
5159
|
});
|
|
5160
5160
|
},
|
|
@@ -5510,49 +5510,109 @@ function defineNavigation(navigation) {
|
|
|
5510
5510
|
}
|
|
5511
5511
|
|
|
5512
5512
|
// src/config/loader.ts
|
|
5513
|
+
import * as fs4 from "fs";
|
|
5514
|
+
import * as path4 from "path";
|
|
5515
|
+
import * as os from "os";
|
|
5516
|
+
|
|
5517
|
+
// src/config/transpileConfigMetadata.ts
|
|
5518
|
+
import * as esbuild from "esbuild";
|
|
5513
5519
|
import * as fs3 from "fs";
|
|
5514
5520
|
import * as path3 from "path";
|
|
5515
|
-
|
|
5521
|
+
var SKEDYUL_SHIM_NAMESPACE = "skedyul-shim";
|
|
5522
|
+
var METADATA_STUB_NAMESPACE = "metadata-stub";
|
|
5523
|
+
function prepareConfigSource(content) {
|
|
5524
|
+
return content.replace(/import\s*\(\s*(['"])([^'"]+)\1\s*\)/g, "Promise.resolve(null)");
|
|
5525
|
+
}
|
|
5526
|
+
function isJsonImport(importPath) {
|
|
5527
|
+
return importPath.endsWith(".json");
|
|
5528
|
+
}
|
|
5529
|
+
async function transpileConfigMetadata(configPath) {
|
|
5530
|
+
const absolutePath = path3.resolve(configPath);
|
|
5531
|
+
const configDir = path3.dirname(absolutePath);
|
|
5532
|
+
const configBasename = path3.basename(absolutePath);
|
|
5533
|
+
const result = await esbuild.build({
|
|
5534
|
+
absWorkingDir: configDir,
|
|
5535
|
+
entryPoints: [absolutePath],
|
|
5536
|
+
bundle: true,
|
|
5537
|
+
format: "cjs",
|
|
5538
|
+
platform: "node",
|
|
5539
|
+
target: "node22",
|
|
5540
|
+
write: false,
|
|
5541
|
+
logLevel: "silent",
|
|
5542
|
+
plugins: [
|
|
5543
|
+
{
|
|
5544
|
+
name: "prepare-config-entry",
|
|
5545
|
+
setup(build2) {
|
|
5546
|
+
build2.onLoad({ filter: new RegExp(`/${configBasename.replace(".", "\\.")}$`) }, () => ({
|
|
5547
|
+
contents: prepareConfigSource(fs3.readFileSync(absolutePath, "utf-8")),
|
|
5548
|
+
loader: "ts"
|
|
5549
|
+
}));
|
|
5550
|
+
}
|
|
5551
|
+
},
|
|
5552
|
+
{
|
|
5553
|
+
name: "skedyul-shim",
|
|
5554
|
+
setup(build2) {
|
|
5555
|
+
build2.onResolve({ filter: /^skedyul$/ }, () => ({
|
|
5556
|
+
path: "skedyul-shim",
|
|
5557
|
+
namespace: SKEDYUL_SHIM_NAMESPACE
|
|
5558
|
+
}));
|
|
5559
|
+
build2.onLoad({ filter: /.*/, namespace: SKEDYUL_SHIM_NAMESPACE }, () => ({
|
|
5560
|
+
contents: [
|
|
5561
|
+
"function defineConfig(config) { return config; }",
|
|
5562
|
+
"module.exports = { defineConfig };"
|
|
5563
|
+
].join("\n"),
|
|
5564
|
+
loader: "js"
|
|
5565
|
+
}));
|
|
5566
|
+
}
|
|
5567
|
+
},
|
|
5568
|
+
{
|
|
5569
|
+
name: "metadata-stub-imports",
|
|
5570
|
+
setup(build2) {
|
|
5571
|
+
build2.onResolve({ filter: /^\.{1,2}\// }, (args) => {
|
|
5572
|
+
if (isJsonImport(args.path)) {
|
|
5573
|
+
return void 0;
|
|
5574
|
+
}
|
|
5575
|
+
return {
|
|
5576
|
+
path: args.path,
|
|
5577
|
+
namespace: METADATA_STUB_NAMESPACE
|
|
5578
|
+
};
|
|
5579
|
+
});
|
|
5580
|
+
build2.onLoad({ filter: /.*/, namespace: METADATA_STUB_NAMESPACE }, () => ({
|
|
5581
|
+
contents: "module.exports = {};\n",
|
|
5582
|
+
loader: "js"
|
|
5583
|
+
}));
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
]
|
|
5587
|
+
});
|
|
5588
|
+
if (result.errors.length > 0) {
|
|
5589
|
+
throw new Error(result.errors.map((error) => error.text).join("\n"));
|
|
5590
|
+
}
|
|
5591
|
+
if (result.outputFiles.length === 0) {
|
|
5592
|
+
throw new Error("Config transpile produced no output");
|
|
5593
|
+
}
|
|
5594
|
+
return result.outputFiles[0].text;
|
|
5595
|
+
}
|
|
5596
|
+
|
|
5597
|
+
// src/config/loader.ts
|
|
5516
5598
|
var CONFIG_FILE_NAMES = [
|
|
5517
5599
|
"skedyul.config.ts",
|
|
5518
5600
|
"skedyul.config.js",
|
|
5519
5601
|
"skedyul.config.mjs",
|
|
5520
5602
|
"skedyul.config.cjs"
|
|
5521
5603
|
];
|
|
5522
|
-
async function transpileTypeScript(filePath) {
|
|
5523
|
-
const content = fs3.readFileSync(filePath, "utf-8");
|
|
5524
|
-
const configDir = path3.dirname(path3.resolve(filePath));
|
|
5525
|
-
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*$/, "}");
|
|
5526
|
-
transpiled = transpiled.replace(
|
|
5527
|
-
/import\s+(\w+)\s+from\s+['"](\.[^'"]+)['"]\s*(?:with\s*\{[^}]*\})?/g,
|
|
5528
|
-
(_match, varName, relativePath) => {
|
|
5529
|
-
const absolutePath = path3.resolve(configDir, relativePath);
|
|
5530
|
-
return `const ${varName} = require('${absolutePath.replace(/\\/g, "/")}')`;
|
|
5531
|
-
}
|
|
5532
|
-
);
|
|
5533
|
-
transpiled = transpiled.replace(
|
|
5534
|
-
/import\s+\{\s*([^}]+)\s*\}\s+from\s+['"](\.[^'"]+)['"]\s*;?\n?/g,
|
|
5535
|
-
(_match, namedImports, relativePath) => {
|
|
5536
|
-
const absolutePath = path3.resolve(configDir, relativePath);
|
|
5537
|
-
return `const { ${namedImports.trim()} } = require('${absolutePath.replace(/\\/g, "/")}')
|
|
5538
|
-
`;
|
|
5539
|
-
}
|
|
5540
|
-
);
|
|
5541
|
-
transpiled = transpiled.replace(/import\s*\(\s*['"][^'"]+['"]\s*\)/g, "null");
|
|
5542
|
-
return transpiled;
|
|
5543
|
-
}
|
|
5544
5604
|
async function loadConfig(configPath) {
|
|
5545
|
-
const absolutePath =
|
|
5546
|
-
if (!
|
|
5605
|
+
const absolutePath = path4.resolve(configPath);
|
|
5606
|
+
if (!fs4.existsSync(absolutePath)) {
|
|
5547
5607
|
throw new Error(`Config file not found: ${absolutePath}`);
|
|
5548
5608
|
}
|
|
5549
5609
|
const isTypeScript = absolutePath.endsWith(".ts");
|
|
5550
5610
|
try {
|
|
5551
5611
|
if (isTypeScript) {
|
|
5552
5612
|
try {
|
|
5553
|
-
const transpiled = await
|
|
5554
|
-
const tempFile =
|
|
5555
|
-
|
|
5613
|
+
const transpiled = await transpileConfigMetadata(absolutePath);
|
|
5614
|
+
const tempFile = path4.join(os.tmpdir(), `skedyul-config-${Date.now()}.cjs`);
|
|
5615
|
+
fs4.writeFileSync(tempFile, transpiled);
|
|
5556
5616
|
try {
|
|
5557
5617
|
const module2 = __require(tempFile);
|
|
5558
5618
|
const config2 = module2.default || module2;
|
|
@@ -5565,7 +5625,7 @@ async function loadConfig(configPath) {
|
|
|
5565
5625
|
return config2;
|
|
5566
5626
|
} finally {
|
|
5567
5627
|
try {
|
|
5568
|
-
|
|
5628
|
+
fs4.unlinkSync(tempFile);
|
|
5569
5629
|
} catch {
|
|
5570
5630
|
}
|
|
5571
5631
|
}
|
|
@@ -5605,13 +5665,13 @@ function validateConfig(config) {
|
|
|
5605
5665
|
}
|
|
5606
5666
|
|
|
5607
5667
|
// src/config/schema-loader.ts
|
|
5608
|
-
import * as
|
|
5609
|
-
import * as
|
|
5668
|
+
import * as fs5 from "fs";
|
|
5669
|
+
import * as path5 from "path";
|
|
5610
5670
|
import * as os2 from "os";
|
|
5611
5671
|
|
|
5612
5672
|
// src/config/resolver.ts
|
|
5613
|
-
import * as
|
|
5614
|
-
import * as
|
|
5673
|
+
import * as fs6 from "fs";
|
|
5674
|
+
import * as path6 from "path";
|
|
5615
5675
|
|
|
5616
5676
|
// src/config/utils.ts
|
|
5617
5677
|
function getAllEnvKeys(config) {
|
|
@@ -5714,7 +5774,7 @@ function getApiToken() {
|
|
|
5714
5774
|
const config = getConfig();
|
|
5715
5775
|
return config.apiToken || process.env.SKEDYUL_API_TOKEN || "";
|
|
5716
5776
|
}
|
|
5717
|
-
async function callRateLimitApi(
|
|
5777
|
+
async function callRateLimitApi(path7, body) {
|
|
5718
5778
|
const baseUrl = getApiBaseUrl();
|
|
5719
5779
|
const token2 = getApiToken();
|
|
5720
5780
|
if (!baseUrl || !token2) {
|
|
@@ -5722,7 +5782,7 @@ async function callRateLimitApi(path6, body) {
|
|
|
5722
5782
|
"SKEDYUL_API_URL and SKEDYUL_API_TOKEN are required for platform queue coordination"
|
|
5723
5783
|
);
|
|
5724
5784
|
}
|
|
5725
|
-
const response = await fetch(`${baseUrl}/api/internal/ratelimit/${
|
|
5785
|
+
const response = await fetch(`${baseUrl}/api/internal/ratelimit/${path7}`, {
|
|
5726
5786
|
method: "POST",
|
|
5727
5787
|
headers: {
|
|
5728
5788
|
Authorization: `Bearer ${token2}`,
|
|
@@ -5732,11 +5792,11 @@ async function callRateLimitApi(path6, body) {
|
|
|
5732
5792
|
});
|
|
5733
5793
|
const payload = await response.json().catch(() => null);
|
|
5734
5794
|
if (!response.ok || payload?.success === false) {
|
|
5735
|
-
const message = payload?.errors?.[0]?.message ?? `Queue coordination API ${
|
|
5795
|
+
const message = payload?.errors?.[0]?.message ?? `Queue coordination API ${path7} failed with status ${response.status}`;
|
|
5736
5796
|
throw new RateLimitBackendError(message, response.status);
|
|
5737
5797
|
}
|
|
5738
5798
|
if (!payload?.data) {
|
|
5739
|
-
throw new RateLimitBackendError(`Queue coordination API ${
|
|
5799
|
+
throw new RateLimitBackendError(`Queue coordination API ${path7} returned empty data`);
|
|
5740
5800
|
}
|
|
5741
5801
|
return payload.data;
|
|
5742
5802
|
}
|
|
@@ -5863,9 +5923,9 @@ var MemoryRateLimitBackend = class {
|
|
|
5863
5923
|
if (canAcquire(state, limits)) {
|
|
5864
5924
|
return this.grant(state, queueKey, limits);
|
|
5865
5925
|
}
|
|
5866
|
-
return new Promise((
|
|
5926
|
+
return new Promise((resolve5, reject) => {
|
|
5867
5927
|
const waiter = {
|
|
5868
|
-
resolve:
|
|
5928
|
+
resolve: resolve5,
|
|
5869
5929
|
reject,
|
|
5870
5930
|
timer: null,
|
|
5871
5931
|
limits
|
|
@@ -5894,7 +5954,7 @@ var MemoryRateLimitBackend = class {
|
|
|
5894
5954
|
if (waiter.timer) {
|
|
5895
5955
|
clearTimeout(waiter.timer);
|
|
5896
5956
|
}
|
|
5897
|
-
|
|
5957
|
+
resolve5(this.grant(state, queueKey, limits));
|
|
5898
5958
|
return;
|
|
5899
5959
|
}
|
|
5900
5960
|
setTimeout(retry, Math.max(getMinTime(limits), 10));
|
|
@@ -6015,7 +6075,7 @@ function defaultShouldRetry(error, _attempt) {
|
|
|
6015
6075
|
return false;
|
|
6016
6076
|
}
|
|
6017
6077
|
function sleep(ms) {
|
|
6018
|
-
return new Promise((
|
|
6078
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
6019
6079
|
}
|
|
6020
6080
|
|
|
6021
6081
|
// src/ratelimit/queued-fetch.ts
|
|
@@ -6295,16 +6355,16 @@ function evaluateTemplate(template, context) {
|
|
|
6295
6355
|
const templateRegex = /\{\{\s*([^}]+)\s*\}\}/g;
|
|
6296
6356
|
const singleMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
|
|
6297
6357
|
if (singleMatch) {
|
|
6298
|
-
const
|
|
6299
|
-
return resolvePath(context,
|
|
6358
|
+
const path7 = singleMatch[1].trim();
|
|
6359
|
+
return resolvePath(context, path7);
|
|
6300
6360
|
}
|
|
6301
|
-
return template.replace(templateRegex, (_,
|
|
6302
|
-
const value = resolvePath(context,
|
|
6361
|
+
return template.replace(templateRegex, (_, path7) => {
|
|
6362
|
+
const value = resolvePath(context, path7.trim());
|
|
6303
6363
|
return value === void 0 || value === null ? "" : String(value);
|
|
6304
6364
|
});
|
|
6305
6365
|
}
|
|
6306
|
-
function resolvePath(obj,
|
|
6307
|
-
const parts =
|
|
6366
|
+
function resolvePath(obj, path7) {
|
|
6367
|
+
const parts = path7.split(".");
|
|
6308
6368
|
let current = obj;
|
|
6309
6369
|
for (const part of parts) {
|
|
6310
6370
|
if (current === null || current === void 0) {
|
|
@@ -6325,14 +6385,14 @@ function evaluateCondition(condition, context) {
|
|
|
6325
6385
|
const expression = match[1].trim();
|
|
6326
6386
|
const eqMatch = expression.match(/^(.+?)\s*==\s*['"](.+)['"]$/);
|
|
6327
6387
|
if (eqMatch) {
|
|
6328
|
-
const [,
|
|
6329
|
-
const actualValue = resolvePath(context,
|
|
6388
|
+
const [, path7, expectedValue] = eqMatch;
|
|
6389
|
+
const actualValue = resolvePath(context, path7.trim());
|
|
6330
6390
|
return actualValue === expectedValue;
|
|
6331
6391
|
}
|
|
6332
6392
|
const neqMatch = expression.match(/^(.+?)\s*!=\s*['"](.+)['"]$/);
|
|
6333
6393
|
if (neqMatch) {
|
|
6334
|
-
const [,
|
|
6335
|
-
const actualValue = resolvePath(context,
|
|
6394
|
+
const [, path7, expectedValue] = neqMatch;
|
|
6395
|
+
const actualValue = resolvePath(context, path7.trim());
|
|
6336
6396
|
return actualValue !== expectedValue;
|
|
6337
6397
|
}
|
|
6338
6398
|
const value = resolvePath(context, expression);
|