@livestore/utils-dev 0.4.0-dev.22 → 0.4.0-dev.24

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 (55) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/node/DockerComposeService/DockerComposeService.d.ts +8 -3
  3. package/dist/node/DockerComposeService/DockerComposeService.d.ts.map +1 -1
  4. package/dist/node/DockerComposeService/DockerComposeService.js +47 -42
  5. package/dist/node/DockerComposeService/DockerComposeService.js.map +1 -1
  6. package/dist/node/DockerComposeService/DockerComposeService.test.js +2 -2
  7. package/dist/node/DockerComposeService/DockerComposeService.test.js.map +1 -1
  8. package/dist/node/FileLogger.d.ts.map +1 -1
  9. package/dist/node/FileLogger.js +3 -3
  10. package/dist/node/FileLogger.js.map +1 -1
  11. package/dist/node/cmd-log.d.ts +2 -2
  12. package/dist/node/cmd-log.d.ts.map +1 -1
  13. package/dist/node/cmd-log.js +6 -6
  14. package/dist/node/cmd-log.js.map +1 -1
  15. package/dist/node/cmd.d.ts +1 -1
  16. package/dist/node/cmd.d.ts.map +1 -1
  17. package/dist/node/cmd.js +25 -27
  18. package/dist/node/cmd.js.map +1 -1
  19. package/dist/node/cmd.test.js +2 -1
  20. package/dist/node/cmd.test.js.map +1 -1
  21. package/dist/node/mod.d.ts +2 -2
  22. package/dist/node/mod.d.ts.map +1 -1
  23. package/dist/node/mod.js +17 -16
  24. package/dist/node/mod.js.map +1 -1
  25. package/dist/node/workspace.d.ts.map +1 -1
  26. package/dist/node/workspace.js.map +1 -1
  27. package/dist/node-vitest/Vitest.d.ts.map +1 -1
  28. package/dist/node-vitest/Vitest.js +11 -11
  29. package/dist/node-vitest/Vitest.js.map +1 -1
  30. package/dist/node-vitest/Vitest.test.d.ts +8 -0
  31. package/dist/node-vitest/Vitest.test.d.ts.map +1 -1
  32. package/dist/node-vitest/Vitest.test.js +11 -7
  33. package/dist/node-vitest/Vitest.test.js.map +1 -1
  34. package/dist/wrangler/WranglerDevServer.d.ts +2 -2
  35. package/dist/wrangler/WranglerDevServer.d.ts.map +1 -1
  36. package/dist/wrangler/WranglerDevServer.js +7 -7
  37. package/dist/wrangler/WranglerDevServer.js.map +1 -1
  38. package/dist/wrangler/WranglerDevServer.test.js +2 -2
  39. package/dist/wrangler/WranglerDevServer.test.js.map +1 -1
  40. package/package.json +68 -15
  41. package/src/node/DockerComposeService/DockerComposeService.test.ts +5 -2
  42. package/src/node/DockerComposeService/DockerComposeService.ts +105 -90
  43. package/src/node/DockerComposeService/test-fixtures/docker-compose.yml +1 -1
  44. package/src/node/FileLogger.ts +4 -3
  45. package/src/node/cmd-log.ts +52 -55
  46. package/src/node/cmd.test.ts +54 -50
  47. package/src/node/cmd.ts +54 -57
  48. package/src/node/mod.ts +20 -18
  49. package/src/node/workspace.ts +1 -0
  50. package/src/node-vitest/Vitest.test.ts +12 -7
  51. package/src/node-vitest/Vitest.ts +28 -24
  52. package/src/wrangler/WranglerDevServer.test.ts +4 -2
  53. package/src/wrangler/WranglerDevServer.ts +9 -8
  54. package/src/wrangler/fixtures/wrangler.toml +1 -1
  55. package/dist/.tsbuildinfo.json +0 -1
@@ -1,10 +1,10 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
-
4
3
  import { CommandExecutor, Duration, Effect, Layer } from '@livestore/utils/effect'
5
4
  import { PlatformNode } from '@livestore/utils/node'
6
5
  import { Vitest } from '@livestore/utils-dev/node-vitest'
7
6
  import { expect } from 'vitest'
