@decohere/crucible 0.0.1-dev.98 → 0.0.1-dev.99
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
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# @decohere/crucible
|
|
2
|
+
|
|
3
|
+
Lightweight dependency injection for Decohere. Register classes (or tokens) on a `CrucibleBuilder`, `build()` a container, then `resolve()` graphs of constructor-injected services.
|
|
4
|
+
|
|
5
|
+
Higher layers (for example Cortex `Builder.withServices`) register on a `CrucibleBuilder`. You can also build and resolve from a container yourself.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
| Use Crucible | Do not use Crucible |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Constructor-injected services and controllers | Replacing Cortex / Protocol / Codex — those compose *on top of* Crucible |
|
|
12
|
+
| Tokens for interfaces, config objects, and “many of this kind” | Auto-scanning folders or Nest-style modules (not in this package) |
|
|
13
|
+
| Request / singleton / transient lifetimes and `using` disposal | Throwing on a missing dependency (unregistered keys resolve to `null`) |
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @decohere/crucible
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { CrucibleBuilder, LifetimeScopeToken, Scopes } from "@decohere/crucible";
|
|
23
|
+
import { inject, injectable } from "@decohere/crucible/decorators";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Decorator metadata is required. Enable it in `tsconfig.json` (the Decohere scaffold and this repo already do):
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"compilerOptions": {
|
|
31
|
+
"experimentalDecorators": true,
|
|
32
|
+
"emitDecoratorMetadata": true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`@decohere/nexus/reflection` loads `reflect-metadata`. Import Crucible (or Nexus) before constructing decorated classes.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Constructor injection — `@inject` is usually unnecessary
|
|
42
|
+
|
|
43
|
+
If the constructor parameter is a **concrete class**, Crucible reads `design:paramtypes` and resolves by the class name. Mark the *consumer* `@injectable()` so TypeScript/SWC emit that metadata. Do **not** add `@inject` for that parameter.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { injectable } from "@decohere/crucible/decorators";
|
|
47
|
+
import { SampleService } from "./sample.service.js";
|
|
48
|
+
|
|
49
|
+
@injectable()
|
|
50
|
+
export class HelloController {
|
|
51
|
+
public constructor(private readonly sample: SampleService) {}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { CrucibleBuilder } from "@decohere/crucible";
|
|
57
|
+
|
|
58
|
+
const builder = new CrucibleBuilder();
|
|
59
|
+
builder.registerSingleton(SampleService);
|
|
60
|
+
builder.registerRequestScoped(HelloController);
|
|
61
|
+
const container = builder.build();
|
|
62
|
+
|
|
63
|
+
using scope = container.beginScope("req");
|
|
64
|
+
const hello = scope.resolve(HelloController);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`@injectable()` is an empty class decorator. Its job is to force `design:paramtypes` onto the constructor. Any other class decorator (`@Controller()`, `@Trace`, …) also does that; `@injectable()` is the one to use when there is nothing else.
|
|
68
|
+
|
|
69
|
+
### Do not `import type` injected classes
|
|
70
|
+
|
|
71
|
+
`import type` is erased at compile time. `emitDecoratorMetadata` then records `Object` (or nothing) instead of the constructor. Crucible looks up `"Object"`, finds no registration, and **returns `null`**. The first method call on that dependency throws (`Cannot read properties of null`).
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// Wrong — SampleService does not exist at runtime, paramtypes collapse to Object
|
|
75
|
+
import type { SampleService } from "./sample.service.js";
|
|
76
|
+
|
|
77
|
+
// Right — value import keeps the constructor for reflect-metadata
|
|
78
|
+
import { SampleService } from "./sample.service.js";
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`import type` is correct for **interfaces** and type-only helpers. Those have no runtime constructor; pair them with `@inject(token)` (see below).
|
|
82
|
+
|
|
83
|
+
Linters that prefer type-only imports (`import type` / `import { type X }`) will flag a class that appears unused except as a constructor type. Keep the value import and suppress that rule locally — for example with oxlint (or Biome, or ESLint):
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// oxlint-disable-next-line typescript/consistent-type-imports -- constructor injection needs the class at runtime
|
|
87
|
+
import { Workspace } from "@decohere/mesh";
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
That pattern is used throughout Forge (`Build`, `Clean`, …). Same idea applies to ESLint `consistent-type-imports` or equivalent.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## When you *do* need `@inject`
|
|
95
|
+
|
|
96
|
+
`@inject(key)` overrides the paramtypes lookup with an explicit key: a class constructor, string, or symbol. Use it when there is no useful constructor in `design:paramtypes`.
|
|
97
|
+
|
|
98
|
+
| Situation | Why `@inject` |
|
|
99
|
+
|---|---|
|
|
100
|
+
| Interface or type alias (`ISampleStore`) | No runtime class; paramtypes is `Object` |
|
|
101
|
+
| String / symbol token | Registration key is not the class name |
|
|
102
|
+
| Array of implementations (`Transformer[]`) | Paramtypes is `Array`; without a token, Crucible would `resolveMany("Array")` |
|
|
103
|
+
| Paramtypes collapsed to `Object` (circular import, decorate-before-init) | Explicit token still resolves |
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { inject, injectable } from "@decohere/crucible/decorators";
|
|
107
|
+
|
|
108
|
+
export const SampleStoreToken = Symbol("SampleStore");
|
|
109
|
+
|
|
110
|
+
export interface ISampleStore {
|
|
111
|
+
get(id: string): Promise<unknown>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@injectable()
|
|
115
|
+
export class SampleService {
|
|
116
|
+
public constructor(
|
|
117
|
+
@inject(SampleStoreToken) private readonly store: ISampleStore,
|
|
118
|
+
) {}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Keep tokens in a `const.ts` (or next to the interface). Register against the same token:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
builder.registerSingleton(SampleStoreToken, PostgresSampleStore);
|
|
126
|
+
// or an already-built instance:
|
|
127
|
+
builder.registerSingleton(SampleStoreToken, store);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`@inject` takes `string | symbol | Constructable`. Prefer `@inject(SampleService)` over `@inject("SampleService")` — the class tracks renames. There is no `@inject({ many, optional })` yet — arrays use `resolveMany` automatically when the reflected type is `Array`, and missing keys are `null` (see [Optional / missing](#optional--missing-dependencies)).
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Registration and lifetimes
|
|
135
|
+
|
|
136
|
+
Keys are the class **name**, a string, or a symbol’s **description**. `resolve(SampleService)`, `resolve("SampleService")`, `@inject(SampleService)`, and `@inject("SampleService")` hit the same registration.
|
|
137
|
+
|
|
138
|
+
Registering the same class (or the same type on a token) twice **throws** `RegistrationError` — silent last-wins hides accidental double-registers. Replace it explicitly:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
builder.override.registerSingleton(SampleService);
|
|
142
|
+
builder.override.registerTransient(SampleService);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`builder.override` is a registrar with replace already bound — the builder itself is not mutated. Two different types on one token is still multi-bind: `resolve` throws `ResolveError`, `resolveMany` returns both.
|
|
146
|
+
|
|
147
|
+
| Lifetime | Cached |
|
|
148
|
+
|---|---|
|
|
149
|
+
| Singleton | On the root container — shared with every nested scope |
|
|
150
|
+
| Request | On the current scope — new instance per `beginScope` |
|
|
151
|
+
| Transient | Never — new instance every `resolve` |
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
builder.registerSingleton(SampleService);
|
|
155
|
+
builder.registerSingleton(Token, SampleService);
|
|
156
|
+
builder.registerSingleton(Token, existingInstance);
|
|
157
|
+
|
|
158
|
+
builder.registerRequestScoped(HelloController);
|
|
159
|
+
builder.registerRequestScoped(Token, HelloController);
|
|
160
|
+
builder.registerRequestScoped(Token, () => createConnection());
|
|
161
|
+
|
|
162
|
+
builder.registerTransient(Helper);
|
|
163
|
+
builder.registerTransient(Token, Helper);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`registerType(type, scope?)` and `registerTypes(...)` are the same registrations without the lifetime in the method name (default **transient**). `Scopes.singleton` is the string `"single-instance"`.
|
|
167
|
+
|
|
168
|
+
Cortex HTTP: controllers are **request-scoped** (one per request). Services are usually **singletons**.
|
|
169
|
+
|
|
170
|
+
`registerSingleton(Class, instance)` binds an existing object under the class name. Terminal does this for the workspace:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
builder.registerSingleton(workspace.constructor, workspace);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Recipes
|
|
179
|
+
|
|
180
|
+
### Many implementations of one token
|
|
181
|
+
|
|
182
|
+
Register several types under the same token. Inject them as an array with `@inject(Token)`.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
export const TransformerKey = Symbol("TransformerKey");
|
|
186
|
+
|
|
187
|
+
builder.registerSingleton(TransformerKey, SwcTranspiler);
|
|
188
|
+
builder.registerSingleton(TransformerKey, TypeEmitter);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
@injectable()
|
|
193
|
+
export class Build {
|
|
194
|
+
public constructor(
|
|
195
|
+
@inject(TransformerKey) private readonly transformers: Transformer[],
|
|
196
|
+
) {}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`resolve(TransformerKey)` **throws** `ResolveError` when more than one registration exists. Use `resolveMany(TransformerKey)` or constructor injection of an array.
|
|
201
|
+
|
|
202
|
+
### Request scopes and disposal
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
builder.registerRequestScoped(UnitOfWork);
|
|
206
|
+
builder.registerSingleton(Clock);
|
|
207
|
+
const container = builder.build();
|
|
208
|
+
|
|
209
|
+
{
|
|
210
|
+
using scope = container.beginScope("req-1");
|
|
211
|
+
const uow = scope.resolve(UnitOfWork); // per-scope
|
|
212
|
+
const clock = scope.resolve(Clock); // same singleton as the root
|
|
213
|
+
} // scope[Symbol.dispose]() — request-scoped instances are disposed
|
|
214
|
+
|
|
215
|
+
await using scope = container.beginScope("req-2");
|
|
216
|
+
// async teardown via Symbol.asyncDispose if the instance implements it
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- Child scopes copy the parent **registry** when created; they do not share the request-instance cache.
|
|
220
|
+
- Nested `beginScope` is allowed; each level has its own request instances.
|
|
221
|
+
- Named scopes: a live duplicate name throws `DuplicateScopeError`. After dispose, the name can be reused. Unnamed scopes do not collide.
|
|
222
|
+
- Disposing a parent does **not** dispose child scopes. Dispose the scope you created.
|
|
223
|
+
- Singletons are cached on the root container and shared with nested scopes. Resolving a singleton after the root is disposed throws.
|
|
224
|
+
|
|
225
|
+
Cortex `App.dispose` (Flux HMR / SIGINT) disposes the **root** container, then Scribe. Implement `Symbol.asyncDispose` (or `Symbol.dispose`) on long-lived services — sockets, pools, timers. Only **constructed** instances are disposed (resolve them at boot; transients are not cached). `export default app` is the composition-root contract; do not wrap it for extra teardown.
|
|
226
|
+
|
|
227
|
+
The current scope is always registered as `LifetimeScopeToken`:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { type LifetimeScope, LifetimeScopeToken } from "@decohere/crucible";
|
|
231
|
+
import { inject, injectable } from "@decohere/crucible/decorators";
|
|
232
|
+
|
|
233
|
+
@injectable()
|
|
234
|
+
export class ExceptionFilterHost {
|
|
235
|
+
public constructor(
|
|
236
|
+
@inject(LifetimeScopeToken) private readonly scope: LifetimeScope,
|
|
237
|
+
) {}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Factories
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
builder.registerRequestScoped("connection", () => createConnection());
|
|
245
|
+
const connection = container.resolve("connection");
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Arrow factories are called as functions (not `new`). Use them when construction needs arguments Crucible should not know about.
|
|
249
|
+
|
|
250
|
+
### Optional / missing dependencies
|
|
251
|
+
|
|
252
|
+
Unregistered keys do **not** throw. `resolve` returns `null` and logs a warning. Optional constructor params (`dep?: Foo`) still go through resolution — you get `null` if nothing is registered.
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
@injectable()
|
|
256
|
+
export class App {
|
|
257
|
+
public constructor(
|
|
258
|
+
@inject(HttpProviderToken) private readonly http?: HttpProvider,
|
|
259
|
+
) {}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
If a concrete class parameter is `null` at runtime, the usual cause is metadata loss (`import type`, missing `@injectable()`, or paramtypes `Object`) — not an optional registration.
|
|
264
|
+
|
|
265
|
+
### Cortex HTTP apps
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { Builder } from "@decohere/cortex";
|
|
269
|
+
import { FastifyProvider } from "@decohere/cortex/providers";
|
|
270
|
+
import { SampleService } from "./services/sample.service.js";
|
|
271
|
+
import { HelloController } from "./controllers/hello.controller.js";
|
|
272
|
+
|
|
273
|
+
await Builder.create()
|
|
274
|
+
.withHttp(FastifyProvider)
|
|
275
|
+
.withControllers(HelloController)
|
|
276
|
+
.withServices((cb) => {
|
|
277
|
+
cb.registerSingleton(SampleService);
|
|
278
|
+
})
|
|
279
|
+
.buildAndStart();
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`withHttp` / `withControllers` already register the HTTP stack (router, controllers as request-scoped, …). Application services go in `withServices`.
|
|
283
|
+
|
|
284
|
+
### Tests
|
|
285
|
+
|
|
286
|
+
Prefer constructing the subject with explicit fakes when you do not need the container. Use `CrucibleBuilder` when the test *is* the wiring (scopes, tokens, many-implementations).
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
const builder = new CrucibleBuilder();
|
|
290
|
+
builder.registerSingleton(SampleService, fakeSample);
|
|
291
|
+
builder.registerRequestScoped(HelloController);
|
|
292
|
+
const container = builder.build();
|
|
293
|
+
using scope = container.beginScope("test");
|
|
294
|
+
const hello = scope.resolve(HelloController);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
To assert constructor arguments under DI, use `spyOnConstructor` from `@decohere/nexus/testing` — a bare `vi.spyOn` / `vi.fn` constructor drops `design:paramtypes`. See [Asserting DI constructor arguments](../../docs/testing/how-tos/vitest-constructor-spy-with-crucible-di.md).
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Public API
|
|
302
|
+
|
|
303
|
+
### `@decohere/crucible`
|
|
304
|
+
|
|
305
|
+
| Export | Role |
|
|
306
|
+
|---|---|
|
|
307
|
+
| `CrucibleBuilder` | Fluent registration; `build()` → `Crucible` |
|
|
308
|
+
| `Crucible` | Container: `resolve`, `resolveMany`, `beginScope`, `Symbol.dispose` / `Symbol.asyncDispose` |
|
|
309
|
+
| `IContainer` / `LifetimeScope` / `ContainerBuilder` / `ServiceProvider` | Composition interfaces |
|
|
310
|
+
| `Scopes` | `{ request, singleton: "single-instance", transient }` |
|
|
311
|
+
| `LifetimeScopeToken` | Symbol for the current scope |
|
|
312
|
+
| `ResolveError` | `resolve` when multiple registrations share a key |
|
|
313
|
+
| `DuplicateScopeError` | `beginScope` with a live duplicate name |
|
|
314
|
+
| `RegistrationError` | Same type registered twice without `builder.override` |
|
|
315
|
+
|
|
316
|
+
### `@decohere/crucible/decorators`
|
|
317
|
+
|
|
318
|
+
| Export | Role |
|
|
319
|
+
|---|---|
|
|
320
|
+
| `injectable()` | Empty class decorator; emit `design:paramtypes` |
|
|
321
|
+
| `inject(key)` | Parameter decorator; resolve by string or symbol instead of the reflected class name |
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Pitfalls
|
|
326
|
+
|
|
327
|
+
1. **`import type` on an injected class** — paramtypes become `Object` → `resolve` returns `null`. Use a value import.
|
|
328
|
+
2. **Missing `@injectable()` (or any decorator) on the consumer** — no paramtypes, constructor is called with zero arguments.
|
|
329
|
+
3. **`resolve` of a multi-bound token** — throws; use `resolveMany` or inject `T[]` with `@inject(token)`.
|
|
330
|
+
4. **Same type registered twice** — throws `RegistrationError`. Use `builder.override.registerSingleton(...)` (tests, plugin replacements).
|
|
331
|
+
5. **Missing registration** — `null`, not an exception. Null-deref on first use is the symptom.
|
|
332
|
+
6. **Symbol keys** — the lookup key is `symbol.description`. Keep descriptions unique and stable.
|
|
333
|
+
7. **Renaming a class** without a token — the registration key is `Class.name`. Tokens isolate you from renames.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Non-goals
|
|
338
|
+
|
|
339
|
+
- Folder scanning / “declare a module and everything is registered”
|
|
340
|
+
- Property or setter injection (constructors only)
|
|
341
|
+
- `@inject({ many, optional })` metadata (not implemented; arrays and `null` cover the current cases)
|
|
342
|
+
- Compile-time graph checking
|
|
343
|
+
|
|
344
|
+
## Dependencies
|
|
345
|
+
|
|
346
|
+
| Package | Role |
|
|
347
|
+
|---|---|
|
|
348
|
+
| [`@decohere/nexus`](../nexus) | Reflection helpers for `design:paramtypes` (`getMeta`, `isConstructable`, …) |
|
|
349
|
+
| [`@decohere/scribe`](../scribe/README.md) | Logger facade |
|
|
350
|
+
|
|
351
|
+
Layering: [architecture](../../docs/architecture/).
|
|
@@ -139,8 +139,9 @@ class Crucible {
|
|
|
139
139
|
if (this.isDisposed) {
|
|
140
140
|
throw new Error("This container has been disposed");
|
|
141
141
|
}
|
|
142
|
-
const {
|
|
143
|
-
|
|
142
|
+
const { type, isInstance } = registration;
|
|
143
|
+
// One cache entry per registration: an instance registration is its own entry.
|
|
144
|
+
const cacheKey = type;
|
|
144
145
|
const existingInstance = this.instances.get(cacheKey);
|
|
145
146
|
if (existingInstance != null) {
|
|
146
147
|
return existingInstance;
|
|
@@ -31,8 +31,8 @@ class ServiceRegistry {
|
|
|
31
31
|
throw new _errors.RegistrationError(`A service is already registered for key ${registration.key}. Use builder.override.registerSingleton() (or the matching register* method) to replace it.`);
|
|
32
32
|
}
|
|
33
33
|
this.setRegistration(registration.key, [
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
...existing,
|
|
35
|
+
registration
|
|
36
36
|
]);
|
|
37
37
|
return registration;
|
|
38
38
|
}
|
|
@@ -129,8 +129,9 @@ export class Crucible {
|
|
|
129
129
|
if (this.isDisposed) {
|
|
130
130
|
throw new Error("This container has been disposed");
|
|
131
131
|
}
|
|
132
|
-
const {
|
|
133
|
-
|
|
132
|
+
const { type, isInstance } = registration;
|
|
133
|
+
// One cache entry per registration: an instance registration is its own entry.
|
|
134
|
+
const cacheKey = type;
|
|
134
135
|
const existingInstance = this.instances.get(cacheKey);
|
|
135
136
|
if (existingInstance != null) {
|
|
136
137
|
return existingInstance;
|
|
@@ -21,8 +21,8 @@ export class ServiceRegistry {
|
|
|
21
21
|
throw new RegistrationError(`A service is already registered for key ${registration.key}. Use builder.override.registerSingleton() (or the matching register* method) to replace it.`);
|
|
22
22
|
}
|
|
23
23
|
this.setRegistration(registration.key, [
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
...existing,
|
|
25
|
+
registration
|
|
26
26
|
]);
|
|
27
27
|
return registration;
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decohere/crucible",
|
|
3
|
-
"version": "0.0.1-dev.
|
|
3
|
+
"version": "0.0.1-dev.99",
|
|
4
4
|
"description": "Lightweight dependency injection container with decorator-based wiring",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@decohere/nexus": "0.0.1-dev.
|
|
34
|
-
"@decohere/scribe": "0.0.1-dev.
|
|
33
|
+
"@decohere/nexus": "0.0.1-dev.99",
|
|
34
|
+
"@decohere/scribe": "0.0.1-dev.99"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {}
|
|
37
37
|
}
|