@grafema/rfdb-client 0.2.5-beta → 0.2.7
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 +25 -14
- package/dist/client.d.ts +113 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +364 -14
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +212 -0
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/stream-queue.d.ts +30 -0
- package/dist/stream-queue.d.ts.map +1 -0
- package/dist/stream-queue.js +74 -0
- package/dist/stream-queue.js.map +1 -0
- package/package.json +2 -2
- package/ts/client.test.ts +635 -0
- package/ts/client.ts +420 -15
- package/ts/index.ts +10 -0
- package/ts/protocol.ts +11 -0
- package/ts/stream-queue.test.ts +114 -0
- package/ts/stream-queue.ts +83 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamQueue Unit Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the push-pull adapter that bridges socket events
|
|
5
|
+
* to async generator iteration. Pure data structure tests — no server dependency.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it } from 'node:test';
|
|
9
|
+
import assert from 'node:assert';
|
|
10
|
+
import { StreamQueue } from '../dist/stream-queue.js';
|
|
11
|
+
|
|
12
|
+
describe('StreamQueue', () => {
|
|
13
|
+
it('push then pull — items available immediately', async () => {
|
|
14
|
+
const q = new StreamQueue<number>();
|
|
15
|
+
q.push(1);
|
|
16
|
+
q.push(2);
|
|
17
|
+
|
|
18
|
+
const r1 = await q.next();
|
|
19
|
+
assert.deepStrictEqual(r1, { value: 1, done: false });
|
|
20
|
+
const r2 = await q.next();
|
|
21
|
+
assert.deepStrictEqual(r2, { value: 2, done: false });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('pull then push — Promise resolves when item arrives', async () => {
|
|
25
|
+
const q = new StreamQueue<number>();
|
|
26
|
+
|
|
27
|
+
// Start waiting before any items
|
|
28
|
+
const promise = q.next();
|
|
29
|
+
|
|
30
|
+
// Push resolves the waiting consumer
|
|
31
|
+
q.push(42);
|
|
32
|
+
|
|
33
|
+
const result = await promise;
|
|
34
|
+
assert.deepStrictEqual(result, { value: 42, done: false });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('end() terminates iteration', async () => {
|
|
38
|
+
const q = new StreamQueue<number>();
|
|
39
|
+
q.push(1);
|
|
40
|
+
q.end();
|
|
41
|
+
|
|
42
|
+
const r1 = await q.next();
|
|
43
|
+
assert.deepStrictEqual(r1, { value: 1, done: false });
|
|
44
|
+
|
|
45
|
+
const r2 = await q.next();
|
|
46
|
+
assert.strictEqual(r2.done, true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('fail() rejects waiting consumers', async () => {
|
|
50
|
+
const q = new StreamQueue<number>();
|
|
51
|
+
|
|
52
|
+
const promise = q.next();
|
|
53
|
+
q.fail(new Error('test error'));
|
|
54
|
+
|
|
55
|
+
await assert.rejects(promise, { message: 'test error' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('return() aborts stream', async () => {
|
|
59
|
+
const q = new StreamQueue<number>();
|
|
60
|
+
q.push(1);
|
|
61
|
+
q.push(2);
|
|
62
|
+
|
|
63
|
+
const r = await q.return();
|
|
64
|
+
assert.strictEqual(r.done, true);
|
|
65
|
+
|
|
66
|
+
// After return, next() returns done
|
|
67
|
+
const r2 = await q.next();
|
|
68
|
+
assert.strictEqual(r2.done, true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('for-await-of integration', async () => {
|
|
72
|
+
const q = new StreamQueue<number>();
|
|
73
|
+
q.push(1);
|
|
74
|
+
q.push(2);
|
|
75
|
+
q.push(3);
|
|
76
|
+
q.end();
|
|
77
|
+
|
|
78
|
+
const collected: number[] = [];
|
|
79
|
+
for await (const item of q) {
|
|
80
|
+
collected.push(item);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
assert.deepStrictEqual(collected, [1, 2, 3]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('end() resolves pending waiters with done', async () => {
|
|
87
|
+
const q = new StreamQueue<number>();
|
|
88
|
+
|
|
89
|
+
const promise = q.next();
|
|
90
|
+
q.end();
|
|
91
|
+
|
|
92
|
+
const result = await promise;
|
|
93
|
+
assert.strictEqual(result.done, true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('push after end is ignored', async () => {
|
|
97
|
+
const q = new StreamQueue<number>();
|
|
98
|
+
q.push(1);
|
|
99
|
+
q.end();
|
|
100
|
+
q.push(2); // Should be ignored
|
|
101
|
+
|
|
102
|
+
const r1 = await q.next();
|
|
103
|
+
assert.deepStrictEqual(r1, { value: 1, done: false });
|
|
104
|
+
const r2 = await q.next();
|
|
105
|
+
assert.strictEqual(r2.done, true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('fail() then next() rejects immediately', async () => {
|
|
109
|
+
const q = new StreamQueue<number>();
|
|
110
|
+
q.fail(new Error('broken'));
|
|
111
|
+
|
|
112
|
+
await assert.rejects(q.next(), { message: 'broken' });
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamQueue<T> — Push-pull adapter for bridging event-driven data
|
|
3
|
+
* arrival (socket responses) to pull-based async iteration (for...await).
|
|
4
|
+
*
|
|
5
|
+
* Used by RFDBClient to bridge _handleResponse (push) to
|
|
6
|
+
* queryNodesStream() (pull via async generator).
|
|
7
|
+
*
|
|
8
|
+
* Backpressure model:
|
|
9
|
+
* - Producer is faster: items buffer in `queue` (unbounded for V1 —
|
|
10
|
+
* TCP flow control on Unix socket provides natural backpressure)
|
|
11
|
+
* - Consumer is faster: consumer waits on a pending Promise
|
|
12
|
+
*/
|
|
13
|
+
export class StreamQueue<T> {
|
|
14
|
+
private _queue: T[] = [];
|
|
15
|
+
private _waiters: Array<{
|
|
16
|
+
resolve: (result: IteratorResult<T, undefined>) => void;
|
|
17
|
+
reject: (error: Error) => void;
|
|
18
|
+
}> = [];
|
|
19
|
+
private _done: boolean = false;
|
|
20
|
+
private _error: Error | null = null;
|
|
21
|
+
|
|
22
|
+
/** Push an item into the queue. If a consumer is waiting, resolves immediately. */
|
|
23
|
+
push(item: T): void {
|
|
24
|
+
if (this._done) return;
|
|
25
|
+
if (this._waiters.length > 0) {
|
|
26
|
+
const waiter = this._waiters.shift()!;
|
|
27
|
+
waiter.resolve({ value: item, done: false });
|
|
28
|
+
} else {
|
|
29
|
+
this._queue.push(item);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Signal that no more items will be pushed. Resolves all waiting consumers. */
|
|
34
|
+
end(): void {
|
|
35
|
+
this._done = true;
|
|
36
|
+
while (this._waiters.length > 0) {
|
|
37
|
+
const waiter = this._waiters.shift()!;
|
|
38
|
+
waiter.resolve({ value: undefined, done: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Signal an error. Rejects all waiting consumers. */
|
|
43
|
+
fail(error: Error): void {
|
|
44
|
+
this._error = error;
|
|
45
|
+
this._done = true;
|
|
46
|
+
while (this._waiters.length > 0) {
|
|
47
|
+
const waiter = this._waiters.shift()!;
|
|
48
|
+
waiter.reject(error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Pull the next item. Returns immediately if buffered, otherwise waits. */
|
|
53
|
+
next(): Promise<IteratorResult<T, undefined>> {
|
|
54
|
+
if (this._error) {
|
|
55
|
+
return Promise.reject(this._error);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (this._queue.length > 0) {
|
|
59
|
+
const item = this._queue.shift()!;
|
|
60
|
+
return Promise.resolve({ value: item, done: false as const });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this._done) {
|
|
64
|
+
return Promise.resolve({ value: undefined, done: true as const });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
this._waiters.push({ resolve, reject });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Consumer abort. Clears buffer and marks stream as done. */
|
|
73
|
+
return(): Promise<IteratorResult<T, undefined>> {
|
|
74
|
+
this._done = true;
|
|
75
|
+
this._queue = [];
|
|
76
|
+
this._waiters = [];
|
|
77
|
+
return Promise.resolve({ value: undefined, done: true as const });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<T> {
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
}
|