abxbus 2.4.28 → 2.4.30

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 (40) hide show
  1. package/README.md +22 -1
  2. package/dist/cjs/EventBus.d.ts +5 -1
  3. package/dist/cjs/EventBus.js +20 -7
  4. package/dist/cjs/EventBus.js.map +2 -2
  5. package/dist/cjs/EventHistory.d.ts +5 -0
  6. package/dist/cjs/EventHistory.js +32 -5
  7. package/dist/cjs/EventHistory.js.map +2 -2
  8. package/dist/cjs/TachyonEventBridge.d.ts +25 -0
  9. package/dist/cjs/TachyonEventBridge.js +427 -0
  10. package/dist/cjs/TachyonEventBridge.js.map +7 -0
  11. package/dist/cjs/bridges.d.ts +1 -0
  12. package/dist/cjs/bridges.js +3 -1
  13. package/dist/cjs/bridges.js.map +2 -2
  14. package/dist/cjs/index.d.ts +2 -2
  15. package/dist/cjs/index.js.map +2 -2
  16. package/dist/cjs/types.d.ts +3 -0
  17. package/dist/cjs/types.js.map +2 -2
  18. package/dist/esm/EventBus.js +20 -7
  19. package/dist/esm/EventBus.js.map +2 -2
  20. package/dist/esm/EventHistory.js +32 -5
  21. package/dist/esm/EventHistory.js.map +2 -2
  22. package/dist/esm/TachyonEventBridge.js +406 -0
  23. package/dist/esm/TachyonEventBridge.js.map +7 -0
  24. package/dist/esm/bridges.js +3 -1
  25. package/dist/esm/bridges.js.map +2 -2
  26. package/dist/esm/index.js.map +2 -2
  27. package/dist/esm/types.js.map +2 -2
  28. package/dist/types/EventBus.d.ts +5 -1
  29. package/dist/types/EventHistory.d.ts +5 -0
  30. package/dist/types/TachyonEventBridge.d.ts +25 -0
  31. package/dist/types/bridges.d.ts +1 -0
  32. package/dist/types/index.d.ts +2 -2
  33. package/dist/types/types.d.ts +3 -0
  34. package/package.json +26 -20
  35. package/src/EventBus.ts +38 -10
  36. package/src/EventHistory.ts +47 -4
  37. package/src/TachyonEventBridge.ts +498 -0
  38. package/src/bridges.ts +1 -0
  39. package/src/index.ts +10 -2
  40. package/src/types.ts +2 -0
@@ -4,7 +4,7 @@ import { EventResult } from './EventResult.js';
4
4
  import { AsyncLock, type EventConcurrencyMode, type EventHandlerConcurrencyMode, type EventHandlerCompletionMode, LockManager } from './LockManager.js';
5
5
  import { EventHandler, type EphemeralFindEventHandler, type EventHandlerJSON } from './EventHandler.js';
6
6
  import type { EventBusMiddleware, EventBusMiddlewareInput } from './EventBusMiddleware.js';
