@cnstra/devtools-server 1.2.0 → 3.0.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/dist/index.cjs +212 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +42 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +213 -64
- package/dist/index.js.map +1 -1
- package/dist/start.d.ts +12 -2
- package/dist/start.d.ts.map +1 -1
- package/package.json +74 -66
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var devtoolsDto = require('@cnstra/devtools-dto');
|
|
4
|
+
var persist = require('@cnstra/persist');
|
|
4
5
|
|
|
5
6
|
// src/index.ts
|
|
6
7
|
var CNSDevToolsServer = class {
|
|
7
|
-
constructor(repository) {
|
|
8
|
+
constructor(repository, stimulationRepository = new persist.CNSInMemoryStimulationRepository()) {
|
|
8
9
|
this.repository = repository;
|
|
9
10
|
/** WebSocket → appId (for app clients) */
|
|
10
11
|
this.appSockets = /* @__PURE__ */ new Map();
|
|
@@ -14,6 +15,7 @@ var CNSDevToolsServer = class {
|
|
|
14
15
|
this.uiClients = /* @__PURE__ */ new Set();
|
|
15
16
|
this.lastCpuUsage = process.cpuUsage();
|
|
16
17
|
this.lastCpuTime = Date.now();
|
|
18
|
+
this.stimulationRepository = stimulationRepository;
|
|
17
19
|
}
|
|
18
20
|
// ─── Message entry point ─────────────────────────────────────────────────
|
|
19
21
|
async handleMessage(ws, raw) {
|
|
@@ -27,14 +29,14 @@ var CNSDevToolsServer = class {
|
|
|
27
29
|
if (!data || typeof data !== "object" || !("type" in data)) return;
|
|
28
30
|
const type = data.type;
|
|
29
31
|
if (type === "batch") {
|
|
30
|
-
const result = devtoolsDto.
|
|
32
|
+
const result = devtoolsDto.CNSAppBatchMessageDTOSchema.safeParse(data);
|
|
31
33
|
if (!result.success) return;
|
|
32
34
|
for (const item of result.data.items) {
|
|
33
35
|
await this.handleAppBatchItem(ws, item);
|
|
34
36
|
}
|
|
35
37
|
return;
|
|
36
38
|
}
|
|
37
|
-
const clientResult = devtoolsDto.
|
|
39
|
+
const clientResult = devtoolsDto.CNSClientMessageDTOSchema.safeParse(data);
|
|
38
40
|
if (!clientResult.success) return;
|
|
39
41
|
const msg = clientResult.data;
|
|
40
42
|
switch (msg.type) {
|
|
@@ -47,17 +49,119 @@ var CNSDevToolsServer = class {
|
|
|
47
49
|
case "topology.query":
|
|
48
50
|
await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);
|
|
49
51
|
break;
|
|
50
|
-
case "stimulations.query":
|
|
51
|
-
await this.handleStimulationsQuery(ws, msg);
|
|
52
|
-
break;
|
|
53
|
-
case "hops.query":
|
|
54
|
-
await this.handleHopsQuery(ws, msg);
|
|
55
|
-
break;
|
|
56
52
|
case "replay.start":
|
|
57
53
|
await this.handleReplayStart(ws, msg);
|
|
58
54
|
break;
|
|
55
|
+
case "stimulation.retry":
|
|
56
|
+
await this.handleStimulationRetry(ws, msg);
|
|
57
|
+
break;
|
|
58
|
+
case "stimulation.clone":
|
|
59
|
+
await this.handleStimulationClone(ws, msg);
|
|
60
|
+
break;
|
|
61
|
+
case "cns.stimulations.list":
|
|
62
|
+
await this.handleStimulationsList(ws, msg);
|
|
63
|
+
break;
|
|
64
|
+
case "cns.stimulations.facets":
|
|
65
|
+
await this.handleStimulationsFacets(ws, msg);
|
|
66
|
+
break;
|
|
67
|
+
case "cns.stimulations.snapshot":
|
|
68
|
+
await this.handleStimulationsSnapshot(ws, msg);
|
|
69
|
+
break;
|
|
70
|
+
case "cns.stimulation.detail":
|
|
71
|
+
await this.handleStimulationDetail(ws, msg);
|
|
72
|
+
break;
|
|
59
73
|
}
|
|
60
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Explorer protocol (bucket-shaped). The roster row = resumable state minus
|
|
77
|
+
* `progress`, plus envelope summaries derived from the run's attempts.
|
|
78
|
+
*
|
|
79
|
+
* Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the
|
|
80
|
+
* Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we
|
|
81
|
+
* fall back to the shared reference derivation over the reader — identical
|
|
82
|
+
* results, only the work moves. Roster shapes are structurally the wire DTOs,
|
|
83
|
+
* so no mapping is needed at the boundary.
|
|
84
|
+
*/
|
|
85
|
+
toRosterFilter(scopeName, filter) {
|
|
86
|
+
return { ...filter ?? {}, ...scopeName ? { scopeName } : {} };
|
|
87
|
+
}
|
|
88
|
+
async handleStimulationsList(ws, msg) {
|
|
89
|
+
const filter = this.toRosterFilter(msg.scopeName, msg.filter);
|
|
90
|
+
const limit = typeof msg.limit === "number" ? msg.limit : 50;
|
|
91
|
+
const opts = { cursor: msg.cursor, limit };
|
|
92
|
+
const store = this.stimulationRepository;
|
|
93
|
+
const page = persist.supportsRosterQuery(store) ? await store.queryRoster(filter, opts) : persist.keysetPage(
|
|
94
|
+
(await persist.loadRosterEntries(store, filter)).map((e) => e.row),
|
|
95
|
+
opts
|
|
96
|
+
);
|
|
97
|
+
this.send(ws, {
|
|
98
|
+
type: "cns.stimulations.list.result",
|
|
99
|
+
requestId: msg.requestId,
|
|
100
|
+
stimulations: page.rows,
|
|
101
|
+
nextCursor: page.nextCursor,
|
|
102
|
+
total: page.total
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async handleStimulationsFacets(ws, msg) {
|
|
106
|
+
const filter = this.toRosterFilter(msg.scopeName, msg.filter);
|
|
107
|
+
const store = this.stimulationRepository;
|
|
108
|
+
const facets = persist.supportsRosterQuery(store) ? await store.queryFacets(filter, { metricPath: msg.metricPath }) : persist.computeRosterFacets(await persist.loadRosterEntries(store, filter), {
|
|
109
|
+
metricPath: msg.metricPath
|
|
110
|
+
});
|
|
111
|
+
this.send(ws, {
|
|
112
|
+
type: "cns.stimulations.facets.result",
|
|
113
|
+
requestId: msg.requestId,
|
|
114
|
+
facets
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async handleStimulationDetail(ws, msg) {
|
|
118
|
+
const attempts = await this.stimulationRepository.getAttempts(
|
|
119
|
+
msg.stimulationId
|
|
120
|
+
);
|
|
121
|
+
const tasks = [];
|
|
122
|
+
for (const a of attempts) {
|
|
123
|
+
const ts = await this.stimulationRepository.getTasks(
|
|
124
|
+
a.stimulationAttemptId
|
|
125
|
+
);
|
|
126
|
+
tasks.push(...ts);
|
|
127
|
+
}
|
|
128
|
+
this.send(ws, {
|
|
129
|
+
type: "cns.stimulation.detail.result",
|
|
130
|
+
requestId: msg.requestId,
|
|
131
|
+
attempts,
|
|
132
|
+
tasks
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The topology/signal views' bulk feed: a bounded, newest-first set of runs as
|
|
137
|
+
* FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by
|
|
138
|
+
* id. Replaces the retired nested `cns.stimulations.query` tree — same data, no
|
|
139
|
+
* server-side join, no client-side flatten.
|
|
140
|
+
*/
|
|
141
|
+
async handleStimulationsSnapshot(ws, msg) {
|
|
142
|
+
const filter = this.toRosterFilter(msg.scopeName, msg.filter);
|
|
143
|
+
const store = this.stimulationRepository;
|
|
144
|
+
const limit = typeof msg.limit === "number" ? msg.limit : 200;
|
|
145
|
+
const page = persist.supportsRosterQuery(store) ? await store.queryRoster(filter, { limit }) : persist.keysetPage(
|
|
146
|
+
(await persist.loadRosterEntries(store, filter)).map((e) => e.row),
|
|
147
|
+
{ limit }
|
|
148
|
+
);
|
|
149
|
+
const attempts = [];
|
|
150
|
+
const tasks = [];
|
|
151
|
+
for (const row of page.rows) {
|
|
152
|
+
const as = await store.getAttempts(row.stimulationId);
|
|
153
|
+
attempts.push(...as);
|
|
154
|
+
for (const a of as)
|
|
155
|
+
tasks.push(...await store.getTasks(a.stimulationAttemptId));
|
|
156
|
+
}
|
|
157
|
+
this.send(ws, {
|
|
158
|
+
type: "cns.stimulations.snapshot.result",
|
|
159
|
+
requestId: msg.requestId,
|
|
160
|
+
stimulations: page.rows,
|
|
161
|
+
attempts,
|
|
162
|
+
tasks
|
|
163
|
+
});
|
|
164
|
+
}
|
|
61
165
|
addClient(ws) {
|
|
62
166
|
this.uiClients.add(ws);
|
|
63
167
|
if (this.uiClients.size === 1) this.startMetrics();
|
|
@@ -79,17 +183,22 @@ var CNSDevToolsServer = class {
|
|
|
79
183
|
case "topology":
|
|
80
184
|
await this.handleTopology(ws, item);
|
|
81
185
|
break;
|
|
82
|
-
|
|
83
|
-
|
|
186
|
+
// ── Name-based resumable model ──
|
|
187
|
+
case "cns.stimulation":
|
|
188
|
+
await this.stimulationRepository.saveStimulation(item.data);
|
|
84
189
|
break;
|
|
85
|
-
case "stimulation.
|
|
86
|
-
await this.
|
|
190
|
+
case "cns.stimulation.attempt":
|
|
191
|
+
await this.stimulationRepository.saveAttempt(item.data);
|
|
87
192
|
break;
|
|
88
|
-
case "stimulation.
|
|
89
|
-
await this.
|
|
193
|
+
case "cns.stimulation.task":
|
|
194
|
+
await this.stimulationRepository.appendTask(item.data);
|
|
90
195
|
break;
|
|
91
196
|
}
|
|
92
197
|
}
|
|
198
|
+
/** The name-based resumable store, for tests/integrations that want to read it back. */
|
|
199
|
+
getStimulationRepository() {
|
|
200
|
+
return this.stimulationRepository;
|
|
201
|
+
}
|
|
93
202
|
async handleTopology(ws, msg) {
|
|
94
203
|
const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;
|
|
95
204
|
const now = Date.now();
|
|
@@ -114,35 +223,6 @@ var CNSDevToolsServer = class {
|
|
|
114
223
|
this.broadcast({ type: "app.connected", app, topology: { cnsId, neurons, collaterals, dendrites } });
|
|
115
224
|
this.broadcastRaw(JSON.stringify(topologyBroadcast));
|
|
116
225
|
}
|
|
117
|
-
async handleStimulationStarted(ws, stimulation) {
|
|
118
|
-
const appId = this.appSockets.get(ws);
|
|
119
|
-
if (appId) {
|
|
120
|
-
const apps = await this.repository.listApps();
|
|
121
|
-
const app = apps.find((a) => a.id === appId);
|
|
122
|
-
if (app) await this.repository.upsertApp({ ...app, lastSeenAt: Date.now() });
|
|
123
|
-
}
|
|
124
|
-
await this.repository.saveStimulation(stimulation);
|
|
125
|
-
this.broadcast({ type: "stimulation.started", stimulation });
|
|
126
|
-
}
|
|
127
|
-
async handleStimulationHop(hop) {
|
|
128
|
-
await this.repository.saveHop(hop);
|
|
129
|
-
this.broadcast({ type: "stimulation.hop", hop });
|
|
130
|
-
}
|
|
131
|
-
async handleStimulationCompleted(item) {
|
|
132
|
-
await this.repository.completeStimulation(
|
|
133
|
-
item.stimulationId,
|
|
134
|
-
item.completedAt,
|
|
135
|
-
item.hopCount,
|
|
136
|
-
item.hasError
|
|
137
|
-
);
|
|
138
|
-
this.broadcast({
|
|
139
|
-
type: "stimulation.completed",
|
|
140
|
-
stimulationId: item.stimulationId,
|
|
141
|
-
completedAt: item.completedAt,
|
|
142
|
-
hopCount: item.hopCount,
|
|
143
|
-
hasError: item.hasError
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
226
|
// ─── UI client query handlers ─────────────────────────────────────────────
|
|
147
227
|
async handleClientConnect(ws) {
|
|
148
228
|
this.addClient(ws);
|
|
@@ -162,25 +242,6 @@ var CNSDevToolsServer = class {
|
|
|
162
242
|
if (appId) snapshots = snapshots.filter((s) => s.appId === appId);
|
|
163
243
|
this.send(ws, { type: "topology.result", requestId, snapshots });
|
|
164
244
|
}
|
|
165
|
-
async handleStimulationsQuery(ws, msg) {
|
|
166
|
-
const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : void 0);
|
|
167
|
-
if (!appId) {
|
|
168
|
-
this.send(ws, { type: "stimulations.result", requestId: msg.requestId, items: [], total: 0, offset: 0 });
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
const { items, total } = await this.repository.getStimulations(appId, msg.filter ?? {});
|
|
172
|
-
this.send(ws, {
|
|
173
|
-
type: "stimulations.result",
|
|
174
|
-
requestId: msg.requestId,
|
|
175
|
-
items,
|
|
176
|
-
total,
|
|
177
|
-
offset: msg.filter?.offset ?? 0
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
async handleHopsQuery(ws, msg) {
|
|
181
|
-
const items = await this.repository.getHops(msg.stimulationId);
|
|
182
|
-
this.send(ws, { type: "hops.result", requestId: msg.requestId, items });
|
|
183
|
-
}
|
|
184
245
|
async handleReplayStart(ws, msg) {
|
|
185
246
|
const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : void 0);
|
|
186
247
|
const appSockets = appId ? this.appWsByAppId.get(appId) : void 0;
|
|
@@ -198,6 +259,94 @@ var CNSDevToolsServer = class {
|
|
|
198
259
|
}
|
|
199
260
|
}
|
|
200
261
|
}
|
|
262
|
+
// ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────
|
|
263
|
+
//
|
|
264
|
+
// The UI's request is thin (a stimulationId). The CNS + name→ref registry live in
|
|
265
|
+
// the APP, so the server can't execute it: it ENRICHES from the resumable store (the
|
|
266
|
+
// stored entry + progress + a server-assigned attempt identity) and forwards a
|
|
267
|
+
// resume/launch command to the owning app, routed by scopeName (= the cns id).
|
|
268
|
+
/** Resolve the app that owns a stimulation's scope, or reject if unreachable. */
|
|
269
|
+
async resolveScopeApp(scopeName) {
|
|
270
|
+
const appId = scopeName ? await this.repository.findAppByCns(scopeName) : void 0;
|
|
271
|
+
const sockets = appId ? this.appWsByAppId.get(appId) : void 0;
|
|
272
|
+
return sockets && sockets.size > 0 ? sockets : void 0;
|
|
273
|
+
}
|
|
274
|
+
async handleStimulationRetry(ws, msg) {
|
|
275
|
+
const reject = (reason) => this.send(ws, {
|
|
276
|
+
type: "stimulation.retry.rejected",
|
|
277
|
+
requestId: msg.requestId,
|
|
278
|
+
stimulationId: msg.stimulationId,
|
|
279
|
+
reason
|
|
280
|
+
});
|
|
281
|
+
const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);
|
|
282
|
+
if (!stim) return reject("unknown stimulation");
|
|
283
|
+
if (stim.progress.tasks.length === 0)
|
|
284
|
+
return reject("nothing to resume (empty frontier)");
|
|
285
|
+
const appSockets = await this.resolveScopeApp(stim.scopeName);
|
|
286
|
+
if (!appSockets) return reject("app not connected");
|
|
287
|
+
const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);
|
|
288
|
+
const nextAttempt = attempts.length + 1;
|
|
289
|
+
const stimulationAttemptId = devtoolsDto.buildAttemptId(msg.stimulationId, nextAttempt);
|
|
290
|
+
this.send(ws, {
|
|
291
|
+
type: "stimulation.retry.accepted",
|
|
292
|
+
requestId: msg.requestId,
|
|
293
|
+
stimulationId: msg.stimulationId,
|
|
294
|
+
newStimulationAttemptId: stimulationAttemptId
|
|
295
|
+
});
|
|
296
|
+
const payload = JSON.stringify({
|
|
297
|
+
type: "cns.stimulation.resume",
|
|
298
|
+
requestId: msg.requestId,
|
|
299
|
+
scopeName: stim.scopeName,
|
|
300
|
+
stimulationId: msg.stimulationId,
|
|
301
|
+
stimulationAttemptId,
|
|
302
|
+
attemptNumber: nextAttempt,
|
|
303
|
+
entry: stim.entry,
|
|
304
|
+
progress: stim.progress,
|
|
305
|
+
options: msg.options
|
|
306
|
+
});
|
|
307
|
+
for (const appWs of appSockets) {
|
|
308
|
+
try {
|
|
309
|
+
appWs.send(payload);
|
|
310
|
+
} catch {
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async handleStimulationClone(ws, msg) {
|
|
315
|
+
const reject = (reason) => this.send(ws, {
|
|
316
|
+
type: "stimulation.clone.rejected",
|
|
317
|
+
requestId: msg.requestId,
|
|
318
|
+
stimulationId: msg.stimulationId,
|
|
319
|
+
reason
|
|
320
|
+
});
|
|
321
|
+
const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);
|
|
322
|
+
if (!stim) return reject("unknown stimulation");
|
|
323
|
+
const appSockets = await this.resolveScopeApp(stim.scopeName);
|
|
324
|
+
if (!appSockets) return reject("app not connected");
|
|
325
|
+
const newStimulationId = devtoolsDto.buildCloneId(msg.stimulationId);
|
|
326
|
+
const stimulationAttemptId = devtoolsDto.buildAttemptId(newStimulationId, 1);
|
|
327
|
+
this.send(ws, {
|
|
328
|
+
type: "stimulation.clone.accepted",
|
|
329
|
+
requestId: msg.requestId,
|
|
330
|
+
stimulationId: msg.stimulationId,
|
|
331
|
+
newStimulationId,
|
|
332
|
+
newStimulationAttemptId: stimulationAttemptId
|
|
333
|
+
});
|
|
334
|
+
const payload = JSON.stringify({
|
|
335
|
+
type: "cns.stimulation.launch",
|
|
336
|
+
requestId: msg.requestId,
|
|
337
|
+
scopeName: stim.scopeName,
|
|
338
|
+
stimulationId: newStimulationId,
|
|
339
|
+
stimulationAttemptId,
|
|
340
|
+
entry: stim.entry,
|
|
341
|
+
options: msg.options
|
|
342
|
+
});
|
|
343
|
+
for (const appWs of appSockets) {
|
|
344
|
+
try {
|
|
345
|
+
appWs.send(payload);
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
201
350
|
// ─── Broadcast helpers ────────────────────────────────────────────────────
|
|
202
351
|
broadcast(message) {
|
|
203
352
|
this.broadcastRaw(JSON.stringify(message));
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["CNSDTOAppBatchMessageSchema","CNSDTOClientMessageSchema"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n CNSDTOApp,\n CNSDTOAppBatchMessageSchema,\n CNSDTOClientMessageSchema,\n CNSDTOStimulation,\n CNSDTOStimulationFilter,\n CNSDTOHop,\n CNSDTOTopologyMessage,\n} from '@cnstra/devtools-dto';\nimport { WebSocket } from 'ws';\n\n// ─── Repository interface ─────────────────────────────────────────────────────\n\nexport type CNSDTOTopologySnapshot = Omit<CNSDTOTopologyMessage, 'type'>;\n\nexport interface ICNSDevToolsServerRepository {\n // Apps\n upsertApp(app: CNSDTOApp): void | Promise<void>;\n listApps(): CNSDTOApp[] | Promise<CNSDTOApp[]>;\n\n // Topology\n saveTopology(snapshot: CNSDTOTopologySnapshot): void | Promise<void>;\n getTopology(cnsId?: string): CNSDTOTopologySnapshot[] | Promise<CNSDTOTopologySnapshot[]>;\n\n // Stimulations\n saveStimulation(stimulation: CNSDTOStimulation): void | Promise<void>;\n completeStimulation(\n stimulationId: string,\n completedAt: number,\n hopCount: number,\n hasError: boolean\n ): void | Promise<void>;\n getStimulations(\n appId: string,\n filter: CNSDTOStimulationFilter\n ): { items: CNSDTOStimulation[]; total: number } | Promise<{ items: CNSDTOStimulation[]; total: number }>;\n\n // Hops\n saveHop(hop: CNSDTOHop): void | Promise<void>;\n getHops(stimulationId: string): CNSDTOHop[] | Promise<CNSDTOHop[]>;\n\n // CNS ↔ App mapping\n addCnsToApp(appId: string, cnsId: string): void | Promise<void>;\n getCnsByApp(appId: string): string[] | Promise<string[]>;\n findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;\n}\n\n// ─── Server ───────────────────────────────────────────────────────────────────\n\nexport class CNSDevToolsServer {\n /** WebSocket → appId (for app clients) */\n private appSockets = new Map<WebSocket, string>();\n /** appId → WebSocket[] (one app may have multiple CNS instances) */\n private appWsByAppId = new Map<string, Set<WebSocket>>();\n /** WebSocket clients that registered as UI clients */\n private uiClients = new Set<WebSocket>();\n\n private metricsTimer?: NodeJS.Timeout;\n private lastCpuUsage = process.cpuUsage();\n private lastCpuTime = Date.now();\n\n constructor(private readonly repository: ICNSDevToolsServerRepository) {}\n\n // ─── Message entry point ─────────────────────────────────────────────────\n\n async handleMessage(\n ws: WebSocket,\n raw: unknown\n ): Promise<void> {\n if (typeof raw !== 'string' && typeof raw !== 'object') return;\n\n let data: unknown;\n try {\n data = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } catch {\n return;\n }\n\n if (!data || typeof data !== 'object' || !('type' in data)) return;\n const type = (data as any).type as string;\n\n // App batch (topology + stimulation events)\n if (type === 'batch') {\n const result = CNSDTOAppBatchMessageSchema.safeParse(data);\n if (!result.success) return;\n for (const item of result.data.items) {\n await this.handleAppBatchItem(ws, item);\n }\n return;\n }\n\n // UI client message\n const clientResult = CNSDTOClientMessageSchema.safeParse(data);\n if (!clientResult.success) return;\n const msg = clientResult.data;\n\n switch (msg.type) {\n case 'client.connect':\n await this.handleClientConnect(ws);\n break;\n case 'apps.query':\n await this.handleAppsQuery(ws, msg.requestId);\n break;\n case 'topology.query':\n await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);\n break;\n case 'stimulations.query':\n await this.handleStimulationsQuery(ws, msg);\n break;\n case 'hops.query':\n await this.handleHopsQuery(ws, msg);\n break;\n case 'replay.start':\n await this.handleReplayStart(ws, msg);\n break;\n }\n }\n\n addClient(ws: WebSocket): void {\n this.uiClients.add(ws);\n if (this.uiClients.size === 1) this.startMetrics();\n }\n\n removeClient(ws: WebSocket): void {\n this.uiClients.delete(ws);\n this.appSockets.delete(ws);\n if (this.uiClients.size === 0) this.stopMetrics();\n }\n\n async getActiveApps(): Promise<CNSDTOApp[]> {\n return this.repository.listApps();\n }\n\n stop(): void {\n this.stopMetrics();\n }\n\n // ─── App batch item handlers ──────────────────────────────────────────────\n\n private async handleAppBatchItem(ws: WebSocket, item: any): Promise<void> {\n switch (item.type) {\n case 'topology':\n await this.handleTopology(ws, item);\n break;\n case 'stimulation.started':\n await this.handleStimulationStarted(ws, item.stimulation);\n break;\n case 'stimulation.hop':\n await this.handleStimulationHop(item.hop);\n break;\n case 'stimulation.completed':\n await this.handleStimulationCompleted(item);\n break;\n }\n }\n\n private async handleTopology(ws: WebSocket, msg: any): Promise<void> {\n const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;\n\n const now = Date.now();\n const app: CNSDTOApp = {\n id: appId,\n name: appName,\n version,\n connectedAt: now,\n lastSeenAt: now,\n };\n\n await this.repository.upsertApp(app);\n await this.repository.addCnsToApp(appId, cnsId);\n await this.repository.saveTopology({ appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites });\n\n // Track app socket\n this.appSockets.set(ws, appId);\n if (!this.appWsByAppId.has(appId)) this.appWsByAppId.set(appId, new Set());\n this.appWsByAppId.get(appId)!.add(ws);\n\n // Broadcast topology + app connected to UI clients\n const topologyBroadcast = {\n type: 'topology.result' as const,\n requestId: '__broadcast__',\n snapshots: await this.repository.getTopology(cnsId),\n };\n\n this.broadcast({ type: 'app.connected', app, topology: { cnsId, neurons, collaterals, dendrites } });\n // Also send topology snapshot so UI can refresh graph\n this.broadcastRaw(JSON.stringify(topologyBroadcast));\n }\n\n private async handleStimulationStarted(ws: WebSocket, stimulation: CNSDTOStimulation): Promise<void> {\n // Update lastSeenAt for the app\n const appId = this.appSockets.get(ws);\n if (appId) {\n const apps = await this.repository.listApps();\n const app = apps.find(a => a.id === appId);\n if (app) await this.repository.upsertApp({ ...app, lastSeenAt: Date.now() });\n }\n\n await this.repository.saveStimulation(stimulation);\n this.broadcast({ type: 'stimulation.started', stimulation });\n }\n\n private async handleStimulationHop(hop: CNSDTOHop): Promise<void> {\n await this.repository.saveHop(hop);\n this.broadcast({ type: 'stimulation.hop', hop });\n }\n\n private async handleStimulationCompleted(item: any): Promise<void> {\n await this.repository.completeStimulation(\n item.stimulationId,\n item.completedAt,\n item.hopCount,\n item.hasError\n );\n this.broadcast({\n type: 'stimulation.completed',\n stimulationId: item.stimulationId,\n completedAt: item.completedAt,\n hopCount: item.hopCount,\n hasError: item.hasError,\n });\n }\n\n // ─── UI client query handlers ─────────────────────────────────────────────\n\n private async handleClientConnect(ws: WebSocket): Promise<void> {\n this.addClient(ws);\n this.send(ws, { type: 'apps.result', requestId: '__init__', items: await this.repository.listApps() });\n this.send(ws, {\n type: 'topology.result',\n requestId: '__init__',\n snapshots: await this.repository.getTopology(),\n });\n }\n\n private async handleAppsQuery(ws: WebSocket, requestId: string): Promise<void> {\n const items = await this.repository.listApps();\n this.send(ws, { type: 'apps.result', requestId, items });\n }\n\n private async handleTopologyQuery(\n ws: WebSocket,\n requestId: string,\n cnsId?: string,\n appId?: string\n ): Promise<void> {\n let snapshots = await this.repository.getTopology(cnsId);\n if (appId) snapshots = snapshots.filter(s => s.appId === appId);\n this.send(ws, { type: 'topology.result', requestId, snapshots });\n }\n\n private async handleStimulationsQuery(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n\n if (!appId) {\n this.send(ws, { type: 'stimulations.result', requestId: msg.requestId, items: [], total: 0, offset: 0 });\n return;\n }\n\n const { items, total } = await this.repository.getStimulations(appId, msg.filter ?? {});\n this.send(ws, {\n type: 'stimulations.result',\n requestId: msg.requestId,\n items,\n total,\n offset: msg.filter?.offset ?? 0,\n });\n }\n\n private async handleHopsQuery(ws: WebSocket, msg: any): Promise<void> {\n const items = await this.repository.getHops(msg.stimulationId);\n this.send(ws, { type: 'hops.result', requestId: msg.requestId, items });\n }\n\n private async handleReplayStart(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n const appSockets = appId ? this.appWsByAppId.get(appId) : undefined;\n\n if (!appSockets || appSockets.size === 0) {\n this.send(ws, { type: 'replay.rejected', replayId: msg.replayId, reason: 'App not connected' });\n return;\n }\n\n const newStimulationId = `${msg.stimulationId}-replay-${Date.now()}`;\n this.send(ws, { type: 'replay.accepted', replayId: msg.replayId, newStimulationId });\n\n const payload = JSON.stringify(msg);\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Broadcast helpers ────────────────────────────────────────────────────\n\n private broadcast(message: object): void {\n this.broadcastRaw(JSON.stringify(message));\n }\n\n private broadcastRaw(payload: string): void {\n for (const client of this.uiClients) {\n try {\n if ((client as any).readyState === 1) client.send(payload);\n } catch {}\n }\n }\n\n private send(ws: WebSocket, message: object): void {\n try {\n if ((ws as any).readyState === 1) ws.send(JSON.stringify(message));\n } catch {}\n }\n\n // ─── Metrics ──────────────────────────────────────────────────────────────\n\n private startMetrics(): void {\n if (this.metricsTimer) return;\n this.metricsTimer = setInterval(() => {\n const mem = process.memoryUsage();\n const now = Date.now();\n const cpu = process.cpuUsage(this.lastCpuUsage);\n const elapsed = (now - this.lastCpuTime) * 1000;\n const cpuPercent = elapsed > 0 ? ((cpu.user + cpu.system) / elapsed) * 100 : 0;\n this.lastCpuUsage = process.cpuUsage();\n this.lastCpuTime = now;\n\n this.broadcast({\n type: 'server.metrics',\n timestamp: now,\n rssMB: mem.rss / 1024 / 1024,\n heapUsedMB: mem.heapUsed / 1024 / 1024,\n heapTotalMB: mem.heapTotal / 1024 / 1024,\n cpuPercent,\n });\n }, 1000);\n }\n\n private stopMetrics(): void {\n if (this.metricsTimer) {\n clearInterval(this.metricsTimer);\n this.metricsTimer = undefined;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["CNSInMemoryStimulationRepository","CNSAppBatchMessageDTOSchema","CNSClientMessageDTOSchema","supportsRosterQuery","keysetPage","loadRosterEntries","computeRosterFacets","buildAttemptId","buildCloneId"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n TCNSAppDTO,\n CNSAppBatchMessageDTOSchema,\n CNSClientMessageDTOSchema,\n TCNSTopologyMessageDTO,\n} from '@cnstra/devtools-dto';\nimport type {\n TCNSStimulationFilter,\n TCNSStimulationTask,\n TCNSStimulationAttempt,\n} from '@cnstra/devtools-dto';\nimport { buildAttemptId, buildCloneId } from '@cnstra/devtools-dto';\nimport type {\n ICNSStimulationRepository,\n TCNSStimulationRosterFilter,\n} from '@cnstra/persist';\nimport {\n CNSInMemoryStimulationRepository,\n supportsRosterQuery,\n loadRosterEntries,\n keysetPage,\n computeRosterFacets,\n} from '@cnstra/persist';\nimport { WebSocket } from 'ws';\n\n// ─── Repository interface ─────────────────────────────────────────────────────\n\nexport type TCNSTopologySnapshotDTO = Omit<TCNSTopologyMessageDTO, 'type'>;\n\nexport interface ICNSDevToolsServerRepository {\n // Apps\n upsertApp(app: TCNSAppDTO): void | Promise<void>;\n listApps(): TCNSAppDTO[] | Promise<TCNSAppDTO[]>;\n\n // Topology\n saveTopology(snapshot: TCNSTopologySnapshotDTO): void | Promise<void>;\n getTopology(cnsId?: string): TCNSTopologySnapshotDTO[] | Promise<TCNSTopologySnapshotDTO[]>;\n\n // CNS ↔ App mapping\n addCnsToApp(appId: string, cnsId: string): void | Promise<void>;\n getCnsByApp(appId: string): string[] | Promise<string[]>;\n findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;\n}\n\n// ─── Server ───────────────────────────────────────────────────────────────────\n\nexport class CNSDevToolsServer {\n /** WebSocket → appId (for app clients) */\n private appSockets = new Map<WebSocket, string>();\n /** appId → WebSocket[] (one app may have multiple CNS instances) */\n private appWsByAppId = new Map<string, Set<WebSocket>>();\n /** WebSocket clients that registered as UI clients */\n private uiClients = new Set<WebSocket>();\n\n private metricsTimer?: NodeJS.Timeout;\n private lastCpuUsage = process.cpuUsage();\n private lastCpuTime = Date.now();\n\n /**\n * Name-based resumable store (Stimulation/Attempt/Task). Written from the three\n * `cns.stimulation*` batch items the producer emits when `trackStimulations` is on.\n * Defaults to in-memory; the example-app can inject a Postgres store.\n */\n private readonly stimulationRepository: ICNSStimulationRepository;\n\n constructor(\n private readonly repository: ICNSDevToolsServerRepository,\n stimulationRepository: ICNSStimulationRepository = new CNSInMemoryStimulationRepository()\n ) {\n this.stimulationRepository = stimulationRepository;\n }\n\n // ─── Message entry point ─────────────────────────────────────────────────\n\n async handleMessage(\n ws: WebSocket,\n raw: unknown\n ): Promise<void> {\n if (typeof raw !== 'string' && typeof raw !== 'object') return;\n\n let data: unknown;\n try {\n data = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } catch {\n return;\n }\n\n if (!data || typeof data !== 'object' || !('type' in data)) return;\n const type = (data as any).type as string;\n\n // App batch (topology + stimulation events)\n if (type === 'batch') {\n const result = CNSAppBatchMessageDTOSchema.safeParse(data);\n if (!result.success) return;\n for (const item of result.data.items) {\n await this.handleAppBatchItem(ws, item);\n }\n return;\n }\n\n // UI client message\n const clientResult = CNSClientMessageDTOSchema.safeParse(data);\n if (!clientResult.success) return;\n const msg = clientResult.data;\n\n switch (msg.type) {\n case 'client.connect':\n await this.handleClientConnect(ws);\n break;\n case 'apps.query':\n await this.handleAppsQuery(ws, msg.requestId);\n break;\n case 'topology.query':\n await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);\n break;\n case 'replay.start':\n await this.handleReplayStart(ws, msg);\n break;\n case 'stimulation.retry':\n await this.handleStimulationRetry(ws, msg);\n break;\n case 'stimulation.clone':\n await this.handleStimulationClone(ws, msg);\n break;\n case 'cns.stimulations.list':\n await this.handleStimulationsList(ws, msg);\n break;\n case 'cns.stimulations.facets':\n await this.handleStimulationsFacets(ws, msg);\n break;\n case 'cns.stimulations.snapshot':\n await this.handleStimulationsSnapshot(ws, msg);\n break;\n case 'cns.stimulation.detail':\n await this.handleStimulationDetail(ws, msg);\n break;\n }\n }\n\n /**\n * Explorer protocol (bucket-shaped). The roster row = resumable state minus\n * `progress`, plus envelope summaries derived from the run's attempts.\n *\n * Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the\n * Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we\n * fall back to the shared reference derivation over the reader — identical\n * results, only the work moves. Roster shapes are structurally the wire DTOs,\n * so no mapping is needed at the boundary.\n */\n private toRosterFilter(\n scopeName: string | undefined,\n filter: TCNSStimulationFilter | undefined\n ): TCNSStimulationRosterFilter {\n return { ...(filter ?? {}), ...(scopeName ? { scopeName } : {}) };\n }\n\n private async handleStimulationsList(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const limit = typeof msg.limit === 'number' ? msg.limit : 50;\n const opts = { cursor: msg.cursor, limit };\n const store = this.stimulationRepository;\n const page = supportsRosterQuery(store)\n ? await store.queryRoster(filter, opts)\n : keysetPage(\n (await loadRosterEntries(store, filter)).map(e => e.row),\n opts\n );\n this.send(ws, {\n type: 'cns.stimulations.list.result',\n requestId: msg.requestId,\n stimulations: page.rows,\n nextCursor: page.nextCursor,\n total: page.total,\n });\n }\n\n private async handleStimulationsFacets(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const store = this.stimulationRepository;\n const facets = supportsRosterQuery(store)\n ? await store.queryFacets(filter, { metricPath: msg.metricPath })\n : computeRosterFacets(await loadRosterEntries(store, filter), {\n metricPath: msg.metricPath,\n });\n this.send(ws, {\n type: 'cns.stimulations.facets.result',\n requestId: msg.requestId,\n facets,\n });\n }\n\n private async handleStimulationDetail(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const attempts = await this.stimulationRepository.getAttempts(\n msg.stimulationId\n );\n const tasks: TCNSStimulationTask[] = [];\n for (const a of attempts) {\n const ts = await this.stimulationRepository.getTasks(\n a.stimulationAttemptId\n );\n tasks.push(...ts);\n }\n this.send(ws, {\n type: 'cns.stimulation.detail.result',\n requestId: msg.requestId,\n attempts,\n tasks,\n });\n }\n\n /**\n * The topology/signal views' bulk feed: a bounded, newest-first set of runs as\n * FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by\n * id. Replaces the retired nested `cns.stimulations.query` tree — same data, no\n * server-side join, no client-side flatten.\n */\n private async handleStimulationsSnapshot(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const store = this.stimulationRepository;\n const limit = typeof msg.limit === 'number' ? msg.limit : 200;\n const page = supportsRosterQuery(store)\n ? await store.queryRoster(filter, { limit })\n : keysetPage(\n (await loadRosterEntries(store, filter)).map(e => e.row),\n { limit }\n );\n const attempts: TCNSStimulationAttempt[] = [];\n const tasks: TCNSStimulationTask[] = [];\n for (const row of page.rows) {\n const as = await store.getAttempts(row.stimulationId);\n attempts.push(...as);\n for (const a of as)\n tasks.push(...(await store.getTasks(a.stimulationAttemptId)));\n }\n this.send(ws, {\n type: 'cns.stimulations.snapshot.result',\n requestId: msg.requestId,\n stimulations: page.rows,\n attempts,\n tasks,\n });\n }\n\n addClient(ws: WebSocket): void {\n this.uiClients.add(ws);\n if (this.uiClients.size === 1) this.startMetrics();\n }\n\n removeClient(ws: WebSocket): void {\n this.uiClients.delete(ws);\n this.appSockets.delete(ws);\n if (this.uiClients.size === 0) this.stopMetrics();\n }\n\n async getActiveApps(): Promise<TCNSAppDTO[]> {\n return this.repository.listApps();\n }\n\n stop(): void {\n this.stopMetrics();\n }\n\n // ─── App batch item handlers ──────────────────────────────────────────────\n\n private async handleAppBatchItem(ws: WebSocket, item: any): Promise<void> {\n switch (item.type) {\n case 'topology':\n await this.handleTopology(ws, item);\n break;\n // ── Name-based resumable model ──\n case 'cns.stimulation':\n await this.stimulationRepository.saveStimulation(item.data);\n break;\n case 'cns.stimulation.attempt':\n await this.stimulationRepository.saveAttempt(item.data);\n break;\n case 'cns.stimulation.task':\n await this.stimulationRepository.appendTask(item.data);\n break;\n }\n }\n\n /** The name-based resumable store, for tests/integrations that want to read it back. */\n getStimulationRepository(): ICNSStimulationRepository {\n return this.stimulationRepository;\n }\n\n private async handleTopology(ws: WebSocket, msg: any): Promise<void> {\n const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;\n\n const now = Date.now();\n const app: TCNSAppDTO = {\n id: appId,\n name: appName,\n version,\n connectedAt: now,\n lastSeenAt: now,\n };\n\n await this.repository.upsertApp(app);\n await this.repository.addCnsToApp(appId, cnsId);\n await this.repository.saveTopology({ appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites });\n\n // Track app socket\n this.appSockets.set(ws, appId);\n if (!this.appWsByAppId.has(appId)) this.appWsByAppId.set(appId, new Set());\n this.appWsByAppId.get(appId)!.add(ws);\n\n // Broadcast topology + app connected to UI clients\n const topologyBroadcast = {\n type: 'topology.result' as const,\n requestId: '__broadcast__',\n snapshots: await this.repository.getTopology(cnsId),\n };\n\n this.broadcast({ type: 'app.connected', app, topology: { cnsId, neurons, collaterals, dendrites } });\n // Also send topology snapshot so UI can refresh graph\n this.broadcastRaw(JSON.stringify(topologyBroadcast));\n }\n\n // ─── UI client query handlers ─────────────────────────────────────────────\n\n private async handleClientConnect(ws: WebSocket): Promise<void> {\n this.addClient(ws);\n this.send(ws, { type: 'apps.result', requestId: '__init__', items: await this.repository.listApps() });\n this.send(ws, {\n type: 'topology.result',\n requestId: '__init__',\n snapshots: await this.repository.getTopology(),\n });\n }\n\n private async handleAppsQuery(ws: WebSocket, requestId: string): Promise<void> {\n const items = await this.repository.listApps();\n this.send(ws, { type: 'apps.result', requestId, items });\n }\n\n private async handleTopologyQuery(\n ws: WebSocket,\n requestId: string,\n cnsId?: string,\n appId?: string\n ): Promise<void> {\n let snapshots = await this.repository.getTopology(cnsId);\n if (appId) snapshots = snapshots.filter(s => s.appId === appId);\n this.send(ws, { type: 'topology.result', requestId, snapshots });\n }\n\n private async handleReplayStart(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n const appSockets = appId ? this.appWsByAppId.get(appId) : undefined;\n\n if (!appSockets || appSockets.size === 0) {\n this.send(ws, { type: 'replay.rejected', replayId: msg.replayId, reason: 'App not connected' });\n return;\n }\n\n const newStimulationId = `${msg.stimulationId}-replay-${Date.now()}`;\n this.send(ws, { type: 'replay.accepted', replayId: msg.replayId, newStimulationId });\n\n const payload = JSON.stringify(msg);\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────\n //\n // The UI's request is thin (a stimulationId). The CNS + name→ref registry live in\n // the APP, so the server can't execute it: it ENRICHES from the resumable store (the\n // stored entry + progress + a server-assigned attempt identity) and forwards a\n // resume/launch command to the owning app, routed by scopeName (= the cns id).\n\n /** Resolve the app that owns a stimulation's scope, or reject if unreachable. */\n private async resolveScopeApp(\n scopeName: string | undefined\n ): Promise<Set<WebSocket> | undefined> {\n const appId = scopeName\n ? await this.repository.findAppByCns(scopeName)\n : undefined;\n const sockets = appId ? this.appWsByAppId.get(appId) : undefined;\n return sockets && sockets.size > 0 ? sockets : undefined;\n }\n\n private async handleStimulationRetry(ws: WebSocket, msg: any): Promise<void> {\n const reject = (reason: string) =>\n this.send(ws, {\n type: 'stimulation.retry.rejected',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n reason,\n });\n\n const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);\n if (!stim) return reject('unknown stimulation');\n if (stim.progress.tasks.length === 0)\n return reject('nothing to resume (empty frontier)');\n\n const appSockets = await this.resolveScopeApp(stim.scopeName);\n if (!appSockets) return reject('app not connected');\n\n const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);\n const nextAttempt = attempts.length + 1;\n const stimulationAttemptId = buildAttemptId(msg.stimulationId, nextAttempt);\n\n this.send(ws, {\n type: 'stimulation.retry.accepted',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n newStimulationAttemptId: stimulationAttemptId,\n });\n\n const payload = JSON.stringify({\n type: 'cns.stimulation.resume',\n requestId: msg.requestId,\n scopeName: stim.scopeName,\n stimulationId: msg.stimulationId,\n stimulationAttemptId,\n attemptNumber: nextAttempt,\n entry: stim.entry,\n progress: stim.progress,\n options: msg.options,\n });\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n private async handleStimulationClone(ws: WebSocket, msg: any): Promise<void> {\n const reject = (reason: string) =>\n this.send(ws, {\n type: 'stimulation.clone.rejected',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n reason,\n });\n\n const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);\n if (!stim) return reject('unknown stimulation');\n\n const appSockets = await this.resolveScopeApp(stim.scopeName);\n if (!appSockets) return reject('app not connected');\n\n const newStimulationId = buildCloneId(msg.stimulationId);\n const stimulationAttemptId = buildAttemptId(newStimulationId, 1);\n\n this.send(ws, {\n type: 'stimulation.clone.accepted',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n newStimulationId,\n newStimulationAttemptId: stimulationAttemptId,\n });\n\n const payload = JSON.stringify({\n type: 'cns.stimulation.launch',\n requestId: msg.requestId,\n scopeName: stim.scopeName,\n stimulationId: newStimulationId,\n stimulationAttemptId,\n entry: stim.entry,\n options: msg.options,\n });\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Broadcast helpers ────────────────────────────────────────────────────\n\n private broadcast(message: object): void {\n this.broadcastRaw(JSON.stringify(message));\n }\n\n private broadcastRaw(payload: string): void {\n for (const client of this.uiClients) {\n try {\n if ((client as any).readyState === 1) client.send(payload);\n } catch {}\n }\n }\n\n private send(ws: WebSocket, message: object): void {\n try {\n if ((ws as any).readyState === 1) ws.send(JSON.stringify(message));\n } catch {}\n }\n\n // ─── Metrics ──────────────────────────────────────────────────────────────\n\n private startMetrics(): void {\n if (this.metricsTimer) return;\n this.metricsTimer = setInterval(() => {\n const mem = process.memoryUsage();\n const now = Date.now();\n const cpu = process.cpuUsage(this.lastCpuUsage);\n const elapsed = (now - this.lastCpuTime) * 1000;\n const cpuPercent = elapsed > 0 ? ((cpu.user + cpu.system) / elapsed) * 100 : 0;\n this.lastCpuUsage = process.cpuUsage();\n this.lastCpuTime = now;\n\n this.broadcast({\n type: 'server.metrics',\n timestamp: now,\n rssMB: mem.rss / 1024 / 1024,\n heapUsedMB: mem.heapUsed / 1024 / 1024,\n heapTotalMB: mem.heapTotal / 1024 / 1024,\n cpuPercent,\n });\n }, 1000);\n }\n\n private stopMetrics(): void {\n if (this.metricsTimer) {\n clearInterval(this.metricsTimer);\n this.metricsTimer = undefined;\n }\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TCNSAppDTO, TCNSTopologyMessageDTO } from '@cnstra/devtools-dto';
|
|
2
|
+
import type { ICNSStimulationRepository } from '@cnstra/persist';
|
|
2
3
|
import { WebSocket } from 'ws';
|
|
3
|
-
export type
|
|
4
|
+
export type TCNSTopologySnapshotDTO = Omit<TCNSTopologyMessageDTO, 'type'>;
|
|
4
5
|
export interface ICNSDevToolsServerRepository {
|
|
5
|
-
upsertApp(app:
|
|
6
|
-
listApps():
|
|
7
|
-
saveTopology(snapshot:
|
|
8
|
-
getTopology(cnsId?: string):
|
|
9
|
-
saveStimulation(stimulation: CNSDTOStimulation): void | Promise<void>;
|
|
10
|
-
completeStimulation(stimulationId: string, completedAt: number, hopCount: number, hasError: boolean): void | Promise<void>;
|
|
11
|
-
getStimulations(appId: string, filter: CNSDTOStimulationFilter): {
|
|
12
|
-
items: CNSDTOStimulation[];
|
|
13
|
-
total: number;
|
|
14
|
-
} | Promise<{
|
|
15
|
-
items: CNSDTOStimulation[];
|
|
16
|
-
total: number;
|
|
17
|
-
}>;
|
|
18
|
-
saveHop(hop: CNSDTOHop): void | Promise<void>;
|
|
19
|
-
getHops(stimulationId: string): CNSDTOHop[] | Promise<CNSDTOHop[]>;
|
|
6
|
+
upsertApp(app: TCNSAppDTO): void | Promise<void>;
|
|
7
|
+
listApps(): TCNSAppDTO[] | Promise<TCNSAppDTO[]>;
|
|
8
|
+
saveTopology(snapshot: TCNSTopologySnapshotDTO): void | Promise<void>;
|
|
9
|
+
getTopology(cnsId?: string): TCNSTopologySnapshotDTO[] | Promise<TCNSTopologySnapshotDTO[]>;
|
|
20
10
|
addCnsToApp(appId: string, cnsId: string): void | Promise<void>;
|
|
21
11
|
getCnsByApp(appId: string): string[] | Promise<string[]>;
|
|
22
12
|
findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;
|
|
@@ -32,23 +22,51 @@ export declare class CNSDevToolsServer {
|
|
|
32
22
|
private metricsTimer?;
|
|
33
23
|
private lastCpuUsage;
|
|
34
24
|
private lastCpuTime;
|
|
35
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Name-based resumable store (Stimulation/Attempt/Task). Written from the three
|
|
27
|
+
* `cns.stimulation*` batch items the producer emits when `trackStimulations` is on.
|
|
28
|
+
* Defaults to in-memory; the example-app can inject a Postgres store.
|
|
29
|
+
*/
|
|
30
|
+
private readonly stimulationRepository;
|
|
31
|
+
constructor(repository: ICNSDevToolsServerRepository, stimulationRepository?: ICNSStimulationRepository);
|
|
36
32
|
handleMessage(ws: WebSocket, raw: unknown): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Explorer protocol (bucket-shaped). The roster row = resumable state minus
|
|
35
|
+
* `progress`, plus envelope summaries derived from the run's attempts.
|
|
36
|
+
*
|
|
37
|
+
* Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the
|
|
38
|
+
* Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we
|
|
39
|
+
* fall back to the shared reference derivation over the reader — identical
|
|
40
|
+
* results, only the work moves. Roster shapes are structurally the wire DTOs,
|
|
41
|
+
* so no mapping is needed at the boundary.
|
|
42
|
+
*/
|
|
43
|
+
private toRosterFilter;
|
|
44
|
+
private handleStimulationsList;
|
|
45
|
+
private handleStimulationsFacets;
|
|
46
|
+
private handleStimulationDetail;
|
|
47
|
+
/**
|
|
48
|
+
* The topology/signal views' bulk feed: a bounded, newest-first set of runs as
|
|
49
|
+
* FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by
|
|
50
|
+
* id. Replaces the retired nested `cns.stimulations.query` tree — same data, no
|
|
51
|
+
* server-side join, no client-side flatten.
|
|
52
|
+
*/
|
|
53
|
+
private handleStimulationsSnapshot;
|
|
37
54
|
addClient(ws: WebSocket): void;
|
|
38
55
|
removeClient(ws: WebSocket): void;
|
|
39
|
-
getActiveApps(): Promise<
|
|
56
|
+
getActiveApps(): Promise<TCNSAppDTO[]>;
|
|
40
57
|
stop(): void;
|
|
41
58
|
private handleAppBatchItem;
|
|
59
|
+
/** The name-based resumable store, for tests/integrations that want to read it back. */
|
|
60
|
+
getStimulationRepository(): ICNSStimulationRepository;
|
|
42
61
|
private handleTopology;
|
|
43
|
-
private handleStimulationStarted;
|
|
44
|
-
private handleStimulationHop;
|
|
45
|
-
private handleStimulationCompleted;
|
|
46
62
|
private handleClientConnect;
|
|
47
63
|
private handleAppsQuery;
|
|
48
64
|
private handleTopologyQuery;
|
|
49
|
-
private handleStimulationsQuery;
|
|
50
|
-
private handleHopsQuery;
|
|
51
65
|
private handleReplayStart;
|
|
66
|
+
/** Resolve the app that owns a stimulation's scope, or reject if unreachable. */
|
|
67
|
+
private resolveScopeApp;
|
|
68
|
+
private handleStimulationRetry;
|
|
69
|
+
private handleStimulationClone;
|
|
52
70
|
private broadcast;
|
|
53
71
|
private broadcastRaw;
|
|
54
72
|
private send;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,UAAU,EAGV,sBAAsB,EACzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EACR,yBAAyB,EAE5B,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAI/B,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;AAE3E,MAAM,WAAW,4BAA4B;IAEzC,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,QAAQ,IAAI,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAGjD,YAAY,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,uBAAuB,EAAE,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAC;IAG5F,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACjF;AAID,qBAAa,iBAAiB;IAoBtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAnB/B,0CAA0C;IAC1C,OAAO,CAAC,UAAU,CAAgC;IAClD,oEAAoE;IACpE,OAAO,CAAC,YAAY,CAAqC;IACzD,sDAAsD;IACtD,OAAO,CAAC,SAAS,CAAwB;IAEzC,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,WAAW,CAAc;IAEjC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA4B;gBAG7C,UAAU,EAAE,4BAA4B,EACzD,qBAAqB,GAAE,yBAAkE;IAOvF,aAAa,CACf,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC;IA8DhB;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;YAOR,sBAAsB;YAuBtB,wBAAwB;YAkBxB,uBAAuB;IAsBrC;;;;;OAKG;YACW,0BAA0B;IA8BxC,SAAS,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAK9B,YAAY,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAM3B,aAAa,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAI5C,IAAI,IAAI,IAAI;YAME,kBAAkB;IAkBhC,wFAAwF;IACxF,wBAAwB,IAAI,yBAAyB;YAIvC,cAAc;YAmCd,mBAAmB;YAUnB,eAAe;YAKf,mBAAmB;YAWnB,iBAAiB;IAyB/B,iFAAiF;YACnE,eAAe;YAUf,sBAAsB;YA4CtB,sBAAsB;IA0CpC,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,YAAY;IAsBpB,OAAO,CAAC,WAAW;CAMtB"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CNSAppBatchMessageDTOSchema, CNSClientMessageDTOSchema, buildAttemptId, buildCloneId } from '@cnstra/devtools-dto';
|
|
2
|
+
import { CNSInMemoryStimulationRepository, supportsRosterQuery, keysetPage, loadRosterEntries, computeRosterFacets } from '@cnstra/persist';
|
|
2
3
|
|
|
3
4
|
// src/index.ts
|
|
4
5
|
var CNSDevToolsServer = class {
|
|
5
|
-
constructor(repository) {
|
|
6
|
+
constructor(repository, stimulationRepository = new CNSInMemoryStimulationRepository()) {
|
|
6
7
|
this.repository = repository;
|
|
7
8
|
/** WebSocket → appId (for app clients) */
|
|
8
9
|
this.appSockets = /* @__PURE__ */ new Map();
|
|
@@ -12,6 +13,7 @@ var CNSDevToolsServer = class {
|
|
|
12
13
|
this.uiClients = /* @__PURE__ */ new Set();
|
|
13
14
|
this.lastCpuUsage = process.cpuUsage();
|
|
14
15
|
this.lastCpuTime = Date.now();
|
|
16
|
+
this.stimulationRepository = stimulationRepository;
|
|
15
17
|
}
|
|
16
18
|
// ─── Message entry point ─────────────────────────────────────────────────
|
|
17
19
|
async handleMessage(ws, raw) {
|
|
@@ -25,14 +27,14 @@ var CNSDevToolsServer = class {
|
|
|
25
27
|
if (!data || typeof data !== "object" || !("type" in data)) return;
|
|
26
28
|
const type = data.type;
|
|
27
29
|
if (type === "batch") {
|
|
28
|
-
const result =
|
|
30
|
+
const result = CNSAppBatchMessageDTOSchema.safeParse(data);
|
|
29
31
|
if (!result.success) return;
|
|
30
32
|
for (const item of result.data.items) {
|
|
31
33
|
await this.handleAppBatchItem(ws, item);
|
|
32
34
|
}
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
|
-
const clientResult =
|
|
37
|
+
const clientResult = CNSClientMessageDTOSchema.safeParse(data);
|
|
36
38
|
if (!clientResult.success) return;
|
|
37
39
|
const msg = clientResult.data;
|
|
38
40
|
switch (msg.type) {
|
|
@@ -45,17 +47,119 @@ var CNSDevToolsServer = class {
|
|
|
45
47
|
case "topology.query":
|
|
46
48
|
await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);
|
|
47
49
|
break;
|
|
48
|
-
case "stimulations.query":
|
|
49
|
-
await this.handleStimulationsQuery(ws, msg);
|
|
50
|
-
break;
|
|
51
|
-
case "hops.query":
|
|
52
|
-
await this.handleHopsQuery(ws, msg);
|
|
53
|
-
break;
|
|
54
50
|
case "replay.start":
|
|
55
51
|
await this.handleReplayStart(ws, msg);
|
|
56
52
|
break;
|
|
53
|
+
case "stimulation.retry":
|
|
54
|
+
await this.handleStimulationRetry(ws, msg);
|
|
55
|
+
break;
|
|
56
|
+
case "stimulation.clone":
|
|
57
|
+
await this.handleStimulationClone(ws, msg);
|
|
58
|
+
break;
|
|
59
|
+
case "cns.stimulations.list":
|
|
60
|
+
await this.handleStimulationsList(ws, msg);
|
|
61
|
+
break;
|
|
62
|
+
case "cns.stimulations.facets":
|
|
63
|
+
await this.handleStimulationsFacets(ws, msg);
|
|
64
|
+
break;
|
|
65
|
+
case "cns.stimulations.snapshot":
|
|
66
|
+
await this.handleStimulationsSnapshot(ws, msg);
|
|
67
|
+
break;
|
|
68
|
+
case "cns.stimulation.detail":
|
|
69
|
+
await this.handleStimulationDetail(ws, msg);
|
|
70
|
+
break;
|
|
57
71
|
}
|
|
58
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Explorer protocol (bucket-shaped). The roster row = resumable state minus
|
|
75
|
+
* `progress`, plus envelope summaries derived from the run's attempts.
|
|
76
|
+
*
|
|
77
|
+
* Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the
|
|
78
|
+
* Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we
|
|
79
|
+
* fall back to the shared reference derivation over the reader — identical
|
|
80
|
+
* results, only the work moves. Roster shapes are structurally the wire DTOs,
|
|
81
|
+
* so no mapping is needed at the boundary.
|
|
82
|
+
*/
|
|
83
|
+
toRosterFilter(scopeName, filter) {
|
|
84
|
+
return { ...filter ?? {}, ...scopeName ? { scopeName } : {} };
|
|
85
|
+
}
|
|
86
|
+
async handleStimulationsList(ws, msg) {
|
|
87
|
+
const filter = this.toRosterFilter(msg.scopeName, msg.filter);
|
|
88
|
+
const limit = typeof msg.limit === "number" ? msg.limit : 50;
|
|
89
|
+
const opts = { cursor: msg.cursor, limit };
|
|
90
|
+
const store = this.stimulationRepository;
|
|
91
|
+
const page = supportsRosterQuery(store) ? await store.queryRoster(filter, opts) : keysetPage(
|
|
92
|
+
(await loadRosterEntries(store, filter)).map((e) => e.row),
|
|
93
|
+
opts
|
|
94
|
+
);
|
|
95
|
+
this.send(ws, {
|
|
96
|
+
type: "cns.stimulations.list.result",
|
|
97
|
+
requestId: msg.requestId,
|
|
98
|
+
stimulations: page.rows,
|
|
99
|
+
nextCursor: page.nextCursor,
|
|
100
|
+
total: page.total
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async handleStimulationsFacets(ws, msg) {
|
|
104
|
+
const filter = this.toRosterFilter(msg.scopeName, msg.filter);
|
|
105
|
+
const store = this.stimulationRepository;
|
|
106
|
+
const facets = supportsRosterQuery(store) ? await store.queryFacets(filter, { metricPath: msg.metricPath }) : computeRosterFacets(await loadRosterEntries(store, filter), {
|
|
107
|
+
metricPath: msg.metricPath
|
|
108
|
+
});
|
|
109
|
+
this.send(ws, {
|
|
110
|
+
type: "cns.stimulations.facets.result",
|
|
111
|
+
requestId: msg.requestId,
|
|
112
|
+
facets
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async handleStimulationDetail(ws, msg) {
|
|
116
|
+
const attempts = await this.stimulationRepository.getAttempts(
|
|
117
|
+
msg.stimulationId
|
|
118
|
+
);
|
|
119
|
+
const tasks = [];
|
|
120
|
+
for (const a of attempts) {
|
|
121
|
+
const ts = await this.stimulationRepository.getTasks(
|
|
122
|
+
a.stimulationAttemptId
|
|
123
|
+
);
|
|
124
|
+
tasks.push(...ts);
|
|
125
|
+
}
|
|
126
|
+
this.send(ws, {
|
|
127
|
+
type: "cns.stimulation.detail.result",
|
|
128
|
+
requestId: msg.requestId,
|
|
129
|
+
attempts,
|
|
130
|
+
tasks
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The topology/signal views' bulk feed: a bounded, newest-first set of runs as
|
|
135
|
+
* FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by
|
|
136
|
+
* id. Replaces the retired nested `cns.stimulations.query` tree — same data, no
|
|
137
|
+
* server-side join, no client-side flatten.
|
|
138
|
+
*/
|
|
139
|
+
async handleStimulationsSnapshot(ws, msg) {
|
|
140
|
+
const filter = this.toRosterFilter(msg.scopeName, msg.filter);
|
|
141
|
+
const store = this.stimulationRepository;
|
|
142
|
+
const limit = typeof msg.limit === "number" ? msg.limit : 200;
|
|
143
|
+
const page = supportsRosterQuery(store) ? await store.queryRoster(filter, { limit }) : keysetPage(
|
|
144
|
+
(await loadRosterEntries(store, filter)).map((e) => e.row),
|
|
145
|
+
{ limit }
|
|
146
|
+
);
|
|
147
|
+
const attempts = [];
|
|
148
|
+
const tasks = [];
|
|
149
|
+
for (const row of page.rows) {
|
|
150
|
+
const as = await store.getAttempts(row.stimulationId);
|
|
151
|
+
attempts.push(...as);
|
|
152
|
+
for (const a of as)
|
|
153
|
+
tasks.push(...await store.getTasks(a.stimulationAttemptId));
|
|
154
|
+
}
|
|
155
|
+
this.send(ws, {
|
|
156
|
+
type: "cns.stimulations.snapshot.result",
|
|
157
|
+
requestId: msg.requestId,
|
|
158
|
+
stimulations: page.rows,
|
|
159
|
+
attempts,
|
|
160
|
+
tasks
|
|
161
|
+
});
|
|
162
|
+
}
|
|
59
163
|
addClient(ws) {
|
|
60
164
|
this.uiClients.add(ws);
|
|
61
165
|
if (this.uiClients.size === 1) this.startMetrics();
|
|
@@ -77,17 +181,22 @@ var CNSDevToolsServer = class {
|
|
|
77
181
|
case "topology":
|
|
78
182
|
await this.handleTopology(ws, item);
|
|
79
183
|
break;
|
|
80
|
-
|
|
81
|
-
|
|
184
|
+
// ── Name-based resumable model ──
|
|
185
|
+
case "cns.stimulation":
|
|
186
|
+
await this.stimulationRepository.saveStimulation(item.data);
|
|
82
187
|
break;
|
|
83
|
-
case "stimulation.
|
|
84
|
-
await this.
|
|
188
|
+
case "cns.stimulation.attempt":
|
|
189
|
+
await this.stimulationRepository.saveAttempt(item.data);
|
|
85
190
|
break;
|
|
86
|
-
case "stimulation.
|
|
87
|
-
await this.
|
|
191
|
+
case "cns.stimulation.task":
|
|
192
|
+
await this.stimulationRepository.appendTask(item.data);
|
|
88
193
|
break;
|
|
89
194
|
}
|
|
90
195
|
}
|
|
196
|
+
/** The name-based resumable store, for tests/integrations that want to read it back. */
|
|
197
|
+
getStimulationRepository() {
|
|
198
|
+
return this.stimulationRepository;
|
|
199
|
+
}
|
|
91
200
|
async handleTopology(ws, msg) {
|
|
92
201
|
const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;
|
|
93
202
|
const now = Date.now();
|
|
@@ -112,35 +221,6 @@ var CNSDevToolsServer = class {
|
|
|
112
221
|
this.broadcast({ type: "app.connected", app, topology: { cnsId, neurons, collaterals, dendrites } });
|
|
113
222
|
this.broadcastRaw(JSON.stringify(topologyBroadcast));
|
|
114
223
|
}
|
|
115
|
-
async handleStimulationStarted(ws, stimulation) {
|
|
116
|
-
const appId = this.appSockets.get(ws);
|
|
117
|
-
if (appId) {
|
|
118
|
-
const apps = await this.repository.listApps();
|
|
119
|
-
const app = apps.find((a) => a.id === appId);
|
|
120
|
-
if (app) await this.repository.upsertApp({ ...app, lastSeenAt: Date.now() });
|
|
121
|
-
}
|
|
122
|
-
await this.repository.saveStimulation(stimulation);
|
|
123
|
-
this.broadcast({ type: "stimulation.started", stimulation });
|
|
124
|
-
}
|
|
125
|
-
async handleStimulationHop(hop) {
|
|
126
|
-
await this.repository.saveHop(hop);
|
|
127
|
-
this.broadcast({ type: "stimulation.hop", hop });
|
|
128
|
-
}
|
|
129
|
-
async handleStimulationCompleted(item) {
|
|
130
|
-
await this.repository.completeStimulation(
|
|
131
|
-
item.stimulationId,
|
|
132
|
-
item.completedAt,
|
|
133
|
-
item.hopCount,
|
|
134
|
-
item.hasError
|
|
135
|
-
);
|
|
136
|
-
this.broadcast({
|
|
137
|
-
type: "stimulation.completed",
|
|
138
|
-
stimulationId: item.stimulationId,
|
|
139
|
-
completedAt: item.completedAt,
|
|
140
|
-
hopCount: item.hopCount,
|
|
141
|
-
hasError: item.hasError
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
224
|
// ─── UI client query handlers ─────────────────────────────────────────────
|
|
145
225
|
async handleClientConnect(ws) {
|
|
146
226
|
this.addClient(ws);
|
|
@@ -160,25 +240,6 @@ var CNSDevToolsServer = class {
|
|
|
160
240
|
if (appId) snapshots = snapshots.filter((s) => s.appId === appId);
|
|
161
241
|
this.send(ws, { type: "topology.result", requestId, snapshots });
|
|
162
242
|
}
|
|
163
|
-
async handleStimulationsQuery(ws, msg) {
|
|
164
|
-
const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : void 0);
|
|
165
|
-
if (!appId) {
|
|
166
|
-
this.send(ws, { type: "stimulations.result", requestId: msg.requestId, items: [], total: 0, offset: 0 });
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
const { items, total } = await this.repository.getStimulations(appId, msg.filter ?? {});
|
|
170
|
-
this.send(ws, {
|
|
171
|
-
type: "stimulations.result",
|
|
172
|
-
requestId: msg.requestId,
|
|
173
|
-
items,
|
|
174
|
-
total,
|
|
175
|
-
offset: msg.filter?.offset ?? 0
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
async handleHopsQuery(ws, msg) {
|
|
179
|
-
const items = await this.repository.getHops(msg.stimulationId);
|
|
180
|
-
this.send(ws, { type: "hops.result", requestId: msg.requestId, items });
|
|
181
|
-
}
|
|
182
243
|
async handleReplayStart(ws, msg) {
|
|
183
244
|
const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : void 0);
|
|
184
245
|
const appSockets = appId ? this.appWsByAppId.get(appId) : void 0;
|
|
@@ -196,6 +257,94 @@ var CNSDevToolsServer = class {
|
|
|
196
257
|
}
|
|
197
258
|
}
|
|
198
259
|
}
|
|
260
|
+
// ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────
|
|
261
|
+
//
|
|
262
|
+
// The UI's request is thin (a stimulationId). The CNS + name→ref registry live in
|
|
263
|
+
// the APP, so the server can't execute it: it ENRICHES from the resumable store (the
|
|
264
|
+
// stored entry + progress + a server-assigned attempt identity) and forwards a
|
|
265
|
+
// resume/launch command to the owning app, routed by scopeName (= the cns id).
|
|
266
|
+
/** Resolve the app that owns a stimulation's scope, or reject if unreachable. */
|
|
267
|
+
async resolveScopeApp(scopeName) {
|
|
268
|
+
const appId = scopeName ? await this.repository.findAppByCns(scopeName) : void 0;
|
|
269
|
+
const sockets = appId ? this.appWsByAppId.get(appId) : void 0;
|
|
270
|
+
return sockets && sockets.size > 0 ? sockets : void 0;
|
|
271
|
+
}
|
|
272
|
+
async handleStimulationRetry(ws, msg) {
|
|
273
|
+
const reject = (reason) => this.send(ws, {
|
|
274
|
+
type: "stimulation.retry.rejected",
|
|
275
|
+
requestId: msg.requestId,
|
|
276
|
+
stimulationId: msg.stimulationId,
|
|
277
|
+
reason
|
|
278
|
+
});
|
|
279
|
+
const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);
|
|
280
|
+
if (!stim) return reject("unknown stimulation");
|
|
281
|
+
if (stim.progress.tasks.length === 0)
|
|
282
|
+
return reject("nothing to resume (empty frontier)");
|
|
283
|
+
const appSockets = await this.resolveScopeApp(stim.scopeName);
|
|
284
|
+
if (!appSockets) return reject("app not connected");
|
|
285
|
+
const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);
|
|
286
|
+
const nextAttempt = attempts.length + 1;
|
|
287
|
+
const stimulationAttemptId = buildAttemptId(msg.stimulationId, nextAttempt);
|
|
288
|
+
this.send(ws, {
|
|
289
|
+
type: "stimulation.retry.accepted",
|
|
290
|
+
requestId: msg.requestId,
|
|
291
|
+
stimulationId: msg.stimulationId,
|
|
292
|
+
newStimulationAttemptId: stimulationAttemptId
|
|
293
|
+
});
|
|
294
|
+
const payload = JSON.stringify({
|
|
295
|
+
type: "cns.stimulation.resume",
|
|
296
|
+
requestId: msg.requestId,
|
|
297
|
+
scopeName: stim.scopeName,
|
|
298
|
+
stimulationId: msg.stimulationId,
|
|
299
|
+
stimulationAttemptId,
|
|
300
|
+
attemptNumber: nextAttempt,
|
|
301
|
+
entry: stim.entry,
|
|
302
|
+
progress: stim.progress,
|
|
303
|
+
options: msg.options
|
|
304
|
+
});
|
|
305
|
+
for (const appWs of appSockets) {
|
|
306
|
+
try {
|
|
307
|
+
appWs.send(payload);
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async handleStimulationClone(ws, msg) {
|
|
313
|
+
const reject = (reason) => this.send(ws, {
|
|
314
|
+
type: "stimulation.clone.rejected",
|
|
315
|
+
requestId: msg.requestId,
|
|
316
|
+
stimulationId: msg.stimulationId,
|
|
317
|
+
reason
|
|
318
|
+
});
|
|
319
|
+
const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);
|
|
320
|
+
if (!stim) return reject("unknown stimulation");
|
|
321
|
+
const appSockets = await this.resolveScopeApp(stim.scopeName);
|
|
322
|
+
if (!appSockets) return reject("app not connected");
|
|
323
|
+
const newStimulationId = buildCloneId(msg.stimulationId);
|
|
324
|
+
const stimulationAttemptId = buildAttemptId(newStimulationId, 1);
|
|
325
|
+
this.send(ws, {
|
|
326
|
+
type: "stimulation.clone.accepted",
|
|
327
|
+
requestId: msg.requestId,
|
|
328
|
+
stimulationId: msg.stimulationId,
|
|
329
|
+
newStimulationId,
|
|
330
|
+
newStimulationAttemptId: stimulationAttemptId
|
|
331
|
+
});
|
|
332
|
+
const payload = JSON.stringify({
|
|
333
|
+
type: "cns.stimulation.launch",
|
|
334
|
+
requestId: msg.requestId,
|
|
335
|
+
scopeName: stim.scopeName,
|
|
336
|
+
stimulationId: newStimulationId,
|
|
337
|
+
stimulationAttemptId,
|
|
338
|
+
entry: stim.entry,
|
|
339
|
+
options: msg.options
|
|
340
|
+
});
|
|
341
|
+
for (const appWs of appSockets) {
|
|
342
|
+
try {
|
|
343
|
+
appWs.send(payload);
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
199
348
|
// ─── Broadcast helpers ────────────────────────────────────────────────────
|
|
200
349
|
broadcast(message) {
|
|
201
350
|
this.broadcastRaw(JSON.stringify(message));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n CNSDTOApp,\n CNSDTOAppBatchMessageSchema,\n CNSDTOClientMessageSchema,\n CNSDTOStimulation,\n CNSDTOStimulationFilter,\n CNSDTOHop,\n CNSDTOTopologyMessage,\n} from '@cnstra/devtools-dto';\nimport { WebSocket } from 'ws';\n\n// ─── Repository interface ─────────────────────────────────────────────────────\n\nexport type CNSDTOTopologySnapshot = Omit<CNSDTOTopologyMessage, 'type'>;\n\nexport interface ICNSDevToolsServerRepository {\n // Apps\n upsertApp(app: CNSDTOApp): void | Promise<void>;\n listApps(): CNSDTOApp[] | Promise<CNSDTOApp[]>;\n\n // Topology\n saveTopology(snapshot: CNSDTOTopologySnapshot): void | Promise<void>;\n getTopology(cnsId?: string): CNSDTOTopologySnapshot[] | Promise<CNSDTOTopologySnapshot[]>;\n\n // Stimulations\n saveStimulation(stimulation: CNSDTOStimulation): void | Promise<void>;\n completeStimulation(\n stimulationId: string,\n completedAt: number,\n hopCount: number,\n hasError: boolean\n ): void | Promise<void>;\n getStimulations(\n appId: string,\n filter: CNSDTOStimulationFilter\n ): { items: CNSDTOStimulation[]; total: number } | Promise<{ items: CNSDTOStimulation[]; total: number }>;\n\n // Hops\n saveHop(hop: CNSDTOHop): void | Promise<void>;\n getHops(stimulationId: string): CNSDTOHop[] | Promise<CNSDTOHop[]>;\n\n // CNS ↔ App mapping\n addCnsToApp(appId: string, cnsId: string): void | Promise<void>;\n getCnsByApp(appId: string): string[] | Promise<string[]>;\n findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;\n}\n\n// ─── Server ───────────────────────────────────────────────────────────────────\n\nexport class CNSDevToolsServer {\n /** WebSocket → appId (for app clients) */\n private appSockets = new Map<WebSocket, string>();\n /** appId → WebSocket[] (one app may have multiple CNS instances) */\n private appWsByAppId = new Map<string, Set<WebSocket>>();\n /** WebSocket clients that registered as UI clients */\n private uiClients = new Set<WebSocket>();\n\n private metricsTimer?: NodeJS.Timeout;\n private lastCpuUsage = process.cpuUsage();\n private lastCpuTime = Date.now();\n\n constructor(private readonly repository: ICNSDevToolsServerRepository) {}\n\n // ─── Message entry point ─────────────────────────────────────────────────\n\n async handleMessage(\n ws: WebSocket,\n raw: unknown\n ): Promise<void> {\n if (typeof raw !== 'string' && typeof raw !== 'object') return;\n\n let data: unknown;\n try {\n data = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } catch {\n return;\n }\n\n if (!data || typeof data !== 'object' || !('type' in data)) return;\n const type = (data as any).type as string;\n\n // App batch (topology + stimulation events)\n if (type === 'batch') {\n const result = CNSDTOAppBatchMessageSchema.safeParse(data);\n if (!result.success) return;\n for (const item of result.data.items) {\n await this.handleAppBatchItem(ws, item);\n }\n return;\n }\n\n // UI client message\n const clientResult = CNSDTOClientMessageSchema.safeParse(data);\n if (!clientResult.success) return;\n const msg = clientResult.data;\n\n switch (msg.type) {\n case 'client.connect':\n await this.handleClientConnect(ws);\n break;\n case 'apps.query':\n await this.handleAppsQuery(ws, msg.requestId);\n break;\n case 'topology.query':\n await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);\n break;\n case 'stimulations.query':\n await this.handleStimulationsQuery(ws, msg);\n break;\n case 'hops.query':\n await this.handleHopsQuery(ws, msg);\n break;\n case 'replay.start':\n await this.handleReplayStart(ws, msg);\n break;\n }\n }\n\n addClient(ws: WebSocket): void {\n this.uiClients.add(ws);\n if (this.uiClients.size === 1) this.startMetrics();\n }\n\n removeClient(ws: WebSocket): void {\n this.uiClients.delete(ws);\n this.appSockets.delete(ws);\n if (this.uiClients.size === 0) this.stopMetrics();\n }\n\n async getActiveApps(): Promise<CNSDTOApp[]> {\n return this.repository.listApps();\n }\n\n stop(): void {\n this.stopMetrics();\n }\n\n // ─── App batch item handlers ──────────────────────────────────────────────\n\n private async handleAppBatchItem(ws: WebSocket, item: any): Promise<void> {\n switch (item.type) {\n case 'topology':\n await this.handleTopology(ws, item);\n break;\n case 'stimulation.started':\n await this.handleStimulationStarted(ws, item.stimulation);\n break;\n case 'stimulation.hop':\n await this.handleStimulationHop(item.hop);\n break;\n case 'stimulation.completed':\n await this.handleStimulationCompleted(item);\n break;\n }\n }\n\n private async handleTopology(ws: WebSocket, msg: any): Promise<void> {\n const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;\n\n const now = Date.now();\n const app: CNSDTOApp = {\n id: appId,\n name: appName,\n version,\n connectedAt: now,\n lastSeenAt: now,\n };\n\n await this.repository.upsertApp(app);\n await this.repository.addCnsToApp(appId, cnsId);\n await this.repository.saveTopology({ appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites });\n\n // Track app socket\n this.appSockets.set(ws, appId);\n if (!this.appWsByAppId.has(appId)) this.appWsByAppId.set(appId, new Set());\n this.appWsByAppId.get(appId)!.add(ws);\n\n // Broadcast topology + app connected to UI clients\n const topologyBroadcast = {\n type: 'topology.result' as const,\n requestId: '__broadcast__',\n snapshots: await this.repository.getTopology(cnsId),\n };\n\n this.broadcast({ type: 'app.connected', app, topology: { cnsId, neurons, collaterals, dendrites } });\n // Also send topology snapshot so UI can refresh graph\n this.broadcastRaw(JSON.stringify(topologyBroadcast));\n }\n\n private async handleStimulationStarted(ws: WebSocket, stimulation: CNSDTOStimulation): Promise<void> {\n // Update lastSeenAt for the app\n const appId = this.appSockets.get(ws);\n if (appId) {\n const apps = await this.repository.listApps();\n const app = apps.find(a => a.id === appId);\n if (app) await this.repository.upsertApp({ ...app, lastSeenAt: Date.now() });\n }\n\n await this.repository.saveStimulation(stimulation);\n this.broadcast({ type: 'stimulation.started', stimulation });\n }\n\n private async handleStimulationHop(hop: CNSDTOHop): Promise<void> {\n await this.repository.saveHop(hop);\n this.broadcast({ type: 'stimulation.hop', hop });\n }\n\n private async handleStimulationCompleted(item: any): Promise<void> {\n await this.repository.completeStimulation(\n item.stimulationId,\n item.completedAt,\n item.hopCount,\n item.hasError\n );\n this.broadcast({\n type: 'stimulation.completed',\n stimulationId: item.stimulationId,\n completedAt: item.completedAt,\n hopCount: item.hopCount,\n hasError: item.hasError,\n });\n }\n\n // ─── UI client query handlers ─────────────────────────────────────────────\n\n private async handleClientConnect(ws: WebSocket): Promise<void> {\n this.addClient(ws);\n this.send(ws, { type: 'apps.result', requestId: '__init__', items: await this.repository.listApps() });\n this.send(ws, {\n type: 'topology.result',\n requestId: '__init__',\n snapshots: await this.repository.getTopology(),\n });\n }\n\n private async handleAppsQuery(ws: WebSocket, requestId: string): Promise<void> {\n const items = await this.repository.listApps();\n this.send(ws, { type: 'apps.result', requestId, items });\n }\n\n private async handleTopologyQuery(\n ws: WebSocket,\n requestId: string,\n cnsId?: string,\n appId?: string\n ): Promise<void> {\n let snapshots = await this.repository.getTopology(cnsId);\n if (appId) snapshots = snapshots.filter(s => s.appId === appId);\n this.send(ws, { type: 'topology.result', requestId, snapshots });\n }\n\n private async handleStimulationsQuery(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n\n if (!appId) {\n this.send(ws, { type: 'stimulations.result', requestId: msg.requestId, items: [], total: 0, offset: 0 });\n return;\n }\n\n const { items, total } = await this.repository.getStimulations(appId, msg.filter ?? {});\n this.send(ws, {\n type: 'stimulations.result',\n requestId: msg.requestId,\n items,\n total,\n offset: msg.filter?.offset ?? 0,\n });\n }\n\n private async handleHopsQuery(ws: WebSocket, msg: any): Promise<void> {\n const items = await this.repository.getHops(msg.stimulationId);\n this.send(ws, { type: 'hops.result', requestId: msg.requestId, items });\n }\n\n private async handleReplayStart(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n const appSockets = appId ? this.appWsByAppId.get(appId) : undefined;\n\n if (!appSockets || appSockets.size === 0) {\n this.send(ws, { type: 'replay.rejected', replayId: msg.replayId, reason: 'App not connected' });\n return;\n }\n\n const newStimulationId = `${msg.stimulationId}-replay-${Date.now()}`;\n this.send(ws, { type: 'replay.accepted', replayId: msg.replayId, newStimulationId });\n\n const payload = JSON.stringify(msg);\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Broadcast helpers ────────────────────────────────────────────────────\n\n private broadcast(message: object): void {\n this.broadcastRaw(JSON.stringify(message));\n }\n\n private broadcastRaw(payload: string): void {\n for (const client of this.uiClients) {\n try {\n if ((client as any).readyState === 1) client.send(payload);\n } catch {}\n }\n }\n\n private send(ws: WebSocket, message: object): void {\n try {\n if ((ws as any).readyState === 1) ws.send(JSON.stringify(message));\n } catch {}\n }\n\n // ─── Metrics ──────────────────────────────────────────────────────────────\n\n private startMetrics(): void {\n if (this.metricsTimer) return;\n this.metricsTimer = setInterval(() => {\n const mem = process.memoryUsage();\n const now = Date.now();\n const cpu = process.cpuUsage(this.lastCpuUsage);\n const elapsed = (now - this.lastCpuTime) * 1000;\n const cpuPercent = elapsed > 0 ? ((cpu.user + cpu.system) / elapsed) * 100 : 0;\n this.lastCpuUsage = process.cpuUsage();\n this.lastCpuTime = now;\n\n this.broadcast({\n type: 'server.metrics',\n timestamp: now,\n rssMB: mem.rss / 1024 / 1024,\n heapUsedMB: mem.heapUsed / 1024 / 1024,\n heapTotalMB: mem.heapTotal / 1024 / 1024,\n cpuPercent,\n });\n }, 1000);\n }\n\n private stopMetrics(): void {\n if (this.metricsTimer) {\n clearInterval(this.metricsTimer);\n this.metricsTimer = undefined;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n TCNSAppDTO,\n CNSAppBatchMessageDTOSchema,\n CNSClientMessageDTOSchema,\n TCNSTopologyMessageDTO,\n} from '@cnstra/devtools-dto';\nimport type {\n TCNSStimulationFilter,\n TCNSStimulationTask,\n TCNSStimulationAttempt,\n} from '@cnstra/devtools-dto';\nimport { buildAttemptId, buildCloneId } from '@cnstra/devtools-dto';\nimport type {\n ICNSStimulationRepository,\n TCNSStimulationRosterFilter,\n} from '@cnstra/persist';\nimport {\n CNSInMemoryStimulationRepository,\n supportsRosterQuery,\n loadRosterEntries,\n keysetPage,\n computeRosterFacets,\n} from '@cnstra/persist';\nimport { WebSocket } from 'ws';\n\n// ─── Repository interface ─────────────────────────────────────────────────────\n\nexport type TCNSTopologySnapshotDTO = Omit<TCNSTopologyMessageDTO, 'type'>;\n\nexport interface ICNSDevToolsServerRepository {\n // Apps\n upsertApp(app: TCNSAppDTO): void | Promise<void>;\n listApps(): TCNSAppDTO[] | Promise<TCNSAppDTO[]>;\n\n // Topology\n saveTopology(snapshot: TCNSTopologySnapshotDTO): void | Promise<void>;\n getTopology(cnsId?: string): TCNSTopologySnapshotDTO[] | Promise<TCNSTopologySnapshotDTO[]>;\n\n // CNS ↔ App mapping\n addCnsToApp(appId: string, cnsId: string): void | Promise<void>;\n getCnsByApp(appId: string): string[] | Promise<string[]>;\n findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;\n}\n\n// ─── Server ───────────────────────────────────────────────────────────────────\n\nexport class CNSDevToolsServer {\n /** WebSocket → appId (for app clients) */\n private appSockets = new Map<WebSocket, string>();\n /** appId → WebSocket[] (one app may have multiple CNS instances) */\n private appWsByAppId = new Map<string, Set<WebSocket>>();\n /** WebSocket clients that registered as UI clients */\n private uiClients = new Set<WebSocket>();\n\n private metricsTimer?: NodeJS.Timeout;\n private lastCpuUsage = process.cpuUsage();\n private lastCpuTime = Date.now();\n\n /**\n * Name-based resumable store (Stimulation/Attempt/Task). Written from the three\n * `cns.stimulation*` batch items the producer emits when `trackStimulations` is on.\n * Defaults to in-memory; the example-app can inject a Postgres store.\n */\n private readonly stimulationRepository: ICNSStimulationRepository;\n\n constructor(\n private readonly repository: ICNSDevToolsServerRepository,\n stimulationRepository: ICNSStimulationRepository = new CNSInMemoryStimulationRepository()\n ) {\n this.stimulationRepository = stimulationRepository;\n }\n\n // ─── Message entry point ─────────────────────────────────────────────────\n\n async handleMessage(\n ws: WebSocket,\n raw: unknown\n ): Promise<void> {\n if (typeof raw !== 'string' && typeof raw !== 'object') return;\n\n let data: unknown;\n try {\n data = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } catch {\n return;\n }\n\n if (!data || typeof data !== 'object' || !('type' in data)) return;\n const type = (data as any).type as string;\n\n // App batch (topology + stimulation events)\n if (type === 'batch') {\n const result = CNSAppBatchMessageDTOSchema.safeParse(data);\n if (!result.success) return;\n for (const item of result.data.items) {\n await this.handleAppBatchItem(ws, item);\n }\n return;\n }\n\n // UI client message\n const clientResult = CNSClientMessageDTOSchema.safeParse(data);\n if (!clientResult.success) return;\n const msg = clientResult.data;\n\n switch (msg.type) {\n case 'client.connect':\n await this.handleClientConnect(ws);\n break;\n case 'apps.query':\n await this.handleAppsQuery(ws, msg.requestId);\n break;\n case 'topology.query':\n await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);\n break;\n case 'replay.start':\n await this.handleReplayStart(ws, msg);\n break;\n case 'stimulation.retry':\n await this.handleStimulationRetry(ws, msg);\n break;\n case 'stimulation.clone':\n await this.handleStimulationClone(ws, msg);\n break;\n case 'cns.stimulations.list':\n await this.handleStimulationsList(ws, msg);\n break;\n case 'cns.stimulations.facets':\n await this.handleStimulationsFacets(ws, msg);\n break;\n case 'cns.stimulations.snapshot':\n await this.handleStimulationsSnapshot(ws, msg);\n break;\n case 'cns.stimulation.detail':\n await this.handleStimulationDetail(ws, msg);\n break;\n }\n }\n\n /**\n * Explorer protocol (bucket-shaped). The roster row = resumable state minus\n * `progress`, plus envelope summaries derived from the run's attempts.\n *\n * Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the\n * Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we\n * fall back to the shared reference derivation over the reader — identical\n * results, only the work moves. Roster shapes are structurally the wire DTOs,\n * so no mapping is needed at the boundary.\n */\n private toRosterFilter(\n scopeName: string | undefined,\n filter: TCNSStimulationFilter | undefined\n ): TCNSStimulationRosterFilter {\n return { ...(filter ?? {}), ...(scopeName ? { scopeName } : {}) };\n }\n\n private async handleStimulationsList(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const limit = typeof msg.limit === 'number' ? msg.limit : 50;\n const opts = { cursor: msg.cursor, limit };\n const store = this.stimulationRepository;\n const page = supportsRosterQuery(store)\n ? await store.queryRoster(filter, opts)\n : keysetPage(\n (await loadRosterEntries(store, filter)).map(e => e.row),\n opts\n );\n this.send(ws, {\n type: 'cns.stimulations.list.result',\n requestId: msg.requestId,\n stimulations: page.rows,\n nextCursor: page.nextCursor,\n total: page.total,\n });\n }\n\n private async handleStimulationsFacets(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const store = this.stimulationRepository;\n const facets = supportsRosterQuery(store)\n ? await store.queryFacets(filter, { metricPath: msg.metricPath })\n : computeRosterFacets(await loadRosterEntries(store, filter), {\n metricPath: msg.metricPath,\n });\n this.send(ws, {\n type: 'cns.stimulations.facets.result',\n requestId: msg.requestId,\n facets,\n });\n }\n\n private async handleStimulationDetail(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const attempts = await this.stimulationRepository.getAttempts(\n msg.stimulationId\n );\n const tasks: TCNSStimulationTask[] = [];\n for (const a of attempts) {\n const ts = await this.stimulationRepository.getTasks(\n a.stimulationAttemptId\n );\n tasks.push(...ts);\n }\n this.send(ws, {\n type: 'cns.stimulation.detail.result',\n requestId: msg.requestId,\n attempts,\n tasks,\n });\n }\n\n /**\n * The topology/signal views' bulk feed: a bounded, newest-first set of runs as\n * FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by\n * id. Replaces the retired nested `cns.stimulations.query` tree — same data, no\n * server-side join, no client-side flatten.\n */\n private async handleStimulationsSnapshot(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const store = this.stimulationRepository;\n const limit = typeof msg.limit === 'number' ? msg.limit : 200;\n const page = supportsRosterQuery(store)\n ? await store.queryRoster(filter, { limit })\n : keysetPage(\n (await loadRosterEntries(store, filter)).map(e => e.row),\n { limit }\n );\n const attempts: TCNSStimulationAttempt[] = [];\n const tasks: TCNSStimulationTask[] = [];\n for (const row of page.rows) {\n const as = await store.getAttempts(row.stimulationId);\n attempts.push(...as);\n for (const a of as)\n tasks.push(...(await store.getTasks(a.stimulationAttemptId)));\n }\n this.send(ws, {\n type: 'cns.stimulations.snapshot.result',\n requestId: msg.requestId,\n stimulations: page.rows,\n attempts,\n tasks,\n });\n }\n\n addClient(ws: WebSocket): void {\n this.uiClients.add(ws);\n if (this.uiClients.size === 1) this.startMetrics();\n }\n\n removeClient(ws: WebSocket): void {\n this.uiClients.delete(ws);\n this.appSockets.delete(ws);\n if (this.uiClients.size === 0) this.stopMetrics();\n }\n\n async getActiveApps(): Promise<TCNSAppDTO[]> {\n return this.repository.listApps();\n }\n\n stop(): void {\n this.stopMetrics();\n }\n\n // ─── App batch item handlers ──────────────────────────────────────────────\n\n private async handleAppBatchItem(ws: WebSocket, item: any): Promise<void> {\n switch (item.type) {\n case 'topology':\n await this.handleTopology(ws, item);\n break;\n // ── Name-based resumable model ──\n case 'cns.stimulation':\n await this.stimulationRepository.saveStimulation(item.data);\n break;\n case 'cns.stimulation.attempt':\n await this.stimulationRepository.saveAttempt(item.data);\n break;\n case 'cns.stimulation.task':\n await this.stimulationRepository.appendTask(item.data);\n break;\n }\n }\n\n /** The name-based resumable store, for tests/integrations that want to read it back. */\n getStimulationRepository(): ICNSStimulationRepository {\n return this.stimulationRepository;\n }\n\n private async handleTopology(ws: WebSocket, msg: any): Promise<void> {\n const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;\n\n const now = Date.now();\n const app: TCNSAppDTO = {\n id: appId,\n name: appName,\n version,\n connectedAt: now,\n lastSeenAt: now,\n };\n\n await this.repository.upsertApp(app);\n await this.repository.addCnsToApp(appId, cnsId);\n await this.repository.saveTopology({ appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites });\n\n // Track app socket\n this.appSockets.set(ws, appId);\n if (!this.appWsByAppId.has(appId)) this.appWsByAppId.set(appId, new Set());\n this.appWsByAppId.get(appId)!.add(ws);\n\n // Broadcast topology + app connected to UI clients\n const topologyBroadcast = {\n type: 'topology.result' as const,\n requestId: '__broadcast__',\n snapshots: await this.repository.getTopology(cnsId),\n };\n\n this.broadcast({ type: 'app.connected', app, topology: { cnsId, neurons, collaterals, dendrites } });\n // Also send topology snapshot so UI can refresh graph\n this.broadcastRaw(JSON.stringify(topologyBroadcast));\n }\n\n // ─── UI client query handlers ─────────────────────────────────────────────\n\n private async handleClientConnect(ws: WebSocket): Promise<void> {\n this.addClient(ws);\n this.send(ws, { type: 'apps.result', requestId: '__init__', items: await this.repository.listApps() });\n this.send(ws, {\n type: 'topology.result',\n requestId: '__init__',\n snapshots: await this.repository.getTopology(),\n });\n }\n\n private async handleAppsQuery(ws: WebSocket, requestId: string): Promise<void> {\n const items = await this.repository.listApps();\n this.send(ws, { type: 'apps.result', requestId, items });\n }\n\n private async handleTopologyQuery(\n ws: WebSocket,\n requestId: string,\n cnsId?: string,\n appId?: string\n ): Promise<void> {\n let snapshots = await this.repository.getTopology(cnsId);\n if (appId) snapshots = snapshots.filter(s => s.appId === appId);\n this.send(ws, { type: 'topology.result', requestId, snapshots });\n }\n\n private async handleReplayStart(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n const appSockets = appId ? this.appWsByAppId.get(appId) : undefined;\n\n if (!appSockets || appSockets.size === 0) {\n this.send(ws, { type: 'replay.rejected', replayId: msg.replayId, reason: 'App not connected' });\n return;\n }\n\n const newStimulationId = `${msg.stimulationId}-replay-${Date.now()}`;\n this.send(ws, { type: 'replay.accepted', replayId: msg.replayId, newStimulationId });\n\n const payload = JSON.stringify(msg);\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────\n //\n // The UI's request is thin (a stimulationId). The CNS + name→ref registry live in\n // the APP, so the server can't execute it: it ENRICHES from the resumable store (the\n // stored entry + progress + a server-assigned attempt identity) and forwards a\n // resume/launch command to the owning app, routed by scopeName (= the cns id).\n\n /** Resolve the app that owns a stimulation's scope, or reject if unreachable. */\n private async resolveScopeApp(\n scopeName: string | undefined\n ): Promise<Set<WebSocket> | undefined> {\n const appId = scopeName\n ? await this.repository.findAppByCns(scopeName)\n : undefined;\n const sockets = appId ? this.appWsByAppId.get(appId) : undefined;\n return sockets && sockets.size > 0 ? sockets : undefined;\n }\n\n private async handleStimulationRetry(ws: WebSocket, msg: any): Promise<void> {\n const reject = (reason: string) =>\n this.send(ws, {\n type: 'stimulation.retry.rejected',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n reason,\n });\n\n const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);\n if (!stim) return reject('unknown stimulation');\n if (stim.progress.tasks.length === 0)\n return reject('nothing to resume (empty frontier)');\n\n const appSockets = await this.resolveScopeApp(stim.scopeName);\n if (!appSockets) return reject('app not connected');\n\n const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);\n const nextAttempt = attempts.length + 1;\n const stimulationAttemptId = buildAttemptId(msg.stimulationId, nextAttempt);\n\n this.send(ws, {\n type: 'stimulation.retry.accepted',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n newStimulationAttemptId: stimulationAttemptId,\n });\n\n const payload = JSON.stringify({\n type: 'cns.stimulation.resume',\n requestId: msg.requestId,\n scopeName: stim.scopeName,\n stimulationId: msg.stimulationId,\n stimulationAttemptId,\n attemptNumber: nextAttempt,\n entry: stim.entry,\n progress: stim.progress,\n options: msg.options,\n });\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n private async handleStimulationClone(ws: WebSocket, msg: any): Promise<void> {\n const reject = (reason: string) =>\n this.send(ws, {\n type: 'stimulation.clone.rejected',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n reason,\n });\n\n const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);\n if (!stim) return reject('unknown stimulation');\n\n const appSockets = await this.resolveScopeApp(stim.scopeName);\n if (!appSockets) return reject('app not connected');\n\n const newStimulationId = buildCloneId(msg.stimulationId);\n const stimulationAttemptId = buildAttemptId(newStimulationId, 1);\n\n this.send(ws, {\n type: 'stimulation.clone.accepted',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n newStimulationId,\n newStimulationAttemptId: stimulationAttemptId,\n });\n\n const payload = JSON.stringify({\n type: 'cns.stimulation.launch',\n requestId: msg.requestId,\n scopeName: stim.scopeName,\n stimulationId: newStimulationId,\n stimulationAttemptId,\n entry: stim.entry,\n options: msg.options,\n });\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Broadcast helpers ────────────────────────────────────────────────────\n\n private broadcast(message: object): void {\n this.broadcastRaw(JSON.stringify(message));\n }\n\n private broadcastRaw(payload: string): void {\n for (const client of this.uiClients) {\n try {\n if ((client as any).readyState === 1) client.send(payload);\n } catch {}\n }\n }\n\n private send(ws: WebSocket, message: object): void {\n try {\n if ((ws as any).readyState === 1) ws.send(JSON.stringify(message));\n } catch {}\n }\n\n // ─── Metrics ──────────────────────────────────────────────────────────────\n\n private startMetrics(): void {\n if (this.metricsTimer) return;\n this.metricsTimer = setInterval(() => {\n const mem = process.memoryUsage();\n const now = Date.now();\n const cpu = process.cpuUsage(this.lastCpuUsage);\n const elapsed = (now - this.lastCpuTime) * 1000;\n const cpuPercent = elapsed > 0 ? ((cpu.user + cpu.system) / elapsed) * 100 : 0;\n this.lastCpuUsage = process.cpuUsage();\n this.lastCpuTime = now;\n\n this.broadcast({\n type: 'server.metrics',\n timestamp: now,\n rssMB: mem.rss / 1024 / 1024,\n heapUsedMB: mem.heapUsed / 1024 / 1024,\n heapTotalMB: mem.heapTotal / 1024 / 1024,\n cpuPercent,\n });\n }, 1000);\n }\n\n private stopMetrics(): void {\n if (this.metricsTimer) {\n clearInterval(this.metricsTimer);\n this.metricsTimer = undefined;\n }\n }\n}\n"]}
|
package/dist/start.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CNSPersistOptionsRegistry, ICNSStimulationRepository } from '@cnstra/persist';
|
|
2
|
+
import type { ICNS, TCNSNeuron } from '@cnstra/types';
|
|
2
3
|
export type StartDevToolsOptions = {
|
|
3
4
|
/** Port for HTTP (UI) and WebSocket. Default: 3141 */
|
|
4
5
|
port?: number;
|
|
@@ -7,6 +8,15 @@ export type StartDevToolsOptions = {
|
|
|
7
8
|
/** App ID shown in DevTools. Default: 'app' */
|
|
8
9
|
appId?: string;
|
|
9
10
|
consoleLogEnabled?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* The canonical name-based Stimulation → Attempt → Task store the Explorer
|
|
13
|
+
* reads its runs / facets / waterfall from. Pass the SAME instance you give a
|
|
14
|
+
* pg-boss / BullMQ worker as its `observe` store, and every job run shows up
|
|
15
|
+
* in the Explorer live (one process, shared memory). Use a
|
|
16
|
+
* `CNSPostgresStimulationRepository` to keep history across restarts, or omit
|
|
17
|
+
* for a fresh ephemeral in-memory store.
|
|
18
|
+
*/
|
|
19
|
+
stimulationRepository?: ICNSStimulationRepository;
|
|
10
20
|
};
|
|
11
21
|
export type DevToolsHandle = {
|
|
12
22
|
url: string;
|
|
@@ -23,5 +33,5 @@ export type DevToolsHandle = {
|
|
|
23
33
|
* await startDevTools(cns, registry);
|
|
24
34
|
* // → 🧠 CNStra DevTools: http://localhost:3141
|
|
25
35
|
*/
|
|
26
|
-
export declare function startDevTools(cns: ICNS<TCNSNeuron<any
|
|
36
|
+
export declare function startDevTools(cns: ICNS<TCNSNeuron<any>>, registry: CNSPersistOptionsRegistry, options?: StartDevToolsOptions): Promise<DevToolsHandle>;
|
|
27
37
|
//# sourceMappingURL=start.d.ts.map
|
package/dist/start.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACR,yBAAyB,EACzB,yBAAyB,EAC5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AA6CtD,MAAM,MAAM,oBAAoB,GAAG;IAC/B,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,yBAAyB,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAC/B,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAC1B,QAAQ,EAAE,yBAAyB,EACnC,OAAO,GAAE,oBAAyB,GACnC,OAAO,CAAC,cAAc,CAAC,CAoEzB"}
|
package/package.json
CHANGED
|
@@ -1,71 +1,79 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
2
|
+
"name": "@cnstra/devtools-server",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "CNStra DevTools server (WebSocket only for now)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cnstra",
|
|
7
|
+
"devtools",
|
|
8
|
+
"server",
|
|
9
|
+
"websocket"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"cnstra-devtools": "./dist/cli.js"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup && tsc --emitDeclarationOnly",
|
|
28
|
+
"build:types": "tsc --emitDeclarationOnly",
|
|
29
|
+
"start": "node dist/index.js",
|
|
30
|
+
"prepublishOnly": "node ../../scripts/check-drift.mjs --gate && npm run test && npm run build",
|
|
31
|
+
"test": "jest"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@cnstra/devtools-dto": "^3.0.0",
|
|
38
|
+
"@cnstra/devtools-server-repository-in-memory": "^3.0.0",
|
|
39
|
+
"@cnstra/types": "^0.2.0",
|
|
40
|
+
"ws": "^8.18.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@cnstra/core": "^3.0.0",
|
|
44
|
+
"@cnstra/devtools": "^3.0.0",
|
|
45
|
+
"@cnstra/devtools-panel-ui": "^3.0.0",
|
|
46
|
+
"@cnstra/devtools-transport-ws": "^2.0.0",
|
|
47
|
+
"@cnstra/persist": "^0.2.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@cnstra/broker": "^0.2.0",
|
|
51
|
+
"@cnstra/core": "^3.0.0",
|
|
52
|
+
"@cnstra/devtools": "^3.0.0",
|
|
53
|
+
"@cnstra/devtools-panel-ui": "^3.0.0",
|
|
54
|
+
"@cnstra/devtools-transport-ws": "^2.0.0",
|
|
55
|
+
"@cnstra/persist": "^0.2.0",
|
|
56
|
+
"@types/node": "^22.7.4",
|
|
57
|
+
"@types/ws": "^8.5.10",
|
|
58
|
+
"jest": "^29.7.0",
|
|
59
|
+
"ts-jest": "^29.1.2",
|
|
60
|
+
"tsup": "8.5.1",
|
|
61
|
+
"typescript": "^5.8.3"
|
|
50
62
|
},
|
|
51
|
-
"
|
|
52
|
-
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"@cnstra/devtools-panel-ui": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@cnstra/devtools": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@cnstra/devtools-transport-ws": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
53
73
|
},
|
|
54
|
-
"
|
|
55
|
-
|
|
74
|
+
"author": "Andrei Baikov <abaikov23@gmail.com>",
|
|
75
|
+
"license": "MIT",
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public"
|
|
56
78
|
}
|
|
57
|
-
},
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@types/node": "^22.7.4",
|
|
60
|
-
"@types/ws": "^8.5.10",
|
|
61
|
-
"jest": "^29.7.0",
|
|
62
|
-
"ts-jest": "^29.1.2",
|
|
63
|
-
"tsup": "8.5.1",
|
|
64
|
-
"typescript": "^5.8.3"
|
|
65
|
-
},
|
|
66
|
-
"author": "Andrei Baikov <abaikov23@gmail.com>",
|
|
67
|
-
"license": "MIT",
|
|
68
|
-
"publishConfig": {
|
|
69
|
-
"access": "public"
|
|
70
|
-
}
|
|
71
79
|
}
|