reqon-dsl 0.3.0 → 0.4.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/README.md +23 -3
- package/dist/ast/nodes.d.ts +8 -0
- package/dist/auth/circuit-breaker.d.ts +11 -0
- package/dist/auth/circuit-breaker.js +83 -12
- package/dist/auth/credentials.d.ts +6 -1
- package/dist/auth/credentials.js +12 -4
- package/dist/auth/oauth2-provider.js +13 -3
- package/dist/auth/rate-limiter.d.ts +8 -1
- package/dist/auth/rate-limiter.js +30 -10
- package/dist/auth/token-store.js +8 -1
- package/dist/cli.d.ts +11 -1
- package/dist/cli.js +65 -6
- package/dist/config/constants.d.ts +15 -4
- package/dist/config/constants.js +15 -4
- package/dist/control/server.d.ts +17 -0
- package/dist/control/server.js +82 -5
- package/dist/control/types.d.ts +6 -0
- package/dist/debug/cli-debugger.js +8 -3
- package/dist/execution/store.js +2 -2
- package/dist/execution-log/events.d.ts +125 -0
- package/dist/execution-log/events.js +17 -0
- package/dist/execution-log/fold.d.ts +38 -0
- package/dist/execution-log/fold.js +54 -0
- package/dist/execution-log/index.d.ts +18 -0
- package/dist/execution-log/index.js +6 -0
- package/dist/execution-log/postgres-store.d.ts +36 -0
- package/dist/execution-log/postgres-store.js +108 -0
- package/dist/execution-log/resume.d.ts +11 -0
- package/dist/execution-log/resume.js +5 -0
- package/dist/execution-log/sqlite-store.d.ts +16 -0
- package/dist/execution-log/sqlite-store.js +101 -0
- package/dist/execution-log/store.d.ts +72 -0
- package/dist/execution-log/store.js +182 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/interpreter/context.d.ts +15 -0
- package/dist/interpreter/context.js +3 -0
- package/dist/interpreter/evaluator.js +38 -8
- package/dist/interpreter/executor.d.ts +63 -1
- package/dist/interpreter/executor.js +406 -30
- package/dist/interpreter/fetch-handler.d.ts +39 -1
- package/dist/interpreter/fetch-handler.js +84 -15
- package/dist/interpreter/http.d.ts +31 -2
- package/dist/interpreter/http.js +187 -26
- package/dist/interpreter/index.d.ts +3 -3
- package/dist/interpreter/index.js +3 -3
- package/dist/interpreter/pagination.d.ts +1 -1
- package/dist/interpreter/pagination.js +7 -1
- package/dist/interpreter/step-handlers/for-handler.d.ts +3 -0
- package/dist/interpreter/step-handlers/for-handler.js +18 -3
- package/dist/interpreter/step-handlers/match-handler.js +5 -2
- package/dist/interpreter/step-handlers/store-handler.d.ts +7 -1
- package/dist/interpreter/step-handlers/store-handler.js +25 -16
- package/dist/interpreter/step-handlers/validate-handler.js +4 -1
- package/dist/interpreter/step-handlers/webhook-handler.d.ts +1 -0
- package/dist/interpreter/step-handlers/webhook-handler.js +13 -3
- package/dist/interpreter/store-manager.d.ts +1 -1
- package/dist/interpreter/store-manager.js +5 -1
- package/dist/loader/index.js +5 -8
- package/dist/mcp/sandbox.d.ts +41 -0
- package/dist/mcp/sandbox.js +76 -0
- package/dist/mcp/server.js +62 -9
- package/dist/oas/loader.d.ts +13 -1
- package/dist/oas/loader.js +25 -3
- package/dist/oas/mock-generator.js +13 -4
- package/dist/oas/validator.js +45 -5
- package/dist/observability/events.d.ts +6 -2
- package/dist/observability/events.js +0 -5
- package/dist/observability/logger.js +17 -10
- package/dist/observability/otel.d.ts +8 -0
- package/dist/observability/otel.js +45 -10
- package/dist/parser/action-parser.js +2 -2
- package/dist/parser/base.d.ts +7 -0
- package/dist/parser/base.js +11 -0
- package/dist/parser/expressions.d.ts +1 -0
- package/dist/parser/expressions.js +17 -4
- package/dist/parser/fetch-parser.js +13 -2
- package/dist/pause/index.d.ts +1 -0
- package/dist/pause/index.js +1 -0
- package/dist/pause/log-store.d.ts +33 -0
- package/dist/pause/log-store.js +98 -0
- package/dist/pause/manager.d.ts +12 -0
- package/dist/pause/manager.js +77 -28
- package/dist/pause/store.js +5 -3
- package/dist/scheduler/cron-parser.d.ts +10 -3
- package/dist/scheduler/cron-parser.js +227 -48
- package/dist/scheduler/scheduler.js +56 -22
- package/dist/stores/factory.d.ts +6 -0
- package/dist/stores/factory.js +11 -1
- package/dist/stores/file.js +9 -17
- package/dist/stores/memory.js +3 -12
- package/dist/stores/postgrest.d.ts +28 -0
- package/dist/stores/postgrest.js +84 -37
- package/dist/sync/index.d.ts +3 -2
- package/dist/sync/index.js +2 -1
- package/dist/sync/log-store.d.ts +30 -0
- package/dist/sync/log-store.js +45 -0
- package/dist/sync/store.js +1 -1
- package/dist/trace/index.d.ts +2 -0
- package/dist/trace/index.js +1 -0
- package/dist/trace/log-view.d.ts +57 -0
- package/dist/trace/log-view.js +76 -0
- package/dist/trace/recorder.d.ts +5 -1
- package/dist/trace/recorder.js +19 -6
- package/dist/trace/store.d.ts +6 -0
- package/dist/trace/store.js +47 -22
- package/dist/utils/deep-merge.d.ts +10 -0
- package/dist/utils/deep-merge.js +23 -0
- package/dist/utils/file.d.ts +13 -4
- package/dist/utils/file.js +70 -12
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/long-timeout.d.ts +19 -0
- package/dist/utils/long-timeout.js +33 -0
- package/dist/utils/path.d.ts +22 -1
- package/dist/utils/path.js +46 -1
- package/dist/utils/redact.d.ts +22 -0
- package/dist/utils/redact.js +42 -0
- package/dist/webhook/server.d.ts +9 -0
- package/dist/webhook/server.js +115 -30
- package/dist/webhook/types.d.ts +9 -1
- package/package.json +22 -4
package/dist/webhook/server.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { createServer } from 'node:http';
|
|
8
8
|
import { parse as parseUrl } from 'node:url';
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { setLongTimeout } from '../utils/long-timeout.js';
|
|
10
11
|
import { MemoryWebhookStore } from './store.js';
|
|
11
12
|
import { WEBHOOK_DEFAULTS } from '../config/index.js';
|
|
12
13
|
/**
|
|
@@ -19,6 +20,8 @@ export class WebhookServer {
|
|
|
19
20
|
store;
|
|
20
21
|
callbacks;
|
|
21
22
|
server;
|
|
23
|
+
// Multiple concurrent waiters may await the same registration; each gets its
|
|
24
|
+
// own entry so a second waiter can't clobber the first's timer/promise.
|
|
22
25
|
pendingWaits = new Map();
|
|
23
26
|
cleanupInterval;
|
|
24
27
|
running = false;
|
|
@@ -29,6 +32,8 @@ export class WebhookServer {
|
|
|
29
32
|
baseUrl: config.baseUrl ?? `http://localhost:${config.port ?? WEBHOOK_DEFAULTS.PORT}`,
|
|
30
33
|
defaultTimeout: config.defaultTimeout ?? WEBHOOK_DEFAULTS.DEFAULT_TIMEOUT_MS,
|
|
31
34
|
verbose: config.verbose ?? false,
|
|
35
|
+
secret: config.secret ?? '',
|
|
36
|
+
maxBodyBytes: config.maxBodyBytes ?? WEBHOOK_DEFAULTS.MAX_BODY_BYTES,
|
|
32
37
|
};
|
|
33
38
|
this.store = store ?? new MemoryWebhookStore();
|
|
34
39
|
this.callbacks = callbacks;
|
|
@@ -39,8 +44,14 @@ export class WebhookServer {
|
|
|
39
44
|
async start() {
|
|
40
45
|
if (this.running)
|
|
41
46
|
return;
|
|
47
|
+
// Warn loudly if exposing an unauthenticated webhook server off-host.
|
|
48
|
+
if (!this.isLoopback(this.config.host) && !this.config.secret) {
|
|
49
|
+
console.warn(`[Webhook] WARNING: binding to ${this.config.host} with no secret — ` +
|
|
50
|
+
`anyone who can reach the port can inject events.`);
|
|
51
|
+
}
|
|
42
52
|
return new Promise((resolve, reject) => {
|
|
43
53
|
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
54
|
+
this.server.setTimeout(WEBHOOK_DEFAULTS.SOCKET_TIMEOUT_MS);
|
|
44
55
|
this.server.on('error', (error) => {
|
|
45
56
|
reject(error);
|
|
46
57
|
});
|
|
@@ -65,13 +76,15 @@ export class WebhookServer {
|
|
|
65
76
|
this.cleanupInterval = undefined;
|
|
66
77
|
}
|
|
67
78
|
// Cancel all pending waits
|
|
68
|
-
for (const [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
for (const [, waiters] of this.pendingWaits) {
|
|
80
|
+
for (const pending of waiters) {
|
|
81
|
+
pending.timer.clear();
|
|
82
|
+
pending.resolve({
|
|
83
|
+
success: false,
|
|
84
|
+
events: [],
|
|
85
|
+
error: 'Server shutting down',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
75
88
|
}
|
|
76
89
|
this.pendingWaits.clear();
|
|
77
90
|
// Close server
|
|
@@ -134,10 +147,11 @@ export class WebhookServer {
|
|
|
134
147
|
return { success: true, events };
|
|
135
148
|
}
|
|
136
149
|
// Wait for more events
|
|
137
|
-
const waitTimeout = timeout ??
|
|
150
|
+
const waitTimeout = timeout ?? registration.expiresAt.getTime() - Date.now();
|
|
138
151
|
return new Promise((resolve) => {
|
|
139
|
-
const
|
|
140
|
-
|
|
152
|
+
const pending = { registrationId, resolve, timer: undefined };
|
|
153
|
+
pending.timer = setLongTimeout(() => {
|
|
154
|
+
this.removePendingWait(registrationId, pending);
|
|
141
155
|
this.store.getEvents(registrationId).then((events) => {
|
|
142
156
|
resolve({
|
|
143
157
|
success: events.length >= registration.expectedEvents,
|
|
@@ -146,23 +160,36 @@ export class WebhookServer {
|
|
|
146
160
|
});
|
|
147
161
|
});
|
|
148
162
|
}, waitTimeout);
|
|
149
|
-
this.pendingWaits.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
163
|
+
let waiters = this.pendingWaits.get(registrationId);
|
|
164
|
+
if (!waiters) {
|
|
165
|
+
waiters = new Set();
|
|
166
|
+
this.pendingWaits.set(registrationId, waiters);
|
|
167
|
+
}
|
|
168
|
+
waiters.add(pending);
|
|
154
169
|
});
|
|
155
170
|
}
|
|
171
|
+
/** Remove a single waiter, dropping the registration's set when empty. */
|
|
172
|
+
removePendingWait(registrationId, pending) {
|
|
173
|
+
const waiters = this.pendingWaits.get(registrationId);
|
|
174
|
+
if (!waiters)
|
|
175
|
+
return;
|
|
176
|
+
waiters.delete(pending);
|
|
177
|
+
if (waiters.size === 0) {
|
|
178
|
+
this.pendingWaits.delete(registrationId);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
156
181
|
/**
|
|
157
182
|
* Unregister a webhook endpoint
|
|
158
183
|
*/
|
|
159
184
|
async unregister(registrationId) {
|
|
160
185
|
await this.store.deleteRegistration(registrationId);
|
|
161
186
|
await this.store.deleteEvents(registrationId);
|
|
162
|
-
// Cancel pending
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
165
|
-
|
|
187
|
+
// Cancel any pending waits for this registration.
|
|
188
|
+
const waiters = this.pendingWaits.get(registrationId);
|
|
189
|
+
if (waiters) {
|
|
190
|
+
for (const pending of waiters) {
|
|
191
|
+
pending.timer.clear();
|
|
192
|
+
}
|
|
166
193
|
this.pendingWaits.delete(registrationId);
|
|
167
194
|
}
|
|
168
195
|
this.log(`Unregistered webhook: ${registrationId}`);
|
|
@@ -211,11 +238,31 @@ export class WebhookServer {
|
|
|
211
238
|
await this.store.deleteRegistration(registration.id);
|
|
212
239
|
return;
|
|
213
240
|
}
|
|
214
|
-
//
|
|
241
|
+
// Require the shared secret if one is configured.
|
|
242
|
+
if (this.config.secret && !this.authorized(req, url.query)) {
|
|
243
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
244
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// Read the request body with a hard size cap to prevent an OOM from a
|
|
248
|
+
// large or slow-drip POST.
|
|
215
249
|
let rawBody = '';
|
|
216
|
-
let body = null;
|
|
217
250
|
try {
|
|
218
251
|
rawBody = await this.readBody(req);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (error.code === 'BODY_TOO_LARGE') {
|
|
255
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
256
|
+
res.end(JSON.stringify({ error: 'Request body too large' }));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
260
|
+
res.end(JSON.stringify({ error: 'Failed to read request body' }));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
// Parse request body
|
|
264
|
+
let body = null;
|
|
265
|
+
try {
|
|
219
266
|
if (rawBody) {
|
|
220
267
|
const contentType = req.headers['content-type'] ?? '';
|
|
221
268
|
if (contentType.includes('application/json')) {
|
|
@@ -229,7 +276,7 @@ export class WebhookServer {
|
|
|
229
276
|
}
|
|
230
277
|
}
|
|
231
278
|
}
|
|
232
|
-
catch
|
|
279
|
+
catch {
|
|
233
280
|
body = rawBody;
|
|
234
281
|
}
|
|
235
282
|
// Create event
|
|
@@ -253,12 +300,14 @@ export class WebhookServer {
|
|
|
253
300
|
if (registration.receivedEvents >= registration.expectedEvents) {
|
|
254
301
|
const events = await this.store.getEvents(registration.id);
|
|
255
302
|
this.callbacks.onRegistrationComplete?.(registration, events);
|
|
256
|
-
// Resolve pending
|
|
257
|
-
const
|
|
258
|
-
if (
|
|
259
|
-
clearTimeout(pending.timeoutId);
|
|
303
|
+
// Resolve every pending waiter for this registration.
|
|
304
|
+
const waiters = this.pendingWaits.get(registration.id);
|
|
305
|
+
if (waiters) {
|
|
260
306
|
this.pendingWaits.delete(registration.id);
|
|
261
|
-
|
|
307
|
+
for (const pending of waiters) {
|
|
308
|
+
pending.timer.clear();
|
|
309
|
+
pending.resolve({ success: true, events });
|
|
310
|
+
}
|
|
262
311
|
}
|
|
263
312
|
}
|
|
264
313
|
// Send response
|
|
@@ -274,17 +323,53 @@ export class WebhookServer {
|
|
|
274
323
|
* Read request body
|
|
275
324
|
*/
|
|
276
325
|
readBody(req) {
|
|
326
|
+
const limit = this.config.maxBodyBytes;
|
|
277
327
|
return new Promise((resolve, reject) => {
|
|
278
|
-
|
|
328
|
+
const chunks = [];
|
|
329
|
+
let size = 0;
|
|
330
|
+
let aborted = false;
|
|
279
331
|
req.on('data', (chunk) => {
|
|
280
|
-
|
|
332
|
+
if (aborted)
|
|
333
|
+
return;
|
|
334
|
+
size += chunk.length;
|
|
335
|
+
if (size > limit) {
|
|
336
|
+
// Stop accumulating (memory stays bounded) and reject; the handler
|
|
337
|
+
// responds 413. We don't destroy the socket here so the response
|
|
338
|
+
// can flush first.
|
|
339
|
+
aborted = true;
|
|
340
|
+
const err = new Error('Request body too large');
|
|
341
|
+
err.code = 'BODY_TOO_LARGE';
|
|
342
|
+
reject(err);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
chunks.push(chunk);
|
|
281
346
|
});
|
|
282
347
|
req.on('end', () => {
|
|
283
|
-
resolve(
|
|
348
|
+
resolve(Buffer.concat(chunks).toString('utf-8'));
|
|
284
349
|
});
|
|
285
350
|
req.on('error', reject);
|
|
286
351
|
});
|
|
287
352
|
}
|
|
353
|
+
/** True if a host string is a loopback address. */
|
|
354
|
+
isLoopback(host) {
|
|
355
|
+
return host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Validate the shared secret from Authorization bearer, X-Webhook-Token
|
|
359
|
+
* header, or a `token` query param.
|
|
360
|
+
*/
|
|
361
|
+
authorized(req, query) {
|
|
362
|
+
const secret = this.config.secret;
|
|
363
|
+
const auth = req.headers.authorization;
|
|
364
|
+
if (auth === `Bearer ${secret}`)
|
|
365
|
+
return true;
|
|
366
|
+
const tokenHeader = req.headers['x-webhook-token'];
|
|
367
|
+
if (tokenHeader === secret)
|
|
368
|
+
return true;
|
|
369
|
+
if (typeof query.token === 'string' && query.token === secret)
|
|
370
|
+
return true;
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
288
373
|
/**
|
|
289
374
|
* Extract headers from request
|
|
290
375
|
*/
|
package/dist/webhook/types.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface WebhookEvent {
|
|
|
51
51
|
export interface WebhookServerConfig {
|
|
52
52
|
/** Port to listen on (default: 3000) */
|
|
53
53
|
port?: number;
|
|
54
|
-
/** Host to bind to (default: '
|
|
54
|
+
/** Host to bind to (default: '127.0.0.1' — loopback only) */
|
|
55
55
|
host?: string;
|
|
56
56
|
/** Base URL for webhook endpoints (e.g., 'https://example.com/webhooks') */
|
|
57
57
|
baseUrl?: string;
|
|
@@ -59,6 +59,14 @@ export interface WebhookServerConfig {
|
|
|
59
59
|
defaultTimeout?: number;
|
|
60
60
|
/** Enable verbose logging */
|
|
61
61
|
verbose?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Shared secret required on inbound webhook requests. When set, a request
|
|
64
|
+
* must present it via `Authorization: Bearer <secret>`, `X-Webhook-Token`,
|
|
65
|
+
* or a `token` query param, or it is rejected with 401.
|
|
66
|
+
*/
|
|
67
|
+
secret?: string;
|
|
68
|
+
/** Maximum accepted request body size in bytes (default 1 MiB) */
|
|
69
|
+
maxBodyBytes?: number;
|
|
62
70
|
}
|
|
63
71
|
/**
|
|
64
72
|
* Callbacks for webhook server events
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reqon-dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A DSL framework for fetch, map, validate pipelines - built on Vague",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
"reqon-mcp": "dist/mcp/server.js"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "tsc",
|
|
14
|
-
"dev": "tsc --watch",
|
|
13
|
+
"build": "tsc -p tsconfig.build.json",
|
|
14
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
15
16
|
"test": "vitest",
|
|
16
17
|
"test:run": "vitest run",
|
|
17
18
|
"test:coverage": "vitest run --coverage",
|
|
19
|
+
"test:integration": "RUN_NETWORK_TESTS=1 vitest run src/api-integration.test.ts",
|
|
20
|
+
"test:crash": "vitest run src/durability/",
|
|
21
|
+
"test:pg": "vitest run src/execution-log/postgres-store.test.ts",
|
|
18
22
|
"lint": "eslint src/",
|
|
19
23
|
"lint:fix": "eslint src/ --fix",
|
|
20
24
|
"format": "prettier --write src/",
|
|
@@ -40,7 +44,7 @@
|
|
|
40
44
|
"README.md"
|
|
41
45
|
],
|
|
42
46
|
"engines": {
|
|
43
|
-
"node": ">=
|
|
47
|
+
"node": ">=20"
|
|
44
48
|
},
|
|
45
49
|
"repository": {
|
|
46
50
|
"type": "git",
|
|
@@ -68,6 +72,18 @@
|
|
|
68
72
|
"vague-lang": "^3.3.0",
|
|
69
73
|
"zod": "^4.2.1"
|
|
70
74
|
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"better-sqlite3": ">=11",
|
|
77
|
+
"pg": ">=8"
|
|
78
|
+
},
|
|
79
|
+
"peerDependenciesMeta": {
|
|
80
|
+
"better-sqlite3": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"pg": {
|
|
84
|
+
"optional": true
|
|
85
|
+
}
|
|
86
|
+
},
|
|
71
87
|
"lint-staged": {
|
|
72
88
|
"src/**/*.{ts,tsx}": [
|
|
73
89
|
"eslint --fix",
|
|
@@ -78,9 +94,11 @@
|
|
|
78
94
|
"@eslint/js": "^9.17.0",
|
|
79
95
|
"@types/node": "^25.0.3",
|
|
80
96
|
"@vitest/coverage-v8": "^4.0.16",
|
|
97
|
+
"better-sqlite3": "^12.11.1",
|
|
81
98
|
"eslint": "^9.17.0",
|
|
82
99
|
"husky": "^9.1.7",
|
|
83
100
|
"lint-staged": "^16.2.7",
|
|
101
|
+
"pg": "^8.22.0",
|
|
84
102
|
"prettier": "^3.4.2",
|
|
85
103
|
"typescript": "^5.9.3",
|
|
86
104
|
"typescript-eslint": "^8.18.2",
|