@pumped-fn/core-next 0.5.47 → 0.5.49
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 +380 -81
- package/dist/chunk-Cl8Af3a2.js +11 -0
- package/dist/index.d.ts +83 -48
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -98
- package/dist/index.js.map +1 -1
- package/package.json +4 -6
package/README.md
CHANGED
|
@@ -1,122 +1,421 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @pumped-fn/core-next Expert (v2.0)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript DI/reactive programming library. Function-based architecture, lazy evaluation, type-safe resolution, graph-based testing.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Assume all of those functions are from `@pumped-fn/core-next`
|
|
7
|
-
- Operator are all directly exported from the package
|
|
8
|
-
- Always try to infer instead of explicit declaration
|
|
5
|
+
## Quick Patterns
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
| Pattern | Code | Use When |
|
|
8
|
+
| ------------------ | -------------------------------------------- | -------------------- |
|
|
9
|
+
| **Basic DI** | `derive(deps, factory)` | Service dependencies |
|
|
10
|
+
| **No Deps** | `provide(factory)` | Config/singletons |
|
|
11
|
+
| **Reactive** | `derive(dep.reactive, f)` | Auto-updates needed |
|
|
12
|
+
| **Lazy** | `executor.lazy` | Defer resolution |
|
|
13
|
+
| **Static Control** | `executor.static` | Manual state control |
|
|
14
|
+
| **Test Mock** | `createScope(preset(exec, mock))` | Testing |
|
|
15
|
+
| **Cleanup** | `(ctl) => { ctl.cleanup(() => r.close()); }` | Resource disposal |
|
|
16
|
+
| **Batch Resolve** | `resolves(exec1, exec2, exec3)` | Multiple executors |
|
|
17
|
+
| **Flow Pattern** | `flow.provide(spec, factory)` | Structured workflows |
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
## Critical Rules ⚠️
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
| ✅ DO | ❌ NEVER |
|
|
22
|
+
| ----------------------------------------------- | --------------------------------------------- |
|
|
23
|
+
| Store main executor: `const e = provide(f)` | Store variants: `const r = e.reactive` |
|
|
24
|
+
| Use inline: `derive(e.reactive, f)` | Use with scope: `scope.update(e.reactive, v)` |
|
|
25
|
+
| Resolve first: `await resolve(e); update(e, v)` | Update unresolved: `update(e, v)` |
|
|
26
|
+
| Functions: `(db) => ({get: () => db.query()})` | Classes: `new Service(db)` |
|
|
27
|
+
| Graph testing: Test entire flows | Unit testing: Test isolated pieces |
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
- `scope#accessor(executor)` will return a Core.Accessor which can help to `get`, `resolve`, `lookup` or `subscribe`
|
|
29
|
+
## Core API Reference
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
Executor is the container. Executor has different access modes for use as dependencies:
|
|
31
|
+
### Executor Creation
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
```typescript
|
|
34
|
+
provide(factory, ...metas); // No dependencies
|
|
35
|
+
derive(deps, factory, ...metas); // With dependencies
|
|
36
|
+
derive([dep1, dep2, dep3], factory, ...metas); // With dependencies array. Factory will receive [resolved1, resolved2, resolved3]
|
|
37
|
+
derive({ dep1, dep2, dep3 }, factory, ...metas); // With dependencies object. Factory will receive { dep1: resolved1, dep2: resolved2, dep3: resolved 3}
|
|
38
|
+
preset(executor, value); // Override value
|
|
39
|
+
placeholder<T>(); // Throws if resolved
|
|
40
|
+
resolves(...executors); // Batch resolution helper
|
|
41
|
+
```
|
|
27
42
|
|
|
28
|
-
|
|
43
|
+
### Scope Operations
|
|
29
44
|
|
|
30
45
|
```typescript
|
|
31
|
-
|
|
46
|
+
scope.resolve(executor); // Get value
|
|
47
|
+
scope.update(executor, value); // Update & trigger reactive
|
|
48
|
+
scope.onUpdate(executor, cb); // Subscribe to changes
|
|
49
|
+
scope.onChange(cb); // Global change events
|
|
50
|
+
scope.onRelease(executor, cb); // Cleanup events
|
|
51
|
+
scope.use(middleware); // Add middleware
|
|
52
|
+
scope.pod(...presets); // Create sub-scope
|
|
53
|
+
scope.dispose(); // Cleanup all
|
|
54
|
+
```
|
|
32
55
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
### Variants (inline only)
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
executor.reactive // Triggers updates
|
|
60
|
+
executor.lazy // Returns Accessor<T>
|
|
61
|
+
executor.static // Returns Accessor<T> (eager)
|
|
62
|
+
|
|
63
|
+
// Accessor<T> interface:
|
|
64
|
+
accessor.get(): T // Current value
|
|
65
|
+
accessor.update(v | fn): void // Update value
|
|
66
|
+
accessor.resolve(force?): T // Re-resolve
|
|
67
|
+
accessor.release(): void // Release
|
|
68
|
+
accessor.lookup(): State<T> // Check state without resolving
|
|
37
69
|
```
|
|
38
70
|
|
|
71
|
+
## Meta System 🏷️
|
|
72
|
+
|
|
73
|
+
Type-safe metadata with StandardSchema:
|
|
74
|
+
|
|
39
75
|
```typescript
|
|
40
|
-
//
|
|
41
|
-
const
|
|
76
|
+
// Define meta with schema
|
|
77
|
+
const serviceName = meta("service", string());
|
|
78
|
+
const version = meta("version", object({ major: number() }));
|
|
79
|
+
|
|
80
|
+
// Apply to executors
|
|
81
|
+
const api = provide(factory, serviceName("user-api"), version({ major: 2 }));
|
|
82
|
+
|
|
83
|
+
// Retrieve meta
|
|
84
|
+
serviceName.find(api); // 'user-api'
|
|
85
|
+
version.get(api); // [{ major: 2 }]
|
|
86
|
+
serviceName.some(api); // true
|
|
87
|
+
|
|
88
|
+
// Use in middleware
|
|
89
|
+
const telemetry = middleware({
|
|
90
|
+
init: (scope) =>
|
|
91
|
+
scope.onChange((event, exec) => {
|
|
92
|
+
const name = serviceName.find(exec);
|
|
93
|
+
if (name) metrics.record(event, name);
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Controller Parameter 🎮
|
|
42
99
|
|
|
43
|
-
|
|
44
|
-
|
|
100
|
+
Every factory receives controller with enhanced capabilities:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// provide((ctl) => ...) or derive(deps, (deps, ctl) => ...)
|
|
104
|
+
ctl.cleanup(() => dispose()); // Register cleanup
|
|
105
|
+
ctl.release(); // Release self
|
|
106
|
+
ctl.scope; // Access parent scope
|
|
107
|
+
ctl.meta; // Access executor metadata
|
|
108
|
+
|
|
109
|
+
// Scope access patterns
|
|
110
|
+
const manager = provide((ctl) => {
|
|
111
|
+
// Create sub-scope
|
|
112
|
+
const pod = ctl.scope.pod(preset(config, override));
|
|
113
|
+
ctl.cleanup(() => pod.dispose());
|
|
114
|
+
|
|
115
|
+
// Access other executors
|
|
116
|
+
const other = await ctl.scope.resolve(otherExec);
|
|
117
|
+
|
|
118
|
+
// Self-management
|
|
119
|
+
if (shouldStop) ctl.release();
|
|
120
|
+
});
|
|
45
121
|
```
|
|
46
122
|
|
|
47
|
-
|
|
123
|
+
## Advanced Patterns
|
|
48
124
|
|
|
49
|
-
|
|
125
|
+
### Static Accessor Control
|
|
50
126
|
|
|
51
127
|
```typescript
|
|
52
|
-
|
|
128
|
+
// Build control systems with .static
|
|
129
|
+
const ctrl = derive(config.static, (cfgAccessor) => ({
|
|
130
|
+
update: (v) => cfgAccessor.update(v),
|
|
131
|
+
reset: () => cfgAccessor.update(defaultConfig),
|
|
132
|
+
get: () => cfgAccessor.get(),
|
|
133
|
+
check: () => cfgAccessor.lookup(), // Check without resolving
|
|
134
|
+
}));
|
|
135
|
+
|
|
136
|
+
// Mixed reactive + control
|
|
137
|
+
const svc = derive([state.reactive, config.static], ([state, cfgCtl], ctl) => {
|
|
138
|
+
// React to state changes, control config
|
|
139
|
+
if (state.error) cfgCtl.update((c) => ({ ...c, retry: true }));
|
|
140
|
+
ctl.cleanup(() => console.log("cleaning"));
|
|
141
|
+
return { state, control: cfgCtl };
|
|
142
|
+
});
|
|
143
|
+
```
|
|
53
144
|
|
|
54
|
-
|
|
55
|
-
const counterController = derive(counter.static, (counterCtl) => ({
|
|
56
|
-
increment: () => counterCtl.update(current => current + 1),
|
|
57
|
-
decrement: () => counterCtl.update(current => current - 1),
|
|
58
|
-
reset: () => counterCtl.update(0),
|
|
59
|
-
setValue: (value: number) => counterCtl.update(value)
|
|
60
|
-
}))
|
|
145
|
+
### Middleware System
|
|
61
146
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
147
|
+
```typescript
|
|
148
|
+
// Enhanced middleware with events
|
|
149
|
+
const analytics = middleware({
|
|
150
|
+
init: (scope) => {
|
|
151
|
+
scope.onChange((event, executor, value) => {
|
|
152
|
+
// 'resolving' | 'resolved' | 'updated' | 'released'
|
|
153
|
+
track(event, meta("name").find(executor));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
scope.onUpdate(userState, (accessor) => {
|
|
157
|
+
// React to specific executor updates
|
|
158
|
+
logUserChange(accessor.get());
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// Transform values
|
|
163
|
+
resolve: (value, executor) => {
|
|
164
|
+
if (shouldSanitize(executor)) {
|
|
165
|
+
return preset(executor, sanitize(value));
|
|
166
|
+
}
|
|
167
|
+
return value;
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
dispose: async () => {
|
|
171
|
+
await analytics.flush();
|
|
172
|
+
},
|
|
173
|
+
});
|
|
65
174
|
```
|
|
66
175
|
|
|
67
|
-
|
|
176
|
+
## Service Architecture
|
|
68
177
|
|
|
69
|
-
|
|
178
|
+
### Function-Based (✅ Preferred)
|
|
70
179
|
|
|
71
180
|
```typescript
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
181
|
+
// Handles async dependencies elegantly
|
|
182
|
+
const createAPI = async (config: Config, http: Http) => {
|
|
183
|
+
await http.connect(config.baseUrl); // Async init
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
async get(path: string) {
|
|
187
|
+
return http.get(config.baseUrl + path);
|
|
188
|
+
},
|
|
189
|
+
async post(path: string, data: any) {
|
|
190
|
+
return http.post(config.baseUrl + path, data);
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const api = derive([config.reactive, httpClient], createAPI);
|
|
196
|
+
```
|
|
76
197
|
|
|
77
|
-
|
|
78
|
-
const lazyService = derive(expensiveComputation.lazy, (lazyCtl) => ({
|
|
79
|
-
getResult: () => lazyCtl.resolve(), // Only compute when explicitly called
|
|
80
|
-
isResolved: () => lazyCtl.lookup()?.kind === 'resolved'
|
|
81
|
-
}))
|
|
198
|
+
### Why Not Classes (❌)
|
|
82
199
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
200
|
+
```typescript
|
|
201
|
+
// PROBLEMS with classes:
|
|
202
|
+
class APIService {
|
|
203
|
+
constructor(private config: Config, private http: Http) {
|
|
204
|
+
// ❌ Can't handle async init in constructor
|
|
205
|
+
// ❌ Can't react to config changes
|
|
206
|
+
// ❌ Encourages mutable state
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ❌ Classes break reactive updates
|
|
211
|
+
const api = derive(
|
|
212
|
+
[config.reactive, http],
|
|
213
|
+
(cfg, http) => new APIService(cfg, http)
|
|
214
|
+
); // Service never updates when config changes
|
|
86
215
|
```
|
|
87
216
|
|
|
217
|
+
## Graph-Based Testing 🧪
|
|
218
|
+
|
|
219
|
+
Test entire dependency graphs, not units:
|
|
220
|
+
|
|
88
221
|
```typescript
|
|
89
|
-
//
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
222
|
+
// Configure entire system for testing
|
|
223
|
+
const testScope = createScope(
|
|
224
|
+
preset(environment, "test"),
|
|
225
|
+
preset(database, mockDb),
|
|
226
|
+
preset(logger, silentLogger)
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Test complete flows
|
|
230
|
+
test("user registration flow", async () => {
|
|
231
|
+
const scope = createScope(
|
|
232
|
+
preset(emailService, mockEmail),
|
|
233
|
+
preset(database, inMemoryDb)
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
// Test entire registration graph
|
|
238
|
+
const registration = await scope.resolve(registrationFlow);
|
|
239
|
+
const result = await registration.execute({
|
|
240
|
+
email: "test@example.com",
|
|
241
|
+
password: "secure123",
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Verify side effects
|
|
245
|
+
expect(mockEmail.sent).toHaveLength(1);
|
|
246
|
+
expect(await inMemoryDb.users.count()).toBe(1);
|
|
247
|
+
} finally {
|
|
248
|
+
await scope.dispose();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Concurrent testing with isolated scopes
|
|
253
|
+
await Promise.all([test1WithScope(), test2WithScope(), test3WithScope()]); // No interference between tests
|
|
254
|
+
```
|
|
93
255
|
|
|
94
|
-
|
|
95
|
-
})
|
|
256
|
+
### State Management Pattern
|
|
96
257
|
|
|
97
|
-
|
|
98
|
-
|
|
258
|
+
```typescript
|
|
259
|
+
// Global state with reactive updates
|
|
260
|
+
const appState = provide(() => ({
|
|
261
|
+
user: null,
|
|
262
|
+
theme: "light",
|
|
263
|
+
notifications: [],
|
|
264
|
+
}));
|
|
265
|
+
|
|
266
|
+
// Derived states
|
|
267
|
+
const isAuthenticated = derive(
|
|
268
|
+
appState.reactive,
|
|
269
|
+
(state) => state.user !== null
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const unreadCount = derive(
|
|
273
|
+
appState.reactive,
|
|
274
|
+
(state) => state.notifications.filter((n) => !n.read).length
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// State updates
|
|
278
|
+
await scope.update(appState, (state) => ({
|
|
279
|
+
...state,
|
|
280
|
+
user: { id: "123", name: "Alice" },
|
|
281
|
+
}));
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Performance Guidelines
|
|
285
|
+
|
|
286
|
+
### Container Selection
|
|
287
|
+
|
|
288
|
+
| Type | Best For | Avoid When | Performance |
|
|
289
|
+
| ----------- | --------------------- | ----------------- | ---------------- |
|
|
290
|
+
| Normal | Standard deps | Need lazy loading | O(n) resolution |
|
|
291
|
+
| `.reactive` | Auto-updates | Static values | O(m) propagation |
|
|
292
|
+
| `.lazy` | Expensive/conditional | Always need value | Deferred O(n) |
|
|
293
|
+
| `.static` | Manual control | Frequent updates | O(1) access |
|
|
99
294
|
|
|
100
|
-
|
|
101
|
-
|
|
295
|
+
### Optimization Patterns
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// Conditional resolution with lazy
|
|
299
|
+
const service = derive(
|
|
300
|
+
{ cfg: config.reactive, db: database.lazy },
|
|
301
|
+
async ({ cfg, db }) => {
|
|
302
|
+
// Only resolve DB if needed
|
|
303
|
+
if (cfg.useDb) {
|
|
304
|
+
const database = await db.resolve();
|
|
305
|
+
return createDbService(database);
|
|
306
|
+
}
|
|
307
|
+
return createMemoryService();
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// Batch updates for performance
|
|
312
|
+
await scope.update(state, (s) => ({
|
|
313
|
+
...s,
|
|
314
|
+
multiple: "changes",
|
|
315
|
+
at: "once",
|
|
316
|
+
})); // Single reactive propagation
|
|
102
317
|
```
|
|
103
318
|
|
|
104
|
-
##
|
|
319
|
+
## Debugging Tools
|
|
105
320
|
|
|
106
|
-
|
|
321
|
+
```typescript
|
|
322
|
+
// Comprehensive tracing
|
|
323
|
+
scope.onChange((event, exec, val) => {
|
|
324
|
+
const name = meta("name").find(exec);
|
|
325
|
+
console.log(`[${event}] ${name || "anonymous"}: ${val}`);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// State inspection
|
|
329
|
+
const state = accessor.lookup();
|
|
330
|
+
switch (state?.kind) {
|
|
331
|
+
case "resolved":
|
|
332
|
+
console.log("Value:", state.value);
|
|
333
|
+
break;
|
|
334
|
+
case "rejected":
|
|
335
|
+
console.error("Error:", state.error);
|
|
336
|
+
break;
|
|
337
|
+
case "pending":
|
|
338
|
+
console.log("Still resolving...");
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Debug metadata
|
|
343
|
+
const debug = meta(
|
|
344
|
+
"debug",
|
|
345
|
+
object({
|
|
346
|
+
created: string(),
|
|
347
|
+
purpose: string(),
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const svc = provide(
|
|
352
|
+
factory,
|
|
353
|
+
debug({
|
|
354
|
+
created: new Date().toISOString(),
|
|
355
|
+
purpose: "User authentication",
|
|
356
|
+
})
|
|
357
|
+
);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Common Issues & Solutions
|
|
361
|
+
|
|
362
|
+
| Error | Cause | Fix |
|
|
363
|
+
| ----------------------- | ----------------------- | ---------------------------------------- |
|
|
364
|
+
| "Executor not resolved" | `.get()` before resolve | `await scope.resolve(e)` first |
|
|
365
|
+
| "Maximum call stack" | Circular reactive deps | Break with `.static` or restructure |
|
|
366
|
+
| Missing updates | Not using `.reactive` | Change to `derive(dep.reactive, f)` |
|
|
367
|
+
| Memory leaks | No cleanup/disposal | Add `ctl.cleanup()` or `scope.dispose()` |
|
|
368
|
+
| Type inference fails | Complex deps | Use `as const` or explicit types |
|
|
369
|
+
| Async in constructor | Using classes | Switch to functions |
|
|
370
|
+
| Flow validation fails | Schema mismatch | Check input/output schemas |
|
|
371
|
+
|
|
372
|
+
## Decision Tree
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
Need DI? → provide/derive
|
|
376
|
+
├─ Changes? → .reactive
|
|
377
|
+
├─ Expensive? → .lazy
|
|
378
|
+
├─ Control? → .static
|
|
379
|
+
├─ Testing? → preset + graph testing
|
|
380
|
+
└─ Workflow? → flow.provide/derive
|
|
381
|
+
|
|
382
|
+
Service Layer?
|
|
383
|
+
├─ Sync deps → (deps) => service
|
|
384
|
+
├─ Async deps → async (deps) => service
|
|
385
|
+
├─ Cleanup → (deps, ctl) => { ctl.cleanup(...); }
|
|
386
|
+
├─ Control → derive(e.static, (accessor) => controlApi)
|
|
387
|
+
├─ Scope access → (ctl) => { /* use ctl.scope */ }
|
|
388
|
+
└─ Validation → flow.provide({ input, output }, factory)
|
|
389
|
+
|
|
390
|
+
State Management?
|
|
391
|
+
├─ Local → single reactive executor
|
|
392
|
+
├─ Derived → derive(state.reactive, transform)
|
|
393
|
+
├─ Global → shared scope with executors
|
|
394
|
+
└─ Updates → scope.update(executor, value)
|
|
395
|
+
|
|
396
|
+
Testing Strategy?
|
|
397
|
+
├─ Unit → Individual executor with mocks
|
|
398
|
+
├─ Integration → Graph with presets
|
|
399
|
+
├─ E2E → Full scope with real deps
|
|
400
|
+
└─ Concurrent → Isolated scopes
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Anti-Pattern Gallery
|
|
107
404
|
|
|
108
405
|
```typescript
|
|
109
|
-
//
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
406
|
+
// ❌ WRONG
|
|
407
|
+
const r = counter.reactive; // Storing variant
|
|
408
|
+
scope.onUpdate(counter.reactive, cb); // Using variant with scope
|
|
409
|
+
class Svc { constructor(db) {} } // Class with deps
|
|
410
|
+
await scope.update(exec, val); // Update before resolve
|
|
411
|
+
new Database() in derive() // Side effects in factory
|
|
412
|
+
derive([a, b, c, d, e, f, g]) // Too many deps (refactor!)
|
|
413
|
+
|
|
414
|
+
// ✅ CORRECT
|
|
415
|
+
const c = provide(() => 0); // Store main
|
|
416
|
+
derive(c.reactive, v => v * 2); // Inline variant
|
|
417
|
+
const svc = (db) => ({ query: db.q }); // Function factory
|
|
418
|
+
await scope.resolve(exec); scope.update(exec, val); // Resolve first
|
|
419
|
+
derive(deps, async () => new Database()) // Async for side effects
|
|
420
|
+
derive(serviceGroup, ([a, b, c]) => ...) // Group related deps
|
|
421
|
+
```
|