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.
Files changed (62) hide show
  1. package/dist/cjs/BaseEvent.d.ts +12 -32
  2. package/dist/cjs/BaseEvent.js +20 -17
  3. package/dist/cjs/BaseEvent.js.map +2 -2
  4. package/dist/cjs/LockManager.js +1 -1
  5. package/dist/cjs/LockManager.js.map +2 -2
  6. package/dist/cjs/events_suck.d.ts +7 -14
  7. package/dist/cjs/events_suck.js +1 -1
  8. package/dist/cjs/events_suck.js.map +2 -2
  9. package/dist/cjs/retry.d.ts +2 -0
  10. package/dist/cjs/retry.js +121 -38
  11. package/dist/cjs/retry.js.map +3 -3
  12. package/dist/cjs/types.d.ts +3 -6
  13. package/dist/cjs/types.js +1 -1
  14. package/dist/cjs/types.js.map +2 -2
  15. package/dist/esm/BaseEvent.js +20 -17
  16. package/dist/esm/BaseEvent.js.map +2 -2
  17. package/dist/esm/LockManager.js +1 -1
  18. package/dist/esm/LockManager.js.map +2 -2
  19. package/dist/esm/events_suck.js +1 -1
  20. package/dist/esm/events_suck.js.map +2 -2
  21. package/dist/esm/retry.js +121 -38
  22. package/dist/esm/retry.js.map +3 -3
  23. package/dist/esm/types.js +1 -1
  24. package/dist/esm/types.js.map +2 -2
  25. package/dist/types/BaseEvent.d.ts +12 -32
  26. package/dist/types/events_suck.d.ts +7 -14
  27. package/dist/types/retry.d.ts +2 -0
  28. package/dist/types/types.d.ts +3 -6
  29. package/package.json +42 -24
  30. package/src/BaseEvent.ts +93 -75
  31. package/src/LockManager.ts +1 -1
  32. package/src/events_suck.ts +17 -20
  33. package/src/retry.ts +146 -41
  34. package/src/types.ts +4 -5
  35. package/dist/cjs/base_event.d.ts +0 -211
  36. package/dist/cjs/bridge_ipc.d.ts +0 -45
  37. package/dist/cjs/bridge_jsonl.d.ts +0 -26
  38. package/dist/cjs/bridge_nats.d.ts +0 -20
  39. package/dist/cjs/bridge_postgres.d.ts +0 -31
  40. package/dist/cjs/bridge_redis.d.ts +0 -34
  41. package/dist/cjs/bridge_sqlite.d.ts +0 -30
  42. package/dist/cjs/event_bus.d.ts +0 -125
  43. package/dist/cjs/event_handler.d.ts +0 -140
  44. package/dist/cjs/event_history.d.ts +0 -45
  45. package/dist/cjs/event_result.d.ts +0 -86
  46. package/dist/cjs/lock_manager.d.ts +0 -70
  47. package/dist/cjs/middleware_otel_tracing.d.ts +0 -49
  48. package/dist/cjs/type_inference.test.d.ts +0 -1
  49. package/dist/types/base_event.d.ts +0 -211
  50. package/dist/types/bridge_ipc.d.ts +0 -45
  51. package/dist/types/bridge_jsonl.d.ts +0 -26
  52. package/dist/types/bridge_nats.d.ts +0 -20
  53. package/dist/types/bridge_postgres.d.ts +0 -31
  54. package/dist/types/bridge_redis.d.ts +0 -34
  55. package/dist/types/bridge_sqlite.d.ts +0 -30
  56. package/dist/types/event_bus.d.ts +0 -125
  57. package/dist/types/event_handler.d.ts +0 -140
  58. package/dist/types/event_history.d.ts +0 -45
  59. package/dist/types/event_result.d.ts +0 -86
  60. package/dist/types/lock_manager.d.ts +0 -70
  61. package/dist/types/middleware_otel_tracing.d.ts +0 -49
  62. 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 EventFactoryMetadataFieldName =
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 EventFactoryMetadataFieldName
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 EventFactoryMetadataFieldName ? never : TShape[K] extends z.ZodTypeAny ? never : K]: TShape[K]
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 EventFactoryClass<TShape extends z.ZodRawShape, TResult = unknown> = (new (
275
- ...args: OptionalFactoryArgs<EventInit<TShape>>
276
- ) => EventWithResultSchema<TResult> & EventPayload<TShape>) &
277
- StaticEventDefaultValues<TShape> & {
278
- event_schema: EventSchema<TShape>
279
- model_fields: EventModelFields<TShape>
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<EventInit<TShape>>): EventWithResultSchema<TResult> & EventPayload<TShape>
288
- new (...args: OptionalFactoryArgs<EventInit<TShape>>): EventWithResultSchema<TResult> & EventPayload<TShape>
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: EventModelFieldsFromSchema<TSchema>
308
- class: SchemaEventFactoryClass<TSchema, TResult>
284
+ model_fields: TModelFields
309
285
  event_type: string
310
286
  event_version: string
