@electric-ax/agents-server 0.4.15 → 0.4.16
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 +1176 -232
- package/dist/index.cjs +1179 -226
- package/dist/index.d.cts +1146 -167
- package/dist/index.d.ts +1146 -167
- package/dist/index.js +1181 -228
- 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/meta/_journal.json +21 -0
- package/package.json +7 -7
- package/src/db/schema.ts +198 -0
- package/src/electric-agents-types.ts +76 -2
- package/src/entity-bridge-manager.ts +57 -6
- package/src/entity-manager.ts +78 -60
- package/src/entity-projector.ts +76 -17
- package/src/entity-registry.ts +608 -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 +344 -20
- package/src/routing/entity-types-router.ts +244 -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 +191 -11
- 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
|
|
@@ -41,6 +46,19 @@ type PublicEntityTypeResponse = ElectricAgentsEntityType & {
|
|
|
41
46
|
const jsonObjectSchema = Type.Record(Type.String(), Type.Unknown())
|
|
42
47
|
const schemaMapSchema = Type.Record(Type.String(), jsonObjectSchema)
|
|
43
48
|
|
|
49
|
+
const typePermissionGrantInputSchema = Type.Object(
|
|
50
|
+
{
|
|
51
|
+
subject_kind: Type.Union([
|
|
52
|
+
Type.Literal(`principal`),
|
|
53
|
+
Type.Literal(`principal_kind`),
|
|
54
|
+
]),
|
|
55
|
+
subject_value: Type.String(),
|
|
56
|
+
permission: Type.Union([Type.Literal(`spawn`), Type.Literal(`manage`)]),
|
|
57
|
+
expires_at: Type.Optional(Type.String()),
|
|
58
|
+
},
|
|
59
|
+
{ additionalProperties: false }
|
|
60
|
+
)
|
|
61
|
+
|
|
44
62
|
const registerEntityTypeBodySchema = Type.Object(
|
|
45
63
|
{
|
|
46
64
|
name: Type.Optional(Type.String()),
|
|
@@ -50,6 +68,9 @@ const registerEntityTypeBodySchema = Type.Object(
|
|
|
50
68
|
state_schemas: Type.Optional(schemaMapSchema),
|
|
51
69
|
serve_endpoint: Type.Optional(Type.String()),
|
|
52
70
|
default_dispatch_policy: Type.Optional(dispatchPolicySchema),
|
|
71
|
+
permission_grants: Type.Optional(
|
|
72
|
+
Type.Array(typePermissionGrantInputSchema)
|
|
73
|
+
),
|
|
53
74
|
},
|
|
54
75
|
{ additionalProperties: false }
|
|
55
76
|
)
|
|
@@ -66,6 +87,7 @@ type RegisterEntityTypeBody = Static<typeof registerEntityTypeBodySchema>
|
|
|
66
87
|
type AmendEntityTypeSchemasBody = Static<
|
|
67
88
|
typeof amendEntityTypeSchemasBodySchema
|
|
68
89
|
>
|
|
90
|
+
type TypePermissionGrantInput = EntityTypePermissionGrantInput
|
|
69
91
|
|
|
70
92
|
export const entityTypesRouter: ElectricAgentsEntityTypeRoutes = Router<
|
|
71
93
|
ElectricAgentsEntityTypeRouteRequest,
|
|
@@ -79,15 +101,47 @@ entityTypesRouter.get(`/`, listEntityTypes)
|
|
|
79
101
|
entityTypesRouter.post(
|
|
80
102
|
`/`,
|
|
81
103
|
withSchema(registerEntityTypeBodySchema),
|
|
104
|
+
withEntityTypeRegistrationPermission,
|
|
82
105
|
registerEntityType
|
|
83
106
|
)
|
|
84
107
|
entityTypesRouter.patch(
|
|
85
108
|
`/:name/schemas`,
|
|
109
|
+
withExistingEntityType,
|
|
110
|
+
withEntityTypeManagePermission,
|
|
86
111
|
withSchema(amendEntityTypeSchemasBodySchema),
|
|
87
112
|
amendSchemas
|
|
88
113
|
)
|
|
89
|
-
entityTypesRouter.get(
|
|
90
|
-
|
|
114
|
+
entityTypesRouter.get(
|
|
115
|
+
`/:name`,
|
|
116
|
+
withExistingEntityType,
|
|
117
|
+
withEntityTypeSpawnPermission,
|
|
118
|
+
getEntityType
|
|
119
|
+
)
|
|
120
|
+
entityTypesRouter.delete(
|
|
121
|
+
`/:name`,
|
|
122
|
+
withExistingEntityType,
|
|
123
|
+
withEntityTypeManagePermission,
|
|
124
|
+
deleteEntityType
|
|
125
|
+
)
|
|
126
|
+
entityTypesRouter.get(
|
|
127
|
+
`/:name/grants`,
|
|
128
|
+
withExistingEntityType,
|
|
129
|
+
withEntityTypeManagePermission,
|
|
130
|
+
listTypePermissionGrants
|
|
131
|
+
)
|
|
132
|
+
entityTypesRouter.post(
|
|
133
|
+
`/:name/grants`,
|
|
134
|
+
withExistingEntityType,
|
|
135
|
+
withSchema(typePermissionGrantInputSchema),
|
|
136
|
+
withEntityTypeManagePermission,
|
|
137
|
+
createTypePermissionGrant
|
|
138
|
+
)
|
|
139
|
+
entityTypesRouter.delete(
|
|
140
|
+
`/:name/grants/:grantId`,
|
|
141
|
+
withExistingEntityType,
|
|
142
|
+
withEntityTypeManagePermission,
|
|
143
|
+
deleteTypePermissionGrant
|
|
144
|
+
)
|
|
91
145
|
|
|
92
146
|
async function registerEntityType(
|
|
93
147
|
request: ElectricAgentsEntityTypeRouteRequest,
|
|
@@ -105,6 +159,7 @@ async function registerEntityType(
|
|
|
105
159
|
}
|
|
106
160
|
|
|
107
161
|
const entityType = await ctx.entityManager.registerEntityType(normalized)
|
|
162
|
+
await applyRegistrationPermissionGrants(ctx, entityType.name, normalized)
|
|
108
163
|
return json(toPublicEntityType(entityType), { status: 201 })
|
|
109
164
|
}
|
|
110
165
|
|
|
@@ -113,7 +168,102 @@ async function listEntityTypes(
|
|
|
113
168
|
ctx: TenantContext
|
|
114
169
|
): Promise<EntityTypeRouteResult> {
|
|
115
170
|
const entityTypes = await ctx.entityManager.registry.listEntityTypes()
|
|
116
|
-
|
|
171
|
+
const visible: Array<ElectricAgentsEntityType> = []
|
|
172
|
+
for (const entityType of entityTypes) {
|
|
173
|
+
if (await canAccessEntityType(ctx, entityType, `spawn`)) {
|
|
174
|
+
visible.push(entityType)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return json(visible.map((entityType) => toPublicEntityType(entityType)))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function withExistingEntityType(
|
|
181
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
182
|
+
ctx: TenantContext
|
|
183
|
+
): Promise<EntityTypeRouteResult> {
|
|
184
|
+
const entityType = await ctx.entityManager.registry.getEntityType(
|
|
185
|
+
request.params.name
|
|
186
|
+
)
|
|
187
|
+
if (!entityType) {
|
|
188
|
+
return apiError(404, ErrCodeNotFound, `Entity type not found`)
|
|
189
|
+
}
|
|
190
|
+
request.entityTypeRoute = { entityType }
|
|
191
|
+
return undefined
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function withEntityTypeManagePermission(
|
|
195
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
196
|
+
ctx: TenantContext
|
|
197
|
+
): Promise<EntityTypeRouteResult> {
|
|
198
|
+
const entityType = request.entityTypeRoute?.entityType
|
|
199
|
+
if (!entityType) {
|
|
200
|
+
throw new Error(`entity type middleware did not run`)
|
|
201
|
+
}
|
|
202
|
+
if (
|
|
203
|
+
await canAccessEntityType(ctx, entityType, `manage`, request as Request)
|
|
204
|
+
) {
|
|
205
|
+
return undefined
|
|
206
|
+
}
|
|
207
|
+
return apiError(
|
|
208
|
+
401,
|
|
209
|
+
ErrCodeUnauthorized,
|
|
210
|
+
`Principal is not allowed to manage ${entityType.name}`
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function withEntityTypeSpawnPermission(
|
|
215
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
216
|
+
ctx: TenantContext
|
|
217
|
+
): Promise<EntityTypeRouteResult> {
|
|
218
|
+
const entityType = request.entityTypeRoute?.entityType
|
|
219
|
+
if (!entityType) {
|
|
220
|
+
throw new Error(`entity type middleware did not run`)
|
|
221
|
+
}
|
|
222
|
+
if (await canAccessEntityType(ctx, entityType, `spawn`, request as Request)) {
|
|
223
|
+
return undefined
|
|
224
|
+
}
|
|
225
|
+
return apiError(
|
|
226
|
+
401,
|
|
227
|
+
ErrCodeUnauthorized,
|
|
228
|
+
`Principal is not allowed to spawn ${entityType.name}`
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function withEntityTypeRegistrationPermission(
|
|
233
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
234
|
+
ctx: TenantContext
|
|
235
|
+
): Promise<EntityTypeRouteResult> {
|
|
236
|
+
const parsed = normalizeEntityTypeRequest(
|
|
237
|
+
routeBody<RegisterEntityTypeBody>(request)
|
|
238
|
+
)
|
|
239
|
+
if (!parsed.name) {
|
|
240
|
+
return undefined
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const existing = await ctx.entityManager.registry.getEntityType(parsed.name)
|
|
244
|
+
if (existing) {
|
|
245
|
+
request.entityTypeRoute = { entityType: existing }
|
|
246
|
+
if (
|
|
247
|
+
await canAccessEntityType(ctx, existing, `manage`, request as Request)
|
|
248
|
+
) {
|
|
249
|
+
return undefined
|
|
250
|
+
}
|
|
251
|
+
return apiError(
|
|
252
|
+
401,
|
|
253
|
+
ErrCodeUnauthorized,
|
|
254
|
+
`Principal is not allowed to manage ${existing.name}`
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (await canRegisterEntityType(ctx, parsed, request as Request)) {
|
|
259
|
+
return undefined
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return apiError(
|
|
263
|
+
401,
|
|
264
|
+
ErrCodeUnauthorized,
|
|
265
|
+
`Principal is not allowed to register entity types`
|
|
266
|
+
)
|
|
117
267
|
}
|
|
118
268
|
|
|
119
269
|
async function discoverServeEndpoint(
|
|
@@ -141,10 +291,12 @@ async function discoverServeEndpoint(
|
|
|
141
291
|
}
|
|
142
292
|
|
|
143
293
|
manifest.serve_endpoint = parsed.serve_endpoint
|
|
294
|
+
manifest.permission_grants = parsed.permission_grants
|
|
144
295
|
|
|
145
296
|
const entityType = await ctx.entityManager.registerEntityType(
|
|
146
297
|
normalizeEntityTypeRequest(manifest)
|
|
147
298
|
)
|
|
299
|
+
await applyRegistrationPermissionGrants(ctx, entityType.name, manifest)
|
|
148
300
|
return json(toPublicEntityType(entityType), { status: 201 })
|
|
149
301
|
} catch (err) {
|
|
150
302
|
if (err instanceof ElectricAgentsError) {
|
|
@@ -161,17 +313,9 @@ async function discoverServeEndpoint(
|
|
|
161
313
|
}
|
|
162
314
|
|
|
163
315
|
async function getEntityType(
|
|
164
|
-
request: ElectricAgentsEntityTypeRouteRequest
|
|
165
|
-
ctx: TenantContext
|
|
316
|
+
request: ElectricAgentsEntityTypeRouteRequest
|
|
166
317
|
): 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))
|
|
318
|
+
return json(toPublicEntityType(request.entityTypeRoute!.entityType))
|
|
175
319
|
}
|
|
176
320
|
|
|
177
321
|
async function amendSchemas(
|
|
@@ -195,6 +339,90 @@ async function deleteEntityType(
|
|
|
195
339
|
return status(204)
|
|
196
340
|
}
|
|
197
341
|
|
|
342
|
+
async function listTypePermissionGrants(
|
|
343
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
344
|
+
ctx: TenantContext
|
|
345
|
+
): Promise<EntityTypeRouteResult> {
|
|
346
|
+
const grants =
|
|
347
|
+
await ctx.entityManager.registry.listEntityTypePermissionGrants(
|
|
348
|
+
request.entityTypeRoute!.entityType.name
|
|
349
|
+
)
|
|
350
|
+
return json({ grants })
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function createTypePermissionGrant(
|
|
354
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
355
|
+
ctx: TenantContext
|
|
356
|
+
): Promise<EntityTypeRouteResult> {
|
|
357
|
+
const parsed = routeBody<TypePermissionGrantInput>(request)
|
|
358
|
+
const grant =
|
|
359
|
+
await ctx.entityManager.registry.createEntityTypePermissionGrant({
|
|
360
|
+
entityType: request.entityTypeRoute!.entityType.name,
|
|
361
|
+
permission: parsed.permission,
|
|
362
|
+
subjectKind: parsed.subject_kind,
|
|
363
|
+
subjectValue: parsed.subject_value,
|
|
364
|
+
expiresAt: parseExpiresAt(parsed.expires_at),
|
|
365
|
+
createdBy: ctx.principal.url,
|
|
366
|
+
})
|
|
367
|
+
return json(grant, { status: 201 })
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function deleteTypePermissionGrant(
|
|
371
|
+
request: ElectricAgentsEntityTypeRouteRequest,
|
|
372
|
+
ctx: TenantContext
|
|
373
|
+
): Promise<EntityTypeRouteResult> {
|
|
374
|
+
const deleted =
|
|
375
|
+
await ctx.entityManager.registry.deleteEntityTypePermissionGrant(
|
|
376
|
+
request.entityTypeRoute!.entityType.name,
|
|
377
|
+
parseGrantId(request)
|
|
378
|
+
)
|
|
379
|
+
return deleted
|
|
380
|
+
? status(204)
|
|
381
|
+
: apiError(404, ErrCodeNotFound, `Grant not found`)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function applyRegistrationPermissionGrants(
|
|
385
|
+
ctx: TenantContext,
|
|
386
|
+
entityType: string,
|
|
387
|
+
request: Pick<RegisterEntityTypeRequest, `permission_grants`>
|
|
388
|
+
): Promise<void> {
|
|
389
|
+
for (const grant of request.permission_grants ?? []) {
|
|
390
|
+
await ctx.entityManager.registry.ensureEntityTypePermissionGrant({
|
|
391
|
+
entityType,
|
|
392
|
+
permission: grant.permission,
|
|
393
|
+
subjectKind: grant.subject_kind,
|
|
394
|
+
subjectValue: grant.subject_value,
|
|
395
|
+
expiresAt: parseExpiresAt(grant.expires_at),
|
|
396
|
+
createdBy: ctx.principal.url,
|
|
397
|
+
})
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function parseGrantId(request: ElectricAgentsEntityTypeRouteRequest): number {
|
|
402
|
+
const grantId = Number.parseInt(String(request.params.grantId), 10)
|
|
403
|
+
if (!Number.isSafeInteger(grantId) || grantId <= 0) {
|
|
404
|
+
throw new ElectricAgentsError(
|
|
405
|
+
ErrCodeInvalidRequest,
|
|
406
|
+
`Invalid grant id`,
|
|
407
|
+
400
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
return grantId
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function parseExpiresAt(value: string | undefined): Date | undefined {
|
|
414
|
+
if (value === undefined) return undefined
|
|
415
|
+
const expiresAt = new Date(value)
|
|
416
|
+
if (Number.isNaN(expiresAt.getTime())) {
|
|
417
|
+
throw new ElectricAgentsError(
|
|
418
|
+
ErrCodeInvalidRequest,
|
|
419
|
+
`Invalid expires_at timestamp`,
|
|
420
|
+
400
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
return expiresAt
|
|
424
|
+
}
|
|
425
|
+
|
|
198
426
|
function normalizeEntityTypeRequest(
|
|
199
427
|
parsed: RegisterEntityTypeBody | RegisterEntityTypeRequest
|
|
200
428
|
): RegisterEntityTypeRequest {
|
|
@@ -213,6 +441,7 @@ function normalizeEntityTypeRequest(
|
|
|
213
441
|
targets: [{ type: `webhook`, url: serveEndpoint }],
|
|
214
442
|
} as RegisterEntityTypeRequest[`default_dispatch_policy`])
|
|
215
443
|
: undefined),
|
|
444
|
+
permission_grants: parsed.permission_grants,
|
|
216
445
|
}
|
|
217
446
|
}
|
|
218
447
|
|
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 }
|