@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
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from '../electric-agents-http.js'
|
|
17
17
|
import { consumerCallbacks, subscriptionWebhooks } from '../db/schema.js'
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
ErrCodeWakeCallbackNotFound,
|
|
20
20
|
ErrCodeForkInProgress,
|
|
21
21
|
ErrCodeSubscriptionNotFound,
|
|
22
22
|
ErrCodeUnauthorized,
|
|
@@ -26,17 +26,20 @@ import { decodeJsonObject } from '../utils/server-utils.js'
|
|
|
26
26
|
import { serverLog } from '../utils/log.js'
|
|
27
27
|
import { applyDurableStreamsBearer } from '../stream-client.js'
|
|
28
28
|
import { getDefaultWebhookSigner } from '../webhook-signing.js'
|
|
29
|
-
import { cronRouter } from './cron-router.js'
|
|
30
29
|
import { resolveDurableStreamsRoutingAdapter } from './durable-streams-routing-adapter.js'
|
|
31
30
|
import { electricProxyRouter } from './electric-proxy-router.js'
|
|
32
31
|
import { entitiesRouter } from './entities-router.js'
|
|
33
32
|
import { entityTypesRouter } from './entity-types-router.js'
|
|
34
33
|
import { getRequestSpan } from './hooks.js'
|
|
34
|
+
import { observationsRouter } from './observations-router.js'
|
|
35
35
|
import { runnersRouter } from './runners-router.js'
|
|
36
36
|
import { routeBody, validateOptionalJsonBody, withSchema } from './schema.js'
|
|
37
37
|
import { withLeadingSlash } from './tenant-stream-paths.js'
|
|
38
38
|
import type { IRequest, RouterType } from 'itty-router'
|
|
39
|
-
import type {
|
|
39
|
+
import type {
|
|
40
|
+
EventSourceContract,
|
|
41
|
+
WebhookSignatureVerifierConfig,
|
|
42
|
+
} from '@electric-ax/agents-runtime'
|
|
40
43
|
import type { TenantContext } from './context.js'
|
|
41
44
|
import type { DurableStreamsRoutingAdapter } from './durable-streams-routing-adapter.js'
|
|
42
45
|
import type { WebhookSigner } from '../webhook-signing.js'
|
|
@@ -66,7 +69,7 @@ const wakeRegistrationBodySchema = Type.Object({
|
|
|
66
69
|
manifestKey: Type.Optional(Type.String()),
|
|
67
70
|
})
|
|
68
71
|
|
|
69
|
-
const
|
|
72
|
+
const subscriptionWebhookBodySchema = Type.Object(
|
|
70
73
|
{
|
|
71
74
|
subscription_id: Type.Optional(Type.String()),
|
|
72
75
|
wake_id: Type.Optional(Type.String()),
|
|
@@ -84,7 +87,7 @@ const webhookForwardBodySchema = Type.Object(
|
|
|
84
87
|
{ additionalProperties: true }
|
|
85
88
|
)
|
|
86
89
|
|
|
87
|
-
const
|
|
90
|
+
const wakeCallbackBodySchema = Type.Object(
|
|
88
91
|
{
|
|
89
92
|
epoch: Type.Optional(Type.Number()),
|
|
90
93
|
generation: Type.Optional(Type.Number()),
|
|
@@ -97,8 +100,8 @@ const callbackForwardBodySchema = Type.Object(
|
|
|
97
100
|
)
|
|
98
101
|
|
|
99
102
|
type WakeRegistrationBody = Static<typeof wakeRegistrationBodySchema>
|
|
100
|
-
type
|
|
101
|
-
type
|
|
103
|
+
type SubscriptionWebhookBody = Static<typeof subscriptionWebhookBodySchema>
|
|
104
|
+
type WakeCallbackBody = Static<typeof wakeCallbackBodySchema>
|
|
102
105
|
|
|
103
106
|
const DS_SUBSCRIPTION_CALLBACK_PREFIX = `ds-subscription:`
|
|
104
107
|
|
|
@@ -117,18 +120,22 @@ export const internalRouter: InternalRoutes = Router<
|
|
|
117
120
|
})
|
|
118
121
|
|
|
119
122
|
internalRouter.get(`/health`, () => json({ status: `ok` }))
|
|
123
|
+
internalRouter.get(`/event-sources`, listEventSources)
|
|
120
124
|
internalRouter.post(
|
|
121
125
|
`/wake`,
|
|
122
126
|
withSchema(wakeRegistrationBodySchema),
|
|
123
127
|
registerWake
|
|
124
128
|
)
|
|
125
|
-
internalRouter.post(
|
|
126
|
-
|
|
129
|
+
internalRouter.post(
|
|
130
|
+
`/subscription-webhooks/:subscriptionId`,
|
|
131
|
+
subscriptionWebhook
|
|
132
|
+
)
|
|
133
|
+
internalRouter.post(`/wake-callbacks/:consumerId`, wakeCallback)
|
|
127
134
|
internalRouter.all(`/runners`, runnersRouter.fetch)
|
|
128
135
|
internalRouter.all(`/runners/*`, runnersRouter.fetch)
|
|
129
136
|
internalRouter.all(`/entities/*`, entitiesRouter.fetch)
|
|
130
137
|
internalRouter.all(`/entity-types/*`, entityTypesRouter.fetch)
|
|
131
|
-
internalRouter.all(`/
|
|
138
|
+
internalRouter.all(`/observations/*`, observationsRouter.fetch)
|
|
132
139
|
internalRouter.get(`/electric/*`, electricProxyRouter.fetch)
|
|
133
140
|
internalRouter.all(`*`, () => status(404))
|
|
134
141
|
|
|
@@ -179,7 +186,7 @@ function resolveWebhookSigner(ctx: TenantContext): WebhookSigner {
|
|
|
179
186
|
|
|
180
187
|
function durableStreamsWebhookJwksUrl(ctx: TenantContext): string {
|
|
181
188
|
if (!ctx.durableStreamsRouting) {
|
|
182
|
-
return
|
|
189
|
+
return appendPathToBackendUrl(ctx.durableStreamsUrl, `/__ds/jwks.json`)
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
return resolveDurableStreamsRoutingAdapter(
|
|
@@ -194,6 +201,28 @@ function durableStreamsWebhookJwksUrl(ctx: TenantContext): string {
|
|
|
194
201
|
.toString()
|
|
195
202
|
}
|
|
196
203
|
|
|
204
|
+
function appendPathToBackendUrl(baseUrl: string, path: string): string {
|
|
205
|
+
const base = new URL(baseUrl)
|
|
206
|
+
const pathUrl = new URL(path, `http://electric-agents.local`)
|
|
207
|
+
const basePath =
|
|
208
|
+
base.pathname === `/` ? `` : base.pathname.replace(/\/+$/, ``)
|
|
209
|
+
const suffix = pathUrl.pathname.startsWith(`/`)
|
|
210
|
+
? pathUrl.pathname
|
|
211
|
+
: `/${pathUrl.pathname}`
|
|
212
|
+
const target = new URL(base)
|
|
213
|
+
|
|
214
|
+
target.pathname = `${basePath}${suffix}`
|
|
215
|
+
target.search = ``
|
|
216
|
+
target.hash = pathUrl.hash
|
|
217
|
+
base.searchParams.forEach((value, key) => {
|
|
218
|
+
target.searchParams.append(key, value)
|
|
219
|
+
})
|
|
220
|
+
pathUrl.searchParams.forEach((value, key) => {
|
|
221
|
+
target.searchParams.append(key, value)
|
|
222
|
+
})
|
|
223
|
+
return target.toString()
|
|
224
|
+
}
|
|
225
|
+
|
|
197
226
|
function durableStreamsJwksFetchClient(ctx: TenantContext): typeof fetch {
|
|
198
227
|
return async (input, init) => {
|
|
199
228
|
const headers = new Headers(init?.headers)
|
|
@@ -265,7 +294,7 @@ function claimTokenFromRequest(request: IRequest): string | undefined {
|
|
|
265
294
|
)
|
|
266
295
|
}
|
|
267
296
|
|
|
268
|
-
function newWebhookPayload(body:
|
|
297
|
+
function newWebhookPayload(body: SubscriptionWebhookBody | undefined): {
|
|
269
298
|
wakeId: string
|
|
270
299
|
generation: number
|
|
271
300
|
primaryStream: string
|
|
@@ -335,13 +364,27 @@ async function registerWake(
|
|
|
335
364
|
return status(204)
|
|
336
365
|
}
|
|
337
366
|
|
|
338
|
-
async function
|
|
367
|
+
async function listEventSources(
|
|
368
|
+
_request: IRequest,
|
|
369
|
+
ctx: TenantContext
|
|
370
|
+
): Promise<Response> {
|
|
371
|
+
const eventSources = ctx.eventSources
|
|
372
|
+
? await ctx.eventSources.listEventSources()
|
|
373
|
+
: []
|
|
374
|
+
return json({ eventSources: eventSources.filter(isAgentVisibleEventSource) })
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function isAgentVisibleEventSource(source: EventSourceContract): boolean {
|
|
378
|
+
return source.agentVisible === true && source.status === `active`
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function subscriptionWebhook(
|
|
339
382
|
request: IRequest,
|
|
340
383
|
ctx: TenantContext
|
|
341
384
|
): Promise<Response> {
|
|
342
385
|
const subscriptionId = routeParam(request, `subscriptionId`)
|
|
343
386
|
const rootSpan = getRequestSpan(request)
|
|
344
|
-
rootSpan?.updateName(`webhook
|
|
387
|
+
rootSpan?.updateName(`subscription-webhook`)
|
|
345
388
|
rootSpan?.setAttribute(
|
|
346
389
|
`electric_agents.webhook.subscription_id`,
|
|
347
390
|
subscriptionId
|
|
@@ -380,7 +423,7 @@ async function webhookForward(
|
|
|
380
423
|
)
|
|
381
424
|
}
|
|
382
425
|
const parsedBodyResult = validateOptionalJsonBody(
|
|
383
|
-
|
|
426
|
+
subscriptionWebhookBodySchema,
|
|
384
427
|
body,
|
|
385
428
|
request.headers.get(`content-type`)
|
|
386
429
|
)
|
|
@@ -388,7 +431,9 @@ async function webhookForward(
|
|
|
388
431
|
|
|
389
432
|
let forwardBody = body
|
|
390
433
|
let runningEntityUrl: string | null = null
|
|
391
|
-
const parsedBody = parsedBodyResult.value as
|
|
434
|
+
const parsedBody = parsedBodyResult.value as
|
|
435
|
+
| SubscriptionWebhookBody
|
|
436
|
+
| undefined
|
|
392
437
|
const newWebhook = newWebhookPayload(parsedBody)
|
|
393
438
|
const routingAdapter = resolveDurableStreamsRoutingAdapter(
|
|
394
439
|
ctx.durableStreamsRouting,
|
|
@@ -467,7 +512,7 @@ async function webhookForward(
|
|
|
467
512
|
})
|
|
468
513
|
.catch((err) => {
|
|
469
514
|
serverLog.warn(
|
|
470
|
-
`[webhook
|
|
515
|
+
`[subscription-webhook] consumerCallbacks upsert failed (non-fatal): ${
|
|
471
516
|
err instanceof Error ? err.message : String(err)
|
|
472
517
|
}`
|
|
473
518
|
)
|
|
@@ -515,7 +560,7 @@ async function webhookForward(
|
|
|
515
560
|
if (consumerId && callbackUrl) {
|
|
516
561
|
const callback = appendPathToUrl(
|
|
517
562
|
ctx.publicUrl,
|
|
518
|
-
`/_electric/
|
|
563
|
+
`/_electric/wake-callbacks/${encodeURIComponent(consumerId)}`
|
|
519
564
|
)
|
|
520
565
|
enriched.callback = callback
|
|
521
566
|
if (newWebhook) {
|
|
@@ -564,7 +609,7 @@ async function webhookForward(
|
|
|
564
609
|
}
|
|
565
610
|
return apiError(
|
|
566
611
|
502,
|
|
567
|
-
`
|
|
612
|
+
`SUBSCRIPTION_WEBHOOK_FAILED`,
|
|
568
613
|
err instanceof Error ? err.message : String(err)
|
|
569
614
|
)
|
|
570
615
|
}
|
|
@@ -575,7 +620,7 @@ async function webhookForward(
|
|
|
575
620
|
return responseFromUpstream(upstream, responseBytes)
|
|
576
621
|
}
|
|
577
622
|
|
|
578
|
-
async function
|
|
623
|
+
async function wakeCallback(
|
|
579
624
|
request: IRequest,
|
|
580
625
|
ctx: TenantContext
|
|
581
626
|
): Promise<Response> {
|
|
@@ -600,19 +645,19 @@ async function callbackForward(
|
|
|
600
645
|
if (!target) {
|
|
601
646
|
return apiError(
|
|
602
647
|
404,
|
|
603
|
-
|
|
604
|
-
`Unknown callback
|
|
648
|
+
ErrCodeWakeCallbackNotFound,
|
|
649
|
+
`Unknown wake-callback consumer`
|
|
605
650
|
)
|
|
606
651
|
}
|
|
607
652
|
|
|
608
653
|
const body = await readRequestBody(request as Request)
|
|
609
654
|
const parsedBodyResult = validateOptionalJsonBody(
|
|
610
|
-
|
|
655
|
+
wakeCallbackBodySchema,
|
|
611
656
|
body,
|
|
612
657
|
request.headers.get(`content-type`)
|
|
613
658
|
)
|
|
614
659
|
if (!parsedBodyResult.ok) return parsedBodyResult.response
|
|
615
|
-
const requestBody = parsedBodyResult.value as
|
|
660
|
+
const requestBody = parsedBodyResult.value as WakeCallbackBody | undefined
|
|
616
661
|
const isClaimRequest =
|
|
617
662
|
requestBody?.wakeId !== undefined || requestBody?.wake_id !== undefined
|
|
618
663
|
const isDoneRequest = requestBody?.done === true
|
|
@@ -635,7 +680,7 @@ async function callbackForward(
|
|
|
635
680
|
return json(responseBody)
|
|
636
681
|
}
|
|
637
682
|
|
|
638
|
-
const upstreamBody =
|
|
683
|
+
const upstreamBody = encodeWakeCallbackBody(
|
|
639
684
|
ctx.service,
|
|
640
685
|
consumerId,
|
|
641
686
|
requestBody,
|
|
@@ -655,7 +700,7 @@ async function callbackForward(
|
|
|
655
700
|
if (!token) {
|
|
656
701
|
return apiError(401, `UNAUTHORIZED`, `Missing claim token`)
|
|
657
702
|
}
|
|
658
|
-
const upstreamPayload =
|
|
703
|
+
const upstreamPayload = encodeWakeCallbackPayload(
|
|
659
704
|
consumerId,
|
|
660
705
|
requestBody,
|
|
661
706
|
(stream) => stream.replace(/^\/+/, ``)
|
|
@@ -676,7 +721,7 @@ async function callbackForward(
|
|
|
676
721
|
} catch (err) {
|
|
677
722
|
return apiError(
|
|
678
723
|
502,
|
|
679
|
-
`
|
|
724
|
+
`WAKE_CALLBACK_FAILED`,
|
|
680
725
|
err instanceof Error ? err.message : String(err)
|
|
681
726
|
)
|
|
682
727
|
}
|
|
@@ -715,7 +760,7 @@ async function callbackForward(
|
|
|
715
760
|
}
|
|
716
761
|
if (upstream.ok && isDoneRequest && target.primaryStream) {
|
|
717
762
|
serverLog.info(
|
|
718
|
-
`[callback
|
|
763
|
+
`[wake-callback] done received for stream=${target.primaryStream} consumer=${consumerId}`
|
|
719
764
|
)
|
|
720
765
|
const stillOwnsClaim = ctx.runtime.claimWriteTokens.owns(
|
|
721
766
|
ctx.service,
|
|
@@ -770,11 +815,11 @@ async function callbackForward(
|
|
|
770
815
|
)
|
|
771
816
|
await ctx.entityBridgeManager.onEntityChanged(entity.url)
|
|
772
817
|
serverLog.info(
|
|
773
|
-
`[callback
|
|
818
|
+
`[wake-callback] status updated after done for ${entity.url}`
|
|
774
819
|
)
|
|
775
820
|
} else if (!entity) {
|
|
776
821
|
serverLog.warn(
|
|
777
|
-
`[callback
|
|
822
|
+
`[wake-callback] done received but no entity found for stream=${target.primaryStream}`
|
|
778
823
|
)
|
|
779
824
|
}
|
|
780
825
|
|
|
@@ -788,19 +833,19 @@ async function callbackForward(
|
|
|
788
833
|
)
|
|
789
834
|
} else if (entity) {
|
|
790
835
|
serverLog.info(
|
|
791
|
-
`[callback
|
|
836
|
+
`[wake-callback] done arrived after in-memory token evicted (stream=${target.primaryStream} consumer=${consumerId})`
|
|
792
837
|
)
|
|
793
838
|
}
|
|
794
839
|
} else if (requestBody?.done === true) {
|
|
795
840
|
serverLog.warn(
|
|
796
|
-
`[callback
|
|
841
|
+
`[wake-callback] done received but skipped: upstream.ok=${upstream.ok} primaryStream=${
|
|
797
842
|
target.primaryStream ?? `null`
|
|
798
843
|
} consumer=${consumerId}`
|
|
799
844
|
)
|
|
800
845
|
}
|
|
801
846
|
} catch (err) {
|
|
802
847
|
serverLog.error(
|
|
803
|
-
`[callback
|
|
848
|
+
`[wake-callback] error processing done for consumer=${consumerId}: ${
|
|
804
849
|
err instanceof Error ? err.message : String(err)
|
|
805
850
|
}`
|
|
806
851
|
)
|
|
@@ -820,21 +865,21 @@ async function mintClaimWriteToken(
|
|
|
820
865
|
return ctx.runtime.claimWriteTokens.mint(ctx.service, streamPath, consumerId)
|
|
821
866
|
}
|
|
822
867
|
|
|
823
|
-
function
|
|
868
|
+
function encodeWakeCallbackBody(
|
|
824
869
|
service: string,
|
|
825
870
|
consumerId: string,
|
|
826
|
-
body:
|
|
871
|
+
body: WakeCallbackBody | undefined,
|
|
827
872
|
routingAdapter: DurableStreamsRoutingAdapter
|
|
828
873
|
): Uint8Array {
|
|
829
|
-
const payload =
|
|
874
|
+
const payload = encodeWakeCallbackPayload(consumerId, body, (stream) =>
|
|
830
875
|
routingAdapter.toBackendStreamPath(service, stream)
|
|
831
876
|
)
|
|
832
877
|
return new TextEncoder().encode(JSON.stringify(payload))
|
|
833
878
|
}
|
|
834
879
|
|
|
835
|
-
function
|
|
880
|
+
function encodeWakeCallbackPayload(
|
|
836
881
|
consumerId: string,
|
|
837
|
-
body:
|
|
882
|
+
body: WakeCallbackBody | undefined,
|
|
838
883
|
mapStream: (stream: string) => string
|
|
839
884
|
): Record<string, unknown> {
|
|
840
885
|
if (!body) return {}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP routes for ensuring observation backing streams.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type, type Static } from '@sinclair/typebox'
|
|
6
|
+
import { Router, json } from 'itty-router'
|
|
7
|
+
import { routeBody, withSchema } from './schema.js'
|
|
8
|
+
import type { JsonRouteRequest } from './schema.js'
|
|
9
|
+
import type { RouterType } from 'itty-router'
|
|
10
|
+
import type { TenantContext } from './context.js'
|
|
11
|
+
|
|
12
|
+
const stringRecordSchema = Type.Record(Type.String(), Type.String())
|
|
13
|
+
|
|
14
|
+
const ensureEntitiesMembershipStreamBodySchema = Type.Object({
|
|
15
|
+
tags: Type.Optional(stringRecordSchema),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const ensureCronStreamBodySchema = Type.Object({
|
|
19
|
+
expression: Type.String(),
|
|
20
|
+
timezone: Type.Optional(Type.String()),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
type EnsureEntitiesMembershipStreamBody = Static<
|
|
24
|
+
typeof ensureEntitiesMembershipStreamBodySchema
|
|
25
|
+
>
|
|
26
|
+
type EnsureCronStreamBody = Static<typeof ensureCronStreamBodySchema>
|
|
27
|
+
|
|
28
|
+
export type ObservationsRoutes = RouterType<
|
|
29
|
+
JsonRouteRequest,
|
|
30
|
+
[TenantContext],
|
|
31
|
+
Response | undefined
|
|
32
|
+
>
|
|
33
|
+
|
|
34
|
+
export const observationsRouter: ObservationsRoutes = Router<
|
|
35
|
+
JsonRouteRequest,
|
|
36
|
+
[TenantContext],
|
|
37
|
+
Response | undefined
|
|
38
|
+
>({
|
|
39
|
+
base: `/_electric/observations`,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
observationsRouter.post(
|
|
43
|
+
`/entities/ensure-stream`,
|
|
44
|
+
withSchema(ensureEntitiesMembershipStreamBodySchema),
|
|
45
|
+
ensureEntitiesMembershipStream
|
|
46
|
+
)
|
|
47
|
+
observationsRouter.post(
|
|
48
|
+
`/cron/ensure-stream`,
|
|
49
|
+
withSchema(ensureCronStreamBodySchema),
|
|
50
|
+
ensureCronStream
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
async function ensureEntitiesMembershipStream(
|
|
54
|
+
request: JsonRouteRequest,
|
|
55
|
+
ctx: TenantContext
|
|
56
|
+
): Promise<Response> {
|
|
57
|
+
const parsed = routeBody<EnsureEntitiesMembershipStreamBody>(request)
|
|
58
|
+
const result = await ctx.entityManager.ensureEntitiesMembershipStream(
|
|
59
|
+
parsed.tags ?? {}
|
|
60
|
+
)
|
|
61
|
+
return json(result)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function ensureCronStream(
|
|
65
|
+
request: JsonRouteRequest,
|
|
66
|
+
ctx: TenantContext
|
|
67
|
+
): Promise<Response> {
|
|
68
|
+
const parsed = routeBody<EnsureCronStreamBody>(request)
|
|
69
|
+
const streamPath = await ctx.entityManager.getOrCreateCronStream(
|
|
70
|
+
parsed.expression,
|
|
71
|
+
parsed.timezone
|
|
72
|
+
)
|
|
73
|
+
return json({ streamUrl: streamPath })
|
|
74
|
+
}
|
|
@@ -628,7 +628,7 @@ async function notificationFromClaim(
|
|
|
628
628
|
streams,
|
|
629
629
|
callback: appendPathToUrl(
|
|
630
630
|
ctx.publicUrl,
|
|
631
|
-
`/_electric/
|
|
631
|
+
`/_electric/wake-callbacks/${encodeURIComponent(input.claim.wake_id)}`
|
|
632
632
|
),
|
|
633
633
|
claimToken: input.claim.token,
|
|
634
634
|
triggerEvent: `message_received`,
|
package/src/server.ts
CHANGED
|
@@ -34,6 +34,7 @@ import type { Principal } from './principal.js'
|
|
|
34
34
|
import type { EntityBridgeCoordinator } from './entity-bridge-manager.js'
|
|
35
35
|
import type { DurableStreamsRoutingAdapter } from './routing/durable-streams-routing-adapter.js'
|
|
36
36
|
import type { OssServerContext } from './routing/oss-server-router.js'
|
|
37
|
+
import type { EventSourceCatalog } from './routing/context.js'
|
|
37
38
|
import type { StartedStandaloneAgentsRuntime } from './standalone-runtime.js'
|
|
38
39
|
import type { DurableStreamsBearerProvider } from './stream-client.js'
|
|
39
40
|
import type {
|
|
@@ -67,6 +68,8 @@ export interface ElectricAgentsServerOptions {
|
|
|
67
68
|
request: Request
|
|
68
69
|
) => Promise<Principal | null> | Principal | null
|
|
69
70
|
allowDevPrincipalFallback?: boolean
|
|
71
|
+
eventSources?: EventSourceCatalog
|
|
72
|
+
ensureEventSourceWakeSource?: (sourceUrl: string) => Promise<void> | void
|
|
70
73
|
/**
|
|
71
74
|
* Disabled by default. When set to a positive interval, periodically
|
|
72
75
|
* recovers expired dispatch claims and stale outstanding wakes.
|
|
@@ -441,6 +444,15 @@ export class ElectricAgentsServer {
|
|
|
441
444
|
streamClient: this.streamClient,
|
|
442
445
|
runtime: this.standaloneRuntime.runtime,
|
|
443
446
|
entityBridgeManager: this.entityBridgeManager,
|
|
447
|
+
...(this.options.eventSources
|
|
448
|
+
? { eventSources: this.options.eventSources }
|
|
449
|
+
: {}),
|
|
450
|
+
...(this.options.ensureEventSourceWakeSource
|
|
451
|
+
? {
|
|
452
|
+
ensureEventSourceWakeSource:
|
|
453
|
+
this.options.ensureEventSourceWakeSource,
|
|
454
|
+
}
|
|
455
|
+
: {}),
|
|
444
456
|
isShuttingDown: () => this.shuttingDown,
|
|
445
457
|
mockAgent: this.mockAgentBootstrap
|
|
446
458
|
? { runtime: this.mockAgentBootstrap.runtime }
|
package/src/wake-registry.ts
CHANGED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP routes under /_electric/cron.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Type, type Static } from '@sinclair/typebox'
|
|
6
|
-
import { Router, json } from 'itty-router'
|
|
7
|
-
import { routeBody, withSchema } from './schema.js'
|
|
8
|
-
import type { JsonRouteRequest } from './schema.js'
|
|
9
|
-
import type { RouterType } from 'itty-router'
|
|
10
|
-
import type { TenantContext } from './context.js'
|
|
11
|
-
|
|
12
|
-
const cronRegisterBodySchema = Type.Object({
|
|
13
|
-
expression: Type.String(),
|
|
14
|
-
timezone: Type.Optional(Type.String()),
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
type CronRegisterBody = Static<typeof cronRegisterBodySchema>
|
|
18
|
-
|
|
19
|
-
export type CronRoutes = RouterType<
|
|
20
|
-
JsonRouteRequest,
|
|
21
|
-
[TenantContext],
|
|
22
|
-
Response | undefined
|
|
23
|
-
>
|
|
24
|
-
|
|
25
|
-
export const cronRouter: CronRoutes = Router<
|
|
26
|
-
JsonRouteRequest,
|
|
27
|
-
[TenantContext],
|
|
28
|
-
Response | undefined
|
|
29
|
-
>({
|
|
30
|
-
base: `/_electric/cron`,
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
cronRouter.post(`/register`, withSchema(cronRegisterBodySchema), registerCron)
|
|
34
|
-
|
|
35
|
-
async function registerCron(
|
|
36
|
-
request: JsonRouteRequest,
|
|
37
|
-
ctx: TenantContext
|
|
38
|
-
): Promise<Response> {
|
|
39
|
-
const parsed = routeBody<CronRegisterBody>(request)
|
|
40
|
-
const streamPath = await ctx.entityManager.getOrCreateCronStream(
|
|
41
|
-
parsed.expression,
|
|
42
|
-
parsed.timezone
|
|
43
|
-
)
|
|
44
|
-
return json({ streamUrl: streamPath })
|
|
45
|
-
}
|