@shivaedev/service 0.5.1 → 0.6.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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.1 - 2026-11-02
4
+
5
+ ### Fixed
6
+
7
+ - Accept native `Context.Reference` keys in `defineService` requirements without treating their `never` identifier as caller-owned `Scope.Scope`.
8
+
9
+ ## 0.6.0 - 2026-10-30
10
+
11
+ ### Added
12
+
13
+ - Jobs services share one scoped pg-boss client through the Effect environment. Registration, worker and producer Layers use the same connection and pool settings.
14
+ - Service definitions accept marked generic methods alongside declared dependencies. Generic signatures retain their caller requirements unchanged; initialization can capture declared dependencies as private state.
15
+
16
+ ### Breaking
17
+
18
+ - Move connection, pool, client factory, cache key and stop options from each `Jobs.layer` call into one `pgBossLayer` from `@shivaedev/jobs/server/service.ts`. Supply `connectionString` as `Redacted<string>` and provide that Layer to the merged jobs services. Keep only `jobs` and `onError` in `Jobs.layer`.
19
+ - Call `jobs.health()` instead of yielding `jobs.health`. Custom jobs service implementations must expose health as a method. Admin's built-in jobs handlers call the method automatically. Jobs services passed to webhook routes and enqueue helpers must also implement the new method shape.
20
+
3
21
  ## 0.5.1 - 2026-10-30
4
22
 
5
23
  ### Changed
package/README.md CHANGED
@@ -65,6 +65,8 @@ The Layer needs `Prefix`. After a caller resolves `Greetings`, `greet` needs no
65
65
 
66
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`.
67
67
 
68
+ 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.
69
+
68
70
  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
71
 
70
72
  ### 2. Annotate generators when inference needs help
@@ -144,7 +146,7 @@ The replacement provides the public method interface, rather than private initia
144
146
 
145
147
  ### Preserve a generic method's signature
146
148
 
147
- Ordinary methods have one concrete signature. For a service with an empty `requires` list, `genericMethod` marks a generic function so its success, failure and caller requirements remain related at each call:
149
+ Ordinary methods have one concrete signature. `genericMethod` marks a generic function so its success, failure and caller requirements remain related at each call:
148
150
 
149
151
  ```ts
150
152
  import { Context, Effect } from "effect";
