@electric-ax/agents-server 0.4.0 → 0.4.1

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/src/runtime.ts CHANGED
@@ -435,6 +435,8 @@ export class ElectricAgentsTenantRuntime {
435
435
  const fireAtRaw = value.fireAt
436
436
  const producerId = value.producerId
437
437
  const targetUrl = value.targetUrl
438
+ const senderUrl =
439
+ typeof value.senderUrl === `string` ? value.senderUrl : ownerEntityUrl
438
440
  if (
439
441
  typeof fireAtRaw !== `string` ||
440
442
  typeof producerId !== `string` ||
@@ -459,7 +461,7 @@ export class ElectricAgentsTenantRuntime {
459
461
  manifestKey,
460
462
  {
461
463
  entityUrl: targetUrl,
462
- from: typeof value.from === `string` ? value.from : ownerEntityUrl,
464
+ from: senderUrl,
463
465
  payload: value.payload,
464
466
  key: `scheduled-${producerId}`,
465
467
  type:
@@ -474,6 +476,7 @@ export class ElectricAgentsTenantRuntime {
474
476
  kind: `schedule`,
475
477
  scheduleType: `future_send`,
476
478
  targetUrl,
479
+ senderUrl,
477
480
  fireAt: fireAt.toISOString(),
478
481
  producerId,
479
482
  status: `pending`,
package/src/scheduler.ts CHANGED
@@ -9,6 +9,8 @@ export interface DelayedSendPayload {
9
9
  payload: unknown
10
10
  key?: string
11
11
  type?: string
12
+ mode?: `immediate` | `queued` | `paused` | `steer`
13
+ position?: string
12
14
  producerId?: string
13
15
  manifest?: {
14
16
  ownerEntityUrl: string
package/src/server.ts CHANGED
@@ -12,6 +12,13 @@ import { ossServerRouter } from './routing/oss-server-router.js'
12
12
  import { startStandaloneAgentsRuntime } from './standalone-runtime.js'
13
13
  import { StreamClient, durableStreamsServiceUrl } from './stream-client.js'
14
14
  import { DEFAULT_TENANT_ID } from './tenant.js'
15
+ import { getDevPrincipal, getPrincipalFromRequest } from './principal.js'
16
+ import { apiError } from './electric-agents-http.js'
17
+ import {
18
+ ErrCodeInvalidRequest,
19
+ ErrCodeUnauthorized,
20
+ } from './electric-agents-types.js'
21
+ import { ElectricAgentsError } from './entity-manager.js'
15
22
  import { serverLog } from './utils/log.js'
16
23
  import type { DrizzleDB, PgClient } from './db/index.js'
17
24
  import type { Server } from 'node:http'
@@ -22,7 +29,7 @@ import type {
22
29
  EntityRegistry,
23
30
  RuntimeHandler,
24
31
  } from '@electric-ax/agents-runtime'
25
- import type { AuthenticateRequest } from './electric-agents-types.js'
32
+ import type { Principal } from './principal.js'
26
33
  import type { EntityBridgeCoordinator } from './entity-bridge-manager.js'
27
34
  import type { DurableStreamsRoutingAdapter } from './routing/durable-streams-routing-adapter.js'
28
35
  import type { OssServerContext } from './routing/oss-server-router.js'
@@ -46,7 +53,10 @@ export interface ElectricAgentsServerOptions {
46
53
  postgresUrl: string
47
54
  electricUrl?: string
48
55
  electricSecret?: string
49
- authenticateRequest?: AuthenticateRequest
56
+ authenticateRequest?: (
57
+ request: Request
58
+ ) => Promise<Principal | null> | Principal | null
59
+ allowDevPrincipalFallback?: boolean
50
60
  /**
51
61
  * Disabled by default. When set to a positive interval, periodically
52
62
  * recovers expired dispatch claims and stale outstanding wakes.
@@ -207,6 +217,7 @@ export class ElectricAgentsServer {
207
217
  })
208
218
  this.electricAgentsManager = this.standaloneRuntime.manager
209
219
  this.entityBridgeManager = this.standaloneRuntime.entityBridgeManager
220
+ await this.electricAgentsManager.ensurePrincipalEntityType()
210
221
 
211
222
  const serverAdapter = createServerAdapter((request) =>
212
223
  this.handleRequest(request)
@@ -323,9 +334,27 @@ export class ElectricAgentsServer {
323
334
  return new Response(null, { status: 503 })
324
335
  }
325
336
 
326
- return await ossServerRouter.fetch(
327
- request as Parameters<typeof ossServerRouter.fetch>[0],
328
- await this.buildTenantContext(request)
337
+ try {
338
+ return await ossServerRouter.fetch(
339
+ request as Parameters<typeof ossServerRouter.fetch>[0],
340
+ await this.buildTenantContext(request)
341
+ )
342
+ } catch (error) {
343
+ if (error instanceof ElectricAgentsError) {
344
+ return apiError(error.status, error.code, error.message, error.details)
345
+ }
346
+ throw error
347
+ }
348
+ }
349
+
350
+ private allowDevPrincipalFallback(): boolean {
351
+ if (this.options.allowDevPrincipalFallback !== undefined) {
352
+ return this.options.allowDevPrincipalFallback
353
+ }
354
+ return (
355
+ process.env.ELECTRIC_INSECURE === `true` ||
356
+ process.env.NODE_ENV !== `production` ||
357
+ Boolean(this.options.durableStreamsServer)
329
358
  )
330
359
  }
331
360
 
@@ -343,10 +372,33 @@ export class ElectricAgentsServer {
343
372
  throw new Error(`agents-server runtime is not started`)
344
373
  }
345
374
 
375
+ let principal: Principal | null
376
+ try {
377
+ principal =
378
+ (await this.options.authenticateRequest?.(request)) ??
379
+ getPrincipalFromRequest(request)
380
+ } catch (error) {
381
+ throw new ElectricAgentsError(
382
+ ErrCodeInvalidRequest,
383
+ error instanceof Error ? error.message : `Invalid principal`,
384
+ 400
385
+ )
386
+ }
387
+
388
+ if (!principal && this.allowDevPrincipalFallback()) {
389
+ principal = getDevPrincipal()
390
+ }
391
+ if (!principal) {
392
+ throw new ElectricAgentsError(
393
+ ErrCodeUnauthorized,
394
+ `Missing Electric-Principal`,
395
+ 401
396
+ )
397
+ }
398
+
346
399
  return {
347
400
  service: this.tenantId,
348
- authenticatedUser:
349
- (await this.options.authenticateRequest?.(request)) ?? undefined,
401
+ principal,
350
402
  publicUrl: this.publicUrl,
351
403
  localUrl: this._url,
352
404
  durableStreamsUrl: this.options.durableStreamsUrl,
@@ -1,46 +0,0 @@
1
- import type {
2
- AuthenticateRequest,
3
- AuthenticatedRequestUser,
4
- } from './electric-agents-types.js'
5
-
6
- export interface DevAssertedAuthOptions {
7
- enabled?: boolean
8
- defaultEmail?: string
9
- defaultName?: string
10
- }
11
-
12
- export const DEV_ASSERTED_EMAIL_HEADER = `x-electric-asserted-email`
13
- export const DEV_ASSERTED_NAME_HEADER = `x-electric-asserted-name`
14
-
15
- function clean(value: string | undefined | null): string | undefined {
16
- const trimmed = value?.trim()
17
- return trimmed || undefined
18
- }
19
-
20
- export function createDevAssertedAuthenticateRequest(
21
- options: DevAssertedAuthOptions
22
- ): AuthenticateRequest | undefined {
23
- if (!options.enabled) return undefined
24
-
25
- return (request): AuthenticatedRequestUser | null => {
26
- const email =
27
- clean(request.headers.get(DEV_ASSERTED_EMAIL_HEADER)) ??
28
- clean(options.defaultEmail)
29
- const name =
30
- clean(request.headers.get(DEV_ASSERTED_NAME_HEADER)) ??
31
- clean(options.defaultName)
32
- const userId = email ?? name
33
- if (!userId) return null
34
- return { userId, email, name }
35
- }
36
- }
37
-
38
- export function devAssertedAuthOptionsFromEnv(
39
- env: Record<string, string | undefined> = process.env
40
- ): DevAssertedAuthOptions {
41
- return {
42
- enabled: env.ELECTRIC_AGENTS_DEV_ASSERTED_AUTH === `1`,
43
- defaultEmail: env.ELECTRIC_ASSERTED_AUTH_EMAIL,
44
- defaultName: env.ELECTRIC_ASSERTED_AUTH_NAME,
45
- }
46
- }