@shivaedev/effect-service 0.0.0 → 0.1.0
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 +10 -0
- package/LICENSE +21 -0
- package/README.md +51 -1
- package/dist/define-service.d.ts +23 -0
- package/dist/define-service.d.ts.map +1 -0
- package/dist/define-service.js +15 -0
- package/dist/define-service.js.map +1 -0
- package/dist/generic-method.d.ts +18 -0
- package/dist/generic-method.d.ts.map +1 -0
- package/dist/generic-method.js +4 -0
- package/dist/generic-method.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/initializer-proof.d.ts +7 -0
- package/dist/initializer-proof.d.ts.map +1 -0
- package/dist/initializer-proof.js +2 -0
- package/dist/initializer-proof.js.map +1 -0
- package/dist/method-proof.d.ts +26 -0
- package/dist/method-proof.d.ts.map +1 -0
- package/dist/method-proof.js +2 -0
- package/dist/method-proof.js.map +1 -0
- package/dist/requirement-proof.d.ts +11 -0
- package/dist/requirement-proof.d.ts.map +1 -0
- package/dist/requirement-proof.js +2 -0
- package/dist/requirement-proof.js.map +1 -0
- package/dist/service-requirements.d.ts +5 -0
- package/dist/service-requirements.d.ts.map +1 -0
- package/dist/service-requirements.js +2 -0
- package/dist/service-requirements.js.map +1 -0
- package/package.json +49 -4
- package/src/define-service.ts +65 -0
- package/src/generic-method.ts +33 -0
- package/src/index.ts +3 -0
- package/src/initializer-proof.ts +12 -0
- package/src/method-proof.ts +57 -0
- package/src/requirement-proof.ts +16 -0
- package/src/service-requirements.ts +12 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-09-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Declare service dependencies once and bind them in scoped initialization and
|
|
8
|
+
ordinary Effect methods.
|
|
9
|
+
- Preserve typed initialization failures, method failures, caller scopes, and
|
|
10
|
+
explicitly marked generic methods.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ShivaeDev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
1
|
# @shivaedev/effect-service
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Declare an Effect service's dependencies once. Its Layer initializes private state, binds those dependencies to the public methods, and releases initialization resources when the Layer's scope closes. Methods remain ordinary Effects with their own success and failure types.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Context, Effect, Layer } from "effect";
|
|
7
|
+
import { defineService } from "@shivaedev/effect-service";
|
|
8
|
+
|
|
9
|
+
class Prefix extends Context.Service<Prefix, string>()("app/Prefix") {}
|
|
10
|
+
|
|
11
|
+
const Greetings = defineService({
|
|
12
|
+
id: "app/Greetings",
|
|
13
|
+
requires: [Prefix],
|
|
14
|
+
initialize: Effect.succeed({ punctuation: "!" }),
|
|
15
|
+
methods: (state) => ({
|
|
16
|
+
greet: (name: string) =>
|
|
17
|
+
Effect.map(Prefix, (prefix) => `${prefix}${name}${state.punctuation}`),
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const live = Greetings.layer.pipe(
|
|
22
|
+
Layer.provide(Layer.succeed(Prefix, "Hello ")),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const greeting = Effect.gen(function* () {
|
|
26
|
+
const greetings = yield* Greetings;
|
|
27
|
+
return yield* greetings.greet("Ada");
|
|
28
|
+
}).pipe(Effect.provide(live));
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`Greetings` is a native Context service. `Greetings.layer` requires `Prefix`; a resolved service's `greet` method does not. Tests can replace the service directly with `Layer.succeed(Greetings, { greet: ... })`.
|
|
32
|
+
|
|
33
|
+
## Contract
|
|
34
|
+
|
|
35
|
+
- `initialize` and ordinary methods may use only declared services, plus `Scope.Scope`.
|
|
36
|
+
- Initialization scope belongs to the Layer. A method's scope belongs to its caller and remains visible in its type. Scope cannot be a declared dependency.
|
|
37
|
+
- Initializer failures remain in the Layer error type; method failures remain on each method.
|
|
38
|
+
- Private initialization state is available to the method factory and is not part of the public service interface.
|
|
39
|
+
- Dependency identifiers must be unique, following normal Effect Context conventions.
|
|
40
|
+
|
|
41
|
+
For named `Effect.fn` generator return annotations, `ServiceRequirements<typeof requirements, Success, Failure, Scope.Scope>` describes the declared requirements and optional caller scope. It is optional when inference is enough.
|
|
42
|
+
|
|
43
|
+
## Generic methods
|
|
44
|
+
|
|
45
|
+
TypeScript cannot generally subtract dependencies from arbitrary generic function signatures without losing their relationships. Services with an empty `requires` list may mark a generic method with `genericMethod(fn)` to preserve its full signature, including caller requirements. Generic methods with declared dependencies and structurally distinct overloads are rejected. Prefer ordinary monomorphic methods for application services.
|
|
46
|
+
|
|
47
|
+
## Scope
|
|
48
|
+
|
|
49
|
+
This package only defines services. It does not register RPC operations, manage transactions, add retries, or change Effect's Layer lifetime and memoization rules. It targets the workspace's Effect 4 release candidate; no Effect 3 compatibility is claimed.
|
|
50
|
+
|
|
51
|
+
## Validation
|
|
52
|
+
|
|
53
|
+
`pnpm ready` checks formatting, both TypeScript compilers, runtime binding/lifetime behavior, positive and negative compiler fixtures, declaration generation, and an installed tarball consumer. Compiler fixtures cover undeclared dependencies, caller scopes, generic signatures, invalid members, and the public/private boundary.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import type { GenericMethodDescriptor } from "./generic-method.ts";
|
|
3
|
+
import type { InitializerProof } from "./initializer-proof.ts";
|
|
4
|
+
import type { MethodInventory, MethodProof } from "./method-proof.ts";
|
|
5
|
+
import type { RequirementProof } from "./requirement-proof.ts";
|
|
6
|
+
import type { RequirementRecord, RequirementsOf } from "./service-requirements.ts";
|
|
7
|
+
type BoundMethod<Method, Requirements> = Method extends (...arguments_: infer Arguments) => Effect.Effect<infer Success, infer Failure, infer Residual> ? (...arguments_: Arguments) => Effect.Effect<Success, Failure, Exclude<Residual, Requirements>> : never;
|
|
8
|
+
type PublicMethod<Entry, Requirements> = Entry extends GenericMethodDescriptor<infer Method> ? Method : BoundMethod<Entry, Requirements>;
|
|
9
|
+
type ServiceShape<Methods extends MethodInventory, Requirements extends RequirementRecord> = Readonly<{
|
|
10
|
+
[Name in keyof Methods]: PublicMethod<Methods[Name], RequirementsOf<Requirements>>;
|
|
11
|
+
}>;
|
|
12
|
+
interface ServiceDefinition<Identifier extends string, Requirements extends RequirementRecord, Initializer extends Effect.Effect<unknown, unknown, unknown>, Methods extends MethodInventory> {
|
|
13
|
+
readonly id: Identifier;
|
|
14
|
+
readonly initialize: Initializer & InitializerProof<Initializer, RequirementsOf<Requirements>>;
|
|
15
|
+
readonly methods: (state: Effect.Success<Initializer>) => Methods & MethodProof<NoInfer<Methods>, Requirements>;
|
|
16
|
+
readonly requires: Requirements & RequirementProof<Requirements>;
|
|
17
|
+
}
|
|
18
|
+
type DefinedService<Identifier extends string, Shape, Failure, Requirements> = Context.Service<Identifier, Shape> & {
|
|
19
|
+
readonly layer: Layer.Layer<Identifier, Failure, Requirements>;
|
|
20
|
+
};
|
|
21
|
+
export declare function defineService<const Identifier extends string, const Requirements extends RequirementRecord, const Initializer extends Effect.Effect<unknown, unknown, unknown>, const Methods extends MethodInventory>(definition: ServiceDefinition<Identifier, Requirements, Initializer, Methods>): DefinedService<Identifier, ServiceShape<Methods, Requirements>, Effect.Error<Initializer>, RequirementsOf<Requirements>>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=define-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-service.d.ts","sourceRoot":"","sources":["../src/define-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAa,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAe,eAAe,EAAE,WAAW,EAAwC,MAAM,mBAAmB,CAAC;AACzH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEnF,KAAK,WAAW,CAAC,MAAM,EAAE,YAAY,IAAI,MAAM,SAAS,CACvD,GAAG,UAAU,EAAE,MAAM,SAAS,KAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,OAAO,EAAE,MAAM,OAAO,EAAE,MAAM,QAAQ,CAAC,GAC7D,CAAC,GAAG,UAAU,EAAE,SAAS,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,GAC9F,KAAK,CAAC;AAET,KAAK,YAAY,CAAC,KAAK,EAAE,YAAY,IAAI,KAAK,SAAS,uBAAuB,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GAAG,WAAW,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;AAEzI,KAAK,YAAY,CAAC,OAAO,SAAS,eAAe,EAAE,YAAY,SAAS,iBAAiB,IAAI,QAAQ,CAAC;KACpG,IAAI,IAAI,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;CAClF,CAAC,CAAC;AAEH,UAAU,iBAAiB,CAC1B,UAAU,SAAS,MAAM,EACzB,YAAY,SAAS,iBAAiB,EACtC,WAAW,SAAS,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAC5D,OAAO,SAAS,eAAe;IAE/B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,WAAW,GAAG,gBAAgB,CAAC,WAAW,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;IAC/F,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC;IAChH,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAC;CACjE;AASD,KAAK,cAAc,CAAC,UAAU,SAAS,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,GAAG;IACnH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;CAC/D,CAAC;AAEF,wBAAgB,aAAa,CAC5B,KAAK,CAAC,UAAU,SAAS,MAAM,EAC/B,KAAK,CAAC,YAAY,SAAS,iBAAiB,EAC5C,KAAK,CAAC,WAAW,SAAS,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAClE,KAAK,CAAC,OAAO,SAAS,eAAe,EAErC,UAAU,EAAE,iBAAiB,CAAC,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,CAAC,GAC3E,cAAc,CAAC,UAAU,EAAE,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Record } from "effect";
|
|
2
|
+
export function defineService(definition) {
|
|
3
|
+
const service = Context.Service(definition.id);
|
|
4
|
+
const layer = Layer.effect(service)(Effect.gen(function* () {
|
|
5
|
+
const ambient = yield* Effect.context();
|
|
6
|
+
const declared = Context.pick(...definition.requires)(ambient);
|
|
7
|
+
const state = yield* Effect.provide(definition.initialize, declared);
|
|
8
|
+
return Record.map(definition.methods(state), (entry) => {
|
|
9
|
+
const method = typeof entry === "function" ? entry : entry.method;
|
|
10
|
+
return (...arguments_) => Effect.provide(method(...arguments_), declared);
|
|
11
|
+
});
|
|
12
|
+
}));
|
|
13
|
+
return Object.assign(service, { layer });
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=define-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-service.js","sourceRoot":"","sources":["../src/define-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAkDxD,MAAM,UAAU,aAAa,CAAQ,UAAoC;IACxE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAuB,UAAU,CAAC,EAAE,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAClC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAU,CAAC;QAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAkB,EAAE,EAAE;YACnE,MAAM,MAAM,GAAc,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;YAC7E,OAAO,CAAC,GAAG,UAAgC,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC;QACjG,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CACF,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Effect } from "effect";
|
|
2
|
+
export type AnyMethod = (...arguments_: ReadonlyArray<never>) => Effect.Effect<unknown, unknown, unknown>;
|
|
3
|
+
export interface GenericMethodDescriptor<Method extends AnyMethod> {
|
|
4
|
+
readonly _tag: "GenericMethod";
|
|
5
|
+
readonly method: Method;
|
|
6
|
+
}
|
|
7
|
+
type Same<Left, Right> = (<Value>() => Value extends Left ? 1 : 2) extends <Value>() => Value extends Right ? 1 : 2 ? true : false;
|
|
8
|
+
export type HasDistinctCallSignatures<Method> = Method extends {
|
|
9
|
+
(...arguments_: infer FirstArguments): infer FirstResult;
|
|
10
|
+
(...arguments_: infer LastArguments): infer LastResult;
|
|
11
|
+
} ? Same<FirstArguments, LastArguments> extends true ? Same<FirstResult, LastResult> extends true ? false : true : true : false;
|
|
12
|
+
export interface GenericOrStructurallyOverloadedMethodsAreUnsupported {
|
|
13
|
+
readonly _serviceDefinitionError: "generic and structurally overloaded methods are unsupported";
|
|
14
|
+
}
|
|
15
|
+
type MarkedMethod<Method extends AnyMethod> = HasDistinctCallSignatures<Method> extends true ? GenericOrStructurallyOverloadedMethodsAreUnsupported : GenericMethodDescriptor<Method>;
|
|
16
|
+
export declare function genericMethod<Method extends AnyMethod>(method: Method): MarkedMethod<Method>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=generic-method.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-method.d.ts","sourceRoot":"","sources":["../src/generic-method.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAE1G,MAAM,WAAW,uBAAuB,CAAC,MAAM,SAAS,SAAS;IAChE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,KAAK,IAAI,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,KAAK,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,OAAO,KAAK,SAAS,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC;AAEnI,MAAM,MAAM,yBAAyB,CAAC,MAAM,IAAI,MAAM,SAAS;IAC9D,CAAC,GAAG,UAAU,EAAE,MAAM,cAAc,GAAG,MAAM,WAAW,CAAC;IACzD,CAAC,GAAG,UAAU,EAAE,MAAM,aAAa,GAAG,MAAM,UAAU,CAAC;CACvD,GACE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,SAAS,IAAI,GAC/C,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,SAAS,IAAI,GACzC,KAAK,GACL,IAAI,GACL,IAAI,GACL,KAAK,CAAC;AAET,MAAM,WAAW,oDAAoD;IACpE,QAAQ,CAAC,uBAAuB,EAAE,6DAA6D,CAAC;CAChG;AAED,KAAK,YAAY,CAAC,MAAM,SAAS,SAAS,IACzC,yBAAyB,CAAC,MAAM,CAAC,SAAS,IAAI,GAAG,oDAAoD,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAEzI,wBAAgB,aAAa,CAAC,MAAM,SAAS,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-method.js","sourceRoot":"","sources":["../src/generic-method.ts"],"names":[],"mappings":"AA8BA,MAAM,UAAU,aAAa,CAAC,MAAiB;IAC9C,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;AAC1C,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Effect, Scope } from "effect";
|
|
2
|
+
interface InitializerHasUndeclaredServiceRequirements {
|
|
3
|
+
readonly _serviceDefinitionError: "initializer has service requirements absent from the service declaration";
|
|
4
|
+
}
|
|
5
|
+
export type InitializerProof<Initializer, Requirements> = Initializer extends Effect.Effect<unknown, unknown, infer Residual> ? [Exclude<Residual, Requirements | Scope.Scope>] extends [never] ? Initializer : InitializerHasUndeclaredServiceRequirements : never;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=initializer-proof.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initializer-proof.d.ts","sourceRoot":"","sources":["../src/initializer-proof.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE5C,UAAU,2CAA2C;IACpD,QAAQ,CAAC,uBAAuB,EAAE,0EAA0E,CAAC;CAC7G;AAED,MAAM,MAAM,gBAAgB,CAAC,WAAW,EAAE,YAAY,IACrD,WAAW,SAAS,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC,GAChE,CAAC,OAAO,CAAC,QAAQ,EAAE,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC9D,WAAW,GACX,2CAA2C,GAC5C,KAAK,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initializer-proof.js","sourceRoot":"","sources":["../src/initializer-proof.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Effect, Scope } from "effect";
|
|
2
|
+
import type { AnyMethod, GenericMethodDescriptor, GenericOrStructurallyOverloadedMethodsAreUnsupported, HasDistinctCallSignatures } from "./generic-method.ts";
|
|
3
|
+
import type { RequirementRecord, RequirementsOf } from "./service-requirements.ts";
|
|
4
|
+
export type MethodEntry = AnyMethod | GenericMethodDescriptor<AnyMethod>;
|
|
5
|
+
export type MethodRecord = Readonly<Record<string, AnyMethod>>;
|
|
6
|
+
export type MethodInventory = Readonly<Record<string, unknown>>;
|
|
7
|
+
export type RuntimeMethodInventory = Readonly<Record<string, MethodEntry>>;
|
|
8
|
+
interface GenericMethodWithDeclaredRequirementsIsUnsupported {
|
|
9
|
+
readonly _serviceDefinitionError: "generic methods cannot subtract declared service requirements";
|
|
10
|
+
}
|
|
11
|
+
type MethodRequirements<Method> = Method extends (...arguments_: ReadonlyArray<never>) => Effect.Effect<unknown, unknown, infer Requirements> ? Requirements : never;
|
|
12
|
+
interface MethodHasUndeclaredServiceRequirements {
|
|
13
|
+
readonly _serviceDefinitionError: "method has service requirements absent from the service declaration";
|
|
14
|
+
}
|
|
15
|
+
interface ServiceMembersMustBeMethods {
|
|
16
|
+
readonly _serviceDefinitionError: "service members must be methods";
|
|
17
|
+
}
|
|
18
|
+
type OrdinaryMethodProof<Method, Requirements extends RequirementRecord> = [
|
|
19
|
+
Exclude<MethodRequirements<Method>, RequirementsOf<Requirements> | Scope.Scope>
|
|
20
|
+
] extends [never] ? Method : MethodHasUndeclaredServiceRequirements;
|
|
21
|
+
type SupportedMethod<Method, Requirements extends RequirementRecord> = Method extends GenericMethodDescriptor<AnyMethod> ? Requirements extends readonly [] ? Method : GenericMethodWithDeclaredRequirementsIsUnsupported : Method extends AnyMethod ? HasDistinctCallSignatures<Method> extends true ? GenericOrStructurallyOverloadedMethodsAreUnsupported : Method extends (...arguments_: infer Arguments) => infer Result ? ((...arguments_: Arguments) => Result) extends Method ? OrdinaryMethodProof<Method, Requirements> : GenericOrStructurallyOverloadedMethodsAreUnsupported : GenericOrStructurallyOverloadedMethodsAreUnsupported : ServiceMembersMustBeMethods;
|
|
22
|
+
export type MethodProof<Methods extends MethodInventory, Requirements extends RequirementRecord> = {
|
|
23
|
+
readonly [Name in keyof Methods]: SupportedMethod<Methods[Name], Requirements>;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=method-proof.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"method-proof.d.ts","sourceRoot":"","sources":["../src/method-proof.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,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEnF,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,UAAU,kDAAkD;IAC3D,QAAQ,CAAC,uBAAuB,EAAE,+DAA+D,CAAC;CAClG;AAED,KAAK,kBAAkB,CAAC,MAAM,IAAI,MAAM,SAAS,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC,GAC1I,YAAY,GACZ,KAAK,CAAC;AAET,UAAU,sCAAsC;IAC/C,QAAQ,CAAC,uBAAuB,EAAE,qEAAqE,CAAC;CACxG;AAED,UAAU,2BAA2B;IACpC,QAAQ,CAAC,uBAAuB,EAAE,iCAAiC,CAAC;CACpE;AAED,KAAK,mBAAmB,CAAC,MAAM,EAAE,YAAY,SAAS,iBAAiB,IAAI;IAC1E,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;CAC/E,SAAS,CAAC,KAAK,CAAC,GACd,MAAM,GACN,sCAAsC,CAAC;AAE1C,KAAK,eAAe,CAAC,MAAM,EAAE,YAAY,SAAS,iBAAiB,IAClE,MAAM,SAAS,uBAAuB,CAAC,SAAS,CAAC,GAC9C,YAAY,SAAS,SAAS,EAAE,GAC/B,MAAM,GACN,kDAAkD,GACnD,MAAM,SAAS,SAAS,GACvB,yBAAyB,CAAC,MAAM,CAAC,SAAS,IAAI,GAC7C,oDAAoD,GACpD,MAAM,SAAS,CAAC,GAAG,UAAU,EAAE,MAAM,SAAS,KAAK,MAAM,MAAM,GAC9D,CAAC,CAAC,GAAG,UAAU,EAAE,SAAS,KAAK,MAAM,CAAC,SAAS,MAAM,GACpD,mBAAmB,CAAC,MAAM,EAAE,YAAY,CAAC,GACzC,oDAAoD,GACrD,oDAAoD,GACtD,2BAA2B,CAAC;AAEjC,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,eAAe,EAAE,YAAY,SAAS,iBAAiB,IAAI;IAClG,QAAQ,EAAE,IAAI,IAAI,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC;CAC9E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"method-proof.js","sourceRoot":"","sources":["../src/method-proof.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Context, Scope } from "effect";
|
|
2
|
+
import type { RequirementRecord } from "./service-requirements.ts";
|
|
3
|
+
interface ScopeCannotBeDeclaredAsAServiceRequirement {
|
|
4
|
+
readonly _serviceDefinitionError: "Scope.Scope is caller-owned and cannot be declared as a service requirement";
|
|
5
|
+
}
|
|
6
|
+
type ProveRequirement<Requirement> = Requirement extends Context.Service.Any ? Context.Service.Identifier<Requirement> extends Scope.Scope ? ScopeCannotBeDeclaredAsAServiceRequirement : Requirement : never;
|
|
7
|
+
export type RequirementProof<Requirements extends RequirementRecord> = {
|
|
8
|
+
readonly [Index in keyof Requirements]: ProveRequirement<Requirements[Index]>;
|
|
9
|
+
};
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=requirement-proof.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirement-proof.d.ts","sourceRoot":"","sources":["../src/requirement-proof.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnE,UAAU,0CAA0C;IACnD,QAAQ,CAAC,uBAAuB,EAAE,6EAA6E,CAAC;CAChH;AAED,KAAK,gBAAgB,CAAC,WAAW,IAAI,WAAW,SAAS,OAAO,CAAC,OAAO,CAAC,GAAG,GACzE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,SAAS,KAAK,CAAC,KAAK,GAC1D,0CAA0C,GAC1C,WAAW,GACZ,KAAK,CAAC;AAET,MAAM,MAAM,gBAAgB,CAAC,YAAY,SAAS,iBAAiB,IAAI;IACtE,QAAQ,EAAE,KAAK,IAAI,MAAM,YAAY,GAAG,gBAAgB,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;CAC7E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirement-proof.js","sourceRoot":"","sources":["../src/requirement-proof.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Context, Effect, Scope } from "effect";
|
|
2
|
+
export type RequirementRecord = ReadonlyArray<Context.Service.Any>;
|
|
3
|
+
export type RequirementsOf<Requirements extends RequirementRecord> = Context.Service.Identifier<Requirements[number]>;
|
|
4
|
+
export type ServiceRequirements<Requirements extends RequirementRecord, Success, Failure = never, CallerScope extends Scope.Scope = never> = Effect.fn.Return<Success, Failure, RequirementsOf<Requirements> | CallerScope>;
|
|
5
|
+
//# sourceMappingURL=service-requirements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-requirements.d.ts","sourceRoot":"","sources":["../src/service-requirements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAEnE,MAAM,MAAM,cAAc,CAAC,YAAY,SAAS,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;AAEtH,MAAM,MAAM,mBAAmB,CAC9B,YAAY,SAAS,iBAAiB,EACtC,OAAO,EACP,OAAO,GAAG,KAAK,EACf,WAAW,SAAS,KAAK,CAAC,KAAK,GAAG,KAAK,IACpC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,GAAG,WAAW,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-requirements.js","sourceRoot":"","sources":["../src/service-requirements.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,14 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-service",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Declared Effect services with scoped initialization and bound dependencies",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
9
|
"url": "git+https://github.com/ShivaeDev/platform.git",
|
|
9
10
|
"directory": "packages/effect-service"
|
|
10
11
|
},
|
|
12
|
+
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-service#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ShivaeDev/platform/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=24"
|
|
18
|
+
},
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src",
|
|
23
|
+
"CHANGELOG.md",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"source": "./src/index.ts",
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
11
35
|
"publishConfig": {
|
|
12
|
-
"access": "public"
|
|
36
|
+
"access": "public",
|
|
37
|
+
"provenance": true
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"effect": "4.0.0-rc.112"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@effect/vitest": "4.0.0-rc.112",
|
|
44
|
+
"@types/node": "24.10.1",
|
|
45
|
+
"@typescript/native": "npm:typescript@7.0.2",
|
|
46
|
+
"effect": "4.0.0-rc.112",
|
|
47
|
+
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
48
|
+
"vitest": "4.1.9"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc6 --project tsconfig.build.json",
|
|
52
|
+
"check": "biome check .",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:package": "node scripts/test-package.mjs",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"typecheck:compat": "tsc6 --noEmit",
|
|
57
|
+
"ready": "pnpm check && pnpm typecheck && pnpm typecheck:compat && pnpm test && pnpm build && pnpm test:package"
|
|
13
58
|
}
|
|
14
|
-
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Record } from "effect";
|
|
2
|
+
import type { AnyMethod, GenericMethodDescriptor } from "./generic-method.ts";
|
|
3
|
+
import type { InitializerProof } from "./initializer-proof.ts";
|
|
4
|
+
import type { MethodEntry, MethodInventory, MethodProof, MethodRecord, RuntimeMethodInventory } from "./method-proof.ts";
|
|
5
|
+
import type { RequirementProof } from "./requirement-proof.ts";
|
|
6
|
+
import type { RequirementRecord, RequirementsOf } from "./service-requirements.ts";
|
|
7
|
+
|
|
8
|
+
type BoundMethod<Method, Requirements> = Method extends (
|
|
9
|
+
...arguments_: infer Arguments
|
|
10
|
+
) => Effect.Effect<infer Success, infer Failure, infer Residual>
|
|
11
|
+
? (...arguments_: Arguments) => Effect.Effect<Success, Failure, Exclude<Residual, Requirements>>
|
|
12
|
+
: never;
|
|
13
|
+
|
|
14
|
+
type PublicMethod<Entry, Requirements> = Entry extends GenericMethodDescriptor<infer Method> ? Method : BoundMethod<Entry, Requirements>;
|
|
15
|
+
|
|
16
|
+
type ServiceShape<Methods extends MethodInventory, Requirements extends RequirementRecord> = Readonly<{
|
|
17
|
+
[Name in keyof Methods]: PublicMethod<Methods[Name], RequirementsOf<Requirements>>;
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
interface ServiceDefinition<
|
|
21
|
+
Identifier extends string,
|
|
22
|
+
Requirements extends RequirementRecord,
|
|
23
|
+
Initializer extends Effect.Effect<unknown, unknown, unknown>,
|
|
24
|
+
Methods extends MethodInventory,
|
|
25
|
+
> {
|
|
26
|
+
readonly id: Identifier;
|
|
27
|
+
readonly initialize: Initializer & InitializerProof<Initializer, RequirementsOf<Requirements>>;
|
|
28
|
+
readonly methods: (state: Effect.Success<Initializer>) => Methods & MethodProof<NoInfer<Methods>, Requirements>;
|
|
29
|
+
readonly requires: Requirements & RequirementProof<Requirements>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface RuntimeDefinition<State> {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly initialize: Effect.Effect<State, unknown, unknown>;
|
|
35
|
+
readonly methods: (state: State) => RuntimeMethodInventory;
|
|
36
|
+
readonly requires: RequirementRecord;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type DefinedService<Identifier extends string, Shape, Failure, Requirements> = Context.Service<Identifier, Shape> & {
|
|
40
|
+
readonly layer: Layer.Layer<Identifier, Failure, Requirements>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function defineService<
|
|
44
|
+
const Identifier extends string,
|
|
45
|
+
const Requirements extends RequirementRecord,
|
|
46
|
+
const Initializer extends Effect.Effect<unknown, unknown, unknown>,
|
|
47
|
+
const Methods extends MethodInventory,
|
|
48
|
+
>(
|
|
49
|
+
definition: ServiceDefinition<Identifier, Requirements, Initializer, Methods>,
|
|
50
|
+
): DefinedService<Identifier, ServiceShape<Methods, Requirements>, Effect.Error<Initializer>, RequirementsOf<Requirements>>;
|
|
51
|
+
export function defineService<State>(definition: RuntimeDefinition<State>): unknown {
|
|
52
|
+
const service = Context.Service<string, MethodRecord>(definition.id);
|
|
53
|
+
const layer = Layer.effect(service)(
|
|
54
|
+
Effect.gen(function* () {
|
|
55
|
+
const ambient = yield* Effect.context<string>();
|
|
56
|
+
const declared = Context.pick(...definition.requires)(ambient);
|
|
57
|
+
const state = yield* Effect.provide(definition.initialize, declared);
|
|
58
|
+
return Record.map(definition.methods(state), (entry: MethodEntry) => {
|
|
59
|
+
const method: AnyMethod = typeof entry === "function" ? entry : entry.method;
|
|
60
|
+
return (...arguments_: ReadonlyArray<never>) => Effect.provide(method(...arguments_), declared);
|
|
61
|
+
});
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
return Object.assign(service, { layer });
|
|
65
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Effect } from "effect";
|
|
2
|
+
|
|
3
|
+
export type AnyMethod = (...arguments_: ReadonlyArray<never>) => Effect.Effect<unknown, unknown, unknown>;
|
|
4
|
+
|
|
5
|
+
export interface GenericMethodDescriptor<Method extends AnyMethod> {
|
|
6
|
+
readonly _tag: "GenericMethod";
|
|
7
|
+
readonly method: Method;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type Same<Left, Right> = (<Value>() => Value extends Left ? 1 : 2) extends <Value>() => Value extends Right ? 1 : 2 ? true : false;
|
|
11
|
+
|
|
12
|
+
export type HasDistinctCallSignatures<Method> = Method extends {
|
|
13
|
+
(...arguments_: infer FirstArguments): infer FirstResult;
|
|
14
|
+
(...arguments_: infer LastArguments): infer LastResult;
|
|
15
|
+
}
|
|
16
|
+
? Same<FirstArguments, LastArguments> extends true
|
|
17
|
+
? Same<FirstResult, LastResult> extends true
|
|
18
|
+
? false
|
|
19
|
+
: true
|
|
20
|
+
: true
|
|
21
|
+
: false;
|
|
22
|
+
|
|
23
|
+
export interface GenericOrStructurallyOverloadedMethodsAreUnsupported {
|
|
24
|
+
readonly _serviceDefinitionError: "generic and structurally overloaded methods are unsupported";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type MarkedMethod<Method extends AnyMethod> =
|
|
28
|
+
HasDistinctCallSignatures<Method> extends true ? GenericOrStructurallyOverloadedMethodsAreUnsupported : GenericMethodDescriptor<Method>;
|
|
29
|
+
|
|
30
|
+
export function genericMethod<Method extends AnyMethod>(method: Method): MarkedMethod<Method>;
|
|
31
|
+
export function genericMethod(method: AnyMethod): unknown {
|
|
32
|
+
return { _tag: "GenericMethod", method };
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Effect, Scope } from "effect";
|
|
2
|
+
|
|
3
|
+
interface InitializerHasUndeclaredServiceRequirements {
|
|
4
|
+
readonly _serviceDefinitionError: "initializer has service requirements absent from the service declaration";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type InitializerProof<Initializer, Requirements> =
|
|
8
|
+
Initializer extends Effect.Effect<unknown, unknown, infer Residual>
|
|
9
|
+
? [Exclude<Residual, Requirements | Scope.Scope>] extends [never]
|
|
10
|
+
? Initializer
|
|
11
|
+
: InitializerHasUndeclaredServiceRequirements
|
|
12
|
+
: never;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Effect, Scope } from "effect";
|
|
2
|
+
import type {
|
|
3
|
+
AnyMethod,
|
|
4
|
+
GenericMethodDescriptor,
|
|
5
|
+
GenericOrStructurallyOverloadedMethodsAreUnsupported,
|
|
6
|
+
HasDistinctCallSignatures,
|
|
7
|
+
} from "./generic-method.ts";
|
|
8
|
+
import type { RequirementRecord, RequirementsOf } from "./service-requirements.ts";
|
|
9
|
+
|
|
10
|
+
export type MethodEntry = AnyMethod | GenericMethodDescriptor<AnyMethod>;
|
|
11
|
+
|
|
12
|
+
export type MethodRecord = Readonly<Record<string, AnyMethod>>;
|
|
13
|
+
|
|
14
|
+
export type MethodInventory = Readonly<Record<string, unknown>>;
|
|
15
|
+
|
|
16
|
+
export type RuntimeMethodInventory = Readonly<Record<string, MethodEntry>>;
|
|
17
|
+
|
|
18
|
+
interface GenericMethodWithDeclaredRequirementsIsUnsupported {
|
|
19
|
+
readonly _serviceDefinitionError: "generic methods cannot subtract declared service requirements";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type MethodRequirements<Method> = Method extends (...arguments_: ReadonlyArray<never>) => Effect.Effect<unknown, unknown, infer Requirements>
|
|
23
|
+
? Requirements
|
|
24
|
+
: never;
|
|
25
|
+
|
|
26
|
+
interface MethodHasUndeclaredServiceRequirements {
|
|
27
|
+
readonly _serviceDefinitionError: "method has service requirements absent from the service declaration";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ServiceMembersMustBeMethods {
|
|
31
|
+
readonly _serviceDefinitionError: "service members must be methods";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type OrdinaryMethodProof<Method, Requirements extends RequirementRecord> = [
|
|
35
|
+
Exclude<MethodRequirements<Method>, RequirementsOf<Requirements> | Scope.Scope>,
|
|
36
|
+
] extends [never]
|
|
37
|
+
? Method
|
|
38
|
+
: MethodHasUndeclaredServiceRequirements;
|
|
39
|
+
|
|
40
|
+
type SupportedMethod<Method, Requirements extends RequirementRecord> =
|
|
41
|
+
Method extends GenericMethodDescriptor<AnyMethod>
|
|
42
|
+
? Requirements extends readonly []
|
|
43
|
+
? Method
|
|
44
|
+
: GenericMethodWithDeclaredRequirementsIsUnsupported
|
|
45
|
+
: Method extends AnyMethod
|
|
46
|
+
? HasDistinctCallSignatures<Method> extends true
|
|
47
|
+
? GenericOrStructurallyOverloadedMethodsAreUnsupported
|
|
48
|
+
: Method extends (...arguments_: infer Arguments) => infer Result
|
|
49
|
+
? ((...arguments_: Arguments) => Result) extends Method
|
|
50
|
+
? OrdinaryMethodProof<Method, Requirements>
|
|
51
|
+
: GenericOrStructurallyOverloadedMethodsAreUnsupported
|
|
52
|
+
: GenericOrStructurallyOverloadedMethodsAreUnsupported
|
|
53
|
+
: ServiceMembersMustBeMethods;
|
|
54
|
+
|
|
55
|
+
export type MethodProof<Methods extends MethodInventory, Requirements extends RequirementRecord> = {
|
|
56
|
+
readonly [Name in keyof Methods]: SupportedMethod<Methods[Name], Requirements>;
|
|
57
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Context, Scope } from "effect";
|
|
2
|
+
import type { RequirementRecord } from "./service-requirements.ts";
|
|
3
|
+
|
|
4
|
+
interface ScopeCannotBeDeclaredAsAServiceRequirement {
|
|
5
|
+
readonly _serviceDefinitionError: "Scope.Scope is caller-owned and cannot be declared as a service requirement";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type ProveRequirement<Requirement> = Requirement extends Context.Service.Any
|
|
9
|
+
? Context.Service.Identifier<Requirement> extends Scope.Scope
|
|
10
|
+
? ScopeCannotBeDeclaredAsAServiceRequirement
|
|
11
|
+
: Requirement
|
|
12
|
+
: never;
|
|
13
|
+
|
|
14
|
+
export type RequirementProof<Requirements extends RequirementRecord> = {
|
|
15
|
+
readonly [Index in keyof Requirements]: ProveRequirement<Requirements[Index]>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Context, Effect, Scope } from "effect";
|
|
2
|
+
|
|
3
|
+
export type RequirementRecord = ReadonlyArray<Context.Service.Any>;
|
|
4
|
+
|
|
5
|
+
export type RequirementsOf<Requirements extends RequirementRecord> = Context.Service.Identifier<Requirements[number]>;
|
|
6
|
+
|
|
7
|
+
export type ServiceRequirements<
|
|
8
|
+
Requirements extends RequirementRecord,
|
|
9
|
+
Success,
|
|
10
|
+
Failure = never,
|
|
11
|
+
CallerScope extends Scope.Scope = never,
|
|
12
|
+
> = Effect.fn.Return<Success, Failure, RequirementsOf<Requirements> | CallerScope>;
|