@electric-ax/agents-server 0.4.9 → 0.4.12

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.
@@ -193,10 +193,6 @@ const eventSourceSubscriptionBodySchema = Type.Object({
193
193
  reason: Type.Optional(Type.String()),
194
194
  })
195
195
 
196
- const entitiesRegisterBodySchema = Type.Object({
197
- tags: Type.Optional(stringRecordSchema),
198
- })
199
-
200
196
  type SpawnBody = Static<typeof spawnBodySchema>
201
197
  type SendBody = Static<typeof sendBodySchema>
202
198
  type InboxMessageBody = Static<typeof inboxMessageBodySchema>
@@ -207,7 +203,6 @@ type ScheduleBody = Static<typeof scheduleBodySchema>
207
203
  type EventSourceSubscriptionBody = Static<
208
204
  typeof eventSourceSubscriptionBodySchema
209
205
  >
210
- type EntitiesRegisterBody = Static<typeof entitiesRegisterBodySchema>
211
206
 
212
207
  export const entitiesRouter: EntitiesRoutes = Router<
213
208
  AgentsRouteRequest,
@@ -218,11 +213,6 @@ export const entitiesRouter: EntitiesRoutes = Router<
218
213
  })
219
214
 
220
215
  entitiesRouter.get(`/`, listEntities)
