Skip to content

Commit dbc7df5

Browse files
refactor(Cocoon/Service): Standardize service definitions with Effect-TS class-based Context
- Migrate service interfaces to class-based `Context.Tag` implementations across IPC, Cancellation, and core services - Reorganize service layers into dedicated namespaces (e.g. `Cancellation`, `Client`) for improved architectural clarity - Update import paths and references to align with new service organization - Simplify layer composition in ExtensionHost through streamlined `Layer.mergeAll` usage This refactoring completes the Effect-TS service pattern adoption in Cocoon's core, strengthening type safety and dependency management for the VS Code extension host implementation. The changes directly support the `Vine` gRPC contract stability by ensuring proper cancellation token handling and IPC client lifecycle management.
1 parent 5e5c5c1 commit dbc7df5

File tree

10 files changed

+83
-82
lines changed

10 files changed

+83
-82
lines changed

Source/Core/APIFactory/Service.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ export interface Interface {
2626
/**
2727
* The `Context.Tag` for the `APIFactory` service.
2828
*/
29-
export const Tag = Context.Tag<Interface>("Core/APIFactory");
29+
export class APIFactory extends Context.Tag("Core/APIFactory")<
30+
APIFactory,
31+
Interface
32+
>() {}

Source/Core/ExtensionHost.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,5 @@ import { Tag } from "./ExtensionHost/Service.js";
1818
* It depends on the APIFactory, logging, IPC, and initialization data services.
1919
*/
2020
export const Live = Layer.effect(Tag, Definition).pipe(
21-
Layer.provide(
22-
Layer.mergeAll(
23-
APIFactory.Live,
24-
Log.Live,
25-
IPC.Live,
26-
),
27-
),
21+
Layer.provide(Layer.mergeAll(APIFactory.Live, Log.Live, IPC.Live)),
2822
);

Source/Service/Cancellation.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@
88
import { Layer } from "effect";
99

1010
import { Definition } from "./Cancellation/Definition.js";
11-
import { Tag } from "./Cancellation/Service.js";
11+
import { Cancellation as CancellationTag } from "./Cancellation/Service.js";
1212

1313
export type { InvalidTokenIDError } from "./Cancellation/Error.js";
14-
export { Tag, type Interface } from "./Cancellation/Service.js";
14+
1515
export type { TokenAndScope } from "./Cancellation/Type.js";
1616

17-
/**
18-
* The live implementation Layer for the CancellationTokenProvider service.
19-
* This is a self-contained layer with no external dependencies.
20-
*/
21-
export const Live = Layer.effect(Tag, Definition);
17+
export namespace Cancellation {
18+
export const Tag = CancellationTag;
19+
export type Interface = CancellationTag;
20+
21+
/**
22+
* The live implementation Layer for the CancellationTokenProvider service.
23+
* This is a self-contained layer with no external dependencies.
24+
*/
25+
export const Live = Layer.effect(Tag, Definition);
26+
}

Source/Service/Cancellation/Service.ts

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,37 @@
44
* This service manages cancellation tokens for long-running RPC operations.
55
*/
66

7-
import { Context, type Effect } from "effect";
7+
import { Context, Scope, type Effect } from "effect";
88

99
import type { InvalidTokenIDError } from "./Error.js";
1010
import type { TokenAndScope } from "./Type.js";
1111

12-
/**
13-
* The service interface for the cancellation token provider.
14-
*/
15-
export interface Interface {
16-
/**
17-
* Acquires a CancellationToken for a given operation ID.
18-
* This returns a scoped Effect. When the scope is closed, the token
19-
* source is automatically disposed and cleaned up.
20-
* @param TokenID The numeric ID for the operation.
21-
* @returns A scoped `Effect` that resolves to a `TokenAndScope` object.
22-
*/
23-
readonly ObtainToken: (
24-
TokenID: number,
25-
) => Effect.Effect<TokenAndScope, InvalidTokenIDError, Effect.Scope>;
26-
27-
/**
28-
* Signals cancellation for a specific token ID.
29-
* @param TokenID The numeric ID of the operation to cancel.
30-
*/
31-
readonly CancelToken: (TokenID: number) => Effect.Effect<void, never>;
12+
export class Cancellation extends Context.Tag(
13+
"Service/CancellationTokenProvider",
14+
)<
15+
Cancellation,
16+
{
17+
/**
18+
* Acquires a CancellationToken for a given operation ID.
19+
* This returns a scoped Effect. When the scope is closed, the token
20+
* source is automatically disposed and cleaned up.
21+
* @param TokenID The numeric ID for the operation.
22+
* @returns A scoped `Effect` that resolves to a `TokenAndScope` object.
23+
*/
24+
readonly ObtainToken: (
25+
TokenID: number,
26+
) => Effect.Effect<TokenAndScope, InvalidTokenIDError, Scope.Scope>;
3227

33-
/**
34-
* Disposes of all currently managed cancellation token sources.
35-
* This is typically called during shutdown.
36-
*/
37-
readonly DisposeAll: () => Effect.Effect<void, never>;
38-
}
28+
/**
29+
* Signals cancellation for a specific token ID.
30+
* @param TokenID The numeric ID of the operation to cancel.
31+
*/
32+
readonly CancelToken: (TokenID: number) => Effect.Effect<void, never>;
3933

40-
/**
41-
* The Context.Tag for the CancellationTokenProvider service.
42-
*/
43-
export const Tag = Context.Tag<Interface>("Service/CancellationTokenProvider");
34+
/**
35+
* Disposes of all currently managed cancellation token sources.
36+
* This is typically called during shutdown.
37+
*/
38+
readonly DisposeAll: () => Effect.Effect<void, never>;
39+
}
40+
>() {}

Source/Service/IPC/Client.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@
88
import { Layer } from "effect";
99

1010
import { Acquire } from "./Client/Acquire.js";
11-
import { Tag, type Interface as ClientService } from "./Client/Service.js";
12-
import type { Configuration } from "./Configuration.js";
11+
import { Client as ClientTag } from "./Client/Service.js";
12+
import { Configuration } from "./Configuration.js";
1313
import type { gRPCConnectionError } from "./Error.js";
1414

15-
/**
16-
* The live implementation `Layer` for the gRPC Client service.
17-
*/
18-
export const Live: Layer.Layer<
19-
ClientService,
20-
gRPCConnectionError,
21-
Configuration
22-
> = Layer.scoped(Tag, Acquire);
15+
export namespace Client {
16+
export const Tag = ClientTag;
17+
export type Interface = ClientTag;
18+
19+
/**
20+
* The live implementation `Layer` for the gRPC Client service.
21+
*/
22+
export const Live: Layer.Layer<
23+
Interface,
24+
gRPCConnectionError,
25+
Configuration
26+
> = Layer.scoped(Tag, Acquire);
27+
}

Source/Service/IPC/Client/Acquire.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import { Effect } from "effect";
1616

1717
import { Configuration as ConfigurationService } from "../Configuration.js";
1818
import { gRPCConnectionError } from "../Error.js";
19-
import type { Interface as ClientService } from "./Service.js";
2019
import { Release } from "./Release.js";
20+
import type { Interface as ClientService } from "./Service.js";
2121

2222
/**
2323
* An `Effect` that loads the gRPC `.proto` file definition from disk.

Source/Service/IPC/Client/Service.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,13 @@ import { Context } from "effect";
1010
// from the `vine.proto` file.
1111
import type { MountainService } from "../Generated.js";
1212

13-
/**
14-
* The service interface for the raw, generated gRPC client.
15-
*
16-
* Higher-level IPC services will use this client to make calls to `Mountain`.
17-
*/
18-
export type Interface = MountainService;
19-
2013
/**
2114
* The `Context.Tag` for the gRPC client service.
2215
*
2316
* This tag is used by other services to declare their dependency on the raw
2417
* gRPC client.
2518
*/
26-
export const Tag = Context.Tag<Interface>("IPC/Client");
19+
export class Client extends Context.Tag("IPC/Client")<
20+
Client,
21+
MountainService
22+
>() {}

Source/Service/IPC/Dispatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { Layer } from "effect";
88

9-
import { Cancellation } from "../../Cancellation.js";
9+
import { Cancellation } from "../Cancellation.js";
1010
import { Definition } from "./Dispatcher/Definition.js";
1111
import { Dispatcher as DispatcherTag } from "./Dispatcher/Service.js";
1212
import { ProtocolAdapter } from "./ProtocolAdapter.js";

Source/Service/IPC/ProtocolAdapter.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@
77

88
import { Layer } from "effect";
99

10-
import { Live as LiveClient } from "./Client.js";
10+
import { Client } from "./Client.js";
1111
import { Definition } from "./ProtocolAdapter/Definition.js";
12-
import { Tag, type Interface } from "./ProtocolAdapter/Service.js";
12+
import { ProtocolAdapter as ProtocolAdapterTag } from "./ProtocolAdapter/Service.js";
1313

14-
// --- Public API Exports ---
15-
export { Tag, type Interface };
16-
17-
// --- Live Implementation ---
18-
19-
/**
20-
* The live implementation `Layer` for the `ProtocolAdapter` service.
21-
* It builds the adapter by composing its `Definition` with the
22-
* `LiveClient` layer it depends on for the underlying gRPC transport.
23-
*/
24-
export const Live = Layer.effect(Tag, Definition).pipe(
25-
Layer.provide(LiveClient),
26-
);
14+
export namespace ProtocolAdapter {
15+
export const Tag = ProtocolAdapterTag;
16+
export type Interface = ProtocolAdapterTag;
17+
/**
18+
* The live implementation `Layer` for the `ProtocolAdapter` service.
19+
*
20+
* This layer builds the adapter by composing its `Definition` with the
21+
* `LiveClient` layer it depends on for the underlying gRPC transport.
22+
*/
23+
export const Live = Layer.effect(Tag, Definition).pipe(
24+
Layer.provide(Client.Live),
25+
);
26+
}

Source/Service/IPC/Server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Server as ServerTag } from "./Server/Service.js";
1515
export namespace Server {
1616
export const Tag = ServerTag;
1717
export type Interface = ServerTag;
18+
1819
/**
1920
* The live implementation `Layer` for the gRPC Server service.
2021
*
@@ -25,7 +26,7 @@ export namespace Server {
2526
*/
2627
export const Live: Layer.Layer<
2728
Interface,
28-
gRPCConnectionError,
29+
gRPCConnectionError | Error,
2930
Configuration
3031
> = Layer.scoped(Tag, Acquire).pipe(Layer.provide(Dispatcher.Live));
3132
}

0 commit comments

Comments
 (0)