@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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +254 -0
  3. package/dist/index.d.ts +620 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +1342 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/node.d.mts +287 -0
  8. package/dist/node.d.mts.map +1 -0
  9. package/dist/node.mjs +387 -0
  10. package/dist/node.mjs.map +1 -0
  11. package/dist/signal-source.types-BI3TYaCi.d.ts +317 -0
  12. package/dist/signal-source.types-BI3TYaCi.d.ts.map +1 -0
  13. package/dist/testing.d.ts +84 -0
  14. package/dist/testing.d.ts.map +1 -0
  15. package/dist/testing.js +276 -0
  16. package/dist/testing.js.map +1 -0
  17. package/package.json +77 -0
  18. package/src/capabilities/host-capability.ts +18 -0
  19. package/src/capabilities/index.ts +2 -0
  20. package/src/conformance/expect-venn-error.ts +18 -0
  21. package/src/conformance/harness.types.ts +14 -0
  22. package/src/conformance/index.ts +2 -0
  23. package/src/dotenv/dotenv-files.ts +30 -0
  24. package/src/dotenv/index.ts +2 -0
  25. package/src/dotenv/parse-dotenv.ts +34 -0
  26. package/src/errors/contract-codes.ts +37 -0
  27. package/src/errors/index.ts +7 -0
  28. package/src/errors/venn-error.ts +20 -0
  29. package/src/host/create-host.ts +16 -0
  30. package/src/host/create-node-host.ts +29 -0
  31. package/src/host/create-test-host.ts +28 -0
  32. package/src/host/create-worker-host.ts +32 -0
  33. package/src/host/host.types.ts +26 -0
  34. package/src/host/index.ts +5 -0
  35. package/src/host/unavailable.ts +23 -0
  36. package/src/index.ts +10 -0
  37. package/src/logger/console-logger.ts +12 -0
  38. package/src/logger/index.ts +3 -0
  39. package/src/logger/logger.types.ts +18 -0
  40. package/src/logger/memory-logger.ts +12 -0
  41. package/src/node.ts +8 -0
  42. package/src/port/assert-capabilities.ts +18 -0
  43. package/src/port/assert-port-shape.ts +18 -0
  44. package/src/port/bind-port.ts +28 -0
  45. package/src/port/index.ts +5 -0
  46. package/src/port/missing-capabilities.ts +14 -0
  47. package/src/port/port.types.ts +36 -0
  48. package/src/ports/clock/clock.port.ts +15 -0
  49. package/src/ports/clock/clock.types.ts +17 -0
  50. package/src/ports/clock/index.ts +4 -0
  51. package/src/ports/clock/system-clock.ts +9 -0
  52. package/src/ports/clock/virtual-clock.ts +22 -0
  53. package/src/ports/console/console.port.ts +13 -0
  54. package/src/ports/console/console.types.ts +17 -0
  55. package/src/ports/console/index.ts +3 -0
  56. package/src/ports/console/memory-console.ts +36 -0
  57. package/src/ports/console/node-console.ts +80 -0
  58. package/src/ports/file-system/file-system.errors.ts +10 -0
  59. package/src/ports/file-system/file-system.port.ts +12 -0
  60. package/src/ports/file-system/file-system.types.ts +27 -0
  61. package/src/ports/file-system/index.ts +6 -0
  62. package/src/ports/file-system/memory-fs.ts +46 -0
  63. package/src/ports/file-system/node-fs.ts +93 -0
  64. package/src/ports/index.ts +9 -0
  65. package/src/ports/lock-provider/fake-lock.ts +6 -0
  66. package/src/ports/lock-provider/in-process-lock.ts +26 -0
  67. package/src/ports/lock-provider/index.ts +4 -0
  68. package/src/ports/lock-provider/lock-provider.port.ts +16 -0
  69. package/src/ports/lock-provider/lock-provider.types.ts +8 -0
  70. package/src/ports/manifest-provider/default-manifest.ts +31 -0
  71. package/src/ports/manifest-provider/edit/edit-dependency.ts +82 -0
  72. package/src/ports/manifest-provider/edit/index.ts +7 -0
  73. package/src/ports/manifest-provider/edit/table-span.ts +39 -0
  74. package/src/ports/manifest-provider/index.ts +31 -0
  75. package/src/ports/manifest-provider/manifest.port.ts +16 -0
  76. package/src/ports/manifest-provider/manifest.types.ts +54 -0
  77. package/src/ports/manifest-provider/memory-manifest.ts +14 -0
  78. package/src/ports/manifest-provider/project.types.ts +94 -0
  79. package/src/ports/manifest-provider/read/index.ts +15 -0
  80. package/src/ports/manifest-provider/read/read-dependencies.ts +27 -0
  81. package/src/ports/manifest-provider/read/read-package.ts +32 -0
  82. package/src/ports/manifest-provider/read/read-profiles.ts +32 -0
  83. package/src/ports/manifest-provider/read/read-targets.ts +37 -0
  84. package/src/ports/manifest-provider/read/read-tooling.ts +15 -0
  85. package/src/ports/manifest-provider/read/read-workspace.ts +22 -0
  86. package/src/ports/manifest-provider/read/scalars.ts +35 -0
  87. package/src/ports/manifest-provider/read-run-settings.ts +38 -0
  88. package/src/ports/manifest-provider/resolve-alias.ts +22 -0
  89. package/src/ports/manifest-provider/toml/index.ts +3 -0
  90. package/src/ports/manifest-provider/toml/parse-toml.ts +52 -0
  91. package/src/ports/manifest-provider/toml/read-value.ts +104 -0
  92. package/src/ports/manifest-provider/toml/sections.ts +39 -0
  93. package/src/ports/manifest-provider/toml-docs.ts +27 -0
  94. package/src/ports/manifest-provider/toml-manifest.ts +41 -0
  95. package/src/ports/process-provider/fake-process.ts +32 -0
  96. package/src/ports/process-provider/index.ts +10 -0
  97. package/src/ports/process-provider/node-spawn.ts +52 -0
  98. package/src/ports/process-provider/process-provider.port.ts +13 -0
  99. package/src/ports/process-provider/process-provider.types.ts +47 -0
  100. package/src/ports/random/fixed-random.ts +10 -0
  101. package/src/ports/random/index.ts +4 -0
  102. package/src/ports/random/random.port.ts +13 -0
  103. package/src/ports/random/random.types.ts +10 -0
  104. package/src/ports/random/seeded-random.ts +19 -0
  105. package/src/ports/secret-provider/env-secrets.ts +19 -0
  106. package/src/ports/secret-provider/index.ts +6 -0
  107. package/src/ports/secret-provider/memory-secrets.ts +15 -0
  108. package/src/ports/secret-provider/secret-provider.port.ts +13 -0
  109. package/src/ports/secret-provider/secret-provider.types.ts +17 -0
  110. package/src/ports/secret-provider/secret.ts +17 -0
  111. package/src/ports/signal-source/fake-signals.ts +44 -0
  112. package/src/ports/signal-source/index.ts +11 -0
  113. package/src/ports/signal-source/node-signals.ts +33 -0
  114. package/src/ports/signal-source/signal-source.port.ts +13 -0
  115. package/src/ports/signal-source/signal-source.types.ts +24 -0
  116. package/src/testing.ts +16 -0
