@shivaedev/service 0.6.0 → 0.6.2
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/CHANGELOG.md +12 -0
- package/README.md +78 -44
- package/dist/MethodProof.d.ts +9 -7
- package/dist/MethodProof.d.ts.map +1 -1
- package/dist/RequirementProof.d.ts +2 -2
- package/dist/RequirementProof.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/MethodProof.ts +10 -8
- package/src/RequirementProof.ts +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.2 - 2026-11-04
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Name the ordinary method and every missing service type in undeclared service requirement diagnostics, excluding declared dependencies and caller scope.
|
|
8
|
+
|
|
9
|
+
## 0.6.1 - 2026-11-02
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Accept native `Context.Reference` keys in `defineService` requirements without treating their `never` identifier as caller-owned `Scope.Scope`.
|
|
14
|
+
|
|
3
15
|
## 0.6.0 - 2026-10-30
|
|
4
16
|
|
|
5
17
|
### Added
|
package/README.md
CHANGED
|
@@ -7,13 +7,16 @@ Declare an Effect service's dependencies once, initialize its private state, and
|
|
|
7
7
|
A service should explain what it needs and what callers can do with it. When every method binds dependencies separately, that explanation is scattered across setup and application code. Put the dependency list, private initialization and public methods in one definition:
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
+
import { defineService } from "@shivaedev/service/defineService.ts";
|
|
11
|
+
import { greet } from "#greetings/greet.ts";
|
|
12
|
+
import { initializeGreetings } from "#greetings/initialize.ts";
|
|
13
|
+
import { Prefix } from "#Prefix.ts";
|
|
14
|
+
|
|
10
15
|
const Greetings = defineService({
|
|
11
16
|
id: "app/Greetings",
|
|
12
17
|
requires: [Prefix],
|
|
13
|
-
initialize:
|
|
14
|
-
methods: (state) => ({
|
|
15
|
-
greet: Effect.fn("Greetings.greet")((name: string) => Effect.map(Prefix, (prefix) => `${prefix}${name}${state.punctuation}`)),
|
|
16
|
-
}),
|
|
18
|
+
initialize: initializeGreetings,
|
|
19
|
+
methods: (state) => ({ greet: greet(state) }),
|
|
17
20
|
});
|
|
18
21
|
```
|
|
19
22
|
|
|
@@ -36,22 +39,51 @@ The work has three parts:
|
|
|
36
39
|
2. Once per service, define its dependency list, private state and methods.
|
|
37
40
|
3. In application code, yield the service and call its methods.
|
|
38
41
|
|
|
39
|
-
### 1.
|
|
42
|
+
### 1. Assemble the service from focused files
|
|
43
|
+
|
|
44
|
+
A service file is an inventory: a top-level `defineService` call lists `id`, `requires`, `initialize` and `methods` directly. Keep each initializer and operation in its own focused file and import its named export. Use `Effect.void` when there is no initialization state. The methods arrow returns the operation record directly. Each entry names an imported operation or calls an imported state factory; `genericMethod` may wrap either form.
|
|
45
|
+
|
|
46
|
+
Quality enforces this with `effect/service-definition-assembly`, including adapters and tests. It rejects a configuration hidden behind a variable, spreads or computed configuration members, hidden method records, inline operations, local implementation references and implementation bodies beside the assembly. Keep setup inside the imported initializer or operation factory. Existing Quality span checks apply to the implementations: use names such as `Greetings.greet`, with an operation matching its binding. No second span convention applies to service assemblies.
|
|
47
|
+
|
|
48
|
+
The initializer module exports its Effect:
|
|
40
49
|
|
|
41
50
|
```ts
|
|
42
|
-
import {
|
|
43
|
-
|
|
51
|
+
import { Effect } from "effect";
|
|
52
|
+
|
|
53
|
+
export const initializeGreetings = Effect.succeed({ punctuation: "!" });
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The operation module exports a state factory; its returned function is the public method:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { Effect } from "effect";
|
|
60
|
+
import { Prefix } from "#Prefix.ts";
|
|
61
|
+
|
|
62
|
+
export function greet(state: { readonly punctuation: string }) {
|
|
63
|
+
return Effect.fn("Greetings.greet")((name: string) => Effect.map(Prefix, (prefix) => `${prefix}${name}${state.punctuation}`));
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The service module imports those exports. `Prefix` is a separate native Context service:
|
|
44
68
|
|
|
45
|
-
|
|
69
|
+
```ts
|
|
70
|
+
import { defineService } from "@shivaedev/service/defineService.ts";
|
|
71
|
+
import { greet } from "#greetings/greet.ts";
|
|
72
|
+
import { initializeGreetings } from "#greetings/initialize.ts";
|
|
73
|
+
import { Prefix } from "#Prefix.ts";
|
|
46
74
|
|
|
47
75
|
const Greetings = defineService({
|
|
48
76
|
id: "app/Greetings",
|
|
49
77
|
requires: [Prefix],
|
|
50
|
-
initialize:
|
|
51
|
-
methods: (state) => ({
|
|
52
|
-
greet: Effect.fn("Greetings.greet")((name: string) => Effect.map(Prefix, (prefix) => `${prefix}${name}${state.punctuation}`)),
|
|
53
|
-
}),
|
|
78
|
+
initialize: initializeGreetings,
|
|
79
|
+
methods: (state) => ({ greet: greet(state) }),
|
|
54
80
|
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Application code composes its Layer and calls the service:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { Effect, Layer } from "effect";
|
|
55
87
|
|
|
56
88
|
const live = Greetings.layer.pipe(Layer.provide(Layer.succeed(Prefix, "Hello ")));
|
|
57
89
|
|
|
@@ -63,7 +95,9 @@ const greeting = Effect.gen(function* () {
|
|
|
63
95
|
|
|
64
96
|
The Layer needs `Prefix`. After a caller resolves `Greetings`, `greet` needs no `Prefix` from that caller. Its argument and success type are inferred from the method. An initialization failure fails the Layer build with that typed error and runs finalizers registered before the failure. A method failure reaches the caller as that method's typed error, and the same service instance can handle the next call. A method defect remains a defect.
|
|
65
97
|
|
|
66
|
-
`initialize` and ordinary methods may require the listed services and `Scope.Scope`. The compiler rejects a definition that reads another service. Scope is supplied by the Layer or method caller, so `Scope.Scope` itself cannot appear in `requires`.
|
|
98
|
+
`initialize` and ordinary methods may require the listed services and `Scope.Scope`. The compiler rejects a definition that reads another service. For an ordinary method, the diagnostic names the method and its missing service types, such as `MethodHasUndeclaredServiceRequirements<"write", Reactivity>`. When a method is missing several services, the diagnostic includes their union; declared services and caller scope are excluded. Scope is supplied by the Layer or method caller, so `Scope.Scope` itself cannot appear in `requires`.
|
|
99
|
+
|
|
100
|
+
Native `Context.Reference` keys can appear in `requires`. Their defaults make them requirement-free in Effect, so they add no Layer dependency, including when the reference's value is a scope.
|
|
67
101
|
|
|
68
102
|
Return functions from the method factory, each producing an Effect. An Effect value placed directly in the method record is rejected. Keep the private state behind those functions; it is absent from the emitted public service declaration.
|
|
69
103
|
|
|
@@ -73,7 +107,6 @@ For an explicitly annotated `Effect.fn` generator, `ServiceRequirements` express
|
|
|
73
107
|
|
|
74
108
|
```ts
|
|
75
109
|
import { Context, Effect, type Scope } from "effect";
|
|
76
|
-
import { defineService } from "@shivaedev/service/defineService.ts";
|
|
77
110
|
import type { ServiceRequirements } from "@shivaedev/service/ServiceRequirements.ts";
|
|
78
111
|
|
|
79
112
|
class Declared extends Context.Service<Declared, { readonly identity: object }>()("app/Declared") {}
|
|
@@ -87,20 +120,11 @@ type Requirements<Success, Failure = never, CallerScope extends Scope.Scope = ne
|
|
|
87
120
|
CallerScope
|
|
88
121
|
>;
|
|
89
122
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}> {
|
|
96
|
-
return { identity: (yield* Declared).identity };
|
|
97
|
-
})(),
|
|
98
|
-
methods: (state) => ({
|
|
99
|
-
sameIdentity: Effect.fn("Identity.sameIdentity")(function* (): Requirements<boolean> {
|
|
100
|
-
return (yield* Declared).identity === state.identity;
|
|
101
|
-
}),
|
|
102
|
-
}),
|
|
103
|
-
});
|
|
123
|
+
export const initializeIdentity = Effect.fn("Identity.initializeIdentity")(function* (): Requirements<{
|
|
124
|
+
readonly identity: object;
|
|
125
|
+
}> {
|
|
126
|
+
return { identity: (yield* Declared).identity };
|
|
127
|
+
})();
|
|
104
128
|
```
|
|
105
129
|
|
|
106
130
|
The alias is `Effect.fn.Return<Success, Failure, declared services | CallerScope>`. The last parameter can be `Scope.Scope` when the generator acquires a scoped resource; it cannot add another undeclared service. The annotation describes the implementation's requirements. `defineService` removes declared dependencies from the public methods' requirements.
|
|
@@ -109,18 +133,15 @@ The alias is `Effect.fn.Return<Success, Failure, declared services | CallerScope
|
|
|
109
133
|
|
|
110
134
|
```ts
|
|
111
135
|
import { Effect, Ref } from "effect";
|
|
112
|
-
import { defineService } from "@shivaedev/service/defineService.ts";
|
|
113
136
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
methods: (released) => ({
|
|
119
|
-
open: Effect.fn("Resources.open")(() => Effect.acquireRelease(Effect.succeed("resource"), () => Ref.update(released, (count) => count + 1))),
|
|
120
|
-
released: Effect.fn("Resources.released")(() => Ref.get(released)),
|
|
121
|
-
}),
|
|
122
|
-
});
|
|
137
|
+
export function open(released: Ref.Ref<number>) {
|
|
138
|
+
return Effect.fn("Resources.open")(() => Effect.acquireRelease(Effect.succeed("resource"), () => Ref.update(released, (count) => count + 1)));
|
|
139
|
+
}
|
|
140
|
+
```
|
|
123
141
|
|
|
142
|
+
The assembly imports `open`, a `released` state factory and an initializer that returns `Ref.make(0)`. Its methods inventory is `(state) => ({ open: open(state), released: released(state) })`. A caller can then use:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
124
145
|
const useResource = Effect.gen(function* () {
|
|
125
146
|
const resources = yield* Resources;
|
|
126
147
|
yield* Effect.scoped(resources.open());
|
|
@@ -147,17 +168,24 @@ The replacement provides the public method interface, rather than private initia
|
|
|
147
168
|
Ordinary methods have one concrete signature. `genericMethod` marks a generic function so its success, failure and caller requirements remain related at each call:
|
|
148
169
|
|
|
149
170
|
```ts
|
|
150
|
-
import {
|
|
151
|
-
import { defineService } from "@shivaedev/service/defineService.ts";
|
|
152
|
-
import { genericMethod } from "@shivaedev/service/genericMethod.ts";
|
|
153
|
-
|
|
154
|
-
class Caller extends Context.Service<Caller, { readonly value: number }>()("app/Caller") {}
|
|
171
|
+
import { Effect } from "effect";
|
|
155
172
|
|
|
156
|
-
const preserve = Effect.fn("Generic.preserve")(
|
|
173
|
+
export const preserve = Effect.fn("Generic.preserve")(
|
|
157
174
|
<Success, Failure, Requirements>(
|
|
158
175
|
effect: Effect.Effect<Success, Failure, Requirements>,
|
|
159
176
|
): Effect.Effect<{ readonly value: Success }, Failure, Requirements> => Effect.map(effect, (value) => ({ value })),
|
|
160
177
|
);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The assembly imports the generic operation and marks it in the inventory:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import { Context, Effect } from "effect";
|
|
184
|
+
import { defineService } from "@shivaedev/service/defineService.ts";
|
|
185
|
+
import { genericMethod } from "@shivaedev/service/genericMethod.ts";
|
|
186
|
+
import { preserve } from "#preserve.ts";
|
|
187
|
+
|
|
188
|
+
class Caller extends Context.Service<Caller, { readonly value: number }>()("app/Caller") {}
|
|
161
189
|
|
|
162
190
|
const Generic = defineService({
|
|
163
191
|
id: "app/Generic",
|
|
@@ -165,7 +193,11 @@ const Generic = defineService({
|
|
|
165
193
|
initialize: Effect.void,
|
|
166
194
|
methods: () => ({ preserve: genericMethod(preserve) }),
|
|
167
195
|
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
A caller keeps the generic operation's requirements:
|
|
168
199
|
|
|
200
|
+
```ts
|
|
169
201
|
const preserved = Effect.gen(function* () {
|
|
170
202
|
const service = yield* Generic;
|
|
171
203
|
return yield* service.preserve(Effect.map(Caller, ({ value }) => value));
|
|
@@ -174,6 +206,8 @@ const preserved = Effect.gen(function* () {
|
|
|
174
206
|
|
|
175
207
|
The wrapped Effect still requires `Caller`, which the caller supplies separately. The generic service's Layer has no declared dependencies. The emitted declarations preserve the generic success and failure types as well as caller requirements.
|
|
176
208
|
|
|
209
|
+
A generic method's remaining requirements follow native Effect rules: execution without them is a compile error. Composing the call into a Layer retains those dependencies until they are provided; an initializer cannot use the call unless its service declares those dependencies.
|
|
210
|
+
|
|
177
211
|
Marked generic methods also work with declared initializer dependencies. Their signatures keep all caller requirements unchanged; unlike ordinary methods, they do not subtract declared dependencies from the public signature. The compiler rejects an unmarked generic that correlates its input and returned Effect, and a structurally distinct two-signature overload; it may not reject other generic or overloaded signatures. Use initialization state to close over bound dependencies in generic methods.
|
|
178
212
|
|
|
179
213
|
Dependency binding wraps the method's outer returned Effect. A Stream returned as its value retains its own service requirements; provide them when running the Stream.
|
package/dist/MethodProof.d.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import type { Effect, Scope } from "effect";
|
|
2
|
-
import type { AnyMethod, GenericMethodDescriptor, GenericOrStructurallyOverloadedMethodsAreUnsupported, HasDistinctCallSignatures } from "
|
|
3
|
-
import type { RequirementRecord, RequirementsOf } from "
|
|
2
|
+
import type { AnyMethod, GenericMethodDescriptor, GenericOrStructurallyOverloadedMethodsAreUnsupported, HasDistinctCallSignatures } from "#genericMethod.ts";
|
|
3
|
+
import type { RequirementRecord, RequirementsOf } from "#ServiceRequirements.ts";
|
|
4
4
|
export type MethodEntry = AnyMethod | GenericMethodDescriptor<AnyMethod>;
|
|
5
5
|
export type MethodRecord = Readonly<Record<string, AnyMethod>>;
|
|
6
6
|
export type MethodInventory = Readonly<Record<string, unknown>>;
|
|
7
7
|
export type RuntimeMethodInventory = Readonly<Record<string, MethodEntry>>;
|
|
8
8
|
type MethodRequirements<TMethod> = TMethod extends (...arguments_: readonly never[]) => Effect.Effect<unknown, unknown, infer TRequirements> ? TRequirements : never;
|
|
9
|
-
interface MethodHasUndeclaredServiceRequirements {
|
|
9
|
+
interface MethodHasUndeclaredServiceRequirements<TName extends PropertyKey, TMissing> {
|
|
10
10
|
readonly _serviceDefinitionError: "method has service requirements absent from the service declaration";
|
|
11
|
+
readonly method: TName;
|
|
12
|
+
readonly missingServices: TMissing;
|
|
11
13
|
}
|
|
12
14
|
interface ServiceMembersMustBeMethods {
|
|
13
15
|
readonly _serviceDefinitionError: "service members must be methods";
|
|
14
16
|
}
|
|
15
|
-
type OrdinaryMethodProof<TMethod, TRequirements extends RequirementRecord> = [
|
|
17
|
+
type OrdinaryMethodProof<TName extends PropertyKey, TMethod, TRequirements extends RequirementRecord> = [
|
|
16
18
|
Exclude<MethodRequirements<TMethod>, RequirementsOf<TRequirements> | Scope.Scope>
|
|
17
|
-
] extends [never] ? TMethod : MethodHasUndeclaredServiceRequirements
|
|
18
|
-
type SupportedMethod<TMethod, TRequirements extends RequirementRecord> = TMethod extends GenericMethodDescriptor<AnyMethod> ? TMethod : TMethod extends AnyMethod ? HasDistinctCallSignatures<TMethod> extends true ? GenericOrStructurallyOverloadedMethodsAreUnsupported : TMethod extends (...arguments_: infer TArguments) => infer TResult ? ((...arguments_: TArguments) => TResult) extends TMethod ? OrdinaryMethodProof<TMethod, TRequirements> : GenericOrStructurallyOverloadedMethodsAreUnsupported : GenericOrStructurallyOverloadedMethodsAreUnsupported : ServiceMembersMustBeMethods;
|
|
19
|
+
] extends [never] ? TMethod : MethodHasUndeclaredServiceRequirements<TName, Exclude<MethodRequirements<TMethod>, RequirementsOf<TRequirements> | Scope.Scope>>;
|
|
20
|
+
type SupportedMethod<TName extends PropertyKey, TMethod, TRequirements extends RequirementRecord> = TMethod extends GenericMethodDescriptor<AnyMethod> ? TMethod : TMethod extends AnyMethod ? HasDistinctCallSignatures<TMethod> extends true ? GenericOrStructurallyOverloadedMethodsAreUnsupported : TMethod extends (...arguments_: infer TArguments) => infer TResult ? ((...arguments_: TArguments) => TResult) extends TMethod ? OrdinaryMethodProof<TName, TMethod, TRequirements> : GenericOrStructurallyOverloadedMethodsAreUnsupported : GenericOrStructurallyOverloadedMethodsAreUnsupported : ServiceMembersMustBeMethods;
|
|
19
21
|
export type MethodProof<TMethods extends MethodInventory, TRequirements extends RequirementRecord> = {
|
|
20
|
-
readonly [TName in keyof TMethods]: SupportedMethod<TMethods[TName], TRequirements>;
|
|
22
|
+
readonly [TName in keyof TMethods]: SupportedMethod<TName, TMethods[TName], TRequirements>;
|
|
21
23
|
};
|
|
22
24
|
export {};
|
|
23
25
|
//# sourceMappingURL=MethodProof.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MethodProof.d.ts","sourceRoot":"","sources":["../src/MethodProof.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EACX,SAAS,EACT,uBAAuB,EACvB,oDAAoD,EACpD,yBAAyB,EACzB,MAAM,
|
|
1
|
+
{"version":3,"file":"MethodProof.d.ts","sourceRoot":"","sources":["../src/MethodProof.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EACX,SAAS,EACT,uBAAuB,EACvB,oDAAoD,EACpD,yBAAyB,EACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEjF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;AAEzE,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAE/D,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEhE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;AAE3E,KAAK,kBAAkB,CAAC,OAAO,IAAI,OAAO,SAAS,CAAC,GAAG,UAAU,EAAE,SAAS,KAAK,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC,GACzI,aAAa,GACb,KAAK,CAAC;AAET,UAAU,sCAAsC,CAAC,KAAK,SAAS,WAAW,EAAE,QAAQ;IACnF,QAAQ,CAAC,uBAAuB,EAAE,qEAAqE,CAAC;IACxG,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC;CACnC;AAED,UAAU,2BAA2B;IACpC,QAAQ,CAAC,uBAAuB,EAAE,iCAAiC,CAAC;CACpE;AAED,KAAK,mBAAmB,CAAC,KAAK,SAAS,WAAW,EAAE,OAAO,EAAE,aAAa,SAAS,iBAAiB,IAAI;IACvG,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;CACjF,SAAS,CAAC,KAAK,CAAC,GACd,OAAO,GACP,sCAAsC,CAAC,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAEpI,KAAK,eAAe,CAAC,KAAK,SAAS,WAAW,EAAE,OAAO,EAAE,aAAa,SAAS,iBAAiB,IAC/F,OAAO,SAAS,uBAAuB,CAAC,SAAS,CAAC,GAC/C,OAAO,GACP,OAAO,SAAS,SAAS,GACxB,yBAAyB,CAAC,OAAO,CAAC,SAAS,IAAI,GAC9C,oDAAoD,GACpD,OAAO,SAAS,CAAC,GAAG,UAAU,EAAE,MAAM,UAAU,KAAK,MAAM,OAAO,GACjE,CAAC,CAAC,GAAG,UAAU,EAAE,UAAU,KAAK,OAAO,CAAC,SAAS,OAAO,GACvD,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,CAAC,GAClD,oDAAoD,GACrD,oDAAoD,GACtD,2BAA2B,CAAC;AAEjC,MAAM,MAAM,WAAW,CAAC,QAAQ,SAAS,eAAe,EAAE,aAAa,SAAS,iBAAiB,IAAI;IACpG,QAAQ,EAAE,KAAK,IAAI,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAC1F,CAAC"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Context, Scope } from "effect";
|
|
2
|
-
import type { RequirementRecord } from "
|
|
2
|
+
import type { RequirementRecord } from "#ServiceRequirements.ts";
|
|
3
3
|
interface ScopeCannotBeDeclaredAsServiceRequirement {
|
|
4
4
|
readonly _serviceDefinitionError: "Scope.Scope is caller-owned and cannot be declared as a service requirement";
|
|
5
5
|
}
|
|
6
|
-
type ProveRequirement<TRequirement> = TRequirement extends Context.Service.Any ? Context.Service.Identifier<TRequirement> extends Scope.Scope ? ScopeCannotBeDeclaredAsServiceRequirement : TRequirement : never;
|
|
6
|
+
type ProveRequirement<TRequirement> = TRequirement extends Context.Service.Any ? [Context.Service.Identifier<TRequirement>] extends [never] ? TRequirement : Context.Service.Identifier<TRequirement> extends Scope.Scope ? ScopeCannotBeDeclaredAsServiceRequirement : TRequirement : never;
|
|
7
7
|
export type RequirementProof<TRequirements extends RequirementRecord> = {
|
|
8
8
|
readonly [TIndex in keyof TRequirements]: ProveRequirement<TRequirements[TIndex]>;
|
|
9
9
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequirementProof.d.ts","sourceRoot":"","sources":["../src/RequirementProof.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"RequirementProof.d.ts","sourceRoot":"","sources":["../src/RequirementProof.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,UAAU,yCAAyC;IAClD,QAAQ,CAAC,uBAAuB,EAAE,6EAA6E,CAAC;CAChH;AAED,KAAK,gBAAgB,CAAC,YAAY,IAAI,YAAY,SAAS,OAAO,CAAC,OAAO,CAAC,GAAG,GAC3E,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACzD,YAAY,GACZ,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,SAAS,KAAK,CAAC,KAAK,GAC3D,yCAAyC,GACzC,YAAY,GACd,KAAK,CAAC;AAET,MAAM,MAAM,gBAAgB,CAAC,aAAa,SAAS,iBAAiB,IAAI;IACvE,QAAQ,EAAE,MAAM,IAAI,MAAM,aAAa,GAAG,gBAAgB,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;CACjF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/service",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Declared Effect services with scoped initialization and bound dependencies",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
],
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@effect/vitest": "4.0.6",
|
|
47
|
-
"@shivaedev/quality": "0.
|
|
48
|
-
"@shivaedev/test": "0.
|
|
49
|
-
"@shivaedev/test-story": "0.
|
|
47
|
+
"@shivaedev/quality": "0.58.0",
|
|
48
|
+
"@shivaedev/test": "0.14.0",
|
|
49
|
+
"@shivaedev/test-story": "0.37.0",
|
|
50
50
|
"@types/node": "24.19.2",
|
|
51
51
|
"effect": "4.0.6",
|
|
52
52
|
"typescript": "7.0.2",
|
package/src/MethodProof.ts
CHANGED
|
@@ -4,8 +4,8 @@ import type {
|
|
|
4
4
|
GenericMethodDescriptor,
|
|
5
5
|
GenericOrStructurallyOverloadedMethodsAreUnsupported,
|
|
6
6
|
HasDistinctCallSignatures,
|
|
7
|
-
} from "
|
|
8
|
-
import type { RequirementRecord, RequirementsOf } from "
|
|
7
|
+
} from "#genericMethod.ts";
|
|
8
|
+
import type { RequirementRecord, RequirementsOf } from "#ServiceRequirements.ts";
|
|
9
9
|
|
|
10
10
|
export type MethodEntry = AnyMethod | GenericMethodDescriptor<AnyMethod>;
|
|
11
11
|
|
|
@@ -19,21 +19,23 @@ type MethodRequirements<TMethod> = TMethod extends (...arguments_: readonly neve
|
|
|
19
19
|
? TRequirements
|
|
20
20
|
: never;
|
|
21
21
|
|
|
22
|
-
interface MethodHasUndeclaredServiceRequirements {
|
|
22
|
+
interface MethodHasUndeclaredServiceRequirements<TName extends PropertyKey, TMissing> {
|
|
23
23
|
readonly _serviceDefinitionError: "method has service requirements absent from the service declaration";
|
|
24
|
+
readonly method: TName;
|
|
25
|
+
readonly missingServices: TMissing;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
interface ServiceMembersMustBeMethods {
|
|
27
29
|
readonly _serviceDefinitionError: "service members must be methods";
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
type OrdinaryMethodProof<TMethod, TRequirements extends RequirementRecord> = [
|
|
32
|
+
type OrdinaryMethodProof<TName extends PropertyKey, TMethod, TRequirements extends RequirementRecord> = [
|
|
31
33
|
Exclude<MethodRequirements<TMethod>, RequirementsOf<TRequirements> | Scope.Scope>,
|
|
32
34
|
] extends [never]
|
|
33
35
|
? TMethod
|
|
34
|
-
: MethodHasUndeclaredServiceRequirements
|
|
36
|
+
: MethodHasUndeclaredServiceRequirements<TName, Exclude<MethodRequirements<TMethod>, RequirementsOf<TRequirements> | Scope.Scope>>;
|
|
35
37
|
|
|
36
|
-
type SupportedMethod<TMethod, TRequirements extends RequirementRecord> =
|
|
38
|
+
type SupportedMethod<TName extends PropertyKey, TMethod, TRequirements extends RequirementRecord> =
|
|
37
39
|
TMethod extends GenericMethodDescriptor<AnyMethod>
|
|
38
40
|
? TMethod
|
|
39
41
|
: TMethod extends AnyMethod
|
|
@@ -41,11 +43,11 @@ type SupportedMethod<TMethod, TRequirements extends RequirementRecord> =
|
|
|
41
43
|
? GenericOrStructurallyOverloadedMethodsAreUnsupported
|
|
42
44
|
: TMethod extends (...arguments_: infer TArguments) => infer TResult
|
|
43
45
|
? ((...arguments_: TArguments) => TResult) extends TMethod
|
|
44
|
-
? OrdinaryMethodProof<TMethod, TRequirements>
|
|
46
|
+
? OrdinaryMethodProof<TName, TMethod, TRequirements>
|
|
45
47
|
: GenericOrStructurallyOverloadedMethodsAreUnsupported
|
|
46
48
|
: GenericOrStructurallyOverloadedMethodsAreUnsupported
|
|
47
49
|
: ServiceMembersMustBeMethods;
|
|
48
50
|
|
|
49
51
|
export type MethodProof<TMethods extends MethodInventory, TRequirements extends RequirementRecord> = {
|
|
50
|
-
readonly [TName in keyof TMethods]: SupportedMethod<TMethods[TName], TRequirements>;
|
|
52
|
+
readonly [TName in keyof TMethods]: SupportedMethod<TName, TMethods[TName], TRequirements>;
|
|
51
53
|
};
|
package/src/RequirementProof.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { Context, Scope } from "effect";
|
|
2
|
-
import type { RequirementRecord } from "
|
|
2
|
+
import type { RequirementRecord } from "#ServiceRequirements.ts";
|
|
3
3
|
|
|
4
4
|
interface ScopeCannotBeDeclaredAsServiceRequirement {
|
|
5
5
|
readonly _serviceDefinitionError: "Scope.Scope is caller-owned and cannot be declared as a service requirement";
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
type ProveRequirement<TRequirement> = TRequirement extends Context.Service.Any
|
|
9
|
-
? Context.Service.Identifier<TRequirement> extends
|
|
10
|
-
?
|
|
11
|
-
: TRequirement
|
|
9
|
+
? [Context.Service.Identifier<TRequirement>] extends [never]
|
|
10
|
+
? TRequirement
|
|
11
|
+
: Context.Service.Identifier<TRequirement> extends Scope.Scope
|
|
12
|
+
? ScopeCannotBeDeclaredAsServiceRequirement
|
|
13
|
+
: TRequirement
|
|
12
14
|
: never;
|
|
13
15
|
|
|
14
16
|
export type RequirementProof<TRequirements extends RequirementRecord> = {
|