@hydranium/data-server 1.0.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/lib/data-server.d.ts +614 -0
  4. package/lib/data-server.d.ts.map +1 -0
  5. package/lib/data-server.js +1003 -0
  6. package/lib/data-server.js.map +1 -0
  7. package/lib/default-diagnostics.browser.d.ts +18 -0
  8. package/lib/default-diagnostics.browser.d.ts.map +1 -0
  9. package/lib/default-diagnostics.browser.js +36 -0
  10. package/lib/default-diagnostics.browser.js.map +1 -0
  11. package/lib/default-diagnostics.d.ts +36 -0
  12. package/lib/default-diagnostics.d.ts.map +1 -0
  13. package/lib/default-diagnostics.js +38 -0
  14. package/lib/default-diagnostics.js.map +1 -0
  15. package/lib/diagnostics-provider.d.ts +65 -0
  16. package/lib/diagnostics-provider.d.ts.map +1 -0
  17. package/lib/diagnostics-provider.js +10 -0
  18. package/lib/diagnostics-provider.js.map +1 -0
  19. package/lib/index.d.ts +11 -0
  20. package/lib/index.d.ts.map +1 -0
  21. package/lib/index.js +15 -0
  22. package/lib/index.js.map +1 -0
  23. package/lib/node/index.d.ts +10 -0
  24. package/lib/node/index.d.ts.map +1 -0
  25. package/lib/node/index.js +16 -0
  26. package/lib/node/index.js.map +1 -0
  27. package/lib/node/node-diagnostics-provider.d.ts +18 -0
  28. package/lib/node/node-diagnostics-provider.d.ts.map +1 -0
  29. package/lib/node/node-diagnostics-provider.js +68 -0
  30. package/lib/node/node-diagnostics-provider.js.map +1 -0
  31. package/lib/testing/data-server-harness.d.ts +85 -0
  32. package/lib/testing/data-server-harness.d.ts.map +1 -0
  33. package/lib/testing/data-server-harness.js +47 -0
  34. package/lib/testing/data-server-harness.js.map +1 -0
  35. package/lib/testing/index.d.ts +11 -0
  36. package/lib/testing/index.d.ts.map +1 -0
  37. package/lib/testing/index.js +10 -0
  38. package/lib/testing/index.js.map +1 -0
  39. package/package.json +93 -0
  40. package/src/data-server.ts +1282 -0
  41. package/src/default-diagnostics.browser.ts +41 -0
  42. package/src/default-diagnostics.ts +40 -0
  43. package/src/diagnostics-provider.ts +70 -0
  44. package/src/index.ts +15 -0
  45. package/src/node/index.ts +17 -0
  46. package/src/node/node-diagnostics-provider.ts +82 -0
  47. package/src/testing/data-server-harness.ts +147 -0
  48. package/src/testing/index.ts +19 -0
