abxbus 2.4.16 → 2.4.18
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/cjs/event_bus.js +1 -1
- package/dist/cjs/event_bus.js.map +2 -2
- package/dist/cjs/event_handler.d.ts +1 -0
- package/dist/cjs/event_handler.js +14 -1
- package/dist/cjs/event_handler.js.map +2 -2
- package/dist/cjs/middleware_otel_tracing.js +3 -3
- package/dist/cjs/middleware_otel_tracing.js.map +2 -2
- package/dist/cjs/retry.js +39 -0
- package/dist/cjs/retry.js.map +2 -2
- package/dist/esm/event_bus.js +1 -1
- package/dist/esm/event_bus.js.map +2 -2
- package/dist/esm/event_handler.js +14 -1
- package/dist/esm/event_handler.js.map +2 -2
- package/dist/esm/middleware_otel_tracing.js +4 -4
- package/dist/esm/middleware_otel_tracing.js.map +2 -2
- package/dist/esm/retry.js +39 -0
- package/dist/esm/retry.js.map +2 -2
- package/dist/types/event_handler.d.ts +1 -0
- package/package.json +5 -1
- package/src/async_context.ts +70 -0
- package/src/base_event.ts +1201 -0
- package/src/bridge_jsonl.ts +174 -0
- package/src/bridge_nats.ts +104 -0
- package/src/bridge_postgres.ts +277 -0
- package/src/bridge_redis.ts +194 -0
- package/src/bridge_sqlite.ts +289 -0
- package/src/bridges.ts +376 -0
- package/src/event_bus.ts +1263 -0
- package/src/event_handler.ts +379 -0
- package/src/event_history.ts +247 -0
- package/src/event_result.ts +483 -0
- package/src/events_suck.ts +96 -0
- package/src/helpers.ts +65 -0
- package/src/index.ts +37 -0
- package/src/lock_manager.ts +401 -0
- package/src/logging.ts +261 -0
- package/src/middleware_otel_tracing.ts +201 -0
- package/src/middlewares.ts +16 -0
- package/src/optional_deps.ts +52 -0
- package/src/retry.ts +578 -0
- package/src/timing.ts +52 -0
- package/src/types.ts +132 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { ROOT_CONTEXT, SpanStatusCode, trace, type Context, type Span, type Tracer } from '@opentelemetry/api'
|
|
2
|
+
|
|
3
|
+
import type { BaseEvent } from './base_event.js'
|
|
4
|
+
import type { EventBus } from './event_bus.js'
|
|
5
|
+
import type { EventResult } from './event_result.js'
|
|
6
|
+
import type { EventBusMiddleware } from './middlewares.js'
|
|
7
|
+
import type { EventStatus } from './types.js'
|
|
8
|
+
|
|
9
|
+
type OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'>
|
|
10
|
+
|
|
11
|
+
export type OtelTracingMiddlewareOptions = {
|
|
12
|
+
tracer?: Tracer
|
|
13
|
+
trace_api?: OpenTelemetryTraceApi
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
17
|
+
private readonly tracer: Tracer
|
|
18
|
+
private readonly trace_api: OpenTelemetryTraceApi
|
|
19
|
+
private readonly event_spans = new Map<string, Span>()
|
|
20
|
+
private readonly event_contexts = new Map<string, Context>()
|
|
21
|
+
private readonly handler_spans = new Map<string, Span>()
|
|
22
|
+
private readonly handler_contexts = new Map<string, Context>()
|
|
23
|
+
|
|
24
|
+
constructor(options: OtelTracingMiddlewareOptions = {}) {
|
|
25
|
+
this.trace_api = options.trace_api ?? trace
|
|
26
|
+
this.tracer = options.tracer ?? this.trace_api.getTracer('abxbus')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onEventChange(eventbus: EventBus, event: BaseEvent, status: EventStatus): void {
|
|
30
|
+
if (status === 'started') {
|
|
31
|
+
this.startEventSpan(eventbus, event)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (status === 'completed') {
|
|
36
|
+
this.completeEventSpan(eventbus, event)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onEventResultChange(eventbus: EventBus, event: BaseEvent, event_result: EventResult, status: EventStatus): void {
|
|
41
|
+
if (status === 'started') {
|
|
42
|
+
this.startHandlerSpan(eventbus, event, event_result)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (status === 'completed') {
|
|
47
|
+
this.completeHandlerSpan(event_result)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private startEventSpan(eventbus: EventBus, event: BaseEvent): Span {
|
|
52
|
+
const existing = this.event_spans.get(event.event_id)
|
|
53
|
+
if (existing) {
|
|
54
|
+
return existing
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const parent_context = this.parentContextForEvent(event) ?? ROOT_CONTEXT
|
|
58
|
+
const span = this.tracer.startSpan(
|
|
59
|
+
`abxbus.event ${event.event_type}`,
|
|
60
|
+
{
|
|
61
|
+
attributes: compactAttributes({
|
|
62
|
+
'abxbus.bus.id': eventbus.id,
|
|
63
|
+
'abxbus.bus.name': eventbus.name,
|
|
64
|
+
'abxbus.event.id': event.event_id,
|
|
65
|
+
'abxbus.event.type': event.event_type,
|
|
66
|
+
'abxbus.event.version': event.event_version,
|
|
67
|
+
'abxbus.event.parent_id': event.event_parent_id,
|
|
68
|
+
'abxbus.event.emitted_by_handler_id': event.event_emitted_by_handler_id,
|
|
69
|
+
'abxbus.event.path': event.event_path.join(' '),
|
|
70
|
+
}),
|
|
71
|
+
startTime: dateFromIso(event.event_started_at),
|
|
72
|
+
},
|
|
73
|
+
parent_context
|
|
74
|
+
)
|
|
75
|
+
const span_context = this.trace_api.setSpan(parent_context, span)
|
|
76
|
+
this.event_spans.set(event.event_id, span)
|
|
77
|
+
this.event_contexts.set(event.event_id, span_context)
|
|
78
|
+
return span
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private completeEventSpan(eventbus: EventBus, event: BaseEvent): void {
|
|
82
|
+
const span = this.event_spans.get(event.event_id) ?? this.startEventSpan(eventbus, event)
|
|
83
|
+
if (event.event_errors.length > 0) {
|
|
84
|
+
recordSpanError(span, event.event_errors[0])
|
|
85
|
+
} else {
|
|
86
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
87
|
+
}
|
|
88
|
+
span.setAttributes(
|
|
89
|
+
compactAttributes({
|
|
90
|
+
'abxbus.event.status': event.event_status,
|
|
91
|
+
'abxbus.event.result_count': event.event_results.size,
|
|
92
|
+
'abxbus.event.error_count': event.event_errors.length,
|
|
93
|
+
'abxbus.event.child_count': event.event_children.length,
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
span.end(dateFromIso(event.event_completed_at))
|
|
97
|
+
this.event_spans.delete(event.event_id)
|
|
98
|
+
this.event_contexts.delete(event.event_id)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private startHandlerSpan(eventbus: EventBus, event: BaseEvent, event_result: EventResult): Span {
|
|
102
|
+
const existing = this.handler_spans.get(event_result.id)
|
|
103
|
+
if (existing) {
|
|
104
|
+
return existing
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parent_context =
|
|
108
|
+
this.event_contexts.get(event.event_id) ?? this.trace_api.setSpan(ROOT_CONTEXT, this.startEventSpan(eventbus, event))
|
|
109
|
+
const span = this.tracer.startSpan(
|
|
110
|
+
`abxbus.handler ${event.event_type} ${event_result.handler_name}`,
|
|
111
|
+
{
|
|
112
|
+
attributes: compactAttributes({
|
|
113
|
+
'abxbus.bus.id': eventbus.id,
|
|
114
|
+
'abxbus.bus.name': eventbus.name,
|
|
115
|
+
'abxbus.event.id': event.event_id,
|
|
116
|
+
'abxbus.event.type': event.event_type,
|
|
117
|
+
'abxbus.handler.id': event_result.handler_id,
|
|
118
|
+
'abxbus.handler.name': event_result.handler_name,
|
|
119
|
+
'abxbus.handler.file_path': event_result.handler_file_path,
|
|
120
|
+
'abxbus.handler.event_pattern': event_result.handler.event_pattern,
|
|
121
|
+
'abxbus.event_result.id': event_result.id,
|
|
122
|
+
}),
|
|
123
|
+
startTime: dateFromIso(event_result.started_at),
|
|
124
|
+
},
|
|
125
|
+
parent_context
|
|
126
|
+
)
|
|
127
|
+
const span_context = this.trace_api.setSpan(parent_context, span)
|
|
128
|
+
this.handler_spans.set(event_result.id, span)
|
|
129
|
+
this.handler_contexts.set(handlerSpanKey(event_result.event_id, event_result.handler_id), span_context)
|
|
130
|
+
return span
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private completeHandlerSpan(event_result: EventResult): void {
|
|
134
|
+
const span = this.handler_spans.get(event_result.id)
|
|
135
|
+
if (!span) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (event_result.error !== undefined) {
|
|
140
|
+
recordSpanError(span, event_result.error)
|
|
141
|
+
} else {
|
|
142
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
143
|
+
}
|
|
144
|
+
span.setAttributes(
|
|
145
|
+
compactAttributes({
|
|
146
|
+
'abxbus.event_result.status': event_result.status,
|
|
147
|
+
'abxbus.handler.child_count': event_result.event_children.length,
|
|
148
|
+
})
|
|
149
|
+
)
|
|
150
|
+
span.end(dateFromIso(event_result.completed_at))
|
|
151
|
+
this.handler_spans.delete(event_result.id)
|
|
152
|
+
this.handler_contexts.delete(handlerSpanKey(event_result.event_id, event_result.handler_id))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private parentContextForEvent(event: BaseEvent): Context | undefined {
|
|
156
|
+
if (event.event_parent_id && event.event_emitted_by_handler_id) {
|
|
157
|
+
const handler_context = this.handler_contexts.get(handlerSpanKey(event.event_parent_id, event.event_emitted_by_handler_id))
|
|
158
|
+
if (handler_context) {
|
|
159
|
+
return handler_context
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return event.event_parent_id ? this.event_contexts.get(event.event_parent_id) : undefined
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function handlerSpanKey(event_id: string, handler_id: string): string {
|
|
168
|
+
return `${event_id}:${handler_id}`
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function dateFromIso(value: string | null | undefined): Date | undefined {
|
|
172
|
+
if (value == null) {
|
|
173
|
+
return undefined
|
|
174
|
+
}
|
|
175
|
+
const date = new Date(value)
|
|
176
|
+
return Number.isNaN(date.getTime()) ? undefined : date
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function compactAttributes(
|
|
180
|
+
attributes: Record<string, string | number | boolean | null | undefined>
|
|
181
|
+
): Record<string, string | number | boolean> {
|
|
182
|
+
const compacted: Record<string, string | number | boolean> = {}
|
|
183
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
184
|
+
if (value !== null && value !== undefined) {
|
|
185
|
+
compacted[key] = value
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return compacted
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function recordSpanError(span: Span, error: unknown): void {
|
|
192
|
+
if (error instanceof Error) {
|
|
193
|
+
span.recordException(error)
|
|
194
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const message = typeof error === 'string' ? error : 'Unknown abxbus handler error'
|
|
199
|
+
span.recordException(message)
|
|
200
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message })
|
|
201
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BaseEvent } from './base_event.js'
|
|
2
|
+
import type { EventBus } from './event_bus.js'
|
|
3
|
+
import type { EventHandler } from './event_handler.js'
|
|
4
|
+
import type { EventResult } from './event_result.js'
|
|
5
|
+
import type { EventStatus } from './types.js'
|
|
6
|
+
|
|
7
|
+
export type { EventStatus } from './types.js'
|
|
8
|
+
|
|
9
|
+
export interface EventBusMiddleware {
|
|
10
|
+
onEventChange?(eventbus: EventBus, event: BaseEvent, status: EventStatus): void | Promise<void>
|
|
11
|
+
onEventResultChange?(eventbus: EventBus, event: BaseEvent, event_result: EventResult, status: EventStatus): void | Promise<void>
|
|
12
|
+
onBusHandlersChange?(eventbus: EventBus, handler: EventHandler, registered: boolean): void | Promise<void>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type EventBusMiddlewareCtor = new () => EventBusMiddleware
|
|
16
|
+
export type EventBusMiddlewareInput = EventBusMiddleware | EventBusMiddlewareCtor
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const isNodeRuntime = (): boolean => {
|
|
2
|
+
const maybe_process = (globalThis as { process?: { versions?: { node?: string } } }).process
|
|
3
|
+
return typeof maybe_process?.versions?.node === 'string'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const missingDependencyError = (bridge_name: string, package_name: string): Error =>
|
|
7
|
+
new Error(`${bridge_name} requires optional dependency "${package_name}". Install it with: npm install ${package_name}`)
|
|
8
|
+
|
|
9
|
+
export const assertOptionalDependencyAvailable = (bridge_name: string, package_name: string): void => {
|
|
10
|
+
if (!isNodeRuntime()) return
|
|
11
|
+
|
|
12
|
+
const maybe_process = (
|
|
13
|
+
globalThis as {
|
|
14
|
+
process?: { getBuiltinModule?: (name: string) => any; cwd?: () => string }
|
|
15
|
+
}
|
|
16
|
+
).process
|
|
17
|
+
const get_builtin_module = maybe_process?.getBuiltinModule
|
|
18
|
+
|
|
19
|
+
let require_fn: { resolve: (specifier: string) => string } | undefined
|
|
20
|
+
try {
|
|
21
|
+
require_fn = Function('return typeof require === "function" ? require : undefined')() as
|
|
22
|
+
| { resolve: (specifier: string) => string }
|
|
23
|
+
| undefined
|
|
24
|
+
} catch {
|
|
25
|
+
require_fn = undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!require_fn && typeof get_builtin_module === 'function') {
|
|
29
|
+
const module_builtin = get_builtin_module('module')
|
|
30
|
+
const create_require = module_builtin?.createRequire
|
|
31
|
+
if (typeof create_require === 'function') {
|
|
32
|
+
const cwd = typeof maybe_process?.cwd === 'function' ? maybe_process.cwd() : '.'
|
|
33
|
+
require_fn = create_require(`${cwd}/package.json`) as { resolve: (specifier: string) => string }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!require_fn) return
|
|
38
|
+
try {
|
|
39
|
+
require_fn.resolve(package_name)
|
|
40
|
+
} catch {
|
|
41
|
+
throw missingDependencyError(bridge_name, package_name)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const importOptionalDependency = async (bridge_name: string, package_name: string): Promise<any> => {
|
|
46
|
+
const dynamic_import = Function('module_name', 'return import(module_name)') as (module_name: string) => Promise<unknown>
|
|
47
|
+
try {
|
|
48
|
+
return (await dynamic_import(package_name)) as any
|
|
49
|
+
} catch {
|
|
50
|
+
throw missingDependencyError(bridge_name, package_name)
|
|
51
|
+
}
|
|
52
|
+
}
|