sandly 2.0.0 → 2.0.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/README.md +165 -133
- package/dist/index.d.ts +17 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sandly
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Sandly ("Services And Layers") is a type-safe dependency injection library for TypeScript. No decorators, no runtime reflection, just compile-time safety that catches errors before your code runs.
|
|
4
4
|
|
|
5
5
|
## Why Sandly?
|
|
6
6
|
|
|
@@ -10,12 +10,16 @@ Most TypeScript DI libraries rely on experimental decorators and runtime reflect
|
|
|
10
10
|
import { Container, Layer } from 'sandly';
|
|
11
11
|
|
|
12
12
|
class Database {
|
|
13
|
-
|
|
13
|
+
query(sql: string) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
class UserService {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
constructor(private db: Database) {}
|
|
20
|
+
getUsers() {
|
|
21
|
+
return this.db.query('SELECT * FROM users');
|
|
22
|
+
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
// Define layers
|
|
@@ -60,24 +64,24 @@ import { Container, Layer, Tag } from 'sandly';
|
|
|
60
64
|
|
|
61
65
|
// Any class can be a dependency - no special base class needed
|
|
62
66
|
class Database {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
async query(sql: string) {
|
|
68
|
+
return [{ id: 1, name: 'Alice' }];
|
|
69
|
+
}
|
|
70
|
+
async close() {
|
|
71
|
+
console.log('Database closed');
|
|
72
|
+
}
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
class UserRepository {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
constructor(private db: Database) {}
|
|
77
|
+
findAll() {
|
|
78
|
+
return this.db.query('SELECT * FROM users');
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
// Create layers
|
|
79
83
|
const dbLayer = Layer.service(Database, [], {
|
|
80
|
-
|
|
84
|
+
cleanup: (db) => db.close(),
|
|
81
85
|
});
|
|
82
86
|
|
|
83
87
|
const userRepoLayer = Layer.service(UserRepository, [Database]);
|
|
@@ -104,7 +108,9 @@ Tags identify dependencies. There are two types:
|
|
|
104
108
|
|
|
105
109
|
```typescript
|
|
106
110
|
class UserService {
|
|
107
|
-
|
|
111
|
+
getUsers() {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
// UserService is both the class and its tag
|
|
@@ -118,7 +124,9 @@ const PortTag = Tag.of('Port')<number>();
|
|
|
118
124
|
const ConfigTag = Tag.of('Config')<{ apiUrl: string }>();
|
|
119
125
|
|
|
120
126
|
const portLayer = Layer.value(PortTag, 3000);
|
|
121
|
-
const configLayer = Layer.value(ConfigTag, {
|
|
127
|
+
const configLayer = Layer.value(ConfigTag, {
|
|
128
|
+
apiUrl: 'https://api.example.com',
|
|
129
|
+
});
|
|
122
130
|
```
|
|
123
131
|
|
|
124
132
|
### Container
|
|
@@ -131,19 +139,20 @@ const container = Container.from(appLayer);
|
|
|
131
139
|
|
|
132
140
|
// Or build manually
|
|
133
141
|
const container = Container.builder()
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
.add(Database, () => new Database())
|
|
143
|
+
.add(
|
|
144
|
+
UserService,
|
|
145
|
+
async (ctx) => new UserService(await ctx.resolve(Database))
|
|
146
|
+
)
|
|
147
|
+
.build();
|
|
139
148
|
|
|
140
149
|
// Resolve dependencies
|
|
141
150
|
const db = await container.resolve(Database);
|
|
142
151
|
const [db, users] = await container.resolveAll(Database, UserService);
|
|
143
152
|
|
|
144
153
|
// Use and discard pattern - resolves, runs callback, then destroys
|
|
145
|
-
const result = await container.use(UserService, (service) =>
|
|
146
|
-
|
|
154
|
+
const result = await container.use(UserService, (service) =>
|
|
155
|
+
service.getUsers()
|
|
147
156
|
);
|
|
148
157
|
|
|
149
158
|
// Manual clean up
|
|
@@ -165,11 +174,12 @@ const configLayer = Layer.value(ConfigTag, { port: 3000 });
|
|
|
165
174
|
|
|
166
175
|
// Layer.create for custom factory logic
|
|
167
176
|
const cacheLayer = Layer.create({
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
177
|
+
requires: [ConfigTag],
|
|
178
|
+
apply: (builder) =>
|
|
179
|
+
builder.add(Cache, async (ctx) => {
|
|
180
|
+
const config = await ctx.resolve(ConfigTag);
|
|
181
|
+
return new Cache({ ttl: config.cacheTtl });
|
|
182
|
+
}),
|
|
173
183
|
});
|
|
174
184
|
```
|
|
175
185
|
|
|
@@ -195,16 +205,17 @@ Scoped containers enable hierarchical dependency management:
|
|
|
195
205
|
```typescript
|
|
196
206
|
// Application scope - use builder to add dependencies
|
|
197
207
|
const appContainer = ScopedContainer.builder('app')
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
.add(Database, () => new Database())
|
|
209
|
+
.build();
|
|
200
210
|
|
|
201
211
|
// Request scope - use child() to create a child builder
|
|
202
|
-
const requestContainer = appContainer
|
|
203
|
-
|
|
204
|
-
|
|
212
|
+
const requestContainer = appContainer
|
|
213
|
+
.child('request')
|
|
214
|
+
.add(RequestContext, () => new RequestContext())
|
|
215
|
+
.build();
|
|
205
216
|
|
|
206
217
|
// Child can resolve both its own and parent dependencies
|
|
207
|
-
const db = await requestContainer.resolve(Database);
|
|
218
|
+
const db = await requestContainer.resolve(Database); // From parent
|
|
208
219
|
const ctx = await requestContainer.resolve(RequestContext); // From child
|
|
209
220
|
|
|
210
221
|
// Destroy child without affecting parent
|
|
@@ -215,8 +226,9 @@ Or use layers with `childFrom`:
|
|
|
215
226
|
|
|
216
227
|
```typescript
|
|
217
228
|
const appContainer = ScopedContainer.from('app', dbLayer);
|
|
218
|
-
const requestContainer = appContainer.childFrom(
|
|
219
|
-
|
|
229
|
+
const requestContainer = appContainer.childFrom(
|
|
230
|
+
'request',
|
|
231
|
+
Layer.value(RequestContext, new RequestContext())
|
|
220
232
|
);
|
|
221
233
|
```
|
|
222
234
|
|
|
@@ -227,8 +239,8 @@ The `use()` method resolves a service, runs a callback, and automatically destro
|
|
|
227
239
|
```typescript
|
|
228
240
|
// Perfect for short-lived operations like Lambda handlers or worker jobs
|
|
229
241
|
const result = await appContainer
|
|
230
|
-
|
|
231
|
-
|
|
242
|
+
.childFrom('request', requestLayer)
|
|
243
|
+
.use(UserService, (service) => service.processEvent(event));
|
|
232
244
|
// Container is automatically destroyed after callback completes
|
|
233
245
|
```
|
|
234
246
|
|
|
@@ -242,7 +254,10 @@ This is especially useful for serverless functions or message handlers where the
|
|
|
242
254
|
|
|
243
255
|
```typescript
|
|
244
256
|
class ApiClient {
|
|
245
|
-
|
|
257
|
+
constructor(
|
|
258
|
+
private config: Config,
|
|
259
|
+
private logger: Logger
|
|
260
|
+
) {}
|
|
246
261
|
}
|
|
247
262
|
|
|
248
263
|
// Dependencies must match constructor parameters in order
|
|
@@ -250,28 +265,37 @@ const apiLayer = Layer.service(ApiClient, [Config, Logger]);
|
|
|
250
265
|
|
|
251
266
|
// With cleanup function
|
|
252
267
|
const dbLayer = Layer.service(Database, [], {
|
|
253
|
-
|
|
268
|
+
cleanup: (db) => db.close(),
|
|
254
269
|
});
|
|
255
270
|
```
|
|
256
271
|
|
|
257
|
-
**Layer.value**: Constant values
|
|
272
|
+
**Layer.value**: Constant values or pre-instantiated instances
|
|
258
273
|
|
|
259
274
|
```typescript
|
|
275
|
+
// ValueTag (constants)
|
|
260
276
|
const ApiKeyTag = Tag.of('apiKey')<string>();
|
|
261
277
|
const configLayer = Layer.value(ApiKeyTag, process.env.API_KEY!);
|
|
278
|
+
|
|
279
|
+
// ServiceTag (pre-instantiated instances, useful for testing)
|
|
280
|
+
class UserService {
|
|
281
|
+
getUsers() { return []; }
|
|
282
|
+
}
|
|
283
|
+
const mockUserService = new UserService();
|
|
284
|
+
const testLayer = Layer.value(UserService, mockUserService);
|
|
262
285
|
```
|
|
263
286
|
|
|
264
287
|
**Layer.create**: Custom factory logic
|
|
265
288
|
|
|
266
289
|
```typescript
|
|
267
290
|
const dbLayer = Layer.create({
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
291
|
+
requires: [ConfigTag],
|
|
292
|
+
apply: (builder) =>
|
|
293
|
+
builder.add(Database, async (ctx) => {
|
|
294
|
+
const config = await ctx.resolve(ConfigTag);
|
|
295
|
+
const db = new Database(config.dbUrl);
|
|
296
|
+
await db.connect();
|
|
297
|
+
return db;
|
|
298
|
+
}),
|
|
275
299
|
});
|
|
276
300
|
```
|
|
277
301
|
|
|
@@ -286,10 +310,10 @@ const serviceLayer = Layer.service(UserService, [UserRepository, Logger]);
|
|
|
286
310
|
|
|
287
311
|
// Compose into complete application
|
|
288
312
|
const appLayer = serviceLayer
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
313
|
+
.provide(repoLayer)
|
|
314
|
+
.provide(dbLayer)
|
|
315
|
+
.provide(configLayer)
|
|
316
|
+
.provide(Layer.service(Logger, []));
|
|
293
317
|
|
|
294
318
|
// Create container - all dependencies satisfied
|
|
295
319
|
const container = Container.from(appLayer);
|
|
@@ -324,48 +348,55 @@ const container = Container.from(incomplete); // Type error!
|
|
|
324
348
|
import { ScopedContainer, Layer } from 'sandly';
|
|
325
349
|
|
|
326
350
|
// App-level dependencies (shared across requests)
|
|
327
|
-
const appContainer = ScopedContainer.from(
|
|
328
|
-
|
|
351
|
+
const appContainer = ScopedContainer.from(
|
|
352
|
+
'app',
|
|
353
|
+
Layer.mergeAll(dbLayer, loggerLayer)
|
|
329
354
|
);
|
|
330
355
|
|
|
331
356
|
// Express middleware
|
|
332
357
|
app.use(async (req, res, next) => {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
358
|
+
// Create request scope with request-specific dependencies
|
|
359
|
+
const requestScope = appContainer.childFrom(
|
|
360
|
+
'request',
|
|
361
|
+
Layer.value(RequestContext, {
|
|
362
|
+
requestId: crypto.randomUUID(),
|
|
363
|
+
userId: req.user?.id,
|
|
364
|
+
})
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
res.locals.container = requestScope;
|
|
368
|
+
|
|
369
|
+
res.on('finish', () => requestScope.destroy());
|
|
370
|
+
next();
|
|
345
371
|
});
|
|
346
372
|
|
|
347
373
|
// Route handler
|
|
348
374
|
app.get('/users', async (req, res) => {
|
|
349
|
-
|
|
350
|
-
|
|
375
|
+
const userService = await res.locals.container.resolve(UserService);
|
|
376
|
+
res.json(await userService.getUsers());
|
|
351
377
|
});
|
|
352
378
|
```
|
|
353
379
|
|
|
354
380
|
### Destruction Order
|
|
355
381
|
|
|
356
382
|
When destroying a scoped container:
|
|
383
|
+
|
|
357
384
|
1. Child scopes are destroyed first
|
|
358
385
|
2. Then the current scope's finalizers run
|
|
359
386
|
3. Parent scope is unaffected
|
|
360
387
|
|
|
361
388
|
```typescript
|
|
362
389
|
const parent = ScopedContainer.builder('parent')
|
|
363
|
-
|
|
364
|
-
|
|
390
|
+
.add(Database, {
|
|
391
|
+
create: () => new Database(),
|
|
392
|
+
cleanup: (db) => db.close(),
|
|
393
|
+
})
|
|
394
|
+
.build();
|
|
365
395
|
|
|
366
|
-
const child = parent
|
|
367
|
-
|
|
368
|
-
|
|
396
|
+
const child = parent
|
|
397
|
+
.child('child')
|
|
398
|
+
.add(Cache, { create: () => new Cache(), cleanup: (c) => c.clear() })
|
|
399
|
+
.build();
|
|
369
400
|
|
|
370
401
|
await parent.destroy(); // Destroys child first (Cache.clear), then parent (Database.close)
|
|
371
402
|
```
|
|
@@ -376,25 +407,25 @@ Sandly provides specific error types for common issues:
|
|
|
376
407
|
|
|
377
408
|
```typescript
|
|
378
409
|
import {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
410
|
+
UnknownDependencyError,
|
|
411
|
+
CircularDependencyError,
|
|
412
|
+
DependencyCreationError,
|
|
413
|
+
DependencyFinalizationError,
|
|
383
414
|
} from 'sandly';
|
|
384
415
|
|
|
385
416
|
try {
|
|
386
|
-
|
|
417
|
+
const service = await container.resolve(UserService);
|
|
387
418
|
} catch (error) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
419
|
+
if (error instanceof CircularDependencyError) {
|
|
420
|
+
console.log(error.message);
|
|
421
|
+
// "Circular dependency detected for UserService: UserService -> Database -> UserService"
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (error instanceof DependencyCreationError) {
|
|
425
|
+
// Get the original error that caused the failure
|
|
426
|
+
const rootCause = error.getRootCause();
|
|
427
|
+
console.log(rootCause.message);
|
|
428
|
+
}
|
|
398
429
|
}
|
|
399
430
|
```
|
|
400
431
|
|
|
@@ -402,68 +433,69 @@ try {
|
|
|
402
433
|
|
|
403
434
|
### Container
|
|
404
435
|
|
|
405
|
-
| Method
|
|
406
|
-
|
|
407
|
-
| `Container.from(layer)`
|
|
408
|
-
| `Container.builder()`
|
|
409
|
-
| `Container.empty()`
|
|
410
|
-
| `Container.scoped(scope)`
|
|
411
|
-
| `container.resolve(tag)`
|
|
412
|
-
| `container.resolveAll(...tags)` | Get multiple dependencies
|
|
413
|
-
| `container.use(tag, fn)`
|
|
414
|
-
| `container.destroy()`
|
|
436
|
+
| Method | Description |
|
|
437
|
+
| ------------------------------- | --------------------------------------------- |
|
|
438
|
+
| `Container.from(layer)` | Create container from a fully resolved layer |
|
|
439
|
+
| `Container.builder()` | Create a container builder |
|
|
440
|
+
| `Container.empty()` | Create an empty container |
|
|
441
|
+
| `Container.scoped(scope)` | Create an empty scoped container |
|
|
442
|
+
| `container.resolve(tag)` | Get a dependency instance |
|
|
443
|
+
| `container.resolveAll(...tags)` | Get multiple dependencies |
|
|
444
|
+
| `container.use(tag, fn)` | Resolve, run callback, then destroy container |
|
|
445
|
+
| `container.destroy()` | Run finalizers and clean up |
|
|
415
446
|
|
|
416
447
|
### ContainerBuilder
|
|
417
448
|
|
|
418
|
-
| Method
|
|
419
|
-
|
|
449
|
+
| Method | Description |
|
|
450
|
+
| ------------------------ | --------------------- |
|
|
420
451
|
| `builder.add(tag, spec)` | Register a dependency |
|
|
421
|
-
| `builder.build()`
|
|
452
|
+
| `builder.build()` | Create the container |
|
|
422
453
|
|
|
423
454
|
### Layer
|
|
424
455
|
|
|
425
|
-
| Method
|
|
426
|
-
|
|
427
|
-
| `Layer.service(class, deps, options?)` | Create layer for a class
|
|
428
|
-
| `Layer.value(tag, value)`
|
|
429
|
-
| `Layer.create({ requires, apply })`
|
|
430
|
-
| `Layer.empty()`
|
|
431
|
-
| `Layer.merge(a, b)`
|
|
432
|
-
| `Layer.mergeAll(...layers)`
|
|
433
|
-
| `layer.provide(dep)`
|
|
434
|
-
| `layer.provideMerge(dep)`
|
|
435
|
-
| `layer.merge(other)`
|
|
456
|
+
| Method | Description |
|
|
457
|
+
| -------------------------------------- | --------------------------------- |
|
|
458
|
+
| `Layer.service(class, deps, options?)` | Create layer for a class |
|
|
459
|
+
| `Layer.value(tag, value)` | Create layer for a constant value |
|
|
460
|
+
| `Layer.create({ requires, apply })` | Create custom layer |
|
|
461
|
+
| `Layer.empty()` | Create empty layer |
|
|
462
|
+
| `Layer.merge(a, b)` | Merge two layers |
|
|
463
|
+
| `Layer.mergeAll(...layers)` | Merge multiple layers |
|
|
464
|
+
| `layer.provide(dep)` | Satisfy dependencies |
|
|
465
|
+
| `layer.provideMerge(dep)` | Satisfy and merge provisions |
|
|
466
|
+
| `layer.merge(other)` | Merge with another layer |
|
|
436
467
|
|
|
437
468
|
### ScopedContainer
|
|
438
469
|
|
|
439
|
-
| Method
|
|
440
|
-
|
|
441
|
-
| `ScopedContainer.builder(scope)`
|
|
442
|
-
| `ScopedContainer.empty(scope)`
|
|
443
|
-
| `ScopedContainer.from(scope, layer)` | Create from layer
|
|
444
|
-
| `container.child(scope)`
|
|
445
|
-
| `container.childFrom(scope, layer)`
|
|
470
|
+
| Method | Description |
|
|
471
|
+
| ------------------------------------ | ------------------------------------------- |
|
|
472
|
+
| `ScopedContainer.builder(scope)` | Create a new scoped container builder |
|
|
473
|
+
| `ScopedContainer.empty(scope)` | Create empty scoped container |
|
|
474
|
+
| `ScopedContainer.from(scope, layer)` | Create from layer |
|
|
475
|
+
| `container.child(scope)` | Create child scope builder |
|
|
476
|
+
| `container.childFrom(scope, layer)` | Create child scope from layer (convenience) |
|
|
446
477
|
|
|
447
478
|
### Tag
|
|
448
479
|
|
|
449
|
-
| Method
|
|
450
|
-
|
|
451
|
-
| `Tag.of(id)<T>()`
|
|
452
|
-
| `Tag.id(tag)`
|
|
453
|
-
| `Tag.isTag(value)` | Check if value is a tag
|
|
480
|
+
| Method | Description |
|
|
481
|
+
| ------------------ | --------------------------- |
|
|
482
|
+
| `Tag.of(id)<T>()` | Create a ValueTag |
|
|
483
|
+
| `Tag.id(tag)` | Get tag's string identifier |
|
|
484
|
+
| `Tag.isTag(value)` | Check if value is a tag |
|
|
454
485
|
|
|
455
486
|
## Comparison with Alternatives
|
|
456
487
|
|
|
457
|
-
| Feature
|
|
458
|
-
|
|
459
|
-
| Compile-time type safety
|
|
460
|
-
| No experimental decorators | ✅
|
|
461
|
-
| Async factories
|
|
462
|
-
| Framework-agnostic
|
|
463
|
-
| Layer composition
|
|
464
|
-
| Zero dependencies
|
|
488
|
+
| Feature | Sandly | NestJS | InversifyJS | TSyringe |
|
|
489
|
+
| -------------------------- | ------ | ------ | ----------- | -------- |
|
|
490
|
+
| Compile-time type safety | ✅ | ❌ | ⚠️ Partial | ❌ |
|
|
491
|
+
| No experimental decorators | ✅ | ❌ | ❌ | ❌ |
|
|
492
|
+
| Async factories | ✅ | ✅ | ❌ | ❌ |
|
|
493
|
+
| Framework-agnostic | ✅ | ❌ | ✅ | ✅ |
|
|
494
|
+
| Layer composition | ✅ | ❌ | ❌ | ❌ |
|
|
495
|
+
| Zero dependencies | ✅ | ❌ | ❌ | ❌ |
|
|
465
496
|
|
|
466
497
|
**Choose Sandly when you want:**
|
|
498
|
+
|
|
467
499
|
- Type safety without sacrificing simplicity
|
|
468
500
|
- DI without experimental decorators
|
|
469
501
|
- Composable, reusable dependency modules
|
package/dist/index.d.ts
CHANGED
|
@@ -325,12 +325,14 @@ declare const Layer: {
|
|
|
325
325
|
cleanup?: Finalizer<InstanceType<TClass>>;
|
|
326
326
|
}): Layer<ExtractTags<TDeps>, TClass>;
|
|
327
327
|
/**
|
|
328
|
-
* Creates a layer that provides a constant value.
|
|
328
|
+
* Creates a layer that provides a constant value or pre-instantiated instance.
|
|
329
329
|
*
|
|
330
|
-
*
|
|
331
|
-
* @param value - The value to provide
|
|
330
|
+
* Works with both ValueTags (for constants) and ServiceTags (for pre-instantiated instances, useful in tests).
|
|
332
331
|
*
|
|
333
|
-
* @
|
|
332
|
+
* @param tag - The tag (ValueTag or ServiceTag) to register
|
|
333
|
+
* @param value - The value or instance to provide
|
|
334
|
+
*
|
|
335
|
+
* @example ValueTag (constant)
|
|
334
336
|
* ```typescript
|
|
335
337
|
* const ApiKeyTag = Tag.of('apiKey')<string>();
|
|
336
338
|
* const ConfigTag = Tag.of('config')<{ port: number }>();
|
|
@@ -338,8 +340,18 @@ declare const Layer: {
|
|
|
338
340
|
* const configLayer = Layer.value(ApiKeyTag, 'secret-key')
|
|
339
341
|
* .merge(Layer.value(ConfigTag, { port: 3000 }));
|
|
340
342
|
* ```
|
|
343
|
+
*
|
|
344
|
+
* @example ServiceTag (pre-instantiated instance, useful for testing)
|
|
345
|
+
* ```typescript
|
|
346
|
+
* class UserService {
|
|
347
|
+
* getUsers() { return []; }
|
|
348
|
+
* }
|
|
349
|
+
*
|
|
350
|
+
* const mockUserService = new UserService();
|
|
351
|
+
* const testLayer = Layer.value(UserService, mockUserService);
|
|
352
|
+
* ```
|
|
341
353
|
*/
|
|
342
|
-
value<T extends
|
|
354
|
+
value<T extends AnyTag>(tag: T, value: TagType<T>): Layer<never, T>;
|
|
343
355
|
/**
|
|
344
356
|
* Creates a custom layer with full control over the factory logic.
|
|
345
357
|
*
|