@prosody-events/prosody 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.
package/index.js ADDED
@@ -0,0 +1,588 @@
1
+ /**
2
+ * @module prosody-js
3
+ * @description A high-performance messaging client for Kafka with built-in OpenTelemetry support.
4
+ * Provides functionality for sending messages, subscribing to topics, and managing consumer state.
5
+ */
6
+
7
+ /**
8
+ * @typedef {Object} Logger
9
+ * @property {Function} error - Function for logging error messages. Called with (message, metadata).
10
+ * @property {Function} warn - Function for logging warning messages. Called with (message, metadata).
11
+ * @property {Function} info - Function for logging informational messages. Called with (message, metadata).
12
+ * @property {Function} debug - Function for logging debug messages. Called with (message, metadata).
13
+ * @property {Function} trace - Function for logging trace messages. Called with (message, metadata).
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} EventHandler
18
+ * @property {Function} [onMessage] - Async callback function to handle incoming messages. Receives (context, message, signal).
19
+ * @property {Function} [onTimer] - Async callback function to handle timer events. Receives (context, timer, signal).
20
+ */
21
+
22
+ /**
23
+ * @typedef {import('./bindings').Configuration} Configuration
24
+ * @typedef {import('./bindings').ConsumerState} ConsumerState
25
+ * @typedef {import('./bindings').Context} Context
26
+ * @typedef {import('./bindings').Message} Message
27
+ * @typedef {import('./bindings').Timer} Timer
28
+ * @typedef {import('./bindings').Mode} Mode
29
+ */
30
+
31
+ const {
32
+ context: otelContext,
33
+ propagation,
34
+ trace,
35
+ SpanStatusCode,
36
+ } = require("@opentelemetry/api");
37
+
38
+ const {
39
+ Mode,
40
+ NativeClient,
41
+ ConsumerState,
42
+ NativeContext,
43
+ initialize,
44
+ loggerIsSet,
45
+ setLogger: setLoggerInternal,
46
+ setLoggerIfUnset: setLoggerIfUnsetInternal,
47
+ } = require("./bindings");
48
+
49
+ let _sentry = undefined;
50
+ function getSentry() {
51
+ if (_sentry !== undefined) return _sentry;
52
+ _sentry = null;
53
+ if (!process.env.SENTRY_DSN) return null;
54
+ try {
55
+ const Sentry = require("@sentry/node");
56
+ if (!Sentry.isInitialized()) {
57
+ Sentry.init({ dsn: process.env.SENTRY_DSN });
58
+ }
59
+ _sentry = Sentry;
60
+ return Sentry;
61
+ } catch (err) {
62
+ const isMissing =
63
+ err?.code === "MODULE_NOT_FOUND" &&
64
+ err.message?.includes("@sentry/node");
65
+ if (isMissing) {
66
+ getCurrentLogger()?.error(
67
+ "SENTRY_DSN is set but @sentry/node is not installed. Run: npm install @sentry/node"
68
+ );
69
+ } else {
70
+ getCurrentLogger()?.warn("Unexpected error loading @sentry/node", err);
71
+ }
72
+ return null;
73
+ }
74
+ }
75
+
76
+ function captureException(error, eventType, context) {
77
+ const Sentry = getSentry();
78
+ if (!Sentry) return;
79
+ Sentry.withScope((scope) => {
80
+ scope.setTag("prosody.event_type", eventType);
81
+ scope.setContext("prosody", context);
82
+ Sentry.captureException(error.cause ?? error);
83
+ });
84
+ }
85
+
86
+ const defaultLogger = {
87
+ error: (message, metadata) => metadata !== undefined ? console.error(message, metadata) : console.error(message),
88
+ warn: (message, metadata) => metadata !== undefined ? console.warn(message, metadata) : console.warn(message),
89
+ info: (message, metadata) => metadata !== undefined ? console.info(message, metadata) : console.info(message),
90
+ debug: (message, metadata) => metadata !== undefined ? console.debug(message, metadata) : console.debug(message),
91
+ trace: (message, metadata) => metadata !== undefined ? console.debug(message, metadata) : console.debug(message),
92
+ };
93
+
94
+ // Keep reference to current logger for use in handlers
95
+ let currentLogger = defaultLogger;
96
+
97
+ function transformLogger(logger) {
98
+ return {
99
+ info: ([msg, meta]) => logger.info(msg, meta),
100
+ error: ([msg, meta]) => logger.error(msg, meta),
101
+ debug: ([msg, meta]) => logger.debug(msg, meta),
102
+ warn: ([msg, meta]) => logger.warn(msg, meta),
103
+ trace: ([msg, meta]) => logger.trace(msg, meta),
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Gets the current configured logger.
109
+ * @returns {Logger|null|undefined} The current logger instance, or null/undefined if no logger is configured.
110
+ */
111
+ function getCurrentLogger() {
112
+ return currentLogger;
113
+ }
114
+
115
+ initialize();
116
+ setLoggerIfUnset(defaultLogger);
117
+
118
+ /**
119
+ * Sets a new JavaScript logger for the Prosody client.
120
+ *
121
+ * This function configures the logging system to use the provided JavaScript logger
122
+ * for all log output. The logger must implement all required log levels.
123
+ *
124
+ * @param {Logger} logger - The JavaScript logger object.
125
+ * @throws {Error} If creating the new JavaScript logger fails.
126
+ */
127
+ function setLogger(logger) {
128
+ currentLogger = logger;
129
+ setLoggerInternal(transformLogger(logger));
130
+ }
131
+
132
+ /**
133
+ * Sets a JavaScript logger only if no logger is currently configured.
134
+ *
135
+ * This function is useful for providing a default logger without overriding
136
+ * an existing one that may have been set earlier.
137
+ *
138
+ * @param {Logger} logger - The JavaScript logger object.
139
+ * @returns {boolean} True if the logger was set (no previous logger existed), false if a logger was already configured.
140
+ * @throws {Error} If creating the new JavaScript logger fails.
141
+ */
142
+ function setLoggerIfUnset(logger) {
143
+ const wasSet = setLoggerIfUnsetInternal(transformLogger(logger));
144
+ if (wasSet) {
145
+ currentLogger = logger;
146
+ }
147
+ return wasSet;
148
+ }
149
+
150
+ /**
151
+ * Main client for interacting with Prosody messaging system.
152
+ * Provides functionality for sending messages, subscribing to topics, and managing consumer state.
153
+ */
154
+ class ProsodyClient {
155
+ /**
156
+ * Creates a new ProsodyClient instance.
157
+ *
158
+ * @param {Configuration} config - The configuration options for the client.
159
+ */
160
+ constructor(config) {
161
+ this.nativeClient = new NativeClient(config);
162
+ }
163
+
164
+ /**
165
+ * Gets the source system identifier configured for the client.
166
+ *
167
+ * @returns {string} The source system identifier.
168
+ */
169
+ get sourceSystem() {
170
+ return this.nativeClient.sourceSystem;
171
+ }
172
+
173
+ /**
174
+ * Gets the current state of the consumer.
175
+ *
176
+ * @returns {Promise<ConsumerState>} The current state of the consumer.
177
+ * @throws {Error} If the operation fails.
178
+ */
179
+ consumerState() {
180
+ return this.nativeClient.consumerState();
181
+ }
182
+
183
+ /**
184
+ * Gets the number of partitions assigned to the consumer.
185
+ *
186
+ * @returns {Promise<number>} The number of assigned partitions, or 0 if the consumer is not in the Running state.
187
+ * @throws {Error} If the operation fails.
188
+ */
189
+ assignedPartitionCount() {
190
+ return this.nativeClient.assignedPartitionCount();
191
+ }
192
+
193
+ /**
194
+ * Checks if the consumer is stalled.
195
+ *
196
+ * @returns {Promise<boolean>} Whether the consumer is stalled, or false if the consumer is not in the Running state.
197
+ * @throws {Error} If the operation fails.
198
+ */
199
+ isStalled() {
200
+ return this.nativeClient.isStalled();
201
+ }
202
+
203
+ /**
204
+ * Sends a message to a specified topic.
205
+ *
206
+ * @param {string} topic - The topic to send the message to.
207
+ * @param {string} key - The key of the message.
208
+ * @param {*} payload - The payload of the message.
209
+ * @param {AbortSignal} [signal] - Optional abort signal to cancel the send operation. When aborted, the promise will reject with the abort reason.
210
+ * @returns {Promise<void>} A promise that resolves when the message has been successfully sent.
211
+ * @throws {Error} If the send operation fails or is aborted.
212
+ */
213
+ async send(topic, key, payload, signal) {
214
+ const carrier = {};
215
+ propagation.inject(otelContext.active(), carrier);
216
+
217
+ await this.nativeClient.send(
218
+ topic,
219
+ key,
220
+ payload,
221
+ carrier,
222
+ signal && onAbort(signal),
223
+ );
224
+ }
225
+
226
+ /**
227
+ * Subscribes to receive messages using the provided event handler.
228
+ *
229
+ * @param {EventHandler} eventHandler - The event handler to process received messages and timers.
230
+ * @returns {Promise<void>} A promise that resolves when the subscription is successfully established and the consumer is ready to receive messages.
231
+ * @throws {Error} If the subscription fails to establish.
232
+ */
233
+ async subscribe(eventHandler) {
234
+ const tracer = trace.getTracer("prosody");
235
+ const {
236
+ onMessage = (context, message, _signal) => {
237
+ getCurrentLogger()?.error(
238
+ "ProsodyClient: Received a message but no onMessage handler was " +
239
+ "provided in subscribe(). To handle messages, implement the onMessage " +
240
+ "method in your EventHandler:",
241
+ {
242
+ topic: message.topic,
243
+ partition: message.partition,
244
+ offset: message.offset,
245
+ key: message.key,
246
+ solution:
247
+ "Add onMessage: async (context, message, signal) => " +
248
+ "{ /* your logic here */ } to your subscribe() call",
249
+ },
250
+ );
251
+ },
252
+ onTimer = (context, timer, _signal) => {
253
+ getCurrentLogger()?.error(
254
+ "ProsodyClient: Received a timer event but no onTimer handler was " +
255
+ "provided in subscribe(). To handle timers, implement the onTimer " +
256
+ "method in your EventHandler:",
257
+ {
258
+ key: timer.key,
259
+ time: timer.time,
260
+ solution:
261
+ "Add onTimer: async (context, timer, signal) => " +
262
+ "{ /* your logic here */ } to your subscribe() call",
263
+ },
264
+ );
265
+ },
266
+ } = eventHandler;
267
+
268
+ await this.nativeClient.subscribe({
269
+ isPermanent: ([err]) => {
270
+ try {
271
+ return err instanceof EventHandlerError && err.isPermanent;
272
+ } catch {
273
+ return false;
274
+ }
275
+ },
276
+
277
+ onMessage: async (err, [nativeContext, message, carrier]) => {
278
+ if (err) throw err;
279
+
280
+ const ctx = propagation.extract(otelContext.active(), carrier);
281
+ await otelContext.with(ctx, async () => {
282
+ await tracer.startActiveSpan("onMessage", async (span) => {
283
+ const controller = new AbortController();
284
+ let completed = false;
285
+
286
+ // Signal abort when cancellation occurs (before handler completes)
287
+ nativeContext.onCancel().then(() => {
288
+ if (!completed) {
289
+ span.setAttribute("cancelled", true);
290
+ controller.abort("message cancelled");
291
+ }
292
+ });
293
+
294
+ try {
295
+ const context = new Context(nativeContext);
296
+ await onMessage(context, message, controller.signal);
297
+ } catch (error) {
298
+ getCurrentLogger()?.error("Message handler error", error.cause ?? error);
299
+ span.recordException(error.cause ?? error);
300
+ captureException(error, "message", {
301
+ topic: message.topic,
302
+ partition: message.partition,
303
+ key: message.key,
304
+ offset: message.offset,
305
+ });
306
+ throw error;
307
+ } finally {
308
+ completed = true;
309
+ span.end();
310
+ }
311
+ });
312
+ });
313
+ },
314
+
315
+ onTimer: async (err, [nativeContext, timer, carrier]) => {
316
+ if (err) throw err;
317
+
318
+ const ctx = propagation.extract(otelContext.active(), carrier);
319
+ await otelContext.with(ctx, async () => {
320
+ await tracer.startActiveSpan("onTimer", async (span) => {
321
+ const controller = new AbortController();
322
+ let completed = false;
323
+
324
+ // Signal abort when cancellation occurs (before handler completes)
325
+ nativeContext.onCancel().then(() => {
326
+ if (!completed) {
327
+ span.setAttribute("cancelled", true);
328
+ controller.abort("timer cancelled");
329
+ }
330
+ });
331
+
332
+ try {
333
+ const context = new Context(nativeContext);
334
+ await onTimer(context, timer, controller.signal);
335
+ } catch (error) {
336
+ getCurrentLogger()?.error("Timer handler error", error.cause ?? error);
337
+ span.recordException(error.cause ?? error);
338
+ captureException(error, "timer", {
339
+ key: timer.key,
340
+ time: timer.time,
341
+ });
342
+ throw error;
343
+ } finally {
344
+ completed = true;
345
+ span.end();
346
+ }
347
+ });
348
+ });
349
+ },
350
+ });
351
+ }
352
+
353
+ /**
354
+ * Unsubscribes from receiving messages and shuts down the consumer.
355
+ *
356
+ * @returns {Promise<void>} A promise that resolves when the unsubscribe operation is complete.
357
+ * @throws {Error} If the unsubscribe operation fails.
358
+ */
359
+ async unsubscribe() {
360
+ await this.nativeClient.unsubscribe();
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Creates a promise that rejects when the abort signal is triggered.
366
+ * @param {AbortSignal} signal - The abort signal to monitor.
367
+ * @returns {Promise<never>} A promise that rejects with the abort reason.
368
+ * @private
369
+ */
370
+ const onAbort = (signal) =>
371
+ new Promise((_, reject) => {
372
+ if (signal.aborted) reject(signal.reason);
373
+ else
374
+ signal.addEventListener("abort", () => reject(signal.reason), {
375
+ once: true,
376
+ });
377
+ });
378
+
379
+ /**
380
+ * Base class for event handler errors.
381
+ * Provides a common interface for determining if an error is permanent.
382
+ * @extends Error
383
+ */
384
+ class EventHandlerError extends Error {
385
+ constructor(message) {
386
+ super(message);
387
+ this.name = this.constructor.name;
388
+ }
389
+
390
+ /**
391
+ * Indicates whether the error is permanent and should not be retried.
392
+ * @abstract
393
+ * @returns {boolean} True if permanent, false if transient.
394
+ */
395
+ get isPermanent() {
396
+ throw new Error("Subclasses must implement isPermanent");
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Represents a transient error that may be resolved by retrying.
402
+ * @extends EventHandlerError
403
+ */
404
+ class TransientError extends EventHandlerError {
405
+ /**
406
+ * @returns {boolean} Always returns false, indicating the error is not permanent.
407
+ */
408
+ get isPermanent() {
409
+ return false;
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Represents a permanent error that should not be retried.
415
+ * @extends EventHandlerError
416
+ */
417
+ class PermanentError extends EventHandlerError {
418
+ /**
419
+ * @returns {boolean} Always returns true, indicating the error is permanent.
420
+ */
421
+ get isPermanent() {
422
+ return true;
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Helper function to create error decorators.
428
+ * @param {Function} ErrorClass - The error class to wrap exceptions with.
429
+ * @returns {Function} A decorator function that wraps specified exceptions.
430
+ * @private
431
+ */
432
+ function createErrorDecorator(ErrorClass) {
433
+ return function decorator(...exceptionTypes) {
434
+ return function (originalMethod, context) {
435
+ if (context.kind !== "method" && context.kind !== "function") {
436
+ throw new TypeError(
437
+ `@${ErrorClass.name} can only decorate methods or functions`,
438
+ );
439
+ }
440
+
441
+ function handleError(error) {
442
+ if (exceptionTypes.some((type) => error instanceof type)) {
443
+ const wrapped = new ErrorClass(error.message);
444
+ wrapped.cause = error;
445
+ return wrapped;
446
+ }
447
+ return error;
448
+ }
449
+
450
+ if (originalMethod.constructor.name === "AsyncFunction") {
451
+ return async function (...args) {
452
+ try {
453
+ return await originalMethod.apply(this, args);
454
+ } catch (error) {
455
+ throw handleError(error);
456
+ }
457
+ };
458
+ } else {
459
+ return function (...args) {
460
+ try {
461
+ return originalMethod.apply(this, args);
462
+ } catch (error) {
463
+ throw handleError(error);
464
+ }
465
+ };
466
+ }
467
+ };
468
+ };
469
+ }
470
+
471
+ /**
472
+ * Decorator factory for marking errors as transient.
473
+ * Can be applied to methods to automatically wrap specified error types as transient.
474
+ * @param {...(new(...args: any[]) => Error)} exceptionTypes - The error types to be treated as transient.
475
+ * @returns {Function} A decorator function.
476
+ */
477
+ const transient = createErrorDecorator(TransientError);
478
+
479
+ /**
480
+ * Decorator factory for marking errors as permanent.
481
+ * Can be applied to methods to automatically wrap specified error types as permanent.
482
+ * @param {...(new(...args: any[]) => Error)} exceptionTypes - The error types to be treated as permanent.
483
+ * @returns {Function} A decorator function.
484
+ */
485
+ const permanent = createErrorDecorator(PermanentError);
486
+
487
+ /**
488
+ * Context class that automatically injects OpenTelemetry context for all operations.
489
+ * This wraps the native Context with automatic OTEL context propagation.
490
+ */
491
+ class Context {
492
+ constructor(nativeContext) {
493
+ this.nativeContext = nativeContext;
494
+ }
495
+
496
+ /**
497
+ * Checks whether cancellation has been signaled.
498
+ * Cancellation includes both message-level cancellation (e.g., timeout) and partition shutdown.
499
+ * @returns {boolean} True if cancellation was requested, otherwise false.
500
+ */
501
+ get shouldCancel() {
502
+ return this.nativeContext.shouldCancel;
503
+ }
504
+
505
+ /**
506
+ * Waits for a cancellation signal.
507
+ * Cancellation includes both message-level cancellation (e.g., timeout) and partition shutdown.
508
+ * @returns {Promise<void>} A promise that resolves when cancellation is signaled.
509
+ */
510
+ async onCancel() {
511
+ return this.nativeContext.onCancel();
512
+ }
513
+
514
+ /**
515
+ * Schedule a timer at the given time.
516
+ * @param {Date} time - The UTC timestamp to schedule.
517
+ * @returns {Promise<void>} A promise that resolves when the timer has been scheduled.
518
+ * @throws {Error} If time conversion or scheduling fails.
519
+ */
520
+ async schedule(time) {
521
+ const carrier = {};
522
+ propagation.inject(otelContext.active(), carrier);
523
+ return this.nativeContext.schedule(time, carrier);
524
+ }
525
+
526
+ /**
527
+ * Clear existing timers and schedule a new one at the given time.
528
+ * @param {Date} time - The UTC timestamp to schedule.
529
+ * @returns {Promise<void>} A promise that resolves when the timer has been scheduled.
530
+ * @throws {Error} If time conversion or scheduling fails.
531
+ */
532
+ async clearAndSchedule(time) {
533
+ const carrier = {};
534
+ propagation.inject(otelContext.active(), carrier);
535
+ return this.nativeContext.clearAndSchedule(time, carrier);
536
+ }
537
+
538
+ /**
539
+ * Unschedules the timer for the specified time.
540
+ * @param {Date} time - The time to unschedule.
541
+ * @returns {Promise<void>} A promise that resolves when the timer has been unscheduled.
542
+ * @throws {Error} If unscheduling fails.
543
+ */
544
+ async unschedule(time) {
545
+ const carrier = {};
546
+ propagation.inject(otelContext.active(), carrier);
547
+ return this.nativeContext.unschedule(time, carrier);
548
+ }
549
+
550
+ /**
551
+ * Clears all scheduled timers.
552
+ * @returns {Promise<void>} A promise that resolves when all timers have been cleared.
553
+ * @throws {Error} If clearing schedules fails.
554
+ */
555
+ async clearScheduled() {
556
+ const carrier = {};
557
+ propagation.inject(otelContext.active(), carrier);
558
+ return this.nativeContext.clearScheduled(carrier);
559
+ }
560
+
561
+ /**
562
+ * Retrieves all scheduled times.
563
+ * @returns {Promise<Date[]>} An array of scheduled times as Date objects.
564
+ * @throws {Error} If retrieval fails.
565
+ */
566
+ async scheduled() {
567
+ const carrier = {};
568
+ propagation.inject(otelContext.active(), carrier);
569
+ return this.nativeContext.scheduled(carrier);
570
+ }
571
+ }
572
+
573
+ module.exports = {
574
+ ConsumerState,
575
+ Context,
576
+ EventHandlerError,
577
+ Mode,
578
+ PermanentError,
579
+ ProsodyClient,
580
+ TransientError,
581
+ getCurrentLogger,
582
+ initialize,
583
+ loggerIsSet,
584
+ permanent,
585
+ setLogger,
586
+ setLoggerIfUnset,
587
+ transient,
588
+ };
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@prosody-events/prosody",
3
+ "version": "0.1.0",
4
+ "main": "index.js",
5
+ "types": "index.d.ts",
6
+ "files": [
7
+ "index.js",
8
+ "index.d.ts",
9
+ "bindings.js",
10
+ "bindings.d.ts",
11
+ "README.md"
12
+ ],
13
+ "napi": {
14
+ "binaryName": "prosody",
15
+ "targets": [
16
+ "x86_64-unknown-linux-gnu",
17
+ "x86_64-pc-windows-msvc",
18
+ "aarch64-apple-darwin",
19
+ "aarch64-unknown-linux-gnu"
20
+ ]
21
+ },
22
+ "license": "MIT",
23
+ "peerDependencies": {
24
+ "@sentry/node": ">=8.0.0"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "@sentry/node": {
28
+ "optional": true
29
+ }
30
+ },
31
+ "dependencies": {
32
+ "@opentelemetry/api": "^1.9.1",
33
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.214.0"
34
+ },
35
+ "devDependencies": {
36
+ "@babel/core": "^7.29.0",
37
+ "@babel/plugin-proposal-decorators": "^7.29.0",
38
+ "@babel/preset-env": "^7.29.2",
39
+ "@napi-rs/cli": "^3.6.1",
40
+ "@opentelemetry/auto-instrumentations-node": "^0.72.0",
41
+ "@opentelemetry/core": "^2.6.1",
42
+ "@opentelemetry/exporter-trace-otlp-http": "^0.214.0",
43
+ "@opentelemetry/sdk-metrics": "^2.6.1",
44
+ "@opentelemetry/sdk-node": "^0.214.0",
45
+ "@opentelemetry/sdk-trace-base": "^2.6.1",
46
+ "@opentelemetry/sdk-trace-node": "^2.6.1",
47
+ "@sentry/node": "^10.48.0",
48
+ "jest": "^30.3.0",
49
+ "prettier": "^3.8.2",
50
+ "yarn-upgrade-all": "^0.7.5"
51
+ },
52
+ "engines": {
53
+ "node": ">= 20"
54
+ },
55
+ "scripts": {
56
+ "artifacts": "napi artifacts",
57
+ "build": "napi build --js bindings.js --dts bindings.d.ts --platform --no-const-enum --release",
58
+ "build:debug": "napi build --js bindings.js --dts bindings.d.ts --platform --no-const-enum",
59
+ "format": "prettier --write .",
60
+ "format:check": "prettier --check .",
61
+ "prepublishOnly": "napi prepublish -t npm --no-gh-release",
62
+ "test": "NODE_OPTIONS='--experimental-vm-modules' yarn run jest",
63
+ "universal": "napi universalize",
64
+ "version": "napi version"
65
+ },
66
+ "packageManager": "yarn@4.4.0",
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "git+https://github.com/prosody-events/prosody-js.git"
70
+ },
71
+ "description": "Javascript bindings for the Prosody Kafka client library",
72
+ "publishConfig": {
73
+ "registry": "https://registry.npmjs.org",
74
+ "access": "public"
75
+ },
76
+ "optionalDependencies": {
77
+ "@prosody-events/prosody-linux-x64-gnu": "0.1.0",
78
+ "@prosody-events/prosody-win32-x64-msvc": "0.1.0",
79
+ "@prosody-events/prosody-darwin-arm64": "0.1.0",
80
+ "@prosody-events/prosody-linux-arm64-gnu": "0.1.0"
81
+ }
82
+ }