@electric-ax/agents-server 0.3.0
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/LICENSE +177 -0
- package/dist/chunk-Cl8Af3a2.js +11 -0
- package/dist/entrypoint.js +7319 -0
- package/dist/index.cjs +7090 -0
- package/dist/index.d.cts +4262 -0
- package/dist/index.d.ts +4263 -0
- package/dist/index.js +7053 -0
- package/drizzle/0000_baseline.sql +97 -0
- package/drizzle/0001_entity_tags_and_bridges.sql +45 -0
- package/drizzle/0002_tag_outbox_hardening.sql +14 -0
- package/drizzle/0003_entity_manifest_sources.sql +11 -0
- package/drizzle/0004_tenant_scoping.sql +139 -0
- package/drizzle/0005_pull_wake_control_plane.sql +156 -0
- package/drizzle/meta/0000_snapshot.json +593 -0
- package/drizzle/meta/_journal.json +48 -0
- package/package.json +89 -0
- package/src/authenticated-user-format.ts +17 -0
- package/src/claim-write-token-store.ts +74 -0
- package/src/db/index.ts +53 -0
- package/src/db/schema.ts +490 -0
- package/src/dev-asserted-auth.ts +46 -0
- package/src/dispatch-policy-schema.ts +52 -0
- package/src/electric-agents/adapter-types.ts +70 -0
- package/src/electric-agents/default-entity-schemas.ts +1 -0
- package/src/electric-agents/schema-validator.ts +143 -0
- package/src/electric-agents-http.ts +46 -0
- package/src/electric-agents-types.ts +335 -0
- package/src/entity-bridge-manager.ts +694 -0
- package/src/entity-manager.ts +2601 -0
- package/src/entity-projector.ts +765 -0
- package/src/entity-registry.ts +1162 -0
- package/src/entrypoint-lib.ts +295 -0
- package/src/entrypoint.ts +11 -0
- package/src/host.ts +323 -0
- package/src/index.ts +49 -0
- package/src/manifest-side-effects.ts +183 -0
- package/src/routing/agent-ui-router.ts +81 -0
- package/src/routing/context.ts +35 -0
- package/src/routing/cron-router.ts +45 -0
- package/src/routing/dispatch-policy.ts +248 -0
- package/src/routing/durable-streams-router.ts +407 -0
- package/src/routing/durable-streams-routing-adapter.ts +96 -0
- package/src/routing/electric-proxy-router.ts +61 -0
- package/src/routing/entities-router.ts +484 -0
- package/src/routing/entity-types-router.ts +229 -0
- package/src/routing/global-router.ts +33 -0
- package/src/routing/hooks.ts +123 -0
- package/src/routing/internal-router.ts +741 -0
- package/src/routing/oss-server-router.ts +56 -0
- package/src/routing/runners-router.ts +416 -0
- package/src/routing/schema.ts +141 -0
- package/src/routing/stream-append.ts +196 -0
- package/src/routing/tenant-stream-paths.ts +26 -0
- package/src/runtime-registry.ts +49 -0
- package/src/runtime.ts +537 -0
- package/src/scheduler.ts +788 -0
- package/src/schema-validation.ts +15 -0
- package/src/server.ts +374 -0
- package/src/standalone-runtime.ts +188 -0
- package/src/stream-client.ts +842 -0
- package/src/tag-stream-outbox-drainer.ts +188 -0
- package/src/tenant.ts +25 -0
- package/src/tracing.ts +57 -0
- package/src/utils/electric-url.ts +15 -0
- package/src/utils/log.ts +95 -0
- package/src/utils/server-utils.ts +245 -0
- package/src/utils/webhook-url.ts +33 -0
- package/src/wake-registry.ts +946 -0
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@electric-ax/agents-server",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Electric Agents entity runtime server",
|
|
5
|
+
"author": "Durable Stream contributors",
|
|
6
|
+
"bin": {
|
|
7
|
+
"electric-agents-server": "./dist/entrypoint.js",
|
|
8
|
+
"durable-streams-electric-agents-server": "./dist/entrypoint.js"
|
|
9
|
+
},
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/electric-sql/electric.git",
|
|
14
|
+
"directory": "packages/agents-server"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"require": {
|
|
30
|
+
"types": "./dist/index.d.cts",
|
|
31
|
+
"default": "./dist/index.cjs"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@anthropic-ai/sdk": "^0.78.0",
|
|
39
|
+
"@durable-streams/client": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/client@350",
|
|
40
|
+
"@durable-streams/server": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/server@350",
|
|
41
|
+
"@durable-streams/state": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/state@350",
|
|
42
|
+
"@electric-sql/client": "^1.5.17",
|
|
43
|
+
"@mariozechner/pi-agent-core": "^0.70.2",
|
|
44
|
+
"@opentelemetry/api": "^1.9.1",
|
|
45
|
+
"@sinclair/typebox": "^0.34.48",
|
|
46
|
+
"@whatwg-node/server": "^0.10.18",
|
|
47
|
+
"ajv": "^8.18.0",
|
|
48
|
+
"cron-parser": "^5.5.0",
|
|
49
|
+
"drizzle-orm": "^0.44.0",
|
|
50
|
+
"fastq": "^1.20.1",
|
|
51
|
+
"itty-router": "^5.0.23",
|
|
52
|
+
"lmdb": "^3.5.1",
|
|
53
|
+
"pino": "^10.3.1",
|
|
54
|
+
"pino-pretty": "^13.0.0",
|
|
55
|
+
"postgres": "^3.4.0",
|
|
56
|
+
"undici": "^7.24.7",
|
|
57
|
+
"@electric-ax/agents-runtime": "0.1.3"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^22.19.15",
|
|
61
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
62
|
+
"cross-env": "^10.1.0",
|
|
63
|
+
"drizzle-kit": "^0.31.0",
|
|
64
|
+
"tsdown": "^0.9.0",
|
|
65
|
+
"tsx": "^4.19.0",
|
|
66
|
+
"typescript": "^5.0.0",
|
|
67
|
+
"vitest": "^4.1.0",
|
|
68
|
+
"@electric-ax/agents-server-conformance-tests": "0.1.2",
|
|
69
|
+
"@electric-ax/agents": "0.3.0",
|
|
70
|
+
"@electric-ax/agents-server-ui": "0.3.0"
|
|
71
|
+
},
|
|
72
|
+
"files": [
|
|
73
|
+
"dist",
|
|
74
|
+
"drizzle",
|
|
75
|
+
"src"
|
|
76
|
+
],
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": ">=18.0.0"
|
|
79
|
+
},
|
|
80
|
+
"scripts": {
|
|
81
|
+
"build": "tsdown",
|
|
82
|
+
"dev": "tsdown --watch",
|
|
83
|
+
"start": "cross-env-shell \"DATABASE_URL=${DATABASE_URL:-postgresql://electric_agents:electric_agents@localhost:5432/electric_agents} ELECTRIC_URL=${ELECTRIC_URL:-http://localhost:3060} tsx --watch src/entrypoint.ts\"",
|
|
84
|
+
"test": "vitest run",
|
|
85
|
+
"coverage": "ELECTRIC_AGENTS_KEEP_BACKEND=1 pnpm exec vitest run --coverage $(find test -name '*.test.ts' ! -name 'conformance.test.ts' | sort) && pnpm exec vitest run test/conformance.test.ts",
|
|
86
|
+
"typecheck": "tsc --noEmit",
|
|
87
|
+
"stylecheck": "eslint . --quiet"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AuthenticatedRequestUser } from './electric-agents-types.js'
|
|
2
|
+
|
|
3
|
+
function clean(value: string | undefined): string | undefined {
|
|
4
|
+
const trimmed = value?.trim()
|
|
5
|
+
return trimmed || undefined
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatAuthenticatedUser(
|
|
9
|
+
user: AuthenticatedRequestUser | null | undefined
|
|
10
|
+
): string | undefined {
|
|
11
|
+
if (!user) return undefined
|
|
12
|
+
const email = clean(user.email)
|
|
13
|
+
const name = clean(user.name)
|
|
14
|
+
const userId = clean(user.userId)
|
|
15
|
+
if (name && email) return `${name} <${email}>`
|
|
16
|
+
return email ?? name ?? userId
|
|
17
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
interface ActiveClaimWriteToken {
|
|
4
|
+
token: string
|
|
5
|
+
consumerId: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class ClaimWriteTokenStore {
|
|
9
|
+
private readonly claimsByStream = new Map<string, ActiveClaimWriteToken>()
|
|
10
|
+
private readonly streamByConsumer = new Map<string, string>()
|
|
11
|
+
|
|
12
|
+
mint(service: string, streamPath: string, consumerId: string): string {
|
|
13
|
+
const streamKey = this.streamKey(service, streamPath)
|
|
14
|
+
const consumerKey = this.consumerKey(service, consumerId)
|
|
15
|
+
const previousClaimForStream = this.claimsByStream.get(streamKey)
|
|
16
|
+
if (previousClaimForStream) {
|
|
17
|
+
this.streamByConsumer.delete(
|
|
18
|
+
this.consumerKey(service, previousClaimForStream.consumerId)
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const previousStreamForConsumer = this.streamByConsumer.get(consumerKey)
|
|
23
|
+
if (previousStreamForConsumer) {
|
|
24
|
+
this.claimsByStream.delete(previousStreamForConsumer)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const token = randomUUID()
|
|
28
|
+
this.claimsByStream.set(streamKey, { token, consumerId })
|
|
29
|
+
this.streamByConsumer.set(consumerKey, streamKey)
|
|
30
|
+
return token
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
isValid(service: string, streamPath: string, token: string): boolean {
|
|
34
|
+
return (
|
|
35
|
+
this.claimsByStream.get(this.streamKey(service, streamPath))?.token ===
|
|
36
|
+
token
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
owns(service: string, streamPath: string, consumerId: string): boolean {
|
|
41
|
+
return (
|
|
42
|
+
this.claimsByStream.get(this.streamKey(service, streamPath))
|
|
43
|
+
?.consumerId === consumerId
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
clearStream(service: string, streamPath: string): void {
|
|
48
|
+
const streamKey = this.streamKey(service, streamPath)
|
|
49
|
+
const activeClaim = this.claimsByStream.get(streamKey)
|
|
50
|
+
if (!activeClaim) return
|
|
51
|
+
|
|
52
|
+
this.claimsByStream.delete(streamKey)
|
|
53
|
+
this.streamByConsumer.delete(
|
|
54
|
+
this.consumerKey(service, activeClaim.consumerId)
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clearConsumer(service: string, consumerId: string): void {
|
|
59
|
+
const consumerKey = this.consumerKey(service, consumerId)
|
|
60
|
+
const streamKey = this.streamByConsumer.get(consumerKey)
|
|
61
|
+
if (!streamKey) return
|
|
62
|
+
|
|
63
|
+
this.streamByConsumer.delete(consumerKey)
|
|
64
|
+
this.claimsByStream.delete(streamKey)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private streamKey(service: string, streamPath: string): string {
|
|
68
|
+
return `${service}\0${streamPath}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private consumerKey(service: string, consumerId: string): string {
|
|
72
|
+
return `${service}\0${consumerId}`
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/db/index.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { dirname, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
5
|
+
import { migrate } from 'drizzle-orm/postgres-js/migrator'
|
|
6
|
+
import postgres from 'postgres'
|
|
7
|
+
import * as schema from './schema.js'
|
|
8
|
+
|
|
9
|
+
export type DrizzleDB = ReturnType<typeof drizzle<typeof schema>>
|
|
10
|
+
export type PgClient = ReturnType<typeof postgres>
|
|
11
|
+
|
|
12
|
+
export function createDb(postgresUrl: string): {
|
|
13
|
+
db: DrizzleDB
|
|
14
|
+
client: PgClient
|
|
15
|
+
} {
|
|
16
|
+
const poolMax = Number(process.env.ELECTRIC_AGENTS_PG_POOL_MAX ?? `100`)
|
|
17
|
+
const client = postgres(postgresUrl, {
|
|
18
|
+
max: poolMax,
|
|
19
|
+
fetch_types: false,
|
|
20
|
+
})
|
|
21
|
+
const db = drizzle(client, { schema })
|
|
22
|
+
return { db, client }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveMigrationsFolder(fromUrl = import.meta.url): string {
|
|
26
|
+
const here = dirname(fileURLToPath(fromUrl))
|
|
27
|
+
const candidates = [
|
|
28
|
+
resolve(here, `../../drizzle`),
|
|
29
|
+
resolve(here, `../drizzle`),
|
|
30
|
+
resolve(process.cwd(), `packages/agents-server/drizzle`),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
const folder = candidates.find((candidate) => existsSync(candidate))
|
|
34
|
+
if (!folder) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Could not locate agent-server migrations directory from ${fromUrl}`
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return folder
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function runMigrations(postgresUrl: string): Promise<void> {
|
|
44
|
+
const migrationClient = postgres(postgresUrl, {
|
|
45
|
+
max: 1,
|
|
46
|
+
onnotice: () => {},
|
|
47
|
+
})
|
|
48
|
+
const db = drizzle(migrationClient)
|
|
49
|
+
await migrate(db, {
|
|
50
|
+
migrationsFolder: resolveMigrationsFolder(),
|
|
51
|
+
})
|
|
52
|
+
await migrationClient.end()
|
|
53
|
+
}
|