abxbus 2.5.6 → 2.5.11
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/BaseEvent.d.ts +12 -32
- package/dist/cjs/BaseEvent.js +20 -17
- package/dist/cjs/BaseEvent.js.map +2 -2
- package/dist/cjs/LockManager.js +1 -1
- package/dist/cjs/LockManager.js.map +2 -2
- package/dist/cjs/events_suck.d.ts +7 -14
- package/dist/cjs/events_suck.js +1 -1
- package/dist/cjs/events_suck.js.map +2 -2
- package/dist/cjs/retry.d.ts +2 -0
- package/dist/cjs/retry.js +121 -38
- package/dist/cjs/retry.js.map +3 -3
- package/dist/cjs/types.d.ts +3 -6
- package/dist/cjs/types.js +1 -1
- package/dist/cjs/types.js.map +2 -2
- package/dist/esm/BaseEvent.js +20 -17
- package/dist/esm/BaseEvent.js.map +2 -2
- package/dist/esm/LockManager.js +1 -1
- package/dist/esm/LockManager.js.map +2 -2
- package/dist/esm/events_suck.js +1 -1
- package/dist/esm/events_suck.js.map +2 -2
- package/dist/esm/retry.js +121 -38
- package/dist/esm/retry.js.map +3 -3
- package/dist/esm/types.js +1 -1
- package/dist/esm/types.js.map +2 -2
- package/dist/types/BaseEvent.d.ts +12 -32
- package/dist/types/events_suck.d.ts +7 -14
- package/dist/types/retry.d.ts +2 -0
- package/dist/types/types.d.ts +3 -6
- package/package.json +42 -24
- package/src/BaseEvent.ts +93 -75
- package/src/LockManager.ts +1 -1
- package/src/events_suck.ts +17 -20
- package/src/retry.ts +146 -41
- package/src/types.ts +4 -5
- package/dist/cjs/base_event.d.ts +0 -211
- package/dist/cjs/bridge_ipc.d.ts +0 -45
- package/dist/cjs/bridge_jsonl.d.ts +0 -26
- package/dist/cjs/bridge_nats.d.ts +0 -20
- package/dist/cjs/bridge_postgres.d.ts +0 -31
- package/dist/cjs/bridge_redis.d.ts +0 -34
- package/dist/cjs/bridge_sqlite.d.ts +0 -30
- package/dist/cjs/event_bus.d.ts +0 -125
- package/dist/cjs/event_handler.d.ts +0 -140
- package/dist/cjs/event_history.d.ts +0 -45
- package/dist/cjs/event_result.d.ts +0 -86
- package/dist/cjs/lock_manager.d.ts +0 -70
- package/dist/cjs/middleware_otel_tracing.d.ts +0 -49
- package/dist/cjs/type_inference.test.d.ts +0 -1
- package/dist/types/base_event.d.ts +0 -211
- package/dist/types/bridge_ipc.d.ts +0 -45
- package/dist/types/bridge_jsonl.d.ts +0 -26
- package/dist/types/bridge_nats.d.ts +0 -20
- package/dist/types/bridge_postgres.d.ts +0 -31
- package/dist/types/bridge_redis.d.ts +0 -34
- package/dist/types/bridge_sqlite.d.ts +0 -30
- package/dist/types/event_bus.d.ts +0 -125
- package/dist/types/event_handler.d.ts +0 -140
- package/dist/types/event_history.d.ts +0 -45
- package/dist/types/event_result.d.ts +0 -86
- package/dist/types/lock_manager.d.ts +0 -70
- package/dist/types/middleware_otel_tracing.d.ts +0 -49
- package/dist/types/type_inference.test.d.ts +0 -1
package/src/BaseEvent.ts
CHANGED
|
@@ -151,8 +151,7 @@ type EventPayloadShape<TShape extends z.ZodRawShape> = {
|
|
|
151
151
|
}
|
|
152
152
|
type EventPayload<TShape extends z.ZodRawShape> =
|
|
153
153
|
EventPayloadShape<TShape> extends Record<string, never> ? {} : z.infer<z.ZodObject<EventPayloadShape<TShape>>>
|
|
154
|
-
type
|
|
155
|
-
| 'class'
|
|
154
|
+
type EventClassMetadataFieldName =
|
|
156
155
|
| 'fromJSON'
|
|
157
156
|
| 'prototype'
|
|
158
157
|
| 'event_schema'
|
|
@@ -165,7 +164,7 @@ type EventModelFields<TShape extends z.ZodRawShape> = {
|
|
|
165
164
|
readonly [K in keyof TShape]: TShape[K]
|
|
166
165
|
}
|
|
167
166
|
type StaticEventDefaultValues<TShape extends z.ZodRawShape> = {
|
|
168
|
-
readonly [K in keyof TShape as K extends
|
|
167
|
+
readonly [K in keyof TShape as K extends EventClassMetadataFieldName
|
|
169
168
|
? never
|
|
170
169
|
: TShape[K] extends StaticDefaultSchema
|
|
171
170
|
? K
|
|
@@ -243,7 +242,7 @@ type ShortcutZodModelFields<TShape> = {
|
|
|
243
242
|
[K in keyof ShortcutModelFields<TShape>]: ShortcutModelFields<TShape>[K] extends z.ZodTypeAny ? ShortcutModelFields<TShape>[K] : never
|
|
244
243
|
}
|
|
245
244
|
type ShortcutStaticDefaultValues<TShape, TModelFields extends z.ZodRawShape> = StaticEventDefaultValues<TModelFields> & {
|
|
246
|
-
readonly [K in keyof TShape as K extends
|
|
245
|
+
readonly [K in keyof TShape as K extends EventClassMetadataFieldName ? never : TShape[K] extends z.ZodTypeAny ? never : K]: TShape[K]
|
|
247
246
|
}
|
|
248
247
|
export type EventResultInclude<TEvent extends BaseEvent> = (
|
|
249
248
|
result: EventResult<TEvent>['result'],
|
|
@@ -271,45 +270,22 @@ type EventResultUpdateOptions<TEvent extends BaseEvent> = {
|
|
|
271
270
|
|
|
272
271
|
const ROOT_EVENTBUS_ID = '00000000-0000-0000-0000-000000000000'
|
|
273
272
|
|
|
274
|
-
export type
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export type EventFactory<
|
|
283
|
-
TShape extends z.ZodRawShape,
|
|
284
|
-
TResult = unknown,
|
|
285
|
-
TStaticFields = StaticEventDefaultValues<TShape>,
|
|
273
|
+
export type EventClass<
|
|
274
|
+
TEvent extends BaseEvent = BaseEvent,
|
|
275
|
+
TInit = never,
|
|
276
|
+
TSchema extends z.ZodTypeAny = AnyEventSchema,
|
|
277
|
+
TModelFields extends z.ZodRawShape = z.ZodRawShape,
|
|
278
|
+
TResultSchema extends z.ZodTypeAny | undefined = z.ZodTypeAny | undefined,
|
|
279
|
+
TStaticFields = {},
|
|
286
280
|
> = TStaticFields & {
|
|
287
|
-
(...args: OptionalFactoryArgs<
|
|
288
|
-
new (...args: OptionalFactoryArgs<
|
|
289
|
-
event_schema: EventSchema<TShape>
|
|
290
|
-
model_fields: EventModelFields<TShape>
|
|
291
|
-
class: EventFactoryClass<TShape, TResult>
|
|
292
|
-
event_type: string
|
|
293
|
-
event_version: string
|
|
294
|
-
event_result_type: ResultTypeSchemaFromShape<TShape>
|
|
295
|
-
fromJSON: (data: unknown) => EventWithResultSchema<TResult> & EventPayload<TShape>
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export type SchemaEventFactoryClass<TSchema extends AnyEventSchema, TResult = unknown> = (new (
|
|
299
|
-
...args: OptionalFactoryArgs<EventInitFromSchema<TSchema>>
|
|
300
|
-
) => EventWithResultSchema<TResult> & EventPayloadFromSchema<TSchema>) &
|
|
301
|
-
StaticEventDefaultValuesFromSchema<TSchema> & { event_schema: TSchema; model_fields: EventModelFieldsFromSchema<TSchema> }
|
|
302
|
-
|
|
303
|
-
export type SchemaEventFactory<TSchema extends AnyEventSchema, TResult = unknown> = StaticEventDefaultValuesFromSchema<TSchema> & {
|
|
304
|
-
(...args: OptionalFactoryArgs<EventInitFromSchema<TSchema>>): EventWithResultSchema<TResult> & EventPayloadFromSchema<TSchema>
|
|
305
|
-
new (...args: OptionalFactoryArgs<EventInitFromSchema<TSchema>>): EventWithResultSchema<TResult> & EventPayloadFromSchema<TSchema>
|
|
281
|
+
(...args: OptionalFactoryArgs<TInit>): TEvent
|
|
282
|
+
new (...args: OptionalFactoryArgs<TInit>): TEvent
|
|
306
283
|
event_schema: TSchema
|
|
307
|
-
model_fields:
|
|
308
|
-
class: SchemaEventFactoryClass<TSchema, TResult>
|
|
284
|
+
model_fields: TModelFields
|
|
309
285
|
event_type: string
|
|
310
286
|
event_version: string
|
|
311
|
-
event_result_type:
|
|
312
|
-
fromJSON: (data: unknown) =>
|
|
287
|
+
event_result_type: TResultSchema
|
|
288
|
+
fromJSON: (data: unknown) => TEvent
|
|
313
289
|
}
|
|
314
290
|
|
|
315
291
|
type ZodShapeFrom<TShape extends Record<string, unknown>> = {
|
|
@@ -680,20 +656,56 @@ export class BaseEvent {
|
|
|
680
656
|
static extend<TSchema extends z.ZodObject<z.ZodRawShape>>(
|
|
681
657
|
event_type: string,
|
|
682
658
|
event_schema: TSchema
|
|
683
|
-
):
|
|
659
|
+
): EventClass<
|
|
660
|
+
EventWithResultSchema<ResultSchemaFromEventSchema<TSchema>> & EventPayloadFromSchema<TSchema>,
|
|
661
|
+
EventInitFromSchema<TSchema>,
|
|
662
|
+
TSchema,
|
|
663
|
+
EventModelFieldsFromSchema<TSchema>,
|
|
664
|
+
ResultTypeSchemaFromEventSchema<TSchema>,
|
|
665
|
+
StaticEventDefaultValuesFromSchema<TSchema>
|
|
666
|
+
>
|
|
684
667
|
static extend<const TShape extends Record<string, unknown>>(
|
|
685
668
|
event_type: string,
|
|
686
669
|
shape?: TShape
|
|
687
|
-
):
|
|
688
|
-
ShortcutZodModelFields<TShape
|
|
689
|
-
|
|
670
|
+
): EventClass<
|
|
671
|
+
EventWithResultSchema<ResultSchemaFromShape<ShortcutZodModelFields<TShape>>> & EventPayload<ShortcutZodModelFields<TShape>>,
|
|
672
|
+
EventInit<ShortcutZodModelFields<TShape>>,
|
|
673
|
+
EventSchema<ShortcutZodModelFields<TShape>>,
|
|
674
|
+
EventModelFields<ShortcutZodModelFields<TShape>>,
|
|
675
|
+
ResultTypeSchemaFromShape<ShortcutZodModelFields<TShape>>,
|
|
690
676
|
ShortcutStaticDefaultValues<TShape, ShortcutZodModelFields<TShape>>
|
|
691
677
|
>
|
|
692
|
-
static extend<TShape extends z.ZodRawShape>(
|
|
678
|
+
static extend<TShape extends z.ZodRawShape>(
|
|
679
|
+
event_type: string,
|
|
680
|
+
shape?: TShape
|
|
681
|
+
): EventClass<
|
|
682
|
+
EventWithResultSchema<ResultSchemaFromShape<TShape>> & EventPayload<TShape>,
|
|
683
|
+
EventInit<TShape>,
|
|
684
|
+
EventSchema<TShape>,
|
|
685
|
+
EventModelFields<TShape>,
|
|
686
|
+
ResultTypeSchemaFromShape<TShape>,
|
|
687
|
+
StaticEventDefaultValues<TShape>
|
|
688
|
+
>
|
|
693
689
|
static extend<const TShape extends Record<string, unknown>>(
|
|
694
690
|
event_type: string,
|
|
695
691
|
shape?: TShape
|
|
696
|
-
):
|
|
692
|
+
):
|
|
693
|
+
| EventClass<
|
|
694
|
+
EventWithResultSchema<ResultSchemaFromShape<ZodShapeFrom<TShape>>> & EventPayload<ZodShapeFrom<TShape>>,
|
|
695
|
+
EventInit<ZodShapeFrom<TShape>>,
|
|
696
|
+
EventSchema<ZodShapeFrom<TShape>>,
|
|
697
|
+
EventModelFields<ZodShapeFrom<TShape>>,
|
|
698
|
+
ResultTypeSchemaFromShape<ZodShapeFrom<TShape>>,
|
|
699
|
+
StaticEventDefaultValues<ZodShapeFrom<TShape>>
|
|
700
|
+
>
|
|
701
|
+
| EventClass<
|
|
702
|
+
EventWithResultSchema<ResultSchemaFromEventSchema<AnyEventSchema>> & EventPayloadFromSchema<AnyEventSchema>,
|
|
703
|
+
EventInitFromSchema<AnyEventSchema>,
|
|
704
|
+
AnyEventSchema,
|
|
705
|
+
EventModelFieldsFromSchema<AnyEventSchema>,
|
|
706
|
+
ResultTypeSchemaFromEventSchema<AnyEventSchema>,
|
|
707
|
+
StaticEventDefaultValuesFromSchema<AnyEventSchema>
|
|
708
|
+
> {
|
|
697
709
|
const built = buildFullEventSchema(event_type, shape ?? {})
|
|
698
710
|
const full_schema = built.event_schema
|
|
699
711
|
const event_parse_schema = built.event_parse_schema
|
|
@@ -701,41 +713,47 @@ export class BaseEvent {
|
|
|
701
713
|
const event_result_type = built.event_result_type
|
|
702
714
|
const event_version = built.event_version
|
|
703
715
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
static
|
|
707
|
-
static
|
|
708
|
-
static
|
|
709
|
-
static
|
|
710
|
-
static
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
super(data as BaseEventInit<Record<string, unknown>>)
|
|
716
|
+
const EventClass = class extends BaseEvent {
|
|
717
|
+
static override event_schema = full_schema as EventSchema<ZodShapeFrom<TShape>>
|
|
718
|
+
static override model_fields = full_schema.shape as EventModelFields<ZodShapeFrom<TShape>>
|
|
719
|
+
static override _event_parse_schema = event_parse_schema
|
|
720
|
+
static override event_type = event_type
|
|
721
|
+
static override event_version = event_version ?? BaseEvent.event_version
|
|
722
|
+
static override event_result_type = event_result_type
|
|
723
|
+
|
|
724
|
+
constructor(data?: EventInit<ZodShapeFrom<TShape>>) {
|
|
725
|
+
super(data)
|
|
715
726
|
}
|
|
716
727
|
}
|
|
717
728
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
function EventFactory(data?: EventInit<ZodShapeFrom<TShape>>): FactoryResult {
|
|
721
|
-
return new ExtendedEvent(data) as FactoryResult
|
|
722
|
-
}
|
|
729
|
+
Object.defineProperty(EventClass, 'name', { value: event_type, configurable: true })
|
|
730
|
+
defineStaticEventFields(EventClass, static_field_defaults)
|
|
723
731
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
EventFactory.prototype = ExtendedEvent.prototype
|
|
734
|
-
defineStaticEventFields(ExtendedEvent, static_field_defaults)
|
|
735
|
-
defineStaticEventFields(EventFactory, static_field_defaults)
|
|
736
|
-
EVENT_TYPE_REGISTRY.set(event_type, ExtendedEvent)
|
|
732
|
+
let CallableEventClass: typeof EventClass
|
|
733
|
+
CallableEventClass = new Proxy(EventClass, {
|
|
734
|
+
apply(target, _this_arg, args) {
|
|
735
|
+
return Reflect.construct(target, args, target)
|
|
736
|
+
},
|
|
737
|
+
construct(target, args, new_target) {
|
|
738
|
+
return Reflect.construct(target, args, new_target === CallableEventClass ? target : new_target)
|
|
739
|
+
},
|
|
740
|
+
})
|
|
737
741
|
|
|
738
|
-
|
|
742
|
+
Object.defineProperty(EventClass.prototype, 'constructor', {
|
|
743
|
+
value: CallableEventClass,
|
|
744
|
+
writable: true,
|
|
745
|
+
configurable: true,
|
|
746
|
+
})
|
|
747
|
+
EVENT_TYPE_REGISTRY.set(event_type, CallableEventClass as unknown as typeof BaseEvent)
|
|
748
|
+
|
|
749
|
+
return CallableEventClass as unknown as EventClass<
|
|
750
|
+
EventWithResultSchema<ResultSchemaFromShape<ZodShapeFrom<TShape>>> & EventPayload<ZodShapeFrom<TShape>>,
|
|
751
|
+
EventInit<ZodShapeFrom<TShape>>,
|
|
752
|
+
EventSchema<ZodShapeFrom<TShape>>,
|
|
753
|
+
EventModelFields<ZodShapeFrom<TShape>>,
|
|
754
|
+
ResultTypeSchemaFromShape<ZodShapeFrom<TShape>>,
|
|
755
|
+
StaticEventDefaultValues<ZodShapeFrom<TShape>>
|
|
756
|
+
>
|
|
739
757
|
}
|
|
740
758
|
|
|
741
759
|
static fromJSON<T extends typeof BaseEvent>(this: T, data: unknown): InstanceType<T> {
|
package/src/LockManager.ts
CHANGED
|
@@ -283,7 +283,7 @@ export class LockManager {
|
|
|
283
283
|
this.idle_waiters.push(finish)
|
|
284
284
|
this.scheduleIdleCheck()
|
|
285
285
|
|
|
286
|
-
if (timeout_seconds === null || timeout_seconds === undefined) {
|
|
286
|
+
if (timeout_seconds === null || timeout_seconds === undefined || timeout_seconds <= 0) {
|
|
287
287
|
return
|
|
288
288
|
}
|
|
289
289
|
|
package/src/events_suck.ts
CHANGED
|
@@ -3,28 +3,22 @@ import { BaseEvent } from './BaseEvent.js'
|
|
|
3
3
|
|
|
4
4
|
import type { EventClass, EventResultType } from './types.js'
|
|
5
5
|
|
|
6
|
-
type EventMap = Record<string, EventClass<BaseEvent>>
|
|
7
|
-
type
|
|
8
|
-
type FunctionMap = Record<string, AnyFn>
|
|
6
|
+
type EventMap = Record<string, EventClass<BaseEvent, never>>
|
|
7
|
+
type FunctionMap = Record<string, (...args: never[]) => unknown>
|
|
9
8
|
type ExtraDict = Record<string, unknown>
|
|
10
9
|
|
|
11
|
-
type EventFieldsFromFn<TFunc extends
|
|
12
|
-
Parameters<TFunc> extends [infer TArg] ? (TArg extends Record<string, unknown> ? TArg : ExtraDict) : ExtraDict
|
|
13
|
-
|
|
14
|
-
type
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
new (
|
|
19
|
-
data: EventFieldsFromFn<TFunc> & ExtraDict
|
|
20
|
-
): BaseEvent & EventFieldsFromFn<TFunc> & { __event_result_type__?: Awaited<ReturnType<TFunc>> }
|
|
21
|
-
event_type?: string
|
|
22
|
-
}
|
|
10
|
+
type EventFieldsFromFn<TFunc extends FunctionMap[string]> =
|
|
11
|
+
Parameters<TFunc> extends [infer TArg, ...unknown[]] ? (TArg extends Record<string, unknown> ? TArg : ExtraDict) : ExtraDict
|
|
12
|
+
|
|
13
|
+
type EventFromFn<TFunc extends FunctionMap[string]> = BaseEvent &
|
|
14
|
+
EventFieldsFromFn<TFunc> & {
|
|
15
|
+
__event_result_type__?: Awaited<ReturnType<TFunc>>
|
|
16
|
+
}
|
|
23
17
|
|
|
24
18
|
export type GeneratedEvents<TEvents extends FunctionMap> = {
|
|
25
|
-
by_name: { [K in keyof TEvents]:
|
|
19
|
+
by_name: { [K in keyof TEvents]: EventClass<EventFromFn<TEvents[K]>, EventFieldsFromFn<TEvents[K]> & ExtraDict> }
|
|
26
20
|
} & {
|
|
27
|
-
[K in keyof TEvents]:
|
|
21
|
+
[K in keyof TEvents]: EventClass<EventFromFn<TEvents[K]>, EventFieldsFromFn<TEvents[K]> & ExtraDict>
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
type EventInit<TEventClass extends EventClass<BaseEvent>> = [ConstructorParameters<TEventClass>[0]] extends [undefined]
|
|
@@ -51,12 +45,12 @@ type DynamicWrappedClient = {
|
|
|
51
45
|
} & Record<string, (...args: unknown[]) => Promise<unknown>>
|
|
52
46
|
|
|
53
47
|
export const make_events = <TEvents extends FunctionMap>(events: TEvents): GeneratedEvents<TEvents> => {
|
|
54
|
-
const by_name = {} as
|
|
48
|
+
const by_name = {} as GeneratedEvents<TEvents>['by_name']
|
|
55
49
|
for (const [event_name] of Object.entries(events) as Array<[keyof TEvents, TEvents[keyof TEvents]]>) {
|
|
56
50
|
if (!/^[A-Za-z_$][\w$]*$/.test(String(event_name))) {
|
|
57
51
|
throw new Error(`Invalid event name: ${String(event_name)}`)
|
|
58
52
|
}
|
|
59
|
-
by_name[event_name] = BaseEvent.extend(String(event_name), {}) as unknown as
|
|
53
|
+
by_name[event_name] = BaseEvent.extend(String(event_name), {}) as unknown as GeneratedEvents<TEvents>['by_name'][typeof event_name]
|
|
60
54
|
}
|
|
61
55
|
return Object.assign({ by_name }, by_name) as GeneratedEvents<TEvents>
|
|
62
56
|
}
|
|
@@ -76,7 +70,10 @@ export const wrap = <TEvents extends EventMap>(class_name: string, methods: TEve
|
|
|
76
70
|
Object.defineProperty(WrappedClient.prototype, method_name, {
|
|
77
71
|
value: async function (this: DynamicWrappedClient, init?: Record<string, unknown>, extra?: Record<string, unknown>) {
|
|
78
72
|
const payload = { ...(init ?? {}), ...(extra ?? {}) }
|
|
79
|
-
return await this.bus
|
|
73
|
+
return await this.bus
|
|
74
|
+
.emit((EventCtor as EventClass<BaseEvent, Record<string, unknown>>)(payload))
|
|
75
|
+
.now({ first_result: true })
|
|
76
|
+
.eventResult()
|
|
80
77
|
},
|
|
81
78
|
writable: true,
|
|
82
79
|
configurable: true,
|
package/src/retry.ts
CHANGED
|
@@ -19,8 +19,9 @@ type RetryDecorator = {
|
|
|
19
19
|
(target: object, property_key: string | symbol, descriptor: LegacyMethodDescriptor): LegacyMethodDescriptor
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const MULTIPROCESS_SEMAPHORE_DIRNAME = 'browser_use_semaphores'
|
|
23
22
|
const MULTIPROCESS_STALE_LOCK_MS = 5 * 60 * 1000
|
|
23
|
+
const RETRY_SLOW_WARNING_THROTTLE_MS = 2000
|
|
24
|
+
const RETRY_SLOW_WARNING_ARGS_MAX_LENGTH = 80
|
|
24
25
|
|
|
25
26
|
let multiprocess_fallback_reason_logged: string | null = null
|
|
26
27
|
|
|
@@ -44,6 +45,9 @@ export interface RetryOptions {
|
|
|
44
45
|
/** Per-attempt timeout in seconds. Default: undefined (no per-attempt timeout) */
|
|
45
46
|
timeout?: number | null
|
|
46
47
|
|
|
48
|
+
/** Emit a warning when a decorated call exceeds this many seconds. Default: undefined (disabled) */
|
|
49
|
+
slow_timeout?: number | null
|
|
50
|
+
|
|
47
51
|
/** Maximum concurrent executions sharing this semaphore. Default: undefined (no concurrency limit) */
|
|
48
52
|
semaphore_limit?: number | null
|
|
49
53
|
|
|
@@ -238,6 +242,7 @@ export function retry(options: RetryOptions = {}): RetryDecorator {
|
|
|
238
242
|
retry_backoff_factor = 1.0,
|
|
239
243
|
retry_on_errors,
|
|
240
244
|
timeout,
|
|
245
|
+
slow_timeout,
|
|
241
246
|
semaphore_limit,
|
|
242
247
|
semaphore_name: semaphore_name_option,
|
|
243
248
|
semaphore_lax = true,
|
|
@@ -245,10 +250,13 @@ export function retry(options: RetryOptions = {}): RetryDecorator {
|
|
|
245
250
|
semaphore_timeout,
|
|
246
251
|
} = options
|
|
247
252
|
|
|
248
|
-
const decorateFunction = <T extends AnyFunction>(target: T, _context?: ClassMethodDecoratorContext): T => {
|
|
249
|
-
const
|
|
253
|
+
const decorateFunction = <T extends AnyFunction>(target: T, _context?: ClassMethodDecoratorContext, owner_name?: string | null): T => {
|
|
254
|
+
const base_fn_name = target.name || (_context?.name as string) || 'anonymous'
|
|
255
|
+
let fn_name = owner_name ? `${owner_name}.${base_fn_name}` : base_fn_name
|
|
250
256
|
const effective_max_attempts = Math.max(1, max_attempts)
|
|
251
257
|
const effective_retry_after = Math.max(0, retry_after)
|
|
258
|
+
const effective_slow_timeout_ms = slow_timeout != null && slow_timeout > 0 ? slow_timeout * 1000 : null
|
|
259
|
+
let last_slow_warning_at = 0
|
|
252
260
|
|
|
253
261
|
const shouldRetry = (error: unknown): boolean => {
|
|
254
262
|
if (!retry_on_errors || retry_on_errors.length === 0) return true
|
|
@@ -275,6 +283,14 @@ export function retry(options: RetryOptions = {}): RetryDecorator {
|
|
|
275
283
|
}
|
|
276
284
|
}
|
|
277
285
|
|
|
286
|
+
const emitSlowWarningIfDue = (args: any[], start_time: number): void => {
|
|
287
|
+
if (effective_slow_timeout_ms == null) return
|
|
288
|
+
const now = Date.now()
|
|
289
|
+
if (now - last_slow_warning_at < RETRY_SLOW_WARNING_THROTTLE_MS) return
|
|
290
|
+
last_slow_warning_at = now
|
|
291
|
+
console.warn(`Warning: ${fn_name}(${formatRetrySlowWarningArgs(args)}) slow (${((now - start_time) / 1000).toFixed(1)}s)`)
|
|
292
|
+
}
|
|
293
|
+
|
|
278
294
|
const runRetryLoopFromThenable = async (
|
|
279
295
|
this_arg: any,
|
|
280
296
|
args: any[],
|
|
@@ -373,26 +389,45 @@ export function retry(options: RetryOptions = {}): RetryDecorator {
|
|
|
373
389
|
|
|
374
390
|
// ── Retry loop (runs inside the semaphore and re-entrancy context) ──
|
|
375
391
|
const runRetryLoop = async (): Promise<any> => {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
392
|
+
const call_started_at = Date.now()
|
|
393
|
+
const warning_args = [...args]
|
|
394
|
+
const slow_warning_timer =
|
|
395
|
+
effective_slow_timeout_ms == null
|
|
396
|
+
? null
|
|
397
|
+
: setTimeout(() => emitSlowWarningIfDue(warning_args, call_started_at), effective_slow_timeout_ms)
|
|
398
|
+
const finishSlowWarning = (): void => {
|
|
399
|
+
if (slow_warning_timer !== null) {
|
|
400
|
+
clearTimeout(slow_warning_timer)
|
|
401
|
+
}
|
|
402
|
+
if (effective_slow_timeout_ms != null && Date.now() - call_started_at >= effective_slow_timeout_ms) {
|
|
403
|
+
emitSlowWarningIfDue(warning_args, call_started_at)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
385
406
|
|
|
386
|
-
|
|
387
|
-
|
|
407
|
+
try {
|
|
408
|
+
for (let attempt = 1; attempt <= effective_max_attempts; attempt++) {
|
|
409
|
+
try {
|
|
410
|
+
if (timeout != null && timeout > 0) {
|
|
411
|
+
return await _runWithTimeout(() => Promise.resolve(target.apply(this, args)), timeout * 1000, attempt)
|
|
412
|
+
} else {
|
|
413
|
+
return await Promise.resolve(target.apply(this, args))
|
|
414
|
+
}
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (!shouldRetry(error)) throw error
|
|
417
|
+
|
|
418
|
+
// Last attempt: rethrow
|
|
419
|
+
if (attempt >= effective_max_attempts) throw error
|
|
388
420
|
|
|
389
|
-
|
|
390
|
-
|
|
421
|
+
// Wait before next attempt with exponential backoff
|
|
422
|
+
await asyncRetryDelay(attempt)
|
|
423
|
+
}
|
|
391
424
|
}
|
|
392
|
-
}
|
|
393
425
|
|
|
394
|
-
|
|
395
|
-
|
|
426
|
+
// Unreachable, but satisfies the type checker
|
|
427
|
+
throw new Error(`retry(${fn_name}): unexpected end of retry loop`)
|
|
428
|
+
} finally {
|
|
429
|
+
finishSlowWarning()
|
|
430
|
+
}
|
|
396
431
|
}
|
|
397
432
|
|
|
398
433
|
try {
|
|
@@ -452,27 +487,52 @@ export function retry(options: RetryOptions = {}): RetryDecorator {
|
|
|
452
487
|
}
|
|
453
488
|
|
|
454
489
|
const runRetryLoop = (): any => {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
490
|
+
const call_started_at = Date.now()
|
|
491
|
+
const warning_args = [...args]
|
|
492
|
+
const slow_warning_timer =
|
|
493
|
+
effective_slow_timeout_ms == null
|
|
494
|
+
? null
|
|
495
|
+
: setTimeout(() => emitSlowWarningIfDue(warning_args, call_started_at), effective_slow_timeout_ms)
|
|
496
|
+
const finishSlowWarning = (): void => {
|
|
497
|
+
if (slow_warning_timer !== null) {
|
|
498
|
+
clearTimeout(slow_warning_timer)
|
|
499
|
+
}
|
|
500
|
+
if (effective_slow_timeout_ms != null && Date.now() - call_started_at >= effective_slow_timeout_ms) {
|
|
501
|
+
emitSlowWarningIfDue(warning_args, call_started_at)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let finish_on_return = true
|
|
506
|
+
try {
|
|
507
|
+
for (let attempt = 1; attempt <= effective_max_attempts; attempt++) {
|
|
508
|
+
const attempt_started_at = Date.now()
|
|
509
|
+
try {
|
|
510
|
+
const result = target.apply(this, args)
|
|
511
|
+
if (isThenable(result)) {
|
|
512
|
+
finish_on_return = false
|
|
513
|
+
return runRetryLoopFromThenable(this, args, result, attempt).finally(finishSlowWarning)
|
|
514
|
+
}
|
|
515
|
+
if (timeout != null && timeout > 0 && Date.now() - attempt_started_at > timeout * 1000) {
|
|
516
|
+
throw new RetryTimeoutError(`Timed out after ${timeout}s (attempt ${attempt})`, {
|
|
517
|
+
timeout_seconds: timeout,
|
|
518
|
+
attempt,
|
|
519
|
+
})
|
|
520
|
+
}
|
|
521
|
+
return result
|
|
522
|
+
} catch (error) {
|
|
523
|
+
if (!shouldRetry(error)) throw error
|
|
524
|
+
if (attempt >= effective_max_attempts) {
|
|
525
|
+
throw error
|
|
526
|
+
}
|
|
527
|
+
syncRetryDelay(attempt)
|
|
467
528
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
529
|
+
}
|
|
530
|
+
throw new Error(`retry(${fn_name}): unexpected end of retry loop`)
|
|
531
|
+
} finally {
|
|
532
|
+
if (finish_on_return) {
|
|
533
|
+
finishSlowWarning()
|
|
473
534
|
}
|
|
474
535
|
}
|
|
475
|
-
throw new Error(`retry(${fn_name}): unexpected end of retry loop`)
|
|
476
536
|
}
|
|
477
537
|
|
|
478
538
|
try {
|
|
@@ -494,7 +554,8 @@ export function retry(options: RetryOptions = {}): RetryDecorator {
|
|
|
494
554
|
_context.addInitializer(function (this: unknown) {
|
|
495
555
|
const owner_name = findDecoratedMethodOwnerName(this, _context, retryWrapper)
|
|
496
556
|
if (owner_name) {
|
|
497
|
-
|
|
557
|
+
fn_name = `${owner_name}.${target.name || (_context.name as string) || 'anonymous'}`
|
|
558
|
+
Object.defineProperty(retryWrapper, 'name', { value: fn_name, configurable: true })
|
|
498
559
|
}
|
|
499
560
|
})
|
|
500
561
|
}
|
|
@@ -507,7 +568,13 @@ export function retry(options: RetryOptions = {}): RetryDecorator {
|
|
|
507
568
|
descriptor?: LegacyMethodDescriptor
|
|
508
569
|
): T | LegacyMethodDescriptor {
|
|
509
570
|
if (descriptor?.value && typeof descriptor.value === 'function') {
|
|
510
|
-
|
|
571
|
+
const owner_name =
|
|
572
|
+
target && (typeof target === 'object' || typeof target === 'function')
|
|
573
|
+
? typeof target === 'function'
|
|
574
|
+
? target.name
|
|
575
|
+
: (target as { constructor?: { name?: string } }).constructor?.name
|
|
576
|
+
: null
|
|
577
|
+
descriptor.value = decorateFunction(descriptor.value, undefined, owner_name)
|
|
511
578
|
return descriptor
|
|
512
579
|
}
|
|
513
580
|
if (typeof target === 'function') {
|
|
@@ -569,6 +636,32 @@ function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
|
569
636
|
)
|
|
570
637
|
}
|
|
571
638
|
|
|
639
|
+
function formatRetrySlowWarningArgs(args: any[]): string {
|
|
640
|
+
const preview = args.map(formatRetrySlowWarningValue).join(', ')
|
|
641
|
+
if (preview.length > RETRY_SLOW_WARNING_ARGS_MAX_LENGTH) {
|
|
642
|
+
return `${preview.slice(0, RETRY_SLOW_WARNING_ARGS_MAX_LENGTH - 3).replace(/,?\s*$/, '')}...`
|
|
643
|
+
}
|
|
644
|
+
return preview
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function formatRetrySlowWarningValue(value: unknown): string {
|
|
648
|
+
let text: string
|
|
649
|
+
if (typeof value === 'string') {
|
|
650
|
+
text = value
|
|
651
|
+
} else if (value === null || value === undefined) {
|
|
652
|
+
text = String(value)
|
|
653
|
+
} else if (typeof value === 'object') {
|
|
654
|
+
try {
|
|
655
|
+
text = JSON.stringify(value)
|
|
656
|
+
} catch {
|
|
657
|
+
text = String(value)
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
text = String(value)
|
|
661
|
+
}
|
|
662
|
+
return text.replace(/['"]/g, '').slice(0, 3)
|
|
663
|
+
}
|
|
664
|
+
|
|
572
665
|
/**
|
|
573
666
|
* Try to acquire a semaphore within a timeout. Returns true if acquired, false if timed out.
|
|
574
667
|
* If the semaphore is acquired after the timeout (due to the waiter remaining queued),
|
|
@@ -667,6 +760,18 @@ async function importNodeModule(specifier: string): Promise<any> {
|
|
|
667
760
|
return dynamic_import(specifier) as Promise<any>
|
|
668
761
|
}
|
|
669
762
|
|
|
763
|
+
function resolveMultiprocessSemaphoreDirectory(path: any, os: any): string {
|
|
764
|
+
const configured = process.env.ABXBUS_MULTIPROCESS_SEMAPHORE_DIR
|
|
765
|
+
if (configured) return path.resolve(configured.replace(/^~(?=$|[/\\])/, os.homedir()))
|
|
766
|
+
|
|
767
|
+
const runtime_dir = process.env.XDG_RUNTIME_DIR
|
|
768
|
+
if (runtime_dir) return path.join(path.resolve(runtime_dir.replace(/^~(?=$|[/\\])/, os.homedir())), 'abxbus', 'semaphores')
|
|
769
|
+
|
|
770
|
+
const cache_home = process.env.XDG_CACHE_HOME
|
|
771
|
+
const cache_dir = cache_home ? path.resolve(cache_home.replace(/^~(?=$|[/\\])/, os.homedir())) : path.join(os.homedir(), '.cache')
|
|
772
|
+
return path.join(cache_dir, 'abxbus', 'semaphores')
|
|
773
|
+
}
|
|
774
|
+
|
|
670
775
|
async function acquireMultiprocessSemaphore(
|
|
671
776
|
scoped_key: string,
|
|
672
777
|
semaphore_limit: number,
|
|
@@ -679,7 +784,7 @@ async function acquireMultiprocessSemaphore(
|
|
|
679
784
|
importNodeModule('node:os'),
|
|
680
785
|
importNodeModule('node:path'),
|
|
681
786
|
])
|
|
682
|
-
const semaphore_directory = path
|
|
787
|
+
const semaphore_directory = resolveMultiprocessSemaphoreDirectory(path, os)
|
|
683
788
|
const lock_prefix = crypto.createHash('sha256').update(scoped_key).digest('hex').slice(0, 40)
|
|
684
789
|
fs.mkdirSync(semaphore_directory, { recursive: true })
|
|
685
790
|
|
|
@@ -777,7 +882,7 @@ function acquireMultiprocessSemaphoreSync(
|
|
|
777
882
|
const fs = importNodeModuleSync('node:fs')
|
|
778
883
|
const os = importNodeModuleSync('node:os')
|
|
779
884
|
const path = importNodeModuleSync('node:path')
|
|
780
|
-
const semaphore_directory = path
|
|
885
|
+
const semaphore_directory = resolveMultiprocessSemaphoreDirectory(path, os)
|
|
781
886
|
const lock_prefix = crypto.createHash('sha256').update(scoped_key).digest('hex').slice(0, 40)
|
|
782
887
|
fs.mkdirSync(semaphore_directory, { recursive: true })
|
|
783
888
|
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import type { BaseEvent } from './BaseEvent.js'
|
|
2
|
+
import type { BaseEvent, EventClass as BaseEventClass } from './BaseEvent.js'
|
|
3
3
|
import { fromJsonSchema, isJsonSchema, type JsonSchema } from './jsonschema.js'
|
|
4
4
|
|
|
5
5
|
export type EventStatus = 'pending' | 'started' | 'completed'
|
|
6
|
+
export type { EventClass } from './BaseEvent.js'
|
|
6
7
|
|
|
7
|
-
export type
|
|
8
|
-
|
|
9
|
-
export type EventPattern<T extends BaseEvent = BaseEvent> = string | EventClass<T>
|
|
8
|
+
export type EventPattern<T extends BaseEvent = BaseEvent> = string | BaseEventClass<T>
|
|
10
9
|
|
|
11
10
|
export type EventWithResultSchema<TResult> = BaseEvent & { __event_result_type__?: TResult }
|
|
12
11
|
|
|
@@ -75,7 +74,7 @@ export const normalizeEventPattern = (event_pattern: EventPattern | '*'): string
|
|
|
75
74
|
} catch {
|
|
76
75
|
preview = String(event_pattern).slice(0, 30)
|
|
77
76
|
}
|
|
78
|
-
throw new Error('bus.on(match_pattern, ...) must be a string event type, "*", or a BaseEvent class, got: ' + preview)
|
|
77
|
+
throw new Error('bus.on(match_pattern, ...) must be a string event type, "*", or a BaseEvent.extend() event class, got: ' + preview)
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
export const isZodSchema = (value: unknown): value is z.ZodTypeAny => !!value && typeof (value as z.ZodTypeAny).safeParse === 'function'
|