@seriousme/opifex 1.11.0 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +5 -5
  2. package/benchmarks/receiver.ts +35 -0
  3. package/benchmarks/sender.ts +32 -0
  4. package/benchmarks/utils.ts +9 -0
  5. package/client/client.ts +6 -10
  6. package/client/context.ts +3 -9
  7. package/client/deps.ts +1 -2
  8. package/client/handlers/handlePublish.ts +2 -2
  9. package/client/handlers/handlePubrel.ts +1 -1
  10. package/deno/tcpServer.test.ts +0 -1
  11. package/dev_utils/dummyConn.ts +5 -5
  12. package/dev_utils/mod.ts +1 -0
  13. package/dev_utils/timers.ts +51 -0
  14. package/dist/client/client.d.ts +2 -6
  15. package/dist/client/client.d.ts.map +1 -1
  16. package/dist/client/client.js +6 -9
  17. package/dist/client/client.js.map +1 -1
  18. package/dist/client/context.d.ts +2 -3
  19. package/dist/client/context.d.ts.map +1 -1
  20. package/dist/client/context.js +4 -8
  21. package/dist/client/context.js.map +1 -1
  22. package/dist/client/deps.d.ts +1 -2
  23. package/dist/client/deps.d.ts.map +1 -1
  24. package/dist/client/deps.js +1 -2
  25. package/dist/client/deps.js.map +1 -1
  26. package/dist/client/handlers/handlePublish.js +2 -2
  27. package/dist/client/handlers/handlePublish.js.map +1 -1
  28. package/dist/client/handlers/handlePubrel.js +1 -1
  29. package/dist/client/handlers/handlePubrel.js.map +1 -1
  30. package/dist/mqttConn/mqttConn.d.ts +2 -7
  31. package/dist/mqttConn/mqttConn.d.ts.map +1 -1
  32. package/dist/mqttConn/mqttConn.js +18 -11
  33. package/dist/mqttConn/mqttConn.js.map +1 -1
  34. package/dist/server/handlers/handlePublish.d.ts.map +1 -1
  35. package/dist/server/handlers/handlePublish.js +17 -2
  36. package/dist/server/handlers/handlePublish.js.map +1 -1
  37. package/dist/server/handlers/handlePubrel.d.ts.map +1 -1
  38. package/dist/server/handlers/handlePubrel.js +6 -10
  39. package/dist/server/handlers/handlePubrel.js.map +1 -1
  40. package/dist/utils/{asyncQueue.d.ts → BufferedAsyncIterable.d.ts} +8 -8
  41. package/dist/utils/BufferedAsyncIterable.d.ts.map +1 -0
  42. package/dist/utils/BufferedAsyncIterable.js +70 -0
  43. package/dist/utils/BufferedAsyncIterable.js.map +1 -0
  44. package/dist/utils/mod.d.ts +1 -2
  45. package/dist/utils/mod.d.ts.map +1 -1
  46. package/dist/utils/mod.js +1 -2
  47. package/dist/utils/mod.js.map +1 -1
  48. package/dist/utils/queue.d.ts +43 -0
  49. package/dist/utils/queue.d.ts.map +1 -0
  50. package/dist/utils/queue.js +63 -0
  51. package/dist/utils/queue.js.map +1 -0
  52. package/examples/tcpServer.test.ts +0 -1
  53. package/mqttConn/mqttConn.test.ts +5 -4
  54. package/mqttConn/mqttConn.ts +19 -15
  55. package/node/tcpServer.test.ts +0 -1
  56. package/package.json +1 -1
  57. package/server/handlers/handlePublish.ts +18 -2
  58. package/server/handlers/handlePubrel.ts +6 -10
  59. package/server/test/handleConnect.test.ts +11 -8
  60. package/utils/BufferedAsyncIterable.ts +76 -0
  61. package/utils/mod.ts +1 -2
  62. package/utils/queue.ts +66 -0
  63. package/dist/utils/asyncQueue.d.ts.map +0 -1
  64. package/dist/utils/asyncQueue.js +0 -65
  65. package/dist/utils/asyncQueue.js.map +0 -1
  66. package/dist/utils/nextTick.d.ts +0 -17
  67. package/dist/utils/nextTick.d.ts.map +0 -1
  68. package/dist/utils/nextTick.js +0 -19
  69. package/dist/utils/nextTick.js.map +0 -1
  70. package/utils/asyncQueue.ts +0 -71
  71. package/utils/nextTick.ts +0 -18
