@electric-ax/agents-server 0.4.9 → 0.4.11

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
@@ -451,7 +451,7 @@ const ErrCodeForkInProgress = `FORK_IN_PROGRESS`;
451
451
  const ErrCodeForkWaitTimeout = `FORK_WAIT_TIMEOUT`;
452
452
  const ErrCodeEntityPersistFailed = `ENTITY_PERSIST_FAILED`;
453
453
  const ErrCodeSubscriptionNotFound = `SUBSCRIPTION_NOT_FOUND`;
454
- const ErrCodeCallbackNotFound = `CALLBACK_NOT_FOUND`;
454
+ const ErrCodeWakeCallbackNotFound = `WAKE_CALLBACK_NOT_FOUND`;
455
455
 
456
456
  //#endregion
457
457
  //#region src/tenant.ts
@@ -2532,7 +2532,7 @@ async function linkStreamToTargetSubscription(ctx, target, entity, subscriptionI
2532
2532
  }
2533
2533
  const webhookUrl = rewriteLoopbackWebhookUrl(target.url);
2534
2534
  if (!webhookUrl) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Webhook dispatch target must include a valid URL`, 400);
2535
- const forwardUrl = (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`);
2535
+ const forwardUrl = (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`);
2536
2536
  await ensureSubscriptionIncludesStream(ctx, subscriptionId, streamPath, {
2537
2537
  type: `webhook`,
2538
2538
  streams: [streamPath],
@@ -3617,7 +3617,7 @@ var EntityManager = class {
3617
3617
  };
3618
3618
  }
3619
3619
  /**
3620
- * Deliver a message to an entity's main stream, with optional input schema
3620
+ * Deliver a message to an entity's main stream, with optional inbox schema
3621
3621
  * validation.
3622
3622
  */
3623
3623
  async send(entityUrl, req, opts) {
@@ -3705,7 +3705,7 @@ var EntityManager = class {
3705
3705
  if (result.changed && this.entityBridgeManager) await this.entityBridgeManager.onEntityChanged(entityUrl);
3706
3706
  return updated;
3707
3707
  }
3708
- async removeTag(entityUrl, key, token) {
3708
+ async deleteTag(entityUrl, key, token) {
3709
3709
  const entity = await this.registry.getEntity(entityUrl);
3710
3710
  if (!entity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
3711
3711
  if (!this.isValidWriteToken(entity, token)) throw new ElectricAgentsError(ErrCodeUnauthorized, `Invalid write token`, 401);
@@ -3716,7 +3716,7 @@ var EntityManager = class {
3716
3716
  if (result.changed && this.entityBridgeManager) await this.entityBridgeManager.onEntityChanged(entityUrl);
3717
3717
  return updated;
3718
3718
  }
3719
- async registerEntitiesSource(tags) {
3719
+ async ensureEntitiesMembershipStream(tags) {
3720
3720
  if (!this.entityBridgeManager) throw new Error(`Entity bridge manager not configured`);
3721
3721
  return this.entityBridgeManager.register(this.validateTags(tags));
3722
3722
  }
@@ -4145,7 +4145,7 @@ var EntityManager = class {
4145
4145
  return null;
4146
4146
  }
4147
4147
  /**
4148
- * Add new input/output schema keys to an entity type directly in Postgres.
4148
+ * Add new inbox/state schema keys to an entity type directly in Postgres.
4149
4149
  */
4150
4150
  async amendSchemas(typeName, schemas) {
4151
4151
  if (typeName === `principal`) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Entity type "principal" is built in and cannot be amended`, 400);
@@ -4185,7 +4185,7 @@ var EntityManager = class {
4185
4185
  }
4186
4186
  /**
4187
4187
  * Enrich webhook payload with entity context.
4188
- * Called by ElectricAgentsServer during webhook forwarding to inject entity context.
4188
+ * Called by ElectricAgentsServer during subscription webhook dispatch to inject entity context.
4189
4189
  */
