@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/index.cjs CHANGED
@@ -4206,11 +4206,18 @@ function durableStreamsBearerHeaders(bearer) {
4206
4206
  if (!bearer) return void 0;
4207
4207
  return { authorization: async () => await resolveDurableStreamsBearer(bearer) ?? `` };
4208
4208
  }
4209
- function durableStreamsServiceUrl(baseUrl, serviceId) {
4209
+ function durableStreamsServiceUrl(baseUrl, serviceId, options = {}) {
4210
4210
  const url = new URL(baseUrl);
4211
- if (/^\/v1\/stream\/[^/]+\/?$/.test(url.pathname)) return baseUrl.replace(/\/+$/, ``);
4212
- const base = baseUrl.replace(/\/+$/, ``);
4213
- return `${base}/v1/stream/${encodeURIComponent(serviceId)}`;
4211
+ if (/\/v1\/streams\/[^/]+\/?$/.test(url.pathname)) return baseUrl.replace(/\/+$/, ``);
4212
+ if (/\/v1\/stream\/[^/]+\/?$/.test(url.pathname)) return baseUrl.replace(/\/+$/, ``);
4213
+ const scope = options.scope ?? `service`;
4214
+ const encodedServiceId = encodeURIComponent(serviceId);
4215
+ const path$2 = url.pathname.replace(/\/+$/, ``) || `/`;
4216
+ if (path$2.endsWith(`/v1/streams`)) url.pathname = `${path$2}/${encodedServiceId}`;
4217
+ else if (path$2.endsWith(`/v1/stream`)) url.pathname = scope === `service` ? `${path$2}/${encodedServiceId}` : path$2;
4218
+ else if (scope === `stream-root`) url.pathname = `${path$2 === `/` ? `` : path$2}/v1/stream`;
4219
+ else url.pathname = `${path$2 === `/` ? `` : path$2}/v1/stream/${encodedServiceId}`;
4220
+ return url.toString().replace(/\/+$/, ``);
4214
4221
  }
4215
4222
  function isNotFoundError(err) {
4216
4223
  return err instanceof __durable_streams_client.DurableStreamError && err.code === ErrCodeNotFound || err instanceof __durable_streams_client.FetchError && err.status === 404;
@@ -4243,34 +4250,15 @@ var StreamClient = class {
4243
4250
  await applyDurableStreamsBearer(headers, this.options.bearer, { overwrite: opts.overwriteBearer });
4244
4251
  return headers;
4245
4252
  }
4246
- subscriptionServiceId() {
4247
- const url = new URL(this.baseUrl);
4248
- const match = /^(.*)\/v1\/stream\/([^/]+)\/?$/.exec(url.pathname);
4249
- return match ? decodeURIComponent(match[2]) : null;
4250
- }
4251
4253
  backendSubscriptionPath(path$2) {
4252
- const normalized = normalizeSubscriptionPath(path$2);
4253
- const serviceId = this.subscriptionServiceId();
4254
- if (!serviceId) return normalized;
4255
- if (normalized === serviceId || normalized.startsWith(`${serviceId}/`)) return normalized;
4256
- return `${serviceId}/${normalized}`;
4254
+ return normalizeSubscriptionPath(path$2);
4257
4255
  }
4258
4256
  runtimeSubscriptionPath(path$2) {
4259
- const normalized = normalizeSubscriptionPath(path$2);
4260
- const serviceId = this.subscriptionServiceId();
4261
- if (!serviceId) return normalized;
4262
- return normalized.startsWith(`${serviceId}/`) ? normalized.slice(serviceId.length + 1) : normalized;
4257
+ return normalizeSubscriptionPath(path$2);
4263
4258
  }
4264
4259
  subscriptionUrl(subscriptionId) {
4265
4260
  const url = new URL(this.baseUrl);
4266
- const match = /^(.*)\/v1\/stream\/([^/]+)\/?$/.exec(url.pathname);
4267
- if (match) {
4268
- const [, prefix = ``, serviceId] = match;
4269
- url.pathname = `${prefix}/v1/stream-meta/subscriptions/${encodeURIComponent(subscriptionId)}`;
4270
- url.searchParams.set(`service`, decodeURIComponent(serviceId));
4271
- return url.toString();
4272
- }
4273
- url.pathname = `${url.pathname.replace(/\/+$/, ``)}/v1/stream-meta/subscriptions/${encodeURIComponent(subscriptionId)}`;
4261
+ url.pathname = `${url.pathname.replace(/\/+$/, ``)}/__ds/subscriptions/${encodeURIComponent(subscriptionId)}`;
4274
4262
  return url.toString();
4275
4263
  }
4276
4264
  subscriptionChildUrl(subscriptionId, ...segments) {
@@ -4685,7 +4673,7 @@ var ElectricAgentsTenantRuntime = class {
4685
4673
  this.service = this.serviceId;
4686
4674
  this.db = options.db;
4687
4675
  if (options.streamClient) this.streamClient = options.streamClient;
4688
- else if (options.durableStreamsUrl) this.streamClient = new StreamClient(durableStreamsServiceUrl(options.durableStreamsUrl, this.serviceId), { bearer: options.durableStreamsBearer });
4676
+ else if (options.durableStreamsUrl) this.streamClient = new StreamClient(durableStreamsServiceUrl(options.durableStreamsUrl, this.serviceId, { scope: `stream-root` }), { bearer: options.durableStreamsBearer });
4689
4677
  else throw new Error(`Either durableStreamsUrl or streamClient is required`);
4690
4678
  this.registry = options.registry ?? new PostgresRegistry(this.db, this.serviceId);
4691
4679
  this.wakeRegistry = options.wakeRegistry;
@@ -5954,27 +5942,6 @@ function validateParsedBody(schema, parsed) {
5954
5942
  };
5955
5943
  }
5956
5944
 
5957
- //#endregion
5958
- //#region src/routing/tenant-stream-paths.ts
5959
- function withoutLeadingSlash(path$2) {
5960
- return path$2.replace(/^\/+/, ``);
5961
- }
5962
- function withLeadingSlash(path$2) {
5963
- return path$2.startsWith(`/`) ? path$2 : `/${path$2}`;
5964
- }
5965
- function prefixTenantStreamPath(path$2, tenantId) {
5966
- const normalized = withoutLeadingSlash(path$2);
5967
- if (!normalized || normalized === tenantId) return tenantId;
5968
- if (normalized.startsWith(`${tenantId}/`)) return normalized;
5969
- return `${tenantId}/${normalized}`;
5970
- }
5971
- function stripTenantStreamPrefix(path$2, tenantId) {
5972
- const normalized = withoutLeadingSlash(path$2);
5973
- if (normalized === tenantId) return ``;
5974
- if (normalized.startsWith(`${tenantId}/`)) return normalized.slice(tenantId.length + 1);
5975
- return normalized;
5976
- }
5977
-
5978
5945
  //#endregion
5979
5946
  //#region src/routing/durable-streams-routing-adapter.ts
5980
5947
  function appendSearch(target, source) {
@@ -5985,43 +5952,30 @@ function removeServiceQuery(target) {
5985
5952
  target.searchParams.delete(`service`);
5986
5953
  return target;
5987
5954
  }
5988
- function logicalStreamPathFromRequest(requestUrl, serviceId) {
5989
- const incomingUrl = new URL(requestUrl, `http://localhost`);
5990
- const segments = incomingUrl.pathname.split(`/`).filter(Boolean);
5991
- if (segments[0] === `v1` && segments[1] === `stream`) return {
5992
- incomingUrl,
5993
- streamPath: segments.length > 2 ? `/${segments.slice(3).join(`/`)}` : `/`
5994
- };
5995
- return {
5996
- incomingUrl,
5997
- streamPath: incomingUrl.pathname || `/${serviceId}`
5998
- };
5999
- }
6000
- function backendStreamUrl(input, backendStreamPath) {
6001
- const path$2 = backendStreamPath.replace(/^\/+/, ``);
6002
- const target = new URL(`/v1/stream/${path$2}`, input.durableStreamsUrl);
6003
- return target;
5955
+ function withoutTrailingSlash(pathname) {
5956
+ return pathname.replace(/\/+$/, ``) || `/`;
6004
5957
  }
6005
- function streamMetaUrlWithoutService(input) {
5958
+ function appendRequestPathToStreamRoot(input) {
6006
5959
  const incomingUrl = new URL(input.requestUrl, `http://localhost`);
6007
- return removeServiceQuery(appendSearch(new URL(incomingUrl.pathname, input.durableStreamsUrl), incomingUrl));
6008
- }
6009
- const pathPrefixedSingleTenantDurableStreamsRoutingAdapter = {
6010
- streamUrl(input) {
6011
- const { incomingUrl, streamPath } = logicalStreamPathFromRequest(input.requestUrl, input.serviceId);
6012
- const target = backendStreamUrl(input, prefixTenantStreamPath(streamPath, input.serviceId));
6013
- return removeServiceQuery(appendSearch(target, incomingUrl));
6014
- },
6015
- streamMetaUrl: streamMetaUrlWithoutService,
6016
- toBackendStreamPath(serviceId, streamPath) {
6017
- return prefixTenantStreamPath(streamPath, serviceId);
5960
+ const path$2 = incomingUrl.pathname.replace(/^\/+/, ``);
5961
+ const target = new URL(input.durableStreamsUrl);
5962
+ target.pathname = path$2 ? `${withoutTrailingSlash(target.pathname)}/${path$2}` : withoutTrailingSlash(target.pathname);
5963
+ return removeServiceQuery(appendSearch(target, incomingUrl));
5964
+ }
5965
+ const streamRootDurableStreamsRoutingAdapter = {
5966
+ streamUrl: appendRequestPathToStreamRoot,
5967
+ controlUrl: appendRequestPathToStreamRoot,
5968
+ toBackendStreamPath(_serviceId, streamPath) {
5969
+ return streamPath.replace(/^\/+/, ``);
6018
5970
  },
6019
- toRuntimeStreamPath(serviceId, streamPath) {
6020
- return stripTenantStreamPrefix(streamPath, serviceId);
5971
+ toRuntimeStreamPath(_serviceId, streamPath) {
5972
+ return streamPath.replace(/^\/+/, ``);
6021
5973
  }
6022
5974
  };
6023
- function resolveDurableStreamsRoutingAdapter(adapter) {
6024
- return adapter ?? pathPrefixedSingleTenantDurableStreamsRoutingAdapter;
5975
+ const pathPrefixedSingleTenantDurableStreamsRoutingAdapter = streamRootDurableStreamsRoutingAdapter;
5976
+ const tenantRootDurableStreamsRoutingAdapter = streamRootDurableStreamsRoutingAdapter;
5977
+ function resolveDurableStreamsRoutingAdapter(adapter, _durableStreamsUrl) {
5978
+ return adapter ?? streamRootDurableStreamsRoutingAdapter;
6025
5979
  }
6026
5980
 
6027
5981
  //#endregion
@@ -6058,13 +6012,13 @@ function buildElectricProxyTarget(options) {
6058
6012
  return target;
6059
6013
  }
6060
6014
  async function forwardFetchRequest(options) {
6061
- const routingAdapter = resolveDurableStreamsRoutingAdapter(options.durableStreamsRouting);
6015
+ const routingAdapter = resolveDurableStreamsRoutingAdapter(options.durableStreamsRouting, options.durableStreamsUrl);
6062
6016
  const routingInput = {
6063
6017
  durableStreamsUrl: options.durableStreamsUrl,
6064
6018
  serviceId: options.serviceId,
6065
6019
  requestUrl: options.request.url
6066
6020
  };
6067
- const upstreamUrl = options.route === `stream-meta` ? routingAdapter.streamMetaUrl(routingInput) : routingAdapter.streamUrl(routingInput);
6021
+ const upstreamUrl = options.route === `control` ? routingAdapter.controlUrl(routingInput) : routingAdapter.streamUrl(routingInput);
6068
6022
  const headers = new Headers(options.request.headers);
6069
6023
  if (options.durableStreamsBearerMode !== `none`) await applyDurableStreamsBearer(headers, options.durableStreamsBearer, { overwrite: options.durableStreamsBearerMode !== `if-missing` });
6070
6024
  const init = {
@@ -6102,8 +6056,21 @@ function sqlStringLiteral(value) {
6102
6056
  //#endregion
6103
6057
  //#region src/routing/durable-streams-router.ts
6104
6058
  const subscriptionProxyBodySchema = __sinclair_typebox.Type.Object({ webhook: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Object({ url: __sinclair_typebox.Type.String() }, { additionalProperties: true })) }, { additionalProperties: true });
6059
+ const subscriptionControlActions = [
6060
+ `callback`,
6061
+ `claim`,
6062
+ `ack`,
6063
+ `release`
6064
+ ];
6105
6065
  const durableStreamsRouter = (0, itty_router.Router)();
6106
- durableStreamsRouter.all(`/v1/stream-meta/subscriptions/*`, subscriptionProxy);
6066
+ durableStreamsRouter.put(`/__ds/subscriptions/:subscriptionId`, putSubscriptionBase);
6067
+ durableStreamsRouter.get(`/__ds/subscriptions/:subscriptionId`, getSubscriptionBase);
6068
+ durableStreamsRouter.delete(`/__ds/subscriptions/:subscriptionId`, deleteSubscriptionBase);
6069
+ durableStreamsRouter.post(`/__ds/subscriptions/:subscriptionId/streams`, postSubscriptionStreams);
6070
+ durableStreamsRouter.delete(`/__ds/subscriptions/:subscriptionId/streams/:streamPath+`, deleteSubscriptionStream);
6071
+ for (const action of subscriptionControlActions) durableStreamsRouter.post(`/__ds/subscriptions/:subscriptionId/${action}`, subscriptionAction(action));
6072
+ durableStreamsRouter.all(`/__ds`, controlPassThrough);
6073
+ durableStreamsRouter.all(`/__ds/*`, controlPassThrough);
6107
6074
  durableStreamsRouter.post(`*`, streamAppend);
6108
6075
  durableStreamsRouter.all(`*`, proxyPassThrough);
6109
6076
  function bodyFromBytes$1(body) {
@@ -6116,7 +6083,7 @@ function responseFromUpstream$1(response, body) {
6116
6083
  headers: responseHeaders(response)
6117
6084
  });
6118
6085
  }
6119
- async function forwardToDurableStreams(ctx, request, body, route = `stream`, urlOverride) {
6086
+ async function forwardToDurableStreams(ctx, request, body, route = `stream`, urlOverride, durableStreamsBearerMode = `overwrite`) {
6120
6087
  const headers = new Headers(request.headers);
6121
6088
  headers.delete(`host`);
6122
6089
  let requestBody = body;
@@ -6130,24 +6097,13 @@ async function forwardToDurableStreams(ctx, request, body, route = `stream`, url
6130
6097
  body: requestBody,
6131
6098
  durableStreamsUrl: ctx.durableStreamsUrl,
6132
6099
  durableStreamsBearer: ctx.durableStreamsBearer,
6133
- durableStreamsBearerMode: usesSubscriptionScopedBearer(urlOverride ?? request.url) ? `if-missing` : `overwrite`,
6100
+ durableStreamsBearerMode,
6134
6101
  durableStreamsRouting: ctx.durableStreamsRouting,
6135
6102
  serviceId: ctx.service,
6136
6103
  dispatcher: ctx.durableStreamsDispatcher,
6137
6104
  route
6138
6105
  });
6139
6106
  }
6140
- function subscriptionIdFromPath(pathname) {
6141
- const match = /^\/v1\/stream-meta\/subscriptions\/([^/]+)(?:\/.*)?$/.exec(pathname);
6142
- return match ? decodeURIComponent(match[1]) : null;
6143
- }
6144
- function isSubscriptionBasePath(pathname) {
6145
- return /^\/v1\/stream-meta\/subscriptions\/[^/]+\/?$/.test(pathname);
6146
- }
6147
- function usesSubscriptionScopedBearer(requestUrl) {
6148
- const pathname = new URL(requestUrl, `http://localhost`).pathname;
6149
- return /^\/v1\/stream-meta\/subscriptions\/[^/]+\/(?:ack|release|callback)\/?$/.test(pathname);
6150
- }
6151
6107
  function rewriteSubscriptionBodyForBackend(payload, service, routingAdapter) {
6152
6108
  if (typeof payload.pattern === `string`) payload.pattern = routingAdapter.toBackendStreamPath(service, payload.pattern);
6153
6109
  if (Array.isArray(payload.streams)) payload.streams = payload.streams.map((stream) => typeof stream === `string` ? routingAdapter.toBackendStreamPath(service, stream) : stream);
@@ -6192,44 +6148,50 @@ function decodeJson(bytes) {
6192
6148
  return null;
6193
6149
  }
6194
6150
  }
6195
- function rewriteSubscriptionStreamPathInUrl(requestUrl, service, routingAdapter) {
6196
- const match = /^(\/v1\/stream-meta\/subscriptions\/[^/]+\/streams\/)(.+)$/.exec(requestUrl.pathname);
6197
- if (!match) return requestUrl.toString();
6198
- const [, prefix, encodedPath] = match;
6199
- const streamPath = decodeURIComponent(encodedPath);
6200
- requestUrl.pathname = `${prefix}${encodeURIComponent(routingAdapter.toBackendStreamPath(service, streamPath))}`;
6201
- return requestUrl.toString();
6151
+ function routeParam$2(request, name) {
6152
+ const value = request.params[name];
6153
+ const raw = Array.isArray(value) ? value[0] : value;
6154
+ return decodeURIComponent(raw ?? ``);
6155
+ }
6156
+ function subscriptionRoutingAdapter(ctx) {
6157
+ return resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl);
6202
6158
  }
6203
- async function subscriptionProxy(request, ctx) {
6204
- const url = new URL(request.url);
6205
- const subscriptionId = subscriptionIdFromPath(url.pathname);
6206
- if (!subscriptionId) return void 0;
6207
- const routingAdapter = resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting);
6208
- let requestBody;
6159
+ async function rewriteSubscriptionRequestBody(request, ctx, subscriptionId, routingAdapter) {
6160
+ const body = await readRequestBody(request);
6161
+ if (body.length === 0) return {
6162
+ ok: true,
6163
+ body,
6164
+ targetWebhookUrl: null
6165
+ };
6166
+ const validation = validateBody(subscriptionProxyBodySchema, body);
6167
+ if (!validation.ok) return {
6168
+ ok: false,
6169
+ response: validation.response
6170
+ };
6171
+ const payload = validation.value;
6209
6172
  let targetWebhookUrl = null;
6210
- let requestUrl = request.url;
6211
- if ([`PUT`, `POST`].includes(request.method.toUpperCase())) {
6212
- requestBody = await readRequestBody(request);
6213
- if (requestBody.length > 0) {
6214
- const validation = validateBody(subscriptionProxyBodySchema, requestBody);
6215
- if (!validation.ok) return validation.response;
6216
- const payload = validation.value;
6217
- if (payload.webhook?.url !== void 0) {
6218
- targetWebhookUrl = rewriteLoopbackWebhookUrl(payload.webhook.url) ?? null;
6219
- payload.webhook.url = (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`);
6220
- }
6221
- rewriteSubscriptionBodyForBackend(payload, ctx.service, routingAdapter);
6222
- requestBody = new TextEncoder().encode(JSON.stringify(payload));
6223
- }
6173
+ if (payload.webhook?.url !== void 0) {
6174
+ targetWebhookUrl = rewriteLoopbackWebhookUrl(payload.webhook.url) ?? null;
6175
+ payload.webhook.url = (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`);
6224
6176
  }
6225
- if (request.method.toUpperCase() === `DELETE` && /\/streams\/.+$/.test(url.pathname)) requestUrl = rewriteSubscriptionStreamPathInUrl(url, ctx.service, routingAdapter);
6226
- const upstream = await forwardToDurableStreams(ctx, request, requestBody, `stream-meta`, requestUrl);
6177
+ rewriteSubscriptionBodyForBackend(payload, ctx.service, routingAdapter);
6178
+ return {
6179
+ ok: true,
6180
+ body: new TextEncoder().encode(JSON.stringify(payload)),
6181
+ targetWebhookUrl
6182
+ };
6183
+ }
6184
+ async function forwardSubscriptionRequest(request, ctx, routingAdapter, opts = {}) {
6185
+ const upstream = await forwardToDurableStreams(ctx, request, opts.body, `control`, opts.requestUrl, opts.bearerMode ?? `overwrite`);
6227
6186
  let responseBytes = upstream.body ? new Uint8Array(await upstream.arrayBuffer()) : new Uint8Array();
6228
6187
  responseBytes = rewriteSubscriptionResponseForClient(responseBytes, upstream, ctx.service, routingAdapter);
6229
- const response = responseFromUpstream$1(upstream, responseBytes);
6230
- if (!upstream.ok) return response;
6231
- if (request.method.toUpperCase() === `DELETE` && isSubscriptionBasePath(url.pathname)) await ctx.pgDb.delete(subscriptionWebhooks).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(subscriptionWebhooks.tenantId, ctx.service), (0, drizzle_orm.eq)(subscriptionWebhooks.subscriptionId, subscriptionId)));
6232
- else if (targetWebhookUrl) await ctx.pgDb.insert(subscriptionWebhooks).values({
6188
+ return {
6189
+ upstream,
6190
+ response: responseFromUpstream$1(upstream, responseBytes)
6191
+ };
6192
+ }
6193
+ async function upsertSubscriptionWebhook(ctx, subscriptionId, targetWebhookUrl) {
6194
+ await ctx.pgDb.insert(subscriptionWebhooks).values({
6233
6195
  tenantId: ctx.service,
6234
6196
  subscriptionId,
6235
6197
  webhookUrl: targetWebhookUrl
@@ -6237,8 +6199,64 @@ async function subscriptionProxy(request, ctx) {
6237
6199
  target: [subscriptionWebhooks.tenantId, subscriptionWebhooks.subscriptionId],
6238
6200
  set: { webhookUrl: targetWebhookUrl }
6239
6201
  });
6202
+ }
6203
+ async function deleteSubscriptionWebhook(ctx, subscriptionId) {
6204
+ await ctx.pgDb.delete(subscriptionWebhooks).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(subscriptionWebhooks.tenantId, ctx.service), (0, drizzle_orm.eq)(subscriptionWebhooks.subscriptionId, subscriptionId)));
6205
+ }
6206
+ function rewriteSubscriptionStreamPathInUrl(requestUrl, service, routingAdapter, streamPath) {
6207
+ const prefix = requestUrl.pathname.slice(0, requestUrl.pathname.indexOf(`/streams/`) + `/streams/`.length);
6208
+ requestUrl.pathname = `${prefix}${encodeURIComponent(routingAdapter.toBackendStreamPath(service, streamPath))}`;
6209
+ return requestUrl.toString();
6210
+ }
6211
+ async function putSubscriptionBase(request, ctx) {
6212
+ const subscriptionId = routeParam$2(request, `subscriptionId`);
6213
+ const routingAdapter = subscriptionRoutingAdapter(ctx);
6214
+ const rewrite = await rewriteSubscriptionRequestBody(request, ctx, subscriptionId, routingAdapter);
6215
+ if (!rewrite.ok) return rewrite.response;
6216
+ const { upstream, response } = await forwardSubscriptionRequest(request, ctx, routingAdapter, { body: rewrite.body });
6217
+ if (upstream.ok && rewrite.targetWebhookUrl) await upsertSubscriptionWebhook(ctx, subscriptionId, rewrite.targetWebhookUrl);
6218
+ return response;
6219
+ }
6220
+ async function getSubscriptionBase(request, ctx) {
6221
+ const routingAdapter = subscriptionRoutingAdapter(ctx);
6222
+ return (await forwardSubscriptionRequest(request, ctx, routingAdapter)).response;
6223
+ }
6224
+ async function deleteSubscriptionBase(request, ctx) {
6225
+ const subscriptionId = routeParam$2(request, `subscriptionId`);
6226
+ const routingAdapter = subscriptionRoutingAdapter(ctx);
6227
+ const { upstream, response } = await forwardSubscriptionRequest(request, ctx, routingAdapter);
6228
+ if (upstream.ok) await deleteSubscriptionWebhook(ctx, subscriptionId);
6240
6229
  return response;
6241
6230
  }
6231
+ async function postSubscriptionStreams(request, ctx) {
6232
+ const subscriptionId = routeParam$2(request, `subscriptionId`);
6233
+ const routingAdapter = subscriptionRoutingAdapter(ctx);
6234
+ const rewrite = await rewriteSubscriptionRequestBody(request, ctx, subscriptionId, routingAdapter);
6235
+ if (!rewrite.ok) return rewrite.response;
6236
+ return (await forwardSubscriptionRequest(request, ctx, routingAdapter, { body: rewrite.body })).response;
6237
+ }
6238
+ async function deleteSubscriptionStream(request, ctx) {
6239
+ const routingAdapter = subscriptionRoutingAdapter(ctx);
6240
+ const requestUrl = rewriteSubscriptionStreamPathInUrl(new URL(request.url), ctx.service, routingAdapter, routeParam$2(request, `streamPath`));
6241
+ return (await forwardSubscriptionRequest(request, ctx, routingAdapter, { requestUrl })).response;
6242
+ }
6243
+ function subscriptionAction(action) {
6244
+ return async (request, ctx) => {
6245
+ const subscriptionId = routeParam$2(request, `subscriptionId`);
6246
+ const routingAdapter = subscriptionRoutingAdapter(ctx);
6247
+ const rewrite = await rewriteSubscriptionRequestBody(request, ctx, subscriptionId, routingAdapter);
6248
+ if (!rewrite.ok) return rewrite.response;
6249
+ const bearerMode = action === `ack` || action === `release` || action === `callback` ? `if-missing` : `overwrite`;
6250
+ return (await forwardSubscriptionRequest(request, ctx, routingAdapter, {
6251
+ body: rewrite.body,
6252
+ bearerMode
6253
+ })).response;
6254
+ };
6255
+ }
6256
+ async function controlPassThrough(request, ctx) {
6257
+ const upstream = await forwardToDurableStreams(ctx, request, void 0, `control`);
6258
+ return responseFromUpstream$1(upstream);
6259
+ }
6242
6260
  async function streamAppend(request, ctx) {
6243
6261
  return await electricAgentsStreamAppendRouter.fetch(createStreamAppendRouteRequest(request), ctx.runtime, (req, body) => forwardFetchRequest({
6244
6262
  request: {
@@ -6259,10 +6277,9 @@ async function proxyPassThrough(request, ctx) {
6259
6277
  const upstream = await forwardToDurableStreams(ctx, request);
6260
6278
  const streamPath = new URL(request.url).pathname;
6261
6279
  const method = request.method.toUpperCase();
6262
- const isControlPath = streamPath.startsWith(`/v1/stream-meta/`);
6263
- const endTrackedRead = method === `GET` && !isControlPath ? await ctx.entityBridgeManager.beginClientRead(streamPath) : null;
6280
+ const endTrackedRead = method === `GET` ? await ctx.entityBridgeManager.beginClientRead(streamPath) : null;
6264
6281
  try {
6265
- if (method === `HEAD` && !isControlPath) await ctx.entityBridgeManager.touchByStreamPath(streamPath);
6282
+ if (method === `HEAD`) await ctx.entityBridgeManager.touchByStreamPath(streamPath);
6266
6283
  return responseFromUpstream$1(upstream);
6267
6284
  } finally {
6268
6285
  await endTrackedRead?.();
@@ -6606,11 +6623,11 @@ async function spawnEntity(request, ctx) {
6606
6623
  wake: parsed.wake,
6607
6624
  created_by: principal.url
6608
6625
  });
6626
+ await linkEntityDispatchSubscription(ctx, entity);
6609
6627
  if (parsed.initialMessage !== void 0) await ctx.entityManager.send(entity.url, {
6610
6628
  from: principal.url,
6611
6629
  payload: parsed.initialMessage
6612
6630
  });
6613
- await linkEntityDispatchSubscription(ctx, entity);
6614
6631
  return (0, itty_router.json)({
6615
6632
  ...toPublicEntity(entity),
6616
6633
  txid: entity.txid
@@ -6809,6 +6826,12 @@ function getRequestSpan(req) {
6809
6826
  return carrier(req)[SPAN_KEY];
6810
6827
  }
6811
6828
 
6829
+ //#endregion
6830
+ //#region src/routing/tenant-stream-paths.ts
6831
+ function withLeadingSlash(path$2) {
6832
+ return path$2.startsWith(`/`) ? path$2 : `/${path$2}`;
6833
+ }
6834
+
6812
6835
  //#endregion
6813
6836
  //#region src/routing/runners-router.ts
6814
6837
  const registerRunnerBodySchema = __sinclair_typebox.Type.Object({
@@ -7132,7 +7155,7 @@ async function webhookForward(request, ctx) {
7132
7155
  let runningEntityUrl = null;
7133
7156
  const parsedBody = parsedBodyResult.value;
7134
7157
  const newWebhook = newWebhookPayload(parsedBody);
7135
- const routingAdapter = resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting);
7158
+ const routingAdapter = resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl);
7136
7159
  if (parsedBody) {
7137
7160
  const rawPrimaryStream = newWebhook?.primaryStream ?? parsedBody.primary_stream ?? parsedBody.primaryStream ?? parsedBody.streamPath ?? null;
7138
7161
  const primaryStream = typeof rawPrimaryStream === `string` ? toRuntimeStreamPath(rawPrimaryStream, ctx.service, routingAdapter) : null;
@@ -7261,7 +7284,7 @@ async function callbackForward(request, ctx) {
7261
7284
  }
7262
7285
  return (0, itty_router.json)(responseBody);
7263
7286
  }
7264
- const upstreamBody = encodeCallbackForwardBody(ctx.service, consumerId, requestBody, resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting));
7287
+ const upstreamBody = encodeCallbackForwardBody(ctx.service, consumerId, requestBody, resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl));
7265
7288
  let upstream;
7266
7289
  try {
7267
7290
  const subscriptionId = durableStreamsSubscriptionCallback(target.callbackUrl);
@@ -7380,4 +7403,6 @@ exports.createDb = createDb
7380
7403
  exports.globalRouter = globalRouter
7381
7404
  exports.isUnregisteredTenantError = isUnregisteredTenantError
7382
7405
  exports.pathPrefixedSingleTenantDurableStreamsRoutingAdapter = pathPrefixedSingleTenantDurableStreamsRoutingAdapter
7383
- exports.runMigrations = runMigrations
7406
+ exports.runMigrations = runMigrations
7407
+ exports.streamRootDurableStreamsRoutingAdapter = streamRootDurableStreamsRoutingAdapter
7408
+ exports.tenantRootDurableStreamsRoutingAdapter = tenantRootDurableStreamsRoutingAdapter
package/dist/index.d.cts CHANGED
@@ -3551,7 +3551,6 @@ declare class StreamClient {
3551
3551
  private streamUrl;
3552
3552
  private streamHeaders;
3553
3553
  private requestHeaders;
3554
- private subscriptionServiceId;
3555
3554
  private backendSubscriptionPath;
3556
3555
  private runtimeSubscriptionPath;
3557
3556
  private subscriptionUrl;
@@ -4251,11 +4250,13 @@ interface DurableStreamsRoutingInput {
4251
4250
  }
4252
4251
  interface DurableStreamsRoutingAdapter {
4253
4252
  streamUrl(input: DurableStreamsRoutingInput): URL;
4254
- streamMetaUrl(input: DurableStreamsRoutingInput): URL;
4253
+ controlUrl(input: DurableStreamsRoutingInput): URL;
4255
4254
  toBackendStreamPath(serviceId: string, streamPath: string): string;
4256
4255
  toRuntimeStreamPath(serviceId: string, streamPath: string): string;
4257
4256
  }
4257
+ declare const streamRootDurableStreamsRoutingAdapter: DurableStreamsRoutingAdapter;
4258
4258
  declare const pathPrefixedSingleTenantDurableStreamsRoutingAdapter: DurableStreamsRoutingAdapter;
4259
+ declare const tenantRootDurableStreamsRoutingAdapter: DurableStreamsRoutingAdapter;
4259
4260
 
4260
4261
  //#endregion
4261
4262
  //#region src/routing/context.d.ts
@@ -4270,6 +4271,7 @@ interface TenantContext {
4270
4271
  principal: Principal;
4271
4272
  publicUrl: string;
4272
4273
  localUrl?: string;
4274
+ /** Resolved Durable Streams root URL for this tenant. */
4273
4275
  durableStreamsUrl: string;
4274
4276
  durableStreamsBearer?: DurableStreamsBearerProvider;
4275
4277
  durableStreamsRouting?: DurableStreamsRoutingAdapter;
@@ -4301,4 +4303,4 @@ declare class UnregisteredTenantError extends Error {
4301
4303
  declare function isUnregisteredTenantError(error: unknown): error is UnregisteredTenantError;
4302
4304
 
4303
4305
  //#endregion
4304
- export { AgentsHost, AgentsHostOptions, AgentsHostTenantConfig, AgentsHostTenantRuntime, AuthenticateRequest, ConsumerClaim, DEFAULT_TENANT_ID, DispatchPolicy, DispatchTarget, DrizzleDB, DurableStreamsBearerProvider, DurableStreamsRoutingAdapter, DurableStreamsRoutingInput, ElectricAgentsRunner, ElectricAgentsUser, EntityBridgeCoordinator, EntityDispatchState, GlobalRoutes, PgClient, Principal, PrincipalKind, PublicWakeNotification, RegisterRunnerRequest, RequestPrincipal, RunnerAdminStatus, RunnerHeartbeatRequest, RunnerKind, RunnerLiveness, SourceStreamOffset, StreamClient, StreamClientOptions, SubscriptionClaimResponse, SubscriptionCreateInput, SubscriptionResponse, SubscriptionStreamInfo, TenantContext, UnregisteredTenantError, WakeNotificationRow, createDb, globalRouter, isUnregisteredTenantError, pathPrefixedSingleTenantDurableStreamsRoutingAdapter, runMigrations };
4306
+ export { AgentsHost, AgentsHostOptions, AgentsHostTenantConfig, AgentsHostTenantRuntime, AuthenticateRequest, ConsumerClaim, DEFAULT_TENANT_ID, DispatchPolicy, DispatchTarget, DrizzleDB, DurableStreamsBearerProvider, DurableStreamsRoutingAdapter, DurableStreamsRoutingInput, ElectricAgentsRunner, ElectricAgentsUser, EntityBridgeCoordinator, EntityDispatchState, GlobalRoutes, PgClient, Principal, PrincipalKind, PublicWakeNotification, RegisterRunnerRequest, RequestPrincipal, RunnerAdminStatus, RunnerHeartbeatRequest, RunnerKind, RunnerLiveness, SourceStreamOffset, StreamClient, StreamClientOptions, SubscriptionClaimResponse, SubscriptionCreateInput, SubscriptionResponse, SubscriptionStreamInfo, TenantContext, UnregisteredTenantError, WakeNotificationRow, createDb, globalRouter, isUnregisteredTenantError, pathPrefixedSingleTenantDurableStreamsRoutingAdapter, runMigrations, streamRootDurableStreamsRoutingAdapter, tenantRootDurableStreamsRoutingAdapter };
package/dist/index.d.ts CHANGED
@@ -3552,7 +3552,6 @@ declare class StreamClient {
3552
3552
  private streamUrl;
3553
3553
  private streamHeaders;
3554
3554
  private requestHeaders;
3555
- private subscriptionServiceId;
3556
3555
  private backendSubscriptionPath;
3557
3556
  private runtimeSubscriptionPath;
3558
3557
  private subscriptionUrl;
@@ -4252,11 +4251,13 @@ interface DurableStreamsRoutingInput {
4252
4251
  }
4253
4252
  interface DurableStreamsRoutingAdapter {
4254
4253
  streamUrl(input: DurableStreamsRoutingInput): URL;
4255
- streamMetaUrl(input: DurableStreamsRoutingInput): URL;
4254
+ controlUrl(input: DurableStreamsRoutingInput): URL;
4256
4255
  toBackendStreamPath(serviceId: string, streamPath: string): string;
4257
4256
  toRuntimeStreamPath(serviceId: string, streamPath: string): string;
4258
4257
  }
4258
+ declare const streamRootDurableStreamsRoutingAdapter: DurableStreamsRoutingAdapter;
4259
4259
  declare const pathPrefixedSingleTenantDurableStreamsRoutingAdapter: DurableStreamsRoutingAdapter;
4260
+ declare const tenantRootDurableStreamsRoutingAdapter: DurableStreamsRoutingAdapter;
4260
4261
 
4261
4262
  //#endregion
4262
4263
  //#region src/routing/context.d.ts
@@ -4271,6 +4272,7 @@ interface TenantContext {
4271
4272
  principal: Principal;
4272
4273
  publicUrl: string;
4273
4274
  localUrl?: string;
4275
+ /** Resolved Durable Streams root URL for this tenant. */
4274
4276
  durableStreamsUrl: string;
4275
4277
  durableStreamsBearer?: DurableStreamsBearerProvider;
4276
4278
  durableStreamsRouting?: DurableStreamsRoutingAdapter;
@@ -4302,4 +4304,4 @@ declare class UnregisteredTenantError extends Error {
4302
4304
  declare function isUnregisteredTenantError(error: unknown): error is UnregisteredTenantError;
4303
4305
 
4304
4306
  //#endregion
4305
- export { AgentsHost, AgentsHostOptions, AgentsHostTenantConfig, AgentsHostTenantRuntime, AuthenticateRequest, ConsumerClaim, DEFAULT_TENANT_ID, DispatchPolicy, DispatchTarget, DrizzleDB, DurableStreamsBearerProvider, DurableStreamsRoutingAdapter, DurableStreamsRoutingInput, ElectricAgentsRunner, ElectricAgentsUser, EntityBridgeCoordinator, EntityDispatchState, GlobalRoutes, PgClient, Principal, PrincipalKind, PublicWakeNotification, RegisterRunnerRequest, RequestPrincipal, RunnerAdminStatus, RunnerHeartbeatRequest, RunnerKind, RunnerLiveness, SourceStreamOffset, StreamClient, StreamClientOptions, SubscriptionClaimResponse, SubscriptionCreateInput, SubscriptionResponse, SubscriptionStreamInfo, TenantContext, UnregisteredTenantError, WakeNotificationRow, createDb, globalRouter, isUnregisteredTenantError, pathPrefixedSingleTenantDurableStreamsRoutingAdapter, runMigrations };
4307
+ export { AgentsHost, AgentsHostOptions, AgentsHostTenantConfig, AgentsHostTenantRuntime, AuthenticateRequest, ConsumerClaim, DEFAULT_TENANT_ID, DispatchPolicy, DispatchTarget, DrizzleDB, DurableStreamsBearerProvider, DurableStreamsRoutingAdapter, DurableStreamsRoutingInput, ElectricAgentsRunner, ElectricAgentsUser, EntityBridgeCoordinator, EntityDispatchState, GlobalRoutes, PgClient, Principal, PrincipalKind, PublicWakeNotification, RegisterRunnerRequest, RequestPrincipal, RunnerAdminStatus, RunnerHeartbeatRequest, RunnerKind, RunnerLiveness, SourceStreamOffset, StreamClient, StreamClientOptions, SubscriptionClaimResponse, SubscriptionCreateInput, SubscriptionResponse, SubscriptionStreamInfo, TenantContext, UnregisteredTenantError, WakeNotificationRow, createDb, globalRouter, isUnregisteredTenantError, pathPrefixedSingleTenantDurableStreamsRoutingAdapter, runMigrations, streamRootDurableStreamsRoutingAdapter, tenantRootDurableStreamsRoutingAdapter };