221
- entitiesRouter.post(
222
- `/register`,
223
- withSchema(entitiesRegisterBodySchema),
224
- registerEntitiesSource
225
- )
226
216
  entitiesRouter.put(
227
217
  `/:type/:instanceId`,
228
218
  withSpawnableEntityType,
@@ -270,7 +260,7 @@ entitiesRouter.post(
270
260
  entitiesRouter.delete(
271
261
  `/:type/:instanceId/tags/:tagKey`,
272
262
  withExistingEntity,
273
- removeTag
263
+ deleteTag
274
264
  )
275
265
  entitiesRouter.put(
276
266
  `/:type/:instanceId/schedules/:scheduleId`,
@@ -429,17 +419,6 @@ async function listEntities(
429
419
  return json(entities.map((entity) => toPublicEntity(entity)))
430
420
  }
431
421
 
432
- async function registerEntitiesSource(
433
- request: AgentsRouteRequest,
434
- ctx: TenantContext
435
- ): Promise<Response> {
436
- const parsed = routeBody<EntitiesRegisterBody>(request)
437
- const result = await ctx.entityManager.registerEntitiesSource(
438
- parsed.tags ?? {}
439
- )
440
- return json(result)
441
- }
442
-
443
422
  async function upsertSchedule(
444
423
  request: AgentsRouteRequest,
445
424
  ctx: TenantContext
@@ -603,7 +582,7 @@ async function setTag(
603
582
  ): Promise<Response> {
604
583
  const principalMutationError = rejectPrincipalEntityMutation(
605
584
  request,
606
- `tagged`
585
+ `tag updated`
607
586
  )
608
587
  if (principalMutationError) return principalMutationError
609
588
 
@@ -619,19 +598,19 @@ async function setTag(
619
598
  return json(toPublicEntity(updated))
620
599
  }
621
600
 
622
- async function removeTag(
601
+ async function deleteTag(
623
602
  request: AgentsRouteRequest,
624
603
  ctx: TenantContext
625
604
  ): Promise<Response> {
626
605
  const principalMutationError = rejectPrincipalEntityMutation(
627
606
  request,
628
- `untagged`
607
+ `tag deleted`
629
608
  )
630
609
  if (principalMutationError) return principalMutationError
631
610
 
632
611
  const { entityUrl } = requireExistingEntityRoute(request)
633
612
  const token = writeTokenFromRequest(request)
634
- const updated = await ctx.entityManager.removeTag(
613
+ const updated = await ctx.entityManager.deleteTag(
635
614
  entityUrl,
636
615
  decodeURIComponent(request.params.tagKey),
637
616
  token
@@ -35,32 +35,32 @@ export type ElectricAgentsEntityTypeRoutes = RouterType<
35
35
  >
36
36
 
37
37
  type PublicEntityTypeResponse = ElectricAgentsEntityType & {
38
- input_schemas?: Record<string, Record<string, unknown>>
39
- output_schemas?: Record<string, Record<string, unknown>>
40
38
  revision: number
41
39
  }
42
40
 
43
41
  const jsonObjectSchema = Type.Record(Type.String(), Type.Unknown())
44
42
  const schemaMapSchema = Type.Record(Type.String(), jsonObjectSchema)
45
43
 
46
- const registerEntityTypeBodySchema = Type.Object({
47
- name: Type.Optional(Type.String()),
48
- description: Type.Optional(Type.String()),
49
- creation_schema: Type.Optional(jsonObjectSchema),
50
- inbox_schemas: Type.Optional(schemaMapSchema),
51
- state_schemas: Type.Optional(schemaMapSchema),
52
- input_schemas: Type.Optional(schemaMapSchema),
53
- output_schemas: Type.Optional(schemaMapSchema),
54
- serve_endpoint: Type.Optional(Type.String()),
55
- default_dispatch_policy: Type.Optional(dispatchPolicySchema),
56
- })
44
+ const registerEntityTypeBodySchema = Type.Object(
45
+ {
46
+ name: Type.Optional(Type.String()),
47
+ description: Type.Optional(Type.String()),
48
+ creation_schema: Type.Optional(jsonObjectSchema),
49
+ inbox_schemas: Type.Optional(schemaMapSchema),
50
+ state_schemas: Type.Optional(schemaMapSchema),
51
+ serve_endpoint: Type.Optional(Type.String()),
52
+ default_dispatch_policy: Type.Optional(dispatchPolicySchema),
53
+ },
54
+ { additionalProperties: false }
55
+ )
57
56
 
58
- const amendEntityTypeSchemasBodySchema = Type.Object({
59
- input_schemas: Type.Optional(schemaMapSchema),
60
- output_schemas: Type.Optional(schemaMapSchema),
61
- inbox_schemas: Type.Optional(schemaMapSchema),
62
- state_schemas: Type.Optional(schemaMapSchema),
63
- })
57
+ const amendEntityTypeSchemasBodySchema = Type.Object(
58
+ {
59
+ inbox_schemas: Type.Optional(schemaMapSchema),
60
+ state_schemas: Type.Optional(schemaMapSchema),
61
+ },
62
+ { additionalProperties: false }
63
+ )
64
64
 
65
65
  type RegisterEntityTypeBody = Static<typeof registerEntityTypeBodySchema>
66
66
  type AmendEntityTypeSchemasBody = Static<
@@ -181,8 +181,8 @@ async function amendSchemas(
181
181
  const parsed = routeBody<AmendEntityTypeSchemasBody>(request)
182
182
 
183
183
  const updated = await ctx.entityManager.amendSchemas(request.params.name, {
184
- inbox_schemas: parsed.inbox_schemas ?? parsed.input_schemas,
185
- state_schemas: parsed.state_schemas ?? parsed.output_schemas,
184
+ inbox_schemas: parsed.inbox_schemas,
185
+ state_schemas: parsed.state_schemas,
186
186
  })
187
187
  return json(toPublicEntityType(updated))
188
188
  }
@@ -199,13 +199,12 @@ function normalizeEntityTypeRequest(
199
199
  parsed: RegisterEntityTypeBody | RegisterEntityTypeRequest
200
200
  ): RegisterEntityTypeRequest {
201
201
  const serveEndpoint = rewriteLoopbackWebhookUrl(parsed.serve_endpoint)
202
- const compatibilityFields = parsed as RegisterEntityTypeBody
203
202
  return {
204
203
  name: parsed.name ?? ``,
205
204
  description: parsed.description ?? ``,
206
205
  creation_schema: parsed.creation_schema,
207
- inbox_schemas: parsed.inbox_schemas ?? compatibilityFields.input_schemas,
208
- state_schemas: parsed.state_schemas ?? compatibilityFields.output_schemas,
206
+ inbox_schemas: parsed.inbox_schemas,
207
+ state_schemas: parsed.state_schemas,
209
208
  serve_endpoint: serveEndpoint,
210
209
  default_dispatch_policy:
211
210
  parsed.default_dispatch_policy ??
@@ -222,8 +221,6 @@ function toPublicEntityType(
222
221
  ): PublicEntityTypeResponse {
223
222
  return {
224
223
  ...entityType,
225
- input_schemas: entityType.inbox_schemas,
226
- output_schemas: entityType.state_schemas,
227
224
  revision: entityType.revision,
228
225
  }
229
226
  }
@@ -121,7 +121,7 @@ export function rejectIfShuttingDown(
121
121
  ): Response | undefined {
122
122
  if (!ctx.isShuttingDown()) return undefined
123
123
  const path = new URL(req.url).pathname
124
- if (!path.startsWith(`/_electric/webhook-forward/`)) return undefined
124
+ if (!path.startsWith(`/_electric/subscription-webhooks/`)) return undefined
125
125
  return apiError(503, `SERVER_STOPPING`, `Server is shutting down`)
126
126
  }
127
127
 
@@ -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
- ErrCodeCallbackNotFound,
19
+ ErrCodeWakeCallbackNotFound,
20
20
  ErrCodeForkInProgress,
21
21
  ErrCodeSubscriptionNotFound,
22
22
  ErrCodeUnauthorized,
@@ -26,12 +26,12 @@ 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'
@@ -69,7 +69,7 @@ const wakeRegistrationBodySchema = Type.Object({
69
69
  manifestKey: Type.Optional(Type.String()),
70
70
  })
71
71
 
72
- const webhookForwardBodySchema = Type.Object(
72
+ const subscriptionWebhookBodySchema = Type.Object(
73
73
  {
74
74
  subscription_id: Type.Optional(Type.String()),
75
75
  wake_id: Type.Optional(Type.String()),
@@ -87,7 +87,7 @@ const webhookForwardBodySchema = Type.Object(
87
87
  { additionalProperties: true }
88
88
  )
89
89
 
90
- const callbackForwardBodySchema = Type.Object(
90
+ const wakeCallbackBodySchema = Type.Object(
91
91
  {
92
92
  epoch: Type.Optional(Type.Number()),
93
93
  generation: Type.Optional(Type.Number()),
@@ -100,8 +100,8 @@ const callbackForwardBodySchema = Type.Object(
100
100
  )
101
101
 
102
102
  type WakeRegistrationBody = Static<typeof wakeRegistrationBodySchema>
103
- type WebhookForwardBody = Static<typeof webhookForwardBodySchema>
104
- type CallbackForwardBody = Static<typeof callbackForwardBodySchema>
103
+ type SubscriptionWebhookBody = Static<typeof subscriptionWebhookBodySchema>
104
+ type WakeCallbackBody = Static<typeof wakeCallbackBodySchema>
105
105
 
106
106
  const DS_SUBSCRIPTION_CALLBACK_PREFIX = `ds-subscription:`
107
107
 
@@ -126,13 +126,16 @@ internalRouter.post(
126
126
  withSchema(wakeRegistrationBodySchema),
127
127
  registerWake
128
128
  )
129
- internalRouter.post(`/webhook-forward/:subscriptionId`, webhookForward)
130
- internalRouter.post(`/callback-forward/:consumerId`, callbackForward)
129
+ internalRouter.post(
130
+ `/subscription-webhooks/:subscriptionId`,
131
+ subscriptionWebhook
132
+ )
133
+ internalRouter.post(`/wake-callbacks/:consumerId`, wakeCallback)
131
134
  internalRouter.all(`/runners`, runnersRouter.fetch)
132
135
  internalRouter.all(`/runners/*`, runnersRouter.fetch)
133
136
  internalRouter.all(`/entities/*`, entitiesRouter.fetch)
134
137
  internalRouter.all(`/entity-types/*`, entityTypesRouter.fetch)
135
- internalRouter.all(`/cron/*`, cronRouter.fetch)
138
+ internalRouter.all(`/observations/*`, observationsRouter.fetch)
136
139
  internalRouter.get(`/electric/*`, electricProxyRouter.fetch)
137
140
  internalRouter.all(`*`, () => status(404))
138
141
 
@@ -183,7 +186,7 @@ function resolveWebhookSigner(ctx: TenantContext): WebhookSigner {
183
186
 
184
187
  function durableStreamsWebhookJwksUrl(ctx: TenantContext): string {
185
188
  if (!ctx.durableStreamsRouting) {
186
- return appendPathToUrl(ctx.durableStreamsUrl, `/__ds/jwks.json`)
189
+ return appendPathToBackendUrl(ctx.durableStreamsUrl, `/__ds/jwks.json`)
187
190
  }
188
191
 
189
192
  return resolveDurableStreamsRoutingAdapter(
@@ -198,6 +201,28 @@ function durableStreamsWebhookJwksUrl(ctx: TenantContext): string {
198
201
  .toString()
199
202
  }
200
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
+
201
226
  function durableStreamsJwksFetchClient(ctx: TenantContext): typeof fetch {
202
227
  return async (input, init) => {
203
228
  const headers = new Headers(init?.headers)
@@ -269,7 +294,7 @@ function claimTokenFromRequest(request: IRequest): string | undefined {
269
294
  )
270
295
  }
271
296
 
272
- function newWebhookPayload(body: WebhookForwardBody | undefined): {
297
+ function newWebhookPayload(body: SubscriptionWebhookBody | undefined): {
273
298
  wakeId: string
274
299
  generation: number
275
300
  primaryStream: string
@@ -353,13 +378,13 @@ function isAgentVisibleEventSource(source: EventSourceContract): boolean {
353
378
  return source.agentVisible === true && source.status === `active`
354
379
  }
355
380
 
356
- async function webhookForward(
381
+ async function subscriptionWebhook(
357
382
  request: IRequest,
358
383
  ctx: TenantContext
359
384
  ): Promise<Response> {
360
385
  const subscriptionId = routeParam(request, `subscriptionId`)
361
386
  const rootSpan = getRequestSpan(request)
362
- rootSpan?.updateName(`webhook-forward`)
387
+ rootSpan?.updateName(`subscription-webhook`)
363
388
  rootSpan?.setAttribute(
364
389
  `electric_agents.webhook.subscription_id`,
365
390
  subscriptionId
@@ -398,7 +423,7 @@ async function webhookForward(
398
423
  )
399
424
  }
400
425
  const parsedBodyResult = validateOptionalJsonBody(
401
- webhookForwardBodySchema,
426
+ subscriptionWebhookBodySchema,
402
427
  body,
403
428
  request.headers.get(`content-type`)
404
429
  )
@@ -406,7 +431,9 @@ async function webhookForward(
406
431
 
407
432
  let forwardBody = body
408
433
  let runningEntityUrl: string | null = null
409
- const parsedBody = parsedBodyResult.value as WebhookForwardBody | undefined
434
+ const parsedBody = parsedBodyResult.value as
435
+ | SubscriptionWebhookBody
436
+ | undefined
410
437
  const newWebhook = newWebhookPayload(parsedBody)
411
438
  const routingAdapter = resolveDurableStreamsRoutingAdapter(
412
439
  ctx.durableStreamsRouting,
@@ -485,7 +512,7 @@ async function webhookForward(
485
512
  })
486
513
  .catch((err) => {
487
514
  serverLog.warn(
488
- `[webhook-forward] consumerCallbacks upsert failed (non-fatal): ${
515
+ `[subscription-webhook] consumerCallbacks upsert failed (non-fatal): ${
489
516
  err instanceof Error ? err.message : String(err)
490
517
  }`
491
518
  )
@@ -533,7 +560,7 @@ async function webhookForward(
533
560
  if (consumerId && callbackUrl) {
534
561
  const callback = appendPathToUrl(
535
562
  ctx.publicUrl,
536
- `/_electric/callback-forward/${encodeURIComponent(consumerId)}`
563
+ `/_electric/wake-callbacks/${encodeURIComponent(consumerId)}`
537
564
  )
538
565
  enriched.callback = callback
539
566
  if (newWebhook) {
@@ -582,7 +609,7 @@ async function webhookForward(
582
609
  }
583
610
  return apiError(
584
611
  502,
585
- `WEBHOOK_FORWARD_FAILED`,
612
+ `SUBSCRIPTION_WEBHOOK_FAILED`,
586
613
  err instanceof Error ? err.message : String(err)
587
614
  )
588
615
  }
@@ -593,7 +620,7 @@ async function webhookForward(
593
620
  return responseFromUpstream(upstream, responseBytes)
594
621
  }
595
622
 
596
- async function callbackForward(
623
+ async function wakeCallback(
597
624
  request: IRequest,
598
625
  ctx: TenantContext
599
626
  ): Promise<Response> {
@@ -618,19 +645,19 @@ async function callbackForward(
618
645
  if (!target) {
619
646
  return apiError(
620
647
  404,
621
- ErrCodeCallbackNotFound,
622
- `Unknown callback-forward consumer`
648
+ ErrCodeWakeCallbackNotFound,
649
+ `Unknown wake-callback consumer`
623
650
  )
624
651
  }
625
652
 
626
653
  const body = await readRequestBody(request as Request)
627
654
  const parsedBodyResult = validateOptionalJsonBody(
628
- callbackForwardBodySchema,
655
+ wakeCallbackBodySchema,
629
656
  body,
630
657
  request.headers.get(`content-type`)
631
658
  )
632
659
  if (!parsedBodyResult.ok) return parsedBodyResult.response
633
- const requestBody = parsedBodyResult.value as CallbackForwardBody | undefined
660
+ const requestBody = parsedBodyResult.value as WakeCallbackBody | undefined
634
661
  const isClaimRequest =
635
662
  requestBody?.wakeId !== undefined || requestBody?.wake_id !== undefined
636
663
  const isDoneRequest = requestBody?.done === true
@@ -653,7 +680,7 @@ async function callbackForward(
653
680
  return json(responseBody)
654
681
  }
655
682
 
656
- const upstreamBody = encodeCallbackForwardBody(
683
+ const upstreamBody = encodeWakeCallbackBody(
657
684
  ctx.service,
658
685
  consumerId,
659
686
  requestBody,
@@ -673,7 +700,7 @@ async function callbackForward(
673
700
  if (!token) {
674
701
  return apiError(401, `UNAUTHORIZED`, `Missing claim token`)
675
702
  }
676
- const upstreamPayload = encodeCallbackForwardPayload(
703
+ const upstreamPayload = encodeWakeCallbackPayload(
677
704
  consumerId,
678
705
  requestBody,
679
706
  (stream) => stream.replace(/^\/+/, ``)
@@ -694,7 +721,7 @@ async function callbackForward(
694
721
  } catch (err) {
695
722
  return apiError(
696
723
  502,
697
- `CALLBACK_FORWARD_FAILED`,
724
+ `WAKE_CALLBACK_FAILED`,
698
725
  err instanceof Error ? err.message : String(err)
699
726
  )
700
727
  }
@@ -733,7 +760,7 @@ async function callbackForward(
733
760
  }
734
761
  if (upstream.ok && isDoneRequest && target.primaryStream) {
735
762
  serverLog.info(
736
- `[callback-forward] done received for stream=${target.primaryStream} consumer=${consumerId}`
763
+ `[wake-callback] done received for stream=${target.primaryStream} consumer=${consumerId}`
737
764
  )
738
765
  const stillOwnsClaim = ctx.runtime.claimWriteTokens.owns(
739
766
  ctx.service,
@@ -788,11 +815,11 @@ async function callbackForward(
788
815
  )
789
816
  await ctx.entityBridgeManager.onEntityChanged(entity.url)
790
817
  serverLog.info(
791
- `[callback-forward] status updated after done for ${entity.url}`
818
+ `[wake-callback] status updated after done for ${entity.url}`
792
819
  )
793
820
  } else if (!entity) {
794
821
  serverLog.warn(
795
- `[callback-forward] done received but no entity found for stream=${target.primaryStream}`
822
+ `[wake-callback] done received but no entity found for stream=${target.primaryStream}`
796
823
  )
797
824
  }
798
825
 
@@ -806,19 +833,19 @@ async function callbackForward(
806
833
  )
807
834
  } else if (entity) {
808
835
  serverLog.info(
809
- `[callback-forward] done arrived after in-memory token evicted (stream=${target.primaryStream} consumer=${consumerId})`
836
+ `[wake-callback] done arrived after in-memory token evicted (stream=${target.primaryStream} consumer=${consumerId})`
810
837
  )
811
838
  }
812
839
  } else if (requestBody?.done === true) {
813
840
  serverLog.warn(
814
- `[callback-forward] done received but skipped: upstream.ok=${upstream.ok} primaryStream=${
841
+ `[wake-callback] done received but skipped: upstream.ok=${upstream.ok} primaryStream=${
815
842
  target.primaryStream ?? `null`
816
843
  } consumer=${consumerId}`
817
844
  )
818
845
  }
819
846
  } catch (err) {
820
847
  serverLog.error(
821
- `[callback-forward] error processing done for consumer=${consumerId}: ${
848
+ `[wake-callback] error processing done for consumer=${consumerId}: ${
822
849
  err instanceof Error ? err.message : String(err)
823
850
  }`
824
851
  )
@@ -838,21 +865,21 @@ async function mintClaimWriteToken(
838
865
  return ctx.runtime.claimWriteTokens.mint(ctx.service, streamPath, consumerId)
839
866
  }
840
867
 
841
- function encodeCallbackForwardBody(
868
+ function encodeWakeCallbackBody(
842
869
  service: string,
843
870
  consumerId: string,
844
- body: CallbackForwardBody | undefined,
871
+ body: WakeCallbackBody | undefined,
845
872
  routingAdapter: DurableStreamsRoutingAdapter
846
873
  ): Uint8Array {
847
- const payload = encodeCallbackForwardPayload(consumerId, body, (stream) =>
874
+ const payload = encodeWakeCallbackPayload(consumerId, body, (stream) =>
848
875
  routingAdapter.toBackendStreamPath(service, stream)
849
876
  )
850
877
  return new TextEncoder().encode(JSON.stringify(payload))
851
878
  }
852
879
 
853
- function encodeCallbackForwardPayload(
880
+ function encodeWakeCallbackPayload(
854
881
  consumerId: string,
855
- body: CallbackForwardBody | undefined,
882
+ body: WakeCallbackBody | undefined,
856
883
  mapStream: (stream: string) => string
857
884
  ): Record<string, unknown> {
858
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/callback-forward/${encodeURIComponent(input.claim.wake_id)}`
631
+ `/_electric/wake-callbacks/${encodeURIComponent(input.claim.wake_id)}`
632
632
  ),
633
633
  claimToken: input.claim.token,
634
634
  triggerEvent: `message_received`,
@@ -229,7 +229,7 @@ export class WakeRegistry {
229
229
  await this.applyShapeMessage(message)
230
230
  if (
231
231
  !settled &&
232
- `control` in message.headers &&
232
+ isControlMessage(message) &&
233
233
  message.headers.control === `up-to-date`
234
234
  ) {
235
235
  settled = true
@@ -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
- }