4190
4190
  async enrichPayload(payload, consumer) {
4191
4191
  const entity = await this.registry.getEntityByStream(consumer.primary_stream);
@@ -5444,7 +5444,7 @@ var WakeRegistry = class {
5444
5444
  try {
5445
5445
  for (const message of messages) {
5446
5446
  await this.applyShapeMessage(message);
5447
- if (!settled && `control` in message.headers && message.headers.control === `up-to-date`) {
5447
+ if (!settled && (0, __electric_sql_client.isControlMessage)(message) && message.headers.control === `up-to-date`) {
5448
5448
  settled = true;
5449
5449
  resolve$1();
5450
5450
  }
@@ -6231,7 +6231,7 @@ function validateParsedBody(schema, parsed) {
6231
6231
  //#region src/routing/durable-streams-routing-adapter.ts
6232
6232
  function appendSearch(target, source) {
6233
6233
  source.searchParams.forEach((value, key) => {
6234
- if (key !== `service`) target.searchParams.append(key, value);
6234
+ target.searchParams.append(key, value);
6235
6235
  });
6236
6236
  return target;
6237
6237
  }
@@ -6551,7 +6551,7 @@ async function rewriteSubscriptionRequestBody(request, ctx, subscriptionId, rout
6551
6551
  let targetWebhookUrl = null;
6552
6552
  if (payload.webhook?.url !== void 0) {
6553
6553
  targetWebhookUrl = rewriteLoopbackWebhookUrl(payload.webhook.url) ?? null;
6554
- payload.webhook.url = (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/webhook-forward/${encodeURIComponent(subscriptionId)}`);
6554
+ payload.webhook.url = (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`);
6555
6555
  }
6556
6556
  rewriteSubscriptionBodyForBackend(payload, ctx.service, routingAdapter);
6557
6557
  return {
@@ -6674,20 +6674,6 @@ async function proxyPassThrough(request, ctx) {
6674
6674
  }
6675
6675
  }
6676
6676
 
6677
- //#endregion
6678
- //#region src/routing/cron-router.ts
6679
- const cronRegisterBodySchema = __sinclair_typebox.Type.Object({
6680
- expression: __sinclair_typebox.Type.String(),
6681
- timezone: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String())
6682
- });
6683
- const cronRouter = (0, itty_router.Router)({ base: `/_electric/cron` });
6684
- cronRouter.post(`/register`, withSchema(cronRegisterBodySchema), registerCron);
6685
- async function registerCron(request, ctx) {
6686
- const parsed = routeBody(request);
6687
- const streamPath = await ctx.entityManager.getOrCreateCronStream(parsed.expression, parsed.timezone);
6688
- return (0, itty_router.json)({ streamUrl: streamPath });
6689
- }
6690
-
6691
6677
  //#endregion
6692
6678
  //#region src/routing/electric-proxy-router.ts
6693
6679
  const electricProxyRouter = (0, itty_router.Router)({ base: `/_electric/electric` });
@@ -6721,7 +6707,7 @@ async function proxyElectric(request, ctx) {
6721
6707
 
6722
6708
  //#endregion
6723
6709
  //#region src/routing/entities-router.ts
6724
- const stringRecordSchema = __sinclair_typebox.Type.Record(__sinclair_typebox.Type.String(), __sinclair_typebox.Type.String());
6710
+ const stringRecordSchema$1 = __sinclair_typebox.Type.Record(__sinclair_typebox.Type.String(), __sinclair_typebox.Type.String());
6725
6711
  function writeTokenFromRequest(request) {
6726
6712
  const electricClaimToken = request.headers.get(`electric-claim-token`)?.trim();
6727
6713
  if (electricClaimToken) return electricClaimToken;
@@ -6738,7 +6724,7 @@ const wakeConditionSchema = __sinclair_typebox.Type.Union([__sinclair_typebox.Ty
6738
6724
  })]);
6739
6725
  const spawnBodySchema = __sinclair_typebox.Type.Object({
6740
6726
  args: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Record(__sinclair_typebox.Type.String(), __sinclair_typebox.Type.Unknown())),
6741
- tags: __sinclair_typebox.Type.Optional(stringRecordSchema),
6727
+ tags: __sinclair_typebox.Type.Optional(stringRecordSchema$1),
6742
6728
  parent: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
6743
6729
  dispatch_policy: __sinclair_typebox.Type.Optional(dispatchPolicySchema),
6744
6730
  initialMessage: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Unknown()),
@@ -6830,10 +6816,8 @@ const eventSourceSubscriptionBodySchema = __sinclair_typebox.Type.Object({
6830
6816
  lifetime: __sinclair_typebox.Type.Optional(subscriptionLifetimeSchema),
6831
6817
  reason: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String())
6832
6818
  });
6833
- const entitiesRegisterBodySchema = __sinclair_typebox.Type.Object({ tags: __sinclair_typebox.Type.Optional(stringRecordSchema) });
6834
6819
  const entitiesRouter = (0, itty_router.Router)({ base: `/_electric/entities` });
6835
6820
  entitiesRouter.get(`/`, listEntities);
6836
- entitiesRouter.post(`/register`, withSchema(entitiesRegisterBodySchema), registerEntitiesSource);
6837
6821
  entitiesRouter.put(`/:type/:instanceId`, withSpawnableEntityType, withSchema(spawnBodySchema), spawnEntity);
6838
6822
  entitiesRouter.get(`/:type/:instanceId`, withExistingEntity, getEntity);
6839
6823
  entitiesRouter.head(`/:type/:instanceId`, withExistingEntity, headEntity);
@@ -6844,7 +6828,7 @@ entitiesRouter.patch(`/:type/:instanceId/inbox/:messageKey`, withExistingEntity,
6844
6828
  entitiesRouter.delete(`/:type/:instanceId/inbox/:messageKey`, withExistingEntity, deleteInboxMessage);
6845
6829
  entitiesRouter.post(`/:type/:instanceId/fork`, withExistingEntity, withSchema(forkBodySchema), forkEntity);
6846
6830
  entitiesRouter.post(`/:type/:instanceId/tags/:tagKey`, withExistingEntity, withSchema(setTagBodySchema), setTag);
6847
- entitiesRouter.delete(`/:type/:instanceId/tags/:tagKey`, withExistingEntity, removeTag);
6831
+ entitiesRouter.delete(`/:type/:instanceId/tags/:tagKey`, withExistingEntity, deleteTag);
6848
6832
  entitiesRouter.put(`/:type/:instanceId/schedules/:scheduleId`, withExistingEntity, withSchema(scheduleBodySchema), upsertSchedule);
6849
6833
  entitiesRouter.delete(`/:type/:instanceId/schedules/:scheduleId`, withExistingEntity, deleteSchedule);
6850
6834
  entitiesRouter.put(`/:type/:instanceId/event-source-subscriptions/:subscriptionId`, withExistingEntity, withSchema(eventSourceSubscriptionBodySchema), upsertEventSourceSubscription);
@@ -6912,11 +6896,6 @@ async function listEntities({ query }, ctx) {
6912
6896
  });
6913
6897
  return (0, itty_router.json)(entities$1.map((entity) => toPublicEntity(entity)));
6914
6898
  }
6915
- async function registerEntitiesSource(request, ctx) {
6916
- const parsed = routeBody(request);
6917
- const result = await ctx.entityManager.registerEntitiesSource(parsed.tags ?? {});
6918
- return (0, itty_router.json)(result);
6919
- }
6920
6899
  async function upsertSchedule(request, ctx) {
6921
6900
  const principalMutationError = rejectPrincipalEntityMutation(request, `scheduled`);
6922
6901
  if (principalMutationError) return principalMutationError;
@@ -6997,7 +6976,7 @@ async function deleteEventSourceSubscription(request, ctx) {
6997
6976
  return (0, itty_router.json)(result);
6998
6977
  }
6999
6978
  async function setTag(request, ctx) {
7000
- const principalMutationError = rejectPrincipalEntityMutation(request, `tagged`);
6979
+ const principalMutationError = rejectPrincipalEntityMutation(request, `tag updated`);
7001
6980
  if (principalMutationError) return principalMutationError;
7002
6981
  const parsed = routeBody(request);
7003
6982
  const { entityUrl } = requireExistingEntityRoute(request);
@@ -7005,12 +6984,12 @@ async function setTag(request, ctx) {
7005
6984
  const updated = await ctx.entityManager.setTag(entityUrl, decodeURIComponent(request.params.tagKey), { value: parsed.value }, token);
7006
6985
  return (0, itty_router.json)(toPublicEntity(updated));
7007
6986
  }
7008
- async function removeTag(request, ctx) {
7009
- const principalMutationError = rejectPrincipalEntityMutation(request, `untagged`);
6987
+ async function deleteTag(request, ctx) {
6988
+ const principalMutationError = rejectPrincipalEntityMutation(request, `tag deleted`);
7010
6989
  if (principalMutationError) return principalMutationError;
7011
6990
  const { entityUrl } = requireExistingEntityRoute(request);
7012
6991
  const token = writeTokenFromRequest(request);
7013
- const updated = await ctx.entityManager.removeTag(entityUrl, decodeURIComponent(request.params.tagKey), token);
6992
+ const updated = await ctx.entityManager.deleteTag(entityUrl, decodeURIComponent(request.params.tagKey), token);
7014
6993
  return (0, itty_router.json)(toPublicEntity(updated));
7015
6994
  }
7016
6995
  async function forkEntity(request, ctx) {
@@ -7142,17 +7121,13 @@ const registerEntityTypeBodySchema = __sinclair_typebox.Type.Object({
7142
7121
  creation_schema: __sinclair_typebox.Type.Optional(jsonObjectSchema),
7143
7122
  inbox_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema),
7144
7123
  state_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema),
7145
- input_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema),
7146
- output_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema),
7147
7124
  serve_endpoint: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
7148
7125
  default_dispatch_policy: __sinclair_typebox.Type.Optional(dispatchPolicySchema)
7149
- });
7126
+ }, { additionalProperties: false });
7150
7127
  const amendEntityTypeSchemasBodySchema = __sinclair_typebox.Type.Object({
7151
- input_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema),
7152
- output_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema),
7153
7128
  inbox_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema),
7154
7129
  state_schemas: __sinclair_typebox.Type.Optional(schemaMapSchema)
7155
- });
7130
+ }, { additionalProperties: false });
7156
7131
  const entityTypesRouter = (0, itty_router.Router)({ base: `/_electric/entity-types` });
