@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.
Files changed (68) hide show
  1. package/LICENSE +177 -0
  2. package/dist/chunk-Cl8Af3a2.js +11 -0
  3. package/dist/entrypoint.js +7319 -0
  4. package/dist/index.cjs +7090 -0
  5. package/dist/index.d.cts +4262 -0
  6. package/dist/index.d.ts +4263 -0
  7. package/dist/index.js +7053 -0
  8. package/drizzle/0000_baseline.sql +97 -0
  9. package/drizzle/0001_entity_tags_and_bridges.sql +45 -0
  10. package/drizzle/0002_tag_outbox_hardening.sql +14 -0
  11. package/drizzle/0003_entity_manifest_sources.sql +11 -0
  12. package/drizzle/0004_tenant_scoping.sql +139 -0
  13. package/drizzle/0005_pull_wake_control_plane.sql +156 -0
  14. package/drizzle/meta/0000_snapshot.json +593 -0
  15. package/drizzle/meta/_journal.json +48 -0
  16. package/package.json +89 -0
  17. package/src/authenticated-user-format.ts +17 -0
  18. package/src/claim-write-token-store.ts +74 -0
  19. package/src/db/index.ts +53 -0
  20. package/src/db/schema.ts +490 -0
  21. package/src/dev-asserted-auth.ts +46 -0
  22. package/src/dispatch-policy-schema.ts +52 -0
  23. package/src/electric-agents/adapter-types.ts +70 -0
  24. package/src/electric-agents/default-entity-schemas.ts +1 -0
  25. package/src/electric-agents/schema-validator.ts +143 -0
  26. package/src/electric-agents-http.ts +46 -0
  27. package/src/electric-agents-types.ts +335 -0
  28. package/src/entity-bridge-manager.ts +694 -0
  29. package/src/entity-manager.ts +2601 -0
  30. package/src/entity-projector.ts +765 -0
  31. package/src/entity-registry.ts +1162 -0
  32. package/src/entrypoint-lib.ts +295 -0
  33. package/src/entrypoint.ts +11 -0
  34. package/src/host.ts +323 -0
  35. package/src/index.ts +49 -0
  36. package/src/manifest-side-effects.ts +183 -0
  37. package/src/routing/agent-ui-router.ts +81 -0
  38. package/src/routing/context.ts +35 -0
  39. package/src/routing/cron-router.ts +45 -0
  40. package/src/routing/dispatch-policy.ts +248 -0
  41. package/src/routing/durable-streams-router.ts +407 -0
  42. package/src/routing/durable-streams-routing-adapter.ts +96 -0
  43. package/src/routing/electric-proxy-router.ts +61 -0
  44. package/src/routing/entities-router.ts +484 -0
  45. package/src/routing/entity-types-router.ts +229 -0
  46. package/src/routing/global-router.ts +33 -0
  47. package/src/routing/hooks.ts +123 -0
  48. package/src/routing/internal-router.ts +741 -0
  49. package/src/routing/oss-server-router.ts +56 -0
  50. package/src/routing/runners-router.ts +416 -0
  51. package/src/routing/schema.ts +141 -0
  52. package/src/routing/stream-append.ts +196 -0
  53. package/src/routing/tenant-stream-paths.ts +26 -0
  54. package/src/runtime-registry.ts +49 -0
  55. package/src/runtime.ts +537 -0
  56. package/src/scheduler.ts +788 -0
  57. package/src/schema-validation.ts +15 -0
  58. package/src/server.ts +374 -0
  59. package/src/standalone-runtime.ts +188 -0
  60. package/src/stream-client.ts +842 -0
  61. package/src/tag-stream-outbox-drainer.ts +188 -0
  62. package/src/tenant.ts +25 -0
  63. package/src/tracing.ts +57 -0
  64. package/src/utils/electric-url.ts +15 -0
  65. package/src/utils/log.ts +95 -0
  66. package/src/utils/server-utils.ts +245 -0
  67. package/src/utils/webhook-url.ts +33 -0
  68. package/src/wake-registry.ts +946 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Library-safe top-level HTTP router for agents-server.
