@nxara/olio-di 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +3 -95
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,103 +1,11 @@
1
1
  # @nxara/olio-di
2
2
 
3
- Typed dependency injection for olio apps, on [Awilix](https://github.com/jeffijoe/awilix).
4
-
5
- Modules already share code through plain imports. DI adds what imports can't:
6
-
7
- - **Per-operation instances:** a DataLoader, a transaction or a "current user" repository, one per request and shared by every resolver in it.
8
- - **Swapping implementations in tests,** without rewiring imports.
9
- - **Lifecycle:** lazy creation, and cleanup when an operation or the app ends.
10
-
11
- Awilix does the work underneath: lifetimes, lazy proxy injection, catching a singleton that holds a per-operation instance, cycle detection, disposal. This package puts typed providers on top: resolvers reach services by name, with autocomplete, and a typo is a compile error.
3
+ Typed dependency injection for olio apps, on Awilix: singletons and per-operation instances, reached as `ctx.di`.
12
4
 
13
5
  ```sh
14
6
  bun add @nxara/olio-di
15
7
  ```
16
8
 
17
- ## Providers
18
-
19
- Providers are plain values, declared next to the code that uses them. A factory asks for what it needs with `get(Provider)`.
20
-
21
- ```ts
22
- import { operation, singleton } from "@nxara/olio-di";
23
-
24
- export const Db = singleton(() => new Pool(env.DATABASE_URL), {
25
- dispose: (pool) => pool.end()
26
- });
27
-
28
- export const ProductLoader = operation(
29
- ({ get }) =>
30
- new DataLoader((skus: readonly string[]) => get(Db).products.bySkus(skus))
31
- );
32
-
33
- export const Orders = operation(
34
- ({ get, ctx }) => new OrderRepo(get(Db), ctx.viewer)
35
- );
36
- ```
37
-
38
- | | Lives | Its factory gets |
39
- | --- | --- | --- |
40
- | `singleton(factory, { dispose? })` | As long as the container | `{ get }`, for other singletons only |
41
- | `operation(factory, { dispose? })` | One operation (one scope) | `{ get, ctx }`, for any provider, plus the operation's context |
42
-
43
- A singleton that asks for an operation provider is a type error, and Awilix rejects it at runtime too. So do cycles (`Cyclic dependencies detected`).
44
-
45
- ## With olio
46
-
47
- Tell DI what an operation's context looks like, the same way you register olio's:
48
-
49
- ```ts
50
- declare module "@nxara/olio-di" {
51
- interface Register {
52
- ctx: RequestInfo; // what your context has, apart from `di` itself
53
- }
54
- }
55
- ```
56
-
57
- Create one container, naming the services resolvers use. Give every operation a scope in its context, and dispose the scope when the operation ends:
58
-
59
- ```ts
60
- // di.ts: the keys are the names. Providers these depend on (Db) needn't be listed.
61
- export const container = createContainer({
62
- productLoader: ProductLoader,
63
- orders: Orders
64
- });
65
-
66
- // in your context type
67
- di: ScopeOf<typeof container>;
68
-
69
- fetchHandler(app, {
70
- context: (request) => {
71
- const info = requestInfo(request);
72
- return { ...info, di: container.scope(info) };
73
- },
74
- onOperationEnd: (ctx) => ctx.di.dispose() // @nxara/olio-http
75
- });
76
-
77
- // in a resolver
78
- product: (line, _args, ctx) => ctx.di.productLoader.load(line.sku); // typed, autocompletes, created on first access
79
- ```
80
-
81
- Subscriptions work the same way: give each one a scope in its context and dispose it when it ends. With graphql-ws that's its `context(ctx, id)` hook, which runs once per subscription, and `onComplete`; with graphql-sse, `context` and `onComplete`. `@nxara/olio-http`'s README shows both.
82
-
83
- ## Tests
84
-
85
- ```ts
86
- const container = createContainer(services, { override: [override(Db, fakeDb)] }); // fakeDb must fit Db
87
- await using scope = container.scope(testInfo); // disposed at the end of the block
88
- expect(await scope.orders.list()).toEqual([...]);
89
- ```
90
-
91
- `dispose` and `ctx` are reserved and can't be service names. Factories use tokens (`get(Db)`) rather than names: typing a factory's dependencies from the list it's part of is circular, and TypeScript can't resolve it.
92
-
93
- Each container is independent, so tests don't share singletons.
94
-
95
- ## API
9
+ **[Read the docs](https://olio.nxara.dev/reference/di/)**
96
10
 
97
- | | |
98
- | --- | --- |
99
- | `createContainer(services, { override? })` | The root: `scope(ctx)`, `dispose()`, `Symbol.asyncDispose` |
100
- | `scope.<name>` | An instance for this operation: its own for `operation` providers, the shared one for singletons |
101
- | `ScopeOf<typeof container>` | The scope's type, for `ctx.di` |
102
- | `scope.dispose()` | Disposes this operation's instances, newest first. Also `Symbol.asyncDispose` |
103
- | `override(provider, value)` | A typed replacement for tests |
11
+ Part of [olio](https://olio.nxara.dev): modular, type-safe GraphQL for TypeScript. MIT licensed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxara/olio-di",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Typed dependency injection for olio apps, on Awilix.",
5
5
  "keywords": [
6
6
  "awilix",