@holo-js/events 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,267 @@
1
+ import { QueueJobDefinition, QueueJsonValue } from '@holo-js/queue';
2
+
3
+ type EventDelayValue = number | Date;
4
+ declare function normalizeOptionalString(value: string | undefined, label: string): string | undefined;
5
+ declare function normalizeOptionalBoolean(value: boolean | undefined, label: string): boolean | undefined;
6
+ declare function normalizeOptionalDelay(value: EventDelayValue | undefined): EventDelayValue | undefined;
7
+ declare function toPosixPath(value: string): string;
8
+ declare function deriveEventNameFromSourcePath(sourcePath: string): string;
9
+ declare function isReadonlyArray(value: unknown): value is readonly unknown[];
10
+ declare function hasEventDefinitionMarker(value: unknown): boolean;
11
+ declare function hasListenerDefinitionMarker(value: unknown): boolean;
12
+ interface EventDefinition<TPayload = unknown, TName extends string | undefined = string | undefined> {
13
+ readonly name?: TName;
14
+ readonly __payloadType?: TPayload;
15
+ }
16
+ interface RegisteredEvent<TPayload = unknown, TName extends string = string> {
17
+ readonly name: TName;
18
+ readonly sourcePath?: string;
19
+ readonly definition: EventDefinition<TPayload, TName>;
20
+ }
21
+ interface EventEnvelope<TName extends string = string, TPayload = unknown> {
22
+ readonly name: TName;
23
+ readonly payload: TPayload;
24
+ readonly occurredAt: number;
25
+ }
26
+ interface HoloEventRegistry {
27
+ }
28
+ interface HoloListenerRegistry {
29
+ }
30
+ type KnownEventName = Extract<keyof HoloEventRegistry, string>;
31
+ type ResolveRegisteredEventDefinition<TEventName extends string> = TEventName extends KnownEventName ? Extract<HoloEventRegistry[TEventName], EventDefinition> extends never ? EventDefinition<unknown, TEventName> : Extract<HoloEventRegistry[TEventName], EventDefinition> : EventDefinition<unknown, TEventName>;
32
+ type EventPayloadFor<TEventName extends string> = ResolveRegisteredEventDefinition<TEventName> extends EventDefinition<infer TPayload, string | undefined> ? TPayload : unknown;
33
+ type ExportedEventDefinition<TValue> = Extract<TValue, EventDefinition> extends never ? EventDefinition : Extract<TValue, EventDefinition>;
34
+ type EventReference = string | EventDefinition<unknown, string | undefined>;
35
+ type EventReferenceInput = EventReference | readonly EventReference[];
36
+ type NormalizeEventReferenceInput<TInput extends EventReferenceInput> = TInput extends readonly EventReference[] ? TInput : readonly [TInput];
37
+ type EventEnvelopeFor<TReference> = TReference extends EventDefinition<infer TPayload, infer TName> ? EventEnvelope<Extract<TName, string> extends never ? string : Extract<TName, string>, TPayload> : TReference extends string ? EventEnvelope<TReference, EventPayloadFor<TReference>> : never;
38
+ type ListenerHandledEvent<TInput extends EventReferenceInput> = EventEnvelopeFor<NormalizeEventReferenceInput<TInput>[number]>;
39
+ interface ListenerDefinition<TInput extends EventReferenceInput = EventReferenceInput, TResult = unknown> {
40
+ readonly name?: string;
41
+ readonly listensTo: NormalizeEventReferenceInput<TInput>;
42
+ readonly queue?: boolean;
43
+ readonly connection?: string;
44
+ readonly queueName?: string;
45
+ readonly delay?: EventDelayValue;
46
+ readonly afterCommit?: boolean;
47
+ handle(event: ListenerHandledEvent<TInput>): TResult | Promise<TResult>;
48
+ }
49
+ interface RegisteredListener<TInput extends EventReferenceInput = EventReferenceInput, TResult = unknown> {
50
+ readonly id: string;
51
+ readonly sourcePath?: string;
52
+ readonly eventNames: readonly string[];
53
+ readonly definition: ListenerDefinition<TInput, TResult>;
54
+ }
55
+ interface EventDispatchResult {
56
+ readonly eventName: string;
57
+ readonly occurredAt: number;
58
+ readonly deferred: boolean;
59
+ readonly syncListeners: number;
60
+ readonly queuedListeners: number;
61
+ }
62
+ interface RegisterEventOptions {
63
+ readonly name?: string;
64
+ readonly sourcePath?: string;
65
+ readonly replaceExisting?: boolean;
66
+ }
67
+ interface RegisterListenerOptions {
68
+ readonly id?: string;
69
+ readonly sourcePath?: string;
70
+ readonly replaceExisting?: boolean;
71
+ }
72
+ interface EventPendingDispatch<TPayload = unknown> extends PromiseLike<EventDispatchResult> {
73
+ afterCommit(): EventPendingDispatch<TPayload>;
74
+ onConnection(name: string): EventPendingDispatch<TPayload>;
75
+ onQueue(name: string): EventPendingDispatch<TPayload>;
76
+ delay(value: EventDelayValue): EventPendingDispatch<TPayload>;
77
+ dispatch(): Promise<EventDispatchResult>;
78
+ then<TResult1 = EventDispatchResult, TResult2 = never>(onfulfilled?: ((value: EventDispatchResult) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
79
+ catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null): Promise<EventDispatchResult | TResult>;
80
+ finally(onfinally?: (() => void) | null): Promise<EventDispatchResult>;
81
+ }
82
+ interface EventQueuedListenerDispatch {
83
+ readonly listenerId: string;
84
+ readonly event: EventEnvelope;
85
+ readonly connection?: string;
86
+ readonly queueName?: string;
87
+ readonly delay?: EventDelayValue;
88
+ }
89
+ interface EventDeferredDispatchContext {
90
+ readonly eventName: string;
91
+ readonly afterCommit: boolean;
92
+ }
93
+ interface EventRuntimeHooks {
94
+ dispatchQueuedListener?(dispatch: EventQueuedListenerDispatch): void | Promise<void>;
95
+ defer?(callback: () => Promise<void>, context: EventDeferredDispatchContext): boolean;
96
+ }
97
+ interface EventRuntimeBinding {
98
+ readonly hooks: Readonly<EventRuntimeHooks>;
99
+ }
100
+ interface EventFacade {
101
+ dispatch<TEventName extends KnownEventName>(event: TEventName, payload: EventPayloadFor<TEventName>): EventPendingDispatch<EventPayloadFor<TEventName>>;
102
+ dispatch<TPayload, TName extends string | undefined = string | undefined>(event: EventDefinition<TPayload, TName>, payload: TPayload): EventPendingDispatch<TPayload>;
103
+ dispatch<TPayload = unknown>(event: string, payload: TPayload): EventPendingDispatch<TPayload>;
104
+ }
105
+ declare function isEventDefinition(value: unknown): value is EventDefinition;
106
+ declare function normalizeEventDefinition<TEvent extends EventDefinition>(event: TEvent): TEvent;
107
+ declare function defineEvent<TPayload, TName extends string | undefined = string | undefined>(event: EventDefinition<TPayload, TName>): EventDefinition<TPayload, TName>;
108
+ declare function normalizeListensTo<TInput extends EventReferenceInput>(input: TInput): NormalizeEventReferenceInput<TInput>;
109
+ declare function isListenerDefinition(value: unknown): value is ListenerDefinition;
110
+ declare function normalizeListenerDefinition<TInput extends EventReferenceInput, TListener extends ListenerDefinition<TInput>>(listener: TListener): TListener;
111
+ declare function defineListener<TInput extends EventReferenceInput, TListener extends ListenerDefinition<TInput>>(listener: TListener): TListener;
112
+ declare const eventInternals: {
113
+ hasEventDefinitionMarker: typeof hasEventDefinitionMarker;
114
+ hasListenerDefinitionMarker: typeof hasListenerDefinitionMarker;
115
+ isReadonlyArray: typeof isReadonlyArray;
116
+ deriveEventNameFromSourcePath: typeof deriveEventNameFromSourcePath;
117
+ normalizeEventDefinition: typeof normalizeEventDefinition;
118
+ normalizeOptionalBoolean: typeof normalizeOptionalBoolean;
119
+ normalizeOptionalDelay: typeof normalizeOptionalDelay;
120
+ normalizeOptionalString: typeof normalizeOptionalString;
121
+ normalizeListensTo: typeof normalizeListensTo;
122
+ normalizeListenerDefinition: typeof normalizeListenerDefinition;
123
+ toPosixPath: typeof toPosixPath;
124
+ };
125
+
126
+ type ListenerRegistrationState = {
127
+ readonly listener: RegisteredListener;
128
+ readonly order: number;
129
+ };
130
+ type EventRegistryState = {
131
+ events: Map<string, RegisteredEvent>;
132
+ listeners: Map<string, ListenerRegistrationState>;
133
+ listenersByEvent: Map<string, ListenerRegistrationState[]>;
134
+ nextListenerOrder: number;
135
+ };
136
+ declare function getEventRegistryState(): EventRegistryState;
137
+ declare function deriveListenerIdFromSourcePath(sourcePath: string): string;
138
+ declare function resolveRegisteredEventName(definition: EventDefinition, options?: RegisterEventOptions): string;
139
+ declare function resolveRegisteredListenerId(definition: ListenerDefinition, options?: RegisterListenerOptions): string;
140
+ declare function resolveListenerEventNames(definition: ListenerDefinition, state: EventRegistryState): readonly string[];
141
+ declare function registerEvent<TPayload, TName extends string = string>(definition: EventDefinition<TPayload, TName>, options?: RegisterEventOptions): RegisteredEvent<TPayload, TName>;
142
+ declare function registerEvents(definitions: ReadonlyArray<{
143
+ readonly definition: EventDefinition;
144
+ readonly options?: RegisterEventOptions;
145
+ }>): readonly RegisteredEvent[];
146
+ declare function getRegisteredEvent(name: string): RegisteredEvent | undefined;
147
+ declare function listRegisteredEvents(): readonly RegisteredEvent[];
148
+ declare function unregisterEvent(name: string): boolean;
149
+ declare function registerListener<TInput extends EventReference | readonly EventReference[], TResult>(definition: ListenerDefinition<TInput, TResult>, options?: RegisterListenerOptions): RegisteredListener<TInput, TResult>;
150
+ declare function registerListeners(definitions: ReadonlyArray<{
151
+ readonly definition: ListenerDefinition;
152
+ readonly options?: RegisterListenerOptions;
153
+ }>): readonly RegisteredListener[];
154
+ declare function getRegisteredListener(id: string): RegisteredListener | undefined;
155
+ declare function listRegisteredListeners(): readonly RegisteredListener[];
156
+ /**
157
+ * Listener order for an event is deterministic and follows listener registration order.
158
+ */
159
+ declare function listRegisteredListenersForEvent(eventName: string): readonly RegisteredListener[];
160
+ declare function unregisterListener(id: string): boolean;
161
+ declare function resetEventRegistry(): void;
162
+ declare function resetListenerRegistry(): void;
163
+ declare function resetEventsRegistry(): void;
164
+ declare const eventRegistryInternals: {
165
+ deriveListenerIdFromSourcePath: typeof deriveListenerIdFromSourcePath;
166
+ getEventRegistryState: typeof getEventRegistryState;
167
+ resolveListenerEventNames: typeof resolveListenerEventNames;
168
+ resolveRegisteredEventName: typeof resolveRegisteredEventName;
169
+ resolveRegisteredListenerId: typeof resolveRegisteredListenerId;
170
+ };
171
+
172
+ declare function deferEventDispatchToDatabaseCommit(callback: () => Promise<void>, _context: EventDeferredDispatchContext): boolean;
173
+
174
+ declare const EVENTS_INVOKE_LISTENER_JOB = "holo.events.invoke-listener";
175
+ type EventsInvokeListenerPayload = Readonly<Record<string, QueueJsonValue>> & Readonly<{
176
+ readonly listenerId: string;
177
+ readonly eventName: string;
178
+ readonly occurredAt: number;
179
+ readonly payload: QueueJsonValue;
180
+ }>;
181
+ declare module '@holo-js/queue' {
182
+ interface HoloQueueJobRegistry {
183
+ 'holo.events.invoke-listener': QueueJobDefinition<EventsInvokeListenerPayload, void>;
184
+ }
185
+ }
186
+ declare function createQueuedListenerEventEnvelope(payload: EventsInvokeListenerPayload): EventEnvelope<string, QueueJsonValue>;
187
+ declare function requireQueuedListener(listenerId: string): RegisteredListener;
188
+ declare function assertQueuedListenerMatchesEvent(listener: RegisteredListener, eventName: string): void;
189
+ declare function runQueuedListenerInvocation(payload: EventsInvokeListenerPayload): Promise<void>;
190
+ declare function ensureEventsQueueJobRegistered(): void;
191
+ declare const eventQueueInternals: {
192
+ assertQueuedListenerMatchesEvent: typeof assertQueuedListenerMatchesEvent;
193
+ createQueuedListenerEventEnvelope: typeof createQueuedListenerEventEnvelope;
194
+ requireQueuedListener: typeof requireQueuedListener;
195
+ };
196
+
197
+ type PendingOptions = {
198
+ readonly afterCommit?: true;
199
+ readonly connection?: string;
200
+ readonly queueName?: string;
201
+ readonly delay?: EventDelayValue;
202
+ };
203
+ type EventPayloadValidationState = {
204
+ readonly seen: Set<unknown>;
205
+ };
206
+ type RuntimeState = {
207
+ hooks: EventRuntimeHooks;
208
+ };
209
+ type ListenerExecutionGroups = {
210
+ readonly immediateSyncListeners: readonly RegisteredListener[];
211
+ readonly immediateQueuedListeners: readonly RegisteredListener[];
212
+ readonly deferredSyncListeners: readonly RegisteredListener[];
213
+ readonly deferredQueuedListeners: readonly RegisteredListener[];
214
+ };
215
+ declare function isPlainObject(value: unknown): value is Record<string, unknown>;
216
+ declare function assertEventJsonValue(value: unknown, path: string, state: EventPayloadValidationState): void;
217
+ declare function validateQueuedEventPayload<TPayload>(payload: TPayload): void;
218
+ declare function getRuntimeState(): RuntimeState;
219
+ declare function createRuntimeBinding(state: RuntimeState): EventRuntimeBinding;
220
+ declare function normalizeEventName(name: string): string;
221
+ declare function resolveDispatchedEventName<TPayload>(event: string | EventDefinition<TPayload, string | undefined>): string;
222
+ declare function requireRegisteredEvent(name: string): RegisteredEvent;
223
+ declare function createEventEnvelope<TPayload>(eventName: string, payload: TPayload, occurredAt?: number): EventEnvelope<string, TPayload>;
224
+ declare function splitRegisteredListeners(listeners: readonly RegisteredListener[]): {
225
+ readonly syncListeners: readonly RegisteredListener[];
226
+ readonly queuedListeners: readonly RegisteredListener[];
227
+ };
228
+ declare function executeSyncListeners<TPayload>(listeners: readonly RegisteredListener[], event: EventEnvelope<string, TPayload>): Promise<number>;
229
+ declare function executeListenerGroups<TPayload>(syncListeners: readonly RegisteredListener[], queuedListeners: readonly RegisteredListener[], event: EventEnvelope<string, TPayload>, options: PendingOptions): Promise<{
230
+ readonly syncCount: number;
231
+ readonly queuedCount: number;
232
+ }>;
233
+ declare function executeListener<TPayload>(listener: RegisteredListener, event: EventEnvelope<string, TPayload>): Promise<void>;
234
+ declare function createQueuedListenerDispatch(listener: RegisteredListener, event: EventEnvelope, options: PendingOptions): EventQueuedListenerDispatch;
235
+ declare function dispatchQueuedListeners<TPayload>(listeners: readonly RegisteredListener[], event: EventEnvelope<string, TPayload>, options: PendingOptions): Promise<number>;
236
+ declare function splitListenersForExecution(listeners: readonly RegisteredListener[], forceAfterCommit: boolean): ListenerExecutionGroups;
237
+ declare function scheduleDeferredDispatch(callback: () => Promise<void>, eventName: string): boolean;
238
+ declare function executeDispatch<TPayload>(event: string | EventDefinition<TPayload, string | undefined>, payload: TPayload, options?: PendingOptions): Promise<EventDispatchResult>;
239
+ declare function configureEventsRuntime(hooks?: EventRuntimeHooks): void;
240
+ declare function getEventsRuntime(): EventRuntimeBinding;
241
+ declare function resetEventsRuntime(): void;
242
+ declare function dispatchEvent<TEventName extends Extract<keyof HoloEventRegistry, string>>(event: TEventName, payload: EventPayloadFor<TEventName>): EventPendingDispatch<EventPayloadFor<TEventName>>;
243
+ declare function dispatchEvent<TPayload, TName extends string | undefined = string | undefined>(event: EventDefinition<TPayload, TName>, payload: TPayload): EventPendingDispatch<TPayload>;
244
+ declare function dispatchEvent<TPayload = unknown>(event: string, payload: TPayload): EventPendingDispatch<TPayload>;
245
+ declare const Event: EventFacade;
246
+ declare const eventRuntimeInternals: {
247
+ assertEventJsonValue: typeof assertEventJsonValue;
248
+ createEventEnvelope: typeof createEventEnvelope;
249
+ createQueuedListenerDispatch: typeof createQueuedListenerDispatch;
250
+ createRuntimeBinding: typeof createRuntimeBinding;
251
+ dispatchQueuedListeners: typeof dispatchQueuedListeners;
252
+ executeListenerGroups: typeof executeListenerGroups;
253
+ executeDispatch: typeof executeDispatch;
254
+ executeListener: typeof executeListener;
255
+ executeSyncListeners: typeof executeSyncListeners;
256
+ getRuntimeState: typeof getRuntimeState;
257
+ isPlainObject: typeof isPlainObject;
258
+ normalizeEventName: typeof normalizeEventName;
259
+ requireRegisteredEvent: typeof requireRegisteredEvent;
260
+ resolveDispatchedEventName: typeof resolveDispatchedEventName;
261
+ scheduleDeferredDispatch: typeof scheduleDeferredDispatch;
262
+ splitRegisteredListeners: typeof splitRegisteredListeners;
263
+ splitListenersForExecution: typeof splitListenersForExecution;
264
+ validateQueuedEventPayload: typeof validateQueuedEventPayload;
265
+ };
266
+
267
+ export { EVENTS_INVOKE_LISTENER_JOB, Event, type EventDeferredDispatchContext, type EventDefinition, type EventDelayValue, type EventDispatchResult, type EventEnvelope, type EventEnvelopeFor, type EventFacade, type EventPayloadFor, type EventPendingDispatch, type EventQueuedListenerDispatch, type EventReference, type EventReferenceInput, type EventRuntimeBinding, type EventRuntimeHooks, type EventsInvokeListenerPayload, type ExportedEventDefinition, type HoloEventRegistry, type HoloListenerRegistry, type ListenerDefinition, type ListenerHandledEvent, type RegisterEventOptions, type RegisterListenerOptions, type RegisteredEvent, type RegisteredListener, configureEventsRuntime, deferEventDispatchToDatabaseCommit, defineEvent, defineListener, dispatchEvent, ensureEventsQueueJobRegistered, eventInternals, eventQueueInternals, eventRegistryInternals, eventRuntimeInternals, getEventsRuntime, getRegisteredEvent, getRegisteredListener, isEventDefinition, isListenerDefinition, listRegisteredEvents, listRegisteredListeners, listRegisteredListenersForEvent, normalizeEventDefinition, normalizeListenerDefinition, registerEvent, registerEvents, registerListener, registerListeners, resetEventRegistry, resetEventsRegistry, resetEventsRuntime, resetListenerRegistry, runQueuedListenerInvocation, unregisterEvent, unregisterListener };
package/dist/index.mjs ADDED
@@ -0,0 +1,812 @@
1
+ // src/contracts.ts
2
+ var HOLO_EVENT_DEFINITION_MARKER = /* @__PURE__ */ Symbol.for("holo-js.events.definition");
3
+ var HOLO_LISTENER_DEFINITION_MARKER = /* @__PURE__ */ Symbol.for("holo-js.events.listener");
4
+ function normalizeOptionalString(value, label) {
5
+ if (typeof value === "undefined") {
6
+ return void 0;
7
+ }
8
+ const normalized = value.trim();
9
+ if (!normalized) {
10
+ throw new Error(`[Holo Events] ${label} must be a non-empty string when provided.`);
11
+ }
12
+ return normalized;
13
+ }
14
+ function normalizeOptionalBoolean(value, label) {
15
+ if (typeof value === "undefined") {
16
+ return void 0;
17
+ }
18
+ if (typeof value !== "boolean") {
19
+ throw new Error(`[Holo Events] ${label} must be a boolean when provided.`);
20
+ }
21
+ return value;
22
+ }
23
+ function normalizeOptionalDelay(value) {
24
+ if (typeof value === "undefined") {
25
+ return void 0;
26
+ }
27
+ if (typeof value === "number") {
28
+ if (!Number.isFinite(value) || value < 0) {
29
+ throw new TypeError("[Holo Events] Listener delay must be a finite number greater than or equal to 0.");
30
+ }
31
+ return value;
32
+ }
33
+ if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
34
+ throw new TypeError("[Holo Events] Listener delay dates must be valid Date instances.");
35
+ }
36
+ return value;
37
+ }
38
+ function toPosixPath(value) {
39
+ return value.replaceAll("\\", "/");
40
+ }
41
+ function deriveEventNameFromSourcePath(sourcePath) {
42
+ const normalized = toPosixPath(sourcePath).replace(/\.[^.]+$/, "");
43
+ const eventRootIndex = normalized.lastIndexOf("/events/");
44
+ const relevant = eventRootIndex >= 0 ? normalized.slice(eventRootIndex + "/events/".length) : normalized;
45
+ const derived = relevant.split("/").map((part) => part.trim()).filter(Boolean).join(".");
46
+ if (!derived) {
47
+ throw new Error("[Holo Events] Derived event names require a non-empty source path.");
48
+ }
49
+ return derived;
50
+ }
51
+ function isReadonlyArray(value) {
52
+ return Array.isArray(value);
53
+ }
54
+ function hasEventDefinitionMarker(value) {
55
+ return !!value && typeof value === "object" && HOLO_EVENT_DEFINITION_MARKER in value;
56
+ }
57
+ function hasListenerDefinitionMarker(value) {
58
+ return !!value && typeof value === "object" && HOLO_LISTENER_DEFINITION_MARKER in value;
59
+ }
60
+ function isEventDefinition(value) {
61
+ return value !== null && typeof value === "object" && !Array.isArray(value);
62
+ }
63
+ function normalizeEventDefinition(event) {
64
+ if (!isEventDefinition(event)) {
65
+ throw new Error("[Holo Events] Events must be plain objects.");
66
+ }
67
+ return {
68
+ ...event,
69
+ ...typeof event.name === "undefined" ? {} : { name: normalizeOptionalString(event.name, "Event name") }
70
+ };
71
+ }
72
+ function defineEvent(event) {
73
+ const normalized = { ...normalizeEventDefinition(event) };
74
+ Object.defineProperty(normalized, HOLO_EVENT_DEFINITION_MARKER, {
75
+ value: true,
76
+ enumerable: false
77
+ });
78
+ return Object.freeze(normalized);
79
+ }
80
+ function normalizeEventReference(value, index) {
81
+ if (typeof value === "string") {
82
+ const normalized = value.trim();
83
+ if (!normalized) {
84
+ throw new Error(`[Holo Events] Listener event reference at index ${index} must be a non-empty string.`);
85
+ }
86
+ return normalized;
87
+ }
88
+ if (!isEventDefinition(value)) {
89
+ throw new Error(`[Holo Events] Listener event reference at index ${index} must be an event definition or string.`);
90
+ }
91
+ return Object.freeze(normalizeEventDefinition(value));
92
+ }
93
+ function normalizeListensTo(input) {
94
+ const entries = isReadonlyArray(input) ? input : [input];
95
+ if (entries.length === 0) {
96
+ throw new Error("[Holo Events] Listeners must listen to at least one event.");
97
+ }
98
+ return Object.freeze(entries.map((entry, index) => normalizeEventReference(entry, index)));
99
+ }
100
+ function isListenerDefinition(value) {
101
+ return value !== null && typeof value === "object" && "listensTo" in value && "handle" in value && typeof value.handle === "function";
102
+ }
103
+ function normalizeListenerDefinition(listener) {
104
+ if (!isListenerDefinition(listener)) {
105
+ throw new Error('[Holo Events] Listeners must define "listensTo" and a "handle" function.');
106
+ }
107
+ const queue = normalizeOptionalBoolean(listener.queue, "Listener queue");
108
+ const connection = normalizeOptionalString(listener.connection, "Listener connection");
109
+ const queueName = normalizeOptionalString(listener.queueName, "Listener queue name");
110
+ const delay = normalizeOptionalDelay(listener.delay);
111
+ const afterCommit = normalizeOptionalBoolean(listener.afterCommit, "Listener afterCommit");
112
+ if (queue !== true && (connection || queueName || typeof delay !== "undefined")) {
113
+ throw new Error("[Holo Events] Listener queue metadata requires queue: true.");
114
+ }
115
+ return {
116
+ ...listener,
117
+ ...typeof listener.name === "undefined" ? {} : { name: normalizeOptionalString(listener.name, "Listener name") },
118
+ listensTo: normalizeListensTo(listener.listensTo),
119
+ ...typeof queue === "undefined" ? {} : { queue },
120
+ ...typeof connection === "undefined" ? {} : { connection },
121
+ ...typeof queueName === "undefined" ? {} : { queueName },
122
+ ...typeof delay === "undefined" ? {} : { delay },
123
+ ...typeof afterCommit === "undefined" ? {} : { afterCommit }
124
+ };
125
+ }
126
+ function defineListener(listener) {
127
+ const normalized = normalizeListenerDefinition(listener);
128
+ if (isReadonlyArray(normalized.listensTo)) {
129
+ Object.freeze(normalized.listensTo);
130
+ }
131
+ const tagged = {
132
+ ...normalized
133
+ };
134
+ Object.defineProperty(tagged, HOLO_LISTENER_DEFINITION_MARKER, {
135
+ value: true,
136
+ enumerable: false
137
+ });
138
+ return Object.freeze(tagged);
139
+ }
140
+ var eventInternals = {
141
+ hasEventDefinitionMarker,
142
+ hasListenerDefinitionMarker,
143
+ isReadonlyArray,
144
+ deriveEventNameFromSourcePath,
145
+ normalizeEventDefinition,
146
+ normalizeOptionalBoolean,
147
+ normalizeOptionalDelay,
148
+ normalizeOptionalString,
149
+ normalizeListensTo,
150
+ normalizeListenerDefinition,
151
+ toPosixPath
152
+ };
153
+
154
+ // src/registry.ts
155
+ function getEventRegistryState() {
156
+ const runtime = globalThis;
157
+ runtime.__holoEventRegistry__ ??= {
158
+ events: /* @__PURE__ */ new Map(),
159
+ listeners: /* @__PURE__ */ new Map(),
160
+ listenersByEvent: /* @__PURE__ */ new Map(),
161
+ nextListenerOrder: 0
162
+ };
163
+ return runtime.__holoEventRegistry__;
164
+ }
165
+ function deriveListenerIdFromSourcePath(sourcePath) {
166
+ const normalized = eventInternals.toPosixPath(sourcePath).replace(/\.[^.]+$/, "");
167
+ const listenerRootIndex = normalized.lastIndexOf("/listeners/");
168
+ const relevant = listenerRootIndex >= 0 ? normalized.slice(listenerRootIndex + "/listeners/".length) : normalized;
169
+ const derived = relevant.split("/").map((part) => part.trim()).filter(Boolean).join(".");
170
+ if (!derived) {
171
+ throw new Error("[Holo Events] Derived listener identifiers require a non-empty source path.");
172
+ }
173
+ return derived;
174
+ }
175
+ function resolveRegisteredEventName(definition, options = {}) {
176
+ const explicitOption = options.name?.trim();
177
+ if (explicitOption) {
178
+ return explicitOption;
179
+ }
180
+ const explicitDefinition = definition.name?.trim();
181
+ if (explicitDefinition) {
182
+ return explicitDefinition;
183
+ }
184
+ const sourcePath = options.sourcePath?.trim();
185
+ if (sourcePath) {
186
+ return eventInternals.deriveEventNameFromSourcePath(sourcePath);
187
+ }
188
+ throw new Error("[Holo Events] Registered events require an explicit name or a sourcePath-derived name.");
189
+ }
190
+ function resolveRegisteredListenerId(definition, options = {}) {
191
+ const explicitOption = options.id?.trim();
192
+ if (explicitOption) {
193
+ return explicitOption;
194
+ }
195
+ const explicitDefinition = definition.name?.trim();
196
+ if (explicitDefinition) {
197
+ return explicitDefinition;
198
+ }
199
+ const sourcePath = options.sourcePath?.trim();
200
+ if (sourcePath) {
201
+ return deriveListenerIdFromSourcePath(sourcePath);
202
+ }
203
+ throw new Error("[Holo Events] Registered listeners require an explicit id, listener name, or a sourcePath-derived id.");
204
+ }
205
+ function resolveListenerEventName(reference) {
206
+ if (typeof reference === "string") {
207
+ return reference;
208
+ }
209
+ const explicitName = reference.name?.trim();
210
+ if (explicitName) {
211
+ return explicitName;
212
+ }
213
+ throw new Error("[Holo Events] Listener event references must resolve to explicit event names before registration.");
214
+ }
215
+ function resolveListenerEventNames(definition, state) {
216
+ const names = [...new Set(definition.listensTo.map(resolveListenerEventName))];
217
+ for (const name of names) {
218
+ if (!state.events.has(name)) {
219
+ throw new Error(`[Holo Events] Listener target event "${name}" is not registered.`);
220
+ }
221
+ }
222
+ return Object.freeze(names);
223
+ }
224
+ function removeListenerFromIndexes(state, entry) {
225
+ for (const eventName of entry.listener.eventNames) {
226
+ const indexed = state.listenersByEvent.get(eventName);
227
+ if (!indexed) {
228
+ continue;
229
+ }
230
+ const filtered = indexed.filter((candidate) => candidate.listener.id !== entry.listener.id);
231
+ if (filtered.length === 0) {
232
+ state.listenersByEvent.delete(eventName);
233
+ continue;
234
+ }
235
+ state.listenersByEvent.set(eventName, filtered);
236
+ }
237
+ }
238
+ function insertListenerIntoIndexes(state, entry) {
239
+ for (const eventName of entry.listener.eventNames) {
240
+ const indexed = state.listenersByEvent.get(eventName) ?? [];
241
+ state.listenersByEvent.set(eventName, [...indexed, entry].sort((left, right) => left.order - right.order));
242
+ }
243
+ }
244
+ function registerEvent(definition, options = {}) {
245
+ if (!isEventDefinition(definition)) {
246
+ throw new Error("[Holo Events] Events must be plain objects.");
247
+ }
248
+ const normalizedDefinition = normalizeEventDefinition(definition);
249
+ const name = resolveRegisteredEventName(normalizedDefinition, options);
250
+ const state = getEventRegistryState();
251
+ if (state.events.has(name) && options.replaceExisting !== true) {
252
+ throw new Error(`[Holo Events] Event "${name}" is already registered.`);
253
+ }
254
+ const entry = Object.freeze({
255
+ name,
256
+ ...options.sourcePath ? { sourcePath: options.sourcePath } : {},
257
+ definition: Object.freeze({
258
+ ...normalizedDefinition,
259
+ name
260
+ })
261
+ });
262
+ state.events.set(name, entry);
263
+ return entry;
264
+ }
265
+ function registerEvents(definitions) {
266
+ return Object.freeze(definitions.map((entry) => {
267
+ return registerEvent(entry.definition, entry.options);
268
+ }));
269
+ }
270
+ function getRegisteredEvent(name) {
271
+ return getEventRegistryState().events.get(name);
272
+ }
273
+ function listRegisteredEvents() {
274
+ return Object.freeze([...getEventRegistryState().events.values()].sort((left, right) => left.name.localeCompare(right.name)));
275
+ }
276
+ function unregisterEvent(name) {
277
+ const state = getEventRegistryState();
278
+ const listeners = state.listenersByEvent.get(name);
279
+ if (listeners && listeners.length > 0) {
280
+ throw new Error(`[Holo Events] Event "${name}" cannot be unregistered while listeners are registered for it.`);
281
+ }
282
+ return state.events.delete(name);
283
+ }
284
+ function registerListener(definition, options = {}) {
285
+ if (!isListenerDefinition(definition)) {
286
+ throw new Error('[Holo Events] Listeners must define "listensTo" and a "handle" function.');
287
+ }
288
+ const normalizedDefinition = normalizeListenerDefinition(definition);
289
+ const id = resolveRegisteredListenerId(normalizedDefinition, options);
290
+ const state = getEventRegistryState();
291
+ if (state.listeners.has(id) && options.replaceExisting !== true) {
292
+ throw new Error(`[Holo Events] Listener "${id}" is already registered.`);
293
+ }
294
+ const eventNames = resolveListenerEventNames(normalizedDefinition, state);
295
+ const existing = state.listeners.get(id);
296
+ if (existing) {
297
+ removeListenerFromIndexes(state, existing);
298
+ }
299
+ const listener = Object.freeze({
300
+ id,
301
+ ...options.sourcePath ? { sourcePath: options.sourcePath } : {},
302
+ eventNames,
303
+ definition: Object.freeze({
304
+ ...normalizedDefinition,
305
+ ...typeof normalizedDefinition.name === "undefined" ? {} : { name: normalizedDefinition.name }
306
+ })
307
+ });
308
+ const registration = {
309
+ listener,
310
+ order: state.nextListenerOrder
311
+ };
312
+ state.nextListenerOrder += 1;
313
+ state.listeners.set(id, registration);
314
+ insertListenerIntoIndexes(state, registration);
315
+ return listener;
316
+ }
317
+ function registerListeners(definitions) {
318
+ return Object.freeze(definitions.map((entry) => {
319
+ return registerListener(entry.definition, entry.options);
320
+ }));
321
+ }
322
+ function getRegisteredListener(id) {
323
+ return getEventRegistryState().listeners.get(id)?.listener;
324
+ }
325
+ function listRegisteredListeners() {
326
+ return Object.freeze(
327
+ [...getEventRegistryState().listeners.values()].sort((left, right) => left.listener.id.localeCompare(right.listener.id)).map((entry) => entry.listener)
328
+ );
329
+ }
330
+ function listRegisteredListenersForEvent(eventName) {
331
+ const indexed = getEventRegistryState().listenersByEvent.get(eventName) ?? [];
332
+ return Object.freeze(indexed.map((entry) => entry.listener));
333
+ }
334
+ function unregisterListener(id) {
335
+ const state = getEventRegistryState();
336
+ const existing = state.listeners.get(id);
337
+ if (!existing) {
338
+ return false;
339
+ }
340
+ removeListenerFromIndexes(state, existing);
341
+ return state.listeners.delete(id);
342
+ }
343
+ function resetEventRegistry() {
344
+ const state = getEventRegistryState();
345
+ state.events.clear();
346
+ state.listeners.clear();
347
+ state.listenersByEvent.clear();
348
+ state.nextListenerOrder = 0;
349
+ }
350
+ function resetListenerRegistry() {
351
+ const state = getEventRegistryState();
352
+ state.listeners.clear();
353
+ state.listenersByEvent.clear();
354
+ state.nextListenerOrder = 0;
355
+ }
356
+ function resetEventsRegistry() {
357
+ resetEventRegistry();
358
+ }
359
+ var eventRegistryInternals = {
360
+ deriveListenerIdFromSourcePath,
361
+ getEventRegistryState,
362
+ resolveListenerEventNames,
363
+ resolveRegisteredEventName,
364
+ resolveRegisteredListenerId
365
+ };
366
+
367
+ // src/db.ts
368
+ import { connectionAsyncContext } from "@holo-js/db";
369
+ function deferEventDispatchToDatabaseCommit(callback, _context) {
370
+ const active = connectionAsyncContext.getActive()?.connection;
371
+ if (!active || active.getScope().kind === "root") {
372
+ return false;
373
+ }
374
+ active.afterCommit(callback);
375
+ return true;
376
+ }
377
+
378
+ // src/queue.ts
379
+ import {
380
+ defineJob,
381
+ dispatch as dispatchQueueJob,
382
+ getRegisteredQueueJob,
383
+ registerQueueJob
384
+ } from "@holo-js/queue";
385
+ var EVENTS_INVOKE_LISTENER_JOB = "holo.events.invoke-listener";
386
+ function createQueuedListenerEventEnvelope(payload) {
387
+ return Object.freeze({
388
+ name: payload.eventName,
389
+ payload: payload.payload,
390
+ occurredAt: payload.occurredAt
391
+ });
392
+ }
393
+ function requireQueuedListener(listenerId) {
394
+ const listener = getRegisteredListener(listenerId);
395
+ if (!listener) {
396
+ throw new Error(`[Holo Events] Queued listener "${listenerId}" is not registered.`);
397
+ }
398
+ return listener;
399
+ }
400
+ function assertQueuedListenerMatchesEvent(listener, eventName) {
401
+ if (!listener.eventNames.includes(eventName)) {
402
+ throw new Error(
403
+ `[Holo Events] Queued listener "${listener.id}" is not registered for event "${eventName}".`
404
+ );
405
+ }
406
+ }
407
+ async function runQueuedListenerInvocation(payload) {
408
+ const listener = requireQueuedListener(payload.listenerId);
409
+ assertQueuedListenerMatchesEvent(listener, payload.eventName);
410
+ await listener.definition.handle(createQueuedListenerEventEnvelope(payload));
411
+ }
412
+ function ensureEventsQueueJobRegistered() {
413
+ if (getRegisteredQueueJob(EVENTS_INVOKE_LISTENER_JOB)) {
414
+ return;
415
+ }
416
+ registerQueueJob(defineJob({
417
+ async handle(payload) {
418
+ await runQueuedListenerInvocation(payload);
419
+ }
420
+ }), {
421
+ name: EVENTS_INVOKE_LISTENER_JOB
422
+ });
423
+ }
424
+ async function dispatchQueuedListenerViaQueue(dispatch) {
425
+ ensureEventsQueueJobRegistered();
426
+ let pending = dispatchQueueJob(EVENTS_INVOKE_LISTENER_JOB, {
427
+ listenerId: dispatch.listenerId,
428
+ eventName: dispatch.event.name,
429
+ occurredAt: dispatch.event.occurredAt,
430
+ payload: dispatch.event.payload
431
+ });
432
+ if (typeof dispatch.connection !== "undefined") {
433
+ pending = pending.onConnection(dispatch.connection);
434
+ }
435
+ if (typeof dispatch.queueName !== "undefined") {
436
+ pending = pending.onQueue(dispatch.queueName);
437
+ }
438
+ if (typeof dispatch.delay !== "undefined") {
439
+ pending = pending.delay(dispatch.delay);
440
+ }
441
+ await pending.dispatch();
442
+ }
443
+ var eventQueueInternals = {
444
+ assertQueuedListenerMatchesEvent,
445
+ createQueuedListenerEventEnvelope,
446
+ requireQueuedListener
447
+ };
448
+
449
+ // src/runtime.ts
450
+ function isPlainObject(value) {
451
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
452
+ return false;
453
+ }
454
+ const prototype = Object.getPrototypeOf(value);
455
+ return prototype === Object.prototype || prototype === null;
456
+ }
457
+ function assertEventJsonValue(value, path, state) {
458
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
459
+ return;
460
+ }
461
+ if (typeof value === "number") {
462
+ if (!Number.isFinite(value)) {
463
+ throw new TypeError(`[Holo Events] Event payload at "${path}" must be JSON-serializable for queued listeners.`);
464
+ }
465
+ return;
466
+ }
467
+ if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") {
468
+ throw new TypeError(`[Holo Events] Event payload at "${path}" must be JSON-serializable for queued listeners.`);
469
+ }
470
+ if (Array.isArray(value)) {
471
+ if (state.seen.has(value)) {
472
+ throw new TypeError(`[Holo Events] Event payload at "${path}" contains a circular reference.`);
473
+ }
474
+ state.seen.add(value);
475
+ for (let index = 0; index < value.length; index += 1) {
476
+ assertEventJsonValue(value[index], `${path}[${index}]`, state);
477
+ }
478
+ state.seen.delete(value);
479
+ return;
480
+ }
481
+ if (!isPlainObject(value)) {
482
+ throw new TypeError(`[Holo Events] Event payload at "${path}" must be a plain JSON object, array, or primitive for queued listeners.`);
483
+ }
484
+ if (state.seen.has(value)) {
485
+ throw new TypeError(`[Holo Events] Event payload at "${path}" contains a circular reference.`);
486
+ }
487
+ state.seen.add(value);
488
+ for (const [key, nested] of Object.entries(value)) {
489
+ assertEventJsonValue(nested, `${path}.${key}`, state);
490
+ }
491
+ state.seen.delete(value);
492
+ }
493
+ function validateQueuedEventPayload(payload) {
494
+ assertEventJsonValue(payload, "payload", { seen: /* @__PURE__ */ new Set() });
495
+ }
496
+ function getRuntimeState() {
497
+ const runtime = globalThis;
498
+ runtime.__holoEventsRuntime__ ??= {
499
+ hooks: {}
500
+ };
501
+ return runtime.__holoEventsRuntime__;
502
+ }
503
+ function createRuntimeBinding(state) {
504
+ return Object.freeze({
505
+ hooks: Object.freeze({
506
+ ...state.hooks
507
+ })
508
+ });
509
+ }
510
+ function normalizeEventName(name) {
511
+ const normalized = name.trim();
512
+ if (!normalized) {
513
+ throw new Error("[Holo Events] Event names must be non-empty strings.");
514
+ }
515
+ return normalized;
516
+ }
517
+ function resolveDispatchedEventName(event) {
518
+ if (typeof event === "string") {
519
+ return normalizeEventName(event);
520
+ }
521
+ if (!isEventDefinition(event)) {
522
+ throw new Error("[Holo Events] Events must be plain objects.");
523
+ }
524
+ const explicitName = event.name?.trim();
525
+ if (!explicitName) {
526
+ throw new Error("[Holo Events] Dispatching an event definition requires an explicit event name.");
527
+ }
528
+ return explicitName;
529
+ }
530
+ function requireRegisteredEvent(name) {
531
+ const registered = getRegisteredEvent(name);
532
+ if (!registered) {
533
+ throw new Error(`[Holo Events] Event "${name}" is not registered.`);
534
+ }
535
+ return registered;
536
+ }
537
+ function createEventEnvelope(eventName, payload, occurredAt = Date.now()) {
538
+ return Object.freeze({
539
+ name: eventName,
540
+ payload,
541
+ occurredAt
542
+ });
543
+ }
544
+ function splitRegisteredListeners(listeners) {
545
+ const syncListeners = [];
546
+ const queuedListeners = [];
547
+ for (const listener of listeners) {
548
+ if (listener.definition.queue === true) {
549
+ queuedListeners.push(listener);
550
+ continue;
551
+ }
552
+ syncListeners.push(listener);
553
+ }
554
+ return Object.freeze({
555
+ syncListeners: Object.freeze(syncListeners),
556
+ queuedListeners: Object.freeze(queuedListeners)
557
+ });
558
+ }
559
+ async function executeSyncListeners(listeners, event) {
560
+ for (const listener of listeners) {
561
+ await executeListener(listener, event);
562
+ }
563
+ return listeners.length;
564
+ }
565
+ async function executeListenerGroups(syncListeners, queuedListeners, event, options) {
566
+ const syncCount = await executeSyncListeners(syncListeners, event);
567
+ const queuedCount = await dispatchQueuedListeners(queuedListeners, event, options);
568
+ return Object.freeze({
569
+ syncCount,
570
+ queuedCount
571
+ });
572
+ }
573
+ async function executeListener(listener, event) {
574
+ await listener.definition.handle(event);
575
+ }
576
+ function createQueuedListenerDispatch(listener, event, options) {
577
+ return Object.freeze({
578
+ listenerId: listener.id,
579
+ event,
580
+ ...typeof (options.connection ?? listener.definition.connection) === "undefined" ? {} : { connection: options.connection ?? listener.definition.connection },
581
+ ...typeof (options.queueName ?? listener.definition.queueName) === "undefined" ? {} : { queueName: options.queueName ?? listener.definition.queueName },
582
+ ...typeof (options.delay ?? listener.definition.delay) === "undefined" ? {} : { delay: options.delay ?? listener.definition.delay }
583
+ });
584
+ }
585
+ async function dispatchQueuedListeners(listeners, event, options) {
586
+ if (listeners.length === 0) {
587
+ return 0;
588
+ }
589
+ validateQueuedEventPayload(event.payload);
590
+ const dispatcher = getRuntimeState().hooks.dispatchQueuedListener ?? dispatchQueuedListenerViaQueue;
591
+ for (const listener of listeners) {
592
+ await dispatcher(createQueuedListenerDispatch(listener, event, options));
593
+ }
594
+ return listeners.length;
595
+ }
596
+ function splitListenersForExecution(listeners, forceAfterCommit) {
597
+ const immediateSyncListeners = [];
598
+ const immediateQueuedListeners = [];
599
+ const deferredSyncListeners = [];
600
+ const deferredQueuedListeners = [];
601
+ for (const listener of listeners) {
602
+ const shouldDefer = forceAfterCommit || listener.definition.afterCommit === true;
603
+ if (shouldDefer) {
604
+ if (listener.definition.queue === true) {
605
+ deferredQueuedListeners.push(listener);
606
+ continue;
607
+ }
608
+ deferredSyncListeners.push(listener);
609
+ continue;
610
+ }
611
+ if (listener.definition.queue === true) {
612
+ immediateQueuedListeners.push(listener);
613
+ continue;
614
+ }
615
+ immediateSyncListeners.push(listener);
616
+ }
617
+ return Object.freeze({
618
+ immediateSyncListeners: Object.freeze(immediateSyncListeners),
619
+ immediateQueuedListeners: Object.freeze(immediateQueuedListeners),
620
+ deferredSyncListeners: Object.freeze(deferredSyncListeners),
621
+ deferredQueuedListeners: Object.freeze(deferredQueuedListeners)
622
+ });
623
+ }
624
+ function scheduleDeferredDispatch(callback, eventName) {
625
+ const defer = getRuntimeState().hooks.defer ?? deferEventDispatchToDatabaseCommit;
626
+ return defer(callback, {
627
+ eventName,
628
+ afterCommit: true
629
+ });
630
+ }
631
+ async function executeDispatch(event, payload, options = {}) {
632
+ const eventName = resolveDispatchedEventName(event);
633
+ requireRegisteredEvent(eventName);
634
+ const envelope = createEventEnvelope(eventName, payload);
635
+ const listeners = listRegisteredListenersForEvent(eventName);
636
+ const groups = splitListenersForExecution(listeners, options.afterCommit === true);
637
+ if (options.afterCommit) {
638
+ const scheduled = scheduleDeferredDispatch(async () => {
639
+ await executeListenerGroups(
640
+ [...groups.immediateSyncListeners, ...groups.deferredSyncListeners],
641
+ [...groups.immediateQueuedListeners, ...groups.deferredQueuedListeners],
642
+ envelope,
643
+ options
644
+ );
645
+ }, eventName);
646
+ if (scheduled) {
647
+ return Object.freeze({
648
+ eventName,
649
+ occurredAt: envelope.occurredAt,
650
+ deferred: true,
651
+ syncListeners: groups.immediateSyncListeners.length + groups.deferredSyncListeners.length,
652
+ queuedListeners: groups.immediateQueuedListeners.length + groups.deferredQueuedListeners.length
653
+ });
654
+ }
655
+ }
656
+ const immediate = await executeListenerGroups(
657
+ groups.immediateSyncListeners,
658
+ groups.immediateQueuedListeners,
659
+ envelope,
660
+ options
661
+ );
662
+ let deferred = false;
663
+ if (groups.deferredSyncListeners.length > 0 || groups.deferredQueuedListeners.length > 0) {
664
+ const scheduled = scheduleDeferredDispatch(async () => {
665
+ await executeListenerGroups(
666
+ groups.deferredSyncListeners,
667
+ groups.deferredQueuedListeners,
668
+ envelope,
669
+ options
670
+ );
671
+ }, eventName);
672
+ if (scheduled) {
673
+ deferred = true;
674
+ } else {
675
+ await executeListenerGroups(
676
+ groups.deferredSyncListeners,
677
+ groups.deferredQueuedListeners,
678
+ envelope,
679
+ options
680
+ );
681
+ }
682
+ }
683
+ return Object.freeze({
684
+ eventName,
685
+ occurredAt: envelope.occurredAt,
686
+ deferred,
687
+ syncListeners: immediate.syncCount + groups.deferredSyncListeners.length,
688
+ queuedListeners: immediate.queuedCount + groups.deferredQueuedListeners.length
689
+ });
690
+ }
691
+ var PendingEventDispatch = class _PendingEventDispatch {
692
+ constructor(event, payload, options = {}) {
693
+ this.event = event;
694
+ this.payload = payload;
695
+ this.options = options;
696
+ }
697
+ options;
698
+ executionPromise;
699
+ afterCommit() {
700
+ return new _PendingEventDispatch(this.event, this.payload, {
701
+ ...this.options,
702
+ afterCommit: true
703
+ });
704
+ }
705
+ onConnection(name) {
706
+ return new _PendingEventDispatch(this.event, this.payload, {
707
+ ...this.options,
708
+ connection: eventInternals.normalizeOptionalString(name, "Dispatch connection")
709
+ });
710
+ }
711
+ onQueue(name) {
712
+ return new _PendingEventDispatch(this.event, this.payload, {
713
+ ...this.options,
714
+ queueName: eventInternals.normalizeOptionalString(name, "Dispatch queue name")
715
+ });
716
+ }
717
+ delay(value) {
718
+ eventInternals.normalizeOptionalDelay(value);
719
+ return new _PendingEventDispatch(this.event, this.payload, {
720
+ ...this.options,
721
+ delay: value
722
+ });
723
+ }
724
+ async dispatch() {
725
+ return this.execute();
726
+ }
727
+ then(onfulfilled, onrejected) {
728
+ return this.execute().then(onfulfilled, onrejected);
729
+ }
730
+ catch(onrejected) {
731
+ return this.execute().catch(onrejected);
732
+ }
733
+ finally(onfinally) {
734
+ return this.execute().finally(onfinally ?? void 0);
735
+ }
736
+ execute() {
737
+ this.executionPromise ??= executeDispatch(this.event, this.payload, this.options);
738
+ return this.executionPromise;
739
+ }
740
+ };
741
+ function configureEventsRuntime(hooks = {}) {
742
+ getRuntimeState().hooks = {
743
+ ...hooks
744
+ };
745
+ }
746
+ function getEventsRuntime() {
747
+ return createRuntimeBinding(getRuntimeState());
748
+ }
749
+ function resetEventsRuntime() {
750
+ getRuntimeState().hooks = {};
751
+ }
752
+ function dispatchEvent(event, payload) {
753
+ return new PendingEventDispatch(event, payload);
754
+ }
755
+ var Event = {
756
+ dispatch: dispatchEvent
757
+ };
758
+ var eventRuntimeInternals = {
759
+ assertEventJsonValue,
760
+ createEventEnvelope,
761
+ createQueuedListenerDispatch,
762
+ createRuntimeBinding,
763
+ dispatchQueuedListeners,
764
+ executeListenerGroups,
765
+ executeDispatch,
766
+ executeListener,
767
+ executeSyncListeners,
768
+ getRuntimeState,
769
+ isPlainObject,
770
+ normalizeEventName,
771
+ requireRegisteredEvent,
772
+ resolveDispatchedEventName,
773
+ scheduleDeferredDispatch,
774
+ splitRegisteredListeners,
775
+ splitListenersForExecution,
776
+ validateQueuedEventPayload
777
+ };
778
+ export {
779
+ EVENTS_INVOKE_LISTENER_JOB,
780
+ Event,
781
+ configureEventsRuntime,
782
+ deferEventDispatchToDatabaseCommit,
783
+ defineEvent,
784
+ defineListener,
785
+ dispatchEvent,
786
+ ensureEventsQueueJobRegistered,
787
+ eventInternals,
788
+ eventQueueInternals,
789
+ eventRegistryInternals,
790
+ eventRuntimeInternals,
791
+ getEventsRuntime,
792
+ getRegisteredEvent,
793
+ getRegisteredListener,
794
+ isEventDefinition,
795
+ isListenerDefinition,
796
+ listRegisteredEvents,
797
+ listRegisteredListeners,
798
+ listRegisteredListenersForEvent,
799
+ normalizeEventDefinition,
800
+ normalizeListenerDefinition,
801
+ registerEvent,
802
+ registerEvents,
803
+ registerListener,
804
+ registerListeners,
805
+ resetEventRegistry,
806
+ resetEventsRegistry,
807
+ resetEventsRuntime,
808
+ resetListenerRegistry,
809
+ runQueuedListenerInvocation,
810
+ unregisterEvent,
811
+ unregisterListener
812
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@holo-js/events",
3
+ "version": "0.1.0",
4
+ "description": "Holo-JS Framework - event contracts and runtime surface",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.mjs",
11
+ "default": "./dist/index.mjs"
12
+ }
13
+ },
14
+ "main": "./dist/index.mjs",
15
+ "types": "./dist/index.d.ts",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "stub": "tsup",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit",
23
+ "test": "vitest --run"
24
+ },
25
+ "dependencies": {
26
+ "@holo-js/db": "workspace:*",
27
+ "@holo-js/queue": "workspace:*"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "catalog:",
31
+ "tsup": "catalog:",
32
+ "typescript": "catalog:",
33
+ "vitest": "catalog:"
34
+ }
35
+ }