311
- event_result_type: ResultTypeSchemaFromEventSchema<TSchema>
312
- fromJSON: (data: unknown) => EventWithResultSchema<TResult> & EventPayloadFromSchema<TSchema>
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
- ): SchemaEventFactory<TSchema, ResultSchemaFromEventSchema<TSchema>>
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
- ): EventFactory<
688
- ShortcutZodModelFields<TShape>,
689
- ResultSchemaFromShape<ShortcutZodModelFields<TShape>>,
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>(event_type: string, shape?: TShape): EventFactory<TShape, ResultSchemaFromShape<TShape>>
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
- ): EventFactory<ZodShapeFrom<TShape>, ResultSchemaFromShape<TShape>> | SchemaEventFactory<AnyEventSchema, unknown> {
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
- // create a new event class that extends BaseEvent and adds the custom fields
705
- class ExtendedEvent extends BaseEvent {
706
- static event_schema = full_schema
707
- static model_fields = full_schema.shape
708
- static _event_parse_schema = event_parse_schema
709
- static event_type = event_type
710
- static event_version = event_version ?? BaseEvent.event_version
711
- static event_result_type = event_result_type
712
-
713
- constructor(data?: EventInit<ZodShapeFrom<TShape>> | EventInitFromSchema<AnyEventSchema>) {
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
- type FactoryResult = EventWithResultSchema<ResultSchemaFromShape<TShape>> & EventPayload<ZodShapeFrom<TShape>>
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
- EventFactory.event_schema = full_schema as EventSchema<ZodShapeFrom<TShape>>
725
- EventFactory.model_fields = EventFactory.event_schema.shape as EventModelFields<ZodShapeFrom<TShape>>
726
- EventFactory.event_type = event_type
727
- EventFactory.event_version = event_version ?? BaseEvent.event_version
728
- EventFactory.event_result_type = event_result_type
729
- EventFactory.class = ExtendedEvent as unknown as new (
730
- data: EventInit<ZodShapeFrom<TShape>>
731
- ) => EventWithResultSchema<ResultSchemaFromShape<TShape>> & EventPayload<ZodShapeFrom<TShape>>
732
- EventFactory.fromJSON = (data: unknown) => ExtendedEvent.fromJSON(data) as FactoryResult
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
- return EventFactory as unknown as EventFactory<ZodShapeFrom<TShape>, ResultSchemaFromShape<TShape>>
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> {
@@ -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
 
@@ -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 AnyFn = (...args: any[]) => any
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 AnyFn> =
12
- Parameters<TFunc> extends [infer TArg] ? (TArg extends Record<string, unknown> ? TArg : ExtraDict) : ExtraDict
13
-
14
- type GeneratedEvent<TFunc extends AnyFn> = {
15
- (
16
- data: EventFieldsFromFn<TFunc> & ExtraDict
17
- ): BaseEvent & EventFieldsFromFn<TFunc> & { __event_result_type__?: Awaited<ReturnType<TFunc>> }
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]: GeneratedEvent<TEvents[K]> }
19
+ by_name: { [K in keyof TEvents]: EventClass<EventFromFn<TEvents[K]>, EventFieldsFromFn<TEvents[K]> & ExtraDict> }
26
20
  } & {
27
- [K in keyof TEvents]: GeneratedEvent<TEvents[K]>
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 { [K in keyof TEvents]: GeneratedEvent<TEvents[K]> }
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 GeneratedEvent<TEvents[keyof TEvents]>
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.emit(new EventCtor(payload)).now({ first_result: true }).eventResult()
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 fn_name = target.name || (_context?.name as string) || 'anonymous'
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
- for (let attempt = 1; attempt <= effective_max_attempts; attempt++) {
377
- try {
378
- if (timeout != null && timeout > 0) {
379
- return await _runWithTimeout(() => Promise.resolve(target.apply(this, args)), timeout * 1000, attempt)
380
- } else {
381
- return await Promise.resolve(target.apply(this, args))
382
- }
383
- } catch (error) {
384
- if (!shouldRetry(error)) throw error
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
- // Last attempt: rethrow
387
- if (attempt >= effective_max_attempts) throw error
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
- // Wait before next attempt with exponential backoff
390
- await asyncRetryDelay(attempt)
421
+ // Wait before next attempt with exponential backoff
422
+ await asyncRetryDelay(attempt)
423
+ }
391
424
  }
392
- }
393
425
 
394
- // Unreachable, but satisfies the type checker
395
- throw new Error(`retry(${fn_name}): unexpected end of retry loop`)
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
- for (let attempt = 1; attempt <= effective_max_attempts; attempt++) {
456
- const attempt_started_at = Date.now()
457
- try {
458
- const result = target.apply(this, args)
459
- if (isThenable(result)) {
460
- return runRetryLoopFromThenable(this, args, result, attempt)
461
- }
462
- if (timeout != null && timeout > 0 && Date.now() - attempt_started_at > timeout * 1000) {
463
- throw new RetryTimeoutError(`Timed out after ${timeout}s (attempt ${attempt})`, {
464
- timeout_seconds: timeout,
465
- attempt,
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
- return result
469
- } catch (error) {
470
- if (!shouldRetry(error)) throw error
471
- if (attempt >= effective_max_attempts) throw error
472
- syncRetryDelay(attempt)
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
- Object.defineProperty(retryWrapper, 'name', { value: `${owner_name}.${fn_name}`, configurable: true })
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
- descriptor.value = decorateFunction(descriptor.value)
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.join(os.tmpdir(), MULTIPROCESS_SEMAPHORE_DIRNAME)
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.join(os.tmpdir(), MULTIPROCESS_SEMAPHORE_DIRNAME)
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 EventClass<T extends BaseEvent = BaseEvent> = { event_type?: string } & (new (...args: any[]) => T)
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'