@livestore/utils-dev 0.4.0 → 0.5.0-dev.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/dist/.tsbuildinfo +1 -1
- package/dist/node/DockerCompose/DockerCompose.d.ts +52 -0
- package/dist/node/DockerCompose/DockerCompose.d.ts.map +1 -0
- package/dist/node/DockerCompose/DockerCompose.js +158 -0
- package/dist/node/DockerCompose/DockerCompose.js.map +1 -0
- package/dist/node/DockerCompose/DockerCompose.test.d.ts +2 -0
- package/dist/node/DockerCompose/DockerCompose.test.d.ts.map +1 -0
- package/dist/node/{DockerComposeService/DockerComposeService.test.js → DockerCompose/DockerCompose.test.js} +16 -16
- package/dist/node/DockerCompose/DockerCompose.test.js.map +1 -0
- package/dist/node/DockerCompose/mod.d.ts +2 -0
- package/dist/node/DockerCompose/mod.d.ts.map +1 -0
- package/dist/node/DockerCompose/mod.js +2 -0
- package/dist/node/DockerCompose/mod.js.map +1 -0
- package/dist/node/FileLogger.d.ts +15 -8
- package/dist/node/FileLogger.d.ts.map +1 -1
- package/dist/node/FileLogger.js +63 -79
- package/dist/node/FileLogger.js.map +1 -1
- package/dist/node/cmd-log.d.ts.map +1 -1
- package/dist/node/cmd-log.js +19 -8
- package/dist/node/cmd-log.js.map +1 -1
- package/dist/node/cmd.d.ts +10 -12
- package/dist/node/cmd.d.ts.map +1 -1
- package/dist/node/cmd.js +73 -36
- package/dist/node/cmd.js.map +1 -1
- package/dist/node/cmd.test.js +17 -17
- package/dist/node/cmd.test.js.map +1 -1
- package/dist/node/mod.d.ts +4 -5
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +58 -55
- package/dist/node/mod.js.map +1 -1
- package/dist/node/workspace.d.ts +2 -2
- package/dist/node/workspace.d.ts.map +1 -1
- package/dist/node/workspace.js +7 -7
- package/dist/node/workspace.js.map +1 -1
- package/dist/node-vitest/Vitest.d.ts +17 -8
- package/dist/node-vitest/Vitest.d.ts.map +1 -1
- package/dist/node-vitest/Vitest.js +5 -5
- package/dist/node-vitest/Vitest.js.map +1 -1
- package/dist/node-vitest/Vitest.test.d.ts +3 -5
- package/dist/node-vitest/Vitest.test.d.ts.map +1 -1
- package/dist/node-vitest/Vitest.test.js +14 -3
- package/dist/node-vitest/Vitest.test.js.map +1 -1
- package/dist/wrangler/WranglerDevServer.d.ts +15 -20
- package/dist/wrangler/WranglerDevServer.d.ts.map +1 -1
- package/dist/wrangler/WranglerDevServer.js +77 -71
- package/dist/wrangler/WranglerDevServer.js.map +1 -1
- package/dist/wrangler/WranglerDevServer.test.js +28 -28
- package/dist/wrangler/WranglerDevServer.test.js.map +1 -1
- package/dist/wrangler/mod.d.ts +1 -1
- package/dist/wrangler/mod.d.ts.map +1 -1
- package/dist/wrangler/mod.js +1 -1
- package/dist/wrangler/mod.js.map +1 -1
- package/package.json +19 -37
- package/src/node/{DockerComposeService/DockerComposeService.test.ts → DockerCompose/DockerCompose.test.ts} +18 -18
- package/src/node/DockerCompose/DockerCompose.ts +328 -0
- package/src/node/DockerCompose/mod.ts +1 -0
- package/src/node/FileLogger.ts +89 -114
- package/src/node/cmd-log.ts +21 -10
- package/src/node/cmd.test.ts +19 -17
- package/src/node/cmd.ts +117 -86
- package/src/node/mod.ts +112 -104
- package/src/node/workspace.ts +9 -10
- package/src/node-vitest/Vitest.test.ts +22 -3
- package/src/node-vitest/Vitest.ts +32 -42
- package/src/wrangler/WranglerDevServer.test.ts +33 -37
- package/src/wrangler/WranglerDevServer.ts +145 -129
- package/src/wrangler/mod.ts +1 -6
- package/dist/node/DockerComposeService/DockerComposeService.d.ts +0 -63
- package/dist/node/DockerComposeService/DockerComposeService.d.ts.map +0 -1
- package/dist/node/DockerComposeService/DockerComposeService.js +0 -149
- package/dist/node/DockerComposeService/DockerComposeService.js.map +0 -1
- package/dist/node/DockerComposeService/DockerComposeService.test.d.ts +0 -2
- package/dist/node/DockerComposeService/DockerComposeService.test.d.ts.map +0 -1
- package/dist/node/DockerComposeService/DockerComposeService.test.js.map +0 -1
- package/src/node/DockerComposeService/DockerComposeService.ts +0 -343
- /package/src/node/{DockerComposeService → DockerCompose}/test-fixtures/docker-compose.yml +0 -0
package/src/node/mod.ts
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks'
|
|
2
2
|
|
|
3
|
-
import * as OtelNodeSdk from '@effect/opentelemetry/NodeSdk'
|
|
4
3
|
import * as otel from '@opentelemetry/api'
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
|
|
5
|
+
import { IS_BUN } from '@livestore/utils'
|
|
6
|
+
import {
|
|
7
|
+
type Tracer,
|
|
8
|
+
Config,
|
|
9
|
+
Context,
|
|
10
|
+
Effect,
|
|
11
|
+
Exit,
|
|
12
|
+
FetchHttpClient,
|
|
13
|
+
Layer,
|
|
14
|
+
Option,
|
|
15
|
+
OtelTracer,
|
|
16
|
+
Otlp,
|
|
17
|
+
Schema,
|
|
18
|
+
} from '@livestore/utils/effect'
|
|
13
19
|
import { OtelLiveDummy } from '@livestore/utils/node'
|
|
14
20
|
|
|
15
21
|
export { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
|
|
16
22
|
export { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
|
17
23
|
export * from './cmd.ts'
|
|
18
|
-
export
|
|
19
|
-
type DockerComposeArgs,
|
|
20
|
-
DockerComposeError,
|
|
21
|
-
type DockerComposeOperations,
|
|
22
|
-
DockerComposeService,
|
|
23
|
-
type LogsOptions,
|
|
24
|
-
type StartOptions,
|
|
25
|
-
startDockerComposeServicesScoped,
|
|
26
|
-
} from './DockerComposeService/DockerComposeService.ts'
|
|
24
|
+
export * from './DockerCompose/mod.ts'
|
|
27
25
|
export * as FileLogger from './FileLogger.ts'
|
|
28
26
|
export * from './workspace.ts'
|
|
29
27
|
|
|
@@ -41,62 +39,45 @@ export const OtelLiveHttp = ({
|
|
|
41
39
|
rootSpanAttributes?: Record<string, unknown>
|
|
42
40
|
skipLogUrl?: boolean
|
|
43
41
|
traceNodeBootstrap?: boolean
|
|
44
|
-
} = {}): Layer.Layer<OtelTracer.OtelTracer | Tracer.ParentSpan> =>
|
|
42
|
+
} = {}): Layer.Layer<OtelTracer.OtelTracer | Tracer.Tracer | Tracer.ParentSpan> =>
|
|
45
43
|
Effect.gen(function* () {
|
|
46
44
|
const configRes = yield* Config.all({
|
|
47
|
-
exporterUrl: Config.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
exporterUrl: Config.nonEmptyString('OTEL_EXPORTER_OTLP_ENDPOINT'),
|
|
46
|
+
serviceName:
|
|
47
|
+
serviceName !== undefined
|
|
48
|
+
? Config.succeed(serviceName)
|
|
49
|
+
: Config.string('OTEL_SERVICE_NAME').pipe(Config.withDefault('livestore-utils-dev')),
|
|
50
|
+
rootSpanName:
|
|
51
|
+
rootSpanName !== undefined
|
|
52
|
+
? Config.succeed(rootSpanName)
|
|
53
|
+
: Config.string('OTEL_ROOT_SPAN_NAME').pipe(Config.withDefault('RootSpan')),
|
|
56
54
|
}).pipe(Effect.option)
|
|
57
55
|
|
|
58
56
|
if (configRes._tag === 'None') {
|
|
59
57
|
const RootSpanLive = Layer.span('DummyRoot', {})
|
|
60
|
-
return RootSpanLive.pipe(Layer.provideMerge(OtelLiveDummy))
|
|
58
|
+
return withTracerReference(RootSpanLive.pipe(Layer.provideMerge(OtelLiveDummy)))
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
const config = configRes.value
|
|
64
62
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
const OtelLive = OtelNodeSdk.layer(() => ({
|
|
73
|
-
resource,
|
|
74
|
-
metricReader,
|
|
75
|
-
spanProcessor: new BatchSpanProcessor(
|
|
76
|
-
new OTLPTraceExporter({ url: `${config.exporterUrl}/v1/traces`, headers: {} }),
|
|
77
|
-
{ scheduledDelayMillis: 50 },
|
|
78
|
-
),
|
|
79
|
-
})).pipe(
|
|
80
|
-
// If an OpenTelemetry backend is not available, the `OtelNodeSdk` layer
|
|
81
|
-
// will ignore the error when attempting to connect and emit a debug log
|
|
82
|
-
// stating the reason for the error (in this case `ECONNREFUSED`). This
|
|
83
|
-
// can cause problems for programs which rely on clean `stdout` (e.g.
|
|
84
|
-
// command-line applications). To remedy this, the below code sets the
|
|
85
|
-
// minimum log level `FiberRef` to `"None"` for the duration of the
|
|
86
|
-
// `OtelNodeSdk`'s layer constructor.
|
|
87
|
-
//
|
|
88
|
-
// This can likely be removed when Livestore is migrated to the Effect
|
|
89
|
-
// native Otlp exporters.
|
|
90
|
-
Layer.locally(FiberRef.currentMinimumLogLevel, LogLevel.None),
|
|
91
|
-
)
|
|
63
|
+
const OtelLive = Otlp.layerJson({
|
|
64
|
+
baseUrl: config.exporterUrl,
|
|
65
|
+
metricsExportInterval: 1000,
|
|
66
|
+
tracerExportInterval: 50,
|
|
67
|
+
resource: { serviceName: config.serviceName },
|
|
68
|
+
}).pipe(Layer.provide(FetchHttpClient.layer))
|
|
69
|
+
const OtelTracerLive = Layer.succeed(OtelTracer.OtelTracer, otel.trace.getTracer(config.serviceName))
|
|
92
70
|
|
|
93
71
|
const RootSpanLive = Layer.span(config.rootSpanName, {
|
|
94
72
|
attributes: { config, ...rootSpanAttributes },
|
|
95
|
-
onEnd: skipLogUrl === true ? undefined : (span
|
|
73
|
+
onEnd: skipLogUrl === true ? undefined : (span) => logTraceUiUrlForTraceId()(span.traceId),
|
|
96
74
|
parent: parentSpan,
|
|
97
75
|
})
|
|
98
76
|
|
|
99
|
-
const
|
|
77
|
+
const baseLayer = RootSpanLive.pipe(Layer.provideMerge(Layer.mergeAll(OtelLive, OtelTracerLive)))
|
|
78
|
+
|
|
79
|
+
const memoMap = yield* Layer.makeMemoMap
|
|
80
|
+
const layer = Layer.fromBuild((_, scope) => Layer.buildWithMemoMap(baseLayer, memoMap, scope))
|
|
100
81
|
|
|
101
82
|
if (traceNodeBootstrap === true && IS_BUN === false) {
|
|
102
83
|
/**
|
|
@@ -104,35 +85,48 @@ export const OtelLiveHttp = ({
|
|
|
104
85
|
* Note: Skipped in Bun since performance.nodeTiming is not properly supported.
|
|
105
86
|
*/
|
|
106
87
|
yield* Effect.gen(function* () {
|
|
107
|
-
const tracer = yield*
|
|
108
|
-
const currentSpan = yield*
|
|
88
|
+
const tracer = yield* Effect.tracer
|
|
89
|
+
const currentSpan = yield* Effect.currentSpan
|
|
109
90
|
|
|
110
91
|
const { nodeTiming, endAbs, durationAttr } = computeBootstrapTiming()
|
|
111
92
|
|
|
112
|
-
const bootSpan = tracer.
|
|
113
|
-
'node-bootstrap',
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
93
|
+
const bootSpan = tracer.span({
|
|
94
|
+
name: 'node-bootstrap',
|
|
95
|
+
parent: Option.some(currentSpan),
|
|
96
|
+
annotations: Context.empty(),
|
|
97
|
+
links: [],
|
|
98
|
+
startTime: millisToNanos(
|
|
99
|
+
IS_BUN === true && typeof nodeTiming.nodeStart === 'number'
|
|
100
|
+
? nodeTiming.nodeStart
|
|
101
|
+
: performance.timeOrigin + nodeTiming.nodeStart,
|
|
102
|
+
),
|
|
103
|
+
kind: 'internal',
|
|
104
|
+
root: false,
|
|
105
|
+
sampled: true,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
for (const [key, value] of Object.entries({
|
|
109
|
+
'node.timing.nodeStart': nodeTiming.nodeStart,
|
|
110
|
+
'node.timing.environment': nodeTiming.environment,
|
|
111
|
+
'node.timing.bootstrapComplete': nodeTiming.bootstrapComplete,
|
|
112
|
+
'node.timing.loopStart': nodeTiming.loopStart,
|
|
113
|
+
'node.timing.duration': durationAttr,
|
|
114
|
+
})) {
|
|
115
|
+
if (value !== undefined) bootSpan.attribute(key, value)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
bootSpan.end(millisToNanos(endAbs), Exit.void)
|
|
128
119
|
}).pipe(Effect.provide(layer), Effect.orDie)
|
|
129
120
|
}
|
|
130
121
|
|
|
131
|
-
return layer
|
|
132
|
-
}).pipe(Layer.
|
|
122
|
+
return withTracerReference(layer)
|
|
123
|
+
}).pipe(Layer.unwrap)
|
|
133
124
|
|
|
134
125
|
export const logTraceUiUrlForSpan = (printMsg?: (url: string) => string) => (span: otel.Span) =>
|
|
135
|
-
|
|
126
|
+
logTraceUiUrlForTraceId(printMsg)(span.spanContext().traceId)
|
|
127
|
+
|
|
128
|
+
const logTraceUiUrlForTraceId = (printMsg?: (url: string) => string) => (traceId: string) =>
|
|
129
|
+
getTracingBackendUrl(traceId).pipe(
|
|
136
130
|
Effect.tap((url) => {
|
|
137
131
|
if (url === undefined) {
|
|
138
132
|
return Effect.logWarning('No tracing backend url found')
|
|
@@ -146,21 +140,21 @@ export const logTraceUiUrlForSpan = (printMsg?: (url: string) => string) => (spa
|
|
|
146
140
|
}),
|
|
147
141
|
)
|
|
148
142
|
|
|
149
|
-
export const getTracingBackendUrl = (
|
|
143
|
+
export const getTracingBackendUrl = (traceIdOrSpan: string | otel.Span) =>
|
|
150
144
|
Effect.gen(function* () {
|
|
151
|
-
const endpoint = yield* Config.string('GRAFANA_ENDPOINT').pipe(Config.option
|
|
145
|
+
const endpoint = yield* Config.string('GRAFANA_ENDPOINT').pipe(Config.option)
|
|
152
146
|
if (endpoint._tag === 'None') return
|
|
153
147
|
|
|
154
|
-
const traceId =
|
|
148
|
+
const traceId = typeof traceIdOrSpan === 'string' ? traceIdOrSpan : traceIdOrSpan.spanContext().traceId
|
|
155
149
|
|
|
156
150
|
// Grafana + Tempo
|
|
157
151
|
|
|
158
152
|
const grafanaEndpoint = endpoint.value
|
|
159
|
-
const left = yield* Schema.
|
|
153
|
+
const left = yield* Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown))({
|
|
160
154
|
datasource: 'tempo',
|
|
161
155
|
queries: [{ query: traceId, queryType: 'traceql', refId: 'A' }],
|
|
162
156
|
range: { from: 'now-1h', to: 'now' },
|
|
163
|
-
})
|
|
157
|
+
})
|
|
164
158
|
const searchParams = new URLSearchParams({
|
|
165
159
|
orgId: '1',
|
|
166
160
|
left,
|
|
@@ -168,7 +162,7 @@ export const getTracingBackendUrl = (span: otel.Span) =>
|
|
|
168
162
|
|
|
169
163
|
// TODO make dynamic via env var
|
|
170
164
|
return `${grafanaEndpoint}/explore?${searchParams.toString()}`
|
|
171
|
-
})
|
|
165
|
+
}).pipe(Effect.orDie)
|
|
172
166
|
|
|
173
167
|
/**
|
|
174
168
|
* Compute absolute start/end timestamps for the Node.js bootstrap span in a
|
|
@@ -190,29 +184,43 @@ const computeBootstrapTiming = () => {
|
|
|
190
184
|
const nodeTiming = performance.nodeTiming
|
|
191
185
|
|
|
192
186
|
// Absolute start time in ms since epoch.
|
|
193
|
-
const startAbs =
|
|
194
|
-
|
|
195
|
-
? nodeTiming.nodeStart
|
|
196
|
-
|
|
197
|
-
|
|
187
|
+
const startAbs =
|
|
188
|
+
IS_BUN === true
|
|
189
|
+
? typeof nodeTiming.nodeStart === 'number'
|
|
190
|
+
? nodeTiming.nodeStart
|
|
191
|
+
: performance.timeOrigin
|
|
192
|
+
: performance.timeOrigin + nodeTiming.nodeStart
|
|
198
193
|
|
|
199
194
|
// Absolute end time.
|
|
200
|
-
const endAbs =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
195
|
+
const endAbs =
|
|
196
|
+
IS_BUN === true
|
|
197
|
+
? (() => {
|
|
198
|
+
const { loopStart, bootstrapComplete } = nodeTiming
|
|
199
|
+
if (typeof loopStart === 'number' && loopStart > 0) return startAbs + loopStart
|
|
200
|
+
if (typeof bootstrapComplete === 'number' && bootstrapComplete >= startAbs) return bootstrapComplete
|
|
201
|
+
return startAbs + 1
|
|
202
|
+
})()
|
|
203
|
+
: startAbs + nodeTiming.duration
|
|
208
204
|
|
|
209
205
|
// Duration attribute value for the span.
|
|
210
|
-
const durationAttr =
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
206
|
+
const durationAttr =
|
|
207
|
+
IS_BUN === true
|
|
208
|
+
? (() => {
|
|
209
|
+
const { loopStart } = nodeTiming
|
|
210
|
+
return typeof loopStart === 'number' && loopStart > 0 ? loopStart : 0
|
|
211
|
+
})()
|
|
212
|
+
: nodeTiming.duration
|
|
216
213
|
|
|
217
214
|
return { nodeTiming, startAbs, endAbs, durationAttr } as const
|
|
218
215
|
}
|
|
216
|
+
|
|
217
|
+
const millisToNanos = (millis: number): bigint => BigInt(Math.round(millis * 1_000_000))
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Effect v4 installs `Tracer.Tracer` through a `Context.Reference` fiber ref.
|
|
221
|
+
* The OTEL layers set that reference at runtime, but their layer output type is
|
|
222
|
+
* `never`, so we localize the public compatibility assertion here.
|
|
223
|
+
*/
|
|
224
|
+
const withTracerReference = <ROut, E, RIn>(
|
|
225
|
+
layer: Layer.Layer<ROut, E, RIn>,
|
|
226
|
+
): Layer.Layer<ROut | Tracer.Tracer, E, RIn> => layer as Layer.Layer<ROut | Tracer.Tracer, E, RIn>
|
package/src/node/workspace.ts
CHANGED
|
@@ -6,33 +6,32 @@ import { Context, Effect, Layer } from '@livestore/utils/effect'
|
|
|
6
6
|
export type WorkspaceInfo = string
|
|
7
7
|
|
|
8
8
|
/** Current working directory. */
|
|
9
|
-
export class CurrentWorkingDirectory extends Context.
|
|
10
|
-
CurrentWorkingDirectory,
|
|
11
|
-
|
|
12
|
-
>() {
|
|
9
|
+
export class CurrentWorkingDirectory extends Context.Service<CurrentWorkingDirectory, WorkspaceInfo>()(
|
|
10
|
+
'CurrentWorkingDirectory',
|
|
11
|
+
) {
|
|
13
12
|
/** Layer that captures the process cwd once. */
|
|
14
13
|
static live = Layer.effect(
|
|
15
14
|
CurrentWorkingDirectory,
|
|
16
|
-
Effect.sync(() => process.cwd()),
|
|
15
|
+
Effect.sync(() => CurrentWorkingDirectory.of(process.cwd())),
|
|
17
16
|
)
|
|
18
17
|
|
|
19
18
|
/** Override CWD for tests or nested invocations. */
|
|
20
|
-
static fromPath = (cwd: string) => Layer.succeed(CurrentWorkingDirectory, cwd)
|
|
19
|
+
static fromPath = (cwd: string) => Layer.succeed(CurrentWorkingDirectory, CurrentWorkingDirectory.of(cwd))
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
/** Livestore workspace root (env required). */
|
|
24
|
-
export class LivestoreWorkspace extends Context.
|
|
23
|
+
export class LivestoreWorkspace extends Context.Service<LivestoreWorkspace, WorkspaceInfo>()('LivestoreWorkspace') {
|
|
25
24
|
/** Resolve from WORKSPACE_ROOT env. */
|
|
26
25
|
static live = Layer.effect(
|
|
27
26
|
LivestoreWorkspace,
|
|
28
27
|
Effect.sync(() => {
|
|
29
28
|
const root = process.env.WORKSPACE_ROOT ?? shouldNeverHappen('WORKSPACE_ROOT is not set')
|
|
30
|
-
return root
|
|
29
|
+
return LivestoreWorkspace.of(root)
|
|
31
30
|
}),
|
|
32
31
|
)
|
|
33
32
|
|
|
34
33
|
/** Provide a fixed Livestore root. */
|
|
35
|
-
static fromPath = (root: string) => Layer.succeed(LivestoreWorkspace, root)
|
|
34
|
+
static fromPath = (root: string) => Layer.succeed(LivestoreWorkspace, LivestoreWorkspace.of(root))
|
|
36
35
|
|
|
37
36
|
/** Derive a CurrentWorkingDirectory layer from the Livestore workspace root (with optional subpath) */
|
|
38
37
|
static toCwd = (/** Relative path to the Livestore workspace root */ subPath?: string) =>
|
|
@@ -40,7 +39,7 @@ export class LivestoreWorkspace extends Context.Tag('LivestoreWorkspace')<Livest
|
|
|
40
39
|
CurrentWorkingDirectory,
|
|
41
40
|
Effect.gen(function* () {
|
|
42
41
|
const root = yield* LivestoreWorkspace
|
|
43
|
-
return path.join(root, subPath ?? '')
|
|
42
|
+
return CurrentWorkingDirectory.of(path.join(root, subPath ?? ''))
|
|
44
43
|
}),
|
|
45
44
|
)
|
|
46
45
|
}
|
|
@@ -14,7 +14,7 @@ Vitest.describe('Vitest.asProp', () => {
|
|
|
14
14
|
|
|
15
15
|
// Always-passing test - should only show initial phase
|
|
16
16
|
Vitest.asProp(
|
|
17
|
-
Vitest.
|
|
17
|
+
Vitest.live,
|
|
18
18
|
'always-pass test (shows only initial runs)',
|
|
19
19
|
[IntArbitrary],
|
|
20
20
|
(properties, _ctx, enhanced) =>
|
|
@@ -38,10 +38,29 @@ Vitest.describe('Vitest.asProp', () => {
|
|
|
38
38
|
{ fastCheck: { numRuns: 4 } },
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
+
Vitest.asProp(
|
|
42
|
+
Vitest.live,
|
|
43
|
+
'converts record-form schemas to arbitraries',
|
|
44
|
+
{
|
|
45
|
+
count: Schema.Int,
|
|
46
|
+
payload: Schema.Struct({
|
|
47
|
+
label: Schema.String,
|
|
48
|
+
note: Schema.optional(Schema.String),
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
({ count, payload }) =>
|
|
52
|
+
Effect.sync(() => {
|
|
53
|
+
Vitest.expect(Number.isInteger(count)).toBe(true)
|
|
54
|
+
Vitest.expect(typeof payload.label).toBe('string')
|
|
55
|
+
Vitest.expect(payload.note === undefined || typeof payload.note === 'string').toBe(true)
|
|
56
|
+
}),
|
|
57
|
+
{ fastCheck: { numRuns: 10 } },
|
|
58
|
+
)
|
|
59
|
+
|
|
41
60
|
// Failing test - should show initial + shrinking phases
|
|
42
61
|
let alreadyFailed = false
|
|
43
62
|
Vitest.asProp(
|
|
44
|
-
Vitest.
|
|
63
|
+
Vitest.live,
|
|
45
64
|
'failing test (shows initial runs + shrinking)',
|
|
46
65
|
[IntArbitrary],
|
|
47
66
|
(properties, ctx, enhanced) =>
|
|
@@ -79,7 +98,7 @@ Vitest.describe('Vitest.asProp', () => {
|
|
|
79
98
|
|
|
80
99
|
// Test with endOnFailure: true - should not show shrinking
|
|
81
100
|
Vitest.asProp(
|
|
82
|
-
Vitest.
|
|
101
|
+
Vitest.live,
|
|
83
102
|
'failing test with endOnFailure (no shrinking)',
|
|
84
103
|
[IntArbitrary],
|
|
85
104
|
(properties, _ctx, enhanced) =>
|
|
@@ -23,24 +23,27 @@ export * from '@effect/vitest'
|
|
|
23
23
|
|
|
24
24
|
export const DEBUGGER_ACTIVE = Boolean(process.env.DEBUGGER_ACTIVE ?? inspector.url() !== undefined)
|
|
25
25
|
|
|
26
|
+
type WithoutTestCtxServices<R> = Exclude<Exclude<R, OtelTracer.OtelTracer>, Scope.Scope>
|
|
27
|
+
type WithoutLayerAndTestCtxServices<R, ROut> = Exclude<Exclude<R, ROut | OtelTracer.OtelTracer>, Scope.Scope>
|
|
28
|
+
|
|
26
29
|
export const makeWithTestCtx: <ROut = never, E1 = never, RIn = never>(
|
|
27
30
|
ctxParams: WithTestCtxParams<ROut, E1, RIn>,
|
|
28
31
|
) => (testContext: Vitest.TestContext) => <A, E, R>(
|
|
29
32
|
self: Effect.Effect<A, E, R>,
|
|
30
33
|
) => Effect.Effect<
|
|
31
34
|
A,
|
|
32
|
-
E | E1 | Cause.
|
|
35
|
+
E | E1 | Cause.TimeoutError,
|
|
33
36
|
// Exclude dependencies provided by `withTestCtx` from the layer dependencies
|
|
34
|
-
|
|
|
37
|
+
| WithoutTestCtxServices<RIn>
|
|
35
38
|
// Exclude dependencies provided by `withTestCtx` **and** dependencies produced
|
|
36
39
|
// by the layer from the effect dependencies
|
|
37
|
-
|
|
|
40
|
+
| WithoutLayerAndTestCtxServices<R, ROut>
|
|
38
41
|
> = (ctxParams) => (testContext: Vitest.TestContext) => withTestCtx(testContext, ctxParams)
|
|
39
42
|
|
|
40
43
|
export type WithTestCtxParams<ROut, E1, RIn> = {
|
|
41
44
|
suffix?: string
|
|
42
45
|
makeLayer?: (testContext: Vitest.TestContext) => Layer.Layer<ROut, E1, RIn | Scope.Scope>
|
|
43
|
-
timeout?: Duration.
|
|
46
|
+
timeout?: Duration.Input
|
|
44
47
|
forceOtel?: boolean
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -54,8 +57,8 @@ export const withTestCtx =
|
|
|
54
57
|
forceOtel = false,
|
|
55
58
|
}: {
|
|
56
59
|
suffix?: string
|
|
57
|
-
makeLayer?: (testContext: Vitest.TestContext) => Layer.Layer<ROut, E1, RIn>
|
|
58
|
-
timeout?: Duration.
|
|
60
|
+
makeLayer?: (testContext: Vitest.TestContext) => Layer.Layer<ROut, E1, RIn | Scope.Scope>
|
|
61
|
+
timeout?: Duration.Input
|
|
59
62
|
forceOtel?: boolean
|
|
60
63
|
} = {},
|
|
61
64
|
) =>
|
|
@@ -63,15 +66,19 @@ export const withTestCtx =
|
|
|
63
66
|
self: Effect.Effect<A, E, R>,
|
|
64
67
|
): Effect.Effect<
|
|
65
68
|
A,
|
|
66
|
-
E | E1 | Cause.
|
|
69
|
+
E | E1 | Cause.TimeoutError,
|
|
67
70
|
// Exclude dependencies provided internally from the provided layer's dependencies
|
|
68
|
-
|
|
|
71
|
+
| WithoutTestCtxServices<RIn>
|
|
69
72
|
// Exclude dependencies provided internally **and** dependencies produced by the
|
|
70
73
|
// provided layer from the effect dependencies
|
|
71
|
-
|
|
|
74
|
+
| WithoutLayerAndTestCtxServices<R, ROut>
|
|
72
75
|
> => {
|
|
73
76
|
const spanName = `${testContext.task.suite?.name}:${testContext.task.name}${suffix !== undefined ? `:${suffix}` : ''}`
|
|
74
|
-
|
|
77
|
+
// `Layer.empty` provides `never`, which is narrower than an arbitrary generic
|
|
78
|
+
// `ROut`; widen it for the no-layer branch so `Effect.provide` can typecheck.
|
|
79
|
+
const layer: Layer.Layer<ROut, E1, RIn | Scope.Scope> =
|
|
80
|
+
makeLayer?.(testContext) ?? (Layer.empty as unknown as Layer.Layer<ROut, E1, RIn | Scope.Scope>)
|
|
81
|
+
const timeoutDuration = Duration.fromInputUnsafe(timeout)
|
|
75
82
|
|
|
76
83
|
const otelLayer =
|
|
77
84
|
DEBUGGER_ACTIVE === true || forceOtel === true
|
|
@@ -84,14 +91,14 @@ export const withTestCtx =
|
|
|
84
91
|
DEBUGGER_ACTIVE === true
|
|
85
92
|
? identity
|
|
86
93
|
: Effect.logWarnIfTakesLongerThan({
|
|
87
|
-
duration: Duration.toMillis(
|
|
88
|
-
label: `${spanName} approaching timeout (timeout: ${Duration.format(
|
|
94
|
+
duration: Duration.toMillis(timeoutDuration) * 0.8,
|
|
95
|
+
label: `${spanName} approaching timeout (timeout: ${Duration.format(timeoutDuration)})`,
|
|
89
96
|
}),
|
|
90
97
|
DEBUGGER_ACTIVE === true ? identity : Effect.timeout(timeout),
|
|
91
98
|
Effect.provide(combinedLayer),
|
|
92
99
|
Effect.scoped, // We need to scope the effect manually here because otherwise the span is not closed
|
|
93
100
|
Effect.annotateLogs({ suffix }),
|
|
94
|
-
)
|
|
101
|
+
)
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
/**
|
|
@@ -121,19 +128,17 @@ export type EnhancedTestContext =
|
|
|
121
128
|
/**
|
|
122
129
|
* Normalizes propOptions to ensure @effect/vitest receives correct fastCheck structure
|
|
123
130
|
*/
|
|
131
|
+
type ArbitraryValues<Arbs extends Vitest.Vitest.Arbitraries> = {
|
|
132
|
+
[K in keyof Arbs]: Arbs[K] extends FC.Arbitrary<infer T> ? T : Arbs[K] extends Schema.Schema<infer T> ? T : never
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type PropOptions<Arbs extends Vitest.Vitest.Arbitraries> = Omit<Vitest.TestOptions, 'fastCheck'> & {
|
|
136
|
+
fastCheck?: FC.Parameters<ArbitraryValues<Arbs>>
|
|
137
|
+
}
|
|
138
|
+
|
|
124
139
|
const normalizePropOptions = <Arbs extends Vitest.Vitest.Arbitraries>(
|
|
125
|
-
propOptions:
|
|
126
|
-
|
|
127
|
-
| (Vitest.TestOptions & {
|
|
128
|
-
fastCheck?: FC.Parameters<{
|
|
129
|
-
[K in keyof Arbs]: Arbs[K] extends FC.Arbitrary<infer T> ? T : Schema.Schema.Type<Arbs[K]>
|
|
130
|
-
}>
|
|
131
|
-
}),
|
|
132
|
-
): Vitest.TestOptions & {
|
|
133
|
-
fastCheck?: FC.Parameters<{
|
|
134
|
-
[K in keyof Arbs]: Arbs[K] extends FC.Arbitrary<infer T> ? T : Schema.Schema.Type<Arbs[K]>
|
|
135
|
-
}>
|
|
136
|
-
} => {
|
|
140
|
+
propOptions: number | PropOptions<Arbs>,
|
|
141
|
+
): PropOptions<Arbs> => {
|
|
137
142
|
// If it's a number, treat as timeout and add our default fastCheck
|
|
138
143
|
if (Predicate.isObject(propOptions) === false) {
|
|
139
144
|
return {
|
|
@@ -180,23 +185,8 @@ export const asProp = <Arbs extends Vitest.Vitest.Arbitraries, A, E, R>(
|
|
|
180
185
|
api: Vitest.Vitest.Tester<R>,
|
|
181
186
|
name: string,
|
|
182
187
|
arbitraries: Arbs,
|
|
183
|
-
test: Vitest.Vitest.TestFunction<
|
|
184
|
-
|
|
185
|
-
E,
|
|
186
|
-
R,
|
|
187
|
-
[
|
|
188
|
-
{ [K in keyof Arbs]: Arbs[K] extends FC.Arbitrary<infer T> ? T : Schema.Schema.Type<Arbs[K]> },
|
|
189
|
-
Vitest.TestContext,
|
|
190
|
-
EnhancedTestContext,
|
|
191
|
-
]
|
|
192
|
-
>,
|
|
193
|
-
propOptions:
|
|
194
|
-
| number
|
|
195
|
-
| (Vitest.TestOptions & {
|
|
196
|
-
fastCheck?: FC.Parameters<{
|
|
197
|
-
[K in keyof Arbs]: Arbs[K] extends FC.Arbitrary<infer T> ? T : Schema.Schema.Type<Arbs[K]>
|
|
198
|
-
}>
|
|
199
|
-
}),
|
|
188
|
+
test: Vitest.Vitest.TestFunction<A, E, R, [ArbitraryValues<Arbs>, Vitest.TestContext, EnhancedTestContext]>,
|
|
189
|
+
propOptions: number | PropOptions<Arbs>,
|
|
200
190
|
) => {
|
|
201
191
|
const normalizedPropOptions = normalizePropOptions(propOptions)
|
|
202
192
|
const numRuns = normalizedPropOptions.fastCheck?.numRuns ?? 100
|