7157
7132
  entityTypesRouter.get(`/`, listEntityTypes);
7158
7133
  entityTypesRouter.post(`/`, withSchema(registerEntityTypeBodySchema), registerEntityType);
@@ -7192,8 +7167,8 @@ async function getEntityType(request, ctx) {
7192
7167
  async function amendSchemas(request, ctx) {
7193
7168
  const parsed = routeBody(request);
7194
7169
  const updated = await ctx.entityManager.amendSchemas(request.params.name, {
7195
- inbox_schemas: parsed.inbox_schemas ?? parsed.input_schemas,
7196
- state_schemas: parsed.state_schemas ?? parsed.output_schemas
7170
+ inbox_schemas: parsed.inbox_schemas,
7171
+ state_schemas: parsed.state_schemas
7197
7172
  });
7198
7173
  return (0, itty_router.json)(toPublicEntityType(updated));
7199
7174
  }
@@ -7203,13 +7178,12 @@ async function deleteEntityType(request, ctx) {
7203
7178
  }
7204
7179
  function normalizeEntityTypeRequest(parsed) {
7205
7180
  const serveEndpoint = rewriteLoopbackWebhookUrl(parsed.serve_endpoint);
7206
- const compatibilityFields = parsed;
7207
7181
  return {
7208
7182
  name: parsed.name ?? ``,
7209
7183
  description: parsed.description ?? ``,
7210
7184
  creation_schema: parsed.creation_schema,
7211
- inbox_schemas: parsed.inbox_schemas ?? compatibilityFields.input_schemas,
7212
- state_schemas: parsed.state_schemas ?? compatibilityFields.output_schemas,
7185
+ inbox_schemas: parsed.inbox_schemas,
7186
+ state_schemas: parsed.state_schemas,
7213
7187
  serve_endpoint: serveEndpoint,
7214
7188
  default_dispatch_policy: parsed.default_dispatch_policy ?? (serveEndpoint ? { targets: [{
7215
7189
  type: `webhook`,
@@ -7220,8 +7194,6 @@ function normalizeEntityTypeRequest(parsed) {
7220
7194
  function toPublicEntityType(entityType) {
7221
7195
  return {
7222
7196
  ...entityType,
7223
- input_schemas: entityType.inbox_schemas,
7224
- output_schemas: entityType.state_schemas,
7225
7197
  revision: entityType.revision
7226
7198
  };
7227
7199
  }
@@ -7305,13 +7277,35 @@ function errorMapper(err, req) {
7305
7277
  function rejectIfShuttingDown(req, ctx) {
7306
7278
  if (!ctx.isShuttingDown()) return void 0;
7307
7279
  const path$2 = new URL(req.url).pathname;
7308
- if (!path$2.startsWith(`/_electric/webhook-forward/`)) return void 0;
7280
+ if (!path$2.startsWith(`/_electric/subscription-webhooks/`)) return void 0;
7309
7281
  return apiError(503, `SERVER_STOPPING`, `Server is shutting down`);
7310
7282
  }
7311
7283
  function getRequestSpan(req) {
7312
7284
  return carrier(req)[SPAN_KEY];
7313
7285
  }
7314
7286
 
7287
+ //#endregion
7288
+ //#region src/routing/observations-router.ts
7289
+ const stringRecordSchema = __sinclair_typebox.Type.Record(__sinclair_typebox.Type.String(), __sinclair_typebox.Type.String());
7290
+ const ensureEntitiesMembershipStreamBodySchema = __sinclair_typebox.Type.Object({ tags: __sinclair_typebox.Type.Optional(stringRecordSchema) });
7291
+ const ensureCronStreamBodySchema = __sinclair_typebox.Type.Object({
7292
+ expression: __sinclair_typebox.Type.String(),
7293
+ timezone: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String())
7294
+ });
7295
+ const observationsRouter = (0, itty_router.Router)({ base: `/_electric/observations` });
7296
+ observationsRouter.post(`/entities/ensure-stream`, withSchema(ensureEntitiesMembershipStreamBodySchema), ensureEntitiesMembershipStream);
7297
+ observationsRouter.post(`/cron/ensure-stream`, withSchema(ensureCronStreamBodySchema), ensureCronStream);
7298
+ async function ensureEntitiesMembershipStream(request, ctx) {
7299
+ const parsed = routeBody(request);
7300
+ const result = await ctx.entityManager.ensureEntitiesMembershipStream(parsed.tags ?? {});
7301
+ return (0, itty_router.json)(result);
7302
+ }
7303
+ async function ensureCronStream(request, ctx) {
7304
+ const parsed = routeBody(request);
7305
+ const streamPath = await ctx.entityManager.getOrCreateCronStream(parsed.expression, parsed.timezone);
7306
+ return (0, itty_router.json)({ streamUrl: streamPath });
7307
+ }
7308
+
7315
7309
  //#endregion
7316
7310
  //#region src/routing/tenant-stream-paths.ts
7317
7311
  function withLeadingSlash(path$2) {
@@ -7650,7 +7644,7 @@ async function notificationFromClaim(ctx, input) {
7650
7644
  wakeId: input.claim.wake_id,
7651
7645
  streamPath: primaryStream,
7652
7646
  streams: streams$1,
7653
- callback: (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/callback-forward/${encodeURIComponent(input.claim.wake_id)}`),
7647
+ callback: (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/wake-callbacks/${encodeURIComponent(input.claim.wake_id)}`),
7654
7648
  claimToken: input.claim.token,
7655
7649
  triggerEvent: `message_received`,
7656
7650
  entity: {
@@ -7685,7 +7679,7 @@ const wakeRegistrationBodySchema = __sinclair_typebox.Type.Object({
7685
7679
  includeResponse: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Boolean()),
7686
7680
  manifestKey: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String())
7687
7681
  });
7688
- const webhookForwardBodySchema = __sinclair_typebox.Type.Object({
7682
+ const subscriptionWebhookBodySchema = __sinclair_typebox.Type.Object({
7689
7683
  subscription_id: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
7690
7684
  wake_id: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
7691
7685
  generation: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Number()),
@@ -7699,7 +7693,7 @@ const webhookForwardBodySchema = __sinclair_typebox.Type.Object({
7699
7693
  consumer_id: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
7700
7694
  callback: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String())
7701
7695
  }, { additionalProperties: true });
7702
- const callbackForwardBodySchema = __sinclair_typebox.Type.Object({
7696
+ const wakeCallbackBodySchema = __sinclair_typebox.Type.Object({
7703
7697
  epoch: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Number()),
7704
7698
  generation: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Number()),
7705
7699
  wakeId: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.String()),
@@ -7712,13 +7706,13 @@ const internalRouter = (0, itty_router.Router)({ base: `/_electric` });
7712
7706
  internalRouter.get(`/health`, () => (0, itty_router.json)({ status: `ok` }));
7713
7707
  internalRouter.get(`/event-sources`, listEventSources);
7714
7708
  internalRouter.post(`/wake`, withSchema(wakeRegistrationBodySchema), registerWake);
7715
- internalRouter.post(`/webhook-forward/:subscriptionId`, webhookForward);
7716
- internalRouter.post(`/callback-forward/:consumerId`, callbackForward);
7709
+ internalRouter.post(`/subscription-webhooks/:subscriptionId`, subscriptionWebhook);
7710
+ internalRouter.post(`/wake-callbacks/:consumerId`, wakeCallback);
7717
7711
  internalRouter.all(`/runners`, runnersRouter.fetch);
7718
7712
  internalRouter.all(`/runners/*`, runnersRouter.fetch);
7719
7713
  internalRouter.all(`/entities/*`, entitiesRouter.fetch);
7720
7714
  internalRouter.all(`/entity-types/*`, entityTypesRouter.fetch);
7721
- internalRouter.all(`/cron/*`, cronRouter.fetch);
7715
+ internalRouter.all(`/observations/*`, observationsRouter.fetch);
7722
7716
  internalRouter.get(`/electric/*`, electricProxyRouter.fetch);
7723
7717
  internalRouter.all(`*`, () => (0, itty_router.status)(404));
7724
7718
  function routeParam(request, name) {
@@ -7751,13 +7745,30 @@ function resolveWebhookSigner(ctx) {
7751
7745
  return ctx.webhookSigner ?? getDefaultWebhookSigner();
7752
7746
  }
7753
7747
  function durableStreamsWebhookJwksUrl(ctx) {
7754
- if (!ctx.durableStreamsRouting) return (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.durableStreamsUrl, `/__ds/jwks.json`);
7748
+ if (!ctx.durableStreamsRouting) return appendPathToBackendUrl(ctx.durableStreamsUrl, `/__ds/jwks.json`);
7755
7749
  return resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl).controlUrl({
7756
7750
  durableStreamsUrl: ctx.durableStreamsUrl,
7757
7751
  serviceId: ctx.service,
7758
7752
  requestUrl: (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/__ds/jwks.json`)
7759
7753
  }).toString();
7760
7754
  }
7755
+ function appendPathToBackendUrl(baseUrl, path$2) {
7756
+ const base = new URL(baseUrl);
7757
+ const pathUrl = new URL(path$2, `http://electric-agents.local`);
7758
+ const basePath = base.pathname === `/` ? `` : base.pathname.replace(/\/+$/, ``);
7759
+ const suffix = pathUrl.pathname.startsWith(`/`) ? pathUrl.pathname : `/${pathUrl.pathname}`;
7760
+ const target = new URL(base);
7761
+ target.pathname = `${basePath}${suffix}`;
7762
+ target.search = ``;
7763
+ target.hash = pathUrl.hash;
7764
+ base.searchParams.forEach((value, key) => {
7765
+ target.searchParams.append(key, value);
7766
+ });
7767
+ pathUrl.searchParams.forEach((value, key) => {
7768
+ target.searchParams.append(key, value);
7769
+ });
7770
+ return target.toString();
7771
+ }
7761
7772
  function durableStreamsJwksFetchClient(ctx) {
7762
7773
  return async (input, init) => {
7763
7774
  const headers = new Headers(init?.headers);
@@ -7821,10 +7832,10 @@ async function listEventSources(_request, ctx) {
7821
7832
  function isAgentVisibleEventSource(source) {
7822
7833
  return source.agentVisible === true && source.status === `active`;
7823
7834
  }
7824
- async function webhookForward(request, ctx) {
7835
+ async function subscriptionWebhook(request, ctx) {
7825
7836
  const subscriptionId = routeParam(request, `subscriptionId`);
7826
7837
  const rootSpan = getRequestSpan(request);
7827
- rootSpan?.updateName(`webhook-forward`);
7838
+ rootSpan?.updateName(`subscription-webhook`);
7828
7839
  rootSpan?.setAttribute(`electric_agents.webhook.subscription_id`, subscriptionId);
7829
7840
  const body = await readRequestBody(request);
7830
7841
  const signatureError = await verifyDurableStreamsWebhook(request, ctx, body);
@@ -7838,7 +7849,7 @@ async function webhookForward(request, ctx) {
7838
7849
  }
7839
7850
  });
7840
7851
  if (!targetWebhookUrl) return apiError(404, ErrCodeSubscriptionNotFound, `Unknown webhook subscription`);
7841
- const parsedBodyResult = validateOptionalJsonBody(webhookForwardBodySchema, body, request.headers.get(`content-type`));
7852
+ const parsedBodyResult = validateOptionalJsonBody(subscriptionWebhookBodySchema, body, request.headers.get(`content-type`));
7842
7853
  if (!parsedBodyResult.ok) return parsedBodyResult.response;
7843
7854
  let forwardBody = body;
7844
7855
  let runningEntityUrl = null;
@@ -7884,7 +7895,7 @@ async function webhookForward(request, ctx) {
7884
7895
  span.end();
7885
7896
  }
7886
7897
  }).catch((err) => {
7887
- serverLog.warn(`[webhook-forward] consumerCallbacks upsert failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
7898
+ serverLog.warn(`[subscription-webhook] consumerCallbacks upsert failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
7888
7899
  }) : void 0;
7889
7900
  const [entity, enriched] = await Promise.all([entityPromise, enrichPromise]);
7890
7901
  if (entity?.status === `stopped` || entity?.status === `paused`) {
@@ -7905,7 +7916,7 @@ async function webhookForward(request, ctx) {
7905
7916
  runningEntityUrl = entity.url;
7906
7917
  }
7907
7918
  if (consumerId && callbackUrl) {
7908
- const callback = (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/callback-forward/${encodeURIComponent(consumerId)}`);
7919
+ const callback = (0, __electric_ax_agents_runtime.appendPathToUrl)(ctx.publicUrl, `/_electric/wake-callbacks/${encodeURIComponent(consumerId)}`);
7909
7920
  enriched.callback = callback;
7910
7921
  if (newWebhook) {
7911
7922
  enriched.consumerId = newWebhook.wakeId;
@@ -7942,21 +7953,21 @@ async function webhookForward(request, ctx) {
7942
7953
  });
7943
7954
  } catch (err) {
7944
7955
  if (runningEntityUrl) await ctx.entityManager.registry.updateStatus(runningEntityUrl, `idle`);
7945
- return apiError(502, `WEBHOOK_FORWARD_FAILED`, err instanceof Error ? err.message : String(err));
7956
+ return apiError(502, `SUBSCRIPTION_WEBHOOK_FAILED`, err instanceof Error ? err.message : String(err));
7946
7957
  }
7947
7958
  const responseBytes = upstream.body ? new Uint8Array(await upstream.arrayBuffer()) : new Uint8Array();
7948
7959
  return responseFromUpstream(upstream, responseBytes);
7949
7960
  }
7950
- async function callbackForward(request, ctx) {
7961
+ async function wakeCallback(request, ctx) {
7951
7962
  const consumerId = routeParam(request, `consumerId`);
7952
7963
  const rows = await ctx.pgDb.select().from(consumerCallbacks).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(consumerCallbacks.tenantId, ctx.service), (0, drizzle_orm.eq)(consumerCallbacks.consumerId, consumerId))).limit(1);
7953
7964
  const target = rows[0] ? {
7954
7965
  callbackUrl: rows[0].callbackUrl,
7955
7966
  primaryStream: rows[0].primaryStream
7956
7967
  } : void 0;
7957
- if (!target) return apiError(404, ErrCodeCallbackNotFound, `Unknown callback-forward consumer`);
7968
+ if (!target) return apiError(404, ErrCodeWakeCallbackNotFound, `Unknown wake-callback consumer`);
7958
7969
  const body = await readRequestBody(request);
7959
- const parsedBodyResult = validateOptionalJsonBody(callbackForwardBodySchema, body, request.headers.get(`content-type`));
7970
+ const parsedBodyResult = validateOptionalJsonBody(wakeCallbackBodySchema, body, request.headers.get(`content-type`));
7960
7971
  if (!parsedBodyResult.ok) return parsedBodyResult.response;
7961
7972
  const requestBody = parsedBodyResult.value;
7962
7973
  const isClaimRequest = requestBody?.wakeId !== void 0 || requestBody?.wake_id !== void 0;
@@ -7974,14 +7985,14 @@ async function callbackForward(request, ctx) {
7974
7985
  }
7975
7986
  return (0, itty_router.json)(responseBody);
7976
7987
  }
7977
- const upstreamBody = encodeCallbackForwardBody(ctx.service, consumerId, requestBody, resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl));
7988
+ const upstreamBody = encodeWakeCallbackBody(ctx.service, consumerId, requestBody, resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl));
7978
7989
  let upstream;
7979
7990
  try {
7980
7991
  const subscriptionId = durableStreamsSubscriptionCallback(target.callbackUrl);
7981
7992
  if (subscriptionId) {
7982
7993
  const token = claimTokenFromRequest(request);
7983
7994
  if (!token) return apiError(401, `UNAUTHORIZED`, `Missing claim token`);
7984
- const upstreamPayload = encodeCallbackForwardPayload(consumerId, requestBody, (stream) => stream.replace(/^\/+/, ``));
7995
+ const upstreamPayload = encodeWakeCallbackPayload(consumerId, requestBody, (stream) => stream.replace(/^\/+/, ``));
7985
7996
  const result = await ctx.streamClient.ackSubscription(subscriptionId, token, upstreamPayload);
7986
7997
  upstream = (0, itty_router.json)(result);
7987
7998
  } else upstream = await fetch(target.callbackUrl, {
@@ -7990,7 +8001,7 @@ async function callbackForward(request, ctx) {
7990
8001
  body: bodyFromBytes(upstreamBody)
7991
8002
  });
7992
8003
  } catch (err) {
7993
- return apiError(502, `CALLBACK_FORWARD_FAILED`, err instanceof Error ? err.message : String(err));
8004
+ return apiError(502, `WAKE_CALLBACK_FAILED`, err instanceof Error ? err.message : String(err));
7994
8005
  }
7995
8006
  let responseBytes = upstream.body ? new Uint8Array(await upstream.arrayBuffer()) : new Uint8Array();
7996
8007
  if (isClaimRequest && upstream.ok && target.primaryStream) {
@@ -8010,7 +8021,7 @@ async function callbackForward(request, ctx) {
8010
8021
  epoch
8011
8022
  });
8012
8023
  if (upstream.ok && isDoneRequest && target.primaryStream) {
8013
- serverLog.info(`[callback-forward] done received for stream=${target.primaryStream} consumer=${consumerId}`);
8024
+ serverLog.info(`[wake-callback] done received for stream=${target.primaryStream} consumer=${consumerId}`);
8014
8025
  const stillOwnsClaim = ctx.runtime.claimWriteTokens.owns(ctx.service, target.primaryStream, consumerId);
8015
8026
  const entity = await ctx.entityManager.registry.getEntityByStream(target.primaryStream);
8016
8027
  let entityCleared = false;
@@ -8032,13 +8043,13 @@ async function callbackForward(request, ctx) {
8032
8043
  if (entity && (entityCleared || stillOwnsClaim)) {
8033
8044
  await ctx.entityManager.registry.updateStatus(entity.url, entity.status === `stopping` ? `stopped` : `idle`);
8034
8045
  await ctx.entityBridgeManager.onEntityChanged(entity.url);
8035
- serverLog.info(`[callback-forward] status updated after done for ${entity.url}`);
8036
- } else if (!entity) serverLog.warn(`[callback-forward] done received but no entity found for stream=${target.primaryStream}`);
8046
+ serverLog.info(`[wake-callback] status updated after done for ${entity.url}`);
8047
+ } else if (!entity) serverLog.warn(`[wake-callback] done received but no entity found for stream=${target.primaryStream}`);
8037
8048
  if (stillOwnsClaim) ctx.runtime.claimWriteTokens.clearStream(ctx.service, target.primaryStream);
8038
- else if (entity) serverLog.info(`[callback-forward] done arrived after in-memory token evicted (stream=${target.primaryStream} consumer=${consumerId})`);
8039
- } else if (requestBody?.done === true) serverLog.warn(`[callback-forward] done received but skipped: upstream.ok=${upstream.ok} primaryStream=${target.primaryStream ?? `null`} consumer=${consumerId}`);
8049
+ else if (entity) serverLog.info(`[wake-callback] done arrived after in-memory token evicted (stream=${target.primaryStream} consumer=${consumerId})`);
8050
+ } else if (requestBody?.done === true) serverLog.warn(`[wake-callback] done received but skipped: upstream.ok=${upstream.ok} primaryStream=${target.primaryStream ?? `null`} consumer=${consumerId}`);
8040
8051
  } catch (err) {
8041
- serverLog.error(`[callback-forward] error processing done for consumer=${consumerId}: ${err instanceof Error ? err.message : String(err)}`);
8052
+ serverLog.error(`[wake-callback] error processing done for consumer=${consumerId}: ${err instanceof Error ? err.message : String(err)}`);
8042
8053
  }
8043
8054
  return responseFromUpstream(upstream, responseBytes);
8044
8055
  }
@@ -8047,11 +8058,11 @@ async function mintClaimWriteToken(ctx, streamPath, consumerId) {
8047
8058
  if (!entity) return void 0;
8048
8059
  return ctx.runtime.claimWriteTokens.mint(ctx.service, streamPath, consumerId);
8049
8060
  }
8050
- function encodeCallbackForwardBody(service, consumerId, body, routingAdapter) {
8051
- const payload = encodeCallbackForwardPayload(consumerId, body, (stream) => routingAdapter.toBackendStreamPath(service, stream));
8061
+ function encodeWakeCallbackBody(service, consumerId, body, routingAdapter) {
8062
+ const payload = encodeWakeCallbackPayload(consumerId, body, (stream) => routingAdapter.toBackendStreamPath(service, stream));
8052
8063
  return new TextEncoder().encode(JSON.stringify(payload));
8053
8064
  }
8054
- function encodeCallbackForwardPayload(consumerId, body, mapStream) {
8065
+ function encodeWakeCallbackPayload(consumerId, body, mapStream) {
8055
8066
  if (!body) return {};
8056
8067
  const generation = body.generation ?? body.epoch;
8057
8068
  const wakeId = body.wake_id ?? body.wakeId ?? consumerId;
package/dist/index.d.cts CHANGED
@@ -4158,7 +4158,7 @@ declare class EntityManager {
4158
4158
  private syncManifestFutureSendSchedule;
4159
4159
  private parseEntityUrl;
4160
4160
  /**
4161
- * Deliver a message to an entity's main stream, with optional input schema
4161
+ * Deliver a message to an entity's main stream, with optional inbox schema
4162
4162
  * validation.
4163
4163
  */
4164
4164
  send(entityUrl: string, req: SendRequest, opts?: {
@@ -4172,8 +4172,8 @@ declare class EntityManager {
4172
4172
  }): Promise<void>;
4173
4173
  deleteInboxMessage(entityUrl: string, key: string): Promise<void>;
4174
4174
  setTag(entityUrl: string, key: string, req: SetTagRequest, token: string): Promise<ElectricAgentsEntity>;
4175
- removeTag(entityUrl: string, key: string, token: string): Promise<ElectricAgentsEntity>;
4176
- registerEntitiesSource(tags: Record<string, string>): Promise<{
4175
+ deleteTag(entityUrl: string, key: string, token: string): Promise<ElectricAgentsEntity>;
4176
+ ensureEntitiesMembershipStream(tags: Record<string, string>): Promise<{
4177
4177
  sourceRef: string;
4178
4178
  streamUrl: string;
4179
4179
  }>;
@@ -4263,7 +4263,7 @@ declare class EntityManager {
4263
4263
  status: number;
4264
4264
  } | null>;
4265
4265
  /**
4266
- * Add new input/output schema keys to an entity type directly in Postgres.
4266
+ * Add new inbox/state schema keys to an entity type directly in Postgres.
4267
4267
  */
4268
4268
  amendSchemas(typeName: string, schemas: {
4269
4269
  inbox_schemas?: Record<string, Record<string, unknown>>;
@@ -4271,7 +4271,7 @@ declare class EntityManager {
4271
4271
  }): Promise<ElectricAgentsEntityType>;
4272
4272
  /**
4273
4273
  * Enrich webhook payload with entity context.
4274
- * Called by ElectricAgentsServer during webhook forwarding to inject entity context.
4274
+ * Called by ElectricAgentsServer during subscription webhook dispatch to inject entity context.
4275
4275
  */
4276
4276
  enrichPayload(payload: Record<string, unknown>, consumer: {
4277
4277
  primary_stream: string;
package/dist/index.d.ts CHANGED
@@ -4159,7 +4159,7 @@ declare class EntityManager {
4159
4159
  private syncManifestFutureSendSchedule;
4160
4160
  private parseEntityUrl;
4161
4161
  /**
4162
- * Deliver a message to an entity's main stream, with optional input schema
4162
+ * Deliver a message to an entity's main stream, with optional inbox schema
4163
4163
  * validation.
4164
4164
  */
4165
4165
  send(entityUrl: string, req: SendRequest, opts?: {
@@ -4173,8 +4173,8 @@ declare class EntityManager {
4173
4173
  }): Promise<void>;
4174
4174
  deleteInboxMessage(entityUrl: string, key: string): Promise<void>;
4175
4175
  setTag(entityUrl: string, key: string, req: SetTagRequest, token: string): Promise<ElectricAgentsEntity>;
4176
- removeTag(entityUrl: string, key: string, token: string): Promise<ElectricAgentsEntity>;
4177
- registerEntitiesSource(tags: Record<string, string>): Promise<{
4176
+ deleteTag(entityUrl: string, key: string, token: string): Promise<ElectricAgentsEntity>;
4177
+ ensureEntitiesMembershipStream(tags: Record<string, string>): Promise<{
4178
4178
  sourceRef: string;
4179
4179
  streamUrl: string;
4180
4180
  }>;
@@ -4264,7 +4264,7 @@ declare class EntityManager {
4264
4264
  status: number;
4265
4265
  } | null>;
4266
4266
  /**
4267
- * Add new input/output schema keys to an entity type directly in Postgres.
4267
+ * Add new inbox/state schema keys to an entity type directly in Postgres.
4268
4268
  */
4269
4269
  amendSchemas(typeName: string, schemas: {
4270
4270
  inbox_schemas?: Record<string, Record<string, unknown>>;
@@ -4272,7 +4272,7 @@ declare class EntityManager {
4272
4272
  }): Promise<ElectricAgentsEntityType>;
4273
4273
  /**
4274
4274
  * Enrich webhook payload with entity context.
4275
- * Called by ElectricAgentsServer during webhook forwarding to inject entity context.
4275
+ * Called by ElectricAgentsServer during subscription webhook dispatch to inject entity context.
4276
4276
  */
4277
4277
  enrichPayload(payload: Record<string, unknown>, consumer: {
4278
4278
  primary_stream: string;