@venn-lang/contracts 0.1.0
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/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/index.d.ts +620 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1342 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.mts +287 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +387 -0
- package/dist/node.mjs.map +1 -0
- package/dist/signal-source.types-BI3TYaCi.d.ts +317 -0
- package/dist/signal-source.types-BI3TYaCi.d.ts.map +1 -0
- package/dist/testing.d.ts +84 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +276 -0
- package/dist/testing.js.map +1 -0
- package/package.json +77 -0
- package/src/capabilities/host-capability.ts +18 -0
- package/src/capabilities/index.ts +2 -0
- package/src/conformance/expect-venn-error.ts +18 -0
- package/src/conformance/harness.types.ts +14 -0
- package/src/conformance/index.ts +2 -0
- package/src/dotenv/dotenv-files.ts +30 -0
- package/src/dotenv/index.ts +2 -0
- package/src/dotenv/parse-dotenv.ts +34 -0
- package/src/errors/contract-codes.ts +37 -0
- package/src/errors/index.ts +7 -0
- package/src/errors/venn-error.ts +20 -0
- package/src/host/create-host.ts +16 -0
- package/src/host/create-node-host.ts +29 -0
- package/src/host/create-test-host.ts +28 -0
- package/src/host/create-worker-host.ts +32 -0
- package/src/host/host.types.ts +26 -0
- package/src/host/index.ts +5 -0
- package/src/host/unavailable.ts +23 -0
- package/src/index.ts +10 -0
- package/src/logger/console-logger.ts +12 -0
- package/src/logger/index.ts +3 -0
- package/src/logger/logger.types.ts +18 -0
- package/src/logger/memory-logger.ts +12 -0
- package/src/node.ts +8 -0
- package/src/port/assert-capabilities.ts +18 -0
- package/src/port/assert-port-shape.ts +18 -0
- package/src/port/bind-port.ts +28 -0
- package/src/port/index.ts +5 -0
- package/src/port/missing-capabilities.ts +14 -0
- package/src/port/port.types.ts +36 -0
- package/src/ports/clock/clock.port.ts +15 -0
- package/src/ports/clock/clock.types.ts +17 -0
- package/src/ports/clock/index.ts +4 -0
- package/src/ports/clock/system-clock.ts +9 -0
- package/src/ports/clock/virtual-clock.ts +22 -0
- package/src/ports/console/console.port.ts +13 -0
- package/src/ports/console/console.types.ts +17 -0
- package/src/ports/console/index.ts +3 -0
- package/src/ports/console/memory-console.ts +36 -0
- package/src/ports/console/node-console.ts +80 -0
- package/src/ports/file-system/file-system.errors.ts +10 -0
- package/src/ports/file-system/file-system.port.ts +12 -0
- package/src/ports/file-system/file-system.types.ts +27 -0
- package/src/ports/file-system/index.ts +6 -0
- package/src/ports/file-system/memory-fs.ts +46 -0
- package/src/ports/file-system/node-fs.ts +93 -0
- package/src/ports/index.ts +9 -0
- package/src/ports/lock-provider/fake-lock.ts +6 -0
- package/src/ports/lock-provider/in-process-lock.ts +26 -0
- package/src/ports/lock-provider/index.ts +4 -0
- package/src/ports/lock-provider/lock-provider.port.ts +16 -0
- package/src/ports/lock-provider/lock-provider.types.ts +8 -0
- package/src/ports/manifest-provider/default-manifest.ts +31 -0
- package/src/ports/manifest-provider/edit/edit-dependency.ts +82 -0
- package/src/ports/manifest-provider/edit/index.ts +7 -0
- package/src/ports/manifest-provider/edit/table-span.ts +39 -0
- package/src/ports/manifest-provider/index.ts +31 -0
- package/src/ports/manifest-provider/manifest.port.ts +16 -0
- package/src/ports/manifest-provider/manifest.types.ts +54 -0
- package/src/ports/manifest-provider/memory-manifest.ts +14 -0
- package/src/ports/manifest-provider/project.types.ts +94 -0
- package/src/ports/manifest-provider/read/index.ts +15 -0
- package/src/ports/manifest-provider/read/read-dependencies.ts +27 -0
- package/src/ports/manifest-provider/read/read-package.ts +32 -0
- package/src/ports/manifest-provider/read/read-profiles.ts +32 -0
- package/src/ports/manifest-provider/read/read-targets.ts +37 -0
- package/src/ports/manifest-provider/read/read-tooling.ts +15 -0
- package/src/ports/manifest-provider/read/read-workspace.ts +22 -0
- package/src/ports/manifest-provider/read/scalars.ts +35 -0
- package/src/ports/manifest-provider/read-run-settings.ts +38 -0
- package/src/ports/manifest-provider/resolve-alias.ts +22 -0
- package/src/ports/manifest-provider/toml/index.ts +3 -0
- package/src/ports/manifest-provider/toml/parse-toml.ts +52 -0
- package/src/ports/manifest-provider/toml/read-value.ts +104 -0
- package/src/ports/manifest-provider/toml/sections.ts +39 -0
- package/src/ports/manifest-provider/toml-docs.ts +27 -0
- package/src/ports/manifest-provider/toml-manifest.ts +41 -0
- package/src/ports/process-provider/fake-process.ts +32 -0
- package/src/ports/process-provider/index.ts +10 -0
- package/src/ports/process-provider/node-spawn.ts +52 -0
- package/src/ports/process-provider/process-provider.port.ts +13 -0
- package/src/ports/process-provider/process-provider.types.ts +47 -0
- package/src/ports/random/fixed-random.ts +10 -0
- package/src/ports/random/index.ts +4 -0
- package/src/ports/random/random.port.ts +13 -0
- package/src/ports/random/random.types.ts +10 -0
- package/src/ports/random/seeded-random.ts +19 -0
- package/src/ports/secret-provider/env-secrets.ts +19 -0
- package/src/ports/secret-provider/index.ts +6 -0
- package/src/ports/secret-provider/memory-secrets.ts +15 -0
- package/src/ports/secret-provider/secret-provider.port.ts +13 -0
- package/src/ports/secret-provider/secret-provider.types.ts +17 -0
- package/src/ports/secret-provider/secret.ts +17 -0
- package/src/ports/signal-source/fake-signals.ts +44 -0
- package/src/ports/signal-source/index.ts +11 -0
- package/src/ports/signal-source/node-signals.ts +33 -0
- package/src/ports/signal-source/signal-source.port.ts +13 -0
- package/src/ports/signal-source/signal-source.types.ts +24 -0
- package/src/testing.ts +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vinicius Borges
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# @venn-lang/contracts
|
|
2
|
+
|
|
3
|
+
> The ports the Venn core runs on, their implementations, and the host that carries them.
|
|
4
|
+
|
|
5
|
+
The core knows nothing about files, clocks, subprocesses or secrets. It knows **ports**: small
|
|
6
|
+
typed interfaces whose implementations arrive at startup inside a `Host` object. This package
|
|
7
|
+
defines every port, ships two implementations of each (a real one and a test double), and provides
|
|
8
|
+
the conformance suites both must pass. There is no DI container and no runtime resolution: the
|
|
9
|
+
entry point assembles a `Host` and passes it inward.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
The CLI assembles the Node host and hands it to the runtime:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createNodeHost } from "@venn-lang/contracts/node";
|
|
17
|
+
|
|
18
|
+
const host = createNodeHost({ root: process.cwd() });
|
|
19
|
+
await host.fs.write("out/report.json", new TextEncoder().encode("{}"));
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Tests take the all-doubles host, overriding only what the test cares about:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { createTestHost, createVirtualClock } from "@venn-lang/contracts";
|
|
26
|
+
|
|
27
|
+
const host = createTestHost({ clock: createVirtualClock({ start: 1_700_000_000_000 }) });
|
|
28
|
+
await host.clock.sleep(5_000); // resolves immediately; now() moved on by 5000 ms
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Binding an implementation to a port goes through `bindPort`, which negotiates capabilities and
|
|
32
|
+
then checks the shape:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { bindPort, createMemoryFs, FileSystemPort } from "@venn-lang/contracts";
|
|
36
|
+
|
|
37
|
+
const fs = bindPort({ port: FileSystemPort, impl: createMemoryFs(), caps: ["fs"] });
|
|
38
|
+
// caps: [] throws VN2010. An impl without `list` throws VN2011.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Entry points
|
|
42
|
+
|
|
43
|
+
| Specifier | Platform | Holds |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `@venn-lang/contracts` | neutral (Node and Web Worker) | ports, doubles, hosts, errors, capability negotiation |
|
|
46
|
+
| `@venn-lang/contracts/node` | Node | the `node:*`-backed implementations and `createNodeHost` |
|
|
47
|
+
| `@venn-lang/contracts/testing` | test | the conformance suites; imports vitest and fast-check |
|
|
48
|
+
|
|
49
|
+
The split is enforced by the build, not by convention: `index` and `testing` are built with tsdown
|
|
50
|
+
`platform: "neutral"`, so a stray `node:*` import fails the build. That is what lets
|
|
51
|
+
[`@venn-lang/core`](../core) run inside the editor's Web Worker.
|
|
52
|
+
|
|
53
|
+
## Two implementations or it is not a port
|
|
54
|
+
|
|
55
|
+
Every port lands in a single commit with three things: the real implementation, the test double,
|
|
56
|
+
and a conformance suite (TCK) that both of them run. If you cannot name the second implementation
|
|
57
|
+
straight away (`memory`, `fake`, `seeded`, `fixed`, `virtual`), it is not a port. It is a module
|
|
58
|
+
with a good interface, and it should stay one.
|
|
59
|
+
|
|
60
|
+
`Port<T>` is a TypeScript descriptor, not a Zod schema. In Zod 4 `z.function()` is a factory rather
|
|
61
|
+
than a validatable type, so a port's function shape cannot be expressed as data. Zod stays on data.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
export interface Port<T> {
|
|
65
|
+
readonly id: string; // "venn.port.filesystem"
|
|
66
|
+
readonly version: number; // the contract version, not the package version
|
|
67
|
+
readonly requires: readonly HostCapability[]; // negotiated against Host.caps
|
|
68
|
+
readonly methods: readonly (keyof T & string)[]; // checked against the interface by the compiler
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Three checks, in order:
|
|
73
|
+
|
|
74
|
+
1. **Compile time.** `methods` cannot name anything that is not a key of `T`.
|
|
75
|
+
2. **Load time.** `bindPort` compares `requires` against `Host.caps` (VN2010), then checks
|
|
76
|
+
`typeof impl[m] === "function"` for each declared method (VN2011).
|
|
77
|
+
3. **Behaviour.** The conformance suite. The suite is the specification; the prose is commentary.
|
|
78
|
+
|
|
79
|
+
`AnyPort` is `Port<T>` with the element type erased, so heterogeneous collections of ports can be
|
|
80
|
+
typed without variance friction.
|
|
81
|
+
|
|
82
|
+
## Ports
|
|
83
|
+
|
|
84
|
+
| Port | `id` | Requires | Methods | Real | Double |
|
|
85
|
+
| --- | --- | --- | --- | --- | --- |
|
|
86
|
+
| `FileSystem` | `venn.port.filesystem` | `fs` | `read` `write` `exists` `remove` `list` | `createNodeFs` (`/node`) | `createMemoryFs` |
|
|
87
|
+
| `Clock` | `venn.port.clock` | `clock` | `now` `sleep` | `createSystemClock` | `createVirtualClock` |
|
|
88
|
+
| `Random` | `venn.port.random` | `random` | `next` `int` | `createSeededRandom` | `createFixedRandom` |
|
|
89
|
+
| `SecretProvider` | `venn.port.secrets` | `secrets` | `get` `has` | `createEnvSecrets` | `createMemorySecrets` |
|
|
90
|
+
| `ProcessProvider` | `venn.port.process` | `process` | `spawn` | `createNodeSpawn` (`/node`) | `createFakeProcess` |
|
|
91
|
+
| `Console` | `venn.port.console` | `io` | `write` `writeError` `readLine` `args` | `createNodeConsole` (`/node`) | `createMemoryConsole` |
|
|
92
|
+
| `SignalSource` | `venn.port.signals` | `process` | `on` | `createNodeSignals` (`/node`) | `createFakeSignals` |
|
|
93
|
+
| `LockProvider` | `venn.port.lock` | none | `acquire` | `createInProcessLock` | `createFakeLock` |
|
|
94
|
+
| `ManifestProvider` | `venn.port.manifest` | none | `load` | `createTomlManifest` | `createMemoryManifest` |
|
|
95
|
+
|
|
96
|
+
Implementations marked `/node` are reachable only through `@venn-lang/contracts/node`, and are
|
|
97
|
+
deliberately absent from the folder barrels so the main entry stays neutral. Each port also
|
|
98
|
+
exports its descriptor: `FileSystemPort`, `ClockPort`, `RandomPort`, `SecretProviderPort`,
|
|
99
|
+
`ProcessProviderPort`, `ConsolePort`, `SignalSourcePort`, `LockProviderPort`,
|
|
100
|
+
`ManifestProviderPort`.
|
|
101
|
+
|
|
102
|
+
`LockProvider` and `ManifestProvider` require no capability: one is promise chaining, the other is
|
|
103
|
+
parsing. Neither touches the outside world.
|
|
104
|
+
|
|
105
|
+
### Notes on individual ports
|
|
106
|
+
|
|
107
|
+
- **FileSystem.** `read` and `remove` on a missing path throw VN8010, never a raw `ENOENT`.
|
|
108
|
+
`list` is one level deep and reads a non-directory as empty. `createNodeFs({ root })` resolves
|
|
109
|
+
relative paths against `root` and leaves absolute paths alone.
|
|
110
|
+
- **Clock.** `createVirtualClock` makes `sleep` advance internal time and resolve at once. Its
|
|
111
|
+
`advance` and `setTime` sit outside `ClockPort.methods` on purpose: they are test controls, not
|
|
112
|
+
part of the negotiated contract.
|
|
113
|
+
- **SecretProvider.** `makeSecret({ reveal })` wraps a raw value so that `toString()` and
|
|
114
|
+
`toJSON()` both collapse to `REDACTED`. `reveal()` is the only way out, and it is deliberately
|
|
115
|
+
explicit. Redaction happens at the producer, so anything reaching a reporter is already marked.
|
|
116
|
+
- **ProcessProvider.** `spawn` streams each chunk to `onOutput` as it arrives and keeps the same
|
|
117
|
+
text in `ProcessResult.output`. `SpawnArgs.env` adds to the host environment rather than
|
|
118
|
+
replacing it, and `shell` is off unless the caller asks for it.
|
|
119
|
+
- **SignalSource.** The fake is what tests use: raising a real SIGINT would stop the test runner
|
|
120
|
+
rather than the code under test. `createNodeSignals` subscribes silently to a signal the platform
|
|
121
|
+
does not have, which `isKnownSignal` reports on directly.
|
|
122
|
+
- **ManifestProvider.** `createTomlManifest({ content })` parses a `venn.toml` into a `Manifest`:
|
|
123
|
+
`[package]`, `[lib]` and `[[bin]]` targets, dependencies, `[patch]`, profiles, `[tooling]`,
|
|
124
|
+
`[workspace]`, `[env.*]`, `[paths]` and `[format]`.
|
|
125
|
+
|
|
126
|
+
### Manifest helpers
|
|
127
|
+
|
|
128
|
+
Reading and rewriting `venn.toml` without going through a provider:
|
|
129
|
+
|
|
130
|
+
| Export | What it does |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `parseToml(content)` | The `venn.toml` subset: sections, nested `[a.b]`, `[[bin]]`, scalars, arrays, inline tables |
|
|
133
|
+
| `tomlDocs(content)` | The comment block written directly above each key, as that key's documentation |
|
|
134
|
+
| `defaultManifest(overrides?)` | A `Manifest` with nothing declared, which is what a project with no `venn.toml` gets |
|
|
135
|
+
| `resolveAlias({ spec, paths })` | Splits a `#alias/rest` specifier against `[paths]`, or `undefined` when it names no alias |
|
|
136
|
+
| `addDependency({ text, name, version, table? })` | The manifest text with one dependency written in, kept in name order |
|
|
137
|
+
| `removeDependency({ text, name, table? })` | The manifest text without that dependency |
|
|
138
|
+
| `readInheritable(data)` | The subset of `[package]` a workspace member may inherit. A name is never inherited |
|
|
139
|
+
| `DEPENDENCIES` `DEFAULT_PROFILES` `LIB_ROOT` `MAIN_ROOT` `BIN_DIR` | The conventional table name, profiles and target roots |
|
|
140
|
+
|
|
141
|
+
Types: `Manifest`, `FormatSettings`, `PackageInfo`, `BuildTarget`, `TargetKind`, `Dependency`,
|
|
142
|
+
`Profile`, `ToolingSettings`, `WorkspaceSettings`, `PackageManagerName`, `DependencyEdit`,
|
|
143
|
+
`AliasTarget`.
|
|
144
|
+
|
|
145
|
+
## Capabilities and the host
|
|
146
|
+
|
|
147
|
+
`ALL_CAPABILITIES` is the full set: `fs`, `process`, `net`, `clock`, `random`, `secrets`, `log`,
|
|
148
|
+
`io`. A host advertises the subset it can actually serve in `Host.caps`, and a port declares what
|
|
149
|
+
it needs in `requires`. Negotiation compares the two before anything runs.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
export interface Host {
|
|
153
|
+
readonly fs: FileSystem;
|
|
154
|
+
readonly proc: ProcessProvider;
|
|
155
|
+
readonly clock: Clock;
|
|
156
|
+
readonly random: Random;
|
|
157
|
+
readonly secrets: SecretProvider;
|
|
158
|
+
readonly log: Logger;
|
|
159
|
+
readonly lock: LockProvider;
|
|
160
|
+
readonly caps: readonly HostCapability[];
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
| Assembler | Capabilities | Built from |
|
|
165
|
+
| --- | --- | --- |
|
|
166
|
+
| `createHost.worker()` | `fs` `clock` `random` `secrets` `log` | memory fs, system clock, seeded random, memory secrets and logger |
|
|
167
|
+
| `createHost.test(overrides?)` | all eight | doubles throughout, with any field overridable |
|
|
168
|
+
| `createNodeHost({ root? })` (`/node`) | all eight | node fs, real spawn, system clock, env secrets, console logger |
|
|
169
|
+
|
|
170
|
+
`createWorkerHost` and `createTestHost` are also exported directly; `createHost` is the object that
|
|
171
|
+
groups the two neutral ones. The Node assembler is deliberately not on it, because it pulls in
|
|
172
|
+
`node:*`.
|
|
173
|
+
|
|
174
|
+
A worker has no `process`, so its `proc` field is built by `unavailable()`. Every declared method
|
|
175
|
+
on it throws VN2012 with a readable message, rather than a `TypeError` halfway through a test.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { ProcessProviderPort, unavailable } from "@venn-lang/contracts";
|
|
179
|
+
import type { ProcessProvider } from "@venn-lang/contracts";
|
|
180
|
+
|
|
181
|
+
const proc = unavailable<ProcessProvider>({
|
|
182
|
+
capability: "process",
|
|
183
|
+
methods: ProcessProviderPort.methods,
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Conformance
|
|
188
|
+
|
|
189
|
+
A suite takes a `ConformanceSpec<T>`: a display name and a `factory` that returns a fresh
|
|
190
|
+
implementation, synchronously or not. Running it is one call per implementation.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// file-system.test.ts
|
|
194
|
+
import { fileSystemConformance } from "@venn-lang/contracts/testing";
|
|
195
|
+
|
|
196
|
+
fileSystemConformance({ name: "memory", factory: () => createMemoryFs() });
|
|
197
|
+
fileSystemConformance({
|
|
198
|
+
name: "node-fs",
|
|
199
|
+
factory: async () => createNodeFs({ root: await mkdtemp(join(tmpdir(), "venn-fs-")) }),
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Some suites ask for more than the spec: `secretProviderConformance` takes a `known` secret,
|
|
204
|
+
`manifestProviderConformance` takes an `expectedName`, `processProviderConformance` takes `runs`
|
|
205
|
+
and `expected`, and `signalSourceConformance` takes a whole `SignalSpec` saying how a signal is
|
|
206
|
+
delivered to that implementation.
|
|
207
|
+
|
|
208
|
+
Exported from `@venn-lang/contracts/testing`: `clockConformance`, `fileSystemConformance`,
|
|
209
|
+
`lockProviderConformance`, `manifestProviderConformance`, `processProviderConformance`,
|
|
210
|
+
`randomConformance`, `secretProviderConformance`, `signalSourceConformance`, plus `expectVennError`
|
|
211
|
+
and the types `ConformanceSpec`, `PortFactory` and `SignalSpec`.
|
|
212
|
+
|
|
213
|
+
`expectVennError({ op, code })` asserts that an async operation rejects with a `VennError` whose
|
|
214
|
+
`.code` matches a regex. Suites assert on codes, never on prose, so error wording stays free to
|
|
215
|
+
improve.
|
|
216
|
+
|
|
217
|
+
Adding an implementation to an existing port is one new file, one line in the folder barrel and one
|
|
218
|
+
line in the test. The suite itself is never touched.
|
|
219
|
+
|
|
220
|
+
## Errors
|
|
221
|
+
|
|
222
|
+
Every failure crossing a contracts boundary is a `VennError`: an `Error` with a stable `code` and
|
|
223
|
+
an optional structured `detail`.
|
|
224
|
+
|
|
225
|
+
| Code | Raised by | When |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| `VN2010` | `hostMissingCapability` | a port requires a capability the host does not advertise |
|
|
228
|
+
| `VN2011` | `portShapeMismatch` | an implementation is missing a method the port declares |
|
|
229
|
+
| `VN2012` | `capabilityUnavailable` | code reached a capability the host marked unavailable |
|
|
230
|
+
| `VN8010` | `fsNotFound` | a read or remove targeted a path that does not exist |
|
|
231
|
+
|
|
232
|
+
## Environment files
|
|
233
|
+
|
|
234
|
+
Both the runner and the editor resolve dotenv files through the same three exports, so they cannot
|
|
235
|
+
disagree about where a value lives.
|
|
236
|
+
|
|
237
|
+
| Export | What it does |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `DOTENV_CONVENTION` | `.env`, `.env.${name}`, `.env.local`, `.env.${name}.local`, lowest precedence first |
|
|
240
|
+
| `dotenvFiles({ configured?, name })` | Which files to read, in order, for one environment |
|
|
241
|
+
| `parseDotenv(content)` | `NAME=value` per line, with comments, blanks, `export ` and quotes. No variable expansion |
|
|
242
|
+
|
|
243
|
+
## Logging
|
|
244
|
+
|
|
245
|
+
`Logger` has a single method, `log(entry)`, where an entry is a `LogLevel` and a message.
|
|
246
|
+
`createConsoleLogger()` writes through the global console and is safe in both Node and a worker.
|
|
247
|
+
`createMemoryLogger()` returns a `MemoryLogger`, which keeps every entry in `entries` for
|
|
248
|
+
assertions.
|
|
249
|
+
|
|
250
|
+
## See also
|
|
251
|
+
|
|
252
|
+
- [`@venn-lang/core`](../core) receives a `Host` and imports nothing else for I/O.
|
|
253
|
+
- [`@venn-lang/runtime`](../runtime) executes flows against that host.
|
|
254
|
+
- [`@venn-lang/cli`](../cli) assembles the Node host and is the only package free to use `node:*`.
|