Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Heavily inspired by Angular's DI system, but designed to work in any environment
- **Type-Safe** – Excellent type inference
- **Lightweight** – Zero dependencies, minimal bundle size
- **Flexible** – Classes, factories, values, and aliases
- **Optional decorators** – Injectable classes with `@NodeInjectable()` decorator
- **Optional decorators** – Injectable classes with `@NodeInjectable()` (or the `@Service()` / `@Scoped()` shorthands)
- **Multi-Tokens** – Built-in multi-provider support
- **Lifecycle hooks** – Graceful container destruction and resource cleanup
- **Plugin System** – Extensible architecture with custom middlewares, scanners, and diagnostics
Expand Down
48 changes: 24 additions & 24 deletions artifacts/coverage/coverage-summary.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
{"total": {"lines":{"total":782,"covered":750,"skipped":0,"pct":95.9},"statements":{"total":909,"covered":852,"skipped":0,"pct":93.72},"functions":{"total":174,"covered":165,"skipped":0,"pct":94.82},"branches":{"total":476,"covered":422,"skipped":0,"pct":88.65},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/plugins.ts": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/testkit.ts": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/api/decorator.ts": {"lines":{"total":14,"covered":14,"skipped":0,"pct":100},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":15,"covered":15,"skipped":0,"pct":100},"branches":{"total":7,"covered":7,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/api/injection.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":14,"covered":14,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/api/proxy.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":3,"covered":2,"skipped":0,"pct":66.66},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/api/token-utils.ts": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":11,"covered":11,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/api/token.ts": {"lines":{"total":19,"covered":19,"skipped":0,"pct":100},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":19,"covered":19,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/container/container.ts": {"lines":{"total":220,"covered":205,"skipped":0,"pct":93.18},"functions":{"total":33,"covered":29,"skipped":0,"pct":87.87},"statements":{"total":266,"covered":236,"skipped":0,"pct":88.72},"branches":{"total":194,"covered":163,"skipped":0,"pct":84.02}}
,"/home/zak/dev/opensource/illuma/core/src/lib/container/lifecycle.ts": {"lines":{"total":29,"covered":27,"skipped":0,"pct":93.1},"functions":{"total":11,"covered":8,"skipped":0,"pct":72.72},"statements":{"total":38,"covered":33,"skipped":0,"pct":86.84},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/context/context.ts": {"lines":{"total":49,"covered":49,"skipped":0,"pct":100},"functions":{"total":14,"covered":14,"skipped":0,"pct":100},"statements":{"total":52,"covered":52,"skipped":0,"pct":100},"branches":{"total":12,"covered":12,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/global/global.ts": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":12,"covered":12,"skipped":0,"pct":100},"statements":{"total":25,"covered":25,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/plugins/diagnostics/built-in.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":7,"covered":6,"skipped":0,"pct":85.71},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}}
,"/home/zak/dev/opensource/illuma/core/src/lib/plugins/diagnostics/default-impl.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/plugins/middlewares/diagnostics.middleware.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/plugins/middlewares/runner.ts": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":9,"covered":8,"skipped":0,"pct":88.88},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}}
,"/home/zak/dev/opensource/illuma/core/src/lib/provider/extractor.ts": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/provider/proto.ts": {"lines":{"total":29,"covered":29,"skipped":0,"pct":100},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":30,"covered":30,"skipped":0,"pct":100},"branches":{"total":12,"covered":11,"skipped":0,"pct":91.66}}
,"/home/zak/dev/opensource/illuma/core/src/lib/provider/resolver.ts": {"lines":{"total":88,"covered":85,"skipped":0,"pct":96.59},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":100,"covered":97,"skipped":0,"pct":97},"branches":{"total":73,"covered":65,"skipped":0,"pct":89.04}}
,"/home/zak/dev/opensource/illuma/core/src/lib/provider/tree-node.ts": {"lines":{"total":176,"covered":166,"skipped":0,"pct":94.31},"functions":{"total":31,"covered":31,"skipped":0,"pct":100},"statements":{"total":207,"covered":193,"skipped":0,"pct":93.23},"branches":{"total":89,"covered":80,"skipped":0,"pct":89.88}}
,"/home/zak/dev/opensource/illuma/core/src/lib/testkit/helpers.ts": {"lines":{"total":14,"covered":14,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":15,"covered":15,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/utils/defer.ts": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":26,"covered":26,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}}
,"/home/zak/dev/opensource/illuma/core/src/lib/utils/inheritance.ts": {"lines":{"total":33,"covered":33,"skipped":0,"pct":100},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"statements":{"total":36,"covered":36,"skipped":0,"pct":100},"branches":{"total":14,"covered":13,"skipped":0,"pct":92.85}}
,"/home/zak/dev/opensource/illuma/core/src/lib/utils/injector.ts": {"lines":{"total":13,"covered":11,"skipped":0,"pct":84.61},"functions":{"total":6,"covered":5,"skipped":0,"pct":83.33},"statements":{"total":17,"covered":14,"skipped":0,"pct":82.35},"branches":{"total":10,"covered":8,"skipped":0,"pct":80}}
{"total": {"lines":{"total":788,"covered":756,"skipped":0,"pct":95.93},"statements":{"total":915,"covered":858,"skipped":0,"pct":93.77},"functions":{"total":178,"covered":169,"skipped":0,"pct":94.94},"branches":{"total":476,"covered":422,"skipped":0,"pct":88.65},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/plugins.ts": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/testkit.ts": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/api/decorator.ts": {"lines":{"total":18,"covered":18,"skipped":0,"pct":100},"functions":{"total":13,"covered":13,"skipped":0,"pct":100},"statements":{"total":19,"covered":19,"skipped":0,"pct":100},"branches":{"total":7,"covered":7,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/api/injection.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":14,"covered":14,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/api/proxy.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":3,"covered":2,"skipped":0,"pct":66.66},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/api/token-utils.ts": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":11,"covered":11,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/api/token.ts": {"lines":{"total":19,"covered":19,"skipped":0,"pct":100},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":19,"covered":19,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/container/container.ts": {"lines":{"total":220,"covered":205,"skipped":0,"pct":93.18},"functions":{"total":33,"covered":29,"skipped":0,"pct":87.87},"statements":{"total":266,"covered":236,"skipped":0,"pct":88.72},"branches":{"total":194,"covered":163,"skipped":0,"pct":84.02}}
,"/tmp/workspace/git-illuma/core/src/lib/container/lifecycle.ts": {"lines":{"total":29,"covered":27,"skipped":0,"pct":93.1},"functions":{"total":11,"covered":8,"skipped":0,"pct":72.72},"statements":{"total":38,"covered":33,"skipped":0,"pct":86.84},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/context/context.ts": {"lines":{"total":49,"covered":49,"skipped":0,"pct":100},"functions":{"total":14,"covered":14,"skipped":0,"pct":100},"statements":{"total":52,"covered":52,"skipped":0,"pct":100},"branches":{"total":12,"covered":12,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/global/global.ts": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":12,"covered":12,"skipped":0,"pct":100},"statements":{"total":25,"covered":25,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/plugins/diagnostics/built-in.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":7,"covered":6,"skipped":0,"pct":85.71},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}}
,"/tmp/workspace/git-illuma/core/src/lib/plugins/diagnostics/default-impl.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/plugins/middlewares/diagnostics.middleware.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/plugins/middlewares/runner.ts": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":9,"covered":8,"skipped":0,"pct":88.88},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}}
,"/tmp/workspace/git-illuma/core/src/lib/provider/extractor.ts": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/provider/proto.ts": {"lines":{"total":29,"covered":29,"skipped":0,"pct":100},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":30,"covered":30,"skipped":0,"pct":100},"branches":{"total":12,"covered":11,"skipped":0,"pct":91.66}}
,"/tmp/workspace/git-illuma/core/src/lib/provider/resolver.ts": {"lines":{"total":88,"covered":85,"skipped":0,"pct":96.59},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":100,"covered":97,"skipped":0,"pct":97},"branches":{"total":73,"covered":65,"skipped":0,"pct":89.04}}
,"/tmp/workspace/git-illuma/core/src/lib/provider/tree-node.ts": {"lines":{"total":176,"covered":166,"skipped":0,"pct":94.31},"functions":{"total":31,"covered":31,"skipped":0,"pct":100},"statements":{"total":207,"covered":193,"skipped":0,"pct":93.23},"branches":{"total":89,"covered":80,"skipped":0,"pct":89.88}}
,"/tmp/workspace/git-illuma/core/src/lib/testkit/helpers.ts": {"lines":{"total":14,"covered":14,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":15,"covered":15,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/utils/defer.ts": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":26,"covered":26,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}}
,"/tmp/workspace/git-illuma/core/src/lib/utils/inheritance.ts": {"lines":{"total":33,"covered":33,"skipped":0,"pct":100},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"statements":{"total":36,"covered":36,"skipped":0,"pct":100},"branches":{"total":14,"covered":13,"skipped":0,"pct":92.85}}
,"/tmp/workspace/git-illuma/core/src/lib/utils/injector.ts": {"lines":{"total":15,"covered":13,"skipped":0,"pct":86.66},"functions":{"total":6,"covered":5,"skipped":0,"pct":83.33},"statements":{"total":19,"covered":16,"skipped":0,"pct":84.21},"branches":{"total":10,"covered":8,"skipped":0,"pct":80}}
}
44 changes: 44 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,50 @@ export type UserService = _UserService;
export const UserService = makeInjectable(_UserService, { singleton: true });
```

### @Service() / makeService()

Shorthand for `@NodeInjectable({ singleton: true })` — marks a class as a root-scoped singleton service shared across the entire container tree.

```typescript
import { Service, makeService } from '@illuma/core';

@Service()
class UserService {
private readonly db = nodeInject(DatabaseService);
}

// Decorator-free equivalent
class _UserService {
public getUser() { return { id: 1 }; }
}

export type UserService = _UserService;
export const UserService = makeService(_UserService);
```

See [Root singleton semantics](#root-singleton-semantics) below for the resolution rules that apply to services.

### @Scoped() / makeScoped()

Shorthand for `@NodeInjectable()` — marks a class as a node-scoped injectable that resolves within the container (or sub-container) that provides it. Useful when you want to make the non-singleton scope explicit at the call site.

```typescript
import { Scoped, makeScoped } from '@illuma/core';

@Scoped()
class RequestContext {
public readonly id = crypto.randomUUID();
}

// Decorator-free equivalent
class _RequestContext {
public readonly id = crypto.randomUUID();
}

export type RequestContext = _RequestContext;
export const RequestContext = makeScoped(_RequestContext);
```

### Root singleton semantics

`{ singleton: true }` implements Angular-like root scope for single providers:
Expand Down
10 changes: 4 additions & 6 deletions docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ To use decorators like `@NodeInjectable()`, enable experimental decorators in yo

### Alternative: Without decorators

If you prefer not to use decorators, use `makeInjectable` to mark classes as injectable:
If you prefer not to use decorators, use `makeInjectable` (or its scope-specific aliases `makeService` / `makeScoped`) to mark classes as injectable:

```typescript
import { makeInjectable } from '@illuma/core';
Expand Down Expand Up @@ -193,12 +193,12 @@ const config = container.get(CONFIG_TOKEN);

## Root-scoped singletons

Use `{ singleton: true }` to make an injectable class behave like Angular's `providedIn: 'root'`.
Use `{ singleton: true }` to make an injectable class behave like Angular's `providedIn: 'root'`. Illuma also exposes a `@Service()` shorthand (and a decorator-free `makeService()` analog) for this common case, with `@Scoped()` / `makeScoped()` as the symmetric pair for the default node-scoped behavior.

```typescript
import { NodeContainer, NodeInjectable } from '@illuma/core';
import { NodeContainer, Service } from '@illuma/core';

@NodeInjectable({ singleton: true })
@Service() // equivalent to @NodeInjectable({ singleton: true })
class GlobalService {
public readonly id = Math.random();
Comment on lines +199 to 203
}
Expand All @@ -208,8 +208,6 @@ const childA = new NodeContainer({ parent: root });
const childB = new NodeContainer({ parent: root });

root.bootstrap();
childA.bootstrap();
childB.bootstrap();

const a = childA.get(GlobalService);
const b = childB.get(GlobalService);
Expand Down
Loading