@contractspec/lib.testing 3.7.5 → 3.7.7

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 CHANGED
@@ -1,51 +1,62 @@
1
1
  # @contractspec/lib.testing
2
2
 
3
- Website: https://contractspec.io/
3
+ Website: https://contractspec.io
4
4
 
5
+ **Contract-aware testing utilities and runners.**
5
6
 
6
- **Safe regeneration verification** — Capture production traffic and generate golden tests automatically.
7
+ ## What It Provides
7
8
 
8
- Golden-test utilities that record real requests/responses and generate runnable test suites. Prove that regenerated code behaves identically to the original.
9
+ - **Layer**: lib.
10
+ - **Consumers**: CLI, bundles.
11
+ - `src/adapters/` contains runtime, provider, or environment-specific adapters.
12
+ - Related ContractSpec packages include `@contractspec/lib.contracts-spec`, `@contractspec/lib.schema`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
13
+ - `src/adapters/` contains runtime, provider, or environment-specific adapters.
9
14
 
10
- - **TrafficRecorder** captures production requests/responses with sampling + sanitization.
11
- - **GoldenTestGenerator** converts snapshots into runnable suites.
12
- - **Adapters** output Vitest or Jest files and helper runners.
13
-
14
- ### Usage
15
-
16
- ```ts
17
- import {
18
- TrafficRecorder,
19
- InMemoryTrafficStore,
20
- } from '@contractspec/lib.testing/recorder';
21
-
22
- const recorder = new TrafficRecorder({
23
- store: new InMemoryTrafficStore(),
24
- sampleRate: 0.01,
25
- });
26
-
27
- await recorder.record({
28
- operation: { name: 'orders.create', version: 3 },
29
- input: payload,
30
- output,
31
- success: true,
32
- timestamp: new Date(),
33
- });
34
- ```
35
-
36
- See `GoldenTestGenerator` for generating suites and CLI in `@contractspec/app.contracts-cli`.
15
+ ## Installation
37
16
 
17
+ `npm install @contractspec/lib.testing`
38
18
 
19
+ or
39
20
 
21
+ `bun add @contractspec/lib.testing`
40
22
 
23
+ ## Usage
41
24
 
25
+ Import the root entrypoint from `@contractspec/lib.testing`, or choose a documented subpath when you only need one part of the package surface.
42
26
 
27
+ ## Architecture
43
28
 
29
+ - `src/adapters/` contains runtime, provider, or environment-specific adapters.
30
+ - `src/generator` is part of the package's public or composition surface.
31
+ - `src/index.ts` is the root public barrel and package entrypoint.
32
+ - `src/recorder` is part of the package's public or composition surface.
33
+ - `src/types.ts` is shared public type definitions.
44
34
 
35
+ ## Public Entry Points
45
36
 
37
+ - Export `.` resolves through `./src/index.ts`.
46
38
 
39
+ ## Local Commands
47
40
 
41
+ - `bun run dev` — contractspec-bun-build dev
42
+ - `bun run build` — bun run prebuild && bun run build:bundle && bun run build:types
43
+ - `bun run test` — bun test
44
+ - `bun run lint` — bun lint:fix
45
+ - `bun run lint:check` — biome check .
46
+ - `bun run lint:fix` — biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .
47
+ - `bun run typecheck` — tsc --noEmit
48
+ - `bun run publish:pkg` — bun publish --tolerate-republish --ignore-scripts --verbose
49
+ - `bun run publish:pkg:canary` — bun publish:pkg --tag canary
50
+ - `bun run clean` — rimraf dist .turbo
51
+ - `bun run build:bundle` — contractspec-bun-build transpile
52
+ - `bun run build:types` — contractspec-bun-build types
53
+ - `bun run prebuild` — contractspec-bun-build prebuild
48
54
 
55
+ ## Recent Updates
49
56
 
57
+ - Replace eslint+prettier by biomejs to optimize speed.
50
58
 
59
+ ## Notes
51
60
 
61
+ - TrafficRecorder and GoldenTestGenerator interfaces are public API — do not break signatures.
62
+ - Test output format must stay compatible with Vitest and Jest runners.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export * from './types';
2
- export * from './recorder/traffic-recorder';
3
- export * from './generator/golden-test-generator';
4
- export * from './generator/assertion-builder';
5
- export * from './adapters/vitest-adapter';
6
1
  export * from './adapters/jest-adapter';
2
+ export * from './adapters/vitest-adapter';
3
+ export * from './generator/assertion-builder';
4
+ export * from './generator/golden-test-generator';
5
+ export * from './recorder/traffic-recorder';
6
+ export * from './types';
package/dist/index.js CHANGED
@@ -1,71 +1,4 @@
1
1
  // @bun
