@prosody-events/prosody 0.5.1 → 0.6.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.
- package/README.md +157 -55
- package/bindings.d.ts +302 -404
- package/bindings.js +168 -85
- package/index.d.ts +30 -962
- package/index.js +42 -1898
- package/lib/client.d.ts +232 -0
- package/lib/client.js +347 -0
- package/lib/context.d.ts +161 -0
- package/lib/context.js +86 -0
- package/lib/errors.d.ts +103 -0
- package/lib/errors.js +102 -0
- package/lib/logging.d.ts +75 -0
- package/lib/logging.js +99 -0
- package/lib/payload.d.ts +59 -0
- package/lib/state/bridge.js +103 -0
- package/lib/state/codec.js +206 -0
- package/lib/state/definitions.d.ts +233 -0
- package/lib/state/definitions.js +151 -0
- package/lib/state/deque.d.ts +75 -0
- package/lib/state/deque.js +77 -0
- package/lib/state/handle.d.ts +25 -0
- package/lib/state/handle.js +26 -0
- package/lib/state/iterator.js +66 -0
- package/lib/state/map.d.ts +94 -0
- package/lib/state/map.js +79 -0
- package/lib/state/published.d.ts +113 -0
- package/lib/state/published.js +147 -0
- package/lib/state/query.d.ts +85 -0
- package/lib/state/query.js +206 -0
- package/lib/state/set.d.ts +52 -0
- package/lib/state/set.js +54 -0
- package/lib/state/value.d.ts +33 -0
- package/lib/state/value.js +31 -0
- package/package.json +21 -20
package/lib/context.d.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The event context passed to handlers, and the demand that started it.
|
|
3
|
+
* @module lib/context
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Message } from "./payload";
|
|
7
|
+
import type {
|
|
8
|
+
DequeDefinition,
|
|
9
|
+
MapDefinition,
|
|
10
|
+
MessageDequeDefinition,
|
|
11
|
+
MessageMapDefinition,
|
|
12
|
+
MessageValueDefinition,
|
|
13
|
+
SetDefinition,
|
|
14
|
+
ValueDefinition,
|
|
15
|
+
} from "./state/definitions";
|
|
16
|
+
import type { DequeState } from "./state/deque";
|
|
17
|
+
import type { MapState } from "./state/map";
|
|
18
|
+
import type { SetState } from "./state/set";
|
|
19
|
+
import type { ValueState } from "./state/value";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The demand that started a handler invocation.
|
|
23
|
+
*/
|
|
24
|
+
export interface Demand {
|
|
25
|
+
/** `"normal"` for a first attempt, or `"failure"` for a retry. */
|
|
26
|
+
readonly kind: "normal" | "failure";
|
|
27
|
+
/**
|
|
28
|
+
* The retry count: 0 for normal demand and 1 on the first retry. It is
|
|
29
|
+
* an estimate. Keep an exact attempt count in keyed state if you need one.
|
|
30
|
+
*/
|
|
31
|
+
readonly retry: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The event context that a handler receives. Each call propagates the active
|
|
36
|
+
* trace context.
|
|
37
|
+
*
|
|
38
|
+
* `state()` caches one handle per definition object, so repeated calls with
|
|
39
|
+
* the same definition return the same handle. It throws a
|
|
40
|
+
* {@link TransientStateError} for an object that no definition constructor
|
|
41
|
+
* made.
|
|
42
|
+
*/
|
|
43
|
+
export declare class Context {
|
|
44
|
+
private constructor();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The demand that started this handler invocation. The value is frozen.
|
|
48
|
+
*/
|
|
49
|
+
get demand(): Demand;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Checks whether cancellation has been signaled.
|
|
53
|
+
* Cancellation includes message-level cancellation (e.g., timeout) and partition shutdown. During shutdown, cancellation is delayed until near the end of the shutdown timeout to allow in-flight work to complete.
|
|
54
|
+
*
|
|
55
|
+
* @returns True if cancellation was requested, otherwise false.
|
|
56
|
+
*/
|
|
57
|
+
get shouldCancel(): boolean;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Waits for a cancellation signal.
|
|
61
|
+
* Cancellation includes message-level cancellation (e.g., timeout) and partition shutdown. During shutdown, cancellation is delayed until near the end of the shutdown timeout to allow in-flight work to complete.
|
|
62
|
+
*
|
|
63
|
+
* @returns A promise that resolves when cancellation is signaled.
|
|
64
|
+
*/
|
|
65
|
+
onCancel(): Promise<void>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Schedule a timer at the given time.
|
|
69
|
+
*
|
|
70
|
+
* @param time - The UTC timestamp to schedule.
|
|
71
|
+
* @returns A promise that resolves when the timer has been scheduled.
|
|
72
|
+
* @throws Error if time conversion or scheduling fails.
|
|
73
|
+
*/
|
|
74
|
+
schedule(time: Date): Promise<void>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Clear existing timers and schedule a new one at the given time.
|
|
78
|
+
*
|
|
79
|
+
* @param time - The UTC timestamp to schedule.
|
|
80
|
+
* @returns A promise that resolves when the timer has been scheduled.
|
|
81
|
+
* @throws Error if time conversion or scheduling fails.
|
|
82
|
+
*/
|
|
83
|
+
clearAndSchedule(time: Date): Promise<void>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Unschedules the timer for the specified time.
|
|
87
|
+
* @param time - The time to unschedule.
|
|
88
|
+
* @returns A promise that resolves when the timer has been unscheduled.
|
|
89
|
+
* @throws Error if unscheduling fails.
|
|
90
|
+
*/
|
|
91
|
+
unschedule(time: Date): Promise<void>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Clears all scheduled timers.
|
|
95
|
+
* @returns A promise that resolves when all timers have been cleared.
|
|
96
|
+
* @throws Error if clearing schedules fails.
|
|
97
|
+
*/
|
|
98
|
+
clearScheduled(): Promise<void>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Retrieves all scheduled times.
|
|
102
|
+
* @returns An array of scheduled times as Date objects.
|
|
103
|
+
* @throws Error if retrieval fails.
|
|
104
|
+
*/
|
|
105
|
+
scheduled(): Promise<Array<Date>>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Binds a registered message single-value collection, vending a handle whose
|
|
109
|
+
* item is the full `Message<P>`.
|
|
110
|
+
*
|
|
111
|
+
* The handle and any iterator it opens are valid only within this event
|
|
112
|
+
* attempt. Throws a {@link PermanentStateError} if the collection name is
|
|
113
|
+
* unregistered or its registered identity mismatches.
|
|
114
|
+
* @param definition - A definition from {@link messageValue}.
|
|
115
|
+
*/
|
|
116
|
+
state<P>(definition: MessageValueDefinition<P>): ValueState<Message<P>>;
|
|
117
|
+
/**
|
|
118
|
+
* Binds a registered message ordered-map collection, vending a handle whose
|
|
119
|
+
* values are the full `Message<P>`. Valid only within this event attempt;
|
|
120
|
+
* throws {@link PermanentStateError} on an unregistered name or identity
|
|
121
|
+
* mismatch.
|
|
122
|
+
* @param definition - A definition from {@link messageMap}.
|
|
123
|
+
*/
|
|
124
|
+
state<P>(definition: MessageMapDefinition<P>): MapState<Message<P>>;
|
|
125
|
+
/**
|
|
126
|
+
* Binds a registered message deque collection, vending a handle whose
|
|
127
|
+
* elements are the full `Message<P>`. Valid only within this event attempt;
|
|
128
|
+
* throws {@link PermanentStateError} on an unregistered name or identity
|
|
129
|
+
* mismatch.
|
|
130
|
+
* @param definition - A definition from {@link messageDeque}.
|
|
131
|
+
*/
|
|
132
|
+
state<P>(definition: MessageDequeDefinition<P>): DequeState<Message<P>>;
|
|
133
|
+
/**
|
|
134
|
+
* Binds a registered single-value JSON collection. Valid only within this
|
|
135
|
+
* event attempt; throws {@link PermanentStateError} on an unregistered name
|
|
136
|
+
* or identity mismatch.
|
|
137
|
+
* @param definition - A definition from {@link value}.
|
|
138
|
+
*/
|
|
139
|
+
state<T>(definition: ValueDefinition<T>): ValueState<T>;
|
|
140
|
+
/**
|
|
141
|
+
* Binds a registered ordered-map JSON collection (string keys). Valid only
|
|
142
|
+
* within this event attempt; throws {@link PermanentStateError} on an
|
|
143
|
+
* unregistered name or identity mismatch.
|
|
144
|
+
* @param definition - A definition from {@link map}.
|
|
145
|
+
*/
|
|
146
|
+
state<V>(definition: MapDefinition<V>): MapState<V>;
|
|
147
|
+
/**
|
|
148
|
+
* Binds a registered set collection of string members. Valid only within
|
|
149
|
+
* this event attempt; throws {@link PermanentStateError} on an unregistered
|
|
150
|
+
* name or identity mismatch.
|
|
151
|
+
* @param definition - A definition from {@link set}.
|
|
152
|
+
*/
|
|
153
|
+
state(definition: SetDefinition): SetState;
|
|
154
|
+
/**
|
|
155
|
+
* Binds a registered deque JSON collection. Valid only within this event
|
|
156
|
+
* attempt; throws {@link PermanentStateError} on an unregistered name or
|
|
157
|
+
* identity mismatch.
|
|
158
|
+
* @param definition - A definition from {@link deque}.
|
|
159
|
+
*/
|
|
160
|
+
state<T>(definition: DequeDefinition<T>): DequeState<T>;
|
|
161
|
+
}
|
package/lib/context.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The event context passed to handlers: cancellation, timers, and state.
|
|
3
|
+
* @module lib/context
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { TransientStateError } = require("./errors");
|
|
8
|
+
const {
|
|
9
|
+
defineHidden,
|
|
10
|
+
injectedCarrier,
|
|
11
|
+
toStateError,
|
|
12
|
+
} = require("./state/bridge");
|
|
13
|
+
const { stateDefinitionAccess } = require("./state/definitions");
|
|
14
|
+
|
|
15
|
+
// The frozen demand of each native context, read on first access. The key is
|
|
16
|
+
// the native context, so the cache also works through a Proxy with a `set`
|
|
17
|
+
// trap. An entry goes away with its native context.
|
|
18
|
+
const demands = new WeakMap();
|
|
19
|
+
|
|
20
|
+
/** The event context that handlers receive. Each call propagates the trace. */
|
|
21
|
+
class Context {
|
|
22
|
+
constructor(nativeContext) {
|
|
23
|
+
// The vended state handles, cached by definition object, so repeated
|
|
24
|
+
// state(definition) calls within one event return the same handle.
|
|
25
|
+
defineHidden(this, { nativeContext, stateHandles: new Map() });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get shouldCancel() {
|
|
29
|
+
return this.nativeContext.shouldCancel;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get demand() {
|
|
33
|
+
let demand = demands.get(this.nativeContext);
|
|
34
|
+
if (demand === undefined) {
|
|
35
|
+
demand = Object.freeze({ ...this.nativeContext.demand });
|
|
36
|
+
demands.set(this.nativeContext, demand);
|
|
37
|
+
}
|
|
38
|
+
return demand;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async onCancel() {
|
|
42
|
+
return this.nativeContext.onCancel();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async schedule(time) {
|
|
46
|
+
return this.nativeContext.schedule(time, injectedCarrier());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async clearAndSchedule(time) {
|
|
50
|
+
return this.nativeContext.clearAndSchedule(time, injectedCarrier());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async unschedule(time) {
|
|
54
|
+
return this.nativeContext.unschedule(time, injectedCarrier());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async clearScheduled() {
|
|
58
|
+
return this.nativeContext.clearScheduled(injectedCarrier());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async scheduled() {
|
|
62
|
+
return this.nativeContext.scheduled(injectedCarrier());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
state(definition) {
|
|
66
|
+
const access = stateDefinitionAccess.get(definition);
|
|
67
|
+
if (access === undefined) {
|
|
68
|
+
throw new TransientStateError(
|
|
69
|
+
"state: definition must come from a Prosody state definition constructor",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const cached = this.stateHandles.get(definition);
|
|
73
|
+
if (cached !== undefined) return cached;
|
|
74
|
+
try {
|
|
75
|
+
const handle = access.owned(this.nativeContext, definition.name);
|
|
76
|
+
this.stateHandles.set(definition, handle);
|
|
77
|
+
return handle;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw toStateError(error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
Context,
|
|
86
|
+
};
|
package/lib/errors.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The handler error classes, the state error classes, and the error decorators.
|
|
3
|
+
* @module lib/errors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base class for event handler errors.
|
|
8
|
+
* Provides a common interface for determining if an error is permanent.
|
|
9
|
+
*/
|
|
10
|
+
export abstract class EventHandlerError extends Error {
|
|
11
|
+
/**
|
|
12
|
+
* @param message - The error message.
|
|
13
|
+
* @param options - The standard error options, such as `cause`.
|
|
14
|
+
*/
|
|
15
|
+
constructor(message?: string, options?: ErrorOptions);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Indicates whether the error is permanent and should not be retried.
|
|
19
|
+
*/
|
|
20
|
+
abstract get isPermanent(): boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents a transient error that may be resolved by retrying.
|
|
25
|
+
* These errors are temporary and the operation should be retried.
|
|
26
|
+
*/
|
|
27
|
+
export class TransientError extends EventHandlerError {
|
|
28
|
+
/**
|
|
29
|
+
* @returns Always false, indicating the error is not permanent.
|
|
30
|
+
*/
|
|
31
|
+
get isPermanent(): false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Represents a permanent error that should not be retried.
|
|
36
|
+
* These errors indicate unrecoverable failures.
|
|
37
|
+
*/
|
|
38
|
+
export class PermanentError extends EventHandlerError {
|
|
39
|
+
/**
|
|
40
|
+
* @returns Always true, indicating the error is permanent.
|
|
41
|
+
*/
|
|
42
|
+
get isPermanent(): true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A keyed-state failure that a later attempt can resolve.
|
|
47
|
+
*
|
|
48
|
+
* A store timeout is transient. A caller mistake is transient too: an
|
|
49
|
+
* unrepresentable write, a wrong item type, or a bad index. The event then
|
|
50
|
+
* retries, so the mistake stays visible and no message is discarded. State
|
|
51
|
+
* handles and scan iterators throw it. It extends {@link TransientError}, so a
|
|
52
|
+
* handler that rethrows it retries the event.
|
|
53
|
+
*/
|
|
54
|
+
export class TransientStateError extends TransientError {}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A keyed-state failure that no retry in this process can resolve.
|
|
58
|
+
*
|
|
59
|
+
* Examples are an unregistered collection name, a registered identity that
|
|
60
|
+
* does not match, a JSON `null` write, and a stored value that cannot be
|
|
61
|
+
* decoded. A caller mistake is a {@link TransientStateError} instead. A
|
|
62
|
+
* handler can also throw this error to mark its own failure permanent. It extends {@link PermanentError},
|
|
63
|
+
* so a handler that rethrows it does not retry the event.
|
|
64
|
+
*/
|
|
65
|
+
export class PermanentStateError extends PermanentError {}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Checks whether a value is a keyed-state error of either category. It
|
|
69
|
+
* matches the error `name`, so it also recognizes an error from another realm.
|
|
70
|
+
*
|
|
71
|
+
* @param error - The value to test.
|
|
72
|
+
* @returns True when the value is a keyed-state error.
|
|
73
|
+
*/
|
|
74
|
+
export function isStateError(
|
|
75
|
+
error: unknown,
|
|
76
|
+
): error is PermanentStateError | TransientStateError;
|
|
77
|
+
|
|
78
|
+
/** Type alias for a constructor of an Error subclass. */
|
|
79
|
+
type ErrorClass<T extends Error> = new (...args: never[]) => T;
|
|
80
|
+
|
|
81
|
+
/** A decorator for a class method. */
|
|
82
|
+
type DecoratorFunction = (
|
|
83
|
+
target: Function,
|
|
84
|
+
context: ClassMethodDecoratorContext,
|
|
85
|
+
) => Function | void;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Decorator factory for marking errors as transient.
|
|
89
|
+
* Apply it to a method. The wrapper keeps the original error as its `cause`.
|
|
90
|
+
* @param exceptionTypes The error types to be treated as transient.
|
|
91
|
+
*/
|
|
92
|
+
export declare function transient<E extends Error>(
|
|
93
|
+
...exceptionTypes: ErrorClass<E>[]
|
|
94
|
+
): DecoratorFunction;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Decorator factory for marking errors as permanent.
|
|
98
|
+
* Apply it to a method. The wrapper keeps the original error as its `cause`.
|
|
99
|
+
* @param exceptionTypes The error types to be treated as permanent.
|
|
100
|
+
*/
|
|
101
|
+
export declare function permanent<E extends Error>(
|
|
102
|
+
...exceptionTypes: ErrorClass<E>[]
|
|
103
|
+
): DecoratorFunction;
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The handler error classes, the state error classes, and the error decorators.
|
|
3
|
+
* @module lib/errors
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { types } = require("node:util");
|
|
8
|
+
|
|
9
|
+
/** Base class for event handler errors. */
|
|
10
|
+
class EventHandlerError extends Error {
|
|
11
|
+
constructor(message, options) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
this.name = this.constructor.name;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get isPermanent() {
|
|
17
|
+
throw new Error("Subclasses must implement isPermanent");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** An event handler error that a retry can resolve. */
|
|
22
|
+
class TransientError extends EventHandlerError {
|
|
23
|
+
get isPermanent() {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** An event handler error that no retry can resolve. */
|
|
29
|
+
class PermanentError extends EventHandlerError {
|
|
30
|
+
get isPermanent() {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A keyed-state failure that a later attempt can resolve. */
|
|
36
|
+
class TransientStateError extends TransientError {}
|
|
37
|
+
|
|
38
|
+
/** A keyed-state failure that no retry in this process can resolve. */
|
|
39
|
+
class PermanentStateError extends PermanentError {}
|
|
40
|
+
|
|
41
|
+
const STATE_ERROR_NAMES = new Set([
|
|
42
|
+
"PermanentStateError",
|
|
43
|
+
"TransientStateError",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
function isStateError(error) {
|
|
47
|
+
return types.isNativeError(error) && STATE_ERROR_NAMES.has(error.name);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Helper function to create error decorators.
|
|
52
|
+
* @param {Function} ErrorClass - The error class to wrap exceptions with.
|
|
53
|
+
* @returns {Function} A decorator function that wraps specified exceptions.
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
function createErrorDecorator(ErrorClass) {
|
|
57
|
+
return function decorator(...exceptionTypes) {
|
|
58
|
+
return function (originalMethod, context) {
|
|
59
|
+
if (context.kind !== "method") {
|
|
60
|
+
throw new TypeError(`@${ErrorClass.name} can only decorate methods`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handleError(error) {
|
|
64
|
+
if (exceptionTypes.some((type) => error instanceof type)) {
|
|
65
|
+
return new ErrorClass(error.message, { cause: error });
|
|
66
|
+
}
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// A method that returns a promise is classified the same way whether
|
|
71
|
+
// or not it is a native async function, because a transpiler can
|
|
72
|
+
// compile an async method into a plain function.
|
|
73
|
+
return function (...args) {
|
|
74
|
+
let result;
|
|
75
|
+
try {
|
|
76
|
+
result = originalMethod.apply(this, args);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
throw handleError(error);
|
|
79
|
+
}
|
|
80
|
+
if (typeof result?.then !== "function") return result;
|
|
81
|
+
return result.then(undefined, (error) => {
|
|
82
|
+
throw handleError(error);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const transient = createErrorDecorator(TransientError);
|
|
90
|
+
|
|
91
|
+
const permanent = createErrorDecorator(PermanentError);
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
EventHandlerError,
|
|
95
|
+
PermanentError,
|
|
96
|
+
PermanentStateError,
|
|
97
|
+
TransientError,
|
|
98
|
+
TransientStateError,
|
|
99
|
+
isStateError,
|
|
100
|
+
permanent,
|
|
101
|
+
transient,
|
|
102
|
+
};
|
package/lib/logging.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The logger interface and the logger setters.
|
|
3
|
+
* @module lib/logging
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* JavaScript logger interface for use with Prosody client.
|
|
8
|
+
*
|
|
9
|
+
* Each logging method receives a message string and optional metadata object.
|
|
10
|
+
*/
|
|
11
|
+
export interface Logger {
|
|
12
|
+
/** Logs error-level messages. */
|
|
13
|
+
error: (
|
|
14
|
+
message: string | undefined | null,
|
|
15
|
+
metadata?: Record<string, unknown>,
|
|
16
|
+
) => void;
|
|
17
|
+
/** Logs warning-level messages. */
|
|
18
|
+
warn: (
|
|
19
|
+
message: string | undefined | null,
|
|
20
|
+
metadata?: Record<string, unknown>,
|
|
21
|
+
) => void;
|
|
22
|
+
/** Logs info-level messages. */
|
|
23
|
+
info: (
|
|
24
|
+
message: string | undefined | null,
|
|
25
|
+
metadata?: Record<string, unknown>,
|
|
26
|
+
) => void;
|
|
27
|
+
/** Logs debug-level messages. */
|
|
28
|
+
debug: (
|
|
29
|
+
message: string | undefined | null,
|
|
30
|
+
metadata?: Record<string, unknown>,
|
|
31
|
+
) => void;
|
|
32
|
+
/** Logs trace-level messages. */
|
|
33
|
+
trace: (
|
|
34
|
+
message: string | undefined | null,
|
|
35
|
+
metadata?: Record<string, unknown>,
|
|
36
|
+
) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sets a new JavaScript logger for the Prosody client.
|
|
41
|
+
*
|
|
42
|
+
* This function configures the logging system to use the provided JavaScript logger
|
|
43
|
+
* for all log output. The logger must implement all required log levels.
|
|
44
|
+
*
|
|
45
|
+
* @param logger - The JavaScript logger object with error, warn, info, debug, and trace methods.
|
|
46
|
+
* @throws Error if creating the new JavaScript logger fails.
|
|
47
|
+
*/
|
|
48
|
+
export function setLogger(logger: Logger): void;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Checks whether the application set a logger. The default console logger
|
|
52
|
+
* does not count.
|
|
53
|
+
*
|
|
54
|
+
* @returns True after a call to `setLogger` or a successful `setLoggerIfUnset`.
|
|
55
|
+
*/
|
|
56
|
+
export function loggerIsSet(): boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Sets a JavaScript logger only if the application has not set one.
|
|
60
|
+
*
|
|
61
|
+
* A library that embeds Prosody can use it to supply a logger without
|
|
62
|
+
* replacing the logger of the host application.
|
|
63
|
+
*
|
|
64
|
+
* @param logger - The JavaScript logger object with error, warn, info, debug, and trace methods.
|
|
65
|
+
* @returns True if the logger was set, false if the application already set one.
|
|
66
|
+
* @throws Error if creating the new JavaScript logger fails.
|
|
67
|
+
*/
|
|
68
|
+
export function setLoggerIfUnset(logger: Logger): boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gets the current logger.
|
|
72
|
+
*
|
|
73
|
+
* @returns The logger that the application set, or the default console logger.
|
|
74
|
+
*/
|
|
75
|
+
export function getCurrentLogger(): Logger;
|
package/lib/logging.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JavaScript logger bridge and the optional Sentry reporter.
|
|
3
|
+
* @module lib/logging
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { initialize, setLogger: setLoggerInternal } = require("../bindings");
|
|
8
|
+
|
|
9
|
+
let _sentry = undefined;
|
|
10
|
+
function getSentry() {
|
|
11
|
+
if (_sentry !== undefined) return _sentry;
|
|
12
|
+
_sentry = null;
|
|
13
|
+
if (!process.env.SENTRY_DSN) return null;
|
|
14
|
+
try {
|
|
15
|
+
const Sentry = require("@sentry/node");
|
|
16
|
+
if (!Sentry.isInitialized()) {
|
|
17
|
+
Sentry.init({ dsn: process.env.SENTRY_DSN });
|
|
18
|
+
}
|
|
19
|
+
_sentry = Sentry;
|
|
20
|
+
return Sentry;
|
|
21
|
+
} catch (err) {
|
|
22
|
+
const isMissing =
|
|
23
|
+
err?.code === "MODULE_NOT_FOUND" && err.message?.includes("@sentry/node");
|
|
24
|
+
if (isMissing) {
|
|
25
|
+
getCurrentLogger().error(
|
|
26
|
+
"SENTRY_DSN is set but @sentry/node is not installed. Run: npm install @sentry/node",
|
|
27
|
+
);
|
|
28
|
+
} else {
|
|
29
|
+
getCurrentLogger().warn("Unexpected error loading @sentry/node", err);
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function captureException(error, eventType, context) {
|
|
36
|
+
const Sentry = getSentry();
|
|
37
|
+
if (!Sentry) return;
|
|
38
|
+
Sentry.withScope((scope) => {
|
|
39
|
+
scope.setTag("prosody.event_type", eventType);
|
|
40
|
+
scope.setContext("prosody", context);
|
|
41
|
+
Sentry.captureException(error.cause ?? error);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Passes the metadata to the console only when the caller gives it.
|
|
46
|
+
const write = (log) => (message, metadata) =>
|
|
47
|
+
metadata === undefined ? log(message) : log(message, metadata);
|
|
48
|
+
|
|
49
|
+
const defaultLogger = {
|
|
50
|
+
error: write(console.error),
|
|
51
|
+
warn: write(console.warn),
|
|
52
|
+
info: write(console.info),
|
|
53
|
+
debug: write(console.debug),
|
|
54
|
+
trace: write(console.debug),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// The logger that handlers use. It is the default console logger until the
|
|
58
|
+
// application sets one.
|
|
59
|
+
let currentLogger = defaultLogger;
|
|
60
|
+
|
|
61
|
+
function transformLogger(logger) {
|
|
62
|
+
return {
|
|
63
|
+
info: ([msg, meta]) => logger.info(msg, meta),
|
|
64
|
+
error: ([msg, meta]) => logger.error(msg, meta),
|
|
65
|
+
debug: ([msg, meta]) => logger.debug(msg, meta),
|
|
66
|
+
warn: ([msg, meta]) => logger.warn(msg, meta),
|
|
67
|
+
trace: ([msg, meta]) => logger.trace(msg, meta),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getCurrentLogger() {
|
|
72
|
+
return currentLogger;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
initialize();
|
|
76
|
+
setLoggerInternal(transformLogger(defaultLogger));
|
|
77
|
+
|
|
78
|
+
function setLogger(logger) {
|
|
79
|
+
currentLogger = logger;
|
|
80
|
+
setLoggerInternal(transformLogger(logger));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function loggerIsSet() {
|
|
84
|
+
return currentLogger !== defaultLogger;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function setLoggerIfUnset(logger) {
|
|
88
|
+
if (loggerIsSet()) return false;
|
|
89
|
+
setLogger(logger);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
captureException,
|
|
95
|
+
getCurrentLogger,
|
|
96
|
+
loggerIsSet,
|
|
97
|
+
setLogger,
|
|
98
|
+
setLoggerIfUnset,
|
|
99
|
+
};
|
package/lib/payload.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON payload types and the message types that carry them.
|
|
3
|
+
* @module lib/payload
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ExciseMessage as NativeExciseMessage,
|
|
8
|
+
Message as NativeMessage,
|
|
9
|
+
} from "../bindings";
|
|
10
|
+
|
|
11
|
+
/** A primitive value representable in JSON. */
|
|
12
|
+
export type JsonPrimitive = null | boolean | number | string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Any value representable in JSON.
|
|
16
|
+
*
|
|
17
|
+
* This is a compile-time contract. Prosody serializes payloads at runtime but
|
|
18
|
+
* does not validate them against a more specific application payload type.
|
|
19
|
+
*/
|
|
20
|
+
export type JsonValue =
|
|
21
|
+
JsonPrimitive | readonly JsonValue[] | { readonly [key: string]: JsonValue };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Maps an application type to its JSON-compatible shape. This lets APIs
|
|
25
|
+
* accept ordinary interfaces and type aliases without requiring an index
|
|
26
|
+
* signature, while rejecting non-JSON members wherever they are nested.
|
|
27
|
+
*/
|
|
28
|
+
export type JsonCompatible<T> = T extends JsonPrimitive
|
|
29
|
+
? T
|
|
30
|
+
: T extends bigint | symbol | undefined | ((...args: never[]) => unknown)
|
|
31
|
+
? never
|
|
32
|
+
: T extends readonly (infer Item)[]
|
|
33
|
+
? readonly JsonCompatible<Item>[]
|
|
34
|
+
: T extends object
|
|
35
|
+
? { readonly [Key in keyof T]: JsonCompatible<T[Key]> }
|
|
36
|
+
: never;
|
|
37
|
+
|
|
38
|
+
/** A value that a handler can return now or through a promise. */
|
|
39
|
+
export type MaybePromise<T> = T | PromiseLike<T>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Represents a message consumed from a Kafka topic.
|
|
43
|
+
*
|
|
44
|
+
* The optional payload type parameter is annotation-level only: the runtime
|
|
45
|
+
* payload is unchanged. An unparameterized `Message` uses {@link JsonValue};
|
|
46
|
+
* provide an application payload type for precise field-level checking.
|
|
47
|
+
* Message-backed keyed-state collections vend their items as `Message<P>`.
|
|
48
|
+
*
|
|
49
|
+
* A message can be stored into a message collection, whether it arrived from the
|
|
50
|
+
* topic or was read back out of a collection. What is stored is the message
|
|
51
|
+
* itself, so mutating its parsed `payload` does not change what is written.
|
|
52
|
+
*/
|
|
53
|
+
export interface Message<P = JsonValue> extends Omit<NativeMessage, "payload"> {
|
|
54
|
+
/** The message payload as a JSON-serializable value. */
|
|
55
|
+
payload: P;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** An excise record with Kafka metadata and no payload. */
|
|
59
|
+
export type ExciseMessage = NativeExciseMessage;
|