@electric-ax/agents-server 0.4.7 → 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/entrypoint.js +198 -83
- package/dist/index.cjs +195 -82
- package/dist/index.d.cts +26 -7
- package/dist/index.d.ts +26 -7
- package/dist/index.js +196 -83
- package/package.json +6 -6
- package/src/electric-agents/default-entity-schemas.ts +1 -1
- package/src/electric-agents-types.ts +2 -1
- package/src/entity-manager.ts +69 -5
- package/src/index.ts +9 -1
- package/src/manifest-side-effects.ts +11 -0
- package/src/routing/context.ts +18 -1
- package/src/routing/dispatch-policy.ts +1 -1
- package/src/routing/durable-streams-router.ts +1 -1
- package/src/routing/durable-streams-routing-adapter.ts +1 -3
- package/src/routing/entities-router.ts +133 -24
- package/src/routing/entity-types-router.ts +23 -26
- package/src/routing/hooks.ts +1 -1
- package/src/routing/internal-router.ts +83 -38
- package/src/routing/observations-router.ts +74 -0
- package/src/routing/runners-router.ts +1 -1
- package/src/server.ts +12 -0
- package/src/wake-registry.ts +1 -1
- package/src/routing/cron-router.ts +0 -45
package/dist/entrypoint.js
CHANGED
|
@@ -4,7 +4,7 @@ import { DurableStreamTestServer } from "@durable-streams/server";
|
|
|
4
4
|
import { createServer } from "node:http";
|
|
5
5
|
import { createServerAdapter } from "@whatwg-node/server";
|
|
6
6
|
import { Agent } from "undici";
|
|
7
|
-
import { appendPathToUrl, assertTags, buildTagsIndex, createEntityRegistry, createRuntimeHandler, entityStateSchema, getCronStreamPath, getCronStreamPathFromSpec, getEntitiesStreamPath, getNextCronFireAt, getSharedStateStreamPath, manifestChildKey, manifestSharedStateKey, manifestSourceKey, normalizeTags, parseCronStreamPath, resolveCronScheduleSpec, sourceRefForTags, verifyWebhookSignature } from "@electric-ax/agents-runtime";
|
|
7
|
+
import { appendPathToUrl, assertTags, buildEventSourceManifestEntry, buildTagsIndex, createEntityRegistry, createRuntimeHandler, entityStateSchema, eventSourceSubscriptionManifestKey, getCronStreamPath, getCronStreamPathFromSpec, getEntitiesStreamPath, getNextCronFireAt, getSharedStateStreamPath, getWebhookStreamPath, manifestChildKey, manifestSharedStateKey, manifestSourceKey, normalizeTags, parseCronStreamPath, resolveCronScheduleSpec, resolveEventSourceSubscription, sourceRefForTags, verifyWebhookSignature } from "@electric-ax/agents-runtime";
|
|
8
8
|
import fs, { existsSync } from "node:fs";
|
|
9
9
|
import path, { dirname, resolve } from "node:path";
|
|
10
10
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
@@ -446,7 +446,7 @@ const ErrCodeForkWaitTimeout = `FORK_WAIT_TIMEOUT`;
|
|
|
446
446
|
const ErrCodeEntityPersistFailed = `ENTITY_PERSIST_FAILED`;
|
|
447
447
|
const ErrCodeAgentUiNotFound = `AGENT_UI_NOT_FOUND`;
|
|
448
448
|
const ErrCodeSubscriptionNotFound = `SUBSCRIPTION_NOT_FOUND`;
|
|
449
|
-
const
|
|
449
|
+
const ErrCodeWakeCallbackNotFound = `WAKE_CALLBACK_NOT_FOUND`;
|
|
450
450
|
|
|
451
451
|
//#endregion
|
|
452
452
|
//#region src/utils/electric-url.ts
|
|
@@ -466,7 +466,7 @@ function electricUrlWithPath(electricUrl, path$1) {
|
|
|
466
466
|
//#region src/routing/durable-streams-routing-adapter.ts
|
|
467
467
|
function appendSearch(target, source) {
|
|
468
468
|
source.searchParams.forEach((value, key) => {
|
|
469
|
-
|
|
469
|
+
target.searchParams.append(key, value);
|
|
470
470
|
});
|
|
471
471
|
return target;
|
|
472
472
|
}
|
|
@@ -1610,7 +1610,7 @@ async function rewriteSubscriptionRequestBody(request, ctx, subscriptionId, rout
|
|
|
1610
1610
|
let targetWebhookUrl = null;
|
|
1611
1611
|
if (payload.webhook?.url !== void 0) {
|
|
1612
1612
|
targetWebhookUrl = rewriteLoopbackWebhookUrl(payload.webhook.url) ?? null;
|
|
1613
|
-
payload.webhook.url = appendPathToUrl(ctx.publicUrl, `/_electric/
|
|
1613
|
+
payload.webhook.url = appendPathToUrl(ctx.publicUrl, `/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`);
|
|
1614
1614
|
}
|
|
1615
1615
|
rewriteSubscriptionBodyForBackend(payload, ctx.service, routingAdapter);
|
|
1616
1616
|
return {
|
|
@@ -1733,20 +1733,6 @@ async function proxyPassThrough(request, ctx) {
|
|
|
1733
1733
|
}
|
|
1734
1734
|
}
|
|
1735
1735
|
|
|
1736
|
-
//#endregion
|
|
1737
|
-
//#region src/routing/cron-router.ts
|
|
1738
|
-
const cronRegisterBodySchema = Type.Object({
|
|
1739
|
-
expression: Type.String(),
|
|
1740
|
-
timezone: Type.Optional(Type.String())
|
|
1741
|
-
});
|
|
1742
|
-
const cronRouter = Router({ base: `/_electric/cron` });
|
|
1743
|
-
cronRouter.post(`/register`, withSchema(cronRegisterBodySchema), registerCron);
|
|
1744
|
-
async function registerCron(request, ctx) {
|
|
1745
|
-
const parsed = routeBody(request);
|
|
1746
|
-
const streamPath = await ctx.entityManager.getOrCreateCronStream(parsed.expression, parsed.timezone);
|
|
1747
|
-
return json({ streamUrl: streamPath });
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
1736
|
//#endregion
|
|
1751
1737
|
//#region src/routing/electric-proxy-router.ts
|
|
1752
1738
|
const electricProxyRouter = Router({ base: `/_electric/electric` });
|
|
@@ -2668,6 +2654,10 @@ function extractManifestSourceUrl(manifest) {
|
|
|
2668
2654
|
}
|
|
2669
2655
|
if (manifest.sourceType === `entities`) return typeof manifest.sourceRef === `string` ? `/_entities/${manifest.sourceRef}` : void 0;
|
|
2670
2656
|
if (manifest.sourceType === `db`) return typeof manifest.sourceRef === `string` ? getSharedStateStreamPath(manifest.sourceRef) : void 0;
|
|
2657
|
+
if (manifest.sourceType === `webhook`) {
|
|
2658
|
+
if (typeof config?.streamUrl === `string`) return config.streamUrl;
|
|
2659
|
+
if (typeof config?.endpointKey === `string`) return getWebhookStreamPath(config.endpointKey, typeof config.bucket === `string` ? config.bucket : void 0);
|
|
2660
|
+
}
|
|
2671
2661
|
return void 0;
|
|
2672
2662
|
}
|
|
2673
2663
|
if (manifest.kind === `shared-state`) return typeof manifest.id === `string` ? getSharedStateStreamPath(manifest.id) : void 0;
|
|
@@ -2954,7 +2944,8 @@ var EntityManager = class {
|
|
|
2954
2944
|
debounceMs: req.wake.debounceMs,
|
|
2955
2945
|
timeoutMs: req.wake.timeoutMs,
|
|
2956
2946
|
oneShot: false,
|
|
2957
|
-
includeResponse: req.wake.includeResponse
|
|
2947
|
+
includeResponse: req.wake.includeResponse,
|
|
2948
|
+
manifestKey: req.wake.manifestKey
|
|
2958
2949
|
});
|
|
2959
2950
|
const contentType = `application/json`;
|
|
2960
2951
|
const createdEvent = entityStateSchema.entityCreated.insert({
|
|
@@ -3627,7 +3618,7 @@ var EntityManager = class {
|
|
|
3627
3618
|
};
|
|
3628
3619
|
}
|
|
3629
3620
|
/**
|
|
3630
|
-
* Deliver a message to an entity's main stream, with optional
|
|
3621
|
+
* Deliver a message to an entity's main stream, with optional inbox schema
|
|
3631
3622
|
* validation.
|
|
3632
3623
|
*/
|
|
3633
3624
|
async send(entityUrl, req, opts) {
|
|
@@ -3715,7 +3706,7 @@ var EntityManager = class {
|
|
|
3715
3706
|
if (result.changed && this.entityBridgeManager) await this.entityBridgeManager.onEntityChanged(entityUrl);
|
|
3716
3707
|
return updated;
|
|
3717
3708
|
}
|
|
3718
|
-
async
|
|
3709
|
+
async deleteTag(entityUrl, key, token) {
|
|
3719
3710
|
const entity = await this.registry.getEntity(entityUrl);
|
|
3720
3711
|
if (!entity) throw new ElectricAgentsError(ErrCodeNotFound, `Entity not found`, 404);
|
|
3721
3712
|
if (!this.isValidWriteToken(entity, token)) throw new ElectricAgentsError(ErrCodeUnauthorized, `Invalid write token`, 401);
|
|
@@ -3726,7 +3717,7 @@ var EntityManager = class {
|
|
|
3726
3717
|
if (result.changed && this.entityBridgeManager) await this.entityBridgeManager.onEntityChanged(entityUrl);
|
|
3727
3718
|
return updated;
|
|
3728
3719
|
}
|
|
3729
|
-
async
|
|
3720
|
+
async ensureEntitiesMembershipStream(tags) {
|
|
3730
3721
|
if (!this.entityBridgeManager) throw new Error(`Entity bridge manager not configured`);
|
|
3731
3722
|
return this.entityBridgeManager.register(this.validateTags(tags));
|
|
3732
3723
|
}
|
|
@@ -3848,6 +3839,35 @@ var EntityManager = class {
|
|
|
3848
3839
|
await this.writeManifestEntry(entityUrl, manifestKey, `delete`, void 0, { txid });
|
|
3849
3840
|
return { txid };
|
|
3850
3841
|
}
|
|
3842
|
+
async upsertEventSourceSubscription(entityUrl, req) {
|
|
3843
|
+
const manifestKey = req.subscription.manifestKey;
|
|
3844
|
+
const txid = randomUUID();
|
|
3845
|
+
await this.writeManifestEntry(entityUrl, manifestKey, `upsert`, req.manifest, { txid });
|
|
3846
|
+
await this.wakeRegistry.unregisterByManifestKey(entityUrl, manifestKey, this.tenantId);
|
|
3847
|
+
await this.wakeRegistry.register({
|
|
3848
|
+
tenantId: this.tenantId,
|
|
3849
|
+
subscriberUrl: entityUrl,
|
|
3850
|
+
sourceUrl: req.subscription.sourceUrl,
|
|
3851
|
+
condition: {
|
|
3852
|
+
on: `change`,
|
|
3853
|
+
collections: [`webhook_event`],
|
|
3854
|
+
ops: [`insert`]
|
|
3855
|
+
},
|
|
3856
|
+
oneShot: false,
|
|
3857
|
+
manifestKey
|
|
3858
|
+
});
|
|
3859
|
+
return {
|
|
3860
|
+
txid,
|
|
3861
|
+
subscription: req.subscription
|
|
3862
|
+
};
|
|
3863
|
+
}
|
|
3864
|
+
async deleteEventSourceSubscription(entityUrl, req) {
|
|
3865
|
+
const manifestKey = eventSourceSubscriptionManifestKey(req.id);
|
|
3866
|
+
const txid = randomUUID();
|
|
3867
|
+
await this.writeManifestEntry(entityUrl, manifestKey, `delete`, void 0, { txid });
|
|
3868
|
+
await this.wakeRegistry.unregisterByManifestKey(entityUrl, manifestKey, this.tenantId);
|
|
3869
|
+
return { txid };
|
|
3870
|
+
}
|
|
3851
3871
|
/**
|
|
3852
3872
|
* Register a wake subscription from a subscriber to a source entity.
|
|
3853
3873
|
*/
|
|
@@ -4126,7 +4146,7 @@ var EntityManager = class {
|
|
|
4126
4146
|
return null;
|
|
4127
4147
|
}
|
|
4128
4148
|
/**
|
|
4129
|
-
* Add new
|
|
4149
|
+
* Add new inbox/state schema keys to an entity type directly in Postgres.
|
|
4130
4150
|
*/
|
|
4131
4151
|
async amendSchemas(typeName, schemas) {
|
|
4132
4152
|
if (typeName === `principal`) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Entity type "principal" is built in and cannot be amended`, 400);
|
|
@@ -4166,7 +4186,7 @@ var EntityManager = class {
|
|
|
4166
4186
|
}
|
|
4167
4187
|
/**
|
|
4168
4188
|
* Enrich webhook payload with entity context.
|
|
4169
|
-
* Called by ElectricAgentsServer during webhook
|
|
4189
|
+
* Called by ElectricAgentsServer during subscription webhook dispatch to inject entity context.
|
|
4170
4190
|
*/
|
|
4171
4191
|
async enrichPayload(payload, consumer) {
|
|
4172
4192
|
const entity = await this.registry.getEntityByStream(consumer.primary_stream);
|
|
@@ -4431,7 +4451,7 @@ async function linkStreamToTargetSubscription(ctx, target, entity, subscriptionI
|
|
|
4431
4451
|
}
|
|
4432
4452
|
const webhookUrl = rewriteLoopbackWebhookUrl(target.url);
|
|
4433
4453
|
if (!webhookUrl) throw new ElectricAgentsError(ErrCodeInvalidRequest, `Webhook dispatch target must include a valid URL`, 400);
|
|
4434
|
-
const forwardUrl = appendPathToUrl(ctx.publicUrl, `/_electric/
|
|
4454
|
+
const forwardUrl = appendPathToUrl(ctx.publicUrl, `/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`);
|
|
4435
4455
|
await ensureSubscriptionIncludesStream(ctx, subscriptionId, streamPath, {
|
|
4436
4456
|
type: `webhook`,
|
|
4437
4457
|
streams: [streamPath],
|
|
@@ -4450,7 +4470,7 @@ async function linkStreamToTargetSubscription(ctx, target, entity, subscriptionI
|
|
|
4450
4470
|
|
|
4451
4471
|
//#endregion
|
|
4452
4472
|
//#region src/routing/entities-router.ts
|
|
4453
|
-
const stringRecordSchema = Type.Record(Type.String(), Type.String());
|
|
4473
|
+
const stringRecordSchema$1 = Type.Record(Type.String(), Type.String());
|
|
4454
4474
|
function writeTokenFromRequest(request) {
|
|
4455
4475
|
const electricClaimToken = request.headers.get(`electric-claim-token`)?.trim();
|
|
4456
4476
|
if (electricClaimToken) return electricClaimToken;
|
|
@@ -4467,7 +4487,7 @@ const wakeConditionSchema = Type.Union([Type.Literal(`runFinished`), Type.Object
|
|
|
4467
4487
|
})]);
|
|
4468
4488
|
const spawnBodySchema = Type.Object({
|
|
4469
4489
|
args: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
4470
|
-
tags: Type.Optional(stringRecordSchema),
|
|
4490
|
+
tags: Type.Optional(stringRecordSchema$1),
|
|
4471
4491
|
parent: Type.Optional(Type.String()),
|
|
4472
4492
|
dispatch_policy: Type.Optional(dispatchPolicySchema),
|
|
4473
4493
|
initialMessage: Type.Optional(Type.Unknown()),
|
|
@@ -4476,7 +4496,8 @@ const spawnBodySchema = Type.Object({
|
|
|
4476
4496
|
condition: wakeConditionSchema,
|
|
4477
4497
|
debounceMs: Type.Optional(Type.Number()),
|
|
4478
4498
|
timeoutMs: Type.Optional(Type.Number()),
|
|
4479
|
-
includeResponse: Type.Optional(Type.Boolean())
|
|
4499
|
+
includeResponse: Type.Optional(Type.Boolean()),
|
|
4500
|
+
manifestKey: Type.Optional(Type.String())
|
|
4480
4501
|
}))
|
|
4481
4502
|
});
|
|
4482
4503
|
const sendBodySchema = Type.Object({
|
|
@@ -4542,10 +4563,24 @@ const scheduleBodySchema = Type.Union([Type.Object({
|
|
|
4542
4563
|
messageType: Type.Optional(Type.String()),
|
|
4543
4564
|
from: Type.Optional(Type.String())
|
|
4544
4565
|
})]);
|
|
4545
|
-
const
|
|
4566
|
+
const subscriptionLifetimeSchema = Type.Union([
|
|
4567
|
+
Type.Object({ kind: Type.Literal(`until_entity_stopped`) }),
|
|
4568
|
+
Type.Object({
|
|
4569
|
+
kind: Type.Literal(`expires_at`),
|
|
4570
|
+
at: Type.String()
|
|
4571
|
+
}),
|
|
4572
|
+
Type.Object({ kind: Type.Literal(`manual`) })
|
|
4573
|
+
]);
|
|
4574
|
+
const eventSourceSubscriptionBodySchema = Type.Object({
|
|
4575
|
+
sourceKey: Type.String(),
|
|
4576
|
+
bucketKey: Type.Optional(Type.String()),
|
|
4577
|
+
params: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
4578
|
+
filterKey: Type.Optional(Type.String()),
|
|
4579
|
+
lifetime: Type.Optional(subscriptionLifetimeSchema),
|
|
4580
|
+
reason: Type.Optional(Type.String())
|
|
4581
|
+
});
|
|
4546
4582
|
const entitiesRouter = Router({ base: `/_electric/entities` });
|
|
4547
4583
|
entitiesRouter.get(`/`, listEntities);
|
|
4548
|
-
entitiesRouter.post(`/register`, withSchema(entitiesRegisterBodySchema), registerEntitiesSource);
|
|
4549
4584
|
entitiesRouter.put(`/:type/:instanceId`, withSpawnableEntityType, withSchema(spawnBodySchema), spawnEntity);
|
|
4550
4585
|
entitiesRouter.get(`/:type/:instanceId`, withExistingEntity, getEntity);
|
|
4551
4586
|
entitiesRouter.head(`/:type/:instanceId`, withExistingEntity, headEntity);
|
|
@@ -4556,9 +4591,11 @@ entitiesRouter.patch(`/:type/:instanceId/inbox/:messageKey`, withExistingEntity,
|
|
|
4556
4591
|
entitiesRouter.delete(`/:type/:instanceId/inbox/:messageKey`, withExistingEntity, deleteInboxMessage);
|
|
4557
4592
|
entitiesRouter.post(`/:type/:instanceId/fork`, withExistingEntity, withSchema(forkBodySchema), forkEntity);
|
|
4558
4593
|
entitiesRouter.post(`/:type/:instanceId/tags/:tagKey`, withExistingEntity, withSchema(setTagBodySchema), setTag);
|
|
4559
|
-
entitiesRouter.delete(`/:type/:instanceId/tags/:tagKey`, withExistingEntity,
|
|
4594
|
+
entitiesRouter.delete(`/:type/:instanceId/tags/:tagKey`, withExistingEntity, deleteTag);
|
|
4560
4595
|
entitiesRouter.put(`/:type/:instanceId/schedules/:scheduleId`, withExistingEntity, withSchema(scheduleBodySchema), upsertSchedule);
|
|
4561
4596
|
entitiesRouter.delete(`/:type/:instanceId/schedules/:scheduleId`, withExistingEntity, deleteSchedule);
|
|
4597
|
+
entitiesRouter.put(`/:type/:instanceId/event-source-subscriptions/:subscriptionId`, withExistingEntity, withSchema(eventSourceSubscriptionBodySchema), upsertEventSourceSubscription);
|
|
4598
|
+
entitiesRouter.delete(`/:type/:instanceId/event-source-subscriptions/:subscriptionId`, withExistingEntity, deleteEventSourceSubscription);
|
|
4562
4599
|
function entityUrlFromSegments(type, instanceId) {
|
|
4563
4600
|
if (!type || !instanceId) return null;
|
|
4564
4601
|
if (type.startsWith(`_`) || type.includes(`*`) || instanceId.includes(`*`)) return null;
|
|
@@ -4622,11 +4659,6 @@ async function listEntities({ query }, ctx) {
|
|
|
4622
4659
|
});
|
|
4623
4660
|
return json(entities$1.map((entity) => toPublicEntity(entity)));
|
|
4624
4661
|
}
|
|
4625
|
-
async function registerEntitiesSource(request, ctx) {
|
|
4626
|
-
const parsed = routeBody(request);
|
|
4627
|
-
const result = await ctx.entityManager.registerEntitiesSource(parsed.tags ?? {});
|
|
4628
|
-
return json(result);
|
|
4629
|
-
}
|
|
4630
4662
|
async function upsertSchedule(request, ctx) {
|
|
4631
4663
|
const principalMutationError = rejectPrincipalEntityMutation(request, `scheduled`);
|
|
4632
4664
|
if (principalMutationError) return principalMutationError;
|
|
@@ -4665,8 +4697,49 @@ async function deleteSchedule(request, ctx) {
|
|
|
4665
4697
|
const result = await ctx.entityManager.deleteSchedule(entityUrl, { id: decodeURIComponent(request.params.scheduleId) });
|
|
4666
4698
|
return json(result);
|
|
4667
4699
|
}
|
|
4700
|
+
async function upsertEventSourceSubscription(request, ctx) {
|
|
4701
|
+
const principalMutationError = rejectPrincipalEntityMutation(request, `subscribed to event sources`);
|
|
4702
|
+
if (principalMutationError) return principalMutationError;
|
|
4703
|
+
const catalog = ctx.eventSources;
|
|
4704
|
+
if (!catalog) return apiError(404, ErrCodeNotFound, `No event source catalog is configured`);
|
|
4705
|
+
const { entityUrl } = requireExistingEntityRoute(request);
|
|
4706
|
+
const parsed = routeBody(request);
|
|
4707
|
+
const source = await catalog.getEventSource(parsed.sourceKey);
|
|
4708
|
+
if (!source) return apiError(404, ErrCodeNotFound, `Event source "${parsed.sourceKey}" not found`);
|
|
4709
|
+
if (parsed.lifetime?.kind === `expires_at`) {
|
|
4710
|
+
const expiresAt = new Date(parsed.lifetime.at);
|
|
4711
|
+
if (Number.isNaN(expiresAt.getTime())) return apiError(400, ErrCodeInvalidRequest, `Invalid expires_at lifetime timestamp`);
|
|
4712
|
+
}
|
|
4713
|
+
let resolved;
|
|
4714
|
+
try {
|
|
4715
|
+
resolved = resolveEventSourceSubscription({
|
|
4716
|
+
contract: source,
|
|
4717
|
+
entityUrl,
|
|
4718
|
+
request: {
|
|
4719
|
+
...parsed,
|
|
4720
|
+
id: decodeURIComponent(request.params.subscriptionId)
|
|
4721
|
+
},
|
|
4722
|
+
createdBy: `tool`
|
|
4723
|
+
});
|
|
4724
|
+
} catch (error) {
|
|
4725
|
+
return apiError(400, ErrCodeInvalidRequest, error instanceof Error ? error.message : String(error));
|
|
4726
|
+
}
|
|
4727
|
+
await ctx.ensureEventSourceWakeSource?.(resolved.subscription.sourceUrl);
|
|
4728
|
+
const result = await ctx.entityManager.upsertEventSourceSubscription(entityUrl, {
|
|
4729
|
+
subscription: resolved.subscription,
|
|
4730
|
+
manifest: buildEventSourceManifestEntry(resolved)
|
|
4731
|
+
});
|
|
4732
|
+
return json(result);
|
|
4733
|
+
}
|
|
4734
|
+
async function deleteEventSourceSubscription(request, ctx) {
|
|
4735
|
+
const principalMutationError = rejectPrincipalEntityMutation(request, `unsubscribed from event sources`);
|
|
4736
|
+
if (principalMutationError) return principalMutationError;
|
|
4737
|
+
const { entityUrl } = requireExistingEntityRoute(request);
|
|
4738
|
+
const result = await ctx.entityManager.deleteEventSourceSubscription(entityUrl, { id: decodeURIComponent(request.params.subscriptionId) });
|
|
4739
|
+
return json(result);
|
|
4740
|
+
}
|
|
4668
4741
|
async function setTag(request, ctx) {
|
|
4669
|
-
const principalMutationError = rejectPrincipalEntityMutation(request, `
|
|
4742
|
+
const principalMutationError = rejectPrincipalEntityMutation(request, `tag updated`);
|
|
4670
4743
|
if (principalMutationError) return principalMutationError;
|
|
4671
4744
|
const parsed = routeBody(request);
|
|
4672
4745
|
const { entityUrl } = requireExistingEntityRoute(request);
|
|
@@ -4674,12 +4747,12 @@ async function setTag(request, ctx) {
|
|
|
4674
4747
|
const updated = await ctx.entityManager.setTag(entityUrl, decodeURIComponent(request.params.tagKey), { value: parsed.value }, token);
|
|
4675
4748
|
return json(toPublicEntity(updated));
|
|
4676
4749
|
}
|
|
4677
|
-
async function
|
|
4678
|
-
const principalMutationError = rejectPrincipalEntityMutation(request, `
|
|
4750
|
+
async function deleteTag(request, ctx) {
|
|
4751
|
+
const principalMutationError = rejectPrincipalEntityMutation(request, `tag deleted`);
|
|
4679
4752
|
if (principalMutationError) return principalMutationError;
|
|
4680
4753
|
const { entityUrl } = requireExistingEntityRoute(request);
|
|
4681
4754
|
const token = writeTokenFromRequest(request);
|
|
4682
|
-
const updated = await ctx.entityManager.
|
|
4755
|
+
const updated = await ctx.entityManager.deleteTag(entityUrl, decodeURIComponent(request.params.tagKey), token);
|
|
4683
4756
|
return json(toPublicEntity(updated));
|
|
4684
4757
|
}
|
|
4685
4758
|
async function forkEntity(request, ctx) {
|
|
@@ -4811,17 +4884,13 @@ const registerEntityTypeBodySchema = Type.Object({
|
|
|
4811
4884
|
creation_schema: Type.Optional(jsonObjectSchema),
|
|
4812
4885
|
inbox_schemas: Type.Optional(schemaMapSchema),
|
|
4813
4886
|
state_schemas: Type.Optional(schemaMapSchema),
|
|
4814
|
-
input_schemas: Type.Optional(schemaMapSchema),
|
|
4815
|
-
output_schemas: Type.Optional(schemaMapSchema),
|
|
4816
4887
|
serve_endpoint: Type.Optional(Type.String()),
|
|
4817
4888
|
default_dispatch_policy: Type.Optional(dispatchPolicySchema)
|
|
4818
|
-
});
|
|
4889
|
+
}, { additionalProperties: false });
|
|
4819
4890
|
const amendEntityTypeSchemasBodySchema = Type.Object({
|
|
4820
|
-
input_schemas: Type.Optional(schemaMapSchema),
|
|
4821
|
-
output_schemas: Type.Optional(schemaMapSchema),
|
|
4822
4891
|
inbox_schemas: Type.Optional(schemaMapSchema),
|
|
4823
4892
|
state_schemas: Type.Optional(schemaMapSchema)
|
|
4824
|
-
});
|
|
4893
|
+
}, { additionalProperties: false });
|
|
4825
4894
|
const entityTypesRouter = Router({ base: `/_electric/entity-types` });
|
|
4826
4895
|
entityTypesRouter.get(`/`, listEntityTypes);
|
|
4827
4896
|
entityTypesRouter.post(`/`, withSchema(registerEntityTypeBodySchema), registerEntityType);
|
|
@@ -4861,8 +4930,8 @@ async function getEntityType(request, ctx) {
|
|
|
4861
4930
|
async function amendSchemas(request, ctx) {
|
|
4862
4931
|
const parsed = routeBody(request);
|
|
4863
4932
|
const updated = await ctx.entityManager.amendSchemas(request.params.name, {
|
|
4864
|
-
inbox_schemas: parsed.inbox_schemas
|
|
4865
|
-
state_schemas: parsed.state_schemas
|
|
4933
|
+
inbox_schemas: parsed.inbox_schemas,
|
|
4934
|
+
state_schemas: parsed.state_schemas
|
|
4866
4935
|
});
|
|
4867
4936
|
return json(toPublicEntityType(updated));
|
|
4868
4937
|
}
|
|
@@ -4872,13 +4941,12 @@ async function deleteEntityType(request, ctx) {
|
|
|
4872
4941
|
}
|
|
4873
4942
|
function normalizeEntityTypeRequest(parsed) {
|
|
4874
4943
|
const serveEndpoint = rewriteLoopbackWebhookUrl(parsed.serve_endpoint);
|
|
4875
|
-
const compatibilityFields = parsed;
|
|
4876
4944
|
return {
|
|
4877
4945
|
name: parsed.name ?? ``,
|
|
4878
4946
|
description: parsed.description ?? ``,
|
|
4879
4947
|
creation_schema: parsed.creation_schema,
|
|
4880
|
-
inbox_schemas: parsed.inbox_schemas
|
|
4881
|
-
state_schemas: parsed.state_schemas
|
|
4948
|
+
inbox_schemas: parsed.inbox_schemas,
|
|
4949
|
+
state_schemas: parsed.state_schemas,
|
|
4882
4950
|
serve_endpoint: serveEndpoint,
|
|
4883
4951
|
default_dispatch_policy: parsed.default_dispatch_policy ?? (serveEndpoint ? { targets: [{
|
|
4884
4952
|
type: `webhook`,
|
|
@@ -4889,8 +4957,6 @@ function normalizeEntityTypeRequest(parsed) {
|
|
|
4889
4957
|
function toPublicEntityType(entityType) {
|
|
4890
4958
|
return {
|
|
4891
4959
|
...entityType,
|
|
4892
|
-
input_schemas: entityType.inbox_schemas,
|
|
4893
|
-
output_schemas: entityType.state_schemas,
|
|
4894
4960
|
revision: entityType.revision
|
|
4895
4961
|
};
|
|
4896
4962
|
}
|
|
@@ -4974,13 +5040,35 @@ function errorMapper(err, req) {
|
|
|
4974
5040
|
function rejectIfShuttingDown(req, ctx) {
|
|
4975
5041
|
if (!ctx.isShuttingDown()) return void 0;
|
|
4976
5042
|
const path$1 = new URL(req.url).pathname;
|
|
4977
|
-
if (!path$1.startsWith(`/_electric/
|
|
5043
|
+
if (!path$1.startsWith(`/_electric/subscription-webhooks/`)) return void 0;
|
|
4978
5044
|
return apiError(503, `SERVER_STOPPING`, `Server is shutting down`);
|
|
4979
5045
|
}
|
|
4980
5046
|
function getRequestSpan(req) {
|
|
4981
5047
|
return carrier(req)[SPAN_KEY];
|
|
4982
5048
|
}
|
|
4983
5049
|
|
|
5050
|
+
//#endregion
|
|
5051
|
+
//#region src/routing/observations-router.ts
|
|
5052
|
+
const stringRecordSchema = Type.Record(Type.String(), Type.String());
|
|
5053
|
+
const ensureEntitiesMembershipStreamBodySchema = Type.Object({ tags: Type.Optional(stringRecordSchema) });
|
|
5054
|
+
const ensureCronStreamBodySchema = Type.Object({
|
|
5055
|
+
expression: Type.String(),
|
|
5056
|
+
timezone: Type.Optional(Type.String())
|
|
5057
|
+
});
|
|
5058
|
+
const observationsRouter = Router({ base: `/_electric/observations` });
|
|
5059
|
+
observationsRouter.post(`/entities/ensure-stream`, withSchema(ensureEntitiesMembershipStreamBodySchema), ensureEntitiesMembershipStream);
|
|
5060
|
+
observationsRouter.post(`/cron/ensure-stream`, withSchema(ensureCronStreamBodySchema), ensureCronStream);
|
|
5061
|
+
async function ensureEntitiesMembershipStream(request, ctx) {
|
|
5062
|
+
const parsed = routeBody(request);
|
|
5063
|
+
const result = await ctx.entityManager.ensureEntitiesMembershipStream(parsed.tags ?? {});
|
|
5064
|
+
return json(result);
|
|
5065
|
+
}
|
|
5066
|
+
async function ensureCronStream(request, ctx) {
|
|
5067
|
+
const parsed = routeBody(request);
|
|
5068
|
+
const streamPath = await ctx.entityManager.getOrCreateCronStream(parsed.expression, parsed.timezone);
|
|
5069
|
+
return json({ streamUrl: streamPath });
|
|
5070
|
+
}
|
|
5071
|
+
|
|
4984
5072
|
//#endregion
|
|
4985
5073
|
//#region src/routing/tenant-stream-paths.ts
|
|
4986
5074
|
function withLeadingSlash(path$1) {
|
|
@@ -5319,7 +5407,7 @@ async function notificationFromClaim(ctx, input) {
|
|
|
5319
5407
|
wakeId: input.claim.wake_id,
|
|
5320
5408
|
streamPath: primaryStream,
|
|
5321
5409
|
streams: streams$1,
|
|
5322
|
-
callback: appendPathToUrl(ctx.publicUrl, `/_electric/
|
|
5410
|
+
callback: appendPathToUrl(ctx.publicUrl, `/_electric/wake-callbacks/${encodeURIComponent(input.claim.wake_id)}`),
|
|
5323
5411
|
claimToken: input.claim.token,
|
|
5324
5412
|
triggerEvent: `message_received`,
|
|
5325
5413
|
entity: {
|
|
@@ -5354,7 +5442,7 @@ const wakeRegistrationBodySchema = Type.Object({
|
|
|
5354
5442
|
includeResponse: Type.Optional(Type.Boolean()),
|
|
5355
5443
|
manifestKey: Type.Optional(Type.String())
|
|
5356
5444
|
});
|
|
5357
|
-
const
|
|
5445
|
+
const subscriptionWebhookBodySchema = Type.Object({
|
|
5358
5446
|
subscription_id: Type.Optional(Type.String()),
|
|
5359
5447
|
wake_id: Type.Optional(Type.String()),
|
|
5360
5448
|
generation: Type.Optional(Type.Number()),
|
|
@@ -5368,7 +5456,7 @@ const webhookForwardBodySchema = Type.Object({
|
|
|
5368
5456
|
consumer_id: Type.Optional(Type.String()),
|
|
5369
5457
|
callback: Type.Optional(Type.String())
|
|
5370
5458
|
}, { additionalProperties: true });
|
|
5371
|
-
const
|
|
5459
|
+
const wakeCallbackBodySchema = Type.Object({
|
|
5372
5460
|
epoch: Type.Optional(Type.Number()),
|
|
5373
5461
|
generation: Type.Optional(Type.Number()),
|
|
5374
5462
|
wakeId: Type.Optional(Type.String()),
|
|
@@ -5379,14 +5467,15 @@ const callbackForwardBodySchema = Type.Object({
|
|
|
5379
5467
|
const DS_SUBSCRIPTION_CALLBACK_PREFIX = `ds-subscription:`;
|
|
5380
5468
|
const internalRouter = Router({ base: `/_electric` });
|
|
5381
5469
|
internalRouter.get(`/health`, () => json({ status: `ok` }));
|
|
5470
|
+
internalRouter.get(`/event-sources`, listEventSources);
|
|
5382
5471
|
internalRouter.post(`/wake`, withSchema(wakeRegistrationBodySchema), registerWake);
|
|
5383
|
-
internalRouter.post(`/
|
|
5384
|
-
internalRouter.post(`/
|
|
5472
|
+
internalRouter.post(`/subscription-webhooks/:subscriptionId`, subscriptionWebhook);
|
|
5473
|
+
internalRouter.post(`/wake-callbacks/:consumerId`, wakeCallback);
|
|
5385
5474
|
internalRouter.all(`/runners`, runnersRouter.fetch);
|
|
5386
5475
|
internalRouter.all(`/runners/*`, runnersRouter.fetch);
|
|
5387
5476
|
internalRouter.all(`/entities/*`, entitiesRouter.fetch);
|
|
5388
5477
|
internalRouter.all(`/entity-types/*`, entityTypesRouter.fetch);
|
|
5389
|
-
internalRouter.all(`/
|
|
5478
|
+
internalRouter.all(`/observations/*`, observationsRouter.fetch);
|
|
5390
5479
|
internalRouter.get(`/electric/*`, electricProxyRouter.fetch);
|
|
5391
5480
|
internalRouter.all(`*`, () => status(404));
|
|
5392
5481
|
function routeParam(request, name) {
|
|
@@ -5419,13 +5508,30 @@ function resolveWebhookSigner(ctx) {
|
|
|
5419
5508
|
return ctx.webhookSigner ?? getDefaultWebhookSigner();
|
|
5420
5509
|
}
|
|
5421
5510
|
function durableStreamsWebhookJwksUrl(ctx) {
|
|
5422
|
-
if (!ctx.durableStreamsRouting) return
|
|
5511
|
+
if (!ctx.durableStreamsRouting) return appendPathToBackendUrl(ctx.durableStreamsUrl, `/__ds/jwks.json`);
|
|
5423
5512
|
return resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl).controlUrl({
|
|
5424
5513
|
durableStreamsUrl: ctx.durableStreamsUrl,
|
|
5425
5514
|
serviceId: ctx.service,
|
|
5426
5515
|
requestUrl: appendPathToUrl(ctx.publicUrl, `/__ds/jwks.json`)
|
|
5427
5516
|
}).toString();
|
|
5428
5517
|
}
|
|
5518
|
+
function appendPathToBackendUrl(baseUrl, path$1) {
|
|
5519
|
+
const base = new URL(baseUrl);
|
|
5520
|
+
const pathUrl = new URL(path$1, `http://electric-agents.local`);
|
|
5521
|
+
const basePath = base.pathname === `/` ? `` : base.pathname.replace(/\/+$/, ``);
|
|
5522
|
+
const suffix = pathUrl.pathname.startsWith(`/`) ? pathUrl.pathname : `/${pathUrl.pathname}`;
|
|
5523
|
+
const target = new URL(base);
|
|
5524
|
+
target.pathname = `${basePath}${suffix}`;
|
|
5525
|
+
target.search = ``;
|
|
5526
|
+
target.hash = pathUrl.hash;
|
|
5527
|
+
base.searchParams.forEach((value, key) => {
|
|
5528
|
+
target.searchParams.append(key, value);
|
|
5529
|
+
});
|
|
5530
|
+
pathUrl.searchParams.forEach((value, key) => {
|
|
5531
|
+
target.searchParams.append(key, value);
|
|
5532
|
+
});
|
|
5533
|
+
return target.toString();
|
|
5534
|
+
}
|
|
5429
5535
|
function durableStreamsJwksFetchClient(ctx) {
|
|
5430
5536
|
return async (input, init) => {
|
|
5431
5537
|
const headers = new Headers(init?.headers);
|
|
@@ -5482,10 +5588,17 @@ async function registerWake(request, ctx) {
|
|
|
5482
5588
|
await ctx.entityManager.registerWake(opts);
|
|
5483
5589
|
return status(204);
|
|
5484
5590
|
}
|
|
5485
|
-
async function
|
|
5591
|
+
async function listEventSources(_request, ctx) {
|
|
5592
|
+
const eventSources = ctx.eventSources ? await ctx.eventSources.listEventSources() : [];
|
|
5593
|
+
return json({ eventSources: eventSources.filter(isAgentVisibleEventSource) });
|
|
5594
|
+
}
|
|
5595
|
+
function isAgentVisibleEventSource(source) {
|
|
5596
|
+
return source.agentVisible === true && source.status === `active`;
|
|
5597
|
+
}
|
|
5598
|
+
async function subscriptionWebhook(request, ctx) {
|
|
5486
5599
|
const subscriptionId = routeParam(request, `subscriptionId`);
|
|
5487
5600
|
const rootSpan = getRequestSpan(request);
|
|
5488
|
-
rootSpan?.updateName(`webhook
|
|
5601
|
+
rootSpan?.updateName(`subscription-webhook`);
|
|
5489
5602
|
rootSpan?.setAttribute(`electric_agents.webhook.subscription_id`, subscriptionId);
|
|
5490
5603
|
const body = await readRequestBody(request);
|
|
5491
5604
|
const signatureError = await verifyDurableStreamsWebhook(request, ctx, body);
|
|
@@ -5499,7 +5612,7 @@ async function webhookForward(request, ctx) {
|
|
|
5499
5612
|
}
|
|
5500
5613
|
});
|
|
5501
5614
|
if (!targetWebhookUrl) return apiError(404, ErrCodeSubscriptionNotFound, `Unknown webhook subscription`);
|
|
5502
|
-
const parsedBodyResult = validateOptionalJsonBody(
|
|
5615
|
+
const parsedBodyResult = validateOptionalJsonBody(subscriptionWebhookBodySchema, body, request.headers.get(`content-type`));
|
|
5503
5616
|
if (!parsedBodyResult.ok) return parsedBodyResult.response;
|
|
5504
5617
|
let forwardBody = body;
|
|
5505
5618
|
let runningEntityUrl = null;
|
|
@@ -5545,7 +5658,7 @@ async function webhookForward(request, ctx) {
|
|
|
5545
5658
|
span.end();
|
|
5546
5659
|
}
|
|
5547
5660
|
}).catch((err) => {
|
|
5548
|
-
serverLog.warn(`[webhook
|
|
5661
|
+
serverLog.warn(`[subscription-webhook] consumerCallbacks upsert failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
5549
5662
|
}) : void 0;
|
|
5550
5663
|
const [entity, enriched] = await Promise.all([entityPromise, enrichPromise]);
|
|
5551
5664
|
if (entity?.status === `stopped` || entity?.status === `paused`) {
|
|
@@ -5566,7 +5679,7 @@ async function webhookForward(request, ctx) {
|
|
|
5566
5679
|
runningEntityUrl = entity.url;
|
|
5567
5680
|
}
|
|
5568
5681
|
if (consumerId && callbackUrl) {
|
|
5569
|
-
const callback = appendPathToUrl(ctx.publicUrl, `/_electric/
|
|
5682
|
+
const callback = appendPathToUrl(ctx.publicUrl, `/_electric/wake-callbacks/${encodeURIComponent(consumerId)}`);
|
|
5570
5683
|
enriched.callback = callback;
|
|
5571
5684
|
if (newWebhook) {
|
|
5572
5685
|
enriched.consumerId = newWebhook.wakeId;
|
|
@@ -5603,21 +5716,21 @@ async function webhookForward(request, ctx) {
|
|
|
5603
5716
|
});
|
|
5604
5717
|
} catch (err) {
|
|
5605
5718
|
if (runningEntityUrl) await ctx.entityManager.registry.updateStatus(runningEntityUrl, `idle`);
|
|
5606
|
-
return apiError(502, `
|
|
5719
|
+
return apiError(502, `SUBSCRIPTION_WEBHOOK_FAILED`, err instanceof Error ? err.message : String(err));
|
|
5607
5720
|
}
|
|
5608
5721
|
const responseBytes = upstream.body ? new Uint8Array(await upstream.arrayBuffer()) : new Uint8Array();
|
|
5609
5722
|
return responseFromUpstream(upstream, responseBytes);
|
|
5610
5723
|
}
|
|
5611
|
-
async function
|
|
5724
|
+
async function wakeCallback(request, ctx) {
|
|
5612
5725
|
const consumerId = routeParam(request, `consumerId`);
|
|
5613
5726
|
const rows = await ctx.pgDb.select().from(consumerCallbacks).where(and(eq(consumerCallbacks.tenantId, ctx.service), eq(consumerCallbacks.consumerId, consumerId))).limit(1);
|
|
5614
5727
|
const target = rows[0] ? {
|
|
5615
5728
|
callbackUrl: rows[0].callbackUrl,
|
|
5616
5729
|
primaryStream: rows[0].primaryStream
|
|
5617
5730
|
} : void 0;
|
|
5618
|
-
if (!target) return apiError(404,
|
|
5731
|
+
if (!target) return apiError(404, ErrCodeWakeCallbackNotFound, `Unknown wake-callback consumer`);
|
|
5619
5732
|
const body = await readRequestBody(request);
|
|
5620
|
-
const parsedBodyResult = validateOptionalJsonBody(
|
|
5733
|
+
const parsedBodyResult = validateOptionalJsonBody(wakeCallbackBodySchema, body, request.headers.get(`content-type`));
|
|
5621
5734
|
if (!parsedBodyResult.ok) return parsedBodyResult.response;
|
|
5622
5735
|
const requestBody = parsedBodyResult.value;
|
|
5623
5736
|
const isClaimRequest = requestBody?.wakeId !== void 0 || requestBody?.wake_id !== void 0;
|
|
@@ -5635,14 +5748,14 @@ async function callbackForward(request, ctx) {
|
|
|
5635
5748
|
}
|
|
5636
5749
|
return json(responseBody);
|
|
5637
5750
|
}
|
|
5638
|
-
const upstreamBody =
|
|
5751
|
+
const upstreamBody = encodeWakeCallbackBody(ctx.service, consumerId, requestBody, resolveDurableStreamsRoutingAdapter(ctx.durableStreamsRouting, ctx.durableStreamsUrl));
|
|
5639
5752
|
let upstream;
|
|
5640
5753
|
try {
|
|
5641
5754
|
const subscriptionId = durableStreamsSubscriptionCallback(target.callbackUrl);
|
|
5642
5755
|
if (subscriptionId) {
|
|
5643
5756
|
const token = claimTokenFromRequest(request);
|
|
5644
5757
|
if (!token) return apiError(401, `UNAUTHORIZED`, `Missing claim token`);
|
|
5645
|
-
const upstreamPayload =
|
|
5758
|
+
const upstreamPayload = encodeWakeCallbackPayload(consumerId, requestBody, (stream) => stream.replace(/^\/+/, ``));
|
|
5646
5759
|
const result = await ctx.streamClient.ackSubscription(subscriptionId, token, upstreamPayload);
|
|
5647
5760
|
upstream = json(result);
|
|
5648
5761
|
} else upstream = await fetch(target.callbackUrl, {
|
|
@@ -5651,7 +5764,7 @@ async function callbackForward(request, ctx) {
|
|
|
5651
5764
|
body: bodyFromBytes(upstreamBody)
|
|
5652
5765
|
});
|
|
5653
5766
|
} catch (err) {
|
|
5654
|
-
return apiError(502, `
|
|
5767
|
+
return apiError(502, `WAKE_CALLBACK_FAILED`, err instanceof Error ? err.message : String(err));
|
|
5655
5768
|
}
|
|
5656
5769
|
let responseBytes = upstream.body ? new Uint8Array(await upstream.arrayBuffer()) : new Uint8Array();
|
|
5657
5770
|
if (isClaimRequest && upstream.ok && target.primaryStream) {
|
|
@@ -5671,7 +5784,7 @@ async function callbackForward(request, ctx) {
|
|
|
5671
5784
|
epoch
|
|
5672
5785
|
});
|
|
5673
5786
|
if (upstream.ok && isDoneRequest && target.primaryStream) {
|
|
5674
|
-
serverLog.info(`[callback
|
|
5787
|
+
serverLog.info(`[wake-callback] done received for stream=${target.primaryStream} consumer=${consumerId}`);
|
|
5675
5788
|
const stillOwnsClaim = ctx.runtime.claimWriteTokens.owns(ctx.service, target.primaryStream, consumerId);
|
|
5676
5789
|
const entity = await ctx.entityManager.registry.getEntityByStream(target.primaryStream);
|
|
5677
5790
|
let entityCleared = false;
|
|
@@ -5693,13 +5806,13 @@ async function callbackForward(request, ctx) {
|
|
|
5693
5806
|
if (entity && (entityCleared || stillOwnsClaim)) {
|
|
5694
5807
|
await ctx.entityManager.registry.updateStatus(entity.url, entity.status === `stopping` ? `stopped` : `idle`);
|
|
5695
5808
|
await ctx.entityBridgeManager.onEntityChanged(entity.url);
|
|
5696
|
-
serverLog.info(`[callback
|
|
5697
|
-
} else if (!entity) serverLog.warn(`[callback
|
|
5809
|
+
serverLog.info(`[wake-callback] status updated after done for ${entity.url}`);
|
|
5810
|
+
} else if (!entity) serverLog.warn(`[wake-callback] done received but no entity found for stream=${target.primaryStream}`);
|
|
5698
5811
|
if (stillOwnsClaim) ctx.runtime.claimWriteTokens.clearStream(ctx.service, target.primaryStream);
|
|
5699
|
-
else if (entity) serverLog.info(`[callback
|
|
5700
|
-
} else if (requestBody?.done === true) serverLog.warn(`[callback
|
|
5812
|
+
else if (entity) serverLog.info(`[wake-callback] done arrived after in-memory token evicted (stream=${target.primaryStream} consumer=${consumerId})`);
|
|
5813
|
+
} else if (requestBody?.done === true) serverLog.warn(`[wake-callback] done received but skipped: upstream.ok=${upstream.ok} primaryStream=${target.primaryStream ?? `null`} consumer=${consumerId}`);
|
|
5701
5814
|
} catch (err) {
|
|
5702
|
-
serverLog.error(`[callback
|
|
5815
|
+
serverLog.error(`[wake-callback] error processing done for consumer=${consumerId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5703
5816
|
}
|
|
5704
5817
|
return responseFromUpstream(upstream, responseBytes);
|
|
5705
5818
|
}
|
|
@@ -5708,11 +5821,11 @@ async function mintClaimWriteToken(ctx, streamPath, consumerId) {
|
|
|
5708
5821
|
if (!entity) return void 0;
|
|
5709
5822
|
return ctx.runtime.claimWriteTokens.mint(ctx.service, streamPath, consumerId);
|
|
5710
5823
|
}
|
|
5711
|
-
function
|
|
5712
|
-
const payload =
|
|
5824
|
+
function encodeWakeCallbackBody(service, consumerId, body, routingAdapter) {
|
|
5825
|
+
const payload = encodeWakeCallbackPayload(consumerId, body, (stream) => routingAdapter.toBackendStreamPath(service, stream));
|
|
5713
5826
|
return new TextEncoder().encode(JSON.stringify(payload));
|
|
5714
5827
|
}
|
|
5715
|
-
function
|
|
5828
|
+
function encodeWakeCallbackPayload(consumerId, body, mapStream) {
|
|
5716
5829
|
if (!body) return {};
|
|
5717
5830
|
const generation = body.generation ?? body.epoch;
|
|
5718
5831
|
const wakeId = body.wake_id ?? body.wakeId ?? consumerId;
|
|
@@ -7294,7 +7407,7 @@ var WakeRegistry = class {
|
|
|
7294
7407
|
try {
|
|
7295
7408
|
for (const message of messages) {
|
|
7296
7409
|
await this.applyShapeMessage(message);
|
|
7297
|
-
if (!settled &&
|
|
7410
|
+
if (!settled && isControlMessage(message) && message.headers.control === `up-to-date`) {
|
|
7298
7411
|
settled = true;
|
|
7299
7412
|
resolve$1();
|
|
7300
7413
|
}
|
|
@@ -8062,6 +8175,8 @@ var ElectricAgentsServer = class {
|
|
|
8062
8175
|
streamClient: this.streamClient,
|
|
8063
8176
|
runtime: this.standaloneRuntime.runtime,
|
|
8064
8177
|
entityBridgeManager: this.entityBridgeManager,
|
|
8178
|
+
...this.options.eventSources ? { eventSources: this.options.eventSources } : {},
|
|
8179
|
+
...this.options.ensureEventSourceWakeSource ? { ensureEventSourceWakeSource: this.options.ensureEventSourceWakeSource } : {},
|
|
8065
8180
|
isShuttingDown: () => this.shuttingDown,
|
|
8066
8181
|
mockAgent: this.mockAgentBootstrap ? { runtime: this.mockAgentBootstrap.runtime } : void 0
|
|
8067
8182
|
};
|