clew-code 0.2.28 → 0.2.30
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 +38 -28
- package/bin/clew.cjs +2 -2
- package/dist/main.js +2318 -2331
- package/docs/architecture.html +90 -91
- package/docs/changelog.html +141 -150
- package/docs/cli-reference.html +89 -90
- package/docs/commands.html +10 -9
- package/docs/daemon.html +62 -62
- package/docs/generated/providers.html +625 -0
- package/docs/generated/tools.html +559 -0
- package/docs/index.html +10 -13
- package/docs/personal-profile.html +3 -3
- package/docs/providers.html +625 -87
- package/docs/quick-start.html +81 -81
- package/docs/tools.html +551 -175
- package/docs/troubleshooting.html +86 -86
- package/package.json +165 -165
- package/scripts/auto-close-duplicates.ts +277 -0
- package/scripts/backfill-duplicate-comments.ts +213 -0
- package/scripts/codegraph.ts +221 -0
- package/scripts/comment-on-duplicates.sh +95 -0
- package/scripts/edit-issue-labels.sh +84 -0
- package/scripts/final-peer-rename.mjs +46 -0
- package/scripts/fix-encoding.mjs +83 -0
- package/scripts/fix-inconsistencies.mjs +67 -0
- package/scripts/generate-docs.ts +307 -0
- package/scripts/gh.sh +96 -0
- package/scripts/install.ps1 +37 -0
- package/scripts/install.sh +49 -0
- package/scripts/issue-lifecycle.ts +38 -0
- package/scripts/lifecycle-comment.ts +53 -0
- package/scripts/normalize-html.mjs +37 -0
- package/scripts/preload.ts +159 -0
- package/scripts/rename-content-peer-to-swarm.mjs +67 -0
- package/scripts/rename-hooks-mesh.mjs +6 -0
- package/scripts/rename-peer-to-swarm.mjs +62 -0
- package/scripts/run_devcontainer_claude_code.ps1 +152 -0
- package/scripts/scrape.py +82 -0
- package/scripts/session.ts +195 -0
- package/scripts/sweep.ts +168 -0
- package/scripts/write-discovery-test.cjs +93 -0
- package/scripts/write-discovery-test2.cjs +65 -0
- package/scripts/write-server-final.cjs +108 -0
- package/scripts/write-server-test.cjs +69 -0
- package/scripts/write-server-test2.cjs +99 -0
- package/scripts/write-server-test3.cjs +59 -0
- package/scripts/write-server-test4.cjs +108 -0
- package/scripts/write-server-v2.cjs +142 -0
- package/scripts/write-server-v2b.cjs +129 -0
- package/scripts/write-server-v2c.cjs +136 -0
- package/scripts/write-store-test.cjs +12 -0
- package/scripts/write-store-test2.cjs +163 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
|
|
3
|
+
const code = `
|
|
4
|
+
test('POST /mesh-exec with onExec returns result', async () => {
|
|
5
|
+
let execCmd = '';
|
|
6
|
+
server.setCallbacks({ onExec: async (cmd) => {
|
|
7
|
+
execCmd = cmd;
|
|
8
|
+
return { stdout:'ok\\\\n', stderr:'', exitCode:0 };
|
|
9
|
+
}});
|
|
10
|
+
const port = await server.start(mi());
|
|
11
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
12
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
13
|
+
body: JSON.stringify({ command:'echo hi', from:'t' }),
|
|
14
|
+
});
|
|
15
|
+
expect(res.status).toBe(200);
|
|
16
|
+
const d = await res.json();
|
|
17
|
+
expect(d.running).toBeTrue();
|
|
18
|
+
expect(execCmd).toBe('echo hi');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('POST /mesh-exec returns 501 without onExec', async () => {
|
|
22
|
+
const port = await server.start(mi());
|
|
23
|
+
const res = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
24
|
+
method:'POST', headers:{'Content-Type':'application/json'},
|
|
25
|
+
body: JSON.stringify({ command:'hi', from:'t' }),
|
|
26
|
+
});
|
|
27
|
+
expect(res.status).toBe(501);
|
|
28
|
+
expect((await res.json()).stderr).toBe('Exec not supported');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('POST /mesh-exec queues when busy', async () => {
|
|
32
|
+
let resolveExec;
|
|
33
|
+
const execPromise = new Promise(r => { resolveExec = r; });
|
|
34
|
+
server.setCallbacks({ onExec: async () => execPromise });
|
|
35
|
+
const port = await server.start(mi());
|
|
36
|
+
|
|
37
|
+
// Start first exec (don't await - it won't complete until we resolve)
|
|
38
|
+
const p1 = fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
39
|
+
method:'POST', body: JSON.stringify({ command:'long', from:'t' }),
|
|
40
|
+
});
|
|
41
|
+
// Wait for server to process the request and mark busy
|
|
42
|
+
await new Promise(r => setTimeout(r, 200));
|
|
43
|
+
expect(server.isBusy).toBeTrue();
|
|
44
|
+
|
|
45
|
+
// Second exec should queue
|
|
46
|
+
const r2 = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
47
|
+
method:'POST', body: JSON.stringify({ command:'q', from:'t' }),
|
|
48
|
+
});
|
|
49
|
+
expect(r2.status).toBe(202);
|
|
50
|
+
const d2 = await r2.json();
|
|
51
|
+
expect(d2.queued).toBeTrue();
|
|
52
|
+
expect(server.queueDepth).toBe(1);
|
|
53
|
+
|
|
54
|
+
// Resolve first exec
|
|
55
|
+
resolveExec({ stdout:'', stderr:'', exitCode:0 });
|
|
56
|
+
const r1 = await p1;
|
|
57
|
+
expect(r1.status).toBe(200);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('POST /mesh-exec 503 queue full', async () => {
|
|
61
|
+
let resolveExec;
|
|
62
|
+
const execPromise = new Promise(r => { resolveExec = r; });
|
|
63
|
+
server.setCallbacks({ onExec: async () => execPromise });
|
|
64
|
+
const port = await server.start(mi());
|
|
65
|
+
|
|
66
|
+
// Start first exec
|
|
67
|
+
const p1 = fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
68
|
+
method:'POST', body: JSON.stringify({ command:'first', from:'t' }),
|
|
69
|
+
});
|
|
70
|
+
await new Promise(r => setTimeout(r, 200));
|
|
71
|
+
expect(server.isBusy).toBeTrue();
|
|
72
|
+
|
|
73
|
+
// Fill queue
|
|
74
|
+
const max = (server).maxQueueSize || 50;
|
|
75
|
+
for (let i = 0; i < max; i++) {
|
|
76
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
77
|
+
method:'POST', body: JSON.stringify({ command:\`t-\${i}\`, from:'t' }),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Next one should be rejected
|
|
82
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
83
|
+
method:'POST', body: JSON.stringify({ command:'x', from:'t' }),
|
|
84
|
+
});
|
|
85
|
+
expect(r.status).toBe(503);
|
|
86
|
+
expect((await r.json()).error).toBe('Queue full');
|
|
87
|
+
|
|
88
|
+
resolveExec({ stdout:'', stderr:'', exitCode:0 });
|
|
89
|
+
await p1;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('GET /mesh-queue-status returns queue state', async () => {
|
|
93
|
+
const port = await server.start(mi());
|
|
94
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-status\`);
|
|
95
|
+
expect(r.status).toBe(200);
|
|
96
|
+
const d = await r.json();
|
|
97
|
+
expect(d.isBusy).toBeFalse();
|
|
98
|
+
expect(d.queueDepth).toBe(0);
|
|
99
|
+
expect(d.currentTask).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('POST /mesh-queue-cancel cancels queued task', async () => {
|
|
103
|
+
let resolveExec;
|
|
104
|
+
const execPromise = new Promise(r => { resolveExec = r; });
|
|
105
|
+
server.setCallbacks({ onExec: async () => execPromise });
|
|
106
|
+
const port = await server.start(mi());
|
|
107
|
+
|
|
108
|
+
const p1 = fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
109
|
+
method:'POST', body: JSON.stringify({ command:'first', from:'t' }),
|
|
110
|
+
});
|
|
111
|
+
await new Promise(r => setTimeout(r, 200));
|
|
112
|
+
|
|
113
|
+
// Queue a task, then cancel it
|
|
114
|
+
const qr = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
115
|
+
method:'POST', body: JSON.stringify({ command:'x', from:'t' }),
|
|
116
|
+
});
|
|
117
|
+
const tid = (await qr.json()).id;
|
|
118
|
+
const cr = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, {
|
|
119
|
+
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ id:tid }),
|
|
120
|
+
});
|
|
121
|
+
expect(cr.status).toBe(200);
|
|
122
|
+
expect((await cr.json()).ok).toBeTrue();
|
|
123
|
+
|
|
124
|
+
resolveExec({ stdout:'', stderr:'', exitCode:0 });
|
|
125
|
+
await p1;
|
|
126
|
+
});
|
|
127
|
+
`;
|
|
128
|
+
fs.appendFileSync(p, code, 'utf8');
|
|
129
|
+
console.log('MeshServer.test.ts part 2 written');
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
|
|
3
|
+
const code = `
|
|
4
|
+
test('POST /mesh-queue-cancel 404 for unknown', async () => {
|
|
5
|
+
const port = await server.start(mi());
|
|
6
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, {
|
|
7
|
+
method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ id:'nope' }),
|
|
8
|
+
});
|
|
9
|
+
expect(r.status).toBe(404);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('POST /mesh-queue-cancel 400 if id missing', async () => {
|
|
13
|
+
const port = await server.start(mi());
|
|
14
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, {
|
|
15
|
+
method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({}),
|
|
16
|
+
});
|
|
17
|
+
expect(r.status).toBe(400);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('POST /mesh-queue-cancel-all', async () => {
|
|
21
|
+
let resolveExec;
|
|
22
|
+
const execPromise = new Promise(r => { resolveExec = r; });
|
|
23
|
+
server.setCallbacks({ onExec: async () => execPromise });
|
|
24
|
+
const port = await server.start(mi());
|
|
25
|
+
|
|
26
|
+
const p1 = fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
27
|
+
method:'POST', body: JSON.stringify({ command:'first', from:'t' }),
|
|
28
|
+
});
|
|
29
|
+
await new Promise(r => setTimeout(r, 200));
|
|
30
|
+
|
|
31
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
32
|
+
method:'POST', body: JSON.stringify({ command:'t1', from:'t' }),
|
|
33
|
+
});
|
|
34
|
+
await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
|
|
35
|
+
method:'POST', body: JSON.stringify({ command:'t2', from:'t' }),
|
|
36
|
+
});
|
|
37
|
+
expect(server.queueDepth).toBe(2);
|
|
38
|
+
|
|
39
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel-all\`, { method:'POST' });
|
|
40
|
+
expect(r.status).toBe(200);
|
|
41
|
+
expect((await r.json()).cancelled).toBe(2);
|
|
42
|
+
expect(server.queueDepth).toBe(0);
|
|
43
|
+
|
|
44
|
+
resolveExec({ stdout:'', stderr:'', exitCode:0 });
|
|
45
|
+
await p1;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('cancelAllQueuedTasks when empty', () => {
|
|
49
|
+
expect(server.cancelAllQueuedTasks()).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('GET /mesh-events SSE returns event-stream content type', async () => {
|
|
53
|
+
const port = await server.start(mi());
|
|
54
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
|
|
55
|
+
expect(r.status).toBe(200);
|
|
56
|
+
expect(r.headers.get('content-type')).toContain('text/event-stream');
|
|
57
|
+
await r.body?.cancel();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('SSE connected event contains status', async () => {
|
|
61
|
+
const port = await server.start(mi());
|
|
62
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
|
|
63
|
+
const reader = r.body?.getReader();
|
|
64
|
+
let data = '';
|
|
65
|
+
if (reader) {
|
|
66
|
+
const {value} = await reader.read();
|
|
67
|
+
data = new TextDecoder().decode(value);
|
|
68
|
+
reader.cancel();
|
|
69
|
+
}
|
|
70
|
+
expect(data).toContain('event: connected');
|
|
71
|
+
expect(data).toContain('"status":"ok"');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('broadcastEvent sends event to SSE clients', async () => {
|
|
75
|
+
const port = await server.start(mi());
|
|
76
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
|
|
77
|
+
const reader = r.body?.getReader();
|
|
78
|
+
const dec = new TextDecoder();
|
|
79
|
+
if (reader) {
|
|
80
|
+
// Read and discard connected event
|
|
81
|
+
await reader.read();
|
|
82
|
+
// Broadcast
|
|
83
|
+
server.broadcastEvent('new_message', { text:'sse test' });
|
|
84
|
+
// Read next event
|
|
85
|
+
const {value} = await reader.read();
|
|
86
|
+
const data = dec.decode(value);
|
|
87
|
+
expect(data).toContain('event: new_message');
|
|
88
|
+
expect(data).toContain('"text":"sse test"');
|
|
89
|
+
reader.cancel();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('updatePeerInfo updates stored peer info', async () => {
|
|
94
|
+
const port = await server.start(mi({ id:'orig' }));
|
|
95
|
+
server.updatePeerInfo({ hostname:'upd' });
|
|
96
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
|
|
97
|
+
const d = await r.json();
|
|
98
|
+
expect(d.hostname).toBe('upd');
|
|
99
|
+
expect(d.id).toBe('orig');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('extraInfo appears in mesh-info response', async () => {
|
|
103
|
+
const port = await server.start(mi());
|
|
104
|
+
server.extraInfo = { cf: 'cv' };
|
|
105
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
|
|
106
|
+
expect((await r.json()).cf).toBe('cv');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('cancelQueuedTask returns false for unknown', () => {
|
|
110
|
+
expect(server.cancelQueuedTask('nope')).toBeFalse();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('getTasks returns queue and current', () => {
|
|
114
|
+
const t = server.getTasks();
|
|
115
|
+
expect(t).toHaveProperty('queue');
|
|
116
|
+
expect(t).toHaveProperty('current');
|
|
117
|
+
expect(t.queue).toEqual([]);
|
|
118
|
+
expect(t.current).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('unknown route returns 404', async () => {
|
|
122
|
+
const port = await server.start(mi());
|
|
123
|
+
expect((await fetch(\`http://127.0.0.1:\${port}/unknown\`)).status).toBe(404);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('malformed JSON returns 400', async () => {
|
|
127
|
+
const port = await server.start(mi());
|
|
128
|
+
const r = await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
|
|
129
|
+
method:'POST', headers:{'Content-Type':'application/json'}, body:'not-json',
|
|
130
|
+
});
|
|
131
|
+
expect(r.status).toBe(400);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
`;
|
|
135
|
+
fs.appendFileSync(p, code, 'utf8');
|
|
136
|
+
console.log('MeshServer.test.ts completed');
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshStore.test.ts';
|
|
3
|
+
const code = `
|
|
4
|
+
import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
|
|
5
|
+
import { MeshStore } from './MeshStore.js';
|
|
6
|
+
|
|
7
|
+
function mp(o) { return { id:'peer-1', hostname:'w', ip:'127.0.0.1', port:4200, cwd:'/r', version:'t', lastSeen:Date.now(), status:'online', ...o }; }
|
|
8
|
+
function mm(o) { return { id:'msg-1', from:'peer-1', fromName:'w', text:'hello', color:'cyan', timestamp:Date.now(), ...o }; }
|
|
9
|
+
function mt(o) { return { id:'todo-1', from:'peer-1', fromName:'w', message:'do', createdAt:Date.now(), status:'pending', ...o }; }
|
|
10
|
+
`;
|
|
11
|
+
fs.writeFileSync(p, code.trimStart(), 'utf8');
|
|
12
|
+
console.log('MeshStore.test.ts part 1 written');
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const p = 'D:/Projects/Github/clew-code/src/mesh/MeshStore.test.ts';
|
|
3
|
+
const code = `
|
|
4
|
+
describe('MeshStore', () => {
|
|
5
|
+
let store;
|
|
6
|
+
beforeEach(() => { store = new MeshStore(); });
|
|
7
|
+
afterEach(() => { store.destroy(); });
|
|
8
|
+
|
|
9
|
+
test('addPeer/getPeers', () => {
|
|
10
|
+
store.addPeer(mp());
|
|
11
|
+
expect(store.getPeers()).toHaveLength(1);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('addPeer updates existing', () => {
|
|
15
|
+
store.addPeer(mp()); store.addPeer(mp({ cwd:'/x' }));
|
|
16
|
+
expect(store.getPeers()[0].cwd).toBe('/x');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('getPeer', () => {
|
|
20
|
+
store.addPeer(mp());
|
|
21
|
+
expect(store.getPeer('peer-1')).toBeDefined();
|
|
22
|
+
expect(store.getPeer('nope')).toBeUndefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('findPeer by id/hostname/displayName', () => {
|
|
26
|
+
store.addPeer(mp({ id:'abc', hostname:'alpha' }));
|
|
27
|
+
store.setPeerName('abc','Builder');
|
|
28
|
+
expect(store.findPeer('abc')?.id).toBe('abc');
|
|
29
|
+
expect(store.findPeer('alpha')?.id).toBe('abc');
|
|
30
|
+
expect(store.findPeer('Builder')?.id).toBe('abc');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('addMesh alias', () => {
|
|
34
|
+
store.addMesh(mp({ id:'x' }));
|
|
35
|
+
expect(store.getPeer('x')).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('removePeer fires callback', () => {
|
|
39
|
+
let rid=''; const s=new MeshStore({onPeerRemoved:i=>{rid=i}});
|
|
40
|
+
s.addPeer(mp()); s.removePeer('peer-1');
|
|
41
|
+
expect(rid).toBe('peer-1'); s.destroy();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('addConnection', () => {
|
|
45
|
+
store.addConnection(mp({ id:'c' }));
|
|
46
|
+
expect(store.getConnections()).toHaveLength(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('addMessage/getMessages/clear/createMessage', () => {
|
|
50
|
+
store.addMessage(mm()); expect(store.getMessages()).toHaveLength(1);
|
|
51
|
+
store.clearMessages(); expect(store.getMessages()).toHaveLength(0);
|
|
52
|
+
const m=store.createMessage('tp','T','hi');
|
|
53
|
+
expect(m.from).toBe('tp'); expect(m.text).toBe('hi');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('getMessagesAfter', () => {
|
|
57
|
+
store.addMessage(mm({id:'m1',timestamp:100}));
|
|
58
|
+
store.addMessage(mm({id:'m2',timestamp:200}));
|
|
59
|
+
expect(store.getMessagesAfter(150)).toHaveLength(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('getReassembledMessages', () => {
|
|
63
|
+
store.addMessage(mm({id:'c1',text:'A',chunkGroup:'g',chunkIndex:0,chunkTotal:2}));
|
|
64
|
+
store.addMessage(mm({id:'c2',text:'B',chunkGroup:'g',chunkIndex:1,chunkTotal:2}));
|
|
65
|
+
const r=store.getReassembledMessages();
|
|
66
|
+
expect(r).toHaveLength(1); expect(r[0].text).toBe('AB');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('getChunkGroupStatus', () => {
|
|
70
|
+
expect(store.getChunkGroupStatus('n')).toBeNull();
|
|
71
|
+
store.addMessage(mm({chunkGroup:'g',chunkIndex:0,chunkTotal:3}));
|
|
72
|
+
store.addMessage(mm({chunkGroup:'g',chunkIndex:2,chunkTotal:3}));
|
|
73
|
+
expect(store.getChunkGroupStatus('g').received).toBe(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('waitForNewMessage', async () => {
|
|
77
|
+
const a=Date.now(); const p=store.waitForNewMessage(a,500);
|
|
78
|
+
store.addMessage(mm({timestamp:Date.now()+5}));
|
|
79
|
+
expect((await p)).toHaveLength(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('waitForNewMessage timeout', async () => {
|
|
83
|
+
const msgs=await store.waitForNewMessage(Date.now(),50);
|
|
84
|
+
expect(msgs).toHaveLength(0);
|
|
85
|
+
},2000);
|
|
86
|
+
|
|
87
|
+
test('waitForMessageFrom', async () => {
|
|
88
|
+
const a=Date.now();
|
|
89
|
+
const p=store.waitForMessageFrom(a,500,'exp');
|
|
90
|
+
store.addMessage(mm({id:'w',from:'wrong',timestamp:Date.now()+5}));
|
|
91
|
+
await new Promise(r=>setTimeout(r,50));
|
|
92
|
+
store.addMessage(mm({id:'r',from:'exp',timestamp:Date.now()+20}));
|
|
93
|
+
expect((await p)).toHaveLength(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('addTodo/updateTodoStatus', () => {
|
|
97
|
+
store.addTodo(mt()); expect(store.getTodos()).toHaveLength(1);
|
|
98
|
+
expect(store.updateTodoStatus('todo-1','done')).toBeTrue();
|
|
99
|
+
expect(store.getTodos()[0].status).toBe('done');
|
|
100
|
+
expect(store.updateTodoStatus('n','done')).toBeFalse();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('peer tags and resolve', () => {
|
|
104
|
+
store.setPeerName('p1','N'); store.setPeerRole('p1','R');
|
|
105
|
+
expect(store.getPeerTags('p1')?.displayName).toBe('N');
|
|
106
|
+
expect(store.getPeerTags('p1')?.role).toBe('R');
|
|
107
|
+
const p=mp({id:'p1',hostname:'h'});
|
|
108
|
+
expect(store.resolveName(p)).toBe('N');
|
|
109
|
+
expect(store.resolveRole(p)).toBe('R');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('mesh alias methods', () => {
|
|
113
|
+
store.setMeshName('x','A'); store.setMeshRole('x','B');
|
|
114
|
+
expect(store.getMeshTags('x')?.displayName).toBe('A');
|
|
115
|
+
expect(store.getAllMeshTags()).toHaveLength(1);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('callbacks', () => {
|
|
119
|
+
let a=false,r=false,u=false;
|
|
120
|
+
const s=new MeshStore({onPeerAdded:()=>{a=true},onPeerUpdated:()=>{u=true},onPeerRemoved:()=>{r=true}});
|
|
121
|
+
s.addPeer(mp()); s.addPeer(mp({status:'off'})); s.removePeer('peer-1');
|
|
122
|
+
expect(a).toBeTrue(); expect(u).toBeTrue(); expect(r).toBeTrue();
|
|
123
|
+
s.destroy();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('setCallbacks merges', () => {
|
|
127
|
+
let a=false,m=false;
|
|
128
|
+
const s=new MeshStore({onPeerAdded:()=>{a=true}});
|
|
129
|
+
s.setCallbacks({onMessageReceived:()=>{m=true}});
|
|
130
|
+
s.addPeer(mp()); s.addMessage(mm());
|
|
131
|
+
expect(a).toBeTrue(); expect(m).toBeTrue();
|
|
132
|
+
s.destroy();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('alias getters', () => {
|
|
136
|
+
store.addPeer(mp({port:7777}));
|
|
137
|
+
expect(store.getMeshs()).toHaveLength(1);
|
|
138
|
+
expect(store.getMeshByPort(7777)).toBeDefined();
|
|
139
|
+
expect(store.findMesh('w')?.id).toBe('peer-1');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('size', () => {
|
|
143
|
+
expect(store.size).toBe(0);
|
|
144
|
+
store.addPeer(mp({id:'p1'}));
|
|
145
|
+
expect(store.size).toBe(1);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('destroy', () => {
|
|
149
|
+
store.addPeer(mp()); store.addMessage(mm()); store.addTodo(mt());
|
|
150
|
+
store.destroy();
|
|
151
|
+
expect(store.getPeers()).toHaveLength(0);
|
|
152
|
+
expect(store.getMessages()).toHaveLength(0);
|
|
153
|
+
expect(store.getTodos()).toHaveLength(0);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('singleton', async () => {
|
|
157
|
+
const {getGlobalMeshStore}=await import('./MeshStore.js');
|
|
158
|
+
expect(getGlobalMeshStore()).toBe(getGlobalMeshStore());
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
`;
|
|
162
|
+
fs.appendFileSync(p, code, 'utf8');
|
|
163
|
+
console.log('MeshStore.test.ts completed');
|