@@ -0,0 +1,41 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ import type { DataServerDiagnosticsProvider, DataServerProfileCapture } from './diagnostics-provider.js';
11
+
12
+ /**
13
+ * Message every method of the browser default rejects with.
14
+ *
15
+ * It **throws rather than no-ops** deliberately. A no-op would hand back an
16
+ * empty snapshot, which reads as "the server is fine" — the same shape as a
17
+ * real answer, and the failure mode this whole seam exists to avoid. Saying
18
+ * plainly that the capability is absent is more useful than a plausible lie.
19
+ */
20
+ function unavailable(method: string): Error {
21
+ return new Error(
22
+ `DataServer.${method} is not available in this host: a browser has no process to inspect. ` +
23
+ 'Heap snapshots, profiling, pod memory and server state are Node-only capabilities.'
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Platform default for a browser host, selected by `package.json`'s `browser`
29
+ * field in place of `default-diagnostics.js`.
30
+ *
31
+ * Free of Node imports by construction — that is its entire reason for
32
+ * existing, and why the head's portable entry can be bundled at all.
33
+ */
34
+ export function defaultDataServerDiagnostics(): DataServerDiagnosticsProvider {
35
+ return {
36
+ dumpServerState: () => Promise.reject(unavailable('dumpServerState')),
37
+ writeHeapSnapshot: () => Promise.reject(unavailable('writeHeapSnapshot')),
38
+ dumpPodMemory: () => Promise.reject(unavailable('dumpPodMemory')),
39
+ startProfiling: (): Promise<DataServerProfileCapture> => Promise.reject(unavailable('startProfiling'))
40
+ };
41
+ }
@@ -0,0 +1,40 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ import type { DataServerDiagnosticsProvider } from './diagnostics-provider.js';
11
+ import { nodeDataServerDiagnostics } from './node/node-diagnostics-provider.js';
12
+
13
+ /**
14
+ * Platform default for `DataServerOptions.diagnostics` — the Node one.
15
+ *
16
+ * # This file is swapped by the bundler, and that is the whole mechanism
17
+ *
18
+ * `package.json`'s `browser` field maps this module to
19
+ * `default-diagnostics.browser.js`, so a `platform: 'browser'` bundler never
20
+ * resolves it and never follows its `@hydranium/core/node` import. Node keeps
21
+ * it, because Node ignores the `browser` field.
22
+ *
23
+ * The alternative — reaching for the Node implementation directly from the head
24
+ * — is what made this package unbundleable for a browser: a static
25
+ * `@hydranium/core/node` import on the portable entry pulls `node:fs`,
26
+ * `node:v8` and `node:perf_hooks` into any browser build, over methods a
27
+ * browser cannot call.
28
+ *
29
+ * **`check:neutral` proves the swap works**, since it bundles this package's `.`
30
+ * entry for the browser and would fail on those imports if the mapping ever
31
+ * stopped applying. That is the same property the gate relies on for
32
+ * `@eclipse-glsp/server`, whose own `browser` field selects its node-free
33
+ * build.
34
+ *
35
+ * A host that wants to be explicit — or to supply its own — passes
36
+ * `diagnostics` and this default is not consulted.
37
+ */
38
+ export function defaultDataServerDiagnostics(): DataServerDiagnosticsProvider {
39
+ return nodeDataServerDiagnostics();
40
+ }
@@ -0,0 +1,70 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ import type { ServerSharedServices } from '@hydranium/core';
11
+ import type { DumpServerStateArgs, StartProfilingArgs, StopProfilingArgs, WriteServerHeapSnapshotArgs } from '@hydranium/protocol';
12
+
13
+ /**
14
+ * The runtime-specific half of `DataServerDiagnosticsProtocol`, supplied
15
+ * by the host rather than reached for directly.
16
+ *
17
+ * # Why this is a seam and not four method bodies
18
+ *
19
+ * Every operation behind it needs a Node runtime — a V8 heap snapshot, an
20
+ * inspector session, `process.memoryUsage()`, cgroup files. Calling them
21
+ * directly puts a static `@hydranium/core/node` import on the data head's
22
+ * portable `.` entry, which no browser bundler can resolve: the head then fails
23
+ * to BUILD for a browser even though nothing in a browser would ever call a
24
+ * diagnostics method. Since the data head exists to serve non-LSP clients —
25
+ * webviews and browser pages among them — that is a defect rather than a
26
+ * trade-off.
27
+ *
28
+ * Node hosts get the real implementation from `@hydranium/data-server/node`.
29
+ * Hosts that supply nothing keep the whole rest of the protocol and reject only
30
+ * these four methods, with a message naming the import that would satisfy them.
31
+ */
32
+ export interface DataServerDiagnosticsProvider {
33
+ /**
34
+ * Snapshot this process's memory, V8 stats, event-loop utilisation and
35
+ * document counts, formatted for a log.
36
+ *
37
+ * Takes the services rather than a document list so the whole shape of the
38
+ * snapshot — including which of the shared tier's stores it reads — stays a
39
+ * decision of the implementation, and no Node-only type appears here.
40
+ */
41
+ dumpServerState(services: ServerSharedServices, args: DumpServerStateArgs): Promise<string>;
42
+
43
+ /** Write a V8 heap snapshot and return its absolute path. */
44
+ writeHeapSnapshot(args: WriteServerHeapSnapshotArgs): Promise<string>;
45
+
46
+ /** Snapshot cgroup/pod memory — the figure an OOM-killer watches. */
47
+ dumpPodMemory(): Promise<string>;
48
+
49
+ /**
50
+ * Begin a windowed sampled-profile capture.
51
+ *
52
+ * Rejecting when a capture is already active is the implementation's
53
+ * responsibility, because the constraint is the runtime's: one inspector
54
+ * session per process.
55
+ */
56
+ startProfiling(args: StartProfilingArgs): Promise<DataServerProfileCapture>;
57
+ }
58
+
59
+ /** A profile capture in flight, returned by {@link DataServerDiagnosticsProvider.startProfiling}. */
60
+ export interface DataServerProfileCapture {
61
+ /**
62
+ * End the capture, write its artefacts when a directory was given, and
63
+ * return the formatted report.
64
+ *
65
+ * Formatting belongs here rather than in the caller so the report type never
66
+ * has to cross this boundary — it is a Node-side shape, and naming it in the
67
+ * portable interface would put the import back.
68
+ */
69
+ stop(args: StopProfilingArgs): Promise<string>;
70
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ // Public API barrel for `@hydranium/data-server` — the typed-RPC
11
+ // protocol head. Test-only helpers (duplex-connection pair, ...) live
12
+ // under the `./testing` subpath and are NOT re-exported here so
13
+ // production bundles stay free of test scaffolding.
14
+ export * from './data-server.js';
15
+ export * from './diagnostics-provider.js';
@@ -0,0 +1,17 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ // Server-only surface of the data head (`@hydranium/data-server/node`).
11
+ //
12
+ // The rule is the same one `@hydranium/core` follows: `.` is portable and
13
+ // bundles for a browser, everything that needs a Node runtime lives here. Adding
14
+ // a module to this directory is how a Node-only capability reaches the head
15
+ // without costing the portable entry its neutrality.
16
+
17
+ export * from './node-diagnostics-provider.js';
@@ -0,0 +1,82 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ import type { ServerSharedServices } from '@hydranium/core';
11
+ import {
12
+ formatPodMemory,
13
+ formatProfileReport,
14
+ formatServerState,
15
+ ProfileCapture,
16
+ recordServerSummaryEntry,
17
+ writeHeapSnapshotToDir
18
+ } from '@hydranium/core/node';
19
+ import type { DumpServerStateArgs, StartProfilingArgs, StopProfilingArgs, WriteServerHeapSnapshotArgs } from '@hydranium/protocol';
20
+ import type { DataServerDiagnosticsProvider, DataServerProfileCapture } from '../diagnostics-provider.js';
21
+
22
+ /**
23
+ * The Node implementation of the data head's diagnostics, and the reason this
24
+ * subpath exists.
25
+ *
26
+ * Everything here needs a process to inspect — a V8 heap snapshot, an inspector
27
+ * session, `process.memoryUsage()`, cgroup files — so it is server-only by
28
+ * nature. Keeping it out of the package's portable `.` entry is what lets a
29
+ * browser bundle the data head at all.
30
+ *
31
+ * Snapshots are computed in THIS process on purpose: the data-server child holds
32
+ * the model store, so they reflect the heap that actually carries the workspace
33
+ * AST/CST — the process that OOMs.
34
+ */
35
+ class NodeProfileCapture implements DataServerProfileCapture {
36
+ constructor(protected readonly capture: ProfileCapture) {}
37
+
38
+ async stop(args: StopProfilingArgs): Promise<string> {
39
+ const report = await this.capture.stop(args.directory, args.label ?? '');
40
+ // When a directory was given (the profile files land there), also fold this
41
+ // window's report into `<dir>/server-summary.json` so an interactive/e2e
42
+ // bundle carries the same per-window summary the headless `ProfilingRun`
43
+ // writes.
44
+ if (args.directory) {
45
+ recordServerSummaryEntry(args.directory, args.label ?? '', report);
46
+ }
47
+ return formatProfileReport(report, args.label ? `Profile report (${args.label})` : undefined);
48
+ }
49
+ }
50
+
51
+ class NodeDataServerDiagnostics implements DataServerDiagnosticsProvider {
52
+ async dumpServerState(services: ServerSharedServices, args: DumpServerStateArgs): Promise<string> {
53
+ const openDocuments = services.workspace.TextDocuments.openDocuments();
54
+ return formatServerState(services.workspace.LangiumDocuments, args.label, { openDocuments });
55
+ }
56
+
57
+ async writeHeapSnapshot(args: WriteServerHeapSnapshotArgs): Promise<string> {
58
+ return writeHeapSnapshotToDir(args.directory, args.label ?? '');
59
+ }
60
+
61
+ async dumpPodMemory(): Promise<string> {
62
+ return formatPodMemory();
63
+ }
64
+
65
+ async startProfiling(args: StartProfilingArgs): Promise<DataServerProfileCapture> {
66
+ // `ProfileCapture` is singleton-guarded — one inspector session per
67
+ // process — so a second concurrent start rejects here rather than in the
68
+ // caller, which is where the constraint actually lives.
69
+ return new NodeProfileCapture(await ProfileCapture.start(args));
70
+ }
71
+ }
72
+
73
+ /**
74
+ * The diagnostics provider a Node-hosted `DataServer` should be constructed
75
+ * with: `new DataServer(connection, shared, { diagnostics: nodeDataServerDiagnostics() })`.
76
+ *
77
+ * Omitting it is not an error — the head runs without it and rejects only the
78
+ * four diagnostics methods — so a host that never calls them can leave it out.
79
+ */
80
+ export function nodeDataServerDiagnostics(): DataServerDiagnosticsProvider {
81
+ return new NodeDataServerDiagnostics();
82
+ }
@@ -0,0 +1,147 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ import { createRpcProxy, type Project, type TransferDiagnostic, type TransferElement } from '@hydranium/protocol';
11
+ import {
12
+ DATA_CLIENT_PROTOCOL_METHODS,
13
+ DATA_SERVER_WIRE_PREFIX,
14
+ type DataClientProtocol,
15
+ type DataServerProtocol,
16
+ type ProjectsChangedEvent,
17
+ type TransferDocumentSavedEvent,
18
+ type TransferDocumentUpdatedEvent
19
+ } from '@hydranium/protocol/data';
20
+ import { type Harness, makeCapturingDataClient } from '@hydranium/protocol/testing';
21
+ import { makeDuplexConnectionPair, type DuplexConnectionPair } from '@hydranium/protocol/testing/node';
22
+ import type { MessageConnection } from 'vscode-jsonrpc/node';
23
+
24
+ /**
25
+ * Configuration for {@link makeDataServerHarness}.
26
+ *
27
+ * `server` is the only required field — a factory that constructs the
28
+ * DataServer subclass under test against the supplied {@link MessageConnection}
29
+ * (the "server side" of the duplex pair). The factory is invoked once,
30
+ * synchronously, after the pair is wired.
31
+ *
32
+ * `client` lets tests override individual handlers on the captured
33
+ * {@link DataClientProtocol}. Unspecified handlers default to pushing
34
+ * incoming events into the {@link DataServerHarness} bundle's `events` /
35
+ * `saves` / `projectsChanges` arrays — the typical assertion target.
36
+ */
37
+ export interface MakeDataServerHarnessOptions<
38
+ TServer,
39
+ TTransfer extends TransferElement,
40
+ TDiagnostic extends TransferDiagnostic = TransferDiagnostic,
41
+ TProject extends Project = Project
42
+ > {
43
+ /**
44
+ * Construct the `DataServer` (or subclass) under test against
45
+ * `channel`. The harness invokes this once after `pair.left` is
46
+ * established.
47
+ */
48
+ server: (channel: MessageConnection) => TServer;
49
+ /**
50
+ * Override per-method handlers on the captured {@link DataClientProtocol}.
51
+ * Each overridden handler REPLACES the default (which pushes into the
52
+ * bundle's capture arrays); test code wanting to BOTH capture AND
53
+ * react should push to the array manually inside the override.
54
+ */
55
+ client?: Partial<DataClientProtocol<TTransfer, TDiagnostic, TProject>>;
56
+ /**
57
+ * Wire namespace the proxy addresses the server under. Defaults to
58
+ * {@link DATA_SERVER_WIRE_PREFIX}. Override when the `DataServer`
59
+ * subclass registers under a custom namespace (its constructor's
60
+ * `methodNamespace` option). Must match the server's namespace, or every
61
+ * request is "Unhandled method".
62
+ */
63
+ methodNamespace?: string;
64
+ }
65
+
66
+ /**
67
+ * Wiring bundle returned by {@link makeDataServerHarness}. Satisfies the
68
+ * uniform {@link Harness} contract — `server` is the **subject** (the
69
+ * DataServer under test), `proxy`/`pair` are the **seam** (the typed RPC
70
+ * proxy tests drive the subject through, plus the underlying duplex pair),
71
+ * `events`/`saves`/`projectsChanges` are the **capture arrays** for inbound
72
+ * client-side events, and `dispose()` is the uniform teardown hook.
73
+ *
74
+ * `dispose()` releases the duplex pair (and therefore both
75
+ * {@link MessageConnection}s); call at test teardown.
76
+ */
77
+ export interface DataServerHarness<
78
+ TServer,
79
+ TTransfer extends TransferElement,
80
+ TDiagnostic extends TransferDiagnostic = TransferDiagnostic,
81
+ TProject extends Project = Project
82
+ > extends Harness {
83
+ readonly server: TServer;
84
+ readonly proxy: DataServerProtocol<TTransfer, TDiagnostic, TProject>;
85
+ readonly pair: DuplexConnectionPair;
86
+ /** Captured `onDocumentUpdated` events — append order; never cleared by the harness. */
87
+ readonly events: ReadonlyArray<TransferDocumentUpdatedEvent<TTransfer, TDiagnostic>>;
88
+ /** Captured `onDocumentSaved` events. */
89
+ readonly saves: ReadonlyArray<TransferDocumentSavedEvent<TTransfer, TDiagnostic>>;
90
+ /** Captured `onProjectsChanged` events. */
91
+ readonly projectsChanges: ReadonlyArray<ProjectsChangedEvent<TProject>>;
92
+ /** Dispose the underlying duplex pair. Idempotent. */
93
+ dispose(): void;
94
+ }
95
+
96
+ /**
97
+ * Wire a DataServer + local {@link DataClientProtocol} + typed RPC proxy
98
+ * over an in-process duplex {@link MessageConnection} pair. Tests then
99
+ * exercise the server via `harness.proxy.*()` and assert on
100
+ * `harness.events` / `harness.saves` / `harness.projectsChanges`.
101
+ *
102
+ * Moves the duplex pair, the local client and the proxy construction behind
103
+ * a single factory call, so a test file carries only its own setup: seeded
104
+ * documents, server options, assertions.
105
+ */
106
+ export function makeDataServerHarness<
107
+ TServer,
108
+ TTransfer extends TransferElement,
109
+ TDiagnostic extends TransferDiagnostic = TransferDiagnostic,
110
+ TProject extends Project = Project
111
+ >(
112
+ options: MakeDataServerHarnessOptions<TServer, TTransfer, TDiagnostic, TProject>
113
+ ): DataServerHarness<TServer, TTransfer, TDiagnostic, TProject> {
114
+ const pair = makeDuplexConnectionPair();
115
+ const server = options.server(pair.left);
116
+
117
+ // The capture half is the shared client double, not a local copy: the same
118
+ // recording semantics (all three channels, an override replacing rather than
119
+ // supplementing) then hold for a client-side suite that stands the double up
120
+ // without a server, so an assertion learnt against one reads the same in the
121
+ // other.
122
+ const {
123
+ client: localClient,
124
+ updates: events,
125
+ saves,
126
+ projectsChanges
127
+ } = makeCapturingDataClient<TTransfer, TDiagnostic, TProject>(options.client);
128
+
129
+ const proxy = createRpcProxy<DataServerProtocol<TTransfer, TDiagnostic, TProject>, DataClientProtocol<TTransfer, TDiagnostic, TProject>>(
130
+ pair.right,
131
+ {
132
+ methodNamespace: options.methodNamespace ?? DATA_SERVER_WIRE_PREFIX,
133
+ localTarget: localClient,
134
+ localMethods: DATA_CLIENT_PROTOCOL_METHODS
135
+ }
136
+ );
137
+
138
+ return {
139
+ server,
140
+ proxy,
141
+ pair,
142
+ events,
143
+ saves,
144
+ projectsChanges,
145
+ dispose: () => pair.dispose()
146
+ };
147
+ }
@@ -0,0 +1,19 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ // Subpath barrel for `@hydranium/data-server/testing` — the
11
+ // `makeDataServerHarness` harness for in-process round-trip testing without a
12
+ // real wire. The duplex `MessageConnection` pair lives in
13
+ // `@hydranium/protocol/testing`, which owns the transport; only its TYPE is
14
+ // re-exported here, because `DataServerHarness.pair` names it in a public
15
+ // signature. A test that wires its own server rather than using the harness
16
+ // calls `makeDuplexConnectionPair` from `@hydranium/protocol/testing` directly.
17
+
18
+ export type { DuplexConnectionPair } from '@hydranium/protocol/testing/node';
19
+ export * from './data-server-harness.js';