@avelonjs/core 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,92 @@
1
+ import type { DriverContract } from './common'
2
+
3
+ /** Literal search features used to constrain query options. */
4
+ export interface SearchCapabilities {
5
+ /** Whether queries may request facet counts. */
6
+ readonly facets: boolean
7
+ }
8
+
9
+ /** Search document with a stable identifier and indexable fields. */
10
+ export interface SearchDocument {
11
+ /** Stable document identifier. */
12
+ id: string
13
+ /** Indexable document fields. */
14
+ fields: Readonly<Record<string, unknown>>
15
+ }
16
+
17
+ /** Portable search query options. */
18
+ export interface SearchOptions {
19
+ /** Maximum hits to return. */
20
+ limit?: number
21
+ /** Number of hits to skip. */
22
+ offset?: number
23
+ /** Exact filters applied by the driver. */
24
+ filters?: Readonly<Record<string, unknown>>
25
+ /** Fields for which facet counts should be returned. */
26
+ facets?: readonly string[]
27
+ }
28
+
29
+ /** One search hit and its normalized relevance score. */
30
+ export interface SearchHit<TDocument extends SearchDocument = SearchDocument> {
31
+ /** Matching document. */
32
+ document: TDocument
33
+ /** Driver-normalized relevance score. */
34
+ score: number
35
+ }
36
+
37
+ /** Portable search response. */
38
+ export interface SearchResult<TDocument extends SearchDocument = SearchDocument> {
39
+ /** Ordered matching documents. */
40
+ hits: readonly SearchHit<TDocument>[]
41
+ /** Total matches before pagination. */
42
+ total: number
43
+ /** Requested facet counts when supported. */
44
+ facets?: Readonly<Record<string, Readonly<Record<string, number>>>>
45
+ }
46
+
47
+ /** Search indexing and query contract. */
48
+ export interface SearchDriver<
49
+ TCapabilities extends SearchCapabilities = SearchCapabilities,
50
+ TRaw = unknown,
51
+ > extends DriverContract<TCapabilities, TRaw> {
52
+ /** Adds or replaces documents in a named index. */
53
+ index(name: string, documents: readonly SearchDocument[]): Promise<void>
54
+ /** Removes documents from a named index. */
55
+ remove(name: string, ids: readonly string[]): Promise<void>
56
+ /** Queries a named index. */
57
+ query<TDocument extends SearchDocument = SearchDocument>(
58
+ name: string,
59
+ query: string,
60
+ options?: SearchOptions,
61
+ ): Promise<SearchResult<TDocument>>
62
+ }
63
+
64
+ type SearchOptionsFor<TCapabilities extends SearchCapabilities> =
65
+ TCapabilities['facets'] extends true
66
+ ? SearchOptions
67
+ : Omit<SearchOptions, 'facets'> & { facets?: never }
68
+
69
+ /** Search facade narrowed to its driver's facet capability. */
70
+ export interface SearchSurface<TDriver extends SearchDriver> {
71
+ /** Driver implementation name. */
72
+ readonly name: TDriver['name']
73
+ /** Configured search index connection name. */
74
+ readonly instance: TDriver['instance']
75
+ /** Exact search capability declaration. */
76
+ readonly capabilities: TDriver['capabilities']
77
+ /** Adds or replaces documents in an index. */
78
+ index(name: string, documents: readonly SearchDocument[]): Promise<void>
79
+ /** Removes documents from an index. */
80
+ remove(name: string, ids: readonly string[]): Promise<void>
81
+ /** Queries an index using only supported options. */
82
+ query<TDocument extends SearchDocument = SearchDocument>(
83
+ name: string,
84
+ query: string,
85
+ options?: SearchOptionsFor<TDriver['capabilities']>,
86
+ ): Promise<SearchResult<TDocument>>
87
+ /** Returns the driver's typed underlying client. */
88
+ raw(): ReturnType<TDriver['raw']>
89
+ }
90
+
91
+ /** Search facade narrowed to its driver's exact capabilities. */
92
+ export type Search<TDriver extends SearchDriver> = SearchSurface<TDriver>
@@ -0,0 +1,63 @@
1
+ import type { CapabilityMembers, DriverContract } from './common'
2
+
3
+ /** Literal social authentication providers accepted by a driver instance. */
4
+ export interface SocialCapabilities<TProvider extends string = string> {
5
+ /** Provider identifiers retained as a literal readonly list. */
6
+ readonly providers: readonly TProvider[]
7
+ }
8
+
9
+ /** Identity data returned after a social authorization callback. */
10
+ export interface SocialIdentity<TProfile = unknown> {
11
+ /** Driver-independent provider identifier. */
12
+ provider: string
13
+ /** Stable provider-side subject identifier. */
14
+ subject: string
15
+ /** Provider profile normalized by the driver. */
16
+ profile: TProfile
17
+ }
18
+
19
+ /** OAuth redirect and callback contract. */
20
+ export interface SocialDriver<
21
+ TCapabilities extends SocialCapabilities = SocialCapabilities,
22
+ TRaw = unknown,
23
+ TProfile = unknown,
24
+ > extends DriverContract<TCapabilities, TRaw> {
25
+ /** Builds the authorization URL for a configured provider. */
26
+ redirect(provider: string, callbackUrl: string, state?: string): Promise<string>
27
+ /** Verifies `state` and exchanges the callback `code` for normalized social identity data. */
28
+ callback(
29
+ provider: string,
30
+ params: Readonly<Record<string, string>>,
31
+ callbackUrl: string,
32
+ ): Promise<SocialIdentity<TProfile>>
33
+ }
34
+
35
+ type SocialProfileOf<TDriver extends SocialDriver> =
36
+ TDriver extends SocialDriver<SocialCapabilities, unknown, infer TProfile> ? TProfile : never
37
+
38
+ /** Social facade restricted to the configured provider literals. */
39
+ export interface SocialSurface<TDriver extends SocialDriver> {
40
+ /** Driver implementation name. */
41
+ readonly name: TDriver['name']
42
+ /** Configured social connection name. */
43
+ readonly instance: TDriver['instance']
44
+ /** Exact provider capability list. */
45
+ readonly capabilities: TDriver['capabilities']
46
+ /** Builds an authorization URL for a configured provider. */
47
+ redirect(
48
+ provider: CapabilityMembers<TDriver['capabilities']['providers']>,
49
+ callbackUrl: string,
50
+ state?: string,
51
+ ): Promise<string>
52
+ /** Verifies `state` and exchanges the configured provider's callback `code`. */
53
+ callback(
54
+ provider: CapabilityMembers<TDriver['capabilities']['providers']>,
55
+ params: Readonly<Record<string, string>>,
56
+ callbackUrl: string,
57
+ ): Promise<SocialIdentity<SocialProfileOf<TDriver>>>
58
+ /** Returns the driver's typed underlying client. */
59
+ raw(): ReturnType<TDriver['raw']>
60
+ }
61
+
62
+ /** Social authentication facade narrowed to configured provider literals. */
63
+ export type Social<TDriver extends SocialDriver> = SocialSurface<TDriver>
@@ -0,0 +1,78 @@
1
+ import type { CapabilityMembers, CapabilitySurface, DriverContract } from './common'
2
+
3
+ /** Portable transform operations a storage driver may advertise. */
4
+ export type StorageTransform = 'resize' | 'crop' | 'format'
5
+
6
+ /** Literal storage features used to narrow file operations. */
7
+ export interface StorageCapabilities<TTransform extends StorageTransform = StorageTransform> {
8
+ /** Whether temporary signed URLs are available. */
9
+ readonly signedUrls: boolean
10
+ /** Transform operations retained as a literal readonly list. */
11
+ readonly transforms: readonly TTransform[]
12
+ }
13
+
14
+ /** Metadata returned for a stored object. */
15
+ export interface StorageObject {
16
+ /** Object path within the configured disk. */
17
+ path: string
18
+ /** Object size in bytes. */
19
+ size: number
20
+ /** Media type when known. */
21
+ contentType?: string
22
+ /** Driver-independent entity tag when available. */
23
+ etag?: string
24
+ }
25
+
26
+ /** Portable image or document transform request. */
27
+ export interface StorageTransformOptions {
28
+ /** Requested output width in pixels. */
29
+ width?: number
30
+ /** Requested output height in pixels. */
31
+ height?: number
32
+ /** Requested output media subtype. */
33
+ format?: string
34
+ }
35
+
36
+ /** Base file storage contract. */
37
+ export interface StorageDriver<
38
+ TCapabilities extends StorageCapabilities = StorageCapabilities,
39
+ TRaw = unknown,
40
+ > extends DriverContract<TCapabilities, TRaw> {
41
+ /** Stores bytes at a path and returns normalized metadata. */
42
+ put(
43
+ path: string,
44
+ contents: Uint8Array | AsyncIterable<Uint8Array>,
45
+ options?: { readonly contentType?: string },
46
+ ): Promise<StorageObject>
47
+ /** Reads an object's bytes. */
48
+ get(path: string): Promise<Uint8Array>
49
+ /** Deletes an object if it exists. */
50
+ delete(path: string): Promise<void>
51
+ /** Reports whether an object exists. */
52
+ exists(path: string): Promise<boolean>
53
+ }
54
+
55
+ /** Signed URL methods exposed only by drivers that support them. */
56
+ export interface SignedUrlStorageSurface {
57
+ /** Creates a temporary URL for reading a stored object. */
58
+ signedUrl(path: string, expiresInSeconds: number): Promise<string>
59
+ }
60
+
61
+ /** Transform methods restricted to a driver's literal operation list. */
62
+ export interface TransformStorageSurface<TTransform extends StorageTransform> {
63
+ /** Creates a URL or derivative for one supported transform operation. */
64
+ transform(path: string, operation: TTransform, options: StorageTransformOptions): Promise<string>
65
+ }
66
+
67
+ type StorageTransformFor<TCapabilities extends StorageCapabilities> =
68
+ TCapabilities['transforms'] extends readonly []
69
+ ? object
70
+ : TransformStorageSurface<CapabilityMembers<TCapabilities['transforms']>>
71
+
72
+ /** Storage facade narrowed to signed URL and literal transform capabilities. */
73
+ export type Storage<TDriver extends StorageDriver> = Pick<
74
+ TDriver,
75
+ 'name' | 'instance' | 'capabilities' | 'put' | 'get' | 'delete' | 'exists' | 'raw'
76
+ > &
77
+ CapabilitySurface<TDriver['capabilities']['signedUrls'], SignedUrlStorageSurface> &
78
+ StorageTransformFor<TDriver['capabilities']>
@@ -0,0 +1,87 @@
1
+ import type { DriverContract } from './common'
2
+
3
+ /** Literal API-token features used to constrain token issuance. */
4
+ export interface TokenCapabilities {
5
+ /** Whether issued tokens may carry ability restrictions. */
6
+ readonly abilities: boolean
7
+ /** Whether issued tokens may expire automatically. */
8
+ readonly expiration: boolean
9
+ }
10
+
11
+ /** Options accepted when issuing an API token. */
12
+ export interface TokenIssueOptions {
13
+ /** Abilities granted to the token. */
14
+ abilities?: readonly string[]
15
+ /** Time after which the token is rejected. */
16
+ expiresAt?: Date
17
+ }
18
+
19
+ /** API-token metadata safe to persist or display. */
20
+ export interface TokenRecord {
21
+ /** Stable token identifier. */
22
+ id: string
23
+ /** Stable application subject authenticated by the token. */
24
+ subject: string
25
+ /** User-facing token name. */
26
+ name: string
27
+ /** Granted ability names. */
28
+ abilities: readonly string[]
29
+ /** Issuance time. */
30
+ createdAt: Date
31
+ /** Expiration time, or null for a non-expiring token. */
32
+ expiresAt: Date | null
33
+ }
34
+
35
+ /** One-time plaintext token returned only at issuance. */
36
+ export interface IssuedToken extends TokenRecord {
37
+ /** Secret token value that must not be returned by later list operations. */
38
+ plainText: string
39
+ }
40
+
41
+ /** API token authentication, issuance, listing, and revocation contract. */
42
+ export interface TokenDriver<
43
+ TCapabilities extends TokenCapabilities = TokenCapabilities,
44
+ TRaw = unknown,
45
+ > extends DriverContract<TCapabilities, TRaw> {
46
+ /**
47
+ * Authenticates a plaintext token, throwing `Unauthenticated` when it is missing, revoked, or
48
+ * expired.
49
+ */
50
+ verify(plainText: string): Promise<TokenRecord>
51
+ /** Issues a named API token. */
52
+ issue(name: string, options?: TokenIssueOptions): Promise<IssuedToken>
53
+ /** Lists metadata for the current actor's tokens. */
54
+ list(): Promise<readonly TokenRecord[]>
55
+ /** Revokes one token by its stable identifier. */
56
+ revoke(id: string): Promise<void>
57
+ }
58
+
59
+ type TokenOptionsFor<TCapabilities extends TokenCapabilities> =
60
+ (TCapabilities['abilities'] extends true ? object : { abilities?: never }) &
61
+ (TCapabilities['expiration'] extends true ? object : { expiresAt?: never })
62
+
63
+ /** Token facade narrowed to the configured issuance features. */
64
+ export interface TokenSurface<TDriver extends TokenDriver> {
65
+ /** Driver implementation name. */
66
+ readonly name: TDriver['name']
67
+ /** Configured token issuer name. */
68
+ readonly instance: TDriver['instance']
69
+ /** Exact token capability declaration. */
70
+ readonly capabilities: TDriver['capabilities']
71
+ /**
72
+ * Authenticates a plaintext token, throwing `Unauthenticated` when it is missing, revoked, or
73
+ * expired.
74
+ */
75
+ verify(plainText: string): Promise<TokenRecord>
76
+ /** Issues a token using only options supported by the driver. */
77
+ issue(name: string, options?: TokenOptionsFor<TDriver['capabilities']>): Promise<IssuedToken>
78
+ /** Lists token metadata without plaintext secrets. */
79
+ list(): Promise<readonly TokenRecord[]>
80
+ /** Revokes one token. */
81
+ revoke(id: string): Promise<void>
82
+ /** Returns the driver's typed underlying client. */
83
+ raw(): ReturnType<TDriver['raw']>
84
+ }
85
+
86
+ /** API-token facade narrowed to its driver's exact capabilities. */
87
+ export type Tokens<TDriver extends TokenDriver> = TokenSurface<TDriver>
package/src/errors.ts ADDED
@@ -0,0 +1,166 @@
1
+ /** Stable machine-readable codes for framework and normalized driver failures. */
2
+ export type ErrorCode =
3
+ | 'NOT_FOUND'
4
+ | 'CONFLICT'
5
+ | 'UNAUTHENTICATED'
6
+ | 'FORBIDDEN'
7
+ | 'RATE_LIMITED'
8
+ | 'INVALID'
9
+ | 'UNAVAILABLE'
10
+ | 'DRIVER_FAULT'
11
+
12
+ /** Constructor options shared by every framework error. */
13
+ export interface AvelonErrorOptions<TMetadata extends object> {
14
+ /** Stable framework metadata without vendor response shapes. */
15
+ metadata: TMetadata
16
+ /** Original failure retained through standard error chaining. */
17
+ cause?: unknown
18
+ }
19
+
20
+ /** Base error carrying a stable code, typed metadata, and a standard cause. */
21
+ export class AvelonError<TMetadata extends object> extends Error {
22
+ /** Stable machine-readable error code. */
23
+ readonly code: ErrorCode
24
+
25
+ /** Framework-owned metadata safe to inspect across driver changes. */
26
+ readonly metadata: Readonly<TMetadata>
27
+
28
+ /** Creates an error with a fixed code and typed metadata. */
29
+ constructor(
30
+ name: string,
31
+ code: ErrorCode,
32
+ message: string,
33
+ options: AvelonErrorOptions<TMetadata>,
34
+ ) {
35
+ super(message, { cause: options.cause })
36
+ this.name = name
37
+ this.code = code
38
+ this.metadata = options.metadata
39
+ }
40
+ }
41
+
42
+ /** Metadata identifying a resource lookup that failed. */
43
+ export interface NotFoundMetadata {
44
+ /** Resource type that could not be found. */
45
+ resource: string
46
+ /** Requested identifier when one is safe to expose. */
47
+ identifier?: string | number
48
+ }
49
+
50
+ /** Failure raised when a requested resource does not exist. */
51
+ export class NotFound extends AvelonError<NotFoundMetadata> {
52
+ /** Creates a not-found failure. */
53
+ constructor(message: string, options: AvelonErrorOptions<NotFoundMetadata>) {
54
+ super('NotFound', 'NOT_FOUND', message, options)
55
+ }
56
+ }
57
+
58
+ /** Metadata describing a framework-level state conflict. */
59
+ export interface ConflictMetadata {
60
+ /** Resource involved in the conflict. */
61
+ resource?: string
62
+ /** Framework-owned conflict key, never a vendor constraint shape. */
63
+ key?: string
64
+ }
65
+
66
+ /** Failure raised when an operation conflicts with existing state. */
67
+ export class Conflict extends AvelonError<ConflictMetadata> {
68
+ /** Creates a conflict failure. */
69
+ constructor(message: string, options: AvelonErrorOptions<ConflictMetadata>) {
70
+ super('Conflict', 'CONFLICT', message, options)
71
+ }
72
+ }
73
+
74
+ /** Metadata describing the authentication boundary that rejected a request. */
75
+ export interface UnauthenticatedMetadata {
76
+ /** Authentication guard or context used by the application. */
77
+ guard?: string
78
+ }
79
+
80
+ /** Failure raised when an operation requires an authenticated actor. */
81
+ export class Unauthenticated extends AvelonError<UnauthenticatedMetadata> {
82
+ /** Creates an unauthenticated failure. */
83
+ constructor(message: string, options: AvelonErrorOptions<UnauthenticatedMetadata>) {
84
+ super('Unauthenticated', 'UNAUTHENTICATED', message, options)
85
+ }
86
+ }
87
+
88
+ /** Metadata describing an authorization denial. */
89
+ export interface ForbiddenMetadata {
90
+ /** Ability or action that was denied. */
91
+ ability?: string
92
+ /** Resource type involved in the authorization check. */
93
+ resource?: string
94
+ }
95
+
96
+ /** Failure raised when an authenticated actor lacks permission. */
97
+ export class Forbidden extends AvelonError<ForbiddenMetadata> {
98
+ /** Creates a forbidden failure. */
99
+ constructor(message: string, options: AvelonErrorOptions<ForbiddenMetadata>) {
100
+ super('Forbidden', 'FORBIDDEN', message, options)
101
+ }
102
+ }
103
+
104
+ /** Metadata describing a rate-limit decision. */
105
+ export interface RateLimitedMetadata {
106
+ /** Stable application key that was limited. */
107
+ key?: string
108
+ /** Milliseconds until another attempt may succeed. */
109
+ retryAfterMs?: number
110
+ /** Limit applied during the current window. */
111
+ limit?: number
112
+ }
113
+
114
+ /** Failure raised when an operation exceeds its allowed rate. */
115
+ export class RateLimited extends AvelonError<RateLimitedMetadata> {
116
+ /** Creates a rate-limited failure. */
117
+ constructor(message: string, options: AvelonErrorOptions<RateLimitedMetadata>) {
118
+ super('RateLimited', 'RATE_LIMITED', message, options)
119
+ }
120
+ }
121
+
122
+ /** Metadata describing invalid input in framework-owned terms. */
123
+ export interface InvalidMetadata {
124
+ /** Field-keyed validation messages. */
125
+ fields?: Readonly<Record<string, readonly string[]>>
126
+ }
127
+
128
+ /** Failure raised when supplied input is invalid. */
129
+ export class Invalid extends AvelonError<InvalidMetadata> {
130
+ /** Creates an invalid-input failure. */
131
+ constructor(message: string, options: AvelonErrorOptions<InvalidMetadata>) {
132
+ super('Invalid', 'INVALID', message, options)
133
+ }
134
+ }
135
+
136
+ /** Metadata describing a temporarily unavailable framework service. */
137
+ export interface UnavailableMetadata {
138
+ /** Framework service or capability that is unavailable. */
139
+ service?: string
140
+ /** Milliseconds after which retrying may succeed. */
141
+ retryAfterMs?: number
142
+ }
143
+
144
+ /** Failure raised when a required service is temporarily unavailable. */
145
+ export class Unavailable extends AvelonError<UnavailableMetadata> {
146
+ /** Creates an unavailable failure. */
147
+ constructor(message: string, options: AvelonErrorOptions<UnavailableMetadata>) {
148
+ super('Unavailable', 'UNAVAILABLE', message, options)
149
+ }
150
+ }
151
+
152
+ /** Metadata identifying a normalized driver failure. */
153
+ export interface DriverFaultMetadata {
154
+ /** Driver contract that failed, such as `database` or `mail`. */
155
+ driver: string
156
+ /** Framework operation being attempted. */
157
+ operation: string
158
+ }
159
+
160
+ /** Failure raised when a driver returns an otherwise unmapped failure. */
161
+ export class DriverFault extends AvelonError<DriverFaultMetadata> {
162
+ /** Creates a normalized driver fault. */
163
+ constructor(message: string, options: AvelonErrorOptions<DriverFaultMetadata>) {
164
+ super('DriverFault', 'DRIVER_FAULT', message, options)
165
+ }
166
+ }
package/src/events.ts ADDED
@@ -0,0 +1,185 @@
1
+ /** Listener delivery strategies supported by the event dispatcher. */
2
+ export type DeliveryMode = 'sync' | 'after' | 'queued'
3
+
4
+ /** Base event with explicit, dispatcher-readable propagation state. */
5
+ export class Event {
6
+ #propagationStopped = false
7
+
8
+ /** Stops remaining listeners, including queued listeners not yet scheduled. */
9
+ stopPropagation(): void {
10
+ this.#propagationStopped = true
11
+ }
12
+
13
+ /** Reports whether a listener has stopped the current dispatch chain. */
14
+ isPropagationStopped(): boolean {
15
+ return this.#propagationStopped
16
+ }
17
+ }
18
+
19
+ /** Constructable event class used when registering listeners. */
20
+ export interface EventConstructor<TEvent extends Event = Event> {
21
+ /** Creates an event instance from its declared constructor arguments. */
22
+ new (...args: never[]): TEvent
23
+ /** Runtime class name used for event discovery. */
24
+ readonly name: string
25
+ }
26
+
27
+ /** Options controlling one event dispatch. */
28
+ export interface EventDispatchOptions {
29
+ /** Holds dispatch until the enclosing transaction commits and drops it on rollback. */
30
+ afterCommit?: boolean
31
+ }
32
+
33
+ /** Stable reference extracted while serializing a queued event. */
34
+ export interface SerializedReference<TId extends string | number = string | number> {
35
+ /** Framework registry type used to rehydrate the reference. */
36
+ type: string
37
+ /** Identifier resolved when the queued listener runs. */
38
+ id: TId
39
+ }
40
+
41
+ /** Serialized event payload crossing the queue boundary. */
42
+ export interface SerializedEvent {
43
+ /** Registered event name. */
44
+ name: string
45
+ /** Serializable event data with references replaced by tokens. */
46
+ payload: unknown
47
+ /** References rehydrated against current state in the worker. */
48
+ references: readonly SerializedReference[]
49
+ }
50
+
51
+ /** Serializer boundary supplied by the model/event integration layer. */
52
+ export interface EventSerializer<TEvent extends Event = Event> {
53
+ /** Converts an event into a queue-safe payload and explicit references. */
54
+ serialize(event: TEvent): Promise<SerializedEvent>
55
+ /** Rehydrates an event and its references from a queued payload. */
56
+ deserialize(serialized: SerializedEvent): Promise<TEvent>
57
+ }
58
+
59
+ /** Context passed to an inline listener. */
60
+ export interface SyncListenerContext {
61
+ /** Actual delivery mode. */
62
+ delivery: 'sync'
63
+ }
64
+
65
+ /** Context passed to a best-effort post-response listener. */
66
+ export interface AfterListenerContext {
67
+ /** Actual delivery mode. */
68
+ delivery: 'after'
69
+ }
70
+
71
+ /** Worker context for a durable listener; actors must be carried explicitly on the event. */
72
+ export interface QueuedListenerContext {
73
+ /** Actual delivery mode. */
74
+ delivery: 'queued'
75
+ /** Named queue processing the listener. */
76
+ queue: string
77
+ /** One-based execution attempt. */
78
+ attempt: number
79
+ /** Maximum attempts before the payload moves to failed storage. */
80
+ tries: number
81
+ }
82
+
83
+ /** Delivery-specific listener context with no implicit request or actor. */
84
+ export type ListenerContext<TDelivery extends DeliveryMode = DeliveryMode> =
85
+ TDelivery extends 'sync'
86
+ ? SyncListenerContext
87
+ : TDelivery extends 'after'
88
+ ? AfterListenerContext
89
+ : QueuedListenerContext
90
+
91
+ /** Result accepted from a listener; false stops propagation. */
92
+ export type ListenerResult<TResult = unknown> = TResult | false | null | void
93
+
94
+ type QueueOptions<TDelivery extends DeliveryMode> = TDelivery extends 'queued'
95
+ ? {
96
+ queue: string
97
+ tries: number
98
+ backoff: number | readonly number[]
99
+ }
100
+ : {
101
+ queue?: never
102
+ tries?: never
103
+ backoff?: never
104
+ }
105
+
106
+ type DeliveryOption<TDelivery extends DeliveryMode> = TDelivery extends 'sync'
107
+ ? { delivery?: 'sync' }
108
+ : { delivery: TDelivery }
109
+
110
+ /** Declarative listener contract with delivery-specific retry and queue settings. */
111
+ export type ListenerDefinition<
112
+ TEvent extends Event,
113
+ TResult = unknown,
114
+ TDelivery extends DeliveryMode = DeliveryMode,
115
+ > = {
116
+ /** Ascending execution priority. Lower values run first. */
117
+ priority?: number
118
+ /** Handles the event in a context determined by its actual delivery mode. */
119
+ handle(
120
+ event: TEvent,
121
+ context: ListenerContext<TDelivery>,
122
+ ): ListenerResult<TResult> | Promise<ListenerResult<TResult>>
123
+ } & DeliveryOption<TDelivery> &
124
+ QueueOptions<TDelivery>
125
+
126
+ /** Creates a listener definition while preserving literal delivery and retry information. */
127
+ export function defineListener<
128
+ TEvent extends Event,
129
+ TResult = unknown,
130
+ const TDelivery extends DeliveryMode = 'sync',
131
+ >(
132
+ definition: ListenerDefinition<TEvent, TResult, TDelivery>,
133
+ ): ListenerDefinition<TEvent, TResult, TDelivery> {
134
+ return definition
135
+ }
136
+
137
+ /** Registration joining an event class to one listener definition. */
138
+ export interface ListenerRegistration<TEvent extends Event = Event> {
139
+ /** Event class this registration handles. */
140
+ event: EventConstructor<TEvent>
141
+ /** Listener invoked for that event. */
142
+ listener: ListenerDefinition<TEvent>
143
+ }
144
+
145
+ /** Multi-event subscriber contract registered through a provider. */
146
+ export interface EventSubscriber {
147
+ /** Returns every event/listener pair owned by the subscriber. */
148
+ listeners(): readonly ListenerRegistration[]
149
+ }
150
+
151
+ /** Durable queue envelope with explicit execution context and no implicit actor. */
152
+ export interface QueuedEventEnvelope {
153
+ /** Serialized event data. */
154
+ event: SerializedEvent
155
+ /** Stable listener registration name. */
156
+ listener: string
157
+ /** Named queue receiving the event. */
158
+ queue: string
159
+ /** One-based attempt number. */
160
+ attempt: number
161
+ /** Earliest epoch time in milliseconds at which the listener may run. */
162
+ availableAt: number
163
+ }
164
+
165
+ /** Event bus contract supporting ordered, post-response, queued, and result-seeking dispatch. */
166
+ export interface EventDispatcher {
167
+ /** Registers one or more listeners for a typed event class. */
168
+ listen<TEvent extends Event>(
169
+ event: EventConstructor<TEvent>,
170
+ listeners: ListenerDefinition<TEvent> | readonly ListenerDefinition<TEvent>[],
171
+ ): void
172
+ /** Registers one listener for an event-name pattern such as `post.*`. */
173
+ listen(pattern: string, listener: ListenerDefinition<Event>): void
174
+ /** Registers every listener exposed by a multi-event subscriber. */
175
+ subscribe(subscriber: EventSubscriber): void
176
+ /** Runs synchronous listeners in priority order before scheduling later delivery modes. */
177
+ dispatch<TEvent extends Event>(event: TEvent, options?: EventDispatchOptions): Promise<void>
178
+ /** Runs every listener after the response regardless of its declared mode. */
179
+ dispatchAfterResponse<TEvent extends Event>(event: TEvent): Promise<void>
180
+ /** Returns the first non-null listener result and stops dispatching. */
181
+ until<TResult, TEvent extends Event>(
182
+ event: TEvent,
183
+ options?: EventDispatchOptions,
184
+ ): Promise<TResult | null>
185
+ }