7
- import type { EventClass, EventHandlerCallable, EventPattern, FindOptions, UntypedEventHandlerFunction } from './types.js';
7
+ import type { EventClass, EventHandlerCallable, EventPattern, FilterOptions, FindOptions, UntypedEventHandlerFunction } from './types.js';
8
8
  export type EventBusOptions = {
9
9
  id?: string;
10
10
  max_history_size?: number | null;
@@ -104,6 +104,10 @@ export declare class EventBus {
104
104
  find(event_pattern: '*', where: (event: BaseEvent) => boolean, options?: FindOptions<BaseEvent>): Promise<BaseEvent | null>;
105
105
  find<T extends BaseEvent>(event_pattern: EventPattern<T>, options?: FindOptions<T>): Promise<T | null>;
106
106
  find<T extends BaseEvent>(event_pattern: EventPattern<T>, where: (event: T) => boolean, options?: FindOptions<T>): Promise<T | null>;
107
+ filter(event_pattern: '*', options?: FilterOptions<BaseEvent>): Promise<BaseEvent[]>;
108
+ filter(event_pattern: '*', where: (event: BaseEvent) => boolean, options?: FilterOptions<BaseEvent>): Promise<BaseEvent[]>;
109
+ filter<T extends BaseEvent>(event_pattern: EventPattern<T>, options?: FilterOptions<T>): Promise<T[]>;
110
+ filter<T extends BaseEvent>(event_pattern: EventPattern<T>, where: (event: T) => boolean, options?: FilterOptions<T>): Promise<T[]>;
107
111
  private _waitForFutureMatch;
108
112
  waitUntilIdle(timeout?: number | null): Promise<boolean>;
109
113
  isIdle(): boolean;
@@ -7,6 +7,9 @@ export type EventHistoryFindOptions = {
7
7
  event_is_child_of?: (event: BaseEvent, ancestor: BaseEvent) => boolean;
8
8
  wait_for_future_match?: (event_pattern: string | '*', matches: (event: BaseEvent) => boolean, future: FindWindow) => Promise<BaseEvent | null>;
9
9
  } & Record<string, unknown>;
10
+ export type EventHistoryFilterOptions = EventHistoryFindOptions & {
11
+ limit?: number | null;
12
+ };
10
13
  export type EventHistoryTrimOptions<TEvent extends BaseEvent = BaseEvent> = {
11
14
  is_event_complete?: (event: TEvent) => boolean;
12
15
  on_remove?: (event: TEvent) => void;
@@ -40,6 +43,8 @@ export declare class EventHistory<TEvent extends BaseEvent = BaseEvent> implemen
40
43
  static normalizeEventPattern(event_pattern: EventPattern | '*'): string | '*';
41
44
  find(event_pattern: '*', where?: (event: TEvent) => boolean, options?: EventHistoryFindOptions): Promise<TEvent | null>;
42
45
  find<TMatch extends TEvent>(event_pattern: EventPattern<TMatch>, where?: (event: TMatch) => boolean, options?: EventHistoryFindOptions): Promise<TMatch | null>;
46
+ filter(event_pattern: '*', where?: (event: TEvent) => boolean, options?: EventHistoryFilterOptions): Promise<TEvent[]>;
47
+ filter<TMatch extends TEvent>(event_pattern: EventPattern<TMatch>, where?: (event: TMatch) => boolean, options?: EventHistoryFilterOptions): Promise<TMatch[]>;
43
48
  trimEventHistory(options?: EventHistoryTrimOptions<TEvent>): number;
44
49
  private eventIsChildOf;
45
50
  }
@@ -0,0 +1,25 @@
1
+ import { BaseEvent } from './BaseEvent.js';
2
+ import type { EventClass, EventHandlerCallable, UntypedEventHandlerFunction } from './types.js';
3
+ export declare class TachyonEventBridge {
4
+ readonly path: string;
5
+ readonly capacity: number;
6
+ readonly name: string;
7
+ private readonly inbound_bus;
8
+ private listener_worker;
9
+ private acted_as_listener;
10
+ private listener_startup_error;
11
+ private sender_worker;
12
+ private sender_ready_promise;
13
+ private send_seq;
14
+ private pending_sends;
15
+ private closed;
16
+ constructor(path: string, capacity?: number, name?: string);
17
+ on<T extends BaseEvent>(event_pattern: EventClass<T>, handler: EventHandlerCallable<T>): void;
18
+ on<T extends BaseEvent>(event_pattern: string | '*', handler: UntypedEventHandlerFunction<T>): void;
19
+ emit<T extends BaseEvent>(event: T): Promise<void>;
20
+ dispatch<T extends BaseEvent>(event: T): Promise<void>;
21
+ start(): Promise<void>;
22
+ close(): Promise<void>;
23
+ private ensureListenerStarted;
24
+ private ensureSenderConnected;
25
+ }
@@ -7,3 +7,4 @@ export { SQLiteEventBridge } from './SQLiteEventBridge.js';
7
7
  export { NATSEventBridge } from './NATSEventBridge.js';
8
8
  export { RedisEventBridge } from './RedisEventBridge.js';
9
9
  export { PostgresEventBridge } from './PostgresEventBridge.js';
10
+ export { TachyonEventBridge } from './TachyonEventBridge.js';
@@ -1,6 +1,6 @@
1
1
  export { BaseEvent, BaseEventSchema } from './BaseEvent.js';
2
2
  export { EventHistory } from './EventHistory.js';
3
- export type { EventHistoryFindOptions, EventHistoryTrimOptions } from './EventHistory.js';
3
+ export type { EventHistoryFilterOptions, EventHistoryFindOptions, EventHistoryTrimOptions } from './EventHistory.js';
4
4
  export { EventResult } from './EventResult.js';
5
5
  export { EventBus } from './EventBus.js';
6
6
  export type { EventBusJSON, EventBusOptions } from './EventBus.js';
@@ -14,7 +14,7 @@ export type { EventBusMiddleware, EventBusMiddlewareCtor, EventBusMiddlewareInpu
14
14
  export { monotonicDatetime } from './helpers.js';
15
15
  export { EventHandlerTimeoutError, EventHandlerCancelledError, EventHandlerAbortedError, EventHandlerResultSchemaError, } from './EventHandler.js';
16
16
  export type { EventConcurrencyMode, EventHandlerConcurrencyMode, EventHandlerCompletionMode, EventBusInterfaceForLockManager, } from './LockManager.js';
17
- export type { EventClass, EventHandlerCallable as EventHandler, EventPattern, EventStatus, FindOptions, FindWindow } from './types.js';
17
+ export type { EventClass, EventHandlerCallable as EventHandler, EventPattern, EventStatus, FilterOptions, FindOptions, FindWindow, } from './types.js';
18
18
  export { retry, clearSemaphoreRegistry, RetryTimeoutError, SemaphoreTimeoutError } from './retry.js';
19
19
  export type { RetryOptions } from './retry.js';
20
20
  export { events_suck } from './events_suck.js';
@@ -26,6 +26,9 @@ export type FindOptions<T extends BaseEvent = BaseEvent> = {
26
26
  future?: FindWindow;
27
27
  child_of?: BaseEvent | null;
28
28
  } & EventFilterFields<T> & Record<string, unknown>;
29
+ export type FilterOptions<T extends BaseEvent = BaseEvent> = FindOptions<T> & {
30
+ limit?: number | null;
31
+ };
29
32
  export declare const normalizeEventPattern: (event_pattern: EventPattern | "*") => string | "*";
30
33
  export declare const isZodSchema: (value: unknown) => value is z.ZodTypeAny;
31
34
  export declare const eventResultTypeFromConstructor: (value: unknown) => z.ZodTypeAny | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abxbus",
3
- "version": "2.4.28",
3
+ "version": "2.4.30",
4
4
  "description": "Event bus library for browsers and ESM Node.js",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -46,35 +46,38 @@
46
46
  "author": "",
47
47
  "license": "MIT",
48
48
  "dependencies": {
49
- "uuid": "^13.0.0",
50
- "zod": "^4.3.6"
49
+ "uuid": "^14.0.0",
50
+ "zod": "^4.4.3"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@opentelemetry/api": "^1.9.1",
54
- "@opentelemetry/exporter-trace-otlp-http": "^0.215.0",
55
- "@opentelemetry/resources": "^2.7.0",
56
- "@opentelemetry/sdk-trace-base": "^2.7.0",
57
- "@types/node": "^25.2.3",
58
- "@typescript-eslint/eslint-plugin": "^8.55.0",
59
- "@typescript-eslint/parser": "^8.55.0",
60
- "esbuild": "^0.27.3",
61
- "eslint": "^9.39.2",
62
- "ioredis": "^5.9.3",
54
+ "@opentelemetry/exporter-trace-otlp-http": "^0.217.0",
55
+ "@opentelemetry/resources": "^2.7.1",
56
+ "@opentelemetry/sdk-trace-base": "^2.7.1",
57
+ "@tachyon-ipc/core": "^0.5.0",
58
+ "@types/node": "^25.6.1",
59
+ "@typescript-eslint/eslint-plugin": "^8.59.2",
60
+ "@typescript-eslint/parser": "^8.59.2",
61
+ "cmake-js": "^8.0.0",
62
+ "esbuild": "^0.28.0",
63
+ "eslint": "^10.3.0",
64
+ "ioredis": "^5.10.1",
63
65
  "nats": "^2.29.3",
64
- "pg": "^8.18.0",
65
- "prettier": "^3.8.1",
66
+ "pg": "^8.20.0",
67
+ "prettier": "^3.8.3",
66
68
  "tsc-files": "^1.1.4",
67
69
  "tsx": "^4.21.0",
68
- "typescript": "^5.9.3"
70
+ "typescript": "^6.0.3"
69
71
  },
70
72
  "peerDependencies": {
71
73
  "@opentelemetry/api": "^1.9.1",
72
- "@opentelemetry/exporter-trace-otlp-http": "^0.215.0",
73
- "@opentelemetry/resources": "^2.7.0",
74
- "@opentelemetry/sdk-trace-base": "^2.7.0",
75
- "ioredis": "^5.9.3",
74
+ "@opentelemetry/exporter-trace-otlp-http": "^0.217.0",
75
+ "@opentelemetry/resources": "^2.7.1",
76
+ "@opentelemetry/sdk-trace-base": "^2.7.1",
77
+ "@tachyon-ipc/core": "^0.5.0",
78
+ "ioredis": "^5.10.1",
76
79
  "nats": "^2.29.3",
77
- "pg": "^8.18.0"
80
+ "pg": "^8.20.0"
78
81
  },
79
82
  "peerDependenciesMeta": {
80
83
  "@opentelemetry/api": {
@@ -89,6 +92,9 @@
89
92
  "@opentelemetry/sdk-trace-base": {
90
93
  "optional": true
91
94
  },
95
+ "@tachyon-ipc/core": {
96
+ "optional": true
97
+ },
92
98
  "ioredis": {
93
99
  "optional": true
94
100
  },
package/src/EventBus.ts CHANGED
@@ -24,7 +24,7 @@ import { v7 as uuidv7 } from 'uuid'
24
24
  import { monotonicDatetime } from './helpers.js'
25
25
 
26
26
  import { normalizeEventPattern } from './types.js'
27
- import type { EventClass, EventHandlerCallable, EventPattern, FindOptions, UntypedEventHandlerFunction } from './types.js'
27
+ import type { EventClass, EventHandlerCallable, EventPattern, FilterOptions, FindOptions, UntypedEventHandlerFunction } from './types.js'
28
28
 
29
29
  export type EventBusOptions = {
30
30
  id?: string
@@ -782,16 +782,39 @@ export class EventBus {
782
782
  ): Promise<T | null> {
783
783
  const where = typeof where_or_options === 'function' ? where_or_options : () => true
784
784
  const options = typeof where_or_options === 'function' ? maybe_options : where_or_options
785
- const match = await this.event_history.find(event_pattern as EventPattern<T> | '*', where, {
785
+ // `limit` field-equality filter would collide with filter()'s cap arg; route it through `where`.
786
+ let effective_where = where
787
+ let effective_options: FindOptions<T> = options
788
+ if (Object.prototype.hasOwnProperty.call(options, 'limit')) {
789
+ const { limit: limit_field_value, ...rest } = options as FindOptions<T> & { limit: unknown }
790
+ const inner_where = where
791
+ effective_where = (event: T) => (event as unknown as Record<string, unknown>).limit === limit_field_value && inner_where(event)
792
+ effective_options = rest as unknown as FindOptions<T>
793
+ }
794
+ const results = await this.filter(event_pattern as EventPattern<T> | '*', effective_where, { ...effective_options, limit: 1 })
795
+ return results.length > 0 ? results[0] : null
796
+ }
797
+
798
+ // same as find() but returns the list of all matching events (newest to oldest)
799
+ // optional `limit` arg caps the number of results returned
800
+ filter(event_pattern: '*', options?: FilterOptions<BaseEvent>): Promise<BaseEvent[]>
801
+ filter(event_pattern: '*', where: (event: BaseEvent) => boolean, options?: FilterOptions<BaseEvent>): Promise<BaseEvent[]>
802
+ filter<T extends BaseEvent>(event_pattern: EventPattern<T>, options?: FilterOptions<T>): Promise<T[]>
803
+ filter<T extends BaseEvent>(event_pattern: EventPattern<T>, where: (event: T) => boolean, options?: FilterOptions<T>): Promise<T[]>
804
+ async filter<T extends BaseEvent>(
805
+ event_pattern: EventPattern<T> | '*',
806
+ where_or_options: ((event: T) => boolean) | FilterOptions<T> = {},
807
+ maybe_options: FilterOptions<T> = {}
808
+ ): Promise<T[]> {
809
+ const where = typeof where_or_options === 'function' ? where_or_options : () => true
810
+ const options = typeof where_or_options === 'function' ? maybe_options : where_or_options
811
+ const matches = await this.event_history.filter(event_pattern as EventPattern<T> | '*', where, {
786
812
  ...options,
787
813
  event_is_child_of: (event, ancestor) => this.eventIsChildOf(event, ancestor),
788
- wait_for_future_match: (normalized_event_pattern, matches, future) =>
789
- this._waitForFutureMatch(normalized_event_pattern, matches, future),
814
+ wait_for_future_match: (normalized_event_pattern, matches_fn, future) =>
815
+ this._waitForFutureMatch(normalized_event_pattern, matches_fn, future),
790
816
  })
791
- if (!match) {
792
- return null
793
- }
794
- return this._getEventProxyScopedToThisBus(match) as T
817
+ return matches.map((match) => this._getEventProxyScopedToThisBus(match) as T)
795
818
  }
796
819
 
797
820
  private async _waitForFutureMatch(
@@ -1000,8 +1023,13 @@ export class EventBus {
1000
1023
  original_event.event_blocks_parent_completion = true
1001
1024
  }
1002
1025
 
1003
- // ensure a pause request is set so the bus _runloop pauses and (will resume when the handler exits)
1004
- currently_active_event_result._ensureQueueJumpPause(this)
1026
+ // Serial event modes need the runloop paused while the queue-jumped child
1027
+ // runs so queued siblings cannot overshoot the suspended parent handler.
1028
+ // Parallel events have no event lock, so pausing here would incorrectly
1029
+ // block later parallel work emitted by the same parent.
1030
+ if (this.locks.getLockForEvent(original_event) !== null) {
1031
+ currently_active_event_result._ensureQueueJumpPause(this)
1032
+ }
1005
1033
  if (original_event.event_status === 'completed') {
1006
1034
  return event
1007
1035
  }
@@ -15,6 +15,8 @@ export type EventHistoryFindOptions = {
15
15
  ) => Promise<BaseEvent | null>
16
16
  } & Record<string, unknown>
17
17
 
18
+ export type EventHistoryFilterOptions = EventHistoryFindOptions & { limit?: number | null }
19
+
18
20
  export type EventHistoryTrimOptions<TEvent extends BaseEvent = BaseEvent> = {
19
21
  is_event_complete?: (event: TEvent) => boolean
20
22
  on_remove?: (event: TEvent) => void
@@ -109,13 +111,45 @@ export class EventHistory<TEvent extends BaseEvent = BaseEvent> implements Itera
109
111
  where: (event: TEvent) => boolean = () => true,
110
112
  options: EventHistoryFindOptions = {}
111
113
  ): Promise<TEvent | null> {
114
+ // `limit` field-equality filter would collide with filter()'s cap arg; route it through `where`.
115
+ let effective_where = where
116
+ let effective_options: EventHistoryFindOptions = options
117
+ if (Object.prototype.hasOwnProperty.call(options, 'limit')) {
118
+ const { limit: limit_field_value, ...rest } = options as EventHistoryFindOptions & { limit: unknown }
119
+ const inner_where = where
120
+ effective_where = (event: TEvent) => (event as unknown as Record<string, unknown>).limit === limit_field_value && inner_where(event)
121
+ effective_options = rest as EventHistoryFindOptions
122
+ }
123
+ const results = await this.filter(event_pattern as EventPattern<TEvent> | '*', effective_where, {
124
+ ...effective_options,
125
+ limit: 1,
126
+ })
127
+ return results.length > 0 ? results[0] : null
128
+ }
129
+
130
+ filter(event_pattern: '*', where?: (event: TEvent) => boolean, options?: EventHistoryFilterOptions): Promise<TEvent[]>
131
+ filter<TMatch extends TEvent>(
132
+ event_pattern: EventPattern<TMatch>,
133
+ where?: (event: TMatch) => boolean,
134
+ options?: EventHistoryFilterOptions
135
+ ): Promise<TMatch[]>
136
+ async filter(
137
+ event_pattern: EventPattern<TEvent> | '*',
138
+ where: (event: TEvent) => boolean = () => true,
139
+ options: EventHistoryFilterOptions = {}
140
+ ): Promise<TEvent[]> {
112
141
  const past = options.past ?? true
113
142
  const future = options.future ?? false
114
143
  const child_of = options.child_of ?? null
115
144
  const eventIsChildOf = options.event_is_child_of ?? ((event: BaseEvent, ancestor: BaseEvent) => this.eventIsChildOf(event, ancestor))
116
145
  const waitForFutureMatch = options.wait_for_future_match
146
+ const limit = options.limit ?? null
117
147
  if (past === false && future === false) {
118
- return null
148
+ return []
149
+ }
150
+
151
+ if (limit !== null && limit <= 0) {
152
+ return []
119
153
  }
120
154
 
121
155
  const event_key = EventHistory.normalizeEventPattern(event_pattern)
@@ -128,6 +162,7 @@ export class EventHistory<TEvent extends BaseEvent = BaseEvent> implements Itera
128
162
  key !== 'child_of' &&
129
163
  key !== 'event_is_child_of' &&
130
164
  key !== 'wait_for_future_match' &&
165
+ key !== 'limit' &&
131
166
  value !== undefined
132
167
  )
133
168
 
@@ -137,6 +172,7 @@ export class EventHistory<TEvent extends BaseEvent = BaseEvent> implements Itera
137
172
  event_field_filters.every(([field_name, expected]) => (event as unknown as Record<string, unknown>)[field_name] === expected) &&
138
173
  where(event as TEvent)
139
174
 
175
+ const results: TEvent[] = []
140
176
  if (past !== false) {
141
177
  const history_values = Array.from(this._events.values())
142
178
  for (let i = history_values.length - 1; i >= 0; i -= 1) {
@@ -145,16 +181,23 @@ export class EventHistory<TEvent extends BaseEvent = BaseEvent> implements Itera
145
181
  continue
146
182
  }
147
183
  if (matches(event)) {
148
- return event
184
+ results.push(event)
185
+ if (limit !== null && results.length >= limit) {
186
+ return results
187
+ }
149
188
  }
150
189
  }
151
190
  }
152
191
 
153
192
  if (future === false || !waitForFutureMatch) {
154
- return null
193
+ return results
155
194
  }
156
195
 
157
- return (await waitForFutureMatch(event_key, matches, future)) as TEvent | null
196
+ const future_match = (await waitForFutureMatch(event_key, matches, future)) as TEvent | null
197
+ if (future_match !== null) {
198
+ results.push(future_match)
199
+ }
200
+ return results
158
201
  }
159
202
 
160
203
  trimEventHistory(options: EventHistoryTrimOptions<TEvent> = {}): number {