@@ -1,12 +1,15 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { test } from "node:test";
3
- import { makeDummyQueueSockConn } from "../../dev_utils/mod.ts";
3
+ import {
4
+ makeDummyQueueSockConn,
5
+ resolveNextTick,
6
+ } from "../../dev_utils/mod.ts";
4
7
  import {
5
8
  type AnyPacket,
6
9
  AuthenticationResult,
7
10
  PacketType,
8
11
  } from "../../mqttPacket/mod.ts";
9
- import { AsyncQueue, nextTick } from "../../utils/mod.ts";
12
+ import { BufferedAsyncIterable } from "../../utils/mod.ts";
10
13
  import { MqttConn, MQTTLevel } from "../deps.ts";
11
14
  import { MqttServer } from "../mod.ts";
12
15
  import { handlers } from "./test-handlers.ts";
@@ -34,11 +37,11 @@ const disconnectPacket: AnyPacket = {
34
37
  const mqttServer = new MqttServer({ handlers });
35
38
 
36
39
  function startServer(): {
37
- reader: AsyncIterableIterator<AnyPacket>;
40
+ reader: AsyncIterator<AnyPacket>;
38
41
  mqttConn: MqttConn;
39
42
  } {
40
- const reader = new AsyncQueue<Uint8Array>();
41
- const writer = new AsyncQueue<Uint8Array>();
43
+ const reader = new BufferedAsyncIterable<Uint8Array>();
44
+ const writer = new BufferedAsyncIterable<Uint8Array>();
42
45
 
43
46
  const outputConn = makeDummyQueueSockConn(writer, reader);
44
47
  const mqttConn = new MqttConn({ conn: outputConn });
@@ -66,7 +69,7 @@ test("Authentication with valid username and password works", async () => {
66
69
  );
67
70
  }
68
71
  mqttConn.send(disconnectPacket);
69
- await nextTick();
72
+ await resolveNextTick();
70
73
  assert.deepStrictEqual(
71
74
  mqttConn.isClosed,
72
75
  true,
@@ -92,7 +95,7 @@ test("Authentication with invalid username fails", async () => {
92
95
  "Expected badUsernameOrPassword",
93
96
  );
94
97
  }
95
- await nextTick();
98
+ await resolveNextTick();
96
99
  assert.deepStrictEqual(
97
100
  mqttConn.isClosed,
98
101
  true,
@@ -118,7 +121,7 @@ test("Authentication with invalid password fails", async () => {
118
121
  "Expected badUsernameOrPassword",
119
122
  );
120
123
  }
121
- await nextTick();
124
+ await resolveNextTick();
122
125
  assert.deepStrictEqual(
123
126
  mqttConn.isClosed,
124
127
  true,
@@ -0,0 +1,76 @@
1
+ import { Deferred } from "./deferred.ts";
2
+ import { ArrayQueue } from "./queue.ts";
3
+
4
+ /**
5
+ * An Async Queue is a queue that can be used to push items to it and then wait
6
+ * for them to be consumed.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const queue = new AsyncQueue<string>();
11
+ * queue.push("hello");
12
+ * for await (const item of queue) {
13
+ * console.log(item); // "hello"
14
+ * }
15
+ * ```
16
+ */
17
+ export class BufferedAsyncIterable<T> implements AsyncIterable<T> {
18
+ private buffer: ArrayQueue<T>;
19
+ private bufferSize: number;
20
+
21
+ private done = false;
22
+
23
+ #next?: Deferred<T>;
24
+
25
+ constructor(bufferSize: number = 2048) {
26
+ this.buffer = new ArrayQueue();
27
+ this.bufferSize = bufferSize;
28
+ }
29
+
30
+ async *[Symbol.asyncIterator](): AsyncGenerator<Awaited<T>, void, unknown> {
31
+ while (!this.done) {
32
+ yield this.next();
33
+ }
34
+ }
35
+
36
+ next(): T | Promise<T> {
37
+ // await nextTick();
38
+ if (this.buffer.size() > 0) {
39
+ return this.buffer.dequeue()!;
40
+ } else if (this.done) {
41
+ throw new Error("Closed");
42
+ } else if (!this.#next) {
43
+ this.#next = new Deferred<T>();
44
+ }
45
+ return this.#next.promise;
46
+ }
47
+
48
+ close(reason = "closed"): void {
49
+ this.done = true;
50
+ if (this.#next) {
51
+ this.#next.reject(new Error(reason));
52
+ }
53
+ }
54
+
55
+ readonly push = (item: T): void | Promise<void> => {
56
+ if (this.#next) {
57
+ this.#next.resolve(item);
58
+ this.#next = undefined;
59
+ return;
60
+ }
61
+ this.buffer.enqueue(item);
62
+ if (this.buffer.size() > this.bufferSize) {
63
+ // If our current buffer has grown greater than the allowed size we adaptively
64
+ // wait for an amount of milliseconds proportional to the size of the buffer.
65
+ // This ensures backpressure is applied to the producer that is pushing items
66
+ // into the queue. Note that this might be an exceedingly simple heuristic,
67
+ // a PID (Proportional-Integral-Derivative) controller could be used to improve
68
+ // the responsiveness of the system.
69
+ return new Promise((resolve) => setTimeout(resolve, this.buffer.size()));
70
+ }
71
+ };
72
+
73
+ get isDone(): boolean {
74
+ return this.done;
75
+ }
76
+ }
package/utils/mod.ts CHANGED
@@ -5,5 +5,4 @@
5
5
  export { assert } from "./assert.ts";
6
6
  export { Deferred } from "./deferred.ts";
7
7
  export { Logger, logger, LogLevel } from "./logger.ts";
8
- export { AsyncQueue } from "./asyncQueue.ts";
9
- export { nextTick } from "./nextTick.ts";
8
+ export { BufferedAsyncIterable } from "./BufferedAsyncIterable.ts";
package/utils/queue.ts ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * A simple queue implementation using an array.
3
+ * This implementation optimizes for O(1) enqueue and dequeue operations
4
+ * by avoiding array shifts, which are O(n).
5
+ *
6
+ * Usage:
7
+ * ```ts
8
+ * const queue = new ArrayQueue<number>();
9
+ * queue.enqueue(1);
10
+ * queue.enqueue(2);
11
+ * console.log(queue.dequeue()); // 1
12
+ * console.log(queue.size()); // 1
13
+ * console.log(queue.isEmpty()); // false
14
+ * queue.dequeue();
15
+ * console.log(queue.isEmpty()); // true
16
+ * ```
17
+ */
18
+
19
+ export class ArrayQueue<T> {
20
+ private items: T[] = [];
21
+ private headIndex: number = 0; // Tracks the front of the queue
22
+
23
+ /**
24
+ * size is the difference between the actual array length and the index offset
25
+ * @returns the number of items in the queue
26
+ */
27
+ public size(): number {
28
+ return this.items.length - this.headIndex;
29
+ }
30
+ /**
31
+ * isEmpty checks if the queue has no items
32
+ * @returns true if the queue is empty
33
+ */
34
+ public isEmpty(): boolean {
35
+ return this.size() === 0;
36
+ }
37
+ /**
38
+ * enqueue adds an item to the end of the queue
39
+ * O(1) - Push to the end is highly optimized in V8.
40
+ */
41
+ public enqueue(data: T): void {
42
+ this.items.push(data);
43
+ }
44
+ /**
45
+ * dequeue removes and returns the item at the front of the queue
46
+ * O(1) - Avoids array shifting by incrementing the index.
47
+ * @returns the item at the front of the queue or undefined if the queue is empty
48
+ */
49
+ public dequeue(): T | undefined {
50
+ if (this.isEmpty()) {
51
+ return undefined;
52
+ }
53
+
54
+ const item = this.items[this.headIndex];
55
+ this.headIndex++; // Move the "front" marker forward
56
+
57
+ // Optimization: Periodically "clean up" the dead space
58
+ // when the empty space (headIndex) exceeds a threshold.
59
+ // This amortizes the cost, keeping average time closer to O(1).
60
+ if (this.headIndex > 2000 && this.headIndex > this.items.length / 4) {
61
+ this.items = this.items.slice(this.headIndex);
62
+ this.headIndex = 0;
63
+ }
64
+ return item;
65
+ }
66
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"asyncQueue.d.ts","sourceRoot":"","sources":["../../utils/asyncQueue.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,qBAAa,UAAU,CAAC,CAAC;;IACvB,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,cAAc,CAAY;IAElC,OAAO,CAAC,IAAI,CAAS;gBAIT,cAAc,CAAC,EAAE,MAAM;IAM7B,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC;IAYxB,KAAK,CAAC,MAAM,SAAW,GAAG,IAAI;IAOvB,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC;IAM1E,IAAI,CAAC,IAAI,EAAE,CAAC;IAYZ,IAAI,MAAM,IAAI,OAAO,CAEpB;CACF"}
@@ -1,65 +0,0 @@
1
- import { Deferred } from "./deferred.js";
2
- import { nextTick } from "./nextTick.js";
3
- /**
4
- * An Async Queue is a queue that can be used to push items to it and then wait
5
- * for them to be consumed.
6
- *
7
- * @example
8
- * ```ts
9
- * const queue = new AsyncQueue<string>();
10
- * queue.push("hello");
11
- * for await (const item of queue) {
12
- * console.log(item); // "hello"
13
- * }
14
- * ```
15
- */
16
- export class AsyncQueue {
17
- queue = [];
18
- maxQueueLength = Infinity;
19
- done = false;
20
- #next;
21
- constructor(maxQueueLength) {
22
- if (maxQueueLength) {
23
- this.maxQueueLength = maxQueueLength;
24
- }
25
- }
26
- async next() {
27
- await nextTick();
28
- if (this.queue.length) {
29
- return Promise.resolve(this.queue.shift());
30
- }
31
- else if (this.done) {
32
- return Promise.reject("Closed");
33
- }
34
- else if (!this.#next) {
35
- this.#next = new Deferred();
36
- }
37
- return this.#next.promise;
38
- }
39
- close(reason = "closed") {
40
- this.done = true;
41
- if (this.#next) {
42
- this.#next.reject(new Error(reason));
43
- }
44
- }
45
- async *[Symbol.asyncIterator]() {
46
- while (true) {
47
- yield await this.next();
48
- }
49
- }
50
- push(item) {
51
- if (this.#next) {
52
- this.#next.resolve(item);
53
- this.#next = undefined;
54
- return;
55
- }
56
- if (this.queue.length > this.maxQueueLength) {
57
- this.queue.shift();
58
- }
59
- this.queue.push(item);
60
- }
61
- get isDone() {
62
- return this.done;
63
- }
64
- }
65
- //# sourceMappingURL=asyncQueue.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"asyncQueue.js","sourceRoot":"","sources":["../../utils/asyncQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC;;;;;;;;;;;;GAYG;AAEH,MAAM,OAAO,UAAU;IACb,KAAK,GAAQ,EAAE,CAAC;IAChB,cAAc,GAAG,QAAQ,CAAC;IAE1B,IAAI,GAAG,KAAK,CAAC;IAErB,KAAK,CAAe;IAEpB,YAAY,cAAuB;QACjC,IAAI,cAAc,EAAE,CAAC;YACnB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACvC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,QAAQ,EAAE,CAAC;QACjB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YACtB,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAG,CAAC,CAAC;QAC9C,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACrB,OAAO,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC;aAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,GAAG,IAAI,QAAQ,EAAK,CAAC;QACjC,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,MAAM,GAAG,QAAQ;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC;QAC3B,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,IAAI,CAAC,IAAO;QACV,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACzB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;YACvB,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;YAC5C,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;CACF"}
@@ -1,17 +0,0 @@
1
- /**
2
- * Returns a promise that resolves on the next tick of the Node.js event loop.
3
- * This is useful for deferring execution of code until after the current call stack is cleared.
4
- *
5
- * @example
6
- * ```ts
7
- * async function example() {
8
- * console.log('Before nextTick');
9
- * await nextTick();
10
- * console.log('After nextTick');
11
- * }
12
- * ```
13
- *
14
- * @returns {Promise<void>} A promise that resolves on the next tick of the event loop
15
- */
16
- export declare function nextTick(): Promise<void>;
17
- //# sourceMappingURL=nextTick.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"nextTick.d.ts","sourceRoot":"","sources":["../../utils/nextTick.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAExC"}
@@ -1,19 +0,0 @@
1
- /**
2
- * Returns a promise that resolves on the next tick of the Node.js event loop.
3
- * This is useful for deferring execution of code until after the current call stack is cleared.
4
- *
5
- * @example
6
- * ```ts
7
- * async function example() {
8
- * console.log('Before nextTick');
9
- * await nextTick();
10
- * console.log('After nextTick');
11
- * }
12
- * ```
13
- *
14
- * @returns {Promise<void>} A promise that resolves on the next tick of the event loop
15
- */
16
- export function nextTick() {
17
- return new Promise((resolve) => setTimeout(resolve, 0));
18
- }
19
- //# sourceMappingURL=nextTick.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"nextTick.js","sourceRoot":"","sources":["../../utils/nextTick.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ;IACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC"}
@@ -1,71 +0,0 @@
1
- import { Deferred } from "./deferred.ts";
2
- import { nextTick } from "./nextTick.ts";
3
- /**
4
- * An Async Queue is a queue that can be used to push items to it and then wait
5
- * for them to be consumed.
6
- *
7
- * @example
8
- * ```ts
9
- * const queue = new AsyncQueue<string>();
10
- * queue.push("hello");
11
- * for await (const item of queue) {
12
- * console.log(item); // "hello"
13
- * }
14
- * ```
15
- */
16
-
17
- export class AsyncQueue<T> {
18
- private queue: T[] = [];
19
- private maxQueueLength = Infinity;
20
-
21
- private done = false;
22
-
23
- #next?: Deferred<T>;
24
-
25
- constructor(maxQueueLength?: number) {
26
- if (maxQueueLength) {
27
- this.maxQueueLength = maxQueueLength;
28
- }
29
- }
30
-
31
- async next(): Promise<T> {
32
- await nextTick();
33
- if (this.queue.length) {
34
- return Promise.resolve(this.queue.shift()!);
35
- } else if (this.done) {
36
- return Promise.reject("Closed");
37
- } else if (!this.#next) {
38
- this.#next = new Deferred<T>();
39
- }
40
- return this.#next.promise;
41
- }
42
-
43
- close(reason = "closed"): void {
44
- this.done = true;
45
- if (this.#next) {
46
- this.#next.reject(new Error(reason));
47
- }
48
- }
49
-
50
- async *[Symbol.asyncIterator](): AsyncGenerator<Awaited<T>, void, unknown> {
51
- while (true) {
52
- yield await this.next();
53
- }
54
- }
55
-
56
- push(item: T) {
57
- if (this.#next) {
58
- this.#next.resolve(item);
59
- this.#next = undefined;
60
- return;
61
- }
62
- if (this.queue.length > this.maxQueueLength) {
63
- this.queue.shift();
64
- }
65
- this.queue.push(item);
66
- }
67
-
68
- get isDone(): boolean {
69
- return this.done;
70
- }
71
- }
package/utils/nextTick.ts DELETED
@@ -1,18 +0,0 @@
1
- /**
2
- * Returns a promise that resolves on the next tick of the Node.js event loop.
3
- * This is useful for deferring execution of code until after the current call stack is cleared.
4
- *
5
- * @example
6
- * ```ts
7
- * async function example() {
8
- * console.log('Before nextTick');
9
- * await nextTick();
10
- * console.log('After nextTick');
11
- * }
12
- * ```
13
- *
14
- * @returns {Promise<void>} A promise that resolves on the next tick of the event loop
15
- */
16
- export function nextTick(): Promise<void> {
17
- return new Promise((resolve) => setTimeout(resolve, 0));
18
- }