@electric-ax/agents-server 0.4.15 → 0.4.17
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 +1230 -235
- package/dist/index.cjs +1233 -229
- package/dist/index.d.cts +1319 -318
- package/dist/index.d.ts +1319 -318
- package/dist/index.js +1235 -231
- package/drizzle/0011_entity_permissions.sql +100 -0
- package/drizzle/0012_horton_user_manage_permission.sql +25 -0
- package/drizzle/0013_worker_user_manage_permission.sql +25 -0
- package/drizzle/0014_entity_type_slash_commands.sql +1 -0
- package/drizzle/meta/_journal.json +28 -0
- package/package.json +7 -7
- package/src/db/schema.ts +199 -0
- package/src/electric-agents-types.ts +80 -2
- package/src/entity-bridge-manager.ts +57 -6
- package/src/entity-manager.ts +124 -61
- package/src/entity-projector.ts +76 -17
- package/src/entity-registry.ts +615 -5
- package/src/index.ts +11 -0
- package/src/permissions.ts +239 -0
- package/src/routing/context.ts +2 -0
- package/src/routing/durable-streams-router.ts +125 -4
- package/src/routing/electric-proxy-router.ts +4 -0
- package/src/routing/entities-router.ts +347 -20
- package/src/routing/entity-types-router.ts +267 -15
- package/src/routing/hooks.ts +1 -0
- package/src/routing/observations-router.ts +2 -1
- package/src/runtime.ts +34 -0
- package/src/scheduler.ts +2 -0
- package/src/server.ts +5 -0
- package/src/utils/server-utils.ts +192 -12
- package/src/wake-registry.ts +8 -0
|
@@ -8,22 +8,27 @@ import { dispatchPolicySchema } from '../dispatch-policy-schema.js'
|
|
|
8
8
|
import { ElectricAgentsError } from '../entity-manager.js'
|
|
9
9
|
import {
|
|
10
10
|
ErrCodeNotFound,
|
|
11
|
+
ErrCodeInvalidRequest,
|
|
12
|
+
ErrCodeUnauthorized,
|
|
11
13
|
ErrCodeServeEndpointNameMismatch,
|
|
12
14
|
ErrCodeServeEndpointUnreachable,
|
|
13
15
|
} from '../electric-agents-types.js'
|
|
14
16
|
import { apiError } from '../electric-agents-http.js'
|
|
15
17
|
import { routeBody, withSchema } from './schema.js'
|
|
16
18
|
import { rewriteLoopbackWebhookUrl } from '../utils/webhook-url.js'
|
|
19
|
+
import { canAccessEntityType, canRegisterEntityType } from '../permissions.js'
|
|
17
20
|
import type {
|
|
18
21
|
ElectricAgentsEntityType,
|
|
19
22
|
RegisterEntityTypeRequest,
|
|
23
|
+
EntityTypePermissionGrantInput,
|
|
20
24
|
} from '../electric-agents-types.js'
|
|
21
25
|
import type { JsonRouteRequest } from './schema.js'
|
|
22
26
|
import type { RouterType } from 'itty-router'
|
|
23
27
|
import type { TenantContext } from './context.js'
|
|
24
28
|
|
|
25
|
-
export interface ElectricAgentsEntityTypeRouteRequest
|
|
26
|
-
|
|
29
|
+
export interface ElectricAgentsEntityTypeRouteRequest extends JsonRouteRequest {
|
|
30
|
+
entityTypeRoute?: { entityType: ElectricAgentsEntityType }
|
|
31
|
+
}
|
|
27
32
|
|
|
28
33
|
type EntityTypeRouteArgs = [TenantContext]
|
|
29
34
|
type EntityTypeRouteResult = Response | undefined
|
|
@@ -40,6 +45,40 @@ type PublicEntityTypeResponse = ElectricAgentsEntityType & {
|
|
|
40
45
|
|
|
41
46
|
const jsonObjectSchema = Type.Record(Type.String(), Type.Unknown())
|
|
42
47
|
const schemaMapSchema = Type.Record(Type.String(), jsonObjectSchema)
|
|
48
|
+
const slashCommandArgumentSchema = Type.Object(
|
|
49
|
+
{
|
|
50
|
+
name: Type.String(),
|
|
51
|
+
type: Type.Union([
|
|
52
|
+
Type.Literal(`string`),
|
|
53
|
+
Type.Literal(`number`),
|
|
54
|
+
Type.Literal(`boolean`),
|
|
55
|
+
]),
|
|
56
|
+
required: Type.Optional(Type.Boolean()),
|
|
57
|
+
description: Type.Optional(Type.String()),
|
|
58
|
+
},
|
|
59
|
+
{ additionalProperties: false }
|
|
60
|
+
)
|
|
61
|
+
const slashCommandSchema = Type.Object(
|
|
62
|
+
{
|
|
63
|
+
name: Type.String(),
|
|
64
|
+
description: Type.Optional(Type.String()),
|
|
65
|
+
arguments: Type.Optional(Type.Array(slashCommandArgumentSchema)),
|
|
66
|
+
},
|
|
67
|
+
{ additionalProperties: false }
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const typePermissionGrantInputSchema = Type.Object(
|
|
71
|
+
{
|
|
72
|
+
subject_kind: Type.Union([
|
|
73
|
+
Type.Literal(`principal`),
|
|
74
|
+
Type.Literal(`principal_kind`),
|
|
75
|
+
]),
|
|
76
|
+
subject_value: Type.String(),
|
|
77
|
+
permission: Type.Union([Type.Literal(`spawn`), Type.Literal(`manage`)]),
|
|
78
|
+
expires_at: Type.Optional(Type.String()),
|
|
79
|
+
},
|
|
80
|
+
{ additionalProperties: false }
|
|
81
|
+
)
|
|
43
82
|
|
|
44
83
|
const registerEntityTypeBodySchema = Type.Object(
|
|
45
84
|
{
|
|
@@ -48,8 +87,12 @@ const registerEntityTypeBodySchema = Type.Object(
|
|
|
48
87
|
creation_schema: Type.Optional(jsonObjectSchema),
|
|
49
88
|
inbox_schemas: Type.Optional(schemaMapSchema),
|
|
50
89
|
state_schemas: Type.Optional(schemaMapSchema),
|
|
90
|
+
slash_commands: Type.Optional(Type.Array(slashCommandSchema)),
|
|
51
91
|
serve_endpoint: Type.Optional(Type.String()),
|
|
52
92
|
default_dispatch_policy: Type.Optional(dispatchPolicySchema),
|
|
93
|
+
permission_grants: Type.Optional(
|
|
94
|
+
Type.Array(typePermissionGrantInputSchema)
|
|
95
|
+
),
|
|
53
96
|
},
|
|
54
97
|
{ additionalProperties: false }
|
|
55
98
|
)
|
|
@@ -66,6 +109,7 @@ type RegisterEntityTypeBody = Static<typeof registerEntityTypeBodySchema>
|
|
|
66
109
|
type AmendEntityTypeSchemasBody = Static<
|
|
67
110
|
typeof amendEntityTypeSchemasBodySchema
|
|
68
111
|
>
|
|
112
|
+
type TypePermissionGrantInput = EntityTypePermissionGrantInput
|
|
69
113
|
|
|
70
114
|
export const entityTypesRouter: ElectricAgentsEntityTypeRoutes = Router<
|
|
71
115
|
ElectricAgentsEntityTypeRouteRequest,
|
|
@@ -79,15 +123,47 @@ entityTypesRouter.get(`/`, listEntityTypes)
|
|
|
79
123
|
entityTypesRouter.post(
|
|
80
124
|
`/`,
|
|
81
125
|
withSchema(registerEntityTypeBodySchema),
|
|
126
|
+
withEntityTypeRegistrationPermission,
|
|
82
127
|
registerEntityType
|
|
83
128
|
)
|
|
84
129
|
entityTypesRouter.patch(
|
|
85
130
|
`/:name/schemas`,
|
|
131
|
+
withExistingEntityType,
|
|
132
|
+
withEntityTypeManagePermission,
|
|
86
133
|
withSchema(amendEntityTypeSchemasBodySchema),
|
|
87
134
|
amendSchemas
|
|
88
135
|
)
|
|
89
|
-
entityTypesRouter.get(
|
|
90
|
-
|
|
136
|
+
entityTypesRouter.get(
|
|
137
|
+
`/:name`,
|
|
138
|
+
withExistingEntityType,
|
|
139
|
+
withEntityTypeSpawnPermission,
|
|
140
|
+
getEntityType
|
|
141
|
+
)
|
|
142
|
+
entityTypesRouter.delete(
|
|
143
|
+
`/:name`,
|
|
144
|
+
withExistingEntityType,
|
|
145
|
+
withEntityTypeManagePermission,
|
|
146
|
+
deleteEntityType
|
|
147
|
+
)
|
|
148
|
+
entityTypesRouter.get(
|
|
149
|
+
`/:name/grants`,
|
|
150
|
+
withExistingEntityType,
|
|
151
|
+
withEntityTypeManagePermission,
|
|
152
|
+
listTypePermissionGrants
|
|
153
|
+
)
|
|
154
|
+
entityTypesRouter.post(
|
|
155
|
+
`/:name/grants`,
|
|
156
|
+
withExistingEntityType,
|
|
157
|
+
withSchema(typePermissionGrantInputSchema),
|
|
158
|
+
withEntityTypeManagePermission,
|
|
159
|
+
createTypePermissionGrant
|
|
160
|
+
)
|
|
161
|
+
entityTypesRouter.delete(
|
|
162
|
+
`/:name/grants/:grantId`,
|
|
163
|
+
withExistingEntityType,
|
|
164
|
+
withEntityTypeManagePermission,
|
|
165
|
+
deleteTypePermissionGrant
|
|
166
|
+
)
|
|
91
167
|
|
|
92
168
|
async function registerEntityType(
|
|
93
169
|
request: ElectricAgentsEntityTypeRouteRequest,
|
|
@@ -105,6 +181,7 @@ async function registerEntityType(
|
|
|
105
181
|
}
|
|
106
182
|
|
|
107
183
|
const entityType = await ctx.entityManager.registerEntityType(normalized)
|
|
184
|
+
await applyRegistrationPermissionGrants(ctx, entityType.name, normalized)
|
|
108
185
|
return json(toPublicEntityType(entityType), { status: 201 })
|
|
109
186
|
}
|
|
110
187
|
|
|
@@ -113,7 +190,102 @@ async function listEntityTypes(
|
|
|
113
190
|
ctx: TenantContext
|
|
114
191
|
): Promise<EntityTypeRouteResult> {
|
|
115
192
|
const entityTypes = await ctx.entityManager.registry.listEntityTypes()
|
|
116
|
-
|
|
193
|
+
const visible: Array<ElectricAgentsEntityType> = []
|
|
194
|
+
for (const entityType of entityTypes) {
|
|
195
|
+
if (await canAccessEntityType(ctx, entityType, `spawn`)) {
|
|
196
|
+
visible.push(entityType)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return json(visible.map((entityType) => toPublicEntityType(entityType)))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function withExistingEntityType(
|
|
203
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
204
|
+
ctx: TenantContext
|
|
205
|
+
): Promise<EntityTypeRouteResult> {
|
|
206
|
+
const entityType = await ctx.entityManager.registry.getEntityType(
|
|
207
|
+
request.params.name
|
|
208
|
+
)
|
|
209
|
+
if (!entityType) {
|
|
210
|
+
return apiError(404, ErrCodeNotFound, `Entity type not found`)
|
|
211
|
+
}
|
|
212
|
+
request.entityTypeRoute = { entityType }
|
|
213
|
+
return undefined
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function withEntityTypeManagePermission(
|
|
217
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
218
|
+
ctx: TenantContext
|
|
219
|
+
): Promise<EntityTypeRouteResult> {
|
|
220
|
+
const entityType = request.entityTypeRoute?.entityType
|
|
221
|
+
if (!entityType) {
|
|
222
|
+
throw new Error(`entity type middleware did not run`)
|
|
223
|
+
}
|
|
224
|
+
if (
|
|
225
|
+
await canAccessEntityType(ctx, entityType, `manage`, request as Request)
|
|
226
|
+
) {
|
|
227
|
+
return undefined
|
|
228
|
+
}
|
|
229
|
+
return apiError(
|
|
230
|
+
401,
|
|
231
|
+
ErrCodeUnauthorized,
|
|
232
|
+
`Principal is not allowed to manage ${entityType.name}`
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function withEntityTypeSpawnPermission(
|
|
237
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
238
|
+
ctx: TenantContext
|
|
239
|
+
): Promise<EntityTypeRouteResult> {
|
|
240
|
+
const entityType = request.entityTypeRoute?.entityType
|
|
241
|
+
if (!entityType) {
|
|
242
|
+
throw new Error(`entity type middleware did not run`)
|
|
243
|
+
}
|
|
244
|
+
if (await canAccessEntityType(ctx, entityType, `spawn`, request as Request)) {
|
|
245
|
+
return undefined
|
|
246
|
+
}
|
|
247
|
+
return apiError(
|
|
248
|
+
401,
|
|
249
|
+
ErrCodeUnauthorized,
|
|
250
|
+
`Principal is not allowed to spawn ${entityType.name}`
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function withEntityTypeRegistrationPermission(
|
|
255
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
256
|
+
ctx: TenantContext
|
|
257
|
+
): Promise<EntityTypeRouteResult> {
|
|
258
|
+
const parsed = normalizeEntityTypeRequest(
|
|
259
|
+
routeBody<RegisterEntityTypeBody>(request)
|
|
260
|
+
)
|
|
261
|
+
if (!parsed.name) {
|
|
262
|
+
return undefined
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const existing = await ctx.entityManager.registry.getEntityType(parsed.name)
|
|
266
|
+
if (existing) {
|
|
267
|
+
request.entityTypeRoute = { entityType: existing }
|
|
268
|
+
if (
|
|
269
|
+
await canAccessEntityType(ctx, existing, `manage`, request as Request)
|
|
270
|
+
) {
|
|
271
|
+
return undefined
|
|
272
|
+
}
|
|
273
|
+
return apiError(
|
|
274
|
+
401,
|
|
275
|
+
ErrCodeUnauthorized,
|
|
276
|
+
`Principal is not allowed to manage ${existing.name}`
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (await canRegisterEntityType(ctx, parsed, request as Request)) {
|
|
281
|
+
return undefined
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return apiError(
|
|
285
|
+
401,
|
|
286
|
+
ErrCodeUnauthorized,
|
|
287
|
+
`Principal is not allowed to register entity types`
|
|
288
|
+
)
|
|
117
289
|
}
|
|
118
290
|
|
|
119
291
|
async function discoverServeEndpoint(
|
|
@@ -141,10 +313,12 @@ async function discoverServeEndpoint(
|
|
|
141
313
|
}
|
|
142
314
|
|
|
143
315
|
manifest.serve_endpoint = parsed.serve_endpoint
|
|
316
|
+
manifest.permission_grants = parsed.permission_grants
|
|
144
317
|
|
|
145
318
|
const entityType = await ctx.entityManager.registerEntityType(
|
|
146
319
|
normalizeEntityTypeRequest(manifest)
|
|
147
320
|
)
|
|
321
|
+
await applyRegistrationPermissionGrants(ctx, entityType.name, manifest)
|
|
148
322
|
return json(toPublicEntityType(entityType), { status: 201 })
|
|
149
323
|
} catch (err) {
|
|
150
324
|
if (err instanceof ElectricAgentsError) {
|
|
@@ -161,17 +335,9 @@ async function discoverServeEndpoint(
|
|
|
161
335
|
}
|
|
162
336
|
|
|
163
337
|
async function getEntityType(
|
|
164
|
-
request: ElectricAgentsEntityTypeRouteRequest
|
|
165
|
-
ctx: TenantContext
|
|
338
|
+
request: ElectricAgentsEntityTypeRouteRequest
|
|
166
339
|
): Promise<EntityTypeRouteResult> {
|
|
167
|
-
|
|
168
|
-
request.params.name
|
|
169
|
-
)
|
|
170
|
-
if (!entityType) {
|
|
171
|
-
return apiError(404, ErrCodeNotFound, `Entity type not found`)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return json(toPublicEntityType(entityType))
|
|
340
|
+
return json(toPublicEntityType(request.entityTypeRoute!.entityType))
|
|
175
341
|
}
|
|
176
342
|
|
|
177
343
|
async function amendSchemas(
|
|
@@ -195,6 +361,90 @@ async function deleteEntityType(
|
|
|
195
361
|
return status(204)
|
|
196
362
|
}
|
|
197
363
|
|
|
364
|
+
async function listTypePermissionGrants(
|
|
365
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
366
|
+
ctx: TenantContext
|
|
367
|
+
): Promise<EntityTypeRouteResult> {
|
|
368
|
+
const grants =
|
|
369
|
+
await ctx.entityManager.registry.listEntityTypePermissionGrants(
|
|
370
|
+
request.entityTypeRoute!.entityType.name
|
|
371
|
+
)
|
|
372
|
+
return json({ grants })
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function createTypePermissionGrant(
|
|
376
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
377
|
+
ctx: TenantContext
|
|
378
|
+
): Promise<EntityTypeRouteResult> {
|
|
379
|
+
const parsed = routeBody<TypePermissionGrantInput>(request)
|
|
380
|
+
const grant =
|
|
381
|
+
await ctx.entityManager.registry.createEntityTypePermissionGrant({
|
|
382
|
+
entityType: request.entityTypeRoute!.entityType.name,
|
|
383
|
+
permission: parsed.permission,
|
|
384
|
+
subjectKind: parsed.subject_kind,
|
|
385
|
+
subjectValue: parsed.subject_value,
|
|
386
|
+
expiresAt: parseExpiresAt(parsed.expires_at),
|
|
387
|
+
createdBy: ctx.principal.url,
|
|
388
|
+
})
|
|
389
|
+
return json(grant, { status: 201 })
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function deleteTypePermissionGrant(
|
|
393
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
394
|
+
ctx: TenantContext
|
|
395
|
+
): Promise<EntityTypeRouteResult> {
|
|
396
|
+
const deleted =
|
|
397
|
+
await ctx.entityManager.registry.deleteEntityTypePermissionGrant(
|
|
398
|
+
request.entityTypeRoute!.entityType.name,
|
|
399
|
+
parseGrantId(request)
|
|
400
|
+
)
|
|
401
|
+
return deleted
|
|
402
|
+
? status(204)
|
|
403
|
+
: apiError(404, ErrCodeNotFound, `Grant not found`)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function applyRegistrationPermissionGrants(
|
|
407
|
+
ctx: TenantContext,
|
|
408
|
+
entityType: string,
|
|
409
|
+
request: Pick<RegisterEntityTypeRequest, `permission_grants`>
|
|
410
|
+
): Promise<void> {
|
|
411
|
+
for (const grant of request.permission_grants ?? []) {
|
|
412
|
+
await ctx.entityManager.registry.ensureEntityTypePermissionGrant({
|
|
413
|
+
entityType,
|
|
414
|
+
permission: grant.permission,
|
|
415
|
+
subjectKind: grant.subject_kind,
|
|
416
|
+
subjectValue: grant.subject_value,
|
|
417
|
+
expiresAt: parseExpiresAt(grant.expires_at),
|
|
418
|
+
createdBy: ctx.principal.url,
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function parseGrantId(request: ElectricAgentsEntityTypeRouteRequest): number {
|
|
424
|
+
const grantId = Number.parseInt(String(request.params.grantId), 10)
|
|
425
|
+
if (!Number.isSafeInteger(grantId) || grantId <= 0) {
|
|
426
|
+
throw new ElectricAgentsError(
|
|
427
|
+
ErrCodeInvalidRequest,
|
|
428
|
+
`Invalid grant id`,
|
|
429
|
+
400
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
return grantId
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function parseExpiresAt(value: string | undefined): Date | undefined {
|
|
436
|
+
if (value === undefined) return undefined
|
|
437
|
+
const expiresAt = new Date(value)
|
|
438
|
+
if (Number.isNaN(expiresAt.getTime())) {
|
|
439
|
+
throw new ElectricAgentsError(
|
|
440
|
+
ErrCodeInvalidRequest,
|
|
441
|
+
`Invalid expires_at timestamp`,
|
|
442
|
+
400
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
return expiresAt
|
|
446
|
+
}
|
|
447
|
+
|
|
198
448
|
function normalizeEntityTypeRequest(
|
|
199
449
|
parsed: RegisterEntityTypeBody | RegisterEntityTypeRequest
|
|
200
450
|
): RegisterEntityTypeRequest {
|
|
@@ -205,6 +455,7 @@ function normalizeEntityTypeRequest(
|
|
|
205
455
|
creation_schema: parsed.creation_schema,
|
|
206
456
|
inbox_schemas: parsed.inbox_schemas,
|
|
207
457
|
state_schemas: parsed.state_schemas,
|
|
458
|
+
slash_commands: parsed.slash_commands,
|
|
208
459
|
serve_endpoint: serveEndpoint,
|
|
209
460
|
default_dispatch_policy:
|
|
210
461
|
parsed.default_dispatch_policy ??
|
|
@@ -213,6 +464,7 @@ function normalizeEntityTypeRequest(
|
|
|
213
464
|
targets: [{ type: `webhook`, url: serveEndpoint }],
|
|
214
465
|
} as RegisterEntityTypeRequest[`default_dispatch_policy`])
|
|
215
466
|
: undefined),
|
|
467
|
+
permission_grants: parsed.permission_grants,
|
|
216
468
|
}
|
|
217
469
|
}
|
|
218
470
|
|
package/src/routing/hooks.ts
CHANGED
|
@@ -56,7 +56,8 @@ async function ensureEntitiesMembershipStream(
|
|
|
56
56
|
): Promise<Response> {
|
|
57
57
|
const parsed = routeBody<EnsureEntitiesMembershipStreamBody>(request)
|
|
58
58
|
const result = await ctx.entityManager.ensureEntitiesMembershipStream(
|
|
59
|
-
parsed.tags ?? {}
|
|
59
|
+
parsed.tags ?? {},
|
|
60
|
+
ctx.principal
|
|
60
61
|
)
|
|
61
62
|
return json(result)
|
|
62
63
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -316,6 +316,8 @@ export class ElectricAgentsTenantRuntime {
|
|
|
316
316
|
payload.entityUrl,
|
|
317
317
|
{
|
|
318
318
|
from: payload.from,
|
|
319
|
+
from_principal: payload.from_principal,
|
|
320
|
+
from_agent: payload.from_agent,
|
|
319
321
|
payload: payload.payload,
|
|
320
322
|
key: payload.key ?? `scheduled-task-${taskId}`,
|
|
321
323
|
type: payload.type,
|
|
@@ -461,6 +463,7 @@ export class ElectricAgentsTenantRuntime {
|
|
|
461
463
|
{
|
|
462
464
|
entityUrl: targetUrl,
|
|
463
465
|
from: senderUrl,
|
|
466
|
+
from_agent: senderUrl,
|
|
464
467
|
payload: value.payload,
|
|
465
468
|
key: `scheduled-${producerId}`,
|
|
466
469
|
type:
|
|
@@ -499,6 +502,14 @@ export class ElectricAgentsTenantRuntime {
|
|
|
499
502
|
manifestKey,
|
|
500
503
|
sourceRef
|
|
501
504
|
)
|
|
505
|
+
|
|
506
|
+
const sharedStateId =
|
|
507
|
+
operation === `delete` ? undefined : this.extractSharedStateId(value)
|
|
508
|
+
await this.manager.registry.replaceSharedStateLink(
|
|
509
|
+
ownerEntityUrl,
|
|
510
|
+
manifestKey,
|
|
511
|
+
sharedStateId
|
|
512
|
+
)
|
|
502
513
|
}
|
|
503
514
|
|
|
504
515
|
private extractEntitiesSourceRef(
|
|
@@ -514,6 +525,29 @@ export class ElectricAgentsTenantRuntime {
|
|
|
514
525
|
return undefined
|
|
515
526
|
}
|
|
516
527
|
|
|
528
|
+
private extractSharedStateId(
|
|
529
|
+
manifest: Record<string, unknown> | undefined
|
|
530
|
+
): string | undefined {
|
|
531
|
+
if (manifest?.kind === `shared-state` && typeof manifest.id === `string`) {
|
|
532
|
+
return manifest.id
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (manifest?.kind !== `source` || manifest.sourceType !== `db`) {
|
|
536
|
+
return undefined
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (typeof manifest.sourceRef === `string`) {
|
|
540
|
+
return manifest.sourceRef
|
|
541
|
+
}
|
|
542
|
+
const config =
|
|
543
|
+
typeof manifest.config === `object` &&
|
|
544
|
+
manifest.config !== null &&
|
|
545
|
+
!Array.isArray(manifest.config)
|
|
546
|
+
? (manifest.config as Record<string, unknown>)
|
|
547
|
+
: undefined
|
|
548
|
+
return typeof config?.id === `string` ? config.id : undefined
|
|
549
|
+
}
|
|
550
|
+
|
|
517
551
|
private async maybeMarkEntityIdleAfterRunFinished(
|
|
518
552
|
entityUrl: string
|
|
519
553
|
): Promise<void> {
|
package/src/scheduler.ts
CHANGED
package/src/server.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { apiError } from './electric-agents-http.js'
|
|
|
16
16
|
import {
|
|
17
17
|
ErrCodeInvalidRequest,
|
|
18
18
|
ErrCodeUnauthorized,
|
|
19
|
+
type AuthorizeRequest,
|
|
19
20
|
} from './electric-agents-types.js'
|
|
20
21
|
import { ElectricAgentsError } from './entity-manager.js'
|
|
21
22
|
import { serverLog } from './utils/log.js'
|
|
@@ -67,6 +68,7 @@ export interface ElectricAgentsServerOptions {
|
|
|
67
68
|
authenticateRequest?: (
|
|
68
69
|
request: Request
|
|
69
70
|
) => Promise<Principal | null> | Principal | null
|
|
71
|
+
authorizeRequest?: AuthorizeRequest
|
|
70
72
|
allowDevPrincipalFallback?: boolean
|
|
71
73
|
eventSources?: EventSourceCatalog
|
|
72
74
|
ensureEventSourceWakeSource?: (sourceUrl: string) => Promise<void> | void
|
|
@@ -453,6 +455,9 @@ export class ElectricAgentsServer {
|
|
|
453
455
|
this.options.ensureEventSourceWakeSource,
|
|
454
456
|
}
|
|
455
457
|
: {}),
|
|
458
|
+
...(this.options.authorizeRequest
|
|
459
|
+
? { authorizeRequest: this.options.authorizeRequest }
|
|
460
|
+
: {}),
|
|
456
461
|
isShuttingDown: () => this.shuttingDown,
|
|
457
462
|
mockAgent: this.mockAgentBootstrap
|
|
458
463
|
? { runtime: this.mockAgentBootstrap.runtime }
|