7
+
8
8
  import { cmd } from './cmd.ts'
9
9
  import { CurrentWorkingDirectory } from './workspace.ts'
10
10
 
@@ -30,55 +30,59 @@ Vitest.describe('cmd helper', () => {
30
30
  }).pipe(withNode(test)),
31
31
  )
32
32
 
33
- Vitest.scopedLive('supports logging with archive + retention', (test) =>
34
- Effect.gen(function* () {
35
- const workspace = process.env.WORKSPACE_ROOT!
36
- const logsDir = path.join(workspace, 'tmp', 'cmd-tests', String(Date.now()))
37
-
38
- // first run
39
- const exit1 = yield* cmd('printf first', { logDir: logsDir })
40
- expect(exit1).toBe(CommandExecutor.ExitCode(0))
41
- const current = path.join(logsDir, 'dev.log')
42
- expect(fs.existsSync(current)).toBe(true)
43
- const firstLog = fs.readFileSync(current, 'utf8')
44
- const firstStdoutLines = firstLog.split('\n').filter((line) => line.includes('[stdout]'))
45
- expect(firstStdoutLines.length).toBeGreaterThan(0)
46
- for (const line of firstStdoutLines) {
47
- expect(line).toContain('[stdout] first')
48
- expect(line).toContain('INFO')
49
- expect(line).toContain('printf first')
50
- }
51
-
52
- // second run — archives previous
53
- const exit2 = yield* cmd('printf second', { logDir: logsDir })
54
- expect(exit2).toBe(CommandExecutor.ExitCode(0))
55
- const archiveDir = path.join(logsDir, 'archive')
56
- const archives = fs.readdirSync(archiveDir).filter((f) => f.endsWith('.log'))
57
- expect(archives.length).toBe(1)
58
- const archivedPath = path.join(archiveDir, archives[0]!)
59
- const archivedLog = fs.readFileSync(archivedPath, 'utf8')
60
- const archivedStdoutLines = archivedLog.split('\n').filter((line) => line.includes('[stdout]'))
61
- expect(archivedStdoutLines.length).toBeGreaterThan(0)
62
- for (const line of archivedStdoutLines) {
63
- expect(line).toContain('[stdout] first')
64
- }
65
-
66
- const secondLog = fs.readFileSync(current, 'utf8')
67
- const secondStdoutLines = secondLog.split('\n').filter((line) => line.includes('[stdout]'))
68
- expect(secondStdoutLines.length).toBeGreaterThan(0)
69
- for (const line of secondStdoutLines) {
70
- expect(line).toContain('[stdout] second')
71
- expect(line).toContain('INFO')
72
- }
73
-
74
- // generate many archives to exercise retention (keep 50)
75
- for (let i = 0; i < 60; i++) {
76
- // Use small unique payloads
77
- yield* cmd(['printf', String(i)], { logDir: logsDir })
78
- }
79
- const archivesAfter = fs.readdirSync(archiveDir).filter((f) => f.endsWith('.log'))
80
- expect(archivesAfter.length).toBeLessThanOrEqual(50)
81
- }).pipe(withNode(test)),
33
+ /** TODO(#1020): Investigate CI timeout flakiness for cmd tests that spawn many child processes */
34
+ Vitest.scopedLive(
35
+ 'supports logging with archive + retention',
36
+ (test) =>
37
+ Effect.gen(function* () {
38
+ const workspace = process.env.WORKSPACE_ROOT!
39
+ const logsDir = path.join(workspace, 'tmp', 'cmd-tests', String(Date.now()))
40
+
41
+ // first run
42
+ const exit1 = yield* cmd('printf first', { logDir: logsDir })
43
+ expect(exit1).toBe(CommandExecutor.ExitCode(0))
44
+ const current = path.join(logsDir, 'dev.log')
45
+ expect(fs.existsSync(current)).toBe(true)
46
+ const firstLog = fs.readFileSync(current, 'utf8')
47
+ const firstStdoutLines = firstLog.split('\n').filter((line) => line.includes('[stdout]'))
48
+ expect(firstStdoutLines.length).toBeGreaterThan(0)
49
+ for (const line of firstStdoutLines) {
50
+ expect(line).toContain('[stdout] first')
51
+ expect(line).toContain('INFO')
52
+ expect(line).toContain('printf first')
53
+ }
54
+
55
+ // second run archives previous
56
+ const exit2 = yield* cmd('printf second', { logDir: logsDir })
57
+ expect(exit2).toBe(CommandExecutor.ExitCode(0))
58
+ const archiveDir = path.join(logsDir, 'archive')
59
+ const archives = fs.readdirSync(archiveDir).filter((f) => f.endsWith('.log'))
60
+ expect(archives.length).toBe(1)
61
+ const archivedPath = path.join(archiveDir, archives[0]!)
62
+ const archivedLog = fs.readFileSync(archivedPath, 'utf8')
63
+ const archivedStdoutLines = archivedLog.split('\n').filter((line) => line.includes('[stdout]'))
64
+ expect(archivedStdoutLines.length).toBeGreaterThan(0)
65
+ for (const line of archivedStdoutLines) {
66
+ expect(line).toContain('[stdout] first')
67
+ }
68
+
69
+ const secondLog = fs.readFileSync(current, 'utf8')
70
+ const secondStdoutLines = secondLog.split('\n').filter((line) => line.includes('[stdout]'))
71
+ expect(secondStdoutLines.length).toBeGreaterThan(0)
72
+ for (const line of secondStdoutLines) {
73
+ expect(line).toContain('[stdout] second')
74
+ expect(line).toContain('INFO')
75
+ }
76
+
77
+ // generate many archives to exercise retention (keep 50)
78
+ for (let i = 0; i < 60; i++) {
79
+ // Use small unique payloads
80
+ yield* cmd(['printf', String(i)], { logDir: logsDir })
81
+ }
82
+ const archivesAfter = fs.readdirSync(archiveDir).filter((f) => f.endsWith('.log'))
83
+ expect(archivesAfter.length).toBeLessThanOrEqual(50)
84
+ }).pipe(withNode(test)),
85
+ { timeout: 30_000, retry: 3 },
82
86
  )
83
87
 
84
88
  Vitest.scopedLive('streams stdout and stderr with logger formatting', (test) =>
package/src/node/cmd.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  Schema,
18
18
  Stream,
19
19
  } from '@livestore/utils/effect'
20
+
20
21
  import { applyLoggingToCommand } from './cmd-log.ts'
21
22
  import * as FileLogger from './FileLogger.ts'
22
23
  import { CurrentWorkingDirectory } from './workspace.ts'
@@ -26,23 +27,21 @@ const SUCCESS_EXIT_CODE: CommandExecutor.ExitCode = 0 as CommandExecutor.ExitCod
26
27
 
27
28
  export const cmd: (
28
29
  commandInput: string | (string | undefined)[],
29
- options?:
30
- | {
31
- stderr?: 'inherit' | 'pipe'
32
- stdout?: 'inherit' | 'pipe'
33
- shell?: boolean
34
- env?: Record<string, string | undefined>
35
- /**
36
- * When provided, streams command output to terminal AND to a canonical log file (`${logDir}/dev.log`) in this directory.
37
- * Also archives the previous run to `${logDir}/archive/dev-<ISO>.log` and keeps only the latest 50 archives.
38
- */
39
- logDir?: string
40
- /** Optional basename for the canonical log file; defaults to 'dev.log' */
41
- logFileName?: string
42
- /** Optional number of archived logs to retain; defaults to 50 */
43
- logRetention?: number
44
- }
45
- | undefined,
30
+ options?: {
31
+ stderr?: 'inherit' | 'pipe'
32
+ stdout?: 'inherit' | 'pipe'
33
+ shell?: boolean
34
+ env?: Record<string, string | undefined>
35
+ /**
36
+ * When provided, streams command output to terminal AND to a canonical log file (`${logDir}/dev.log`) in this directory.
37
+ * Also archives the previous run to `${logDir}/archive/dev-<ISO>.log` and keeps only the latest 50 archives.
38
+ */
39
+ logDir?: string
40
+ /** Optional basename for the canonical log file; defaults to 'dev.log' */
41
+ logFileName?: string
42
+ /** Optional number of archived logs to retain; defaults to 50 */
43
+ logRetention?: number
44
+ },
46
45
  ) => Effect.Effect<
47
46
  CommandExecutor.ExitCode,
48
47
  PlatformError.PlatformError | CmdError,
@@ -51,27 +50,26 @@ export const cmd: (
51
50
  const cwd = yield* CurrentWorkingDirectory
52
51
 
53
52
  const asArray = Array.isArray(commandInput)
54
- const parts = asArray ? (commandInput as (string | undefined)[]).filter(isNotUndefined) : undefined
55
- const [command, ...args] = asArray ? (parts as string[]) : (commandInput as string).split(' ')
53
+ const parts = asArray === true ? commandInput.filter(isNotUndefined) : undefined
54
+ const [command, ...args] = asArray === true ? (parts as string[]) : commandInput.split(' ')
56
55
 
57
56
  const debugEnvStr = Object.entries(options?.env ?? {})
58
- .map(([key, value]) => `${key}='${value}' `)
57
+ .map(([key, value]) => `${key}='${String(value)}' `)
59
58
  .join('')
60
59
 
61
60
  const loggingOpts = {
62
- ...(options?.logDir ? { logDir: options.logDir } : {}),
63
- ...(options?.logFileName ? { logFileName: options.logFileName } : {}),
64
- ...(options?.logRetention ? { logRetention: options.logRetention } : {}),
61
+ ...(options?.logDir !== undefined ? { logDir: options.logDir } : {}),
62
+ ...(options?.logFileName !== undefined ? { logFileName: options.logFileName } : {}),
63
+ ...(options?.logRetention !== undefined ? { logRetention: options.logRetention } : {}),
65
64
  } as const
66
65
  const { input: finalInput, subshell: needsShell, logPath } = yield* applyLoggingToCommand(commandInput, loggingOpts)
67
66
 
68
67
  const stdoutMode = options?.stdout ?? 'inherit'
69
68
  const stderrMode = options?.stderr ?? 'inherit'
70
- const useShell = (options?.shell ? true : false) || needsShell
69
+ const useShell = (options?.shell === true ? true : false) || needsShell
71
70
 
72
- const commandDebugStr =
73
- debugEnvStr + (Array.isArray(finalInput) ? (finalInput as string[]).join(' ') : (finalInput as string))
74
- const subshellStr = useShell ? ' (in subshell)' : ''
71
+ const commandDebugStr = debugEnvStr + (Array.isArray(finalInput) === true ? finalInput.join(' ') : finalInput)
72
+ const subshellStr = useShell === true ? ' (in subshell)' : ''
75
73
 
76
74
  yield* Effect.logDebug(`Running '${commandDebugStr}' in '${cwd}'${subshellStr}`)
77
75
  yield* Effect.annotateCurrentSpan({
@@ -91,7 +89,7 @@ export const cmd: (
91
89
  useShell,
92
90
  } as const
93
91
 
94
- const exitCode = yield* isNotUndefined(logPath)
92
+ const exitCode = yield* isNotUndefined(logPath) === true
95
93
  ? Effect.gen(function* () {
96
94
  yield* Effect.sync(() => console.log(`Logging output to ${logPath}`))
97
95
  return yield* runWithLogging({ ...baseArgs, logPath, threadName: commandDebugStr })
@@ -99,15 +97,13 @@ export const cmd: (
99
97
  : runWithoutLogging(baseArgs)
100
98
 
101
99
  if (exitCode !== SUCCESS_EXIT_CODE) {
102
- return yield* Effect.fail(
103
- CmdError.make({
104
- command: command!,
105
- args,
106
- cwd,
107
- env: options?.env ?? {},
108
- stderr: stderrMode,
109
- }),
110
- )
100
+ return yield* CmdError.make({
101
+ command: command!,
102
+ args,
103
+ cwd,
104
+ env: options?.env ?? {},
105
+ stderr: stderrMode,
106
+ })
111
107
  }
112
108
 
113
109
  return exitCode
@@ -123,15 +119,14 @@ export const cmdText: (
123
119
  ) => Effect.Effect<string, PlatformError.PlatformError, CommandExecutor.CommandExecutor | CurrentWorkingDirectory> =
124
120
  Effect.fn('cmdText')(function* (commandInput, options) {
125
121
  const cwd = yield* CurrentWorkingDirectory
126
- const [command, ...args] = Array.isArray(commandInput)
127
- ? commandInput.filter(isNotUndefined)
128
- : commandInput.split(' ')
122
+ const [command, ...args] =
123
+ Array.isArray(commandInput) === true ? commandInput.filter(isNotUndefined) : commandInput.split(' ')
129
124
  const debugEnvStr = Object.entries(options?.env ?? {})
130
- .map(([key, value]) => `${key}='${value}' `)
125
+ .map(([key, value]) => `${key}='${String(value)}' `)
131
126
  .join('')
132
127
 
133
128
  const commandDebugStr = debugEnvStr + [command, ...args].join(' ')
134
- const subshellStr = options?.runInShell ? ' (in subshell)' : ''
129
+ const subshellStr = options?.runInShell === true ? ' (in subshell)' : ''
135
130
 
136
131
  yield* Effect.logDebug(`Running '${commandDebugStr}' in '${cwd}'${subshellStr}`)
137
132
  yield* Effect.annotateCurrentSpan({ 'span.label': commandDebugStr, command, cwd })
@@ -140,13 +135,13 @@ export const cmdText: (
140
135
  // inherit = Stream stderr to process.stderr, pipe = Stream stderr to process.stdout
141
136
  Command.stderr(options?.stderr ?? 'inherit'),
142
137
  Command.workingDirectory(cwd),
143
- options?.runInShell ? Command.runInShell(true) : identity,
138
+ options?.runInShell === true ? Command.runInShell(true) : identity,
144
139
  Command.env(options?.env ?? {}),
145
140
  Command.string,
146
141
  )
147
142
  })
148
143
 
149
- export class CmdError extends Schema.TaggedError<CmdError>()('CmdError', {
144
+ export class CmdError extends Schema.TaggedError<CmdError>('~@livestore/utils-dev/CmdError')('CmdError', {
150
145
  command: Schema.String,
151
146
  args: Schema.Array(Schema.String),
152
147
  cwd: Schema.String,
@@ -169,7 +164,7 @@ const runWithoutLogging = ({ commandInput, cwd, env, stdoutMode, stderrMode, use
169
164
  Command.stdout(stdoutMode),
170
165
  Command.stderr(stderrMode),
171
166
  Command.workingDirectory(cwd),
172
- useShell ? Command.runInShell(true) : identity,
167
+ useShell === true ? Command.runInShell(true) : identity,
173
168
  Command.env(env),
174
169
  Command.exitCode,
175
170
  )
@@ -228,14 +223,16 @@ const runWithLogging = ({
228
223
  Command.stdout('pipe'),
229
224
  Command.stderr('pipe'),
230
225
  Command.workingDirectory(cwd),
231
- useShell ? Command.runInShell(true) : identity,
226
+ useShell === true ? Command.runInShell(true) : identity,
232
227
  Command.env(envWithColor),
233
228
  )
234
229
 
235
230
  // Acquire/start the command and make sure we kill it on interruption.
236
231
  const runningProcess = yield* Effect.acquireRelease(command.pipe(Command.start), (proc) =>
237
232
  proc.isRunning.pipe(
238
- Effect.flatMap((running) => (running ? proc.kill().pipe(Effect.catchAll(() => Effect.void)) : Effect.void)),
233
+ Effect.flatMap((running) =>
234
+ running === true ? proc.kill().pipe(Effect.catchAll(() => Effect.void)) : Effect.void,
235
+ ),
239
236
  Effect.ignore,
240
237
  ),
241
238
  )
@@ -266,7 +263,7 @@ const runWithLogging = ({
266
263
  // Dump any buffered data and finish both stream fibers before we return.
267
264
  const flushOutputs = Effect.gen(function* () {
268
265
  const stillRunning = yield* runningProcess.isRunning.pipe(Effect.catchAll(() => Effect.succeed(false)))
269
- if (stillRunning) {
266
+ if (stillRunning === true) {
270
267
  yield* Effect.ignore(runningProcess.kill())
271
268
  }
272
269
  yield* Effect.ignore(Fiber.join(stdoutFiber))
@@ -282,12 +279,12 @@ const runWithLogging = ({
282
279
  )
283
280
 
284
281
  const buildCommand = (input: string | string[], useShell: boolean) => {
285
- if (Array.isArray(input)) {
282
+ if (Array.isArray(input) === true) {
286
283
  const [c, ...a] = input
287
284
  return Command.make(c!, ...a)
288
285
  }
289
286
 
290
- if (useShell) {
287
+ if (useShell === true) {
291
288
  return Command.make(input)
292
289
  }
293
290
 
@@ -298,8 +295,8 @@ const buildCommand = (input: string | string[], useShell: boolean) => {
298
295
  type TLineTerminator = 'newline' | 'carriage-return' | 'none'
299
296
 
300
297
  type TStreamHandler = {
301
- readonly onChunk: (chunk: string) => Effect.Effect<void, never>
302
- readonly flush: () => Effect.Effect<void, never>
298
+ readonly onChunk: (chunk: string) => Effect.Effect<void>
299
+ readonly flush: () => Effect.Effect<void>
303
300
  }
304
301
 
305
302
  const makeStreamHandler = ({
@@ -309,7 +306,7 @@ const makeStreamHandler = ({
309
306
  }: {
310
307
  readonly channel: 'stdout' | 'stderr'
311
308
  readonly mirrorTarget?: NodeJS.WriteStream
312
- readonly appendLog: (args: { channel: 'stdout' | 'stderr'; content: string }) => Effect.Effect<void, never>
309
+ readonly appendLog: (args: { channel: 'stdout' | 'stderr'; content: string }) => Effect.Effect<void>
313
310
  }): TStreamHandler => {
314
311
  let buffer = ''
315
312
 
@@ -322,11 +319,11 @@ const makeStreamHandler = ({
322
319
  channel,
323
320
  content,
324
321
  terminator,
325
- ...(mirrorTarget ? { mirrorTarget } : {}),
322
+ ...(mirrorTarget !== undefined ? { mirrorTarget } : {}),
326
323
  appendLog,
327
324
  })
328
325
 
329
- const consumeBuffer = (): Effect.Effect<void, never> => {
326
+ const consumeBuffer = (): Effect.Effect<void> => {
330
327
  if (buffer.length === 0) return Effect.void
331
328
 
332
329
  const lastChar = buffer[buffer.length - 1]
@@ -390,10 +387,10 @@ const emitSegment = ({
390
387
  readonly content: string
391
388
  readonly terminator: TLineTerminator
392
389
  readonly mirrorTarget?: NodeJS.WriteStream
393
- readonly appendLog: (args: { channel: 'stdout' | 'stderr'; content: string }) => Effect.Effect<void, never>
390
+ readonly appendLog: (args: { channel: 'stdout' | 'stderr'; content: string }) => Effect.Effect<void>
394
391
  }) =>
395
392
  Effect.gen(function* () {
396
- if (mirrorTarget) {
393
+ if (mirrorTarget !== undefined) {
397
394
  yield* Effect.sync(() => mirrorSegment(mirrorTarget, content, terminator))
398
395
  }
399
396
 
package/src/node/mod.ts CHANGED
@@ -1,16 +1,17 @@
1
1
  import { performance } from 'node:perf_hooks'
2
2
 
3
3
  import * as OtelNodeSdk from '@effect/opentelemetry/NodeSdk'
4
- import { IS_BUN, isNonEmptyString } from '@livestore/utils'
5
- import type { Tracer } from '@livestore/utils/effect'
6
- import { Config, Effect, FiberRef, Layer, LogLevel, OtelTracer } from '@livestore/utils/effect'
7
- import { OtelLiveDummy } from '@livestore/utils/node'
8
4
  import * as otel from '@opentelemetry/api'
9
5
  import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
10
6
  import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
11
7
  import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
12
8
  import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
13
9
 
10
+ import { IS_BUN, isNonEmptyString } from '@livestore/utils'
11
+ import type { Tracer } from '@livestore/utils/effect'
12
+ import { Config, Effect, FiberRef, Layer, LogLevel, OtelTracer, Schema } from '@livestore/utils/effect'
13
+ import { OtelLiveDummy } from '@livestore/utils/node'
14
+
14
15
  export { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
15
16
  export { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
16
17
  export * from './cmd.ts'
@@ -40,16 +41,16 @@ export const OtelLiveHttp = ({
40
41
  rootSpanAttributes?: Record<string, unknown>
41
42
  skipLogUrl?: boolean
42
43
  traceNodeBootstrap?: boolean
43
- } = {}): Layer.Layer<OtelTracer.OtelTracer | Tracer.ParentSpan, never, never> =>
44
+ } = {}): Layer.Layer<OtelTracer.OtelTracer | Tracer.ParentSpan> =>
44
45
  Effect.gen(function* () {
45
46
  const configRes = yield* Config.all({
46
47
  exporterUrl: Config.string('OTEL_EXPORTER_OTLP_ENDPOINT').pipe(
47
48
  Config.validate({ message: 'OTEL_EXPORTER_OTLP_ENDPOINT must be set', validation: isNonEmptyString }),
48
49
  ),
49
- serviceName: serviceName
50
+ serviceName: serviceName !== undefined
50
51
  ? Config.succeed(serviceName)
51
52
  : Config.string('OTEL_SERVICE_NAME').pipe(Config.withDefault('livestore-utils-dev')),
52
- rootSpanName: rootSpanName
53
+ rootSpanName: rootSpanName !== undefined
53
54
  ? Config.succeed(rootSpanName)
54
55
  : Config.string('OTEL_ROOT_SPAN_NAME').pipe(Config.withDefault('RootSpan')),
55
56
  }).pipe(Effect.option)
@@ -91,13 +92,13 @@ export const OtelLiveHttp = ({
91
92
 
92
93
  const RootSpanLive = Layer.span(config.rootSpanName, {
93
94
  attributes: { config, ...rootSpanAttributes },
94
- onEnd: skipLogUrl ? undefined : (span: any) => logTraceUiUrlForSpan()(span.span),
95
+ onEnd: skipLogUrl === true ? undefined : (span: any) => logTraceUiUrlForSpan()(span.span),
95
96
  parent: parentSpan,
96
97
  })
97
98
 
98
99
  const layer = yield* Layer.memoize(RootSpanLive.pipe(Layer.provideMerge(OtelLive)))
99
100
 
100
- if (traceNodeBootstrap && !IS_BUN) {
101
+ if (traceNodeBootstrap === true && IS_BUN === false) {
101
102
  /**
102
103
  * Create a span representing the Node.js bootstrap duration.
103
104
  * Note: Skipped in Bun since performance.nodeTiming is not properly supported.
@@ -136,7 +137,7 @@ export const logTraceUiUrlForSpan = (printMsg?: (url: string) => string) => (spa
136
137
  if (url === undefined) {
137
138
  return Effect.logWarning('No tracing backend url found')
138
139
  } else {
139
- if (printMsg) {
140
+ if (printMsg !== undefined) {
140
141
  return Effect.log(printMsg(url))
141
142
  } else {
142
143
  return Effect.log(`Trace URL: ${url}`)
@@ -155,13 +156,14 @@ export const getTracingBackendUrl = (span: otel.Span) =>
155
156
  // Grafana + Tempo
156
157
 
157
158
  const grafanaEndpoint = endpoint.value
159
+ const left = yield* Schema.encode(Schema.parseJson())({
160
+ datasource: 'tempo',
161
+ queries: [{ query: traceId, queryType: 'traceql', refId: 'A' }],
162
+ range: { from: 'now-1h', to: 'now' },
163
+ }).pipe(Effect.orDie)
158
164
  const searchParams = new URLSearchParams({
159
165
  orgId: '1',
160
- left: JSON.stringify({
161
- datasource: 'tempo',
162
- queries: [{ query: traceId, queryType: 'traceql', refId: 'A' }],
163
- range: { from: 'now-1h', to: 'now' },
164
- }),
166
+ left,
165
167
  })
166
168
 
167
169
  // TODO make dynamic via env var
@@ -188,14 +190,14 @@ const computeBootstrapTiming = () => {
188
190
  const nodeTiming = performance.nodeTiming
189
191
 
190
192
  // Absolute start time in ms since epoch.
191
- const startAbs = IS_BUN
193
+ const startAbs = IS_BUN === true
192
194
  ? typeof nodeTiming.nodeStart === 'number'
193
195
  ? nodeTiming.nodeStart
194
196
  : performance.timeOrigin
195
197
  : performance.timeOrigin + nodeTiming.nodeStart
196
198
 
197
199
  // Absolute end time.
198
- const endAbs = IS_BUN
200
+ const endAbs = IS_BUN === true
199
201
  ? (() => {
200
202
  const { loopStart, bootstrapComplete } = nodeTiming
201
203
  if (typeof loopStart === 'number' && loopStart > 0) return startAbs + loopStart
@@ -205,7 +207,7 @@ const computeBootstrapTiming = () => {
205
207
  : startAbs + nodeTiming.duration
206
208
 
207
209
  // Duration attribute value for the span.
208
- const durationAttr = IS_BUN
210
+ const durationAttr = IS_BUN === true
209
211
  ? (() => {
210
212
  const { loopStart } = nodeTiming
211
213
  return typeof loopStart === 'number' && loopStart > 0 ? loopStart : 0
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path'
2
+
2
3
  import { shouldNeverHappen } from '@livestore/utils'
3
4
  import { Context, Effect, Layer } from '@livestore/utils/effect'
4
5
 
@@ -1,6 +1,11 @@
1
- import { Effect, FastCheck } from '@livestore/utils/effect'
1
+ import { Effect, FastCheck, Schema } from '@livestore/utils/effect'
2
+
2
3
  import * as Vitest from './Vitest.ts'
3
4
 
5
+ export class TestError extends Schema.TaggedError<TestError>()('TestError', {
6
+ message: Schema.String,
7
+ }) {}
8
+
4
9
  // Demonstrate enhanced asProp functionality with clear shrinking progress
5
10
  // This showcases the fix for the "Run 26/6" bug and accurate shrinking detection
6
11
 
@@ -16,7 +21,7 @@ Vitest.describe('Vitest.asProp', () => {
16
21
  Effect.gen(function* () {
17
22
  const [value] = properties
18
23
  if (value === undefined) {
19
- return yield* Effect.fail(new Error('Value is undefined'))
24
+ return yield* new TestError({ message: 'Value is undefined' })
20
25
  }
21
26
 
22
27
  console.log(
@@ -43,7 +48,7 @@ Vitest.describe('Vitest.asProp', () => {
43
48
  Effect.gen(function* () {
44
49
  const [value] = properties
45
50
  if (value === undefined) {
46
- return yield* Effect.fail(new Error('Value is undefined'))
51
+ return yield* new TestError({ message: 'Value is undefined' })
47
52
  }
48
53
 
49
54
  const displayInfo =
@@ -59,10 +64,10 @@ Vitest.describe('Vitest.asProp', () => {
59
64
  // Fail when value is greater than 80 to trigger shrinking
60
65
  if (value > 80) {
61
66
  alreadyFailed = true
62
- return yield* Effect.fail(new Error(`Value ${value} is too large (> 80)`))
67
+ return yield* new TestError({ message: `Value ${value} is too large (> 80)` })
63
68
  }
64
69
 
65
- if (alreadyFailed && enhanced._tag === 'shrinking') {
70
+ if (alreadyFailed === true && enhanced._tag === 'shrinking') {
66
71
  ctx.skip("For the sake of this test, we don't want to fail but want to skip")
67
72
  return
68
73
  }
@@ -81,7 +86,7 @@ Vitest.describe('Vitest.asProp', () => {
81
86
  Effect.gen(function* () {
82
87
  const [value] = properties
83
88
  if (value === undefined) {
84
- return yield* Effect.fail(new Error('Value is undefined'))
89
+ return yield* new TestError({ message: 'Value is undefined' })
85
90
  }
86
91
 
87
92
  console.log(
@@ -91,7 +96,7 @@ Vitest.describe('Vitest.asProp', () => {
91
96
 
92
97
  // This will fail but shrinking is disabled
93
98
  if (value > 50) {
94
- return yield* Effect.fail(new Error(`Value ${value} is too large (> 50) - but no shrinking!`))
99
+ return yield* new TestError({ message: `Value ${value} is too large (> 50) - but no shrinking!` })
95
100
  }
96
101
 
97
102
  return