@shivaedev/service 0.8.2 → 0.9.1
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 +3 -1
- package/dist/adapters/requirementLayer.d.ts +12 -0
- package/dist/adapters/requirementLayer.d.ts.map +1 -0
- package/dist/adapters/requirementLayer.js +13 -0
- package/dist/adapters/requirementLayer.js.map +1 -0
- package/dist/defineService.d.ts +1 -1
- package/dist/defineService.d.ts.map +1 -1
- package/dist/defineService.js +3 -10
- package/dist/defineService.js.map +1 -1
- package/package.json +7 -8
- package/src/adapters/requirementLayer.ts +24 -0
- package/src/defineService.ts +5 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.1 - 2026-11-11
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Align published dependency requirements with the workspace.
|
|
8
|
+
|
|
9
|
+
## 0.9.0 - 2026-11-08
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Expose `requirementLayer` at `@shivaedev/service/adapters/requirementLayer.ts` for binding declared requirements in the service Layer.
|
|
14
|
+
|
|
3
15
|
## 0.8.2 - 2026-11-07
|
|
4
16
|
|
|
5
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ A **service definition** has four parts: an identity, the services it requires,
|
|
|
46
46
|
|
|
47
47
|
An **operation** is an `Effect.fn("Service.operation")` export in its own file, in the service's folder beside its `service.ts`. It reads every dependency with `yield*`: repositories, `SqlClient`, HTTP clients, config, other services and the caller. A dependency never arrives as a parameter, a closure capture or a bundle, and the acting user's id is never an argument. A target id, such as the account an administrator acts on, is an ordinary argument.
|
|
48
48
|
|
|
49
|
-
A **declared dependency** is a service tag in `requires`. The Layer supplies those services to initialization and binds them to each operation, and the public operation's type no longer requires them. If a caller provides a different value for a declared dependency around a call, the operation still uses the value supplied when the Layer was built.
|
|
49
|
+
A **declared dependency** is a service tag in `requires`. The Layer supplies those services to initialization and binds them to each operation, and the public operation's type no longer requires them. The requirement binder lives in `src/adapters/requirementLayer.ts`; it captures the Layer's declared services once and provides them to initialization and operations. If a caller provides a different value for a declared dependency around a call, the operation still uses the value supplied when the Layer was built.
|
|
50
50
|
|
|
51
51
|
The **lifetime** of a service is its Layer's. Each build of the Layer runs `initialize` once, and every call on that build shares its state. Where the Layer is registered decides the lifetime: built once by the host, the service is global; built for each request, it is per request and may require the caller, such as `SessionUser` or `Subject`, which the host provides for that request; built with `Layer.fresh` or for a single use, it is fresh each time. A service that requires the caller cannot be built globally, because nothing global provides the caller.
|
|
52
52
|
|
|
@@ -266,6 +266,8 @@ Import the module that defines the name:
|
|
|
266
266
|
| `@shivaedev/service/genericMethod.ts` | `genericMethod(method)` | Preserve a generic Effect-returning function and its caller requirements. |
|
|
267
267
|
| `@shivaedev/service/ServiceRequirements.ts` | `ServiceRequirements<Requirements, Success, Failure = never, CallerScope = never>` | Annotate an `Effect.fn` generator with declared dependencies and optional caller scope. |
|
|
268
268
|
|
|
269
|
+
The public `defineService` entry stays at `@shivaedev/service/defineService.ts`. Its context-capturing requirement binder lives in `@shivaedev/service/adapters/requirementLayer.ts`.
|
|
270
|
+
|
|
269
271
|
The definition's fields are:
|
|
270
272
|
|
|
271
273
|
| Field | Meaning |
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import type { MethodRecord, RuntimeMethodInventory } from "#MethodProof.ts";
|
|
3
|
+
import type { RequirementRecord } from "#ServiceRequirements.ts";
|
|
4
|
+
interface RuntimeDefinition<TState> {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly initialize: Effect.Effect<TState, unknown, unknown>;
|
|
7
|
+
readonly methods: (state: TState) => RuntimeMethodInventory;
|
|
8
|
+
readonly requires: RequirementRecord;
|
|
9
|
+
}
|
|
10
|
+
export declare function requirementLayer<TState>(service: Context.Service<string, MethodRecord>, definition: RuntimeDefinition<TState>): Layer.Layer<string, unknown, string>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=requirementLayer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirementLayer.d.ts","sourceRoot":"","sources":["../../src/adapters/requirementLayer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAe,YAAY,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,UAAU,iBAAiB,CAAC,MAAM;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7D,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,sBAAsB,CAAC;IAC5D,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;CACrC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,UAAU,EAAE,iBAAiB,CAAC,MAAM,CAAC,wCAY7H"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Record } from "effect";
|
|
2
|
+
export function requirementLayer(service, definition) {
|
|
3
|
+
return Layer.effect(service)(Effect.gen(function* () {
|
|
4
|
+
const ambient = yield* Effect.context();
|
|
5
|
+
const declared = Context.pick(...definition.requires)(ambient);
|
|
6
|
+
const state = yield* Effect.provide(definition.initialize, declared);
|
|
7
|
+
return Record.map(definition.methods(state), (entry) => {
|
|
8
|
+
const method = typeof entry === "function" ? entry : entry.method;
|
|
9
|
+
return (...arguments_) => Effect.provide(method(...arguments_), declared);
|
|
10
|
+
});
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=requirementLayer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirementLayer.js","sourceRoot":"","sources":["../../src/adapters/requirementLayer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAWxD,MAAM,UAAU,gBAAgB,CAAS,OAA8C,EAAE,UAAqC;IAC7H,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAC3B,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,GAAG,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;YAClE,OAAO,CAAC,GAAG,UAA4B,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC7F,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CACF,CAAC;AACH,CAAC"}
|
package/dist/defineService.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Effect, Layer } from "effect";
|
|
1
|
+
import { Context, type Effect, type Layer } from "effect";
|
|
2
2
|
import type { GenericMethodDescriptor } from "#genericMethod.ts";
|
|
3
3
|
import type { InitializerProof } from "#InitializerProof.ts";
|
|
4
4
|
import type { MethodInventory, MethodProof } from "#MethodProof.ts";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineService.d.ts","sourceRoot":"","sources":["../src/defineService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"defineService.d.ts","sourceRoot":"","sources":["../src/defineService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAwC,MAAM,iBAAiB,CAAC;AAC1G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEjF,KAAK,WAAW,CAAC,OAAO,EAAE,aAAa,IAAI,OAAO,SAAS,CAC1D,GAAG,UAAU,EAAE,MAAM,UAAU,KAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,QAAQ,EAAE,MAAM,QAAQ,EAAE,MAAM,SAAS,CAAC,GAChE,CAAC,GAAG,UAAU,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,GACnG,KAAK,CAAC;AAET,KAAK,YAAY,CAAC,MAAM,EAAE,aAAa,IAAI,MAAM,SAAS,uBAAuB,CAAC,MAAM,OAAO,CAAC,GAAG,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEhJ,KAAK,YAAY,CAAC,QAAQ,SAAS,eAAe,EAAE,aAAa,SAAS,iBAAiB,IAAI,QAAQ,CAAC;KACtG,KAAK,IAAI,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC;CACvF,CAAC,CAAC;AAEH,UAAU,iBAAiB,CAC1B,WAAW,SAAS,MAAM,EAC1B,aAAa,SAAS,iBAAiB,EACvC,YAAY,SAAS,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAC7D,QAAQ,SAAS,eAAe;IAEhC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,YAAY,GAAG,gBAAgB,CAAC,YAAY,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;IAClG,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,CAAC;IACpH,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,gBAAgB,CAAC,aAAa,CAAC,CAAC;CACnE;AASD,KAAK,cAAc,CAAC,WAAW,SAAS,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG;IACzH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;CAClE,CAAC;AAEF,wBAAgB,aAAa,CAC5B,KAAK,CAAC,WAAW,SAAS,MAAM,EAChC,KAAK,CAAC,aAAa,SAAS,iBAAiB,EAC7C,KAAK,CAAC,YAAY,SAAS,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EACnE,KAAK,CAAC,QAAQ,SAAS,eAAe,EAEtC,UAAU,EAAE,iBAAiB,CAAC,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,CAAC,GAC/E,cAAc,CAAC,WAAW,EAAE,YAAY,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC"}
|
package/dist/defineService.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import { Context
|
|
1
|
+
import { Context } from "effect";
|
|
2
|
+
import { requirementLayer } from "#adapters/requirementLayer.ts";
|
|
2
3
|
export function defineService(definition) {
|
|
3
4
|
const service = Context.Service(definition.id);
|
|
4
|
-
const layer =
|
|
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
|
-
}));
|
|
5
|
+
const layer = requirementLayer(service, definition);
|
|
13
6
|
return Object.assign(service, { layer });
|
|
14
7
|
}
|
|
15
8
|
//# sourceMappingURL=defineService.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineService.js","sourceRoot":"","sources":["../src/defineService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"defineService.js","sourceRoot":"","sources":["../src/defineService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAA2B,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAkDjE,MAAM,UAAU,aAAa,CAAS,UAAqC;IAC1E,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAuB,UAAU,CAAC,EAAE,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACpD,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAC1C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/service",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Declared Effect services with scoped initialization and bound dependencies",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -43,17 +43,16 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@effect/vitest": "4.1.
|
|
47
|
-
"@shivaedev/quality": "0.
|
|
48
|
-
"@shivaedev/test": "0.
|
|
49
|
-
"@shivaedev/test-story": "0.45.3",
|
|
46
|
+
"@effect/vitest": "4.1.2",
|
|
47
|
+
"@shivaedev/quality": "0.77.0",
|
|
48
|
+
"@shivaedev/test-story": "0.47.0",
|
|
50
49
|
"@types/node": "24.19.2",
|
|
51
|
-
"effect": "4.1.
|
|
50
|
+
"effect": "4.1.2",
|
|
52
51
|
"typescript": "7.0.2",
|
|
53
|
-
"vitest": "5.0
|
|
52
|
+
"vitest": "5.1.0"
|
|
54
53
|
},
|
|
55
54
|
"peerDependencies": {
|
|
56
|
-
"effect": "4.1.
|
|
55
|
+
"effect": "4.1.2"
|
|
57
56
|
},
|
|
58
57
|
"engines": {
|
|
59
58
|
"node": ">=24"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Record } from "effect";
|
|
2
|
+
import type { MethodEntry, MethodRecord, RuntimeMethodInventory } from "#MethodProof.ts";
|
|
3
|
+
import type { RequirementRecord } from "#ServiceRequirements.ts";
|
|
4
|
+
|
|
5
|
+
interface RuntimeDefinition<TState> {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly initialize: Effect.Effect<TState, unknown, unknown>;
|
|
8
|
+
readonly methods: (state: TState) => RuntimeMethodInventory;
|
|
9
|
+
readonly requires: RequirementRecord;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function requirementLayer<TState>(service: Context.Service<string, MethodRecord>, definition: RuntimeDefinition<TState>) {
|
|
13
|
+
return Layer.effect(service)(
|
|
14
|
+
Effect.gen(function* () {
|
|
15
|
+
const ambient = yield* Effect.context<string>();
|
|
16
|
+
const declared = Context.pick(...definition.requires)(ambient);
|
|
17
|
+
const state = yield* Effect.provide(definition.initialize, declared);
|
|
18
|
+
return Record.map(definition.methods(state), (entry: MethodEntry) => {
|
|
19
|
+
const method = typeof entry === "function" ? entry : entry.method;
|
|
20
|
+
return (...arguments_: readonly never[]) => Effect.provide(method(...arguments_), declared);
|
|
21
|
+
});
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
}
|
package/src/defineService.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Context, Effect, Layer
|
|
2
|
-
import
|
|
1
|
+
import { Context, type Effect, type Layer } from "effect";
|
|
2
|
+
import { requirementLayer } from "#adapters/requirementLayer.ts";
|
|
3
|
+
import type { GenericMethodDescriptor } from "#genericMethod.ts";
|
|
3
4
|
import type { InitializerProof } from "#InitializerProof.ts";
|
|
4
|
-
import type {
|
|
5
|
+
import type { MethodInventory, MethodProof, MethodRecord, RuntimeMethodInventory } from "#MethodProof.ts";
|
|
5
6
|
import type { RequirementProof } from "#RequirementProof.ts";
|
|
6
7
|
import type { RequirementRecord, RequirementsOf } from "#ServiceRequirements.ts";
|
|
7
8
|
|
|
@@ -50,16 +51,6 @@ export function defineService<
|
|
|
50
51
|
): DefinedService<TIdentifier, ServiceShape<TMethods, TRequirements>, Effect.Error<TInitializer>, RequirementsOf<TRequirements>>;
|
|
51
52
|
export function defineService<TState>(definition: RuntimeDefinition<TState>): unknown {
|
|
52
53
|
const service = Context.Service<string, MethodRecord>(definition.id);
|
|
53
|
-
const layer =
|
|
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_: readonly never[]) => Effect.provide(method(...arguments_), declared);
|
|
61
|
-
});
|
|
62
|
-
}),
|
|
63
|
-
);
|
|
54
|
+
const layer = requirementLayer(service, definition);
|
|
64
55
|
return Object.assign(service, { layer });
|
|
65
56
|
}
|