2
- // src/recorder/traffic-recorder.ts
3
- import { randomUUID } from "crypto";
4
-
5
- class InMemoryTrafficStore {
6
- items = [];
7
- async save(snapshot) {
8
- this.items.push(snapshot);
9
- }
10
- async list(operation) {
11
- if (!operation)
12
- return [...this.items];
13
- return this.items.filter((item) => item.operation.name === operation);
14
- }
15
- }
16
-
17
- class TrafficRecorder {
18
- store;
19
- sampleRate;
20
- sanitize;
21
- constructor(options) {
22
- this.store = options.store;
23
- this.sampleRate = options.sampleRate ?? 1;
24
- this.sanitize = options.sanitize;
25
- }
26
- async record(input) {
27
- if (!this.shouldSample())
28
- return;
29
- const snapshot = {
30
- id: randomUUID(),
31
- operation: input.operation,
32
- input: structuredCloneSafe(input.input),
33
- output: structuredCloneSafe(input.output),
34
- error: input.error ? structuredCloneSafe(input.error) : undefined,
35
- success: input.success,
36
- timestamp: new Date,
37
- durationMs: input.durationMs,
38
- tenantId: input.tenantId,
39
- userId: input.userId,
40
- channel: input.channel,
41
- metadata: input.metadata
42
- };
43
- const sanitized = this.sanitize ? this.sanitize(snapshot) : snapshot;
44
- await this.store.save(sanitized);
45
- }
46
- shouldSample() {
47
- if (this.sampleRate >= 1)
48
- return true;
49
- return Math.random() <= this.sampleRate;
50
- }
51
- }
52
- function structuredCloneSafe(value) {
53
- if (value == null)
54
- return value ?? undefined;
55
- try {
56
- const clone = globalThis.structuredClone;
57
- if (typeof clone === "function") {
58
- return clone(value);
59
- }
60
- return JSON.parse(JSON.stringify(value));
61
- } catch {
62
- return;
63
- }
64
- }
65
- // src/generator/golden-test-generator.ts
66
- import { randomUUID as randomUUID2 } from "crypto";
67
- import { performance } from "perf_hooks";
68
-
69
2
  // src/generator/assertion-builder.ts
70
3
  function buildAssertions(testCase, ctx) {
71
4
  if (testCase.success) {
@@ -87,60 +20,60 @@ function serialize(value) {
87
20
  }, 2);
88
21
  }
89
22
 
