@electric-ax/agents-server 0.4.2 → 0.4.3
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/entrypoint.js +178 -157
- package/dist/index.cjs +161 -136
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +159 -136
- package/package.json +5 -5
- package/src/index.ts +5 -1
- package/src/routing/context.ts +1 -0
- package/src/routing/durable-streams-router.ts +286 -116
- package/src/routing/durable-streams-routing-adapter.ts +26 -58
- package/src/routing/entities-router.ts +1 -1
- package/src/routing/internal-router.ts +6 -2
- package/src/routing/runners-router.ts +1 -0
- package/src/runtime.ts +3 -1
- package/src/server.ts +8 -7
- package/src/standalone-runtime.ts +3 -1
- package/src/stream-client.ts +24 -32
- package/src/utils/server-utils.ts +5 -4
package/dist/entrypoint.js
CHANGED
|
@@ -331,76 +331,6 @@ async function runMigrations(postgresUrl) {
|
|
|
331
331
|
await migrationClient.end();
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
//#endregion
|
|
335
|
-
//#region src/routing/tenant-stream-paths.ts
|
|
336
|
-
function withoutLeadingSlash(path$1) {
|
|
337
|
-
return path$1.replace(/^\/+/, ``);
|
|
338
|
-
}
|
|
339
|
-
function withLeadingSlash(path$1) {
|
|
340
|
-
return path$1.startsWith(`/`) ? path$1 : `/${path$1}`;
|
|
341
|
-
}
|
|
342
|
-
function prefixTenantStreamPath(path$1, tenantId) {
|
|
343
|
-
const normalized = withoutLeadingSlash(path$1);
|
|
344
|
-
if (!normalized || normalized === tenantId) return tenantId;
|
|
345
|
-
if (normalized.startsWith(`${tenantId}/`)) return normalized;
|
|
346
|
-
return `${tenantId}/${normalized}`;
|
|
347
|
-
}
|
|
348
|
-
function stripTenantStreamPrefix(path$1, tenantId) {
|
|
349
|
-
const normalized = withoutLeadingSlash(path$1);
|
|
350
|
-
if (normalized === tenantId) return ``;
|
|
351
|
-
if (normalized.startsWith(`${tenantId}/`)) return normalized.slice(tenantId.length + 1);
|
|
352
|
-
return normalized;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
//#endregion
|
|
356
|
-
//#region src/routing/durable-streams-routing-adapter.ts
|
|
357
|
-
function appendSearch(target, source) {
|
|
358
|
-
target.search = source.search;
|
|
359
|
-
return target;
|
|
360
|
-
}
|
|
361
|
-
function removeServiceQuery(target) {
|
|
362
|
-
target.searchParams.delete(`service`);
|
|
363
|
-
return target;
|
|
364
|
-
}
|
|
365
|
-
function logicalStreamPathFromRequest(requestUrl, serviceId) {
|
|
366
|
-
const incomingUrl = new URL(requestUrl, `http://localhost`);
|
|
367
|
-
const segments = incomingUrl.pathname.split(`/`).filter(Boolean);
|
|
368
|
-
if (segments[0] === `v1` && segments[1] === `stream`) return {
|
|
369
|
-
incomingUrl,
|
|
370
|
-
streamPath: segments.length > 2 ? `/${segments.slice(3).join(`/`)}` : `/`
|
|
371
|
-
};
|
|
372
|
-
return {
|
|
373
|
-
incomingUrl,
|
|
374
|
-
streamPath: incomingUrl.pathname || `/${serviceId}`
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
function backendStreamUrl(input, backendStreamPath) {
|
|
378
|
-
const path$1 = backendStreamPath.replace(/^\/+/, ``);
|
|
379
|
-
const target = new URL(`/v1/stream/${path$1}`, input.durableStreamsUrl);
|
|
380
|
-
return target;
|
|
381
|
-
}
|
|
382
|
-
function streamMetaUrlWithoutService(input) {
|
|
383
|
-
const incomingUrl = new URL(input.requestUrl, `http://localhost`);
|
|
384
|
-
return removeServiceQuery(appendSearch(new URL(incomingUrl.pathname, input.durableStreamsUrl), incomingUrl));
|
|
385
|
-
}
|
|
386
|
-
const pathPrefixedSingleTenantDurableStreamsRoutingAdapter = {
|
|
387
|
-
streamUrl(input) {
|
|
388
|
-
const { incomingUrl, streamPath } = logicalStreamPathFromRequest(input.requestUrl, input.serviceId);
|
|
389
|
-
const target = backendStreamUrl(input, prefixTenantStreamPath(streamPath, input.serviceId));
|
|
390
|
-
return removeServiceQuery(appendSearch(target, incomingUrl));
|
|
391
|
-
},
|
|
392
|
-
streamMetaUrl: streamMetaUrlWithoutService,
|
|
393
|
-
toBackendStreamPath(serviceId, streamPath) {
|
|
394
|
-
return prefixTenantStreamPath(streamPath, serviceId);
|
|
395
|
-
},
|
|
396
|
-
toRuntimeStreamPath(serviceId, streamPath) {
|
|
397
|
-
return stripTenantStreamPrefix(streamPath, serviceId);
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
function resolveDurableStreamsRoutingAdapter(adapter) {
|
|
401
|
-
return adapter ?? pathPrefixedSingleTenantDurableStreamsRoutingAdapter;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
334
|
//#endregion
|
|
405
335
|
//#region src/electric-agents-http.ts
|
|
406
336
|
function apiError(status$1, code, message, details) {
|
|
@@ -501,6 +431,40 @@ function electricUrlWithPath(electricUrl, path$1) {
|
|
|
501
431
|
return target;
|
|
502
432
|
}
|
|
503
433
|
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/routing/durable-streams-routing-adapter.ts
|
|
436
|
+
function appendSearch(target, source) {
|
|
437
|
+
target.search = source.search;
|
|
438
|
+
return target;
|
|
439
|
+
}
|
|
440
|
+
function removeServiceQuery(target) {
|
|
441
|
+
target.searchParams.delete(`service`);
|
|
442
|
+
return target;
|
|
443
|
+
}
|
|
444
|
+
function withoutTrailingSlash(pathname) {
|
|
445
|
+
return pathname.replace(/\/+$/, ``) || `/`;
|
|
446
|
+
}
|
|
447
|
+
function appendRequestPathToStreamRoot(input) {
|
|
448
|
+
const incomingUrl = new URL(input.requestUrl, `http://localhost`);
|
|
449
|
+
const path$1 = incomingUrl.pathname.replace(/^\/+/, ``);
|
|
450
|
+
const target = new URL(input.durableStreamsUrl);
|
|
451
|
+
target.pathname = path$1 ? `${withoutTrailingSlash(target.pathname)}/${path$1}` : withoutTrailingSlash(target.pathname);
|
|
452
|
+
return removeServiceQuery(appendSearch(target, incomingUrl));
|
|
453
|
+
}
|
|
454
|
+
const streamRootDurableStreamsRoutingAdapter = {
|
|
455
|
+
streamUrl: appendRequestPathToStreamRoot,
|
|
456
|
+
controlUrl: appendRequestPathToStreamRoot,
|
|
457
|
+
toBackendStreamPath(_serviceId, streamPath) {
|
|
458
|
+
return streamPath.replace(/^\/+/, ``);
|
|
459
|
+
},
|
|
460
|
+
toRuntimeStreamPath(_serviceId, streamPath) {
|
|
461
|
+
return streamPath.replace(/^\/+/, ``);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
function resolveDurableStreamsRoutingAdapter(adapter, _durableStreamsUrl) {
|
|
465
|
+
return adapter ?? streamRootDurableStreamsRoutingAdapter;
|
|
466
|
+
}
|
|
467
|
+
|
|
504
468
|
//#endregion
|
|
505
469
|
//#region src/tracing.ts
|
|
506
470
|
const tracer = trace.getTracer(`agent-server`);
|
|
@@ -579,11 +543,18 @@ function durableStreamsBearerHeaders(bearer) {
|
|
|
579
543
|
if (!bearer) return void 0;
|
|
580
544
|
return { authorization: async () => await resolveDurableStreamsBearer(bearer) ?? `` };
|
|
581
545
|
}
|
|
582
|
-
function durableStreamsServiceUrl(baseUrl, serviceId) {
|
|
546
|
+
function durableStreamsServiceUrl(baseUrl, serviceId, options = {}) {
|
|
583
547
|
const url = new URL(baseUrl);
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
|
|
548
|
+
if (/\/v1\/streams\/[^/]+\/?$/.test(url.pathname)) return baseUrl.replace(/\/+$/, ``);
|
|
549
|
+
if (/\/v1\/stream\/[^/]+\/?$/.test(url.pathname)) return baseUrl.replace(/\/+$/, ``);
|
|
550
|
+
const scope = options.scope ?? `service`;
|
|
551
|
+
const encodedServiceId = encodeURIComponent(serviceId);
|
|
552
|
+
const path$1 = url.pathname.replace(/\/+$/, ``) || `/`;
|
|
553
|
+
if (path$1.endsWith(`/v1/streams`)) url.pathname = `${path$1}/${encodedServiceId}`;
|
|
554
|
+
else if (path$1.endsWith(`/v1/stream`)) url.pathname = scope === `service` ? `${path$1}/${encodedServiceId}` : path$1;
|
|
555
|
+
else if (scope === `stream-root`) url.pathname = `${path$1 === `/` ? `` : path$1}/v1/stream`;
|
|
556
|
+
else url.pathname = `${path$1 === `/` ? `` : path$1}/v1/stream/${encodedServiceId}`;
|
|
557
|
+
return url.toString().replace(/\/+$/, ``);
|
|
587
558
|
}
|
|
588
559
|
function isNotFoundError(err) {
|
|
589
560
|
return err instanceof DurableStreamError && err.code === ErrCodeNotFound || err instanceof FetchError && err.status === 404;
|
|
@@ -616,34 +587,15 @@ var StreamClient = class {
|
|
|
616
587
|
await applyDurableStreamsBearer(headers, this.options.bearer, { overwrite: opts.overwriteBearer });
|
|
617
588
|
return headers;
|
|
618
589
|
}
|
|
619
|
-
subscriptionServiceId() {
|
|
620
|
-
const url = new URL(this.baseUrl);
|
|
621
|
-
const match = /^(.*)\/v1\/stream\/([^/]+)\/?$/.exec(url.pathname);
|
|
622
|
-
return match ? decodeURIComponent(match[2]) : null;
|
|
623
|
-
}
|
|
624
590
|
backendSubscriptionPath(path$1) {
|
|
625
|
-
|
|
626
|
-
const serviceId = this.subscriptionServiceId();
|
|
627
|
-
if (!serviceId) return normalized;
|
|
628
|
-
if (normalized === serviceId || normalized.startsWith(`${serviceId}/`)) return normalized;
|
|
629
|
-
return `${serviceId}/${normalized}`;
|
|
591
|
+
return normalizeSubscriptionPath(path$1);
|
|
630
592
|
}
|
|
631
593
|
runtimeSubscriptionPath(path$1) {
|
|
632
|
-
|
|
633
|
-
const serviceId = this.subscriptionServiceId();
|
|
634
|
-
if (!serviceId) return normalized;
|
|
635
|
-
return normalized.startsWith(`${serviceId}/`) ? normalized.slice(serviceId.length + 1) : normalized;
|
|
594
|
+
return normalizeSubscriptionPath(path$1);
|
|
636
595
|
}
|
|
637
596
|
subscriptionUrl(subscriptionId) {
|
|
638
597
|
const url = new URL(this.baseUrl);
|
|
639
|
-
|
|
640
|
-
if (match) {
|
|
641
|
-
const [, prefix = ``, serviceId] = match;
|
|
642
|
-
url.pathname = `${prefix}/v1/stream-meta/subscriptions/${encodeURIComponent(subscriptionId)}`;
|
|
643
|
-
url.searchParams.set(`service`, decodeURIComponent(serviceId));
|
|
644
|
-
return url.toString();
|
|
645
|
-
}
|
|
646
|
-
url.pathname = `${url.pathname.replace(/\/+$/, ``)}/v1/stream-meta/subscriptions/${encodeURIComponent(subscriptionId)}`;
|
|
598
|
+
url.pathname = `${url.pathname.replace(/\/+$/, ``)}/__ds/subscriptions/${encodeURIComponent(subscriptionId)}`;
|
|
647
599
|
return url.toString();
|
|
648
600
|
}
|
|
649
601
|
subscriptionChildUrl(subscriptionId, ...segments) {
|
|
@@ -1112,13 +1064,13 @@ function buildElectricProxyTarget(options) {
|
|
|
1112
1064
|
return target;
|
|
1113
1065
|
}
|
|
1114
1066
|
async function forwardFetchRequest(options) {
|
|
1115
|
-
const routingAdapter = resolveDurableStreamsRoutingAdapter(options.durableStreamsRouting);
|
|
1067
|
+
const routingAdapter = resolveDurableStreamsRoutingAdapter(options.durableStreamsRouting, options.durableStreamsUrl);
|
|
1116
1068
|
const routingInput = {
|
|
1117
1069
|
durableStreamsUrl: options.durableStreamsUrl,
|
|
1118
1070
|
serviceId: options.serviceId,
|
|
1119
1071
|
requestUrl: options.request.url
|
|
1120
1072
|
};
|
|
1121
|
-
const upstreamUrl = options.route === `
|
|
1073
|
+
const upstreamUrl = options.route === `control` ? routingAdapter.controlUrl(routingInput) : routingAdapter.streamUrl(routingInput);
|
|
1122
1074
|
const headers = new Headers(options.request.headers);
|
|
1123
1075
|
if (options.durableStreamsBearerMode !== `none`) await applyDurableStreamsBearer(headers, options.durableStreamsBearer, { overwrite: options.durableStreamsBearerMode !== `if-missing` });
|
|
1124
1076
|
const init = {
|
|
@@ -1434,8 +1386,21 @@ function isLoopbackHostname(hostname) {
|
|
|
1434
1386
|
//#endregion
|
|
1435
1387
|
//#region src/routing/durable-streams-router.ts
|
|
1436
1388
|
const subscriptionProxyBodySchema = Type.Object({ webhook: Type.Optional(Type.Object({ url: Type.String() }, { additionalProperties: true })) }, { additionalProperties: true });
|
|
1389
|
+
const subscriptionControlActions = [
|
|
1390
|
+
`callback`,
|
|
1391
|
+
`claim`,
|
|
1392
|
+
`ack`,
|
|
1393
|
+
`release`
|
|
1394
|
+
];
|
|
1437
1395
|
const durableStreamsRouter = Router();
|
|
1438
|
-
durableStreamsRouter.
|
|
1396
|
+
durableStreamsRouter.put(`/__ds/subscriptions/:subscriptionId`, putSubscriptionBase);
|
|
1397
|
+
durableStreamsRouter.get(`/__ds/subscriptions/:subscriptionId`, getSubscriptionBase);
|
|
1398
|
+
durableStreamsRouter.delete(`/__ds/subscriptions/:subscriptionId`, deleteSubscriptionBase);
|
|
1399
|
+
durableStreamsRouter.post(`/__ds/subscriptions/:subscriptionId/streams`, postSubscriptionStreams);
|
|
1400
|
+
durableStreamsRouter.delete(`/__ds/subscriptions/:subscriptionId/streams/:streamPath+`, deleteSubscriptionStream);
|
|
1401
|
+
for (const action of subscriptionControlActions) durableStreamsRouter.post(`/__ds/subscriptions/:subscriptionId/${action}`, subscriptionAction(action));
|
|
1402
|
+
durableStreamsRouter.all(`/__ds`, controlPassThrough);
|
|
1403
|
+
durableStreamsRouter.all(`/__ds/*`, controlPassThrough);
|
|
1439
1404
|
durableStreamsRouter.post(`*`, streamAppend);
|
|
1440
1405
|
durableStreamsRouter.all(`*`, proxyPassThrough);
|
|
1441
1406
|
function bodyFromBytes$1(body) {
|
|
@@ -1448,7 +1413,7 @@ function responseFromUpstream$1(response, body) {
|
|
|
1448
1413
|
headers: responseHeaders(response)
|
|
1449
1414
|
});
|
|
1450
1415
|
}
|
|
1451
|
-
async function forwardToDurableStreams(ctx, request, body, route = `stream`, urlOverride) {
|
|
1416
|
+
async function forwardToDurableStreams(ctx, request, body, route = `stream`, urlOverride, durableStreamsBearerMode = `overwrite`) {
|
|
1452
1417
|
const headers = new Headers(request.headers);
|
|
1453
1418
|
headers.delete(`host`);
|
|
1454
1419
|
let requestBody = body;
|
|
@@ -1462,24 +1427,13 @@ async function forwardToDurableStreams(ctx, request, body, route = `stream`, url
|
|
|
1462
1427
|
body: requestBody,
|
|
1463
1428
|
durableStreamsUrl: ctx.durableStreamsUrl,
|
|
1464
1429
|
durableStreamsBearer: ctx.durableStreamsBearer,
|
|
1465
|
-
durableStreamsBearerMode
|
|
1430
|
+
durableStreamsBearerMode,
|
|
1466
1431
|
durableStreamsRouting: ctx.durableStreamsRouting,
|
|
1467
1432
|
serviceId: ctx.service,
|
|
1468
1433
|
dispatcher: ctx.durableStreamsDispatcher,
|
|
1469
1434
|
route
|
|
1470
1435
|
});
|
|
1471
1436
|
}
|
|
1472
|
-
function subscriptionIdFromPath(pathname) {
|
|
1473
|
-
const match = /^\/v1\/stream-meta\/subscriptions\/([^/]+)(?:\/.*)?$/.exec(pathname);
|
|
1474
|
-
return match ? decodeURIComponent(match[1]) : null;
|
|
1475
|
-
}
|
|
1476
|
-
function isSubscriptionBasePath(pathname) {
|
|
1477
|
-
return /^\/v1\/stream-meta\/subscriptions\/[^/]+\/?$/.test(pathname);
|
|
1478
|
-
}
|
|
1479
|
-
function usesSubscriptionScopedBearer(requestUrl) {
|
|
1480
|
-
const pathname = new URL(requestUrl, `http://localhost`).pathname;
|
|
1481
|
-
return /^\/v1\/stream-meta\/subscriptions\/[^/]+\/(?:ack|release|callback)\/?$/.test(pathname);
|
|
1482
|
-
}
|
|
1483
1437
|
function rewriteSubscriptionBodyForBackend(payload, service, routingAdapter) {
|
|
1484
1438
|
if (typeof payload.pattern === `string`) payload.pattern = routingAdapter.toBackendStreamPath(service, payload.pattern);
|
|
1485
1439
|
if (Array.isArray(payload.streams)) payload.streams = payload.streams.map((stream) => typeof stream === `string` ? routingAdapter.toBackendStreamPath(service, stream) : stream);
|
|
@@ -1524,44 +1478,50 @@ function decodeJson(bytes) {
|
|
|
1524
1478
|
return null;
|
|
1525
1479
|
}
|
|
1526
1480
|
}
|
|
1527
|
-
function
|
|
1528
|
-
const
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
return
|
|
1481
|
+
function routeParam$2(request, name) {
|
|
1482
|
+
const value = request.params[name];
|
|
1483
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
1484
|
+
return decodeURIComponent(raw ?? ``);
|
|
1485
|
+
}
|
|
1486
|
+
function subscriptionRoutingAdapter(ctx) {
|
|
1487
|
+
return resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl);
|
|
1534
1488
|
}
|
|
1535
|
-
async function
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1489
|
+
async function rewriteSubscriptionRequestBody(request, ctx, subscriptionId, routingAdapter) {
|
|
1490
|
+
const body = await readRequestBody(request);
|
|
1491
|
+
if (body.length === 0) return {
|
|
1492
|
+
ok: true,
|
|
1493
|
+
body,
|
|
1494
|
+
targetWebhookUrl: null
|
|
1495
|
+
};
|
|
1496
|
+
const validation = validateBody(subscriptionProxyBodySchema, body);
|
|
1497
|
+
if (!validation.ok) return {
|
|
1498
|
+
ok: false,
|
|
1499
|
+
response: validation.response
|
|
1500
|
+
};
|
|
1501
|
+
const payload = validation.value;
|
|
1541
1502
|
let targetWebhookUrl = null;
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
if (requestBody.length > 0) {
|
|
1546
|
-
const validation = validateBody(subscriptionProxyBodySchema, requestBody);
|
|
1547
|
-
if (!validation.ok) return validation.response;
|
|
1548
|
-
const payload = validation.value;
|
|
1549
|
-
if (payload.webhook?.url !== void 0) {
|
|
1550
|
-
targetWebhookUrl = rewriteLoopbackWebhookUrl(payload.webhook.url) ?? null;
|
|
1551
|
-
payload.webhook.url = appendPathToUrl(ctx.publicUrl, `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`);
|
|
1552
|
-
}
|
|
1553
|
-
rewriteSubscriptionBodyForBackend(payload, ctx.service, routingAdapter);
|
|
1554
|
-
requestBody = new TextEncoder().encode(JSON.stringify(payload));
|
|
1555
|
-
}
|
|
1503
|
+
if (payload.webhook?.url !== void 0) {
|
|
1504
|
+
targetWebhookUrl = rewriteLoopbackWebhookUrl(payload.webhook.url) ?? null;
|
|
1505
|
+
payload.webhook.url = appendPathToUrl(ctx.publicUrl, `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`);
|
|
1556
1506
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1507
|
+
rewriteSubscriptionBodyForBackend(payload, ctx.service, routingAdapter);
|
|
1508
|
+
return {
|
|
1509
|
+
ok: true,
|
|
1510
|
+
body: new TextEncoder().encode(JSON.stringify(payload)),
|
|
1511
|
+
targetWebhookUrl
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
async function forwardSubscriptionRequest(request, ctx, routingAdapter, opts = {}) {
|
|
1515
|
+
const upstream = await forwardToDurableStreams(ctx, request, opts.body, `control`, opts.requestUrl, opts.bearerMode ?? `overwrite`);
|
|
1559
1516
|
let responseBytes = upstream.body ? new Uint8Array(await upstream.arrayBuffer()) : new Uint8Array();
|
|
1560
1517
|
responseBytes = rewriteSubscriptionResponseForClient(responseBytes, upstream, ctx.service, routingAdapter);
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1518
|
+
return {
|
|
1519
|
+
upstream,
|
|
1520
|
+
response: responseFromUpstream$1(upstream, responseBytes)
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
async function upsertSubscriptionWebhook(ctx, subscriptionId, targetWebhookUrl) {
|
|
1524
|
+
await ctx.pgDb.insert(subscriptionWebhooks).values({
|
|
1565
1525
|
tenantId: ctx.service,
|
|
1566
1526
|
subscriptionId,
|
|
1567
1527
|
webhookUrl: targetWebhookUrl
|
|
@@ -1569,8 +1529,64 @@ async function subscriptionProxy(request, ctx) {
|
|
|
1569
1529
|
target: [subscriptionWebhooks.tenantId, subscriptionWebhooks.subscriptionId],
|
|
1570
1530
|
set: { webhookUrl: targetWebhookUrl }
|
|
1571
1531
|
});
|
|
1532
|
+
}
|
|
1533
|
+
async function deleteSubscriptionWebhook(ctx, subscriptionId) {
|
|
1534
|
+
await ctx.pgDb.delete(subscriptionWebhooks).where(and(eq(subscriptionWebhooks.tenantId, ctx.service), eq(subscriptionWebhooks.subscriptionId, subscriptionId)));
|
|
1535
|
+
}
|
|
1536
|
+
function rewriteSubscriptionStreamPathInUrl(requestUrl, service, routingAdapter, streamPath) {
|
|
1537
|
+
const prefix = requestUrl.pathname.slice(0, requestUrl.pathname.indexOf(`/streams/`) + `/streams/`.length);
|
|
1538
|
+
requestUrl.pathname = `${prefix}${encodeURIComponent(routingAdapter.toBackendStreamPath(service, streamPath))}`;
|
|
1539
|
+
return requestUrl.toString();
|
|
1540
|
+
}
|
|
1541
|
+
async function putSubscriptionBase(request, ctx) {
|
|
1542
|
+
const subscriptionId = routeParam$2(request, `subscriptionId`);
|
|
1543
|
+
const routingAdapter = subscriptionRoutingAdapter(ctx);
|
|
1544
|
+
const rewrite = await rewriteSubscriptionRequestBody(request, ctx, subscriptionId, routingAdapter);
|
|
1545
|
+
if (!rewrite.ok) return rewrite.response;
|
|
1546
|
+
const { upstream, response } = await forwardSubscriptionRequest(request, ctx, routingAdapter, { body: rewrite.body });
|
|
1547
|
+
if (upstream.ok && rewrite.targetWebhookUrl) await upsertSubscriptionWebhook(ctx, subscriptionId, rewrite.targetWebhookUrl);
|
|
1548
|
+
return response;
|
|
1549
|
+
}
|
|
1550
|
+
async function getSubscriptionBase(request, ctx) {
|
|
1551
|
+
const routingAdapter = subscriptionRoutingAdapter(ctx);
|
|
1552
|
+
return (await forwardSubscriptionRequest(request, ctx, routingAdapter)).response;
|
|
1553
|
+
}
|
|
1554
|
+
async function deleteSubscriptionBase(request, ctx) {
|
|
1555
|
+
const subscriptionId = routeParam$2(request, `subscriptionId`);
|
|
1556
|
+
const routingAdapter = subscriptionRoutingAdapter(ctx);
|
|
1557
|
+
const { upstream, response } = await forwardSubscriptionRequest(request, ctx, routingAdapter);
|
|
1558
|
+
if (upstream.ok) await deleteSubscriptionWebhook(ctx, subscriptionId);
|
|
1572
1559
|
return response;
|
|
1573
1560
|
}
|
|
1561
|
+
async function postSubscriptionStreams(request, ctx) {
|
|
1562
|
+
const subscriptionId = routeParam$2(request, `subscriptionId`);
|
|
1563
|
+
const routingAdapter = subscriptionRoutingAdapter(ctx);
|
|
1564
|
+
const rewrite = await rewriteSubscriptionRequestBody(request, ctx, subscriptionId, routingAdapter);
|
|
1565
|
+
if (!rewrite.ok) return rewrite.response;
|
|
1566
|
+
return (await forwardSubscriptionRequest(request, ctx, routingAdapter, { body: rewrite.body })).response;
|
|
1567
|
+
}
|
|
1568
|
+
async function deleteSubscriptionStream(request, ctx) {
|
|
1569
|
+
const routingAdapter = subscriptionRoutingAdapter(ctx);
|
|
1570
|
+
const requestUrl = rewriteSubscriptionStreamPathInUrl(new URL(request.url), ctx.service, routingAdapter, routeParam$2(request, `streamPath`));
|
|
1571
|
+
return (await forwardSubscriptionRequest(request, ctx, routingAdapter, { requestUrl })).response;
|
|
1572
|
+
}
|
|
1573
|
+
function subscriptionAction(action) {
|
|
1574
|
+
return async (request, ctx) => {
|
|
1575
|
+
const subscriptionId = routeParam$2(request, `subscriptionId`);
|
|
1576
|
+
const routingAdapter = subscriptionRoutingAdapter(ctx);
|
|
1577
|
+
const rewrite = await rewriteSubscriptionRequestBody(request, ctx, subscriptionId, routingAdapter);
|
|
1578
|
+
if (!rewrite.ok) return rewrite.response;
|
|
1579
|
+
const bearerMode = action === `ack` || action === `release` || action === `callback` ? `if-missing` : `overwrite`;
|
|
1580
|
+
return (await forwardSubscriptionRequest(request, ctx, routingAdapter, {
|
|
1581
|
+
body: rewrite.body,
|
|
1582
|
+
bearerMode
|
|
1583
|
+
})).response;
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
async function controlPassThrough(request, ctx) {
|
|
1587
|
+
const upstream = await forwardToDurableStreams(ctx, request, void 0, `control`);
|
|
1588
|
+
return responseFromUpstream$1(upstream);
|
|
1589
|
+
}
|
|
1574
1590
|
async function streamAppend(request, ctx) {
|
|
1575
1591
|
return await electricAgentsStreamAppendRouter.fetch(createStreamAppendRouteRequest(request), ctx.runtime, (req, body) => forwardFetchRequest({
|
|
1576
1592
|
request: {
|
|
@@ -1591,10 +1607,9 @@ async function proxyPassThrough(request, ctx) {
|
|
|
1591
1607
|
const upstream = await forwardToDurableStreams(ctx, request);
|
|
1592
1608
|
const streamPath = new URL(request.url).pathname;
|
|
1593
1609
|
const method = request.method.toUpperCase();
|
|
1594
|
-
const
|
|
1595
|
-
const endTrackedRead = method === `GET` && !isControlPath ? await ctx.entityBridgeManager.beginClientRead(streamPath) : null;
|
|
1610
|
+
const endTrackedRead = method === `GET` ? await ctx.entityBridgeManager.beginClientRead(streamPath) : null;
|
|
1596
1611
|
try {
|
|
1597
|
-
if (method === `HEAD`
|
|
1612
|
+
if (method === `HEAD`) await ctx.entityBridgeManager.touchByStreamPath(streamPath);
|
|
1598
1613
|
return responseFromUpstream$1(upstream);
|
|
1599
1614
|
} finally {
|
|
1600
1615
|
await endTrackedRead?.();
|
|
@@ -4378,11 +4393,11 @@ async function spawnEntity(request, ctx) {
|
|
|
4378
4393
|
wake: parsed.wake,
|
|
4379
4394
|
created_by: principal.url
|
|
4380
4395
|
});
|
|
4396
|
+
await linkEntityDispatchSubscription(ctx, entity);
|
|
4381
4397
|
if (parsed.initialMessage !== void 0) await ctx.entityManager.send(entity.url, {
|
|
4382
4398
|
from: principal.url,
|
|
4383
4399
|
payload: parsed.initialMessage
|
|
4384
4400
|
});
|
|
4385
|
-
await linkEntityDispatchSubscription(ctx, entity);
|
|
4386
4401
|
return json({
|
|
4387
4402
|
...toPublicEntity(entity),
|
|
4388
4403
|
txid: entity.txid
|
|
@@ -4581,6 +4596,12 @@ function getRequestSpan(req) {
|
|
|
4581
4596
|
return carrier(req)[SPAN_KEY];
|
|
4582
4597
|
}
|
|
4583
4598
|
|
|
4599
|
+
//#endregion
|
|
4600
|
+
//#region src/routing/tenant-stream-paths.ts
|
|
4601
|
+
function withLeadingSlash(path$1) {
|
|
4602
|
+
return path$1.startsWith(`/`) ? path$1 : `/${path$1}`;
|
|
4603
|
+
}
|
|
4604
|
+
|
|
4584
4605
|
//#endregion
|
|
4585
4606
|
//#region src/routing/runners-router.ts
|
|
4586
4607
|
const registerRunnerBodySchema = Type.Object({
|
|
@@ -4904,7 +4925,7 @@ async function webhookForward(request, ctx) {
|
|
|
4904
4925
|
let runningEntityUrl = null;
|
|
4905
4926
|
const parsedBody = parsedBodyResult.value;
|
|
4906
4927
|
const newWebhook = newWebhookPayload(parsedBody);
|
|
4907
|
-
const routingAdapter = resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting);
|
|
4928
|
+
const routingAdapter = resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl);
|
|
4908
4929
|
if (parsedBody) {
|
|
4909
4930
|
const rawPrimaryStream = newWebhook?.primaryStream ?? parsedBody.primary_stream ?? parsedBody.primaryStream ?? parsedBody.streamPath ?? null;
|
|
4910
4931
|
const primaryStream = typeof rawPrimaryStream === `string` ? toRuntimeStreamPath(rawPrimaryStream, ctx.service, routingAdapter) : null;
|
|
@@ -5033,7 +5054,7 @@ async function callbackForward(request, ctx) {
|
|
|
5033
5054
|
}
|
|
5034
5055
|
return json(responseBody);
|
|
5035
5056
|
}
|
|
5036
|
-
const upstreamBody = encodeCallbackForwardBody(ctx.service, consumerId, requestBody, resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting));
|
|
5057
|
+
const upstreamBody = encodeCallbackForwardBody(ctx.service, consumerId, requestBody, resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl));
|
|
5037
5058
|
let upstream;
|
|
5038
5059
|
try {
|
|
5039
5060
|
const subscriptionId = durableStreamsSubscriptionCallback(target.callbackUrl);
|
|
@@ -6202,7 +6223,7 @@ var ElectricAgentsTenantRuntime = class {
|
|
|
6202
6223
|
this.service = this.serviceId;
|
|
6203
6224
|
this.db = options.db;
|
|
6204
6225
|
if (options.streamClient) this.streamClient = options.streamClient;
|
|
6205
|
-
else if (options.durableStreamsUrl) this.streamClient = new StreamClient(durableStreamsServiceUrl(options.durableStreamsUrl, this.serviceId), { bearer: options.durableStreamsBearer });
|
|
6226
|
+
else if (options.durableStreamsUrl) this.streamClient = new StreamClient(durableStreamsServiceUrl(options.durableStreamsUrl, this.serviceId, { scope: `stream-root` }), { bearer: options.durableStreamsBearer });
|
|
6206
6227
|
else throw new Error(`Either durableStreamsUrl or streamClient is required`);
|
|
6207
6228
|
this.registry = options.registry ?? new PostgresRegistry(this.db, this.serviceId);
|
|
6208
6229
|
this.wakeRegistry = options.wakeRegistry;
|
|
@@ -7120,7 +7141,7 @@ var WakeRegistry = class {
|
|
|
7120
7141
|
//#region src/standalone-runtime.ts
|
|
7121
7142
|
async function startStandaloneAgentsRuntime(options) {
|
|
7122
7143
|
const serviceId = options.service ?? options.tenantId ?? DEFAULT_TENANT_ID;
|
|
7123
|
-
const streamClient = options.streamClient ?? (options.durableStreamsUrl ? new StreamClient(durableStreamsServiceUrl(options.durableStreamsUrl, serviceId), { bearer: options.durableStreamsBearer }) : void 0);
|
|
7144
|
+
const streamClient = options.streamClient ?? (options.durableStreamsUrl ? new StreamClient(durableStreamsServiceUrl(options.durableStreamsUrl, serviceId, { scope: `stream-root` }), { bearer: options.durableStreamsBearer }) : void 0);
|
|
7124
7145
|
if (!streamClient) throw new Error(`Either durableStreamsUrl or streamClient is required`);
|
|
7125
7146
|
const registry = new PostgresRegistry(options.db, serviceId);
|
|
7126
7147
|
const wakeRegistry = options.wakeRegistry ?? new WakeRegistry(options.db, serviceId);
|
|
@@ -7284,7 +7305,7 @@ var ElectricAgentsServer = class {
|
|
|
7284
7305
|
constructor(options) {
|
|
7285
7306
|
if (!options.durableStreamsUrl && !options.durableStreamsServer) throw new Error(`Either durableStreamsUrl or durableStreamsServer is required`);
|
|
7286
7307
|
this.options = options;
|
|
7287
|
-
this.streamClient = options.durableStreamsUrl ? new StreamClient(durableStreamsServiceUrl(options.durableStreamsUrl, this.tenantId), { bearer: options.durableStreamsBearer }) : null;
|
|
7308
|
+
this.streamClient = options.durableStreamsUrl ? new StreamClient(durableStreamsServiceUrl(options.durableStreamsUrl, this.tenantId, { scope: `stream-root` }), { bearer: options.durableStreamsBearer }) : null;
|
|
7288
7309
|
}
|
|
7289
7310
|
get url() {
|
|
7290
7311
|
if (!this._url) throw new Error(`Server not started`);
|
|
@@ -7305,7 +7326,7 @@ var ElectricAgentsServer = class {
|
|
|
7305
7326
|
const streamsUrl = await this.options.durableStreamsServer.start();
|
|
7306
7327
|
serverLog.info(`[agent-server] durable streams server started at ${streamsUrl}`);
|
|
7307
7328
|
this.options.durableStreamsUrl = streamsUrl;
|
|
7308
|
-
this.streamClient = new StreamClient(durableStreamsServiceUrl(streamsUrl, this.tenantId), { bearer: this.options.durableStreamsBearer });
|
|
7329
|
+
this.streamClient = new StreamClient(durableStreamsServiceUrl(streamsUrl, this.tenantId, { scope: `stream-root` }), { bearer: this.options.durableStreamsBearer });
|
|
7309
7330
|
}
|
|
7310
7331
|
this.streamsAgent = new Agent({
|
|
7311
7332
|
keepAliveTimeout: 6e4,
|
|
@@ -7435,9 +7456,9 @@ var ElectricAgentsServer = class {
|
|
|
7435
7456
|
principal,
|
|
7436
7457
|
publicUrl: this.publicUrl,
|
|
7437
7458
|
localUrl: this._url,
|
|
7438
|
-
durableStreamsUrl: this.
|
|
7459
|
+
durableStreamsUrl: this.streamClient.baseUrl,
|
|
7439
7460
|
durableStreamsBearer: this.options.durableStreamsBearer,
|
|
7440
|
-
durableStreamsRouting: this.options.durableStreamsRouting
|
|
7461
|
+
durableStreamsRouting: this.options.durableStreamsRouting,
|
|
7441
7462
|
durableStreamsDispatcher: this.streamsAgent,
|
|
7442
7463
|
electricUrl: this.options.electricUrl,
|
|
7443
7464
|
electricSecret: this.options.electricSecret,
|