@livestore/utils 0.4.0-dev.1 → 0.4.0-dev.10
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.json +1 -1
- package/dist/NoopTracer.d.ts.map +1 -1
- package/dist/NoopTracer.js +1 -0
- package/dist/NoopTracer.js.map +1 -1
- package/dist/effect/Effect.d.ts +9 -3
- package/dist/effect/Effect.d.ts.map +1 -1
- package/dist/effect/Effect.js +4 -2
- package/dist/effect/Effect.js.map +1 -1
- package/dist/effect/Error.d.ts +1 -1
- package/dist/effect/Error.js.map +1 -1
- package/dist/effect/Logger.d.ts +4 -1
- package/dist/effect/Logger.d.ts.map +1 -1
- package/dist/effect/Logger.js +12 -3
- package/dist/effect/Logger.js.map +1 -1
- package/dist/effect/OtelTracer.d.ts +5 -0
- package/dist/effect/OtelTracer.d.ts.map +1 -0
- package/dist/effect/OtelTracer.js +8 -0
- package/dist/effect/OtelTracer.js.map +1 -0
- package/dist/effect/RpcClient.d.ts +32 -0
- package/dist/effect/RpcClient.d.ts.map +1 -0
- package/dist/effect/RpcClient.js +149 -0
- package/dist/effect/RpcClient.js.map +1 -0
- package/dist/effect/Schema/index.d.ts +2 -2
- package/dist/effect/Schema/index.d.ts.map +1 -1
- package/dist/effect/Schema/index.js +12 -2
- package/dist/effect/Schema/index.js.map +1 -1
- package/dist/effect/Stream.d.ts +73 -2
- package/dist/effect/Stream.d.ts.map +1 -1
- package/dist/effect/Stream.js +68 -1
- package/dist/effect/Stream.js.map +1 -1
- package/dist/effect/Stream.test.d.ts +2 -0
- package/dist/effect/Stream.test.d.ts.map +1 -0
- package/dist/effect/Stream.test.js +84 -0
- package/dist/effect/Stream.test.js.map +1 -0
- package/dist/effect/SubscriptionRef.d.ts +2 -2
- package/dist/effect/SubscriptionRef.d.ts.map +1 -1
- package/dist/effect/SubscriptionRef.js +6 -1
- package/dist/effect/SubscriptionRef.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/WebSocket.js +1 -1
- package/dist/effect/WebSocket.js.map +1 -1
- package/dist/effect/index.d.ts +17 -11
- package/dist/effect/index.d.ts.map +1 -1
- package/dist/effect/index.js +20 -15
- package/dist/effect/index.js.map +1 -1
- package/dist/global.d.ts +1 -0
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js.map +1 -1
- package/dist/misc.js +1 -1
- package/dist/misc.js.map +1 -1
- package/dist/mod.d.ts +2 -0
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +4 -0
- package/dist/mod.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js +66 -10
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +177 -3
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +10 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +7 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +11 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +16 -0
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js +98 -2
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +1 -1
- package/dist/node/mod.d.ts +7 -1
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +10 -2
- package/dist/node/mod.js.map +1 -1
- package/package.json +42 -41
- package/src/NoopTracer.ts +1 -0
- package/src/effect/Effect.ts +31 -4
- package/src/effect/Error.ts +1 -1
- package/src/effect/Logger.ts +14 -4
- package/src/effect/OtelTracer.ts +11 -0
- package/src/effect/RpcClient.ts +212 -0
- package/src/effect/Schema/index.ts +17 -3
- package/src/effect/Stream.test.ts +127 -0
- package/src/effect/Stream.ts +111 -2
- package/src/effect/SubscriptionRef.ts +14 -2
- package/src/effect/WebChannel/common.ts +1 -1
- package/src/effect/WebSocket.ts +1 -1
- package/src/effect/index.ts +39 -14
- package/src/global.ts +1 -0
- package/src/misc.ts +1 -1
- package/src/mod.ts +9 -0
- package/src/node/ChildProcessRunner/ChildProcessRunner.ts +71 -10
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +258 -3
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +14 -1
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +14 -1
- package/src/node/ChildProcessRunner/ChildProcessWorker.ts +111 -3
- package/src/node/mod.ts +12 -5
- package/dist/effect/Schema/msgpack.d.ts +0 -3
- package/dist/effect/Schema/msgpack.d.ts.map +0 -1
- package/dist/effect/Schema/msgpack.js +0 -7
- package/dist/effect/Schema/msgpack.js.map +0 -1
- package/src/effect/Schema/msgpack.ts +0 -8
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Effect, Option, Stream } from 'effect'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { concatWithLastElement, runCollectReadonlyArray } from './Stream.ts'
|
|
4
|
+
|
|
5
|
+
describe('concatWithLastElement', () => {
|
|
6
|
+
it('should concatenate streams with access to last element of first stream', async () => {
|
|
7
|
+
const stream1 = Stream.make(1, 2, 3)
|
|
8
|
+
const result = concatWithLastElement(stream1, (lastElement) =>
|
|
9
|
+
lastElement.pipe(
|
|
10
|
+
Option.match({
|
|
11
|
+
onNone: () => Stream.make('no-previous'),
|
|
12
|
+
onSome: (last) => Stream.make(`last-was-${last}`, 'continuing'),
|
|
13
|
+
}),
|
|
14
|
+
),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const collected = await Effect.runPromise(runCollectReadonlyArray(result))
|
|
18
|
+
expect(collected).toEqual([1, 2, 3, 'last-was-3', 'continuing'])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should handle empty first stream', async () => {
|
|
22
|
+
const stream1 = Stream.empty
|
|
23
|
+
const result = concatWithLastElement(stream1, (lastElement) =>
|
|
24
|
+
lastElement.pipe(
|
|
25
|
+
Option.match({
|
|
26
|
+
onNone: () => Stream.make('no-previous-element'),
|
|
27
|
+
onSome: (last) => Stream.make(`last-was-${last}`),
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const collected = await Effect.runPromise(runCollectReadonlyArray(result))
|
|
33
|
+
expect(collected).toEqual(['no-previous-element'])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should handle single element first stream', async () => {
|
|
37
|
+
const stream1 = Stream.make('single')
|
|
38
|
+
const result = concatWithLastElement(stream1, (lastElement) =>
|
|
39
|
+
lastElement.pipe(
|
|
40
|
+
Option.match({
|
|
41
|
+
onNone: () => Stream.make('unexpected'),
|
|
42
|
+
onSome: (last) => Stream.make(`after-${last}`),
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const collected = await Effect.runPromise(runCollectReadonlyArray(result))
|
|
48
|
+
expect(collected).toEqual(['single', 'after-single'])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should handle empty second stream', async () => {
|
|
52
|
+
const stream1 = Stream.make(1, 2, 3)
|
|
53
|
+
const result = concatWithLastElement(stream1, () => Stream.empty)
|
|
54
|
+
|
|
55
|
+
const collected = await Effect.runPromise(runCollectReadonlyArray(result))
|
|
56
|
+
expect(collected).toEqual([1, 2, 3])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should preserve error handling from first stream', async () => {
|
|
60
|
+
const stream1 = Stream.fail('first-error')
|
|
61
|
+
const result = concatWithLastElement(stream1, () => Stream.make('should-not-reach'))
|
|
62
|
+
|
|
63
|
+
const outcome = await Effect.runPromise(Effect.either(runCollectReadonlyArray(result)))
|
|
64
|
+
expect(outcome._tag).toBe('Left')
|
|
65
|
+
if (outcome._tag === 'Left') {
|
|
66
|
+
expect(outcome.left).toBe('first-error')
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should preserve error handling from second stream', async () => {
|
|
71
|
+
const stream1 = Stream.make(1, 2)
|
|
72
|
+
const result = concatWithLastElement(stream1, () => Stream.fail('second-error'))
|
|
73
|
+
|
|
74
|
+
const outcome = await Effect.runPromise(Effect.either(runCollectReadonlyArray(result)))
|
|
75
|
+
expect(outcome._tag).toBe('Left')
|
|
76
|
+
if (outcome._tag === 'Left') {
|
|
77
|
+
expect(outcome.left).toBe('second-error')
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should work with different types in streams', async () => {
|
|
82
|
+
const stream1 = Stream.make(1, 2, 3)
|
|
83
|
+
const result = concatWithLastElement(stream1, (lastElement) =>
|
|
84
|
+
lastElement.pipe(
|
|
85
|
+
Option.match({
|
|
86
|
+
onNone: () => Stream.make('no-number') as Stream.Stream<number | string, never, never>,
|
|
87
|
+
onSome: (last) => Stream.make(last * 10, last * 100),
|
|
88
|
+
}),
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
const collected = await Effect.runPromise(runCollectReadonlyArray(result))
|
|
93
|
+
expect(collected).toEqual([1, 2, 3, 30, 300])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should handle async effects in streams', async () => {
|
|
97
|
+
const stream1 = Stream.fromEffect(Effect.succeed('async-value'))
|
|
98
|
+
const result = concatWithLastElement(stream1, (lastElement) =>
|
|
99
|
+
lastElement.pipe(
|
|
100
|
+
Option.match({
|
|
101
|
+
onNone: () => Stream.fromEffect(Effect.succeed('no-async')),
|
|
102
|
+
onSome: (last) => Stream.fromEffect(Effect.succeed(`processed-${last}`)),
|
|
103
|
+
}),
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const collected = await Effect.runPromise(runCollectReadonlyArray(result))
|
|
108
|
+
expect(collected).toEqual(['async-value', 'processed-async-value'])
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should work with dual function - piped style', async () => {
|
|
112
|
+
const stream1 = Stream.make('a', 'b', 'c')
|
|
113
|
+
const result = stream1.pipe(
|
|
114
|
+
concatWithLastElement((lastElement) =>
|
|
115
|
+
lastElement.pipe(
|
|
116
|
+
Option.match({
|
|
117
|
+
onNone: () => Stream.make('no-last'),
|
|
118
|
+
onSome: (last) => Stream.make(`last-${last}`, 'done'),
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
const collected = await Effect.runPromise(runCollectReadonlyArray(result))
|
|
125
|
+
expect(collected).toEqual(['a', 'b', 'c', 'last-c', 'done'])
|
|
126
|
+
})
|
|
127
|
+
})
|
package/src/effect/Stream.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
/** biome-ignore-all lint/suspicious/useIterableCallbackReturn: Biome bug */
|
|
1
2
|
export * from 'effect/Stream'
|
|
2
3
|
|
|
3
|
-
import type
|
|
4
|
-
import {
|
|
4
|
+
import { type Cause, Chunk, Effect, Option, pipe, Ref, Stream } from 'effect'
|
|
5
|
+
import { dual } from 'effect/Function'
|
|
5
6
|
|
|
6
7
|
export const tapLog = <R, E, A>(stream: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>
|
|
7
8
|
tapChunk<never, never, A, void>(Effect.forEach((_) => Effect.succeed(console.log(_))))(stream)
|
|
@@ -61,3 +62,111 @@ export const skipRepeated_ = <R, E, A>(
|
|
|
61
62
|
),
|
|
62
63
|
),
|
|
63
64
|
)
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns the first element of the stream or `None` if the stream is empty.
|
|
68
|
+
* It's different than `Stream.runHead` which runs the stream to completion.
|
|
69
|
+
* */
|
|
70
|
+
export const runFirst = <A, E, R>(stream: Stream.Stream<A, E, R>): Effect.Effect<Option.Option<A>, E, R> =>
|
|
71
|
+
stream.pipe(Stream.take(1), Stream.runCollect, Effect.map(Chunk.head))
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns the first element of the stream or throws a `NoSuchElementException` if the stream is empty.
|
|
75
|
+
* It's different than `Stream.runHead` which runs the stream to completion.
|
|
76
|
+
* */
|
|
77
|
+
export const runFirstUnsafe = <A, E, R>(
|
|
78
|
+
stream: Stream.Stream<A, E, R>,
|
|
79
|
+
): Effect.Effect<A, Cause.NoSuchElementException | E, R> => runFirst(stream).pipe(Effect.flatten)
|
|
80
|
+
|
|
81
|
+
export const runCollectReadonlyArray = <A, E, R>(stream: Stream.Stream<A, E, R>): Effect.Effect<readonly A[], E, R> =>
|
|
82
|
+
stream.pipe(Stream.runCollect, Effect.map(Chunk.toReadonlyArray))
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Concatenates two streams where the second stream has access to the last element
|
|
86
|
+
* of the first stream as an `Option`. If the first stream is empty, the callback
|
|
87
|
+
* receives `Option.none()`.
|
|
88
|
+
*
|
|
89
|
+
* @param stream - The first stream to consume
|
|
90
|
+
* @param getStream2 - Function that receives the last element from the first stream
|
|
91
|
+
* and returns the second stream to concatenate
|
|
92
|
+
* @returns A new stream containing all elements from both streams
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* // Direct usage
|
|
97
|
+
* const result = concatWithLastElement(
|
|
98
|
+
* Stream.make(1, 2, 3),
|
|
99
|
+
* lastElement => lastElement.pipe(
|
|
100
|
+
* Option.match({
|
|
101
|
+
* onNone: () => Stream.make('empty'),
|
|
102
|
+
* onSome: last => Stream.make(`last-was-${last}`)
|
|
103
|
+
* })
|
|
104
|
+
* )
|
|
105
|
+
* )
|
|
106
|
+
*
|
|
107
|
+
* // Piped usage
|
|
108
|
+
* const result = Stream.make(1, 2, 3).pipe(
|
|
109
|
+
* concatWithLastElement(lastElement =>
|
|
110
|
+
* Stream.make(lastElement.pipe(Option.getOrElse(() => 0)) * 10)
|
|
111
|
+
* )
|
|
112
|
+
* )
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export const concatWithLastElement: {
|
|
116
|
+
<A1, A2, E2, R2>(
|
|
117
|
+
getStream2: (lastElement: Option.Option<A1>) => Stream.Stream<A2, E2, R2>,
|
|
118
|
+
): <E1, R1>(stream: Stream.Stream<A1, E1, R1>) => Stream.Stream<A1 | A2, E1 | E2, R1 | R2>
|
|
119
|
+
<A1, E1, R1, A2, E2, R2>(
|
|
120
|
+
stream: Stream.Stream<A1, E1, R1>,
|
|
121
|
+
getStream2: (lastElement: Option.Option<A1>) => Stream.Stream<A2, E2, R2>,
|
|
122
|
+
): Stream.Stream<A1 | A2, E1 | E2, R1 | R2>
|
|
123
|
+
} = dual(
|
|
124
|
+
2,
|
|
125
|
+
<A1, E1, R1, A2, E2, R2>(
|
|
126
|
+
stream1: Stream.Stream<A1, E1, R1>,
|
|
127
|
+
getStream2: (lastElement: Option.Option<A1>) => Stream.Stream<A2, E2, R2>,
|
|
128
|
+
): Stream.Stream<A1 | A2, E1 | E2, R1 | R2> =>
|
|
129
|
+
pipe(
|
|
130
|
+
Ref.make<Option.Option<A1>>(Option.none()),
|
|
131
|
+
Stream.fromEffect,
|
|
132
|
+
Stream.flatMap((lastRef) =>
|
|
133
|
+
pipe(
|
|
134
|
+
stream1,
|
|
135
|
+
Stream.tap((value) => Ref.set(lastRef, Option.some(value))),
|
|
136
|
+
Stream.concat(pipe(Ref.get(lastRef), Effect.map(getStream2), Stream.unwrap)),
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Emits a default value if the stream is empty, otherwise passes through all elements.
|
|
144
|
+
* Uses `concatWithLastElement` internally to detect if the stream was empty.
|
|
145
|
+
*
|
|
146
|
+
* @param fallbackValue - The value to emit if the stream is empty
|
|
147
|
+
* @returns A dual function that can be used in pipe or direct call
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* // Direct usage
|
|
152
|
+
* const result = emitIfEmpty(Stream.empty, 'default')
|
|
153
|
+
* // Emits: 'default'
|
|
154
|
+
*
|
|
155
|
+
* // Piped usage
|
|
156
|
+
* const result = Stream.make(1, 2, 3).pipe(emitIfEmpty('fallback'))
|
|
157
|
+
* // Emits: 1, 2, 3
|
|
158
|
+
*
|
|
159
|
+
* const empty = Stream.empty.pipe(emitIfEmpty('fallback'))
|
|
160
|
+
* // Emits: 'fallback'
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export const emitIfEmpty: {
|
|
164
|
+
<A>(fallbackValue: A): <E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R>
|
|
165
|
+
<A, E, R>(stream: Stream.Stream<A, E, R>, fallbackValue: A): Stream.Stream<A, E, R>
|
|
166
|
+
} = dual(
|
|
167
|
+
2,
|
|
168
|
+
<A, E, R>(stream: Stream.Stream<A, E, R>, fallbackValue: A): Stream.Stream<A, E, R> =>
|
|
169
|
+
concatWithLastElement(stream, (lastElement) =>
|
|
170
|
+
lastElement._tag === 'None' ? Stream.make(fallbackValue) : Stream.empty,
|
|
171
|
+
),
|
|
172
|
+
)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Chunk, Effect, pipe, Stream } from 'effect'
|
|
1
|
+
import { Chunk, Effect, pipe, Stream, SubscriptionRef } from 'effect'
|
|
3
2
|
import { dual } from 'effect/Function'
|
|
4
3
|
import type { Predicate, Refinement } from 'effect/Predicate'
|
|
5
4
|
|
|
@@ -20,3 +19,16 @@ export const waitUntil: {
|
|
|
20
19
|
} = dual(2, <A>(sref: SubscriptionRef.SubscriptionRef<A>, predicate: (a: A) => boolean) =>
|
|
21
20
|
pipe(sref.changes, Stream.filter(predicate), Stream.take(1), Stream.runCollect, Effect.map(Chunk.unsafeHead)),
|
|
22
21
|
)
|
|
22
|
+
|
|
23
|
+
export const fromStream = <A>(stream: Stream.Stream<A>, initialValue: A) =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
const sref = yield* SubscriptionRef.make(initialValue)
|
|
26
|
+
|
|
27
|
+
yield* stream.pipe(
|
|
28
|
+
Stream.tap((a) => SubscriptionRef.set(sref, a)),
|
|
29
|
+
Stream.runDrain,
|
|
30
|
+
Effect.forkScoped,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return sref
|
|
34
|
+
})
|
|
@@ -15,7 +15,7 @@ export interface WebChannel<MsgListen, MsgSend, E = never> {
|
|
|
15
15
|
closedDeferred: Deferred.Deferred<void>
|
|
16
16
|
shutdown: Effect.Effect<void>
|
|
17
17
|
schema: { listen: Schema.Schema<MsgListen, any>; send: Schema.Schema<MsgSend, any> }
|
|
18
|
-
debugInfo?: Record<string, any>
|
|
18
|
+
debugInfo?: Record<string, any> | undefined
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export const DebugPingMessage = Schema.TaggedStruct('WebChannel.DebugPing', {
|
package/src/effect/WebSocket.ts
CHANGED
package/src/effect/index.ts
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import '../global.ts'
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export {
|
|
4
|
+
AiError,
|
|
5
|
+
LanguageModel,
|
|
6
|
+
LanguageModel as AiLanguageModel,
|
|
7
|
+
McpSchema,
|
|
8
|
+
McpServer,
|
|
9
|
+
Model,
|
|
10
|
+
Model as AiModel,
|
|
11
|
+
Prompt,
|
|
12
|
+
Tool,
|
|
13
|
+
Tool as AiTool,
|
|
14
|
+
Toolkit,
|
|
15
|
+
Toolkit as AiToolkit,
|
|
16
|
+
} from '@effect/ai'
|
|
17
|
+
// export { DevTools as EffectDevtools } from '@effect/experimental'
|
|
18
|
+
export { Sse } from '@effect/experimental'
|
|
19
|
+
export * as Otlp from '@effect/opentelemetry/Otlp'
|
|
4
20
|
export {
|
|
5
21
|
Command,
|
|
6
22
|
CommandExecutor,
|
|
@@ -8,6 +24,11 @@ export {
|
|
|
8
24
|
FetchHttpClient,
|
|
9
25
|
FileSystem,
|
|
10
26
|
Headers,
|
|
27
|
+
HttpApi,
|
|
28
|
+
HttpApiClient,
|
|
29
|
+
HttpApiEndpoint,
|
|
30
|
+
HttpApiGroup,
|
|
31
|
+
HttpApp,
|
|
11
32
|
HttpClient,
|
|
12
33
|
HttpClientError,
|
|
13
34
|
HttpClientRequest,
|
|
@@ -18,6 +39,7 @@ export {
|
|
|
18
39
|
HttpServerRequest,
|
|
19
40
|
HttpServerResponse,
|
|
20
41
|
KeyValueStore,
|
|
42
|
+
MsgPack,
|
|
21
43
|
Socket,
|
|
22
44
|
Terminal,
|
|
23
45
|
Transferable,
|
|
@@ -29,7 +51,8 @@ export {
|
|
|
29
51
|
export { BrowserWorker, BrowserWorkerRunner } from '@effect/platform-browser'
|
|
30
52
|
export {
|
|
31
53
|
Rpc,
|
|
32
|
-
RpcClient,
|
|
54
|
+
// RpcClient, // TODO bring back "original" RpcClient from effect/rpc
|
|
55
|
+
RpcClientError,
|
|
33
56
|
RpcGroup,
|
|
34
57
|
RpcMessage,
|
|
35
58
|
RpcMiddleware,
|
|
@@ -47,8 +70,8 @@ export {
|
|
|
47
70
|
Cause,
|
|
48
71
|
Channel,
|
|
49
72
|
Chunk,
|
|
50
|
-
// Logger,
|
|
51
73
|
Config,
|
|
74
|
+
ConfigError,
|
|
52
75
|
Console,
|
|
53
76
|
Context,
|
|
54
77
|
Data,
|
|
@@ -58,6 +81,7 @@ export {
|
|
|
58
81
|
Equal,
|
|
59
82
|
ExecutionStrategy,
|
|
60
83
|
Exit,
|
|
84
|
+
FastCheck,
|
|
61
85
|
Fiber,
|
|
62
86
|
FiberHandle,
|
|
63
87
|
FiberId,
|
|
@@ -98,6 +122,7 @@ export {
|
|
|
98
122
|
Runtime,
|
|
99
123
|
RuntimeFlags,
|
|
100
124
|
Scope,
|
|
125
|
+
Sink,
|
|
101
126
|
SortedMap,
|
|
102
127
|
STM,
|
|
103
128
|
SynchronizedRef,
|
|
@@ -107,26 +132,26 @@ export {
|
|
|
107
132
|
Tracer,
|
|
108
133
|
Types,
|
|
109
134
|
} from 'effect'
|
|
110
|
-
export {
|
|
135
|
+
export type { NonEmptyArray } from 'effect/Array'
|
|
136
|
+
export { constVoid, dual } from 'effect/Function'
|
|
137
|
+
export * as Graph from 'effect/Graph'
|
|
111
138
|
export { TreeFormatter } from 'effect/ParseResult'
|
|
112
139
|
export type { Serializable, SerializableWithResult } from 'effect/Schema'
|
|
113
|
-
|
|
114
140
|
export * as SchemaAST from 'effect/SchemaAST'
|
|
115
141
|
export * as BucketQueue from './BucketQueue.ts'
|
|
142
|
+
export * as Effect from './Effect.ts'
|
|
143
|
+
export * from './Error.ts'
|
|
116
144
|
export * as Logger from './Logger.ts'
|
|
145
|
+
export * as OtelTracer from './OtelTracer.ts'
|
|
146
|
+
export * as RpcClient from './RpcClient.ts'
|
|
147
|
+
export * as Schedule from './Schedule.ts'
|
|
148
|
+
export * as Scheduler from './Scheduler.ts'
|
|
117
149
|
export * as Schema from './Schema/index.ts'
|
|
150
|
+
export * as ServiceContext from './ServiceContext.ts'
|
|
118
151
|
export * as Stream from './Stream.ts'
|
|
119
152
|
export * as Subscribable from './Subscribable.ts'
|
|
120
153
|
export * as SubscriptionRef from './SubscriptionRef.ts'
|
|
121
154
|
export * as TaskTracing from './TaskTracing.ts'
|
|
122
155
|
export * as WebChannel from './WebChannel/mod.ts'
|
|
123
|
-
export * as WebSocket from './WebSocket.ts'
|
|
124
|
-
|
|
125
|
-
// export { DevTools as EffectDevtools } from '@effect/experimental'
|
|
126
|
-
|
|
127
|
-
export * as Effect from './Effect.ts'
|
|
128
|
-
export * from './Error.ts'
|
|
129
|
-
export * as Schedule from './Schedule.ts'
|
|
130
|
-
export * as Scheduler from './Scheduler.ts'
|
|
131
|
-
export * as ServiceContext from './ServiceContext.ts'
|
|
132
156
|
export * as WebLock from './WebLock.ts'
|
|
157
|
+
export * as WebSocket from './WebSocket.ts'
|
package/src/global.ts
CHANGED
package/src/misc.ts
CHANGED
package/src/mod.ts
CHANGED
|
@@ -234,4 +234,13 @@ export const isPromise = (value: any): value is Promise<unknown> => typeof value
|
|
|
234
234
|
|
|
235
235
|
export const isIterable = <T>(value: any): value is Iterable<T> => typeof value?.[Symbol.iterator] === 'function'
|
|
236
236
|
|
|
237
|
+
/** This utility "lies" as a means of compat with libs that don't explicitly type optionals as unioned with `undefined`. */
|
|
238
|
+
export const omitUndefineds = <T extends Record<keyof any, unknown>>(
|
|
239
|
+
rec: T,
|
|
240
|
+
): {
|
|
241
|
+
[K in keyof T]: Exclude<T[K], undefined>
|
|
242
|
+
} => {
|
|
243
|
+
return rec as never
|
|
244
|
+
}
|
|
245
|
+
|
|
237
246
|
export { objectToString as errorToString } from './misc.ts'
|
|
@@ -13,6 +13,53 @@ import * as Layer from 'effect/Layer'
|
|
|
13
13
|
import * as Runtime from 'effect/Runtime'
|
|
14
14
|
import * as Scope from 'effect/Scope'
|
|
15
15
|
|
|
16
|
+
// Parent death monitoring setup
|
|
17
|
+
let parentDeathDetectionEnabled = false
|
|
18
|
+
let parentDeathTimer: NodeJS.Timeout | null = null
|
|
19
|
+
|
|
20
|
+
const stopParentDeathMonitoring = () => {
|
|
21
|
+
parentDeathDetectionEnabled = false
|
|
22
|
+
if (parentDeathTimer) {
|
|
23
|
+
clearTimeout(parentDeathTimer)
|
|
24
|
+
parentDeathTimer = null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const setupParentDeathMonitoring = (parentPid: number) => {
|
|
29
|
+
if (parentDeathDetectionEnabled) return
|
|
30
|
+
parentDeathDetectionEnabled = true
|
|
31
|
+
|
|
32
|
+
let consecutiveFailures = 0
|
|
33
|
+
const maxFailures = 3 // Require 3 consecutive failures before self-terminating
|
|
34
|
+
|
|
35
|
+
// Check if parent is still alive every 2 seconds (more conservative)
|
|
36
|
+
const checkParentAlive = () => {
|
|
37
|
+
if (!parentDeathDetectionEnabled) return
|
|
38
|
+
try {
|
|
39
|
+
// Send signal 0 to check if process exists (doesn't actually send signal)
|
|
40
|
+
process.kill(parentPid, 0)
|
|
41
|
+
// If we reach here, parent is still alive, reset failure counter and check again later
|
|
42
|
+
consecutiveFailures = 0
|
|
43
|
+
parentDeathTimer = setTimeout(checkParentAlive, 2000)
|
|
44
|
+
} catch {
|
|
45
|
+
consecutiveFailures++
|
|
46
|
+
console.warn(`[Worker ${process.pid}] Parent check failed (${consecutiveFailures}/${maxFailures})`)
|
|
47
|
+
|
|
48
|
+
if (consecutiveFailures >= maxFailures) {
|
|
49
|
+
// Parent process has been gone for multiple checks, self-terminate
|
|
50
|
+
console.error(`[Worker ${process.pid}] Parent process ${parentPid} confirmed dead, self-terminating`)
|
|
51
|
+
process.exit(0)
|
|
52
|
+
} else {
|
|
53
|
+
// Try again sooner on failure
|
|
54
|
+
parentDeathTimer = setTimeout(checkParentAlive, 1000)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Start monitoring after a longer initial delay to let things settle
|
|
60
|
+
parentDeathTimer = setTimeout(checkParentAlive, 5000)
|
|
61
|
+
}
|
|
62
|
+
|
|
16
63
|
const platformRunnerImpl = Runner.PlatformRunner.of({
|
|
17
64
|
[Runner.PlatformRunnerTypeId]: Runner.PlatformRunnerTypeId,
|
|
18
65
|
start<I, O>(closeLatch: typeof CloseLatch.Service) {
|
|
@@ -43,18 +90,32 @@ const platformRunnerImpl = Runner.PlatformRunner.of({
|
|
|
43
90
|
Deferred.unsafeDone(closeLatch, Exit.die(exit.cause))
|
|
44
91
|
}
|
|
45
92
|
}
|
|
46
|
-
port.on('message', (message: Runner.BackingRunner.Message<I>) => {
|
|
93
|
+
port.on('message', (message: Runner.BackingRunner.Message<I> | any) => {
|
|
47
94
|
// console.log('message', message)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
95
|
+
|
|
96
|
+
// Handle parent death detection setup messages
|
|
97
|
+
if (Array.isArray(message) && message[0] === 'setup-parent-death-detection' && message[1]?.parentPid) {
|
|
98
|
+
const parentPid = message[1].parentPid
|
|
99
|
+
// console.log(`[Worker ${process.pid}] Setting up parent death detection for parent ${parentPid}`)
|
|
100
|
+
setupParentDeathMonitoring(parentPid)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Handle normal Effect worker messages
|
|
105
|
+
if (Array.isArray(message) && typeof message[0] === 'number') {
|
|
106
|
+
if (message[0] === 0) {
|
|
107
|
+
const result = handler(0, message[1])
|
|
108
|
+
if (Effect.isEffect(result)) {
|
|
109
|
+
const fiber = runFork(result)
|
|
110
|
+
fiber.addObserver(onExit)
|
|
111
|
+
FiberSet.unsafeAdd(fiberSet, fiber)
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
// Graceful shutdown requested by parent: stop monitoring and close port
|
|
115
|
+
stopParentDeathMonitoring()
|
|
116
|
+
Deferred.unsafeDone(closeLatch, Exit.void)
|
|
117
|
+
port.close()
|
|
54
118
|
}
|
|
55
|
-
} else {
|
|
56
|
-
Deferred.unsafeDone(closeLatch, Exit.void)
|
|
57
|
-
port.close()
|
|
58
119
|
}
|
|
59
120
|
})
|
|
60
121
|
port.on('messageerror', (cause) => {
|