@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
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { DEFAULT_STATE_SCHEMAS } from '@electric-ax/agents-runtime'
|
|
@@ -400,6 +400,7 @@ export interface TypedSpawnRequest {
|
|
|
400
400
|
debounceMs?: number
|
|
401
401
|
timeoutMs?: number
|
|
402
402
|
includeResponse?: boolean
|
|
403
|
+
manifestKey?: string
|
|
403
404
|
}
|
|
404
405
|
}
|
|
405
406
|
|
|
@@ -456,4 +457,4 @@ export const ErrCodeForkWaitTimeout = `FORK_WAIT_TIMEOUT`
|
|
|
456
457
|
export const ErrCodeEntityPersistFailed = `ENTITY_PERSIST_FAILED`
|
|
457
458
|
export const ErrCodeAgentUiNotFound = `AGENT_UI_NOT_FOUND`
|
|
458
459
|
export const ErrCodeSubscriptionNotFound = `SUBSCRIPTION_NOT_FOUND`
|
|
459
|
-
export const
|
|
460
|
+
export const ErrCodeWakeCallbackNotFound = `WAKE_CALLBACK_NOT_FOUND`
|
package/src/entity-manager.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getCronStreamPath,
|
|
7
7
|
getSharedStateStreamPath,
|
|
8
8
|
getNextCronFireAt,
|
|
9
|
+
eventSourceSubscriptionManifestKey,
|
|
9
10
|
manifestChildKey,
|
|
10
11
|
manifestSharedStateKey,
|
|
11
12
|
manifestSourceKey,
|
|
@@ -50,6 +51,7 @@ import type { queueAsPromised } from 'fastq'
|
|
|
50
51
|
import type { SchedulerClient } from './scheduler.js'
|
|
51
52
|
import type { WakeEvalResult, WakeRegistry } from './wake-registry.js'
|
|
52
53
|
import type { WakeMessage } from '@electric-ax/agents-runtime'
|
|
54
|
+
import type { EventSourceSubscription } from '@electric-ax/agents-runtime'
|
|
53
55
|
import type { PostgresRegistry } from './entity-registry.js'
|
|
54
56
|
import type { SchemaValidator } from './electric-agents/schema-validator.js'
|
|
55
57
|
import type { StreamClient } from './stream-client.js'
|
|
@@ -554,6 +556,7 @@ export class EntityManager {
|
|
|
554
556
|
timeoutMs: req.wake.timeoutMs,
|
|
555
557
|
oneShot: false,
|
|
556
558
|
includeResponse: req.wake.includeResponse,
|
|
559
|
+
manifestKey: req.wake.manifestKey,
|
|
557
560
|
})
|
|
558
561
|
}
|
|
559
562
|
|
|
@@ -1672,7 +1675,7 @@ export class EntityManager {
|
|
|
1672
1675
|
// ==========================================================================
|
|
1673
1676
|
|
|
1674
1677
|
/**
|
|
1675
|
-
* Deliver a message to an entity's main stream, with optional
|
|
1678
|
+
* Deliver a message to an entity's main stream, with optional inbox schema
|
|
1676
1679
|
* validation.
|
|
1677
1680
|
*/
|
|
1678
1681
|
async send(
|
|
@@ -1885,7 +1888,7 @@ export class EntityManager {
|
|
|
1885
1888
|
return updated
|
|
1886
1889
|
}
|
|
1887
1890
|
|
|
1888
|
-
async
|
|
1891
|
+
async deleteTag(
|
|
1889
1892
|
entityUrl: string,
|
|
1890
1893
|
key: string,
|
|
1891
1894
|
token: string
|
|
@@ -1927,7 +1930,7 @@ export class EntityManager {
|
|
|
1927
1930
|
return updated
|
|
1928
1931
|
}
|
|
1929
1932
|
|
|
1930
|
-
async
|
|
1933
|
+
async ensureEntitiesMembershipStream(tags: Record<string, string>): Promise<{
|
|
1931
1934
|
sourceRef: string
|
|
1932
1935
|
streamUrl: string
|
|
1933
1936
|
}> {
|
|
@@ -2162,6 +2165,67 @@ export class EntityManager {
|
|
|
2162
2165
|
return { txid }
|
|
2163
2166
|
}
|
|
2164
2167
|
|
|
2168
|
+
async upsertEventSourceSubscription(
|
|
2169
|
+
entityUrl: string,
|
|
2170
|
+
req: {
|
|
2171
|
+
subscription: EventSourceSubscription
|
|
2172
|
+
manifest: Record<string, unknown>
|
|
2173
|
+
}
|
|
2174
|
+
): Promise<{ txid: string; subscription: EventSourceSubscription }> {
|
|
2175
|
+
const manifestKey = req.subscription.manifestKey
|
|
2176
|
+
const txid = randomUUID()
|
|
2177
|
+
await this.writeManifestEntry(
|
|
2178
|
+
entityUrl,
|
|
2179
|
+
manifestKey,
|
|
2180
|
+
`upsert`,
|
|
2181
|
+
req.manifest,
|
|
2182
|
+
{
|
|
2183
|
+
txid,
|
|
2184
|
+
}
|
|
2185
|
+
)
|
|
2186
|
+
|
|
2187
|
+
// The manifest is the durable source of truth. Register side effects after
|
|
2188
|
+
// it is appended so failures can be repaired by manifest replay.
|
|
2189
|
+
await this.wakeRegistry.unregisterByManifestKey(
|
|
2190
|
+
entityUrl,
|
|
2191
|
+
manifestKey,
|
|
2192
|
+
this.tenantId
|
|
2193
|
+
)
|
|
2194
|
+
await this.wakeRegistry.register({
|
|
2195
|
+
tenantId: this.tenantId,
|
|
2196
|
+
subscriberUrl: entityUrl,
|
|
2197
|
+
sourceUrl: req.subscription.sourceUrl,
|
|
2198
|
+
condition: {
|
|
2199
|
+
on: `change`,
|
|
2200
|
+
collections: [`webhook_event`],
|
|
2201
|
+
ops: [`insert`],
|
|
2202
|
+
},
|
|
2203
|
+
oneShot: false,
|
|
2204
|
+
manifestKey,
|
|
2205
|
+
})
|
|
2206
|
+
|
|
2207
|
+
return { txid, subscription: req.subscription }
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
async deleteEventSourceSubscription(
|
|
2211
|
+
entityUrl: string,
|
|
2212
|
+
req: { id: string }
|
|
2213
|
+
): Promise<{ txid: string }> {
|
|
2214
|
+
const manifestKey = eventSourceSubscriptionManifestKey(req.id)
|
|
2215
|
+
const txid = randomUUID()
|
|
2216
|
+
await this.writeManifestEntry(entityUrl, manifestKey, `delete`, undefined, {
|
|
2217
|
+
txid,
|
|
2218
|
+
})
|
|
2219
|
+
|
|
2220
|
+
await this.wakeRegistry.unregisterByManifestKey(
|
|
2221
|
+
entityUrl,
|
|
2222
|
+
manifestKey,
|
|
2223
|
+
this.tenantId
|
|
2224
|
+
)
|
|
2225
|
+
|
|
2226
|
+
return { txid }
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2165
2229
|
// ==========================================================================
|
|
2166
2230
|
// Wake Evaluation
|
|
2167
2231
|
// ==========================================================================
|
|
@@ -2678,7 +2742,7 @@ export class EntityManager {
|
|
|
2678
2742
|
// ==========================================================================
|
|
2679
2743
|
|
|
2680
2744
|
/**
|
|
2681
|
-
* Add new
|
|
2745
|
+
* Add new inbox/state schema keys to an entity type directly in Postgres.
|
|
2682
2746
|
*/
|
|
2683
2747
|
async amendSchemas(
|
|
2684
2748
|
typeName: string,
|
|
@@ -2767,7 +2831,7 @@ export class EntityManager {
|
|
|
2767
2831
|
|
|
2768
2832
|
/**
|
|
2769
2833
|
* Enrich webhook payload with entity context.
|
|
2770
|
-
* Called by ElectricAgentsServer during webhook
|
|
2834
|
+
* Called by ElectricAgentsServer during subscription webhook dispatch to inject entity context.
|
|
2771
2835
|
*/
|
|
2772
2836
|
async enrichPayload(
|
|
2773
2837
|
payload: Record<string, unknown>,
|
package/src/index.ts
CHANGED
|
@@ -53,10 +53,18 @@ export type {
|
|
|
53
53
|
SignalResponse,
|
|
54
54
|
TypedSpawnRequest,
|
|
55
55
|
} from './electric-agents-types.js'
|
|
56
|
+
export type {
|
|
57
|
+
EventSourceBucket,
|
|
58
|
+
EventSourceContract,
|
|
59
|
+
EventSourceFilter,
|
|
60
|
+
EventSourceSubscription,
|
|
61
|
+
EventSourceSubscriptionInput,
|
|
62
|
+
SubscriptionLifetime,
|
|
63
|
+
} from '@electric-ax/agents-runtime'
|
|
56
64
|
export type { Principal, PrincipalKind } from './principal.js'
|
|
57
65
|
export { globalRouter } from './routing/global-router.js'
|
|
58
66
|
export type { GlobalRoutes } from './routing/global-router.js'
|
|
59
|
-
export type { TenantContext } from './routing/context.js'
|
|
67
|
+
export type { EventSourceCatalog, TenantContext } from './routing/context.js'
|
|
60
68
|
export {
|
|
61
69
|
streamRootDurableStreamsRoutingAdapter,
|
|
62
70
|
pathPrefixedSingleTenantDurableStreamsRoutingAdapter,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getCronStreamPathFromSpec,
|
|
3
|
+
getWebhookStreamPath,
|
|
3
4
|
getSharedStateStreamPath,
|
|
4
5
|
resolveCronScheduleSpec,
|
|
5
6
|
} from '@electric-ax/agents-runtime'
|
|
@@ -56,6 +57,16 @@ export function extractManifestSourceUrl(
|
|
|
56
57
|
: undefined
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
if (manifest.sourceType === `webhook`) {
|
|
61
|
+
if (typeof config?.streamUrl === `string`) return config.streamUrl
|
|
62
|
+
if (typeof config?.endpointKey === `string`) {
|
|
63
|
+
return getWebhookStreamPath(
|
|
64
|
+
config.endpointKey,
|
|
65
|
+
typeof config.bucket === `string` ? config.bucket : undefined
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
return undefined
|
|
60
71
|
}
|
|
61
72
|
|
package/src/routing/context.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Agent } from 'undici'
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
EventSourceContract,
|
|
4
|
+
WebhookSignatureVerifierConfig,
|
|
5
|
+
} from '@electric-ax/agents-runtime'
|
|
3
6
|
import type { DrizzleDB } from '../db/index.js'
|
|
4
7
|
import type { EntityBridgeCoordinator } from '../entity-bridge-manager.js'
|
|
5
8
|
import type { EntityManager } from '../entity-manager.js'
|
|
@@ -10,6 +13,18 @@ import type { Principal } from '../principal.js'
|
|
|
10
13
|
import type { DurableStreamsBearerProvider } from '../stream-client.js'
|
|
11
14
|
import type { WebhookSigner } from '../webhook-signing.js'
|
|
12
15
|
|
|
16
|
+
export interface EventSourceCatalog {
|
|
17
|
+
listEventSources: () =>
|
|
18
|
+
| Array<EventSourceContract>
|
|
19
|
+
| Promise<Array<EventSourceContract>>
|
|
20
|
+
getEventSource: (
|
|
21
|
+
sourceKey: string
|
|
22
|
+
) =>
|
|
23
|
+
| EventSourceContract
|
|
24
|
+
| undefined
|
|
25
|
+
| Promise<EventSourceContract | undefined>
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
/**
|
|
14
29
|
* Per-request tenant context passed through every router and handler.
|
|
15
30
|
*
|
|
@@ -38,5 +53,7 @@ export interface TenantContext {
|
|
|
38
53
|
streamClient: StreamClient
|
|
39
54
|
runtime: ElectricAgentsTenantRuntime
|
|
40
55
|
entityBridgeManager: EntityBridgeCoordinator
|
|
56
|
+
eventSources?: EventSourceCatalog
|
|
57
|
+
ensureEventSourceWakeSource?: (sourceUrl: string) => Promise<void> | void
|
|
41
58
|
isShuttingDown: () => boolean
|
|
42
59
|
}
|
|
@@ -326,7 +326,7 @@ async function linkStreamToTargetSubscription(
|
|
|
326
326
|
}
|
|
327
327
|
const forwardUrl = appendPathToUrl(
|
|
328
328
|
ctx.publicUrl,
|
|
329
|
-
`/_electric/
|
|
329
|
+
`/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`
|
|
330
330
|
)
|
|
331
331
|
await ensureSubscriptionIncludesStream(
|
|
332
332
|
ctx,
|
|
@@ -332,7 +332,7 @@ async function rewriteSubscriptionRequestBody(
|
|
|
332
332
|
targetWebhookUrl = rewriteLoopbackWebhookUrl(payload.webhook.url) ?? null
|
|
333
333
|
payload.webhook.url = appendPathToUrl(
|
|
334
334
|
ctx.publicUrl,
|
|
335
|
-
`/_electric/
|
|
335
|
+
`/_electric/subscription-webhooks/${encodeURIComponent(subscriptionId)}`
|
|
336
336
|
)
|
|
337
337
|
}
|
|
338
338
|
|
|
@@ -13,9 +13,7 @@ export interface DurableStreamsRoutingAdapter {
|
|
|
13
13
|
|
|
14
14
|
function appendSearch(target: URL, source: URL): URL {
|
|
15
15
|
source.searchParams.forEach((value, key) => {
|
|
16
|
-
|
|
17
|
-
target.searchParams.append(key, value)
|
|
18
|
-
}
|
|
16
|
+
target.searchParams.append(key, value)
|
|
19
17
|
})
|
|
20
18
|
return target
|
|
21
19
|
}
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Type, type Static } from '@sinclair/typebox'
|
|
6
|
+
import {
|
|
7
|
+
buildEventSourceManifestEntry,
|
|
8
|
+
resolveEventSourceSubscription,
|
|
9
|
+
} from '@electric-ax/agents-runtime'
|
|
6
10
|
import { Router, json, status } from 'itty-router'
|
|
7
11
|
import { apiError } from '../electric-agents-http.js'
|
|
8
12
|
import { parsePrincipalKey, principalUrl } from '../principal.js'
|
|
@@ -26,6 +30,7 @@ import type { ElectricAgentsEntity } from '../electric-agents-types.js'
|
|
|
26
30
|
import type { JsonRouteRequest } from './schema.js'
|
|
27
31
|
import type { RouterType } from 'itty-router'
|
|
28
32
|
import type { TenantContext } from './context.js'
|
|
33
|
+
import type { EventSourceSubscriptionInput } from '@electric-ax/agents-runtime'
|
|
29
34
|
|
|
30
35
|
interface AgentsRouteRequest extends JsonRouteRequest {
|
|
31
36
|
entityRoute?: ExistingEntityRoute
|
|
@@ -84,6 +89,7 @@ const spawnBodySchema = Type.Object({
|
|
|
84
89
|
debounceMs: Type.Optional(Type.Number()),
|
|
85
90
|
timeoutMs: Type.Optional(Type.Number()),
|
|
86
91
|
includeResponse: Type.Optional(Type.Boolean()),
|
|
92
|
+
manifestKey: Type.Optional(Type.String()),
|
|
87
93
|
})
|
|
88
94
|
),
|
|
89
95
|
})
|
|
@@ -169,8 +175,22 @@ const scheduleBodySchema = Type.Union([
|
|
|
169
175
|
}),
|
|
170
176
|
])
|
|
171
177
|
|
|
172
|
-
const
|
|
173
|
-
|
|
178
|
+
const subscriptionLifetimeSchema = Type.Union([
|
|
179
|
+
Type.Object({ kind: Type.Literal(`until_entity_stopped`) }),
|
|
180
|
+
Type.Object({
|
|
181
|
+
kind: Type.Literal(`expires_at`),
|
|
182
|
+
at: Type.String(),
|
|
183
|
+
}),
|
|
184
|
+
Type.Object({ kind: Type.Literal(`manual`) }),
|
|
185
|
+
])
|
|
186
|
+
|
|
187
|
+
const eventSourceSubscriptionBodySchema = Type.Object({
|
|
188
|
+
sourceKey: Type.String(),
|
|
189
|
+
bucketKey: Type.Optional(Type.String()),
|
|
190
|
+
params: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
191
|
+
filterKey: Type.Optional(Type.String()),
|
|
192
|
+
lifetime: Type.Optional(subscriptionLifetimeSchema),
|
|
193
|
+
reason: Type.Optional(Type.String()),
|
|
174
194
|
})
|
|
175
195
|
|
|
176
196
|
type SpawnBody = Static<typeof spawnBodySchema>
|
|
@@ -180,7 +200,9 @@ type ForkBody = Static<typeof forkBodySchema>
|
|
|
180
200
|
type SetTagBody = Static<typeof setTagBodySchema>
|
|
181
201
|
type SignalBody = Static<typeof signalBodySchema>
|
|
182
202
|
type ScheduleBody = Static<typeof scheduleBodySchema>
|
|
183
|
-
type
|
|
203
|
+
type EventSourceSubscriptionBody = Static<
|
|
204
|
+
typeof eventSourceSubscriptionBodySchema
|
|
205
|
+
>
|
|
184
206
|
|
|
185
207
|
export const entitiesRouter: EntitiesRoutes = Router<
|
|
186
208
|
AgentsRouteRequest,
|
|
@@ -191,11 +213,6 @@ export const entitiesRouter: EntitiesRoutes = Router<
|
|
|
191
213
|
})
|
|
192
214
|
|
|
193
215
|
entitiesRouter.get(`/`, listEntities)
|
|
194
|
-
entitiesRouter.post(
|
|
195
|
-
`/register`,
|
|
196
|
-
withSchema(entitiesRegisterBodySchema),
|
|
197
|
-
registerEntitiesSource
|
|
198
|
-
)
|
|
199
216
|
entitiesRouter.put(
|
|
200
217
|
`/:type/:instanceId`,
|
|
201
218
|
withSpawnableEntityType,
|
|
@@ -243,7 +260,7 @@ entitiesRouter.post(
|
|
|
243
260
|
entitiesRouter.delete(
|
|
244
261
|
`/:type/:instanceId/tags/:tagKey`,
|
|
245
262
|
withExistingEntity,
|
|
246
|
-
|
|
263
|
+
deleteTag
|
|
247
264
|
)
|
|
248
265
|
entitiesRouter.put(
|
|
249
266
|
`/:type/:instanceId/schedules/:scheduleId`,
|
|
@@ -256,6 +273,17 @@ entitiesRouter.delete(
|
|
|
256
273
|
withExistingEntity,
|
|
257
274
|
deleteSchedule
|
|
258
275
|
)
|
|
276
|
+
entitiesRouter.put(
|
|
277
|
+
`/:type/:instanceId/event-source-subscriptions/:subscriptionId`,
|
|
278
|
+
withExistingEntity,
|
|
279
|
+
withSchema(eventSourceSubscriptionBodySchema),
|
|
280
|
+
upsertEventSourceSubscription
|
|
281
|
+
)
|
|
282
|
+
entitiesRouter.delete(
|
|
283
|
+
`/:type/:instanceId/event-source-subscriptions/:subscriptionId`,
|
|
284
|
+
withExistingEntity,
|
|
285
|
+
deleteEventSourceSubscription
|
|
286
|
+
)
|
|
259
287
|
|
|
260
288
|
function entityUrlFromSegments(
|
|
261
289
|
type: string,
|
|
@@ -391,17 +419,6 @@ async function listEntities(
|
|
|
391
419
|
return json(entities.map((entity) => toPublicEntity(entity)))
|
|
392
420
|
}
|
|
393
421
|
|
|
394
|
-
async function registerEntitiesSource(
|
|
395
|
-
request: AgentsRouteRequest,
|
|
396
|
-
ctx: TenantContext
|
|
397
|
-
): Promise<Response> {
|
|
398
|
-
const parsed = routeBody<EntitiesRegisterBody>(request)
|
|
399
|
-
const result = await ctx.entityManager.registerEntitiesSource(
|
|
400
|
-
parsed.tags ?? {}
|
|
401
|
-
)
|
|
402
|
-
return json(result)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
422
|
async function upsertSchedule(
|
|
406
423
|
request: AgentsRouteRequest,
|
|
407
424
|
ctx: TenantContext
|
|
@@ -467,13 +484,105 @@ async function deleteSchedule(
|
|
|
467
484
|
return json(result)
|
|
468
485
|
}
|
|
469
486
|
|
|
487
|
+
async function upsertEventSourceSubscription(
|
|
488
|
+
request: AgentsRouteRequest,
|
|
489
|
+
ctx: TenantContext
|
|
490
|
+
): Promise<Response> {
|
|
491
|
+
const principalMutationError = rejectPrincipalEntityMutation(
|
|
492
|
+
request,
|
|
493
|
+
`subscribed to event sources`
|
|
494
|
+
)
|
|
495
|
+
if (principalMutationError) return principalMutationError
|
|
496
|
+
|
|
497
|
+
const catalog = ctx.eventSources
|
|
498
|
+
if (!catalog) {
|
|
499
|
+
return apiError(
|
|
500
|
+
404,
|
|
501
|
+
ErrCodeNotFound,
|
|
502
|
+
`No event source catalog is configured`
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const { entityUrl } = requireExistingEntityRoute(request)
|
|
507
|
+
const parsed = routeBody<EventSourceSubscriptionBody>(request)
|
|
508
|
+
const source = await catalog.getEventSource(parsed.sourceKey)
|
|
509
|
+
if (!source) {
|
|
510
|
+
return apiError(
|
|
511
|
+
404,
|
|
512
|
+
ErrCodeNotFound,
|
|
513
|
+
`Event source "${parsed.sourceKey}" not found`
|
|
514
|
+
)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (parsed.lifetime?.kind === `expires_at`) {
|
|
518
|
+
const expiresAt = new Date(parsed.lifetime.at)
|
|
519
|
+
if (Number.isNaN(expiresAt.getTime())) {
|
|
520
|
+
return apiError(
|
|
521
|
+
400,
|
|
522
|
+
ErrCodeInvalidRequest,
|
|
523
|
+
`Invalid expires_at lifetime timestamp`
|
|
524
|
+
)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let resolved: ReturnType<typeof resolveEventSourceSubscription>
|
|
529
|
+
try {
|
|
530
|
+
resolved = resolveEventSourceSubscription({
|
|
531
|
+
contract: source,
|
|
532
|
+
entityUrl,
|
|
533
|
+
request: {
|
|
534
|
+
...(parsed as EventSourceSubscriptionInput),
|
|
535
|
+
id: decodeURIComponent(request.params.subscriptionId),
|
|
536
|
+
},
|
|
537
|
+
createdBy: `tool`,
|
|
538
|
+
})
|
|
539
|
+
} catch (error) {
|
|
540
|
+
return apiError(
|
|
541
|
+
400,
|
|
542
|
+
ErrCodeInvalidRequest,
|
|
543
|
+
error instanceof Error ? error.message : String(error)
|
|
544
|
+
)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
await ctx.ensureEventSourceWakeSource?.(resolved.subscription.sourceUrl)
|
|
548
|
+
|
|
549
|
+
const result = await ctx.entityManager.upsertEventSourceSubscription(
|
|
550
|
+
entityUrl,
|
|
551
|
+
{
|
|
552
|
+
subscription: resolved.subscription,
|
|
553
|
+
manifest: buildEventSourceManifestEntry(resolved),
|
|
554
|
+
}
|
|
555
|
+
)
|
|
556
|
+
return json(result)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async function deleteEventSourceSubscription(
|
|
560
|
+
request: AgentsRouteRequest,
|
|
561
|
+
ctx: TenantContext
|
|
562
|
+
): Promise<Response> {
|
|
563
|
+
const principalMutationError = rejectPrincipalEntityMutation(
|
|
564
|
+
request,
|
|
565
|
+
`unsubscribed from event sources`
|
|
566
|
+
)
|
|
567
|
+
if (principalMutationError) return principalMutationError
|
|
568
|
+
|
|
569
|
+
const { entityUrl } = requireExistingEntityRoute(request)
|
|
570
|
+
const result = await ctx.entityManager.deleteEventSourceSubscription(
|
|
571
|
+
entityUrl,
|
|
572
|
+
{
|
|
573
|
+
id: decodeURIComponent(request.params.subscriptionId),
|
|
574
|
+
}
|
|
575
|
+
)
|
|
576
|
+
return json(result)
|
|
577
|
+
}
|
|
578
|
+
|
|
470
579
|
async function setTag(
|
|
471
580
|
request: AgentsRouteRequest,
|
|
472
581
|
ctx: TenantContext
|
|
473
582
|
): Promise<Response> {
|
|
474
583
|
const principalMutationError = rejectPrincipalEntityMutation(
|
|
475
584
|
request,
|
|
476
|
-
`
|
|
585
|
+
`tag updated`
|
|
477
586
|
)
|
|
478
587
|
if (principalMutationError) return principalMutationError
|
|
479
588
|
|
|
@@ -489,19 +598,19 @@ async function setTag(
|
|
|
489
598
|
return json(toPublicEntity(updated))
|
|
490
599
|
}
|
|
491
600
|
|
|
492
|
-
async function
|
|
601
|
+
async function deleteTag(
|
|
493
602
|
request: AgentsRouteRequest,
|
|
494
603
|
ctx: TenantContext
|
|
495
604
|
): Promise<Response> {
|
|
496
605
|
const principalMutationError = rejectPrincipalEntityMutation(
|
|
497
606
|
request,
|
|
498
|
-
`
|
|
607
|
+
`tag deleted`
|
|
499
608
|
)
|
|
500
609
|
if (principalMutationError) return principalMutationError
|
|
501
610
|
|
|
502
611
|
const { entityUrl } = requireExistingEntityRoute(request)
|
|
503
612
|
const token = writeTokenFromRequest(request)
|
|
504
|
-
const updated = await ctx.entityManager.
|
|
613
|
+
const updated = await ctx.entityManager.deleteTag(
|
|
505
614
|
entityUrl,
|
|
506
615
|
decodeURIComponent(request.params.tagKey),
|
|
507
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
185
|
-
state_schemas: parsed.state_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
|
|
208
|
-
state_schemas: parsed.state_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
|
}
|
package/src/routing/hooks.ts
CHANGED
|
@@ -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/
|
|
124
|
+
if (!path.startsWith(`/_electric/subscription-webhooks/`)) return undefined
|
|
125
125
|
return apiError(503, `SERVER_STOPPING`, `Server is shutting down`)
|
|
126
126
|
}
|
|
127
127
|
|