@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.
- package/LICENSE +21 -0
- package/README.md +91 -0
- package/lib/data-server.d.ts +614 -0
- package/lib/data-server.d.ts.map +1 -0
- package/lib/data-server.js +1003 -0
- package/lib/data-server.js.map +1 -0
- package/lib/default-diagnostics.browser.d.ts +18 -0
- package/lib/default-diagnostics.browser.d.ts.map +1 -0
- package/lib/default-diagnostics.browser.js +36 -0
- package/lib/default-diagnostics.browser.js.map +1 -0
- package/lib/default-diagnostics.d.ts +36 -0
- package/lib/default-diagnostics.d.ts.map +1 -0
- package/lib/default-diagnostics.js +38 -0
- package/lib/default-diagnostics.js.map +1 -0
- package/lib/diagnostics-provider.d.ts +65 -0
- package/lib/diagnostics-provider.d.ts.map +1 -0
- package/lib/diagnostics-provider.js +10 -0
- package/lib/diagnostics-provider.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +15 -0
- package/lib/index.js.map +1 -0
- package/lib/node/index.d.ts +10 -0
- package/lib/node/index.d.ts.map +1 -0
- package/lib/node/index.js +16 -0
- package/lib/node/index.js.map +1 -0
- package/lib/node/node-diagnostics-provider.d.ts +18 -0
- package/lib/node/node-diagnostics-provider.d.ts.map +1 -0
- package/lib/node/node-diagnostics-provider.js +68 -0
- package/lib/node/node-diagnostics-provider.js.map +1 -0
- package/lib/testing/data-server-harness.d.ts +85 -0
- package/lib/testing/data-server-harness.d.ts.map +1 -0
- package/lib/testing/data-server-harness.js +47 -0
- package/lib/testing/data-server-harness.js.map +1 -0
- package/lib/testing/index.d.ts +11 -0
- package/lib/testing/index.d.ts.map +1 -0
- package/lib/testing/index.js +10 -0
- package/lib/testing/index.js.map +1 -0
- package/package.json +93 -0
- package/src/data-server.ts +1282 -0
- package/src/default-diagnostics.browser.ts +41 -0
- package/src/default-diagnostics.ts +40 -0
- package/src/diagnostics-provider.ts +70 -0
- package/src/index.ts +15 -0
- package/src/node/index.ts +17 -0
- package/src/node/node-diagnostics-provider.ts +82 -0
- package/src/testing/data-server-harness.ts +147 -0
- 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';
|