@livestore/utils 0.4.0-dev.21 → 0.4.0-dev.23
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 -0
- package/dist/NoopTracer.d.ts.map +1 -1
- package/dist/NoopTracer.js +15 -5
- package/dist/NoopTracer.js.map +1 -1
- package/dist/binary.js +1 -1
- package/dist/binary.js.map +1 -1
- package/dist/browser/Opfs/Opfs.d.ts +3 -3
- package/dist/browser/Opfs/Opfs.d.ts.map +1 -1
- package/dist/browser/Opfs/Opfs.js +1 -1
- package/dist/browser/Opfs/Opfs.js.map +1 -1
- package/dist/browser/Opfs/debug-utils.d.ts.map +1 -1
- package/dist/browser/Opfs/debug-utils.js +2 -2
- package/dist/browser/Opfs/debug-utils.js.map +1 -1
- package/dist/browser/Opfs/utils.d.ts.map +1 -1
- package/dist/browser/Opfs/utils.js +10 -10
- package/dist/browser/Opfs/utils.js.map +1 -1
- package/dist/browser/QuotaExceededError.d.ts.map +1 -1
- package/dist/browser/QuotaExceededError.js.map +1 -1
- package/dist/browser/WebChannelBrowser.d.ts +1 -1
- package/dist/browser/WebChannelBrowser.d.ts.map +1 -1
- package/dist/browser/WebError.d.ts +50 -54
- package/dist/browser/WebError.d.ts.map +1 -1
- package/dist/browser/WebError.js +25 -23
- package/dist/browser/WebError.js.map +1 -1
- package/dist/browser/WebLock.js +9 -9
- package/dist/browser/WebLock.js.map +1 -1
- package/dist/browser/detect.js +6 -6
- package/dist/browser/detect.js.map +1 -1
- package/dist/cuid/cuid.browser.js +1 -1
- package/dist/cuid/cuid.browser.js.map +1 -1
- package/dist/cuid/cuid.node.js +1 -1
- package/dist/cuid/cuid.node.js.map +1 -1
- package/dist/effect/BucketQueue.d.ts +1 -1
- package/dist/effect/BucketQueue.d.ts.map +1 -1
- package/dist/effect/BucketQueue.js.map +1 -1
- package/dist/effect/Debug.d.ts +7 -2
- package/dist/effect/Debug.d.ts.map +1 -1
- package/dist/effect/Debug.js +69 -61
- package/dist/effect/Debug.js.map +1 -1
- package/dist/effect/Effect.d.ts +72 -12
- package/dist/effect/Effect.d.ts.map +1 -1
- package/dist/effect/Effect.js +96 -12
- package/dist/effect/Effect.js.map +1 -1
- package/dist/effect/Error.js +1 -1
- package/dist/effect/Error.js.map +1 -1
- package/dist/effect/Logger.js +2 -2
- package/dist/effect/Logger.js.map +1 -1
- package/dist/effect/RpcClient.d.ts.map +1 -1
- package/dist/effect/RpcClient.js +4 -4
- package/dist/effect/RpcClient.js.map +1 -1
- package/dist/effect/Schema/debug-diff.js +5 -4
- package/dist/effect/Schema/debug-diff.js.map +1 -1
- package/dist/effect/Schema/index.d.ts +5 -3
- package/dist/effect/Schema/index.d.ts.map +1 -1
- package/dist/effect/Schema/index.js +1 -1
- package/dist/effect/Schema/index.js.map +1 -1
- package/dist/effect/ServiceContext.js +6 -6
- package/dist/effect/ServiceContext.js.map +1 -1
- package/dist/effect/Stream.test.js +3 -3
- package/dist/effect/Stream.test.js.map +1 -1
- package/dist/effect/SubscriptionRef.d.ts +4 -4
- package/dist/effect/SubscriptionRef.d.ts.map +1 -1
- package/dist/effect/WebChannel/WebChannel.d.ts +2 -2
- package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
- package/dist/effect/WebChannel/WebChannel.js +4 -4
- package/dist/effect/WebChannel/WebChannel.js.map +1 -1
- package/dist/effect/WebChannel/broadcastChannelWithAck.js +4 -4
- package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
- package/dist/effect/WebChannel/common.d.ts +1 -1
- package/dist/effect/WebChannel/common.d.ts.map +1 -1
- package/dist/effect/WebChannel/common.js +2 -2
- package/dist/effect/WebChannel/common.js.map +1 -1
- package/dist/effect/WebSocket.js +3 -3
- package/dist/effect/WebSocket.js.map +1 -1
- package/dist/effect/mod.d.ts +1 -1
- package/dist/effect/mod.d.ts.map +1 -1
- package/dist/effect/mod.js +1 -1
- package/dist/effect/mod.js.map +1 -1
- package/dist/effect/spanEvent.d.ts +12 -0
- package/dist/effect/spanEvent.d.ts.map +1 -0
- package/dist/effect/spanEvent.js +12 -0
- package/dist/effect/spanEvent.js.map +1 -0
- package/dist/effect/spanEvent.test.d.ts +2 -0
- package/dist/effect/spanEvent.test.d.ts.map +1 -0
- package/dist/effect/spanEvent.test.js +82 -0
- package/dist/effect/spanEvent.test.js.map +1 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +1 -1
- package/dist/env.js.map +1 -1
- package/dist/fast-deep-equal.js +9 -9
- package/dist/fast-deep-equal.js.map +1 -1
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js.map +1 -1
- package/dist/misc.d.ts +9 -1
- package/dist/misc.d.ts.map +1 -1
- package/dist/misc.js +11 -3
- package/dist/misc.js.map +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +12 -12
- package/dist/mod.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js +15 -9
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +8 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +10 -6
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +2 -2
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js +4 -4
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +1 -1
- package/dist/node/mod.d.ts +31 -4
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +33 -6
- package/dist/node/mod.js.map +1 -1
- package/dist/object/index.d.ts.map +1 -1
- package/dist/object/index.js.map +1 -1
- package/dist/object/omit.js +1 -1
- package/dist/object/omit.js.map +1 -1
- package/dist/object/stringify-object.js +2 -2
- package/dist/object/stringify-object.js.map +1 -1
- package/dist/object/stringify-object.test.js.map +1 -1
- package/dist/qr.js +11 -11
- package/dist/qr.js.map +1 -1
- package/dist/set.js +1 -1
- package/dist/set.js.map +1 -1
- package/dist/time.js +1 -1
- package/dist/time.js.map +1 -1
- package/package.json +66 -52
- package/src/NoopTracer.ts +20 -9
- package/src/binary.ts +1 -1
- package/src/browser/Opfs/Opfs.ts +12 -4
- package/src/browser/Opfs/debug-utils.ts +8 -6
- package/src/browser/Opfs/utils.ts +11 -10
- package/src/browser/QuotaExceededError.ts +0 -2
- package/src/browser/WebChannelBrowser.ts +1 -1
- package/src/browser/WebError.ts +100 -86
- package/src/browser/WebLock.ts +15 -15
- package/src/browser/detect.ts +6 -6
- package/src/cuid/cuid.browser.ts +1 -1
- package/src/cuid/cuid.node.ts +1 -1
- package/src/effect/BucketQueue.ts +1 -1
- package/src/effect/Debug.ts +73 -55
- package/src/effect/Effect.ts +118 -36
- package/src/effect/Error.ts +1 -1
- package/src/effect/Logger.ts +2 -2
- package/src/effect/RpcClient.ts +7 -6
- package/src/effect/Schema/debug-diff.ts +6 -5
- package/src/effect/Schema/index.ts +9 -6
- package/src/effect/ServiceContext.ts +6 -6
- package/src/effect/Stream.test.ts +5 -4
- package/src/effect/SubscriptionRef.ts +5 -5
- package/src/effect/WebChannel/WebChannel.ts +6 -6
- package/src/effect/WebChannel/broadcastChannelWithAck.ts +4 -4
- package/src/effect/WebChannel/common.ts +4 -4
- package/src/effect/WebSocket.ts +3 -3
- package/src/effect/mod.ts +1 -0
- package/src/effect/spanEvent.test.ts +95 -0
- package/src/effect/spanEvent.ts +15 -0
- package/src/env.ts +2 -1
- package/src/fast-deep-equal.ts +9 -9
- package/src/global.ts +0 -2
- package/src/misc.ts +12 -4
- package/src/mod.ts +11 -11
- package/src/node/ChildProcessRunner/ChildProcessRunner.ts +23 -10
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +11 -7
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +10 -11
- package/src/node/ChildProcessRunner/ChildProcessWorker.ts +5 -4
- package/src/node/mod.ts +38 -6
- package/src/object/index.ts +1 -1
- package/src/object/omit.ts +1 -1
- package/src/object/stringify-object.test.ts +1 -0
- package/src/object/stringify-object.ts +2 -2
- package/src/qr.ts +11 -11
- package/src/set.ts +1 -1
- package/src/time.ts +1 -1
- package/dist/.tsbuildinfo.json +0 -1
package/src/mod.ts
CHANGED
|
@@ -119,7 +119,7 @@ export const debugCatch = <T>(try_: () => T): T => {
|
|
|
119
119
|
try {
|
|
120
120
|
return try_()
|
|
121
121
|
} catch (e: any) {
|
|
122
|
-
//
|
|
122
|
+
// oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for debugging exceptions
|
|
123
123
|
debugger
|
|
124
124
|
throw e
|
|
125
125
|
}
|
|
@@ -130,7 +130,7 @@ export const debugCatch = <T>(try_: () => T): T => {
|
|
|
130
130
|
* Mutates the input value.
|
|
131
131
|
*/
|
|
132
132
|
export const recRemoveUndefinedValues = (val: any): void => {
|
|
133
|
-
if (Array.isArray(val)) {
|
|
133
|
+
if (Array.isArray(val) === true) {
|
|
134
134
|
val.forEach(recRemoveUndefinedValues)
|
|
135
135
|
} else if (typeof val === 'object') {
|
|
136
136
|
Object.keys(val).forEach((key) => {
|
|
@@ -173,8 +173,8 @@ export const isReadonlyArray = <I, T>(value: ReadonlyArray<I> | T): value is Rea
|
|
|
173
173
|
* union have been accounted for.
|
|
174
174
|
*/
|
|
175
175
|
|
|
176
|
-
export
|
|
177
|
-
//
|
|
176
|
+
export const casesHandled = (unexpectedCase: never): never => {
|
|
177
|
+
// oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for unhandled cases
|
|
178
178
|
debugger
|
|
179
179
|
throw new Error(`A case was not handled for value: ${truncate(objectToString(unexpectedCase), 1000)}`)
|
|
180
180
|
}
|
|
@@ -189,7 +189,7 @@ export function casesHandled(unexpectedCase: never): never {
|
|
|
189
189
|
*/
|
|
190
190
|
export const assertNever = (failIfFalse: boolean, msg?: string): void => {
|
|
191
191
|
if (failIfFalse === false) {
|
|
192
|
-
//
|
|
192
|
+
// oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for impossible states
|
|
193
193
|
debugger
|
|
194
194
|
throw new Error(`This should never happen: ${msg}`)
|
|
195
195
|
}
|
|
@@ -204,7 +204,7 @@ export const assertNever = (failIfFalse: boolean, msg?: string): void => {
|
|
|
204
204
|
* ```
|
|
205
205
|
*/
|
|
206
206
|
export const debuggerPipe = <T>(val: T): T => {
|
|
207
|
-
//
|
|
207
|
+
// oxlint-disable-next-line eslint(no-debugger) -- intentional: this function's purpose is to trigger debugger
|
|
208
208
|
debugger
|
|
209
209
|
return val
|
|
210
210
|
}
|
|
@@ -231,7 +231,7 @@ const truncate = (str: string, length: number): string => {
|
|
|
231
231
|
* ```
|
|
232
232
|
*/
|
|
233
233
|
export const notYetImplemented = (msg?: string): never => {
|
|
234
|
-
//
|
|
234
|
+
// oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for unimplemented code paths
|
|
235
235
|
debugger
|
|
236
236
|
throw new Error(`Not yet implemented: ${msg}`)
|
|
237
237
|
}
|
|
@@ -303,7 +303,7 @@ export const throttle = (fn: () => void, ms: number) => {
|
|
|
303
303
|
let shouldCallAgain = false
|
|
304
304
|
|
|
305
305
|
const timeoutFunc = () => {
|
|
306
|
-
if (shouldCallAgain) {
|
|
306
|
+
if (shouldCallAgain === true) {
|
|
307
307
|
fn()
|
|
308
308
|
shouldCallAgain = false
|
|
309
309
|
setTimeout(timeoutFunc, ms)
|
|
@@ -313,7 +313,7 @@ export const throttle = (fn: () => void, ms: number) => {
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
return () => {
|
|
316
|
-
if (shouldWait) {
|
|
316
|
+
if (shouldWait === true) {
|
|
317
317
|
shouldCallAgain = true
|
|
318
318
|
return
|
|
319
319
|
}
|
|
@@ -374,7 +374,7 @@ export const memoizeByStringifyArgs = <T extends (...args: any[]) => any>(fn: T)
|
|
|
374
374
|
|
|
375
375
|
return ((...args: any[]) => {
|
|
376
376
|
const key = JSON.stringify(args)
|
|
377
|
-
if (cache.has(key)) {
|
|
377
|
+
if (cache.has(key) === true) {
|
|
378
378
|
return cache.get(key)
|
|
379
379
|
}
|
|
380
380
|
|
|
@@ -400,7 +400,7 @@ export const memoizeByRef = <T extends (arg: any) => any>(fn: T): T => {
|
|
|
400
400
|
const cache = new Map<Parameters<T>[0], ReturnType<T>>()
|
|
401
401
|
|
|
402
402
|
return ((arg: any) => {
|
|
403
|
-
if (cache.has(arg)) {
|
|
403
|
+
if (cache.has(arg) === true) {
|
|
404
404
|
return cache.get(arg)
|
|
405
405
|
}
|
|
406
406
|
|
|
@@ -17,16 +17,29 @@ import * as Scope from 'effect/Scope'
|
|
|
17
17
|
let parentDeathDetectionEnabled = false
|
|
18
18
|
let parentDeathTimer: NodeJS.Timeout | null = null
|
|
19
19
|
|
|
20
|
+
type SetupParentDeathDetectionMessage = ['setup-parent-death-detection', { parentPid: number }]
|
|
21
|
+
type RunnerMessage<I> = Runner.BackingRunner.Message<I> | SetupParentDeathDetectionMessage
|
|
22
|
+
|
|
23
|
+
const isSetupParentDeathDetectionMessage = (
|
|
24
|
+
message: unknown,
|
|
25
|
+
): message is SetupParentDeathDetectionMessage =>
|
|
26
|
+
Array.isArray(message) &&
|
|
27
|
+
message[0] === 'setup-parent-death-detection' &&
|
|
28
|
+
typeof message[1] === 'object' &&
|
|
29
|
+
message[1] !== null &&
|
|
30
|
+
'parentPid' in message[1] &&
|
|
31
|
+
typeof (message[1] as { parentPid: unknown }).parentPid === 'number'
|
|
32
|
+
|
|
20
33
|
const stopParentDeathMonitoring = () => {
|
|
21
34
|
parentDeathDetectionEnabled = false
|
|
22
|
-
if (parentDeathTimer) {
|
|
35
|
+
if (parentDeathTimer !== null) {
|
|
23
36
|
clearTimeout(parentDeathTimer)
|
|
24
37
|
parentDeathTimer = null
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
const setupParentDeathMonitoring = (parentPid: number) => {
|
|
29
|
-
if (parentDeathDetectionEnabled) return
|
|
42
|
+
if (parentDeathDetectionEnabled === true) return
|
|
30
43
|
parentDeathDetectionEnabled = true
|
|
31
44
|
|
|
32
45
|
let consecutiveFailures = 0
|
|
@@ -34,7 +47,7 @@ const setupParentDeathMonitoring = (parentPid: number) => {
|
|
|
34
47
|
|
|
35
48
|
// Check if parent is still alive every 2 seconds (more conservative)
|
|
36
49
|
const checkParentAlive = () => {
|
|
37
|
-
if (
|
|
50
|
+
if (parentDeathDetectionEnabled === false) return
|
|
38
51
|
try {
|
|
39
52
|
// Send signal 0 to check if process exists (doesn't actually send signal)
|
|
40
53
|
process.kill(parentPid, 0)
|
|
@@ -64,13 +77,13 @@ const platformRunnerImpl = Runner.PlatformRunner.of({
|
|
|
64
77
|
[Runner.PlatformRunnerTypeId]: Runner.PlatformRunnerTypeId,
|
|
65
78
|
start<I, O>(closeLatch: typeof CloseLatch.Service) {
|
|
66
79
|
return Effect.gen(function* () {
|
|
67
|
-
if (
|
|
80
|
+
if (process.send == null) {
|
|
68
81
|
return yield* new WorkerError({ reason: 'spawn', cause: new Error('not in a child process') })
|
|
69
82
|
}
|
|
70
83
|
const port = {
|
|
71
84
|
postMessage: (message: any) => process.send!(message),
|
|
72
85
|
on: (event: string, handler: (message: any) => void) => process.on(event, handler),
|
|
73
|
-
close: () => process.disconnect(),
|
|
86
|
+
close: () => process.disconnect?.(),
|
|
74
87
|
}
|
|
75
88
|
const send = (_portId: number, message: O, _transfers?: ReadonlyArray<unknown>) =>
|
|
76
89
|
Effect.sync(() => port.postMessage([1, message] /*, transfers as any*/))
|
|
@@ -85,16 +98,16 @@ const platformRunnerImpl = Runner.PlatformRunner.of({
|
|
|
85
98
|
const fiberSet = yield* FiberSet.make<any, WorkerError | E>()
|
|
86
99
|
const runFork = Runtime.runFork(runtime)
|
|
87
100
|
const onExit = (exit: Exit.Exit<any, E>) => {
|
|
88
|
-
if (exit._tag === 'Failure' &&
|
|
101
|
+
if (exit._tag === 'Failure' && Cause.isInterruptedOnly(exit.cause) === false) {
|
|
89
102
|
// Deferred.unsafeDone(closeLatch, Exit.die(Cause.squash(exit.cause)))
|
|
90
103
|
Deferred.unsafeDone(closeLatch, Exit.die(exit.cause))
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
|
-
|
|
106
|
+
port.on('message', (message: RunnerMessage<I>) => {
|
|
94
107
|
// console.log('message', message)
|
|
95
108
|
|
|
96
109
|
// Handle parent death detection setup messages
|
|
97
|
-
if (
|
|
110
|
+
if (isSetupParentDeathDetectionMessage(message) === true) {
|
|
98
111
|
const parentPid = message[1].parentPid
|
|
99
112
|
// console.log(`[Worker ${process.pid}] Setting up parent death detection for parent ${parentPid}`)
|
|
100
113
|
setupParentDeathMonitoring(parentPid)
|
|
@@ -102,10 +115,10 @@ const platformRunnerImpl = Runner.PlatformRunner.of({
|
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
// Handle normal Effect worker messages
|
|
105
|
-
if (Array.isArray(message) && typeof message[0] === 'number') {
|
|
118
|
+
if (Array.isArray(message) === true && typeof message[0] === 'number') {
|
|
106
119
|
if (message[0] === 0) {
|
|
107
120
|
const result = handler(0, message[1])
|
|
108
|
-
if (Effect.isEffect(result)) {
|
|
121
|
+
if (Effect.isEffect(result) === true) {
|
|
109
122
|
const fiber = runFork(result)
|
|
110
123
|
fiber.addObserver(onExit)
|
|
111
124
|
FiberSet.unsafeAdd(fiberSet, fiber)
|
|
@@ -2,7 +2,11 @@ import * as ChildProcess from 'node:child_process'
|
|
|
2
2
|
|
|
3
3
|
import * as EffectWorker from '@effect/platform/Worker'
|
|
4
4
|
import { assert, describe, it } from '@effect/vitest'
|
|
5
|
-
import { Chunk, Deferred, Effect, Exit, Fiber, Scope, Stream } from 'effect'
|
|
5
|
+
import { Chunk, Deferred, Effect, Exit, Fiber, Schema, Scope, Stream } from 'effect'
|
|
6
|
+
|
|
7
|
+
export class TestError extends Schema.TaggedError<TestError>()('TestError', {
|
|
8
|
+
message: Schema.String,
|
|
9
|
+
}) {}
|
|
6
10
|
|
|
7
11
|
import * as ChildProcessWorker from '../ChildProcessWorker.ts'
|
|
8
12
|
import type { WorkerMessage } from './schema.ts'
|
|
@@ -84,7 +88,7 @@ describe('ChildProcessRunner', { timeout: 10_000 }, () => {
|
|
|
84
88
|
const workerPid = yield* Deferred.await(workerPidDeferred).pipe(
|
|
85
89
|
Effect.raceFirst(
|
|
86
90
|
Fiber.join(fiber).pipe(
|
|
87
|
-
Effect.flatMap(() =>
|
|
91
|
+
Effect.flatMap(() => new TestError({ message: 'testEffect completed before reporting worker PID' })),
|
|
88
92
|
),
|
|
89
93
|
),
|
|
90
94
|
Effect.timeout(10_000),
|
|
@@ -120,7 +124,7 @@ describe('ChildProcessRunner', { timeout: 10_000 }, () => {
|
|
|
120
124
|
workerPid = result.pid
|
|
121
125
|
|
|
122
126
|
// Verify the worker is running
|
|
123
|
-
assert.strictEqual(isProcessRunning(workerPid
|
|
127
|
+
assert.strictEqual(isProcessRunning(workerPid), true, 'Worker process should be running')
|
|
124
128
|
} finally {
|
|
125
129
|
// Abruptly close the scope (simulating test abortion)
|
|
126
130
|
yield* Scope.close(scope, Exit.void)
|
|
@@ -130,7 +134,7 @@ describe('ChildProcessRunner', { timeout: 10_000 }, () => {
|
|
|
130
134
|
yield* Effect.sleep('1 second')
|
|
131
135
|
|
|
132
136
|
// This should pass but will initially fail due to zombie process issue
|
|
133
|
-
if (workerPid) {
|
|
137
|
+
if (workerPid !== undefined) {
|
|
134
138
|
assert.strictEqual(
|
|
135
139
|
isProcessRunning(workerPid),
|
|
136
140
|
false,
|
|
@@ -189,7 +193,7 @@ describe('ChildProcessRunner', { timeout: 10_000 }, () => {
|
|
|
189
193
|
yield* Effect.sleep('2 seconds')
|
|
190
194
|
|
|
191
195
|
// This test should initially fail - child process will still be running
|
|
192
|
-
if (workerPid) {
|
|
196
|
+
if (workerPid !== undefined) {
|
|
193
197
|
assert.strictEqual(
|
|
194
198
|
isProcessRunning(workerPid),
|
|
195
199
|
false,
|
|
@@ -274,7 +278,7 @@ describe('ChildProcessRunner', { timeout: 10_000 }, () => {
|
|
|
274
278
|
yield* worker.executeEffect(new StartStubbornWorker({ blockDuration: 60_000 }))
|
|
275
279
|
|
|
276
280
|
// Verify process is running
|
|
277
|
-
if (childPid) {
|
|
281
|
+
if (childPid !== undefined) {
|
|
278
282
|
assert.strictEqual(isProcessRunning(childPid), true, 'Child process should be running')
|
|
279
283
|
}
|
|
280
284
|
|
|
@@ -293,7 +297,7 @@ describe('ChildProcessRunner', { timeout: 10_000 }, () => {
|
|
|
293
297
|
yield* Effect.sleep('3 seconds')
|
|
294
298
|
|
|
295
299
|
// This test should initially fail - demonstrating the zombie process issue
|
|
296
|
-
if (childPid) {
|
|
300
|
+
if (childPid !== undefined) {
|
|
297
301
|
assert.strictEqual(
|
|
298
302
|
isProcessRunning(childPid),
|
|
299
303
|
false,
|
|
@@ -33,19 +33,18 @@ const WorkerLive = Runner.layerSerialized(WorkerMessage, {
|
|
|
33
33
|
// return req.name
|
|
34
34
|
// }),
|
|
35
35
|
// ),
|
|
36
|
-
GetSpan: (_)
|
|
37
|
-
Effect.
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
GetSpan: Effect.fn('GetSpan')(function* (_) {
|
|
37
|
+
const span = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
38
|
+
return {
|
|
39
|
+
traceId: span.traceId,
|
|
40
|
+
spanId: span.spanId,
|
|
41
|
+
name: span.name,
|
|
42
|
+
parent: Option.map(span.parent, (span) => ({
|
|
40
43
|
traceId: span.traceId,
|
|
41
44
|
spanId: span.spanId,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
spanId: span.spanId,
|
|
46
|
-
})),
|
|
47
|
-
}
|
|
48
|
-
}).pipe(Effect.withSpan('GetSpan')),
|
|
45
|
+
})),
|
|
46
|
+
}
|
|
47
|
+
}),
|
|
49
48
|
RunnerInterrupt: () => Effect.interrupt,
|
|
50
49
|
StartStubbornWorker: ({ blockDuration }) =>
|
|
51
50
|
Effect.gen(function* () {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as ChildProcess from 'node:child_process'
|
|
2
|
+
|
|
2
3
|
import * as Worker from '@effect/platform/Worker'
|
|
3
4
|
import { WorkerError } from '@effect/platform/WorkerError'
|
|
4
5
|
import * as Deferred from 'effect/Deferred'
|
|
@@ -14,7 +15,7 @@ const childProcesses = new Set<ChildProcess.ChildProcess>()
|
|
|
14
15
|
const forceCleanupChildren = (signal: NodeJS.Signals = 'SIGKILL') => {
|
|
15
16
|
for (const child of childProcesses) {
|
|
16
17
|
try {
|
|
17
|
-
if (
|
|
18
|
+
if (child.killed === false) {
|
|
18
19
|
child.kill(signal)
|
|
19
20
|
}
|
|
20
21
|
} catch {
|
|
@@ -28,7 +29,7 @@ const forceCleanupChildren = (signal: NodeJS.Signals = 'SIGKILL') => {
|
|
|
28
29
|
let signalHandlersInstalled = false
|
|
29
30
|
|
|
30
31
|
const installSignalHandlers = () => {
|
|
31
|
-
if (signalHandlersInstalled) return
|
|
32
|
+
if (signalHandlersInstalled === true) return
|
|
32
33
|
signalHandlersInstalled = true
|
|
33
34
|
|
|
34
35
|
// Use 'beforeExit' instead of signal handlers since tests may interfere with signals
|
|
@@ -95,14 +96,14 @@ const platformWorkerImpl = Worker.makePlatform<ChildProcess.ChildProcess>()({
|
|
|
95
96
|
Effect.catchAllCause(() =>
|
|
96
97
|
Effect.sync(() => {
|
|
97
98
|
// Enhanced cleanup with escalating signals
|
|
98
|
-
if (
|
|
99
|
+
if (childProcess.killed === false) {
|
|
99
100
|
try {
|
|
100
101
|
// First try SIGTERM
|
|
101
102
|
childProcess.kill('SIGTERM')
|
|
102
103
|
|
|
103
104
|
// If still running after a short delay, use SIGKILL
|
|
104
105
|
setTimeout(() => {
|
|
105
|
-
if (
|
|
106
|
+
if (childProcess.killed === false) {
|
|
106
107
|
childProcess.kill('SIGKILL')
|
|
107
108
|
}
|
|
108
109
|
}, 1000)
|
package/src/node/mod.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as http from 'node:http'
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { layer as ParcelWatcherLayer } from '@effect/platform-node/NodeFileSystem/ParcelWatcher'
|
|
4
4
|
import { Effect, Layer } from 'effect'
|
|
5
|
+
|
|
5
6
|
import { OtelTracer, UnknownError } from '../effect/mod.ts'
|
|
6
7
|
import { makeNoopTracer } from '../NoopTracer.ts'
|
|
7
8
|
|
|
@@ -28,7 +29,7 @@ export const getFreePort: Effect.Effect<number, UnknownError> = Effect.async<num
|
|
|
28
29
|
server.listen(0, () => {
|
|
29
30
|
const address = server.address()
|
|
30
31
|
|
|
31
|
-
if (address && typeof address === 'object') {
|
|
32
|
+
if (address !== null && typeof address === 'object') {
|
|
32
33
|
const port = address.port
|
|
33
34
|
server.close(() => cb(Effect.succeed(port)))
|
|
34
35
|
} else {
|
|
@@ -53,8 +54,39 @@ export const OtelLiveDummy: Layer.Layer<OtelTracer.OtelTracer> = Layer.suspend((
|
|
|
53
54
|
})
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
|
-
* Layer that
|
|
57
|
-
*
|
|
58
|
-
*
|
|
57
|
+
* Layer that provides WatchBackend for recursive file watching via @parcel/watcher.
|
|
58
|
+
* This layer alone does NOT provide FileSystem - it only provides the watch backend.
|
|
59
|
+
*
|
|
60
|
+
* IMPORTANT: Layer ordering matters! When composing with NodeFileSystem.layer, use
|
|
61
|
+
* `NodeFileSystemWithWatch` instead, or ensure WatchBackend is available when FileSystem
|
|
62
|
+
* is constructed by using `Layer.provideMerge`:
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* // ✅ CORRECT: Use the pre-composed layer
|
|
66
|
+
* Effect.provide(NodeFileSystemWithWatch)
|
|
67
|
+
*
|
|
68
|
+
* // ✅ CORRECT: Manual composition with Layer.provideMerge
|
|
69
|
+
* const layer = PlatformNode.NodeFileSystem.layer.pipe(Layer.provideMerge(NodeRecursiveWatchLayer))
|
|
70
|
+
* Effect.provide(layer)
|
|
71
|
+
*
|
|
72
|
+
* // ❌ WRONG: Chained Effect.provide - WatchBackend won't be used!
|
|
73
|
+
* Effect.provide(NodeRecursiveWatchLayer).pipe(Effect.provide(PlatformNode.NodeFileSystem.layer))
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @see https://github.com/Effect-TS/effect/issues/5913
|
|
59
77
|
*/
|
|
60
|
-
export const NodeRecursiveWatchLayer =
|
|
78
|
+
export const NodeRecursiveWatchLayer = ParcelWatcherLayer
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Pre-composed layer providing FileSystem with recursive file watching via @parcel/watcher.
|
|
82
|
+
* This is the recommended way to get a FileSystem that supports recursive watching.
|
|
83
|
+
*
|
|
84
|
+
* Use this layer when you need to watch files recursively (e.g., watching nested directories).
|
|
85
|
+
* Without recursive watching, Node.js's built-in fs.watch only detects changes in the
|
|
86
|
+
* immediate directory, not in subdirectories.
|
|
87
|
+
*/
|
|
88
|
+
export { NodeFileSystem } from '@effect/platform-node'
|
|
89
|
+
|
|
90
|
+
import { NodeFileSystem } from '@effect/platform-node'
|
|
91
|
+
|
|
92
|
+
export const NodeFileSystemWithWatch = NodeFileSystem.layer.pipe(Layer.provideMerge(ParcelWatcherLayer))
|
package/src/object/index.ts
CHANGED
package/src/object/omit.ts
CHANGED
|
@@ -9,7 +9,7 @@ export const omit = <Obj extends Record<string, any>, Keys extends keyof Obj>(
|
|
|
9
9
|
keys: Keys[],
|
|
10
10
|
): Omit<Obj, Keys> => {
|
|
11
11
|
return Object.keys(obj).reduce((acc, key: any) => {
|
|
12
|
-
if (
|
|
12
|
+
if (keys.includes(key) === false) {
|
|
13
13
|
acc[key] = (obj as any)[key]
|
|
14
14
|
}
|
|
15
15
|
return acc
|
|
@@ -11,10 +11,10 @@ export const stringifyObject = (obj: object, prefix = ''): string => {
|
|
|
11
11
|
for (const [key, value] of Object.entries(obj)) {
|
|
12
12
|
const fullKey = prefix !== '' ? `${prefix}.${key}` : key
|
|
13
13
|
|
|
14
|
-
if (typeof value === 'object' && value !== null &&
|
|
14
|
+
if (typeof value === 'object' && value !== null && Array.isArray(value) === false) {
|
|
15
15
|
// Recursively stringify nested objects with dot notation
|
|
16
16
|
entries.push(stringifyObject(value, fullKey))
|
|
17
|
-
} else if (Array.isArray(value)) {
|
|
17
|
+
} else if (Array.isArray(value) === true) {
|
|
18
18
|
// Arrays get converted to comma-separated values
|
|
19
19
|
entries.push(`${fullKey}=${value.join(',')}`)
|
|
20
20
|
} else {
|
package/src/qr.ts
CHANGED
|
@@ -61,7 +61,7 @@ export const printQrTerminal = (text: string, options?: PrintQrTerminalOptions):
|
|
|
61
61
|
const col = x - margin
|
|
62
62
|
if (row < 0 || col < 0 || row >= size || col >= size) return false
|
|
63
63
|
const bit = qr.isDark(row, col)
|
|
64
|
-
return invert ? !bit : bit
|
|
64
|
+
return invert === true ? !bit : bit
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
const lines: string[] = []
|
|
@@ -87,8 +87,8 @@ export const printQrTerminal = (text: string, options?: PrintQrTerminalOptions):
|
|
|
87
87
|
let currentStyle = ''
|
|
88
88
|
const setStyle = (style: string) => {
|
|
89
89
|
if (style !== currentStyle) {
|
|
90
|
-
if (currentStyle) row += RESET
|
|
91
|
-
if (style) row += style
|
|
90
|
+
if (currentStyle !== undefined) row += RESET
|
|
91
|
+
if (style !== undefined) row += style
|
|
92
92
|
currentStyle = style
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -97,26 +97,26 @@ export const printQrTerminal = (text: string, options?: PrintQrTerminalOptions):
|
|
|
97
97
|
const top = isDarkAt(x, y)
|
|
98
98
|
const bottom = isDarkAt(x, y + 1)
|
|
99
99
|
|
|
100
|
-
if (useAnsi) {
|
|
100
|
+
if (useAnsi === true) {
|
|
101
101
|
// Represent two stacked modules with a single `▀` character.
|
|
102
102
|
// Foreground corresponds to the top pixel; background to the bottom pixel.
|
|
103
103
|
// Always paint both halves to enforce a white quiet zone even on dark terminals.
|
|
104
|
-
const fg = top ? FG_BLACK : FG_WHITE
|
|
105
|
-
const bg = bottom ? BG_BLACK : BG_WHITE
|
|
104
|
+
const fg = top === true ? FG_BLACK : FG_WHITE
|
|
105
|
+
const bg = bottom === true ? BG_BLACK : BG_WHITE
|
|
106
106
|
setStyle(fg + bg)
|
|
107
107
|
row += '▀'
|
|
108
108
|
} else {
|
|
109
109
|
// No ANSI: approximate using block characters.
|
|
110
110
|
let ch = ' '
|
|
111
|
-
if (top && bottom) ch = '█'
|
|
112
|
-
else if (top &&
|
|
113
|
-
else if (
|
|
111
|
+
if (top === true && bottom === true) ch = '█'
|
|
112
|
+
else if (top === true && bottom === false) ch = '▀'
|
|
113
|
+
else if (top === false && bottom === true) ch = '▄'
|
|
114
114
|
row += ch
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
if (useAnsi) {
|
|
119
|
-
if (currentStyle) row += RESET
|
|
118
|
+
if (useAnsi === true) {
|
|
119
|
+
if (currentStyle !== undefined) row += RESET
|
|
120
120
|
}
|
|
121
121
|
lines.push(row)
|
|
122
122
|
}
|
package/src/set.ts
CHANGED
package/src/time.ts
CHANGED
|
@@ -17,7 +17,7 @@ export const msAsTimeString = (ms: number) => {
|
|
|
17
17
|
const remainingSeconds = (seconds % 60).toString().padStart(2, '0')
|
|
18
18
|
const remainingMinutes = hours > 0 ? (minutes % 60).toString().padStart(2, '0') : minutes % 60
|
|
19
19
|
|
|
20
|
-
const timeString = [hours > 0 ? `${hours}:` : '', `${remainingMinutes}:`,
|
|
20
|
+
const timeString = [hours > 0 ? `${hours}:` : '', `${remainingMinutes}:`, remainingSeconds]
|
|
21
21
|
.filter((val) => val !== '')
|
|
22
22
|
.join('')
|
|
23
23
|
|