@@ -0,0 +1,317 @@
1
+ //#region src/ports/clock/clock.types.d.ts
2
+ /** Now, sleeping, deadlines. */
3
+ interface Clock {
4
+ /** Milliseconds since the epoch, or since the virtual clock's start. */
5
+ now(): number;
6
+ sleep(ms: number): Promise<void>;
7
+ }
8
+ /**
9
+ * A {@link Clock} whose flow of time a test controls.
10
+ *
11
+ * `advance` and `setTime` sit outside the negotiated contract, so code holding
12
+ * a `Clock` can never reach them.
13
+ */
14
+ interface VirtualClock extends Clock {
15
+ advance(ms: number): void;
16
+ setTime(epochMs: number): void;
17
+ }
18
+ //#endregion
19
+ //#region src/ports/file-system/file-system.types.d.ts
20
+ /**
21
+ * Read and write bytes.
22
+ *
23
+ * This is the port that lets `@venn-lang/core` run both in Node (the CLI) and in a
24
+ * Web Worker (the LSP). A leaked `import fs from "node:fs"` in core would break
25
+ * the editor, so all byte I/O goes through here.
26
+ */
27
+ interface FileSystem {
28
+ /** @throws VennError VN8010 when the path does not exist. */
29
+ read(path: string): Promise<Uint8Array>;
30
+ write(path: string, bytes: Uint8Array): Promise<void>;
31
+ exists(path: string): Promise<boolean>;
32
+ /** @throws VennError VN8010 when the path does not exist. */
33
+ remove(path: string): Promise<void>;
34
+ /**
35
+ * What a directory holds, one level deep, in no promised order. A path that
36
+ * is not a directory reads as empty rather than raising: asking what is
37
+ * inside something that holds nothing has an answer.
38
+ */
39
+ list(path: string): Promise<readonly DirEntry[]>;
40
+ }
41
+ /** One name inside a directory, and whether it holds more. */
42
+ interface DirEntry {
43
+ name: string;
44
+ directory: boolean;
45
+ }
46
+ //#endregion
47
+ //#region src/ports/lock-provider/lock-provider.types.d.ts
48
+ /** Releases a held lock. Calling it more than once is a no-op. */
49
+ type Release = () => void;
50
+ /** Named mutexes: the semantics behind `@lock` and `@serial`. */
51
+ interface LockProvider {
52
+ /** Resolves once the lock named `name` is held by this caller alone. */
53
+ acquire(name: string): Promise<Release>;
54
+ }
55
+ //#endregion
56
+ //#region src/ports/process-provider/process-provider.types.d.ts
57
+ /** What to run, where, and how to hear from it while it runs. */
58
+ interface SpawnArgs {
59
+ command: string;
60
+ args?: readonly string[];
61
+ /** Where it runs. The current directory unless this says otherwise. */
62
+ cwd?: string;
63
+ /** Added to the environment the host was started with. */
64
+ env?: Record<string, string>;
65
+ /**
66
+ * Whether to go through the platform shell.
67
+ *
68
+ * Needed for a command that is a script rather than an executable: on Windows
69
+ * `pnpm` is a `.cmd`, and nothing runs it without one. Off by default,
70
+ * because a shell re-reads the arguments and a quote that meant one thing to
71
+ * the caller means another to `cmd.exe`. Only the caller knows which of the
72
+ * two it is holding.
73
+ */
74
+ shell?: boolean;
75
+ /**
76
+ * Called with each chunk the command writes, as it writes it.
77
+ *
78
+ * A package manager installing a hundred packages takes half a minute, and a
79
+ * command silent for half a minute reads as one that has hung. What is
80
+ * streamed here is kept as well, so a caller can show it and read it after.
81
+ */
82
+ onOutput?: (chunk: string) => void;
83
+ }
84
+ /** How a run ended. */
85
+ interface ProcessResult {
86
+ code: number;
87
+ /** Everything it wrote, stdout and stderr together, in the order written. */
88
+ output: string;
89
+ }
90
+ /** A spawned subprocess. */
91
+ interface ProcessHandle {
92
+ readonly pid: number;
93
+ /** Resolves when the command ends. Never rejects: a failure is a result. */
94
+ wait(): Promise<ProcessResult>;
95
+ kill(): void;
96
+ }
97
+ /** Spawn subprocesses: a package manager, a driver, anything the host allows. */
98
+ interface ProcessProvider {
99
+ spawn(args: SpawnArgs): ProcessHandle;
100
+ }
101
+ //#endregion
102
+ //#region src/ports/random/random.types.d.ts
103
+ /**
104
+ * A source of randomness. Every implementation is reproducible, so a failing
105
+ * run can be replayed.
106
+ */
107
+ interface Random {
108
+ /** Uniform float in [0, 1). */
109
+ next(): number;
110
+ /** Uniform integer in the inclusive range [min, max]. */
111
+ int(min: number, max: number): number;
112
+ }
113
+ //#endregion
114
+ //#region src/ports/secret-provider/secret-provider.types.d.ts
115
+ /**
116
+ * A value that redacts itself. Serialising one, by any route, yields the
117
+ * redaction marker: redaction happens at the producer, because redacting in the
118
+ * UI is already too late.
119
+ */
120
+ interface Secret {
121
+ /** The only way to the raw value, and deliberately something you must ask for. */
122
+ reveal(): string;
123
+ toString(): string;
124
+ toJSON(): string;
125
+ }
126
+ /** Resolves `secrets.*` names to redaction-marked values. */
127
+ interface SecretProvider {
128
+ get(name: string): Secret | undefined;
129
+ has(name: string): boolean;
130
+ }
131
+ //#endregion
132
+ //#region src/conformance/harness.types.d.ts
133
+ /**
134
+ * Builds a fresh implementation for a single conformance run.
135
+ *
136
+ * Called once per test rather than once per suite, so no test can observe state
137
+ * another test left behind.
138
+ */
139
+ type PortFactory<T> = () => T | Promise<T>;
140
+ /** One implementation under test. */
141
+ interface ConformanceSpec<T> {
142
+ /** Shown in the test name, e.g. "memory" or "node". */
143
+ readonly name: string;
144
+ readonly factory: PortFactory<T>;
145
+ }
146
+ //#endregion
147
+ //#region src/ports/secret-provider/secret-provider.suite.d.ts
148
+ /** A secret the implementation under test is known to hold. */
149
+ type KnownSecret = {
150
+ name: string;
151
+ raw: string;
152
+ };
153
+ /**
154
+ * The {@link SecretProvider} TCK. The redaction law is the load-bearing one:
155
+ * an implementation that leaks through `JSON.stringify` fails here.
156
+ */
157
+ declare function secretProviderConformance(spec: ConformanceSpec<SecretProvider> & {
158
+ known: KnownSecret;
159
+ }): void;
160
+ //#endregion
161
+ //#region src/ports/manifest-provider/project.types.d.ts
162
+ /**
163
+ * What a manifest declares about the project itself, as against how it runs.
164
+ *
165
+ * The shape follows Cargo's, because the questions are the same and an answer
166
+ * the reader already knows beats a better one they must learn. Where Cargo has
167
+ * something this does not, the gap is noted so it reads as a decision.
168
+ */
169
+ /** `[package]`: who this is. */
170
+ interface PackageInfo {
171
+ name: string;
172
+ /**
173
+ * What `[package] version` said, and nothing when it said nothing.
174
+ *
175
+ * Optional so "not written" stays distinguishable from "written as 0.0.0": a
176
+ * workspace member inherits the first and keeps the second. `Manifest.version`
177
+ * is the resolved value, with the default already applied.
178
+ */
179
+ version?: string;
180
+ description?: string;
181
+ license?: string;
182
+ authors: readonly string[];
183
+ /** `[package] edition`: which language revision this was written against. */
184
+ edition?: string;
185
+ }
186
+ /** What a project builds. A server is a `bin` that does not end. */
187
+ type TargetKind = "lib" | "bin" | "test";
188
+ /** `[lib]` or one entry of `[[bin]]`: a thing to build, and where it starts. */
189
+ interface BuildTarget {
190
+ kind: TargetKind;
191
+ /** The name it is known by; a `bin`'s name is what `venn run --bin` takes. */
192
+ name: string;
193
+ /** The `.vn` file this target starts from, relative to the manifest. */
194
+ path: string;
195
+ }
196
+ /** One entry of `[dependencies]` / `[dev-dependencies]`. */
197
+ interface Dependency {
198
+ name: string;
199
+ /** A semver range, or absent when the version comes from elsewhere. */
200
+ version?: string;
201
+ /** `{ path = "../other" }`: a sibling in this workspace or on disk. */
202
+ path?: string;
203
+ /** `{ workspace = true }`: take the version the workspace pinned. */
204
+ fromWorkspace: boolean;
205
+ /** `{ optional = true }`: resolved and locked, installed only on demand. */
206
+ optional: boolean;
207
+ }
208
+ /**
209
+ * `[profile.dev]` and `[profile.release]`: how a build is made.
210
+ *
211
+ * One key, because one key is what a build does today. Settings about generated
212
+ * code, such as `sourceMap` and `minify`, arrive with the compiler: declaring
213
+ * them now would read as a contract and honour nothing.
214
+ */
215
+ interface Profile {
216
+ /** Whether a problem stops the build instead of only being reported. */
217
+ strict?: boolean;
218
+ }
219
+ /**
220
+ * Which package manager runs underneath `venn add` and `venn remove`.
221
+ *
222
+ * The interface is ours, the resolution is not: resolving versions well is
223
+ * years of work against three moving targets.
224
+ */
225
+ type PackageManagerName = "pnpm" | "npm" | "bun" | "yarn";
226
+ /** The `[tooling]` table. */
227
+ interface ToolingSettings {
228
+ manager: PackageManagerName;
229
+ }
230
+ /**
231
+ * `[workspace]`: a root that owns members, one lockfile and one `target/`.
232
+ *
233
+ * `[features]` is deliberately absent. It is the most intricate part of Cargo,
234
+ * it rests on conditional compilation this language does not have, and half of
235
+ * it would be worse than none.
236
+ */
237
+ interface WorkspaceSettings {
238
+ /** Path globs naming the members, e.g. `["packages/*"]`. */
239
+ members: readonly string[];
240
+ exclude: readonly string[];
241
+ /** Which members a command with no target acts on. Empty means all of them. */
242
+ defaultMembers: readonly string[];
243
+ /** `[workspace.package]`: metadata a member may inherit. */
244
+ package: Partial<PackageInfo>;
245
+ /** `[workspace.dependencies]`: the versions `{ workspace = true }` takes. */
246
+ dependencies: readonly Dependency[];
247
+ }
248
+ //#endregion
249
+ //#region src/ports/manifest-provider/manifest.types.d.ts
250
+ /** A parsed `venn.toml`: the project manifest the LSP and the runner read. */
251
+ interface Manifest {
252
+ name: string;
253
+ version: string;
254
+ /** `[package]` in full. `name` and `version` above are the short way in. */
255
+ package: PackageInfo;
256
+ /** `[lib]` and `[[bin]]`, in the order they were written. */
257
+ targets: readonly BuildTarget[];
258
+ dependencies: readonly Dependency[];
259
+ devDependencies: readonly Dependency[];
260
+ /** `[patch]`: a version this project insists on, wherever it is asked for. */
261
+ patch: readonly Dependency[];
262
+ /** `[profile.<name>]`, keyed by name. `dev` and `release` are built in. */
263
+ profiles: Record<string, Profile>;
264
+ tooling: ToolingSettings;
265
+ /** `[workspace]`, or absent when this manifest is a plain package. */
266
+ workspace?: WorkspaceSettings;
267
+ /** `[env.<name>]` sections, mapping an environment to its variables. */
268
+ env: Record<string, Record<string, string>>;
269
+ /**
270
+ * `[env] files`: which dotenv files to read, in order, later winning.
271
+ * `${name}` stands for the selected environment. Empty means the convention.
272
+ */
273
+ envFiles: readonly string[];
274
+ /** `[paths]`, mapping an `#alias` to a directory. */
275
+ paths: Record<string, string>;
276
+ /** `[format]`: how `venn fmt` and the editor format this project. */
277
+ format: FormatSettings;
278
+ }
279
+ /** The `[format]` table. Absent keys fall back to the language defaults. */
280
+ interface FormatSettings {
281
+ indent?: number;
282
+ tabs?: boolean;
283
+ organize?: boolean;
284
+ sort?: boolean;
285
+ }
286
+ /**
287
+ * Produces a {@link Manifest}. Implementations: `toml-manifest` parses one,
288
+ * `memory-manifest` returns a preset.
289
+ */
290
+ interface ManifestProvider {
291
+ load(): Manifest;
292
+ }
293
+ //#endregion
294
+ //#region src/ports/signal-source/signal-source.types.d.ts
295
+ /**
296
+ * The ways a system asks a program to stop. `SIGBREAK` is Windows' Ctrl+Break,
297
+ * `SIGHUP` is the terminal itself going away.
298
+ */
299
+ declare const ALL_SIGNALS: readonly ["SIGINT", "SIGTERM", "SIGBREAK", "SIGHUP"];
300
+ /** One signal name, drawn from {@link ALL_SIGNALS}. */
301
+ type SystemSignal = (typeof ALL_SIGNALS)[number];
302
+ /** Called on every delivery of the signal it was registered for. */
303
+ type SignalHandler = (signal: SystemSignal) => void;
304
+ /** Drops a subscription. Calling it more than once is harmless. */
305
+ type Unsubscribe = () => void;
306
+ /**
307
+ * Where a program hears the system asking it to stop.
308
+ *
309
+ * The double matters more than usual here: raising a true SIGINT inside a test
310
+ * run stops the test runner, not the code under test.
311
+ */
312
+ interface SignalSource {
313
+ on(signal: SystemSignal, handler: SignalHandler): Unsubscribe;
314
+ }
315
+ //#endregion
316
+ export { DirEntry as A, Random as C, SpawnArgs as D, ProcessResult as E, Clock as M, VirtualClock as N, LockProvider as O, SecretProvider as S, ProcessProvider as T, KnownSecret as _, Unsubscribe as a, PortFactory as b, ManifestProvider as c, PackageInfo as d, PackageManagerName as f, WorkspaceSettings as g, ToolingSettings as h, SystemSignal as i, FileSystem as j, Release as k, BuildTarget as l, TargetKind as m, SignalHandler as n, FormatSettings as o, Profile as p, SignalSource as r, Manifest as s, ALL_SIGNALS as t, Dependency as u, secretProviderConformance as v, ProcessHandle as w, Secret as x, ConformanceSpec as y };
317
+ //# sourceMappingURL=signal-source.types-BI3TYaCi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal-source.types-BI3TYaCi.d.ts","names":[],"sources":["../src/ports/clock/clock.types.ts","../src/ports/file-system/file-system.types.ts","../src/ports/lock-provider/lock-provider.types.ts","../src/ports/process-provider/process-provider.types.ts","../src/ports/random/random.types.ts","../src/ports/secret-provider/secret-provider.types.ts","../src/conformance/harness.types.ts","../src/ports/secret-provider/secret-provider.suite.ts","../src/ports/manifest-provider/project.types.ts","../src/ports/manifest-provider/manifest.types.ts","../src/ports/signal-source/signal-source.types.ts"],"mappings":";;UACiB;;EAEf;EACA,MAAM,aAAa;;;;;;;;UASJ,qBAAqB;EACpC,QAAQ;EACR,QAAQ;;;;;;;;;;;UCRO;;EAEf,KAAK,eAAe,QAAQ;EAC5B,MAAM,cAAc,OAAO,aAAa;EACxC,OAAO,eAAe;;EAEtB,OAAO,eAAe;;;;;;EAMtB,KAAK,eAAe,iBAAiB;;;UAItB;EACf;EACA;;;;;KCxBU;;UAGK;;EAEf,QAAQ,eAAe,QAAQ;;;;;UCLhB;EACf;EACA;;EAEA;;EAEA,MAAM;;;;;;;;;;EAUN;;;;;;;;EAQA,YAAY;;;UAIG;EACf;;EAEA;;;UAIe;WACN;;EAET,QAAQ,QAAQ;EAChB;;;UAIe;EACf,MAAM,MAAM,YAAY;;;;;;;;UCzCT;;EAEf;;EAEA,IAAI,aAAa;;;;;;;;;UCHF;;EAEf;EACA;EACA;;;UAIe;EACf,IAAI,eAAe;EACnB,IAAI;;;;;;;;;;KCTM,YAAY,WAAW,IAAI,QAAQ;;UAG9B,gBAAgB;;WAEtB;WACA,SAAS,YAAY;;;;;KCPpB;EAAgB;EAAc;;;;;;iBAM1B,0BACd,MAAM,gBAAgB;EAAoB,OAAO;;;;;;;;;;;;UCHlC;EACf;;;;;;;;EAQA;EACA;EACA;EACA;;EAEA;;;KAIU;;UAGK;EACf,MAAM;;EAEN;;EAEA;;;UAIe;EACf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;;UAUe;;EAEf;;;;;;;;KASU;;UAGK;EACf,SAAS;;;;;;;;;UAUM;;EAEf;EACA;;EAEA;;EAEA,SAAS,QAAQ;;EAEjB,uBAAuB;;;;;UClFR;EACf;EACA;;EAEA,SAAS;;EAET,kBAAkB;EAClB,uBAAuB;EACvB,0BAA0B;;EAE1B,gBAAgB;;EAEhB,UAAU,eAAe;EACzB,SAAS;;EAET,YAAY;;EAEZ,KAAK,eAAe;;;;;EAKpB;;EAEA,OAAO;;EAEP,QAAQ;;;UAIO;EACf;EACA;EACA;EACA;;;;;;UAOe;EACf,QAAQ;;;;;;;;cChDG;;KAGD,uBAAuB;;KAGvB,iBAAiB,QAAQ;;KAGzB;;;;;;;UAQK;EACf,GAAG,QAAQ,cAAc,SAAS,gBAAgB"}
@@ -0,0 +1,84 @@
1
+ import { C as Random, D as SpawnArgs, M as Clock, O as LockProvider, T as ProcessProvider, b as PortFactory, c as ManifestProvider, i as SystemSignal, j as FileSystem, r as SignalSource, v as secretProviderConformance, y as ConformanceSpec } from "./signal-source.types-BI3TYaCi.js";
2
+ //#region src/conformance/expect-venn-error.d.ts
3
+ /**
4
+ * Asserts that an async operation rejects with a `VennError` whose `.code`
5
+ * matches.
6
+ *
7
+ * Suites assert on the code and never on the message, so wording can be
8
+ * improved without breaking a conformance run.
9
+ *
10
+ * @param args.op - the operation expected to reject.
11
+ * @param args.code - pattern the error code must match, e.g. `/^VN8/`.
12
+ */
13
+ declare function expectVennError(args: {
14
+ op: () => Promise<unknown>;
15
+ code: RegExp;
16
+ }): Promise<void>;
17
+ //#endregion
18
+ //#region src/ports/clock/clock.suite.d.ts
19
+ /** The {@link Clock} TCK. Both the system and virtual clocks satisfy it. */
20
+ declare function clockConformance(spec: ConformanceSpec<Clock>): void;
21
+ //#endregion
22
+ //#region src/ports/file-system/file-system.suite.d.ts
23
+ /** The {@link FileSystem} TCK. Real and double both run it. */
24
+ declare function fileSystemConformance(spec: ConformanceSpec<FileSystem>): void;
25
+ //#endregion
26
+ //#region src/ports/lock-provider/lock-provider.suite.d.ts
27
+ /**
28
+ * The {@link LockProvider} TCK. Acquiring, releasing and acquiring again must
29
+ * not deadlock, which is the one law the fake and the real mutex share.
30
+ */
31
+ declare function lockProviderConformance(spec: ConformanceSpec<LockProvider>): void;
32
+ //#endregion
33
+ //#region src/ports/manifest-provider/manifest.suite.d.ts
34
+ /**
35
+ * The {@link ManifestProvider} TCK. Whatever the source, `load()` returns a
36
+ * manifest with a name and an env map, never a partial one.
37
+ */
38
+ declare function manifestProviderConformance(spec: ConformanceSpec<ManifestProvider> & {
39
+ expectedName: string;
40
+ }): void;
41
+ //#endregion
42
+ //#region src/ports/process-provider/process-provider.suite.d.ts
43
+ /**
44
+ * The {@link ProcessProvider} TCK.
45
+ *
46
+ * Each implementation is asked to run something that says a known thing and
47
+ * ends with a known code. *What* it is asked to run differs, since a fake has
48
+ * no shell and the real one has no script, but the answer must not.
49
+ */
50
+ declare function processProviderConformance(spec: ConformanceSpec<ProcessProvider> & {
51
+ runs: SpawnArgs;
52
+ expected: {
53
+ code: number;
54
+ output: string;
55
+ };
56
+ }): void;
57
+ //#endregion
58
+ //#region src/ports/random/random.suite.d.ts
59
+ /** The {@link Random} TCK: the range laws both seeded and fixed satisfy. */
60
+ declare function randomConformance(spec: ConformanceSpec<Random>): void;
61
+ //#endregion
62
+ //#region src/ports/signal-source/signal-source.suite.d.ts
63
+ /** How a suite delivers a signal to the implementation it is testing. */
64
+ interface SignalSpec {
65
+ name: string;
66
+ factory(): SignalSource;
67
+ /** Deliver `signal` the way this implementation really receives one. */
68
+ raise(args: {
69
+ source: SignalSource;
70
+ signal: SystemSignal;
71
+ }): void;
72
+ /** The signal this implementation can be asked to deliver here. */
73
+ delivers: SystemSignal;
74
+ /** A second signal, subscribed to but never raised, to prove they stay apart. */
75
+ other: SystemSignal;
76
+ }
77
+ /**
78
+ * The {@link SignalSource} TCK. The process and the fake both run it, because a
79
+ * double that delivers differently is a test that lies about shutdown.
80
+ */
81
+ declare function signalSourceConformance(spec: SignalSpec): void;
82
+ //#endregion
83
+ export { type ConformanceSpec, type PortFactory, type SignalSpec, clockConformance, expectVennError, fileSystemConformance, lockProviderConformance, manifestProviderConformance, processProviderConformance, randomConformance, secretProviderConformance, signalSourceConformance };
84
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","names":[],"sources":["../src/conformance/expect-venn-error.ts","../src/ports/clock/clock.suite.ts","../src/ports/file-system/file-system.suite.ts","../src/ports/lock-provider/lock-provider.suite.ts","../src/ports/manifest-provider/manifest.suite.ts","../src/ports/process-provider/process-provider.suite.ts","../src/ports/random/random.suite.ts","../src/ports/signal-source/signal-source.suite.ts"],"mappings":";;;;;;;;;;;;iBAYsB,gBAAgB;EACpC,UAAU;EACV,MAAM;IACJ;;;;iBCVY,iBAAiB,MAAM,gBAAgB;;;;iBCCvC,sBAAsB,MAAM,gBAAgB;;;;;;;iBCE5C,wBAAwB,MAAM,gBAAgB;;;;;;;iBCA9C,4BACd,MAAM,gBAAgB;EAAsB;;;;;;;;;;;iBCE9B,2BACd,MAAM,gBAAgB;EACpB,MAAM;EACN;IAAY;IAAc;;;;;;iBCTd,kBAAkB,MAAM,gBAAgB;;;;UCDvC;EACf;EACA,WAAW;;EAEX,MAAM;IAAQ,QAAQ;IAAc,QAAQ;;;EAE5C,UAAU;;EAEV,OAAO;;;;;;iBAOO,wBAAwB,MAAM"}
@@ -0,0 +1,276 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { fc, test } from "@fast-check/vitest";
3
+ //#region src/conformance/expect-venn-error.ts
4
+ /**
5
+ * Asserts that an async operation rejects with a `VennError` whose `.code`
6
+ * matches.
7
+ *
8
+ * Suites assert on the code and never on the message, so wording can be
9
+ * improved without breaking a conformance run.
10
+ *
11
+ * @param args.op - the operation expected to reject.
12
+ * @param args.code - pattern the error code must match, e.g. `/^VN8/`.
13
+ */
14
+ async function expectVennError(args) {
15
+ await expect(args.op()).rejects.toMatchObject({ code: expect.stringMatching(args.code) });
16
+ }
17
+ //#endregion
18
+ //#region src/ports/clock/clock.suite.ts
19
+ /** The {@link Clock} TCK. Both the system and virtual clocks satisfy it. */
20
+ function clockConformance(spec) {
21
+ describe(`Clock · ${spec.name}`, () => {
22
+ it("now() is non-decreasing", async () => {
23
+ const clock = await spec.factory();
24
+ const first = clock.now();
25
+ expect(clock.now()).toBeGreaterThanOrEqual(first);
26
+ });
27
+ it("sleep advances now() by about the requested delay", async () => {
28
+ const clock = await spec.factory();
29
+ const before = clock.now();
30
+ await clock.sleep(20);
31
+ expect(clock.now()).toBeGreaterThanOrEqual(before + 18);
32
+ });
33
+ });
34
+ }
35
+ //#endregion
36
+ //#region src/ports/file-system/file-system.suite.ts
37
+ /** The {@link FileSystem} TCK. Real and double both run it. */
38
+ function fileSystemConformance(spec) {
39
+ describe(`FileSystem · ${spec.name}`, () => {
40
+ it("read returns exactly the bytes written", async () => {
41
+ const fs = await spec.factory();
42
+ const bytes = new Uint8Array([
43
+ 1,
44
+ 2,
45
+ 3,
46
+ 4
47
+ ]);
48
+ await fs.write("a.bin", bytes);
49
+ expect(await fs.read("a.bin")).toEqual(bytes);
50
+ });
51
+ it("reading a missing path fails with VN8, never a raw error", async () => {
52
+ const fs = await spec.factory();
53
+ await expectVennError({
54
+ op: () => fs.read("missing.bin"),
55
+ code: /^VN8/
56
+ });
57
+ });
58
+ it("exists reflects write then remove", async () => {
59
+ const fs = await spec.factory();
60
+ await fs.write("k.bin", new Uint8Array([0]));
61
+ expect(await fs.exists("k.bin")).toBe(true);
62
+ await fs.remove("k.bin");
63
+ expect(await fs.exists("k.bin")).toBe(false);
64
+ });
65
+ /**
66
+ * Bounded in both draws and payload, because `node-fs` runs this against a
67
+ * real disk. Twenty-five draws demonstrate last-write-wins as well as
68
+ * fast-check's default hundred, and the raised timeout means a loaded
69
+ * machine reports a slow disk rather than a failure.
70
+ */
71
+ test.prop([fc.uint8Array({ maxLength: 4096 })], { numRuns: 25 })("write is last-write-wins", async (bytes) => {
72
+ const fs = await spec.factory();
73
+ await fs.write("k.bin", new Uint8Array([9]));
74
+ await fs.write("k.bin", bytes);
75
+ expect(await fs.read("k.bin")).toEqual(bytes);
76
+ }, 2e4);
77
+ /**
78
+ * A workspace's `members = ["packages/*"]` rests on listing, so the two
79
+ * implementations have to agree about it. The double has no directories at
80
+ * all, only paths, which is exactly why this is asked of both.
81
+ */
82
+ it("lists what a directory holds, one level deep", async () => {
83
+ const fs = await spec.factory();
84
+ await fs.write("pkg/a/venn.toml", new Uint8Array([1]));
85
+ await fs.write("pkg/b/venn.toml", new Uint8Array([1]));
86
+ await fs.write("pkg/top.txt", new Uint8Array([1]));
87
+ expect((await fs.list("pkg")).map((entry) => entry.name).sort()).toEqual([
88
+ "a",
89
+ "b",
90
+ "top.txt"
91
+ ]);
92
+ });
93
+ it("says which of them hold more", async () => {
94
+ const fs = await spec.factory();
95
+ await fs.write("root/inside/deep.txt", new Uint8Array([1]));
96
+ await fs.write("root/flat.txt", new Uint8Array([1]));
97
+ const found = await fs.list("root");
98
+ expect(found.find((entry) => entry.name === "inside")?.directory).toBe(true);
99
+ expect(found.find((entry) => entry.name === "flat.txt")?.directory).toBe(false);
100
+ });
101
+ /** Asking what is inside something that holds nothing has an answer. */
102
+ it("reads a path that is not a directory as empty", async () => {
103
+ expect(await (await spec.factory()).list("nao/existe")).toEqual([]);
104
+ });
105
+ });
106
+ }
107
+ //#endregion
108
+ //#region src/ports/lock-provider/lock-provider.suite.ts
109
+ /**
110
+ * The {@link LockProvider} TCK. Acquiring, releasing and acquiring again must
111
+ * not deadlock, which is the one law the fake and the real mutex share.
112
+ */
113
+ function lockProviderConformance(spec) {
114
+ describe(`LockProvider · ${spec.name}`, () => {
115
+ it("acquire returns a release, and re-acquiring after release works", async () => {
116
+ const lock = await spec.factory();
117
+ (await lock.acquire("k"))();
118
+ const again = await lock.acquire("k");
119
+ again();
120
+ expect(typeof again).toBe("function");
121
+ });
122
+ });
123
+ }
124
+ //#endregion
125
+ //#region src/ports/manifest-provider/manifest.suite.ts
126
+ /**
127
+ * The {@link ManifestProvider} TCK. Whatever the source, `load()` returns a
128
+ * manifest with a name and an env map, never a partial one.
129
+ */
130
+ function manifestProviderConformance(spec) {
131
+ describe(`ManifestProvider · ${spec.name}`, () => {
132
+ it("loads a manifest with a name and env map", async () => {
133
+ const manifest = (await spec.factory()).load();
134
+ expect(manifest.name).toBe(spec.expectedName);
135
+ expect(typeof manifest.env).toBe("object");
136
+ });
137
+ });
138
+ }
139
+ //#endregion
140
+ //#region src/ports/process-provider/process-provider.suite.ts
141
+ /**
142
+ * The {@link ProcessProvider} TCK.
143
+ *
144
+ * Each implementation is asked to run something that says a known thing and
145
+ * ends with a known code. *What* it is asked to run differs, since a fake has
146
+ * no shell and the real one has no script, but the answer must not.
147
+ */
148
+ function processProviderConformance(spec) {
149
+ describe(`ProcessProvider · ${spec.name}`, () => {
150
+ it("waits for the command and reports how it ended", async () => {
151
+ const result = await (await spec.factory()).spawn(spec.runs).wait();
152
+ expect(result.code).toBe(spec.expected.code);
153
+ expect(result.output).toContain(spec.expected.output);
154
+ });
155
+ /** Half a minute of silence reads as a command that has hung. */
156
+ it("streams what the command writes, as it writes it", async () => {
157
+ const proc = await spec.factory();
158
+ const seen = [];
159
+ await proc.spawn({
160
+ ...spec.runs,
161
+ onOutput: (chunk) => seen.push(chunk)
162
+ }).wait();
163
+ expect(seen.join("")).toContain(spec.expected.output);
164
+ });
165
+ it("hands back a handle that can be asked to stop", async () => {
166
+ const handle = (await spec.factory()).spawn(spec.runs);
167
+ expect(typeof handle.pid).toBe("number");
168
+ expect(() => handle.kill()).not.toThrow();
169
+ });
170
+ });
171
+ }
172
+ //#endregion
173
+ //#region src/ports/random/random.suite.ts
174
+ /** The {@link Random} TCK: the range laws both seeded and fixed satisfy. */
175
+ function randomConformance(spec) {
176
+ describe(`Random · ${spec.name}`, () => {
177
+ it("next() stays within [0, 1)", async () => {
178
+ const random = await spec.factory();
179
+ for (let i = 0; i < 100; i++) {
180
+ const value = random.next();
181
+ expect(value).toBeGreaterThanOrEqual(0);
182
+ expect(value).toBeLessThan(1);
183
+ }
184
+ });
185
+ it("int(min, max) stays within the inclusive range", async () => {
186
+ const random = await spec.factory();
187
+ for (let i = 0; i < 100; i++) {
188
+ const value = random.int(3, 7);
189
+ expect(value).toBeGreaterThanOrEqual(3);
190
+ expect(value).toBeLessThanOrEqual(7);
191
+ }
192
+ });
193
+ });
194
+ }
195
+ //#endregion
196
+ //#region src/ports/secret-provider/secret-provider.suite.ts
197
+ /**
198
+ * The {@link SecretProvider} TCK. The redaction law is the load-bearing one:
199
+ * an implementation that leaks through `JSON.stringify` fails here.
200
+ */
201
+ function secretProviderConformance(spec) {
202
+ describe(`SecretProvider · ${spec.name}`, () => {
203
+ it("reveals the known secret", async () => {
204
+ expect((await spec.factory()).get(spec.known.name)?.reveal()).toBe(spec.known.raw);
205
+ });
206
+ it("never leaks the raw value through serialization", async () => {
207
+ const secret = (await spec.factory()).get(spec.known.name);
208
+ expect(JSON.stringify(secret)).not.toContain(spec.known.raw);
209
+ expect(String(secret)).not.toContain(spec.known.raw);
210
+ });
211
+ });
212
+ }
213
+ //#endregion
214
+ //#region src/ports/signal-source/signal-source.suite.ts
215
+ /**
216
+ * The {@link SignalSource} TCK. The process and the fake both run it, because a
217
+ * double that delivers differently is a test that lies about shutdown.
218
+ */
219
+ function signalSourceConformance(spec) {
220
+ describe(`SignalSource · ${spec.name}`, () => {
221
+ const listen = () => {
222
+ const source = spec.factory();
223
+ return {
224
+ source,
225
+ seen: [],
226
+ raise: () => spec.raise({
227
+ source,
228
+ signal: spec.delivers
229
+ })
230
+ };
231
+ };
232
+ it("calls the handler with the signal that arrived", () => {
233
+ const { source, seen, raise } = listen();
234
+ const off = source.on(spec.delivers, (signal) => seen.push(signal));
235
+ raise();
236
+ off();
237
+ expect(seen).toEqual([spec.delivers]);
238
+ });
239
+ it("calls every handler waiting on that signal", () => {
240
+ const { source, seen, raise } = listen();
241
+ const first = source.on(spec.delivers, () => seen.push(spec.delivers));
242
+ const second = source.on(spec.delivers, () => seen.push(spec.delivers));
243
+ raise();
244
+ first();
245
+ second();
246
+ expect(seen).toHaveLength(2);
247
+ });
248
+ it("leaves other signals alone", () => {
249
+ const { source, seen, raise } = listen();
250
+ const off = source.on(spec.other, (signal) => seen.push(signal));
251
+ raise();
252
+ off();
253
+ expect(seen).toEqual([]);
254
+ });
255
+ it("stops delivering once unsubscribed, and unsubscribing twice is harmless", () => {
256
+ const { source, seen, raise } = listen();
257
+ const off = source.on(spec.delivers, (signal) => seen.push(signal));
258
+ off();
259
+ off();
260
+ raise();
261
+ expect(seen).toEqual([]);
262
+ });
263
+ it("keeps delivering while the subscription lives", () => {
264
+ const { source, seen, raise } = listen();
265
+ const off = source.on(spec.delivers, (signal) => seen.push(signal));
266
+ raise();
267
+ raise();
268
+ off();
269
+ expect(seen).toEqual([spec.delivers, spec.delivers]);
270
+ });
271
+ });
272
+ }
273
+ //#endregion
274
+ export { clockConformance, expectVennError, fileSystemConformance, lockProviderConformance, manifestProviderConformance, processProviderConformance, randomConformance, secretProviderConformance, signalSourceConformance };
275
+
276
+ //# sourceMappingURL=testing.js.map