3
+ */
4
+
5
+ import { AutoRouter, withParams } from 'itty-router'
6
+ import { durableStreamsRouter } from './durable-streams-router.js'
7
+ import { internalRouter } from './internal-router.js'
8
+ import {
9
+ applyCors,
10
+ errorMapper,
11
+ otelEndSpan,
12
+ otelStartSpan,
13
+ preflightCors,
14
+ rejectIfShuttingDown,
15
+ } from './hooks.js'
16
+ import type { AutoRouterType, IRequest } from 'itty-router'
17
+ import type { TenantContext } from './context.js'
18
+
19
+ export type GlobalRoutes = AutoRouterType<IRequest, [TenantContext], Response>
20
+
21
+ export const globalRouter: GlobalRoutes = AutoRouter<
22
+ IRequest,
23
+ [TenantContext],
24
+ Response
25
+ >({
26
+ before: [preflightCors, withParams, otelStartSpan, rejectIfShuttingDown],
27
+ catch: errorMapper,
28
+ finally: [otelEndSpan, applyCors],
29
+ })
30
+
31
+ globalRouter.all(`/_electric/shared-state/*`, durableStreamsRouter.fetch)
32
+ globalRouter.all(`/_electric/*`, internalRouter.fetch)
33
+ globalRouter.all(`*`, durableStreamsRouter.fetch)
@@ -0,0 +1,123 @@
1
+ import { SpanKind, SpanStatusCode } from '@opentelemetry/api'
2
+ import { apiError } from '../electric-agents-http.js'
3
+ import { ElectricAgentsError } from '../entity-manager.js'
4
+ import { ATTR, extractTraceContext, tracer } from '../tracing.js'
5
+ import { serverLog } from '../utils/log.js'
6
+ import type { Span } from '@opentelemetry/api'
7
+ import type { IRequest } from 'itty-router'
8
+ import type { TenantContext } from './context.js'
9
+
10
+ const SPAN_KEY = Symbol(`agents-server.otel-span`)
11
+
12
+ interface SpanCarrier {
13
+ [SPAN_KEY]?: Span
14
+ }
15
+
16
+ function headersRecord(
17
+ headers: Headers
18
+ ): Record<string, string | Array<string> | undefined> {
19
+ const out: Record<string, string> = {}
20
+ headers.forEach((value, key) => {
21
+ out[key] = value
22
+ })
23
+ return out
24
+ }
25
+
26
+ function carrier(req: IRequest): IRequest & SpanCarrier {
27
+ return req as IRequest & SpanCarrier
28
+ }
29
+
30
+ export function startRequestSpan(req: IRequest, ctx: TenantContext): Span {
31
+ const existing = carrier(req)[SPAN_KEY]
32
+ if (existing) return existing
33
+
34
+ const url = new URL(req.url)
35
+ const parentCtx = extractTraceContext(headersRecord(req.headers))
36
+ const span = tracer.startSpan(
37
+ `HTTP ${req.method}`,
38
+ {
39
+ kind: SpanKind.SERVER,
40
+ attributes: {
41
+ [ATTR.HTTP_METHOD]: req.method,
42
+ [ATTR.HTTP_ROUTE]: url.pathname,
43
+ 'electric_agents.tenant_id': ctx.service,
44
+ },
45
+ },
46
+ parentCtx
47
+ )
48
+
49
+ carrier(req)[SPAN_KEY] = span
50
+ return span
51
+ }
52
+
53
+ export function otelStartSpan(req: IRequest, ctx: TenantContext): undefined {
54
+ startRequestSpan(req, ctx)
55
+ return undefined
56
+ }
57
+
58
+ export function otelEndSpan(
59
+ response: Response | undefined,
60
+ req: IRequest
61
+ ): void {
62
+ const span = carrier(req)[SPAN_KEY]
63
+ if (!span) return
64
+ if (response) {
65
+ span.setAttribute(ATTR.HTTP_STATUS, response.status)
66
+ }
67
+ span.end()
68
+ carrier(req)[SPAN_KEY] = undefined
69
+ }
70
+
71
+ export function applyCors(
72
+ response: Response | undefined
73
+ ): Response | undefined {
74
+ if (!response) return response
75
+ const headers = new Headers(response.headers)
76
+ headers.set(`access-control-allow-origin`, `*`)
77
+ headers.set(
78
+ `access-control-allow-methods`,
79
+ `GET, POST, PUT, PATCH, DELETE, OPTIONS`
80
+ )
81
+ headers.set(
82
+ `access-control-allow-headers`,
83
+ `content-type, authorization, electric-claim-token, x-electric-asserted-email, x-electric-asserted-name, ngrok-skip-browser-warning`
84
+ )
85
+ headers.set(`access-control-expose-headers`, `*`)
86
+ return new Response(response.body, {
87
+ status: response.status,
88
+ statusText: response.statusText,
89
+ headers,
90
+ })
91
+ }
92
+
93
+ export function preflightCors(req: IRequest): Response | undefined {
94
+ if (req.method !== `OPTIONS`) return undefined
95
+ return new Response(null, { status: 204 })
96
+ }
97
+
98
+ export function errorMapper(err: unknown, req: IRequest): Response {
99
+ const span = carrier(req)[SPAN_KEY]
100
+ if (err instanceof Error) {
101
+ span?.recordException(err)
102
+ span?.setStatus({ code: SpanStatusCode.ERROR, message: err.message })
103
+ }
104
+ if (err instanceof ElectricAgentsError) {
105
+ return apiError(err.status, err.code, err.message, err.details)
106
+ }
107
+ serverLog.error(`[agent-server] Unhandled error:`, err)
108
+ return apiError(500, `INTERNAL_SERVER_ERROR`, `Internal server error`)
109
+ }
110
+
111
+ export function rejectIfShuttingDown(
112
+ req: IRequest,
113
+ ctx: TenantContext
114
+ ): Response | undefined {
115
+ if (!ctx.isShuttingDown()) return undefined
116
+ const path = new URL(req.url).pathname
117
+ if (!path.startsWith(`/_electric/webhook-forward/`)) return undefined
118
+ return apiError(503, `SERVER_STOPPING`, `Server is shutting down`)
119
+ }
120
+
121
+ export function getRequestSpan(req: IRequest): Span | undefined {
122
+ return carrier(req)[SPAN_KEY]
123
+ }