@@ -174,7 +176,9 @@ const preserved = Effect.gen(function* () {
174
176
 
175
177
  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
178
 
177
- Marked generic methods require a service with no declared dependencies. 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 ordinary methods with concrete signatures when the service binds dependencies.
179
+ 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.
180
+
181
+ 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
182
 
179
183
  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.
180
184
 
@@ -185,7 +189,7 @@ Import the module that defines the name:
185
189
  | Module | Name | Use |
186
190
  | ------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
187
191
  | `@shivaedev/service/defineService.ts` | `defineService(definition)` | Create a native service with its `.layer`. |
188
- | `@shivaedev/service/genericMethod.ts` | `genericMethod(method)` | Mark a generic Effect-returning function in a service with no declared dependencies. |
192
+ | `@shivaedev/service/genericMethod.ts` | `genericMethod(method)` | Preserve a generic Effect-returning function and its caller requirements. |
189
193
  | `@shivaedev/service/ServiceRequirements.ts` | `ServiceRequirements<Requirements, Success, Failure = never, CallerScope = never>` | Annotate an `Effect.fn` generator with declared dependencies and optional caller scope. |
190
194
 
191
195
  The definition's fields are:
@@ -5,9 +5,6 @@ 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
- interface GenericMethodWithDeclaredRequirementsIsUnsupported {
9
- readonly _serviceDefinitionError: "generic methods cannot subtract declared service requirements";
10
- }
11
8
  type MethodRequirements<TMethod> = TMethod extends (...arguments_: readonly never[]) => Effect.Effect<unknown, unknown, infer TRequirements> ? TRequirements : never;
12
9
  interface MethodHasUndeclaredServiceRequirements {
13
10
  readonly _serviceDefinitionError: "method has service requirements absent from the service declaration";
@@ -18,7 +15,7 @@ interface ServiceMembersMustBeMethods {
18
15
  type OrdinaryMethodProof<TMethod, TRequirements extends RequirementRecord> = [
19
16
  Exclude<MethodRequirements<TMethod>, RequirementsOf<TRequirements> | Scope.Scope>
20
17
  ] extends [never] ? TMethod : MethodHasUndeclaredServiceRequirements;
21
- type SupportedMethod<TMethod, TRequirements extends RequirementRecord> = TMethod extends GenericMethodDescriptor<AnyMethod> ? TRequirements extends readonly [] ? TMethod : GenericMethodWithDeclaredRequirementsIsUnsupported : 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;
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;
22
19
  export type MethodProof<TMethods extends MethodInventory, TRequirements extends RequirementRecord> = {
23
20
  readonly [TName in keyof TMethods]: SupportedMethod<TMethods[TName], TRequirements>;
24
21
  };
@@ -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,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAElF,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,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;IAC/C,QAAQ,CAAC,uBAAuB,EAAE,qEAAqE,CAAC;CACxG;AAED,UAAU,2BAA2B;IACpC,QAAQ,CAAC,uBAAuB,EAAE,iCAAiC,CAAC;CACpE;AAED,KAAK,mBAAmB,CAAC,OAAO,EAAE,aAAa,SAAS,iBAAiB,IAAI;IAC5E,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;AAE1C,KAAK,eAAe,CAAC,OAAO,EAAE,aAAa,SAAS,iBAAiB,IACpE,OAAO,SAAS,uBAAuB,CAAC,SAAS,CAAC,GAC/C,aAAa,SAAS,SAAS,EAAE,GAChC,OAAO,GACP,kDAAkD,GACnD,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,OAAO,EAAE,aAAa,CAAC,GAC3C,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,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CACnF,CAAC"}
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,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAElF,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;IAC/C,QAAQ,CAAC,uBAAuB,EAAE,qEAAqE,CAAC;CACxG;AAED,UAAU,2BAA2B;IACpC,QAAQ,CAAC,uBAAuB,EAAE,iCAAiC,CAAC;CACpE;AAED,KAAK,mBAAmB,CAAC,OAAO,EAAE,aAAa,SAAS,iBAAiB,IAAI;IAC5E,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;AAE1C,KAAK,eAAe,CAAC,OAAO,EAAE,aAAa,SAAS,iBAAiB,IACpE,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,OAAO,EAAE,aAAa,CAAC,GAC3C,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,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CACnF,CAAC"}
@@ -1,9 +1,9 @@
1
1
  import type { Context, Scope } from "effect";
2
- import type { RequirementRecord } from "./ServiceRequirements.ts";
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,0BAA0B,CAAC;AAElE,UAAU,yCAAyC;IAClD,QAAQ,CAAC,uBAAuB,EAAE,6EAA6E,CAAC;CAChH;AAED,KAAK,gBAAgB,CAAC,YAAY,IAAI,YAAY,SAAS,OAAO,CAAC,OAAO,CAAC,GAAG,GAC3E,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,SAAS,KAAK,CAAC,KAAK,GAC3D,yCAAyC,GACzC,YAAY,GACb,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"}
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.5.1",
3
+ "version": "0.6.1",
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.45.0",
48
- "@shivaedev/test": "0.10.1",
49
- "@shivaedev/test-story": "0.24.0",
47
+ "@shivaedev/quality": "0.55.0",
48
+ "@shivaedev/test": "0.12.1",
49
+ "@shivaedev/test-story": "0.33.0",
50
50
  "@types/node": "24.19.2",
51
51
  "effect": "4.0.6",
52
52
  "typescript": "7.0.2",
@@ -15,10 +15,6 @@ export type MethodInventory = Readonly<Record<string, unknown>>;
15
15
 
16
16
  export type RuntimeMethodInventory = Readonly<Record<string, MethodEntry>>;
17
17
 
18
- interface GenericMethodWithDeclaredRequirementsIsUnsupported {
19
- readonly _serviceDefinitionError: "generic methods cannot subtract declared service requirements";
20
- }
21
-
22
18
  type MethodRequirements<TMethod> = TMethod extends (...arguments_: readonly never[]) => Effect.Effect<unknown, unknown, infer TRequirements>
23
19
  ? TRequirements
24
20
  : never;
@@ -39,9 +35,7 @@ type OrdinaryMethodProof<TMethod, TRequirements extends RequirementRecord> = [
39
35
 
40
36
  type SupportedMethod<TMethod, TRequirements extends RequirementRecord> =
41
37
  TMethod extends GenericMethodDescriptor<AnyMethod>
42
- ? TRequirements extends readonly []
43
- ? TMethod
44
- : GenericMethodWithDeclaredRequirementsIsUnsupported
38
+ ? TMethod
45
39
  : TMethod extends AnyMethod
46
40
  ? HasDistinctCallSignatures<TMethod> extends true
47
41
  ? GenericOrStructurallyOverloadedMethodsAreUnsupported
@@ -1,14 +1,16 @@
1
1
  import type { Context, Scope } from "effect";
2
- import type { RequirementRecord } from "./ServiceRequirements.ts";
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 Scope.Scope
10
- ? ScopeCannotBeDeclaredAsServiceRequirement
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> = {