Skip to content

Commit 8bdeb7c

Browse files
refactor(Cocoon/IPC): Consolidate service layer implementations with Effect-TS patterns
- Restructured IPC services (Dispatcher, ProtocolAdapter, Server) into namespaced modules with unified Layer exports - Migrated service interfaces to class-based Context.Tag definitions for improved type safety and dependency management - Aligned gRPC server lifecycle handling with Effect's scoped resource model through Layer composition - Standardized service method parameter naming (ParameterArray) across RPC handlers for consistency - Updated Configuration service tagging to match latest Effect Context API conventions These changes strengthen the Cocoon sidecar's IPC foundation by fully leveraging Effect-TS's dependency injection capabilities, ensuring proper separation between gRPC transport logic and VS Code extension protocol handling. The new structure directly supports Land's phased architecture by maintaining clear service boundaries between Cocoon's Node.js environment and Mountain's native backend. Refs #4cc1488e20f81f89f02b2aeef8ce9a5bca857941
1 parent 4cc1488 commit 8bdeb7c

File tree

8 files changed

+73
-91
lines changed

8 files changed

+73
-91
lines changed

Source/Service/IPC/Configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ export interface Configuration {
2525
/**
2626
* The `Context.Tag` for the IPC configuration.
2727
*/
28-
export const Tag = Context.Tag<Configuration>("IPC/Configuration");
28+
export const Tag = Context.Tag("IPC/Configuration")<Configuration>;

Source/Service/IPC/Dispatcher.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@
66

77
import { Layer } from "effect";
88

9-
import { Live as LiveCancellation } from "../Cancellation.js";
9+
import { Cancellation } from "../../Cancellation.js";
1010
import { Definition } from "./Dispatcher/Definition.js";
11-
import { Tag } from "./Dispatcher/Service.js";
12-
import { Live as LiveProtocolAdapter } from "./ProtocolAdapter.js";
11+
import { Dispatcher as DispatcherTag } from "./Dispatcher/Service.js";
12+
import { ProtocolAdapter } from "./ProtocolAdapter.js";
1313

14-
/**
15-
* The live implementation Layer for the Dispatcher service.
16-
* It depends on the ProtocolAdapter (for the underlying transport) and the
17-
* Cancellation service (for handling cancellation signals).
18-
*/
19-
export const Live = Layer.effect(Tag, Definition).pipe(
20-
Layer.provide(Layer.merge(LiveProtocolAdapter, LiveCancellation)),
21-
);
14+
export namespace Dispatcher {
15+
export const Tag = DispatcherTag;
16+
export type Interface = DispatcherTag;
17+
18+
/**
19+
* The live implementation Layer for the Dispatcher service.
20+
* It depends on the ProtocolAdapter (for the underlying transport) and the
21+
* Cancellation service (for handling cancellation signals).
22+
*/
23+
export const Live = Layer.effect(Tag, Definition).pipe(
24+
Layer.provide(Layer.merge(ProtocolAdapter.Live, Cancellation.Live)),
25+
);
26+
}

Source/Service/IPC/Dispatcher/Service.ts

Lines changed: 24 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,61 +6,30 @@
66
*/
77

88
import { Context, type Effect } from "effect";
9-
import type { IDisposable } from "vscode";
9+
import type { Disposable } from "vscode";
1010

1111
import type { IPCError } from "../Error.js";
1212

13-
export interface Interface {
14-
/**
15-
* Dispatches an incoming request from the host.
16-
* It will first check for a custom invoke handler, then fall back to the
17-
* main VS Code RPCProtocol.
18-
* @param Method The name of the method being invoked.
19-
* @param Parameters The arguments for the method.
20-
* @returns An `Effect` that resolves with the result of the handler.
21-
*/
22-
readonly DispatchRequest: (
23-
Method: string,
24-
Parameters: any[],
25-
) => Effect.Effect<any, Error>;
26-
27-
/**
28-
* Dispatches an incoming fire-and-forget notification from the host.
29-
* @param Method The name of the notification.
30-
* @param Parameters The arguments for the notification.
31-
*/
32-
readonly DispatchNotification: (
33-
Method: string,
34-
Parameters: any[],
35-
) => Effect.Effect<void, never>;
36-
37-
/**
38-
* Dispatches an incoming cancellation signal from the host.
39-
* @param RequestID The ID of the request to cancel.
40-
*/
41-
readonly CancelOperation: (RequestID: number) => Effect.Effect<void, never>;
42-
43-
/**
44-
* Processes raw binary data for the VS Code RPCProtocol by passing it to
45-
* the underlying protocol adapter.
46-
* @param Data The raw Uint8Array from the host.
47-
*/
48-
readonly ProcessIncomingData: (
49-
Data: Uint8Array,
50-
) => Effect.Effect<void, never>;
51-
52-
/**
53-
* Registers a custom handler for a specific invoke method from the host.
54-
* This is used for top-level methods like `initExtensionHost` that are
55-
* not part of the standard VS Code RPC protocol.
56-
* @param Channel The channel/method name.
57-
* @param Handler An async function that handles the request.
58-
* @returns A `Disposable` to unregister the handler.
59-
*/
60-
readonly RegisterInvokeHandler: (
61-
Channel: string,
62-
Handler: (...Arguments: any[]) => Promise<any>,
63-
) => IDisposable;
64-
}
65-
66-
export const Tag = Context.Tag<Interface>("IPC/Dispatcher");
13+
export class Dispatcher extends Context.Tag("IPC/Dispatcher")<
14+
Dispatcher,
15+
{
16+
readonly DispatchRequest: (
17+
Method: string,
18+
ParameterArray: any[],
19+
) => Effect.Effect<any, Error>;
20+
readonly DispatchNotification: (
21+
Method: string,
22+
ParameterArray: any[],
23+
) => Effect.Effect<void, never>;
24+
readonly CancelOperation: (
25+
RequestID: number,
26+
) => Effect.Effect<void, never>;
27+
readonly ProcessIncomingData: (
28+
Data: Uint8Array,
29+
) => Effect.Effect<void, never>;
30+
readonly RegisterInvokeHandler: (
31+
Channel: string,
32+
Handler: (...ArgumentArray: any[]) => Promise<any>,
33+
) => Disposable;
34+
}
35+
>() {}

Source/Service/IPC/ProtocolAdapter.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ import { Layer } from "effect";
99

1010
import { Live as LiveClient } from "./Client.js";
1111
import { Definition } from "./ProtocolAdapter/Definition.js";
12-
import { Tag } from "./ProtocolAdapter/Service.js";
12+
import { Tag, type Interface } from "./ProtocolAdapter/Service.js";
13+
14+
// --- Public API Exports ---
15+
export { Tag, type Interface };
16+
17+
// --- Live Implementation ---
1318

1419
/**
1520
* The live implementation `Layer` for the `ProtocolAdapter` service.
16-
*
17-
* This layer builds the adapter by composing its `Definition` with the
21+
* It builds the adapter by composing its `Definition` with the
1822
* `LiveClient` layer it depends on for the underlying gRPC transport.
1923
*/
2024
export const Live = Layer.effect(Tag, Definition).pipe(

Source/Service/IPC/Server.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,26 @@
66

77
import { Layer } from "effect";
88

9-
import type { Configuration } from "./Configuration.js";
10-
import { Live as LiveDispatcher } from "./Dispatcher.js";
9+
import { Configuration } from "./Configuration.js";
10+
import { Dispatcher } from "./Dispatcher.js";
1111
import type { gRPCConnectionError } from "./Error.js";
1212
import { Acquire } from "./Server/Acquire.js";
1313
import { Tag, type Interface as ServerService } from "./Server/Service.js";
1414

15-
/**
16-
* The live implementation `Layer` for the gRPC Server service.
17-
*/
18-
export const Live: Layer.Layer<
19-
ServerService,
20-
gRPCConnectionError,
21-
Configuration | Dispatcher.Interface
22-
> = Layer.scoped(Tag, Acquire).pipe(Layer.provide(LiveDispatcher));
15+
export namespace Server {
16+
export const Tag = Tag;
17+
export type Interface = ServerService;
18+
/**
19+
* The live implementation `Layer` for the gRPC Server service.
20+
*
21+
* This scoped layer creates and manages the lifecycle of the gRPC server instance,
22+
* ensuring it starts with the application and is shut down gracefully. It
23+
* composes the `Acquire` effect with the `LiveDispatcher` layer, which is a
24+
* dependency for creating the server's request handlers.
25+
*/
26+
export const Live: Layer.Layer<
27+
Interface,
28+
gRPCConnectionError,
29+
Configuration.Configuration
30+
> = Layer.scoped(Tag, Acquire).pipe(Layer.provide(Dispatcher.Live));
31+
}

Source/Service/IPC/Server/Acquire.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from "@grpc/proto-loader";
1414
import { Effect } from "effect";
1515

16-
import { Configuration as ConfigurationService } from "../Configuration.js";
16+
import { Configuration } from "../Configuration.js";
1717
import { Dispatcher } from "../Dispatcher.js";
1818
import { gRPCConnectionError } from "../Error.js";
1919
import { CreateServiceImplementation } from "./CreateServiceImplementation.js";
@@ -78,7 +78,7 @@ function StartServer(
7878

7979
export const Acquire = Effect.acquireRelease(
8080
Effect.gen(function* () {
81-
const Config = yield* ConfigurationService.Tag;
81+
const Config = yield* Configuration.Tag;
8282
const DispatcherService = yield* Dispatcher.Tag;
8383
const ProtoPath = Path.join(process.cwd(), "proto/vine.proto");
8484

Source/Service/IPC/Server/CreateServiceImplementation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
import * as gRPC from "@grpc/grpc-js";
88
import type { UntypedServiceImplementation } from "@grpc/grpc-js";
9-
import { Effect } from "effect";
9+
import { Context, Effect } from "effect";
1010

11-
import type { Dispatcher } from "../Dispatcher/Service.js";
11+
import { Dispatcher } from "../Dispatcher.js";
1212
import {
1313
Empty,
1414
GenericResponse,
@@ -20,7 +20,7 @@ import {
2020
import { DecodeValue, EncodeValue } from "../ProtoConverter.js";
2121

2222
export function CreateServiceImplementation(
23-
DispatcherService: Dispatcher.Interface,
23+
DispatcherService: Context.Tag.Service<Dispatcher.Interface>,
2424
): UntypedServiceImplementation {
2525
return {
2626
/**

Source/Service/IPC/Server/Service.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,10 @@
77
import type * as gRPC from "@grpc/grpc-js";
88
import { Context } from "effect";
99

10-
/**
11-
* The service type, which is the underlying `grpc.Server` class.
12-
*/
13-
export type Interface = gRPC.Server;
14-
1510
/**
1611
* The `Context.Tag` for the gRPC server instance.
1712
*
1813
* This tag provides access to the raw server object if needed, for example,
1914
* by the `acquireRelease` logic that manages its lifecycle.
2015
*/
21-
export const Tag = Context.Tag<Interface>("IPC/Server");
16+
export class Server extends Context.Tag("IPC/Server")<Server, gRPC.Server>() {}

0 commit comments

Comments
 (0)