90
- // src/adapters/vitest-adapter.ts
91
- function generateVitestSuite(options) {
23
+ // src/adapters/jest-adapter.ts
24
+ function generateJestSuite(options) {
92
25
  const caseBlocks = options.cases.map((testCase) => {
93
26
  const inputConst = serialize(testCase.input);
94
27
  const metadataConst = serialize(testCase.metadata ?? {});
95
- const assertions = testCase.success ? [
96
- `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});`,
97
- `expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`
98
- ] : [
99
- `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`
100
- ];
28
+ const successBlock = `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});
29
+ expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`;
30
+ const failureBlock = `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`;
101
31
  return `
102
- it('${testCase.name}', async () => {
32
+ test('${testCase.name}', async () => {
103
33
  const input${testCase.id} = ${inputConst};
104
34
  const metadata${testCase.id} = ${metadataConst};
105
- ${assertions.join(`
106
- `)}
35
+ ${testCase.success ? successBlock : failureBlock}
107
36
  });`;
108
37
  }).join(`
109
38
  `);
110
39
  return `
111
- import { describe, it, expect } from 'bun:test';
112
40
  import { ${options.runnerFunction} } from '${options.runnerImport}';
113
41
 
114
42
  describe('${options.suiteName}', () => {${caseBlocks}
115
43
  });
116
44
  `.trim();
117
45
  }
118
-
119
- // src/adapters/jest-adapter.ts
120
- function generateJestSuite(options) {
46
+ // src/adapters/vitest-adapter.ts
47
+ function generateVitestSuite(options) {
121
48
  const caseBlocks = options.cases.map((testCase) => {
122
49
  const inputConst = serialize(testCase.input);
123
50
  const metadataConst = serialize(testCase.metadata ?? {});
124
- const successBlock = `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});
125
- expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`;
126
- const failureBlock = `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`;
51
+ const assertions = testCase.success ? [
52
+ `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});`,
53
+ `expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`
54
+ ] : [
55
+ `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`
56
+ ];
127
57
  return `
128
- test('${testCase.name}', async () => {
58
+ it('${testCase.name}', async () => {
129
59
  const input${testCase.id} = ${inputConst};
130
60
  const metadata${testCase.id} = ${metadataConst};
131
- ${testCase.success ? successBlock : failureBlock}
61
+ ${assertions.join(`
62
+ `)}
132
63
  });`;
133
64
  }).join(`
134
65
  `);
135
66
  return `
67
+ import { describe, it, expect } from 'bun:test';
136
68
  import { ${options.runnerFunction} } from '${options.runnerImport}';
137
69
 
138
70
  describe('${options.suiteName}', () => {${caseBlocks}
139
71
  });
140
72
  `.trim();
141
73
  }
142
-
143
74
  // src/generator/golden-test-generator.ts
75
+ import { randomUUID } from "crypto";
76
+ import { performance } from "perf_hooks";
144
77
  class GoldenTestGenerator {
145
78
  serializeMetadata;
146
79
  constructor(serializeMetadata = (snapshot) => ({
@@ -152,7 +85,7 @@ class GoldenTestGenerator {
152
85
  }
153
86
  createCases(snapshots) {
154
87
  return snapshots.map((snapshot, index) => ({
155
- id: snapshot.id ?? randomUUID2(),
88
+ id: snapshot.id ?? randomUUID(),
156
89
  name: snapshot.success ? `case-${index + 1}-success` : `case-${index + 1}-failure`,
157
90
  input: snapshot.input,
158
91
  expectedOutput: snapshot.output,
@@ -212,6 +145,69 @@ async function runGoldenTests(cases, runner) {
212
145
  }
213
146
  return results;
214
147
  }
148
+ // src/recorder/traffic-recorder.ts
149
+ import { randomUUID as randomUUID2 } from "crypto";
150
+
151
+ class InMemoryTrafficStore {
152
+ items = [];
153
+ async save(snapshot) {
154
+ this.items.push(snapshot);
155
+ }
156
+ async list(operation) {
157
+ if (!operation)
158
+ return [...this.items];
159
+ return this.items.filter((item) => item.operation.name === operation);
160
+ }
161
+ }
162
+
163
+ class TrafficRecorder {
164
+ store;
165
+ sampleRate;
166
+ sanitize;
167
+ constructor(options) {
168
+ this.store = options.store;
169
+ this.sampleRate = options.sampleRate ?? 1;
170
+ this.sanitize = options.sanitize;
171
+ }
172
+ async record(input) {
173
+ if (!this.shouldSample())
174
+ return;
175
+ const snapshot = {
176
+ id: randomUUID2(),
177
+ operation: input.operation,
178
+ input: structuredCloneSafe(input.input),
179
+ output: structuredCloneSafe(input.output),
180
+ error: input.error ? structuredCloneSafe(input.error) : undefined,
181
+ success: input.success,
182
+ timestamp: new Date,
183
+ durationMs: input.durationMs,
184
+ tenantId: input.tenantId,
185
+ userId: input.userId,
186
+ channel: input.channel,
187
+ metadata: input.metadata
188
+ };
189
+ const sanitized = this.sanitize ? this.sanitize(snapshot) : snapshot;
190
+ await this.store.save(sanitized);
191
+ }
192
+ shouldSample() {
193
+ if (this.sampleRate >= 1)
194
+ return true;
195
+ return Math.random() <= this.sampleRate;
196
+ }
197
+ }
198
+ function structuredCloneSafe(value) {
199
+ if (value == null)
200
+ return value ?? undefined;
201
+ try {
202
+ const clone = globalThis.structuredClone;
203
+ if (typeof clone === "function") {
204
+ return clone(value);
205
+ }
206
+ return JSON.parse(JSON.stringify(value));
207
+ } catch {
208
+ return;
209
+ }
210
+ }
215
211
  export {
216
212
  serialize,
217
213
  runGoldenTests,
@@ -1,70 +1,3 @@
1
- // src/recorder/traffic-recorder.ts
2
- import { randomUUID } from "node:crypto";
3
-
4
- class InMemoryTrafficStore {
5
- items = [];
6
- async save(snapshot) {
7
- this.items.push(snapshot);
8
- }
9
- async list(operation) {
10
- if (!operation)
11
- return [...this.items];
12
- return this.items.filter((item) => item.operation.name === operation);
13
- }
14
- }
15
-
16
- class TrafficRecorder {
17
- store;
18
- sampleRate;
19
- sanitize;
20
- constructor(options) {
21
- this.store = options.store;
22
- this.sampleRate = options.sampleRate ?? 1;
23
- this.sanitize = options.sanitize;
24
- }
25
- async record(input) {
26
- if (!this.shouldSample())
27
- return;
28
- const snapshot = {
29
- id: randomUUID(),
30
- operation: input.operation,
31
- input: structuredCloneSafe(input.input),
32
- output: structuredCloneSafe(input.output),
33
- error: input.error ? structuredCloneSafe(input.error) : undefined,
34
- success: input.success,
35
- timestamp: new Date,
36
- durationMs: input.durationMs,
37
- tenantId: input.tenantId,
38
- userId: input.userId,
39
- channel: input.channel,
40
- metadata: input.metadata
41
- };
42
- const sanitized = this.sanitize ? this.sanitize(snapshot) : snapshot;
43
- await this.store.save(sanitized);
44
- }
45
- shouldSample() {
46
- if (this.sampleRate >= 1)
47
- return true;
48
- return Math.random() <= this.sampleRate;
49
- }
50
- }
51
- function structuredCloneSafe(value) {
52
- if (value == null)
53
- return value ?? undefined;
54
- try {
55
- const clone = globalThis.structuredClone;
56
- if (typeof clone === "function") {
57
- return clone(value);
58
- }
59
- return JSON.parse(JSON.stringify(value));
60
- } catch {
61
- return;
62
- }
63
- }
64
- // src/generator/golden-test-generator.ts
65
- import { randomUUID as randomUUID2 } from "node:crypto";
66
- import { performance } from "node:perf_hooks";
67
-
68
1
  // src/generator/assertion-builder.ts
69
2
  function buildAssertions(testCase, ctx) {
70
3
  if (testCase.success) {
@@ -86,60 +19,60 @@ function serialize(value) {
86
19
  }, 2);
87
20
  }
88
21
 
89
- // src/adapters/vitest-adapter.ts
90
- function generateVitestSuite(options) {
22
+ // src/adapters/jest-adapter.ts
23
+ function generateJestSuite(options) {
91
24
  const caseBlocks = options.cases.map((testCase) => {
92
25
  const inputConst = serialize(testCase.input);
93
26
  const metadataConst = serialize(testCase.metadata ?? {});
94
- const assertions = testCase.success ? [
95
- `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});`,
96
- `expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`
97
- ] : [
98
- `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`
99
- ];
27
+ const successBlock = `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});
28
+ expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`;
29
+ const failureBlock = `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`;
100
30
  return `
101
- it('${testCase.name}', async () => {
31
+ test('${testCase.name}', async () => {
102
32
  const input${testCase.id} = ${inputConst};
103
33
  const metadata${testCase.id} = ${metadataConst};
104
- ${assertions.join(`
105
- `)}
34
+ ${testCase.success ? successBlock : failureBlock}
106
35
  });`;
107
36
  }).join(`
108
37
  `);
109
38
  return `
110
- import { describe, it, expect } from 'bun:test';
111
39
  import { ${options.runnerFunction} } from '${options.runnerImport}';
112
40
 
113
41
  describe('${options.suiteName}', () => {${caseBlocks}
114
42
  });
115
43
  `.trim();
116
44
  }
117
-
118
- // src/adapters/jest-adapter.ts
119
- function generateJestSuite(options) {
45
+ // src/adapters/vitest-adapter.ts
46
+ function generateVitestSuite(options) {
120
47
  const caseBlocks = options.cases.map((testCase) => {
121
48
  const inputConst = serialize(testCase.input);
122
49
  const metadataConst = serialize(testCase.metadata ?? {});
123
- const successBlock = `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});
124
- expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`;
125
- const failureBlock = `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`;
50
+ const assertions = testCase.success ? [
51
+ `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});`,
52
+ `expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`
53
+ ] : [
54
+ `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`
55
+ ];
126
56
  return `
127
- test('${testCase.name}', async () => {
57
+ it('${testCase.name}', async () => {
128
58
  const input${testCase.id} = ${inputConst};
129
59
  const metadata${testCase.id} = ${metadataConst};
130
- ${testCase.success ? successBlock : failureBlock}
60
+ ${assertions.join(`
61
+ `)}
131
62
  });`;
132
63
  }).join(`
133
64
  `);
134
65
  return `
66
+ import { describe, it, expect } from 'bun:test';
135
67
  import { ${options.runnerFunction} } from '${options.runnerImport}';
136
68
 
137
69
  describe('${options.suiteName}', () => {${caseBlocks}
138
70
  });
139
71
  `.trim();
140
72
  }
141
-
142
73
  // src/generator/golden-test-generator.ts
74
+ import { randomUUID } from "node:crypto";
75
+ import { performance } from "node:perf_hooks";
143
76
  class GoldenTestGenerator {
144
77
  serializeMetadata;
145
78
  constructor(serializeMetadata = (snapshot) => ({
@@ -151,7 +84,7 @@ class GoldenTestGenerator {
151
84
  }
152
85
  createCases(snapshots) {
153
86
  return snapshots.map((snapshot, index) => ({
154
- id: snapshot.id ?? randomUUID2(),
87
+ id: snapshot.id ?? randomUUID(),
155
88
  name: snapshot.success ? `case-${index + 1}-success` : `case-${index + 1}-failure`,
156
89
  input: snapshot.input,
157
90
  expectedOutput: snapshot.output,
@@ -211,6 +144,69 @@ async function runGoldenTests(cases, runner) {
211
144
  }
212
145
  return results;
213
146
  }
147
+ // src/recorder/traffic-recorder.ts
148
+ import { randomUUID as randomUUID2 } from "node:crypto";
149
+
150
+ class InMemoryTrafficStore {
151
+ items = [];
152
+ async save(snapshot) {
153
+ this.items.push(snapshot);
154
+ }
155
+ async list(operation) {
156
+ if (!operation)
157
+ return [...this.items];
158
+ return this.items.filter((item) => item.operation.name === operation);
159
+ }
160
+ }
161
+
162
+ class TrafficRecorder {
163
+ store;
164
+ sampleRate;
165
+ sanitize;
166
+ constructor(options) {
167
+ this.store = options.store;
168
+ this.sampleRate = options.sampleRate ?? 1;
169
+ this.sanitize = options.sanitize;
170
+ }
171
+ async record(input) {
172
+ if (!this.shouldSample())
173
+ return;
174
+ const snapshot = {
175
+ id: randomUUID2(),
176
+ operation: input.operation,
177
+ input: structuredCloneSafe(input.input),
178
+ output: structuredCloneSafe(input.output),
179
+ error: input.error ? structuredCloneSafe(input.error) : undefined,
180
+ success: input.success,
181
+ timestamp: new Date,
182
+ durationMs: input.durationMs,
183
+ tenantId: input.tenantId,
184
+ userId: input.userId,
185
+ channel: input.channel,
186
+ metadata: input.metadata
187
+ };
188
+ const sanitized = this.sanitize ? this.sanitize(snapshot) : snapshot;
189
+ await this.store.save(sanitized);
190
+ }
191
+ shouldSample() {
192
+ if (this.sampleRate >= 1)
193
+ return true;
194
+ return Math.random() <= this.sampleRate;
195
+ }
196
+ }
197
+ function structuredCloneSafe(value) {
198
+ if (value == null)
199
+ return value ?? undefined;
200
+ try {
201
+ const clone = globalThis.structuredClone;
202
+ if (typeof clone === "function") {
203
+ return clone(value);
204
+ }
205
+ return JSON.parse(JSON.stringify(value));
206
+ } catch {
207
+ return;
208
+ }
209
+ }
214
210
  export {
215
211
  serialize,
216
212
  runGoldenTests,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/lib.testing",
3
- "version": "3.7.5",
3
+ "version": "3.7.7",
4
4
  "description": "Contract-aware testing utilities and runners",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -23,20 +23,20 @@
23
23
  "dev": "contractspec-bun-build dev",
24
24
  "clean": "rimraf dist .turbo",
25
25
  "lint": "bun lint:fix",
26
- "lint:fix": "eslint src --fix",
27
- "lint:check": "eslint src",
26
+ "lint:fix": "biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .",
27
+ "lint:check": "biome check .",
28
28
  "test": "bun test",
29
29
  "prebuild": "contractspec-bun-build prebuild",
30
30
  "typecheck": "tsc --noEmit"
31
31
  },
32
32
  "dependencies": {
33
- "@contractspec/lib.schema": "3.7.5",
34
- "@contractspec/lib.contracts-spec": "3.7.5"
33
+ "@contractspec/lib.schema": "3.7.6",
34
+ "@contractspec/lib.contracts-spec": "4.0.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@contractspec/tool.typescript": "3.7.5",
37
+ "@contractspec/tool.typescript": "3.7.6",
38
38
  "typescript": "^5.9.3",
39
- "@contractspec/tool.bun": "3.7.5"
39
+ "@contractspec/tool.bun": "3.7.6"
40
40
  },
41
41
  "exports": {
42
42
  ".": {